Skip to content

Commit

Permalink
Implement temperature setpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisTerBeke committed Apr 24, 2024
1 parent 6aed986 commit 57e9100
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 78 deletions.
3 changes: 2 additions & 1 deletion app.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
},
"class": "thermostat",
"capabilities": [
"measure_temperature"
"measure_temperature",
"target_temperature"
],
"platforms": [
"local"
Expand Down
34 changes: 24 additions & 10 deletions drivers/uponor/device.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Device } from 'homey'
import { UponorHTTPClient } from '../../lib/UponorHTTPClient'
import { UponorHTTPClient, Mode } from '../../lib/UponorHTTPClient'
// import { DiscoveryResultMAC } from 'homey/lib/DiscoveryStrategy'

// sync thermostat every minute
Expand All @@ -15,10 +15,12 @@ class UponorThermostatDevice extends Device {
// }

async onInit() {
const { ip_address, controller_id, thermostat_id } = this.getData()
this._client = new UponorHTTPClient(ip_address)
this._syncInterval = setInterval(this._syncAttributes.bind(this, controller_id, thermostat_id), POLL_INTERVAL_MS)
await this._syncAttributes(controller_id, thermostat_id)
const { IPAddress } = this.getData()
this._client = new UponorHTTPClient(IPAddress)
this._syncInterval = setInterval(this._syncAttributes.bind(this), POLL_INTERVAL_MS)
this.registerCapabilityListener('target_temperature', this._setTargetTemperature.bind(this))
// this.registerCapabilityListener('thermostat_mode', this._setThermostatMode.bind(this))
await this._syncAttributes()
}

async onDeleted(): Promise<void> {
Expand All @@ -27,13 +29,25 @@ class UponorThermostatDevice extends Device {
this._client = undefined
}

private async _syncAttributes(controller_id: number, thermostat_id: number) {
private async _syncAttributes() {
await this._client?.syncAttributes()
const data = this._client?.getThermostat(controller_id, thermostat_id)

const { controllerID, thermostatID } = this.getData()
const data = this._client?.getThermostat(controllerID, thermostatID)
this.setCapabilityValue('measure_temperature', data?.temperature)
// this.setCapabilityValue('target_temperature', data?.setPoint)
// this.setCapabilityValue('thermostat_mode', 'auto')
this.setCapabilityValue('target_temperature', data?.setPoint)
// this.setCapabilityValue('thermostat_mode', data?.mode)
}

private async _setTargetTemperature(value: number) {
const { controllerID, thermostatID } = this.getData()
await this._client?.setTargetTemperature(controllerID, thermostatID, value)
await this._syncAttributes()
}

private async _setThermostatMode(value: Mode) {
const { controllerID, thermostatID } = this.getData()
await this._client?.setMode(controllerID, thermostatID, value)
await this._syncAttributes()
}
}

Expand Down
3 changes: 2 additions & 1 deletion drivers/uponor/driver.compose.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
},
"class": "thermostat",
"capabilities": [
"measure_temperature"
"measure_temperature",
"target_temperature"
],
"platforms": [
"local"
Expand Down
18 changes: 9 additions & 9 deletions drivers/uponor/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@ class UponorDriver extends Driver {
// const discoveryStrategy = this.getDiscoveryStrategy()
// const discoveryResults = discoveryStrategy.getDiscoveryResults()
// console.log('discoveryResults', discoveryResults)

const client = new UponorHTTPClient('192.168.2.17')
await client.syncAttributes()

const devices: any[] = []
const thermostats = client.getThermostats()
console.log('thermostats', thermostats)

return thermostats.map((thermostat: Thermostat) => {
return {
thermostats.forEach((thermostat: Thermostat) => {
devices.push({
name: thermostat.name,
data: {
id: thermostat.id,
name: thermostat.name,
ip_address: '192.168.2.17',
controller_id: thermostat.controller_id,
thermostat_id: thermostat.thermostat_id,
IPAddress: '192.168.2.17',
controllerID: thermostat.controllerID,
thermostatID: thermostat.thermostatID,
},
}
})
})

return devices
}
}

Expand Down
131 changes: 74 additions & 57 deletions lib/UponorHTTPClient.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import fetch from 'node-fetch'

export type Mode = 'auto' | 'heat' | 'cool' | 'off'

export type Thermostat = {
id: string | undefined
controller_id: number | undefined
thermostat_id: number | undefined
controllerID: number | undefined
thermostatID: number | undefined
name: string | undefined
temperature: number | undefined
setPoint: number | undefined
mode: Mode | undefined
}

type AttributesResponse = {
Expand All @@ -23,6 +26,7 @@ export class UponorHTTPClient {

private _url: string
private _attributes: Map<string, string> = new Map()
private _thermostats: Map<string, Thermostat> = new Map()

constructor(ip_address: string) {
this._url = `http://${ip_address}/JNAP/`
Expand All @@ -36,91 +40,104 @@ export class UponorHTTPClient {
return this._attributes.get(name)
}

public getThermostats(): Thermostat[] {
public getThermostats(): Map<string, Thermostat> {
return this._thermostats
}

public getThermostat(controllerID: number, thermostatID: number): Thermostat | undefined {
const ctKey = UponorHTTPClient._createKey(controllerID, thermostatID)
return this._thermostats.get(ctKey)
}

public async syncAttributes(): Promise<void> {
this._attributes = await this._syncAttributes()
this._thermostats = this._syncThermostats()
}

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'
},
body: '{}'
})
const data: AttributesResponse = await request.json() as AttributesResponse
if (data.result != 'OK') {
return Promise.reject(data.result)
}

const result = new Map<string, string>()
for (const v of data.output.vars) {
result.set(v.waspVarName, v.waspVarValue)
}

return result
}

private _syncThermostats(): Map<string, Thermostat> {
const attributes = this.getAttributes()
const thermostats: Thermostat[] = []
const thermostats: Map<string, Thermostat> = new Map()

// TODO: only do this once on init
attributes.forEach((value, key) => {

const regex = /cust_C(\d+)_T(\d+)_name/
const matches = regex.exec(key)
if (!matches) {
return
}
if (!matches) return
const controllerID = matches[1] // first capture group
const thermostatID = matches[2] // second capture group
const ctKey = UponorHTTPClient._createKey(controllerID, thermostatID)

const controller_id = matches[1] // first capture group
const thermostat_id = matches[2] // second capture group
const ctKey = `C${controller_id}_T${thermostat_id}`

thermostats.push({
thermostats.set(ctKey, {
id: ctKey,
name: value,
controller_id: parseInt(controller_id),
thermostat_id: parseInt(thermostat_id),
temperature: this._formatTemperature(this.getAttribute(`${ctKey}_room_temperature`)),
setPoint: this._formatTemperature(this.getAttribute(`${ctKey}_setpoint`)),
controllerID: parseInt(controllerID),
thermostatID: parseInt(thermostatID),
temperature: UponorHTTPClient._formatTemperature(this.getAttribute(`${ctKey}_room_temperature`)),
setPoint: UponorHTTPClient._formatTemperature(this.getAttribute(`${ctKey}_setpoint`)),
mode: 'auto', // TODO: calculate mode using heat/cool/eco/holiday/comfort mode attributes
})
})

return thermostats
}

public getThermostat(controller_id: number, thermostat_id: number): Thermostat {
const ctKey = `C${controller_id}_T${thermostat_id}`
return {
id: ctKey,
name: this.getAttribute(`cust_${ctKey}_name`),
controller_id: controller_id,
thermostat_id: thermostat_id,
temperature: this._formatTemperature(this.getAttribute(`${ctKey}_room_temperature`)),
setPoint: this._formatTemperature(this.getAttribute(`${ctKey}_setpoint`)),
}
public async setTargetTemperature(controllerID: number, thermostatID: number, value: number): Promise<void> {
const setPoint = Math.round(((value * 9 / 5) + 32) * 10).toString()
await this._setAttribute(`C${controllerID}_T${thermostatID}_setpoint`, setPoint)
}

public async syncAttributes(): Promise<void> {
this._attributes = await this._getAllAttributes()
public async setMode(controllerID: number, thermostatID: number, value: Mode): Promise<void> {
// TODO: convert value to correct heat/cool/eco/holiday/comfort attributes
// await this._setAttribute("", "")
}

private _formatTemperature(input: string | undefined): number {
const fahrenheit = parseFloat(input || '0') / 10
return Math.round((fahrenheit - 32) * 5 / 9)
}

private async _getAllAttributes(): Promise<Map<string, string>> {
private async _setAttribute(key: string, value: string): Promise<void> {
const body = JSON.stringify({
"vars": [
{ "waspVarName": key, "waspVarValue": value },
]
})
const request = await fetch(this._url, {
method: 'POST',
headers: {
'x-jnap-action': 'http://phyn.com/jnap/uponorsky/GetAttributes'
'x-jnap-action': 'http://phyn.com/jnap/uponorsky/SetAttributes'
},
body: '{}'
body: body,
})
const data: AttributesResponse = await request.json() as AttributesResponse

if (data.result != 'OK') {
return Promise.reject(data.result)
}

const result = new Map<string, string>()
for (const v of data.output.vars) {
result.set(v.waspVarName, v.waspVarValue)
}

return result
await this._syncAttributes()
}

private async _setAttributes() {
// def send_data(self, data):
// items = []
// for k, v in data.items():
// items.append('{'waspVarName': '' + k + '','waspVarValue': '' + str(v) + ''}')
// payload = '{'vars': [' + ','.join(items) + ']}'

// r = requests.post(url = self.url, headers = { 'x-jnap-action': 'http://phyn.com/jnap/uponorsky/SetAttributes' },
// data = payload)
// r_json = r.json()
private static _formatTemperature(input: string | undefined): number {
const fahrenheit = parseFloat(input || '0') / 10
return Math.round((fahrenheit - 32) * 5 / 9)
}

// if 'result' in r_json and not r_json['result'] == 'OK':
// raise ValueError(r_json)
private static _createKey(controllerID: string | number, thermostatID: string | number): string {
return `C${controllerID}_T${thermostatID}`
}
}

0 comments on commit 57e9100

Please sign in to comment.