diff --git a/.homeycompose/discovery/uponor.json b/.homeycompose/discovery/uponor.json index 4481b5f..095f548 100644 --- a/.homeycompose/discovery/uponor.json +++ b/.homeycompose/discovery/uponor.json @@ -3,9 +3,9 @@ "mac": { "manufacturer": [ [ - 54, - 206, - 75 + 40, + 245, + 55 ] ] }, diff --git a/app.json b/app.json index 64d1fef..1200bb3 100644 --- a/app.json +++ b/app.json @@ -56,6 +56,12 @@ "large": "/drivers/uponor/assets/images/large.png" }, "pair": [ + { + "id": "start", + "navigation": { + "next": "list_devices" + } + }, { "id": "list_devices", "template": "list_devices", @@ -77,7 +83,7 @@ "label": { "en": "IP Address" }, - "type": "label", + "type": "text", "required": false, "hint": { "en": "The IP address of the device." @@ -92,9 +98,9 @@ "mac": { "manufacturer": [ [ - 54, - 206, - 75 + 40, + 245, + 55 ] ] }, diff --git a/drivers/uponor/device.ts b/drivers/uponor/device.ts index f88f62d..a8793e7 100644 --- a/drivers/uponor/device.ts +++ b/drivers/uponor/device.ts @@ -16,8 +16,7 @@ class UponorThermostatDevice extends Device { } async onAdded(): Promise { - const { address } = this.getSettings() - await this._init(address) + await this._init() } async onUninit(): Promise { @@ -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 { - await this._init(discoveryResult.address) + await this._updateAddress(discoveryResult.address) } async onDiscoveryLastSeenChanged(discoveryResult: DiscoveryResultMAC): Promise { - await this._init(discoveryResult.address) + await this._updateAddress(discoveryResult.address) } async onDeleted(): Promise { await this._uninit() } - async _init(newAddress: string): Promise { - // 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 { 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() { diff --git a/drivers/uponor/driver.compose.json b/drivers/uponor/driver.compose.json index 56b55cb..8eefdb9 100644 --- a/drivers/uponor/driver.compose.json +++ b/drivers/uponor/driver.compose.json @@ -20,6 +20,12 @@ "large": "{{driverAssetsPath}}/images/large.png" }, "pair": [ + { + "id": "start", + "navigation": { + "next": "list_devices" + } + }, { "id": "list_devices", "template": "list_devices", @@ -41,7 +47,7 @@ "label": { "en": "IP Address" }, - "type": "label", + "type": "text", "required": false, "hint": { "en": "The IP address of the device." diff --git a/drivers/uponor/driver.ts b/drivers/uponor/driver.ts index e2d6e5f..8d7c07d 100644 --- a/drivers/uponor/driver.ts +++ b/drivers/uponor/driver.ts @@ -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 { - const discoveryStrategy = this.getDiscoveryStrategy() - const discoveryResults = discoveryStrategy.getDiscoveryResults() + private _customIpAddress: string | undefined + + async onPair(session: PairSession): Promise { + + 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 { 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 } diff --git a/drivers/uponor/pair/start.html b/drivers/uponor/pair/start.html new file mode 100644 index 0000000..e88e696 --- /dev/null +++ b/drivers/uponor/pair/start.html @@ -0,0 +1,25 @@ + + +

+ 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. +

+ + +
+
+
+ + +
+
+
diff --git a/lib/UponorHTTPClient.ts b/lib/UponorHTTPClient.ts index 3de14c1..1305d45 100644 --- a/lib/UponorHTTPClient.ts +++ b/lib/UponorHTTPClient.ts @@ -34,6 +34,7 @@ export class UponorHTTPClient { this._url = `http://${ip_address}/JNAP/` } + public getAttributes(): Map { return this._attributes } @@ -56,12 +57,19 @@ export class UponorHTTPClient { this._thermostats = this._syncThermostats() } + public async testConnection(): Promise { + 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> { 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