Skip to content

Commit

Permalink
Allow custom IP during pairing, robustness in discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisTerBeke committed Apr 25, 2024
1 parent 8760c18 commit 314cd34
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 42 deletions.
6 changes: 3 additions & 3 deletions .homeycompose/discovery/uponor.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"mac": {
"manufacturer": [
[
54,
206,
75
40,
245,
55
]
]
},
Expand Down
14 changes: 10 additions & 4 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
"large": "/drivers/uponor/assets/images/large.png"
},
"pair": [
{
"id": "start",
"navigation": {
"next": "list_devices"
}
},
{
"id": "list_devices",
"template": "list_devices",
Expand All @@ -77,7 +83,7 @@
"label": {
"en": "IP Address"
},
"type": "label",
"type": "text",
"required": false,
"hint": {
"en": "The IP address of the device."
Expand All @@ -92,9 +98,9 @@
"mac": {
"manufacturer": [
[
54,
206,
75
40,
245,
55
]
]
},
Expand Down
36 changes: 27 additions & 9 deletions drivers/uponor/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ class UponorThermostatDevice extends Device {
}

async onAdded(): Promise<void> {
const { address } = this.getSettings()
await this._init(address)
await this._init()
}

async onUninit(): Promise<void> {
Expand All @@ -29,28 +28,47 @@ class UponorThermostatDevice extends Device {
}

async onDiscoveryAvailable(discoveryResult: DiscoveryResultMAC) {
await this._init(discoveryResult.address)
await this._updateAddress(discoveryResult.address)
}

async onDiscoveryAddressChanged(discoveryResult: DiscoveryResultMAC): Promise<void> {
await this._init(discoveryResult.address)
await this._updateAddress(discoveryResult.address)
}

async onDiscoveryLastSeenChanged(discoveryResult: DiscoveryResultMAC): Promise<void> {
await this._init(discoveryResult.address)
await this._updateAddress(discoveryResult.address)
}

async onDeleted(): Promise<void> {
await this._uninit()
}

async _init(newAddress: string): Promise<void> {
// TODO: validate new IP address is correct before updating everything (ARP is not always reliable)
private _getAddress(): string {
const settingAddress = this.getSetting('address')
if (settingAddress) return settingAddress
const storeAddress = this.getStoreValue('address')
if (storeAddress) return storeAddress
return ""
}

private async _updateAddress(newAddress: string) {
const client = new UponorHTTPClient(newAddress)
const connected = await client.testConnection()
if (!connected) {
await this.setUnavailable(`Could not find Uponor controller on IP address ${newAddress}`)
return
}
await this.setStoreValue('address', newAddress)
await this._init()
}

async _init(): Promise<void> {
await this._uninit()
await this.setSettings({ address: newAddress })
this._client = new UponorHTTPClient(newAddress)
const address = this._getAddress()
this._client = new UponorHTTPClient(address)
this._syncInterval = setInterval(this._syncAttributes.bind(this), POLL_INTERVAL_MS)
await this._syncAttributes()
await this.setAvailable()
}

async _uninit() {
Expand Down
8 changes: 7 additions & 1 deletion drivers/uponor/driver.compose.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
"large": "{{driverAssetsPath}}/images/large.png"
},
"pair": [
{
"id": "start",
"navigation": {
"next": "list_devices"
}
},
{
"id": "list_devices",
"template": "list_devices",
Expand All @@ -41,7 +47,7 @@
"label": {
"en": "IP Address"
},
"type": "label",
"type": "text",
"required": false,
"hint": {
"en": "The IP address of the device."
Expand Down
69 changes: 47 additions & 22 deletions drivers/uponor/driver.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
import { Driver } from 'homey'
import { Thermostat, UponorHTTPClient } from '../../lib/UponorHTTPClient'
import { UponorHTTPClient } from '../../lib/UponorHTTPClient'
import { PairSession } from 'homey/lib/Driver';

class UponorDriver extends Driver {

async onPairListDevices(): Promise<any[]> {
const discoveryStrategy = this.getDiscoveryStrategy()
const discoveryResults = discoveryStrategy.getDiscoveryResults()
private _customIpAddress: string | undefined

async onPair(session: PairSession): Promise<void> {

const driver = this

session.setHandler('custom_ip_address', async function (address: string) {
driver._customIpAddress = address
})

session.setHandler('list_devices', async function () {
const discoveryStrategy = driver.getDiscoveryStrategy()
const discoveryResults = discoveryStrategy.getDiscoveryResults()

for await (let discoveryResult of Object.values(discoveryResults)) {
return await driver._findDevices(discoveryResult.address, discoveryResult.id)
}

if (driver._customIpAddress) {
// TODO: find actual MAC address for custom IP
return await driver._findDevices(driver._customIpAddress, 'custom')
}

return []
})
}

private async _findDevices(ip: string, mac: string): Promise<any[]> {
const devices: any[] = []
const client = new UponorHTTPClient(ip)
const connected = await client.testConnection()
if (!connected) return devices

for await (let discoveryResult of Object.values(discoveryResults)) {
const client = new UponorHTTPClient(discoveryResult.address)
await client.syncAttributes()
const thermostats = client.getThermostats()
thermostats.forEach((thermostat: Thermostat) => {
devices.push({
name: thermostat.name,
data: {
id: `${discoveryResult.id}_${thermostat.id}`,
MACAddress: discoveryResult.id,
controllerID: thermostat.controllerID,
thermostatID: thermostat.thermostatID,
},
settings: {
address: discoveryResult.address,
}
})
await client.syncAttributes()
client.getThermostats().forEach((thermostat) => {
devices.push({
name: thermostat.name,
data: {
id: `${mac}_${thermostat.id}`,
MACAddress: mac,
controllerID: thermostat.controllerID,
thermostatID: thermostat.thermostatID,
},
store: {
address: ip,
}
})
}
})

return devices
}
Expand Down
25 changes: 25 additions & 0 deletions drivers/uponor/pair/start.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script type="application/javascript">
Homey.setTitle("Uponor setup")
Homey.setSubtitle("Select the pairing method")

function setCustomIP() {
var address = document.getElementById("address").value
Homey.emit("custom_ip_address", address)
}
</script>

<p>
Automated discovery mode scans for known Uponor devices in the local network.
This does not always work depending on the network configuration.
If you do not see your Uponor devices in the list, please try entering the IP address before continuing.
</p>


<form class="homey-form">
<fieldset class="homey-form-fieldset">
<div class="homey-form-group">
<label class="homey-form-label" for="address">IP Address</label>
<input class="homey-form-input" id="address" type="text" value="" onblur="setCustomIP()" />
</div>
</fieldset>
</form>
14 changes: 11 additions & 3 deletions lib/UponorHTTPClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class UponorHTTPClient {
this._url = `http://${ip_address}/JNAP/`
}


public getAttributes(): Map<string, string> {
return this._attributes
}
Expand All @@ -56,12 +57,19 @@ export class UponorHTTPClient {
this._thermostats = this._syncThermostats()
}

public async testConnection(): Promise<boolean> {
const request = await fetch(this._url, {
method: 'POST',
headers: { 'x-jnap-action': 'http://phyn.com/jnap/uponorsky/GetAttributes' },
body: '{}'
})
return request.status == 200
}

private async _syncAttributes(): Promise<Map<string, string>> {
const request = await fetch(this._url, {
method: 'POST',
headers: {
'x-jnap-action': 'http://phyn.com/jnap/uponorsky/GetAttributes'
},
headers: { 'x-jnap-action': 'http://phyn.com/jnap/uponorsky/GetAttributes' },
body: '{}'
})
const data: AttributesResponse = await request.json() as AttributesResponse
Expand Down

0 comments on commit 314cd34

Please sign in to comment.