Skip to content

Commit

Permalink
Many fixes after testing
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisTerBeke committed Jan 19, 2025
1 parent 53844bf commit 7af501d
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 69 deletions.
30 changes: 14 additions & 16 deletions drivers/uponor/device.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Device, DiscoveryResultMAC } from 'homey'
import { UponorHTTPClient } from '../../lib/UponorHTTPClient'
import { UponorDriver } from './driver'

const POLL_INTERVAL_MS = 1000 * 60 * 1
import { MEASURE_TEMPERATURE_CAPABILITY, TARGET_TEMPERATURE_CAPABILITY, POLL_INTERVAL_MS, INIT_TIMEOUT_MS } from '../../lib/constants'

class UponorThermostatDevice extends Device {

async onInit(): Promise<void> {
this.homey.setInterval(this._sync.bind(this), POLL_INTERVAL_MS)
this.homey.setTimeout(this._sync.bind(this), 2000)
await this._syncCapabilities()
this.homey.setInterval(this._syncAttributes.bind(this), POLL_INTERVAL_MS)
this.homey.setTimeout(this._syncAttributes.bind(this), INIT_TIMEOUT_MS)
}

onDiscoveryResult(discoveryResult: DiscoveryResultMAC): boolean {
Expand All @@ -33,18 +33,14 @@ class UponorThermostatDevice extends Device {
return await driver.setIpAddress(newAddress)
}

private async _sync(): Promise<void> {
await this._syncCapabilities()
await this._syncAttributes()
}

private async _syncCapabilities(): Promise<void> {
this._ensureCapabilityListener('target_temperature', this._setTargetTemperature.bind(this))
await this._ensureCapability(MEASURE_TEMPERATURE_CAPABILITY)
await this._ensureCapability(TARGET_TEMPERATURE_CAPABILITY, this._setTargetTemperature.bind(this))
}

private _ensureCapabilityListener(capability: string, callback: (value: any) => Promise<void>): void {
if (this.listenerCount(capability) === 1) return
this.registerCapabilityListener(capability, callback)
private async _ensureCapability(capability: string, callback: Device.CapabilityCallback | undefined = undefined): Promise<void> {
if (!this.hasCapability(capability)) await this.addCapability(capability)
if (callback) this.registerCapabilityListener(capability, callback)
}

private async _syncAttributes(): Promise<void> {
Expand All @@ -54,18 +50,20 @@ class UponorThermostatDevice extends Device {
const data = this._getClient().getThermostat(controllerID, thermostatID)
if (!data) return this.setUnavailable('Could not find thermostat data')
this.setAvailable()
this.setCapabilityValue('measure_temperature', data.temperature)
this.setCapabilityValue('target_temperature', data.setPoint)
this.setCapabilityValue(MEASURE_TEMPERATURE_CAPABILITY, data.temperature)
this.setCapabilityValue(TARGET_TEMPERATURE_CAPABILITY, data.setPoint)
} catch (error) {
this.homey.error(error)
this.setUnavailable('Could not fetch data from Uponor controller')
}
}

private async _setTargetTemperature(value: number): Promise<void> {
private async _setTargetTemperature(value: number, _opts: any): Promise<void> {
const { controllerID, thermostatID } = this.getData()
try {
await this._getClient().setTargetTemperature(controllerID, thermostatID, value)
} catch (error) {
this.homey.error(error)
this.setUnavailable('Could not send data to Uponor controller')
}
}
Expand Down
69 changes: 40 additions & 29 deletions drivers/uponor/driver.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,81 @@
import { Driver } from 'homey'
import { Thermostat, UponorHTTPClient } from '../../lib/UponorHTTPClient'
import { PairSession } from 'homey/lib/Driver'
import {
IP_ADDRESS_SETTINGS_KEY,
CUSTOM_IP_ADDRESS_SETTINGS_KEY,
DEBUG_DEVICES_SETTINGS_KEY,
LIST_DEVICES_PAIR_KEY,
CUSTOM_IP_ADDRESS_PAIR_KEY,
} from '../../lib/constants'

export class UponorDriver extends Driver {

private _client?: UponorHTTPClient

getClient(): UponorHTTPClient {
const ip_address = this.getIpAddress()
if (!ip_address) throw new Error('IP address not discovered or set during pairing')
if (!this._client) this._client = new UponorHTTPClient(ip_address)
getClient(address: string | null = null): UponorHTTPClient {
address = address || this.getIpAddress()
if (!address) throw new Error('IP address not discovered or set during pairing')
if (!this._client) this._client = new UponorHTTPClient(address)
return this._client
}

getIpAddress(): string | undefined {
return this.homey.settings.get('ip_address')
getIpAddress(): string {
const address = this.getCustomIpAddress() || this.homey.settings.get(IP_ADDRESS_SETTINGS_KEY)
if (!address) throw new Error('IP address not discovered or set during pairing')
return address
}

async setIpAddress(ipAddress: string): Promise<boolean> {
return await this.getClient().updateAddress(ipAddress)
async setIpAddress(address: string): Promise<boolean> {
this.homey.settings.set(IP_ADDRESS_SETTINGS_KEY, address)
return await this.getClient(address).updateAddress(address)
}

async onPair(session: PairSession): Promise<void> {
session.setHandler('custom_ip_address', this._setCustomIpAddress.bind(this))
session.setHandler('list_devices', this._listDevices.bind(this))
getCustomIpAddress(): string {
return this.homey.settings.get(CUSTOM_IP_ADDRESS_SETTINGS_KEY)
}

private async _setCustomIpAddress(address: string): Promise<void> {
this.homey.settings.set('ip_address', address)
return this.homey.settings.set(CUSTOM_IP_ADDRESS_SETTINGS_KEY, address)
}

async onPair(session: PairSession): Promise<void> {
this.homey.settings.unset(CUSTOM_IP_ADDRESS_SETTINGS_KEY)
session.setHandler(CUSTOM_IP_ADDRESS_PAIR_KEY, this._setCustomIpAddress.bind(this))
session.setHandler(LIST_DEVICES_PAIR_KEY, this._listDevices.bind(this))
}

private async _listDevices(): Promise<any[]> {

// when a custom IP address is set, only return devices for that address
const ip_address = this.getIpAddress()
if (ip_address) {
return await this._findDevices(ip_address, `custom_${new Date().getTime()}`)
}
const custom_address = this.getCustomIpAddress()
if (custom_address) return await this._findDevices(custom_address, `custom_${new Date().getTime()}`)

// otherwise discover devices on the network and use the first one found
const discoveryStrategy = this.getDiscoveryStrategy()
const discoveryResults = discoveryStrategy.getDiscoveryResults()
const controller = Object.values(discoveryResults).pop()
if (controller) {
this.setIpAddress(controller.address)
return await this._findDevices(controller.address, controller.id)
}
if (!controller) return []

return []
this.setIpAddress(controller.address)
return await this._findDevices(controller.address, controller.id)
}

private async _findDevices(ipAddress: string, systemID: string): Promise<any[]> {
private async _findDevices(address: string, systemID: string): Promise<any[]> {
try {
const connected = await this.getClient().testConnection()
if (!connected) return []
const success = await this.getClient(address).updateAddress(address)
if (!success) throw new Error(`Could not connect to Uponor controller at IP address ${address}`)
await this.getClient().syncAttributes()
const debug = await this.getClient().debug()
this.homey.settings.set('debug_devices', JSON.stringify(debug))
this.homey.settings.set(DEBUG_DEVICES_SETTINGS_KEY, JSON.stringify(debug))
const thermostats = Array.from(this.getClient().getThermostats().values())
return thermostats.map(this._mapDevice.bind(this, ipAddress, systemID))
return thermostats.map(this._mapDevice.bind(this, address, systemID))
} catch (error) {
this.homey.error(error)
return []
}
}

private _mapDevice(ipAddress: string, systemID: string, thermostat: Thermostat): any {
private _mapDevice(address: string, systemID: string, thermostat: Thermostat): any {
return {
name: thermostat.name,
data: {
Expand All @@ -73,7 +84,7 @@ export class UponorDriver extends Driver {
thermostatID: thermostat.thermostatID,
},
store: {
address: ipAddress,
address: address,
}
}
}
Expand Down
13 changes: 8 additions & 5 deletions lib/UponorHTTPClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ export class UponorHTTPClient {
}

public async syncAttributes(): Promise<void> {
await this._syncRawAttributes()
await this._parseAttributes()
const syncedRaw = await this._syncRawAttributes()
if (!syncedRaw) return console.error('Could not sync raw attributes')
const parsed = await this._parseAttributes()
if (!parsed) return console.error('Could not parse attributes')
await this._syncThermostats()
}

Expand Down Expand Up @@ -104,10 +106,11 @@ export class UponorHTTPClient {
}
}

private async _parseAttributes(): Promise<Map<string, string>> {
private async _parseAttributes(): Promise<boolean> {
const data = this._rawAttributes as AttributesResponse
if (data.result != 'OK') return Promise.reject(data.result)
return new Map(data.output.vars.map(v => [v.waspVarName, v.waspVarValue]))
if (data && data.result != 'OK') return false
this._attributes = new Map(data.output.vars.map(v => [v.waspVarName, v.waspVarValue]))
return true
}

private async _syncThermostats(): Promise<void> {
Expand Down
9 changes: 9 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const IP_ADDRESS_SETTINGS_KEY = 'ip_address'
export const CUSTOM_IP_ADDRESS_SETTINGS_KEY = 'custom_ip_address'
export const DEBUG_DEVICES_SETTINGS_KEY = 'debug_devices'
export const CUSTOM_IP_ADDRESS_PAIR_KEY = 'custom_ip_address'
export const LIST_DEVICES_PAIR_KEY = 'list_devices'
export const MEASURE_TEMPERATURE_CAPABILITY = 'measure_temperature'
export const TARGET_TEMPERATURE_CAPABILITY = 'target_temperature'
export const POLL_INTERVAL_MS = 1000 * 60 * 1 // 1 minute
export const INIT_TIMEOUT_MS = 1000 * 2 // 2 seconds
6 changes: 4 additions & 2 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"custom_ip_address_warning": "Automatic discovery is recommended to prevent issues when the IP address of the controller changes."
},
"settings": {
"title": "Settings",
"debug_devices_label": "Debug: devices"
"info_title": "Info",
"ip_address_label": "Discovered IP address",
"custom_ip_address_label": "Custom pairing IP address",
"debug_devices_label": "Debug data"
}
}
58 changes: 41 additions & 17 deletions settings/index.html
Original file line number Diff line number Diff line change
@@ -1,26 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="/homey.js" data-origin="settings"></script>
<script type="text/javascript">
function onHomeyReady(Homey) {
Homey.ready()
<script type="text/javascript" src="/homey.js" data-origin="settings"></script>
<script type="text/javascript">
function onHomeyReady(Homey) {
Homey.ready()

Homey.get('debug_devices', function (err, debug_devices) {
if (err) return Homey.alert(err)
document.getElementById('debug_devices').value = debug_devices
})
}
</script>
var ipAddressInput = document.getElementById('ip_address')
var customIpAdressInput = document.getElementById('custom_ip_address')
var debugDevicesInput = document.getElementById('debug_devices')

Homey.get('ip_address', function (err, ip_address) {
if (err) return Homey.alert(err)
ipAddressInput.value = ip_address
})

Homey.get('custom_ip_address', function (err, ip_address) {
if (err) return Homey.alert(err)
customIpAdressInput.value = ip_address
})

Homey.get('debug_devices', function (err, debug_devices) {
if (err) return Homey.alert(err)
debugDevicesInput.value = debug_devices
})
}
</script>
</head>
<body>
<header class="homey-header">
<h1 class="homey-title" data-i18n="settings.title"></h1>
</header>
<fieldset class="homey-form-fieldset">
<legend class="homey-form-legend" data-i18n="settings.info_title"></legend>

<div class="homey-form-group">
<label class="homey-form-label" data-i18n="settings.ip_address_label"></label>
<input class="homey-form-input" id="ip_address" type="text" disabled></input>
</div>

<div class="homey-form-group">
<label class="homey-form-label" data-i18n="settings.custom_ip_address_label"></label>
<input class="homey-form-input" id="custom_ip_address" type="text"></input>
</div>

<div class="homey-form-group">
<label class="homey-form-label" data-i18n="settings.debug_devices_label"></label>
<textarea class="homey-form-textarea" id="debug_devices"></textarea>
</div>
<div class="homey-form-group">
<label class="homey-form-label" data-i18n="settings.debug_devices_label"></label>
<textarea class="homey-form-textarea" id="debug_devices" disabled></textarea>
</div>
</fieldset>
</body>
</html>

0 comments on commit 7af501d

Please sign in to comment.