From 8a02ad219e90e5669c48f4fdf185010a2c79f8e9 Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 8 Dec 2023 14:46:52 -0500 Subject: [PATCH 01/11] Use proper rounding for controllers using imperial units --- config.schema.json | 4 ++-- src/constants.ts | 3 ++- src/platformAccessory.ts | 38 +++++++++++++++++++++----------------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/config.schema.json b/config.schema.json index 2804604..7a6446a 100644 --- a/config.schema.json +++ b/config.schema.json @@ -40,14 +40,14 @@ "description": "Minimum temperature to be used by HomeKit slider. Use same units as configured above.", "type": "number", "required": true, - "default": "120" + "default": "98" }, "maximumTemperature": { "title": "Maximum Temperature", "description": "Maximum temperature to be used by HomeKit slider. Use same units as configured above.", "type": "number", "required": true, - "default": "140" + "default": "120" } } } diff --git a/src/constants.ts b/src/constants.ts index ed2f3f9..7201d58 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -273,7 +273,8 @@ export enum TemperatureUnits { F = 'F', } -export const THERMOSTAT_STEP_VALUE = 0.5; +export const THERMOSTAT_STEP_VALUE = 0.5; // in C, as HomeKit uses this unit for accessories +export const WATER_HEATER_STEP_VALUE_IN_F = 2; // Controllers with the imperial unit, use 98/100/102/etc // Increment only for breaking service changes to remove and re-add devices export const PREVIOUS_UUID_SUFFICES = ['']; diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 211a950..f129068 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -9,6 +9,7 @@ import { MANUFACTURER, SET_STATE_WAIT_TIME_MILLIS, TemperatureUnits, THERMOSTAT_STEP_VALUE, + WATER_HEATER_STEP_VALUE_IN_F, UNKNOWN, } from './constants'; import {celsiusToFahrenheit, fahrenheitToCelsius} from './util'; @@ -25,9 +26,9 @@ export class RinnaiControlrPlatformAccessory { // eslint-disable-next-line @typescript-eslint/no-explicit-any private device: any; private readonly isFahrenheit: boolean; - private readonly minValue: number; - private readonly maxValue: number; - private temperature: number; + private readonly minValue: number; // in C + private readonly maxValue: number; // in C + private targetTemperature: number; // in C constructor( private readonly platform: RinnaiControlrHomebridgePlatform, @@ -46,12 +47,15 @@ export class RinnaiControlrPlatformAccessory { this.maxValue = fahrenheitToCelsius(this.maxValue); } - this.temperature = this.isFahrenheit && this.device.info?.domestic_temperature + this.minValue = Math.floor(this.minValue / THERMOSTAT_STEP_VALUE) * THERMOSTAT_STEP_VALUE; + this.maxValue = Math.ceil(this.maxValue / THERMOSTAT_STEP_VALUE) * THERMOSTAT_STEP_VALUE; + + this.targetTemperature = this.isFahrenheit && this.device.info?.domestic_temperature ? fahrenheitToCelsius(this.device.info.domestic_temperature) : this.device.info.domestic_temperature; - this.platform.log.debug(`Temperature Slider Min: ${this.minValue}, Max: ${this.maxValue}, ` + - `current temperature: ${this.temperature}`); + this.platform.log.info(`Temperature Slider Min: ${this.minValue}, Max: ${this.maxValue}, ` + + `target temperature: ${this.targetTemperature}`); // set accessory information this.accessory.getService(this.platform.Service.AccessoryInformation)! @@ -70,18 +74,18 @@ export class RinnaiControlrPlatformAccessory { bindTemperature() { this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature) - .onSet(this.setTemperature.bind(this)) - .onGet(this.getTemperature.bind(this)) + .onSet(this.setTargetTemperature.bind(this)) + .onGet(this.getTargetTemperature.bind(this)) .setProps({ minValue: this.minValue, maxValue: this.maxValue, minStep: THERMOSTAT_STEP_VALUE, }) - .updateValue(this.temperature); + .updateValue(this.targetTemperature); this.service.getCharacteristic(this.platform.Characteristic.CurrentTemperature) - .onGet(this.getTemperature.bind(this)) - .updateValue(this.temperature) + .onGet(this.getTargetTemperature.bind(this)) + .updateValue(this.targetTemperature) .setProps({ minValue: this.minValue, maxValue: this.maxValue, @@ -133,14 +137,14 @@ export class RinnaiControlrPlatformAccessory { await this.platform.setState(this.accessory, state); } - async setTemperature(value: CharacteristicValue) { + async setTargetTemperature(value: CharacteristicValue) { this.platform.log.info(`setTemperature to ${value} for device ${this.device.dsn}`); const convertedValue: number = this.isFahrenheit - ? Math.round(celsiusToFahrenheit(value as number) / 5) * 5 // Round to nearest 5 + ? Math.round(celsiusToFahrenheit(value as number) / WATER_HEATER_STEP_VALUE_IN_F) * WATER_HEATER_STEP_VALUE_IN_F : value as number; - this.platform.log.debug('Sending converted/rounded temperature: ${convertedValue}'); + this.platform.log.info(`Sending converted/rounded temperature: ${convertedValue}`); const state: Record = { [API_KEY_SET_PRIORITY_STATUS]: true, @@ -151,11 +155,11 @@ export class RinnaiControlrPlatformAccessory { setTimeout(() => { this.platform.throttledPoll(); }, SET_STATE_WAIT_TIME_MILLIS); - this.temperature = this.isFahrenheit ? fahrenheitToCelsius(convertedValue) : convertedValue; + this.targetTemperature = this.isFahrenheit ? fahrenheitToCelsius(convertedValue) : convertedValue; } - async getTemperature(): Promise> { + async getTargetTemperature(): Promise> { this.platform.throttledPoll(); - return this.temperature; + return this.targetTemperature; } } From 4ff63b0123a0814eae4abc38155711ee68bda168 Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 22 Dec 2023 11:15:31 -0500 Subject: [PATCH 02/11] Fixed the UUID generation to use `id` field (`dsn` always returns `null`) --- src/constants.ts | 4 ++-- src/platform.ts | 22 ++++++++++++++++++---- src/platformAccessory.ts | 12 ++++++------ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 7201d58..dfec90c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -277,5 +277,5 @@ export const THERMOSTAT_STEP_VALUE = 0.5; // in C, as HomeKit uses this unit for export const WATER_HEATER_STEP_VALUE_IN_F = 2; // Controllers with the imperial unit, use 98/100/102/etc // Increment only for breaking service changes to remove and re-add devices -export const PREVIOUS_UUID_SUFFICES = ['']; -export const UUID_SUFFIX = '-1'; \ No newline at end of file +export const PREVIOUS_UUID_SUFFICES = ['-1']; +export const UUID_SUFFIX = '-2'; diff --git a/src/platform.ts b/src/platform.ts index 36483b8..efadc24 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -78,7 +78,7 @@ export class RinnaiControlrHomebridgePlatform implements DynamicPlatformPlugin { configureAccessory(accessory: PlatformAccessory) { this.log.debug('Loading accessory from cache:', accessory.displayName); if (this.removeBrokenAccessories(accessory.context)) { - this.log.warn(''); + this.log.warn('Removed accessory:', accessory.displayName); } else { this.accessories.push(accessory); } @@ -169,10 +169,12 @@ export class RinnaiControlrHomebridgePlatform implements DynamicPlatformPlugin { this.log.debug(`Found ${devices.length} Rinnai devices.`); // loop over the discovered devices and register each one if it has not already been registered for (const device of devices) { + this.log.debug(`Processing device: ${JSON.stringify(device.id, null, 2)}`); + this.removeBrokenAccessories(device); - this.log.debug(`Generating UUID from DSN ${device.dsn}`); - const uuid = this.api.hap.uuid.generate(`${device.dsn}${UUID_SUFFIX}`); + this.log.debug(`Generating UUID from S/N ${device.id}`); + const uuid = this.generateAccessoryUuid(device, UUID_SUFFIX); // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above @@ -221,10 +223,22 @@ export class RinnaiControlrHomebridgePlatform implements DynamicPlatformPlugin { }); } + generateAccessoryUuid(device, uuidSuffix: string): string { + switch (uuidSuffix) { + case '-1': + return this.api.hap.uuid.generate(`${device.dsn}${uuidSuffix}`); + case '-2': + return this.api.hap.uuid.generate(`${device.id}${uuidSuffix}`); + } + + throw new Error('Unknown suffix'); + return 'bad-uuid'; + } + removeBrokenAccessories(device): boolean { let removed = false; PREVIOUS_UUID_SUFFICES.forEach(uuidSuffix => { - const oldUuid = this.api.hap.uuid.generate(`${device.dsn}${uuidSuffix}`); + const oldUuid = this.generateAccessoryUuid(device, uuidSuffix); const oldAccessory = this.accessories.find(accessory => accessory.UUID === oldUuid); if (oldAccessory) { this.log.info(`Removing existing accessory from cache because of breaking change: ${oldAccessory.displayName}`); diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index f129068..7869ac4 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -61,7 +61,7 @@ export class RinnaiControlrPlatformAccessory { this.accessory.getService(this.platform.Service.AccessoryInformation)! .setCharacteristic(this.platform.Characteristic.Manufacturer, MANUFACTURER) .setCharacteristic(this.platform.Characteristic.Model, this.device.model || UNKNOWN) - .setCharacteristic(this.platform.Characteristic.SerialNumber, this.device.dsn || UNKNOWN); + .setCharacteristic(this.platform.Characteristic.SerialNumber, this.device.id || UNKNOWN); this.service = this.accessory.getService(this.platform.Service.Thermostat) || this.accessory.addService(this.platform.Service.Thermostat); @@ -103,15 +103,15 @@ export class RinnaiControlrPlatformAccessory { bindRecirculation() { if (this.accessory.context.info?.recirculation_capable === API_VALUE_TRUE) { - this.platform.log.debug(`Device ${this.device.dsn} has recirculation capabilities. Adding service.`); + this.platform.log.debug(`Device ${this.device.id} has recirculation capabilities. Adding service.`); const recircService = this.accessory.getService(RECIRC_SERVICE_NAME) || - this.accessory.addService(this.platform.Service.Switch, RECIRC_SERVICE_NAME, `${this.device.dsn}-Recirculation`); + this.accessory.addService(this.platform.Service.Switch, RECIRC_SERVICE_NAME, `${this.device.id}-Recirculation`); recircService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setRecirculateActive.bind(this)); recircService.updateCharacteristic(this.platform.Characteristic.On, this.device.shadow.recirculation_enabled); } else { - this.platform.log.debug(`Device ${this.device.dsn} does not support recirculation.`); + this.platform.log.debug(`Device ${this.device.id} does not support recirculation.`); } } @@ -127,7 +127,7 @@ export class RinnaiControlrPlatformAccessory { async setRecirculateActive(value: CharacteristicValue) { const duration = (value as boolean) ? `${this.platform.getConfig().recirculationDuration}` : '0'; - this.platform.log.info(`setRecirculateActive to ${value} for device ${this.device.dsn}`); + this.platform.log.info(`setRecirculateActive to ${value} for device ${this.device.id}`); const state: Record = { [API_KEY_SET_PRIORITY_STATUS]: true, @@ -138,7 +138,7 @@ export class RinnaiControlrPlatformAccessory { } async setTargetTemperature(value: CharacteristicValue) { - this.platform.log.info(`setTemperature to ${value} for device ${this.device.dsn}`); + this.platform.log.info(`setTemperature to ${value} for device ${this.device.id}`); const convertedValue: number = this.isFahrenheit ? Math.round(celsiusToFahrenheit(value as number) / WATER_HEATER_STEP_VALUE_IN_F) * WATER_HEATER_STEP_VALUE_IN_F From f9dcf8d47d0c865a1a76c5db2279c43dd9207be1 Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 22 Dec 2023 12:01:22 -0500 Subject: [PATCH 03/11] Avoided to create `RinnaiControlrPlatformAccessory` for each poll --- src/platform.ts | 13 +++++++++---- src/platformAccessory.ts | 29 +++++++++++++---------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index efadc24..0165c85 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -45,6 +45,7 @@ export class RinnaiControlrHomebridgePlatform implements DynamicPlatformPlugin { // this is used to track restored cached accessories public readonly accessories: PlatformAccessory[] = []; + private initializedAccessories: string[] = []; constructor( public readonly log: Logger, @@ -169,7 +170,7 @@ export class RinnaiControlrHomebridgePlatform implements DynamicPlatformPlugin { this.log.debug(`Found ${devices.length} Rinnai devices.`); // loop over the discovered devices and register each one if it has not already been registered for (const device of devices) { - this.log.debug(`Processing device: ${JSON.stringify(device.id, null, 2)}`); + this.log.debug(`Processing device: ${JSON.stringify(device, null, 2)}`); this.removeBrokenAccessories(device); @@ -188,9 +189,12 @@ export class RinnaiControlrHomebridgePlatform implements DynamicPlatformPlugin { accessory.context = device; this.api.updatePlatformAccessories([accessory]); - // create the accessory handler for the restored accessory - // this is imported from `platformAccessory.ts` - new RinnaiControlrPlatformAccessory(this, accessory); + if (!this.initializedAccessories.find(accessoryUuid => accessoryUuid === uuid)) { + // create the accessory handler for the restored accessory + // this is imported from `platformAccessory.ts` + new RinnaiControlrPlatformAccessory(this, accessory); + this.initializedAccessories.push(accessory.UUID); + } } else { // the accessory does not yet exist, so we need to create it this.log.debug(`Adding new accessory because ${accessory} was not restored from cache:`, device.device_name); @@ -210,6 +214,7 @@ export class RinnaiControlrHomebridgePlatform implements DynamicPlatformPlugin { // link the accessory to your platform this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); + this.initializedAccessories.push(accessory.UUID); } } diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 7869ac4..32cb2a1 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -34,10 +34,7 @@ export class RinnaiControlrPlatformAccessory { private readonly platform: RinnaiControlrHomebridgePlatform, private readonly accessory: PlatformAccessory, ) { - - this.device = this.accessory.context; - - this.platform.log.debug(`Setting accessory details for device: ${JSON.stringify(this.device, null, 2)}`); + this.platform.log.debug(`Setting accessory details for device: ${JSON.stringify(this.accessory.context, null, 2)}`); this.isFahrenheit = this.platform.getConfig().temperatureUnits === TemperatureUnits.F; this.minValue = this.platform.getConfig().minimumTemperature; @@ -50,9 +47,9 @@ export class RinnaiControlrPlatformAccessory { this.minValue = Math.floor(this.minValue / THERMOSTAT_STEP_VALUE) * THERMOSTAT_STEP_VALUE; this.maxValue = Math.ceil(this.maxValue / THERMOSTAT_STEP_VALUE) * THERMOSTAT_STEP_VALUE; - this.targetTemperature = this.isFahrenheit && this.device.info?.domestic_temperature - ? fahrenheitToCelsius(this.device.info.domestic_temperature) - : this.device.info.domestic_temperature; + this.targetTemperature = this.isFahrenheit && this.accessory.context.info?.domestic_temperature + ? fahrenheitToCelsius(this.accessory.context.info.domestic_temperature) + : this.accessory.context.info.domestic_temperature; this.platform.log.info(`Temperature Slider Min: ${this.minValue}, Max: ${this.maxValue}, ` + `target temperature: ${this.targetTemperature}`); @@ -60,12 +57,12 @@ export class RinnaiControlrPlatformAccessory { // set accessory information this.accessory.getService(this.platform.Service.AccessoryInformation)! .setCharacteristic(this.platform.Characteristic.Manufacturer, MANUFACTURER) - .setCharacteristic(this.platform.Characteristic.Model, this.device.model || UNKNOWN) - .setCharacteristic(this.platform.Characteristic.SerialNumber, this.device.id || UNKNOWN); + .setCharacteristic(this.platform.Characteristic.Model, this.accessory.context.model || UNKNOWN) + .setCharacteristic(this.platform.Characteristic.SerialNumber, this.accessory.context.id || UNKNOWN); this.service = this.accessory.getService(this.platform.Service.Thermostat) || this.accessory.addService(this.platform.Service.Thermostat); - this.service.setCharacteristic(this.platform.Characteristic.Name, this.device.device_name); + this.service.setCharacteristic(this.platform.Characteristic.Name, this.accessory.context.device_name); this.bindTemperature(); this.bindRecirculation(); @@ -103,15 +100,15 @@ export class RinnaiControlrPlatformAccessory { bindRecirculation() { if (this.accessory.context.info?.recirculation_capable === API_VALUE_TRUE) { - this.platform.log.debug(`Device ${this.device.id} has recirculation capabilities. Adding service.`); + this.platform.log.debug(`Device ${this.accessory.context.id} has recirculation capabilities. Adding service.`); const recircService = this.accessory.getService(RECIRC_SERVICE_NAME) || - this.accessory.addService(this.platform.Service.Switch, RECIRC_SERVICE_NAME, `${this.device.id}-Recirculation`); + this.accessory.addService(this.platform.Service.Switch, RECIRC_SERVICE_NAME, `${this.accessory.context.id}-Recirculation`); recircService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setRecirculateActive.bind(this)); recircService.updateCharacteristic(this.platform.Characteristic.On, - this.device.shadow.recirculation_enabled); + this.accessory.context.shadow.recirculation_enabled); } else { - this.platform.log.debug(`Device ${this.device.id} does not support recirculation.`); + this.platform.log.debug(`Device ${this.accessory.context.id} does not support recirculation.`); } } @@ -127,7 +124,7 @@ export class RinnaiControlrPlatformAccessory { async setRecirculateActive(value: CharacteristicValue) { const duration = (value as boolean) ? `${this.platform.getConfig().recirculationDuration}` : '0'; - this.platform.log.info(`setRecirculateActive to ${value} for device ${this.device.id}`); + this.platform.log.info(`setRecirculateActive to ${value} for device ${this.accessory.context.id}`); const state: Record = { [API_KEY_SET_PRIORITY_STATUS]: true, @@ -138,7 +135,7 @@ export class RinnaiControlrPlatformAccessory { } async setTargetTemperature(value: CharacteristicValue) { - this.platform.log.info(`setTemperature to ${value} for device ${this.device.id}`); + this.platform.log.info(`setTemperature to ${value} for device ${this.accessory.context.id}`); const convertedValue: number = this.isFahrenheit ? Math.round(celsiusToFahrenheit(value as number) / WATER_HEATER_STEP_VALUE_IN_F) * WATER_HEATER_STEP_VALUE_IN_F From 055692c2efeae2305856f88966b002dd8ec49aac Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 22 Dec 2023 13:35:59 -0500 Subject: [PATCH 04/11] Fixed the incorrect use of _.throttle --- src/platform.ts | 12 ++++-------- src/platformAccessory.ts | 2 -- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 0165c85..feb7483 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -110,14 +110,6 @@ export class RinnaiControlrHomebridgePlatform implements DynamicPlatformPlugin { } } - throttledPoll() { - const throttle = _.throttle(() => { - this.pollDeviceStatus(); - }, API_POLL_THROTTLE_MILLIS); - - throttle(); - } - getConfig(): RinnaiControlrConfig { return this.config as RinnaiControlrConfig; } @@ -228,6 +220,10 @@ export class RinnaiControlrHomebridgePlatform implements DynamicPlatformPlugin { }); } + public throttledPoll = _.throttle(async () => { + await this.pollDeviceStatus(); + }, API_POLL_THROTTLE_MILLIS); + generateAccessoryUuid(device, uuidSuffix: string): string { switch (uuidSuffix) { case '-1': diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 32cb2a1..6bdf395 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -23,8 +23,6 @@ const RECIRC_SERVICE_NAME = 'Recirculation'; */ export class RinnaiControlrPlatformAccessory { private service: Service; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private device: any; private readonly isFahrenheit: boolean; private readonly minValue: number; // in C private readonly maxValue: number; // in C From f5fea0b35a57ea6e24b68e4eda4a27cce5dbe83a Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 8 Dec 2023 17:10:26 -0500 Subject: [PATCH 05/11] Support heating state and current temperature * Added the support of heating state ("Heating to" when the combustation is ON) and the current water temperature (from the outlet temperature). * Improved the stepped target temperature calculation for controllers using imperial unit. * Avoid adding recirulation button if the heater is not configured for it yet --- package-lock.json | 4 +- package.json | 2 +- src/constants.ts | 19 ++++- src/platformAccessory.ts | 148 +++++++++++++++++++++++++++++++-------- 4 files changed, 137 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa2b2a2..afa35e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "homebridge-rinnai-controlr", - "version": "1.0.24", + "version": "1.0.27", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "homebridge-rinnai-controlr", - "version": "1.0.24", + "version": "1.0.27", "license": "Apache-2.0", "dependencies": { "@aws-amplify/api-graphql": "^3.0.11", diff --git a/package.json b/package.json index b23a997..61e62b8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "Homebridge Rinnai Control-R", "name": "homebridge-rinnai-controlr", - "version": "1.0.24", + "version": "1.0.27", "description": "Integrates with Rinnai Control-R for HomeKit control of water heaters", "license": "Apache-2.0", "repository": { diff --git a/src/constants.ts b/src/constants.ts index dfec90c..aa37c06 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -259,12 +259,15 @@ export const API_KEY_SET_PRIORITY_STATUS = 'set_priority_status'; export const API_KEY_RECIRCULATION_DURATION = 'recirculation_duration'; export const API_KEY_SET_RECIRCULATION_ENABLED = 'set_recirculation_enabled'; export const API_KEY_SET_TEMPERATURE = 'set_domestic_temperature'; +export const API_KEY_DO_MAINTENANCE_RETRIEVAL = 'do_maintenance_retrieval'; export const API_VALUE_TRUE = 'true'; export const API_VALUE_FALSE = 'false'; export const API_POLL_THROTTLE_MILLIS = 1000; export const SET_STATE_WAIT_TIME_MILLIS = 5000; +export const ACCESSARY_INFO_UPDATE_THROTTLE_MILLIS = 30000; + export const MANUFACTURER = 'Rinnai'; export const UNKNOWN = 'Unknown'; @@ -273,8 +276,20 @@ export enum TemperatureUnits { F = 'F', } -export const THERMOSTAT_STEP_VALUE = 0.5; // in C, as HomeKit uses this unit for accessories -export const WATER_HEATER_STEP_VALUE_IN_F = 2; // Controllers with the imperial unit, use 98/100/102/etc +// All numbers below use metric units (C), as required by HomeKit APIs +export const THERMOSTAT_TARGET_TEMP_STEP_VALUE = 1; + +export const THERMOSTAT_CURRENT_TEMP_STEP_VALUE = 0.5; +export const THERMOSTAT_CURRENT_TEMP_MAX_VALUE = 65; // 60C for residential water heater based on the manual, adding 5 as buffer +export const THERMOSTAT_CURRENT_TEMP_MIN_VALUE = 0; // 0C, frozen pipe... + +/** + * For water heater controllers with the imperial unit, + * the steps are 98/100/102/.../108/110/105/110/115/120/... + */ +export const WATER_HEATER_SMALL_STEP_VALUE_IN_F = 2; +export const WATER_HEATER_BIG_STEP_START_IN_F = 110; +export const WATER_HEATER_BIG_STEP_VALUE_IN_F = 5; // Increment only for breaking service changes to remove and re-add devices export const PREVIOUS_UUID_SUFFICES = ['-1']; diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 6bdf395..24bf30e 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -5,13 +5,22 @@ import { API_KEY_RECIRCULATION_DURATION, API_KEY_SET_PRIORITY_STATUS, API_KEY_SET_RECIRCULATION_ENABLED, API_KEY_SET_TEMPERATURE, + API_KEY_DO_MAINTENANCE_RETRIEVAL, API_VALUE_TRUE, MANUFACTURER, SET_STATE_WAIT_TIME_MILLIS, - TemperatureUnits, THERMOSTAT_STEP_VALUE, - WATER_HEATER_STEP_VALUE_IN_F, + TemperatureUnits, THERMOSTAT_TARGET_TEMP_STEP_VALUE, + WATER_HEATER_SMALL_STEP_VALUE_IN_F, + THERMOSTAT_CURRENT_TEMP_MAX_VALUE, + THERMOSTAT_CURRENT_TEMP_MIN_VALUE, UNKNOWN, + ACCESSARY_INFO_UPDATE_THROTTLE_MILLIS, + API_VALUE_FALSE, + THERMOSTAT_CURRENT_TEMP_STEP_VALUE, + WATER_HEATER_BIG_STEP_START_IN_F, + WATER_HEATER_BIG_STEP_VALUE_IN_F, } from './constants'; +import _ from 'lodash'; import {celsiusToFahrenheit, fahrenheitToCelsius} from './util'; const RECIRC_SERVICE_NAME = 'Recirculation'; @@ -23,16 +32,19 @@ const RECIRC_SERVICE_NAME = 'Recirculation'; */ export class RinnaiControlrPlatformAccessory { private service: Service; + // Controller unit private readonly isFahrenheit: boolean; - private readonly minValue: number; // in C - private readonly maxValue: number; // in C - private targetTemperature: number; // in C + // All values below are in C as required by HomeKit + private readonly minValue: number; + private readonly maxValue: number; + private targetTemperature!: number; + private outletTemperature!: number; + private isRunning!: boolean; constructor( private readonly platform: RinnaiControlrHomebridgePlatform, private readonly accessory: PlatformAccessory, ) { - this.platform.log.debug(`Setting accessory details for device: ${JSON.stringify(this.accessory.context, null, 2)}`); this.isFahrenheit = this.platform.getConfig().temperatureUnits === TemperatureUnits.F; this.minValue = this.platform.getConfig().minimumTemperature; @@ -42,15 +54,11 @@ export class RinnaiControlrPlatformAccessory { this.maxValue = fahrenheitToCelsius(this.maxValue); } - this.minValue = Math.floor(this.minValue / THERMOSTAT_STEP_VALUE) * THERMOSTAT_STEP_VALUE; - this.maxValue = Math.ceil(this.maxValue / THERMOSTAT_STEP_VALUE) * THERMOSTAT_STEP_VALUE; + this.minValue = Math.floor(this.minValue / THERMOSTAT_TARGET_TEMP_STEP_VALUE) * THERMOSTAT_TARGET_TEMP_STEP_VALUE; + this.maxValue = Math.ceil(this.maxValue / THERMOSTAT_TARGET_TEMP_STEP_VALUE) * THERMOSTAT_TARGET_TEMP_STEP_VALUE; + this.platform.log.debug(`Target Temperature Slider Min: ${this.minValue}, Max: ${this.maxValue}`); - this.targetTemperature = this.isFahrenheit && this.accessory.context.info?.domestic_temperature - ? fahrenheitToCelsius(this.accessory.context.info.domestic_temperature) - : this.accessory.context.info.domestic_temperature; - - this.platform.log.info(`Temperature Slider Min: ${this.minValue}, Max: ${this.maxValue}, ` + - `target temperature: ${this.targetTemperature}`); + this.extractDeviceInfo(); // set accessory information this.accessory.getService(this.platform.Service.AccessoryInformation)! @@ -67,6 +75,29 @@ export class RinnaiControlrPlatformAccessory { this.bindStaticValues(); } + extractDeviceInfo() { + this.platform.log.debug(`Setting accessory details from device payload: ${JSON.stringify(this.accessory.context, null, 2)}`); + + if (this.accessory.context.info) { + this.targetTemperature = this.isFahrenheit && this.accessory.context.info.domestic_temperature + ? fahrenheitToCelsius(this.accessory.context.info.domestic_temperature) + : this.accessory.context.info.domestic_temperature; + + this.outletTemperature = this.isFahrenheit && this.accessory.context.info.m02_outlet_temperature + ? fahrenheitToCelsius(this.accessory.context.info.m02_outlet_temperature) + : this.accessory.context.info.m02_outlet_temperature; + + this.isRunning = this.accessory.context.info.domestic_combustion == API_VALUE_TRUE; + } else { + this.platform.log.error(`Cannot extract details from ${JSON.stringify(this.accessory.context, null, 2)}`); + } + + this.platform.log.debug(`Extracted device info: ` + + `target temperature = ${this.targetTemperature}, ` + + `outlet temperature = ${this.outletTemperature}, ` + + `is running = ${this.isRunning}`); + } + bindTemperature() { this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature) .onSet(this.setTargetTemperature.bind(this)) @@ -74,17 +105,17 @@ export class RinnaiControlrPlatformAccessory { .setProps({ minValue: this.minValue, maxValue: this.maxValue, - minStep: THERMOSTAT_STEP_VALUE, + minStep: THERMOSTAT_TARGET_TEMP_STEP_VALUE, }) .updateValue(this.targetTemperature); this.service.getCharacteristic(this.platform.Characteristic.CurrentTemperature) - .onGet(this.getTargetTemperature.bind(this)) - .updateValue(this.targetTemperature) + .onGet(this.getOutletTemperature.bind(this)) + .updateValue(this.outletTemperature) .setProps({ - minValue: this.minValue, - maxValue: this.maxValue, - minStep: THERMOSTAT_STEP_VALUE, + minValue: THERMOSTAT_CURRENT_TEMP_MIN_VALUE, + maxValue: THERMOSTAT_CURRENT_TEMP_MAX_VALUE, + minStep: THERMOSTAT_CURRENT_TEMP_STEP_VALUE, }); this.service.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState) @@ -94,10 +125,20 @@ export class RinnaiControlrPlatformAccessory { maxValue: this.platform.Characteristic.TargetHeatingCoolingState.HEAT, validValues: [this.platform.Characteristic.TargetHeatingCoolingState.HEAT], }); + + this.service.getCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState) + .onGet(this.getIsRunning.bind(this)) + .updateValue(this.isRunning ? this.platform.Characteristic.CurrentHeatingCoolingState.HEAT : this.platform.Characteristic.CurrentHeatingCoolingState.OFF) + .setProps({ + minValue: this.platform.Characteristic.CurrentHeatingCoolingState.OFF, + maxValue: this.platform.Characteristic.CurrentHeatingCoolingState.HEAT, + validValues: [this.platform.Characteristic.CurrentHeatingCoolingState.OFF, this.platform.Characteristic.CurrentHeatingCoolingState.HEAT], + }); } bindRecirculation() { - if (this.accessory.context.info?.recirculation_capable === API_VALUE_TRUE) { + if (this.accessory.context.info?.recirculation_capable === API_VALUE_TRUE && + this.accessory.context.shadow?.recirculation_not_configured === API_VALUE_FALSE) { this.platform.log.debug(`Device ${this.accessory.context.id} has recirculation capabilities. Adding service.`); const recircService = this.accessory.getService(RECIRC_SERVICE_NAME) || this.accessory.addService(this.platform.Service.Switch, RECIRC_SERVICE_NAME, `${this.accessory.context.id}-Recirculation`); @@ -106,7 +147,7 @@ export class RinnaiControlrPlatformAccessory { recircService.updateCharacteristic(this.platform.Characteristic.On, this.accessory.context.shadow.recirculation_enabled); } else { - this.platform.log.debug(`Device ${this.accessory.context.id} does not support recirculation.`); + this.platform.log.debug(`Device ${this.accessory.context.id} does not support recirculation or has not be configured for recirculation.`); } } @@ -115,8 +156,6 @@ export class RinnaiControlrPlatformAccessory { this.platform.getConfig().temperatureUnits === TemperatureUnits.F ? this.platform.Characteristic.TemperatureDisplayUnits.FAHRENHEIT : this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS); - this.service.updateCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, - this.platform.Characteristic.CurrentHeatingCoolingState.HEAT); } async setRecirculateActive(value: CharacteristicValue) { @@ -132,14 +171,41 @@ export class RinnaiControlrPlatformAccessory { await this.platform.setState(this.accessory, state); } - async setTargetTemperature(value: CharacteristicValue) { - this.platform.log.info(`setTemperature to ${value} for device ${this.accessory.context.id}`); + async retrieveMaintenanceInfo() { + const state: Record = { + [API_KEY_DO_MAINTENANCE_RETRIEVAL]: true, + }; + + await this.platform.setState(this.accessory, state); + } + + public throttledRetrieveMaintenanceInfo = _.throttle(async () => { + await this.retrieveMaintenanceInfo(); + }, ACCESSARY_INFO_UPDATE_THROTTLE_MILLIS); + + accessoryToControllerTemperature(value: number): number { + let convertedValue: number = this.isFahrenheit ? celsiusToFahrenheit(value) : value; + if (this.isFahrenheit) { + if (convertedValue >= WATER_HEATER_BIG_STEP_START_IN_F) { + convertedValue = Math.round(celsiusToFahrenheit(convertedValue) / WATER_HEATER_SMALL_STEP_VALUE_IN_F) * WATER_HEATER_SMALL_STEP_VALUE_IN_F + } else { + convertedValue = Math.round(celsiusToFahrenheit(convertedValue) / WATER_HEATER_BIG_STEP_VALUE_IN_F) * WATER_HEATER_BIG_STEP_VALUE_IN_F + } + } else { + convertedValue = Math.round(convertedValue); + } + + convertedValue = Math.max(this.platform.getConfig().minimumTemperature as number, convertedValue); + convertedValue = Math.min(this.platform.getConfig().maximumTemperature as number, convertedValue); - const convertedValue: number = this.isFahrenheit - ? Math.round(celsiusToFahrenheit(value as number) / WATER_HEATER_STEP_VALUE_IN_F) * WATER_HEATER_STEP_VALUE_IN_F - : value as number; + return convertedValue; + } + + async setTargetTemperature(value: CharacteristicValue) { + this.platform.log.info(`setTemperature to ${value} C for device ${this.accessory.context.id}`); - this.platform.log.info(`Sending converted/rounded temperature: ${convertedValue}`); + const convertedValue = this.accessoryToControllerTemperature(value as number); + this.platform.log.info(`Sending converted/rounded temperature: ${convertedValue} ${this.platform.getConfig().temperatureUnits}`); const state: Record = { [API_KEY_SET_PRIORITY_STATUS]: true, @@ -147,14 +213,34 @@ export class RinnaiControlrPlatformAccessory { }; await this.platform.setState(this.accessory, state); + setTimeout(() => { this.platform.throttledPoll(); }, SET_STATE_WAIT_TIME_MILLIS); + this.targetTemperature = this.isFahrenheit ? fahrenheitToCelsius(convertedValue) : convertedValue; } async getTargetTemperature(): Promise> { - this.platform.throttledPoll(); + await this.platform.throttledPoll(); + this.extractDeviceInfo(); + return this.targetTemperature; } + + async getOutletTemperature(): Promise> { + await this.throttledRetrieveMaintenanceInfo() + + await this.platform.throttledPoll(); + this.extractDeviceInfo(); + + return this.outletTemperature; + } + + async getIsRunning(): Promise> { + await this.platform.throttledPoll(); + this.extractDeviceInfo(); + + return this.isRunning; + } } From db5a173193ac5a4c593e603e806c6ad61a0c0b13 Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 22 Dec 2023 13:58:50 -0500 Subject: [PATCH 06/11] improve logging --- src/platformAccessory.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 24bf30e..7d8a415 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -56,7 +56,7 @@ export class RinnaiControlrPlatformAccessory { this.minValue = Math.floor(this.minValue / THERMOSTAT_TARGET_TEMP_STEP_VALUE) * THERMOSTAT_TARGET_TEMP_STEP_VALUE; this.maxValue = Math.ceil(this.maxValue / THERMOSTAT_TARGET_TEMP_STEP_VALUE) * THERMOSTAT_TARGET_TEMP_STEP_VALUE; - this.platform.log.debug(`Target Temperature Slider Min: ${this.minValue}, Max: ${this.maxValue}`); + this.platform.log.info(`Water Heater ${this.accessory.context.id}: Target Temperature Slider Min: ${this.minValue}, Max: ${this.maxValue}`); this.extractDeviceInfo(); @@ -92,10 +92,10 @@ export class RinnaiControlrPlatformAccessory { this.platform.log.error(`Cannot extract details from ${JSON.stringify(this.accessory.context, null, 2)}`); } - this.platform.log.debug(`Extracted device info: ` + - `target temperature = ${this.targetTemperature}, ` + - `outlet temperature = ${this.outletTemperature}, ` + - `is running = ${this.isRunning}`); + this.platform.log.info(`Water Heater ${this.accessory.context.id}: ` + + `targetTemperature = ${this.targetTemperature}, ` + + `outletTemperature = ${this.outletTemperature}, ` + + `isRunning = ${this.isRunning}`); } bindTemperature() { @@ -202,10 +202,10 @@ export class RinnaiControlrPlatformAccessory { } async setTargetTemperature(value: CharacteristicValue) { - this.platform.log.info(`setTemperature to ${value} C for device ${this.accessory.context.id}`); + this.platform.log.info(`Water Heater ${this.accessory.context.id}: HomeKit sets target temperature to ${value}C`); const convertedValue = this.accessoryToControllerTemperature(value as number); - this.platform.log.info(`Sending converted/rounded temperature: ${convertedValue} ${this.platform.getConfig().temperatureUnits}`); + this.platform.log.info(`Water Heater ${this.accessory.context.id}: Sending converted/rounded temperature: ${convertedValue}${this.platform.getConfig().temperatureUnits}`); const state: Record = { [API_KEY_SET_PRIORITY_STATUS]: true, From 96e8e414c44f987fa1b2a8cc944c59d5900c0fa3 Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 22 Dec 2023 14:39:16 -0500 Subject: [PATCH 07/11] improve device data polling --- src/constants.ts | 2 +- src/platformAccessory.ts | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index aa37c06..4114668 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -266,7 +266,7 @@ export const API_VALUE_FALSE = 'false'; export const API_POLL_THROTTLE_MILLIS = 1000; export const SET_STATE_WAIT_TIME_MILLIS = 5000; -export const ACCESSARY_INFO_UPDATE_THROTTLE_MILLIS = 30000; +export const MAINTENANCE_RETRIEVAL_THROTTLE_MILLIS = 30000; export const MANUFACTURER = 'Rinnai'; export const UNKNOWN = 'Unknown'; diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 7d8a415..973a4cf 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -14,7 +14,7 @@ import { THERMOSTAT_CURRENT_TEMP_MAX_VALUE, THERMOSTAT_CURRENT_TEMP_MIN_VALUE, UNKNOWN, - ACCESSARY_INFO_UPDATE_THROTTLE_MILLIS, + MAINTENANCE_RETRIEVAL_THROTTLE_MILLIS, API_VALUE_FALSE, THERMOSTAT_CURRENT_TEMP_STEP_VALUE, WATER_HEATER_BIG_STEP_START_IN_F, @@ -181,7 +181,7 @@ export class RinnaiControlrPlatformAccessory { public throttledRetrieveMaintenanceInfo = _.throttle(async () => { await this.retrieveMaintenanceInfo(); - }, ACCESSARY_INFO_UPDATE_THROTTLE_MILLIS); + }, MAINTENANCE_RETRIEVAL_THROTTLE_MILLIS); accessoryToControllerTemperature(value: number): number { let convertedValue: number = this.isFahrenheit ? celsiusToFahrenheit(value) : value; @@ -221,25 +221,28 @@ export class RinnaiControlrPlatformAccessory { this.targetTemperature = this.isFahrenheit ? fahrenheitToCelsius(convertedValue) : convertedValue; } - async getTargetTemperature(): Promise> { + public throttledPollDeviceInfo = _.throttle(async () => { + await this.throttledRetrieveMaintenanceInfo() + await this.platform.throttledPoll(); + this.extractDeviceInfo(); + }, SET_STATE_WAIT_TIME_MILLIS); + + async getTargetTemperature(): Promise> { + await this.throttledPollDeviceInfo(); return this.targetTemperature; } async getOutletTemperature(): Promise> { - await this.throttledRetrieveMaintenanceInfo() - - await this.platform.throttledPoll(); - this.extractDeviceInfo(); + await this.throttledPollDeviceInfo(); return this.outletTemperature; } async getIsRunning(): Promise> { - await this.platform.throttledPoll(); - this.extractDeviceInfo(); + await this.throttledPollDeviceInfo(); return this.isRunning; } From 816e90bcc5c30f0eb187e6112ddc18b36d7d0a95 Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 22 Dec 2023 14:44:39 -0500 Subject: [PATCH 08/11] fix wrong step function for temp --- src/platformAccessory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 973a4cf..97cb0ac 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -186,10 +186,10 @@ export class RinnaiControlrPlatformAccessory { accessoryToControllerTemperature(value: number): number { let convertedValue: number = this.isFahrenheit ? celsiusToFahrenheit(value) : value; if (this.isFahrenheit) { - if (convertedValue >= WATER_HEATER_BIG_STEP_START_IN_F) { - convertedValue = Math.round(celsiusToFahrenheit(convertedValue) / WATER_HEATER_SMALL_STEP_VALUE_IN_F) * WATER_HEATER_SMALL_STEP_VALUE_IN_F + if (convertedValue < WATER_HEATER_BIG_STEP_START_IN_F) { + convertedValue = Math.round(convertedValue / WATER_HEATER_SMALL_STEP_VALUE_IN_F) * WATER_HEATER_SMALL_STEP_VALUE_IN_F } else { - convertedValue = Math.round(celsiusToFahrenheit(convertedValue) / WATER_HEATER_BIG_STEP_VALUE_IN_F) * WATER_HEATER_BIG_STEP_VALUE_IN_F + convertedValue = Math.round(convertedValue / WATER_HEATER_BIG_STEP_VALUE_IN_F) * WATER_HEATER_BIG_STEP_VALUE_IN_F } } else { convertedValue = Math.round(convertedValue); From 68ccb6f3354777d550e4285902b3b959adb7f1ad Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 22 Dec 2023 14:51:27 -0500 Subject: [PATCH 09/11] improve device data polling --- src/platformAccessory.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 97cb0ac..6b058c3 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -179,10 +179,6 @@ export class RinnaiControlrPlatformAccessory { await this.platform.setState(this.accessory, state); } - public throttledRetrieveMaintenanceInfo = _.throttle(async () => { - await this.retrieveMaintenanceInfo(); - }, MAINTENANCE_RETRIEVAL_THROTTLE_MILLIS); - accessoryToControllerTemperature(value: number): number { let convertedValue: number = this.isFahrenheit ? celsiusToFahrenheit(value) : value; if (this.isFahrenheit) { @@ -222,12 +218,12 @@ export class RinnaiControlrPlatformAccessory { } public throttledPollDeviceInfo = _.throttle(async () => { - await this.throttledRetrieveMaintenanceInfo() + await this.retrieveMaintenanceInfo(); await this.platform.throttledPoll(); this.extractDeviceInfo(); - }, SET_STATE_WAIT_TIME_MILLIS); + }, MAINTENANCE_RETRIEVAL_THROTTLE_MILLIS); async getTargetTemperature(): Promise> { await this.throttledPollDeviceInfo(); From 08d1781a7cecd6d6b3ea2177f407e3635df4605b Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 22 Dec 2023 22:38:10 -0500 Subject: [PATCH 10/11] improve device data polling --- src/constants.ts | 3 ++- src/platformAccessory.ts | 30 +++++++++++++++++++----------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 4114668..6d5619d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -266,7 +266,8 @@ export const API_VALUE_FALSE = 'false'; export const API_POLL_THROTTLE_MILLIS = 1000; export const SET_STATE_WAIT_TIME_MILLIS = 5000; -export const MAINTENANCE_RETRIEVAL_THROTTLE_MILLIS = 30000; +export const MAINTENANCE_RETRIEVAL_RUNNING_THROTTLE_MILLIS = 30000; // 30 secs when heater is running +export const MAINTENANCE_RETRIEVAL_IDLE_THROTTLE_MILLIS = 300000; // 5 mins when heater is idling export const MANUFACTURER = 'Rinnai'; export const UNKNOWN = 'Unknown'; diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 6b058c3..201201e 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -14,7 +14,8 @@ import { THERMOSTAT_CURRENT_TEMP_MAX_VALUE, THERMOSTAT_CURRENT_TEMP_MIN_VALUE, UNKNOWN, - MAINTENANCE_RETRIEVAL_THROTTLE_MILLIS, + MAINTENANCE_RETRIEVAL_IDLE_THROTTLE_MILLIS, + MAINTENANCE_RETRIEVAL_RUNNING_THROTTLE_MILLIS, API_VALUE_FALSE, THERMOSTAT_CURRENT_TEMP_STEP_VALUE, WATER_HEATER_BIG_STEP_START_IN_F, @@ -217,29 +218,36 @@ export class RinnaiControlrPlatformAccessory { this.targetTemperature = this.isFahrenheit ? fahrenheitToCelsius(convertedValue) : convertedValue; } - public throttledPollDeviceInfo = _.throttle(async () => { - await this.retrieveMaintenanceInfo(); - + async pollBasicInfo() { await this.platform.throttledPoll(); + this.extractDeviceInfo(); + } + async pollMaintenanceInfo() { + await this.retrieveMaintenanceInfo(); + await this.platform.throttledPoll(); this.extractDeviceInfo(); - }, MAINTENANCE_RETRIEVAL_THROTTLE_MILLIS); + } - async getTargetTemperature(): Promise> { - await this.throttledPollDeviceInfo(); + public pollMaintenanceInfoWhenIdling = _.throttle(this.pollMaintenanceInfo, MAINTENANCE_RETRIEVAL_IDLE_THROTTLE_MILLIS); + public pollMaintenanceInfoWhenRunning = _.throttle(this.pollMaintenanceInfo, MAINTENANCE_RETRIEVAL_RUNNING_THROTTLE_MILLIS); + async getTargetTemperature(): Promise> { + await this.pollBasicInfo(); return this.targetTemperature; } async getOutletTemperature(): Promise> { - await this.throttledPollDeviceInfo(); - + if (this.isRunning) { + await this.pollMaintenanceInfoWhenRunning(); + } else { + await this.pollMaintenanceInfoWhenIdling(); + } return this.outletTemperature; } async getIsRunning(): Promise> { - await this.throttledPollDeviceInfo(); - + await this.pollBasicInfo(); return this.isRunning; } } From 5e72c75665be54f3ae3f04c9a853061275f09e30 Mon Sep 17 00:00:00 2001 From: Zimu Liu Date: Fri, 22 Dec 2023 23:07:02 -0500 Subject: [PATCH 11/11] improve device data polling --- src/constants.ts | 4 ++-- src/platformAccessory.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 6d5619d..dba45ad 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -263,11 +263,11 @@ export const API_KEY_DO_MAINTENANCE_RETRIEVAL = 'do_maintenance_retrieval'; export const API_VALUE_TRUE = 'true'; export const API_VALUE_FALSE = 'false'; -export const API_POLL_THROTTLE_MILLIS = 1000; +export const API_POLL_THROTTLE_MILLIS = 30000; export const SET_STATE_WAIT_TIME_MILLIS = 5000; export const MAINTENANCE_RETRIEVAL_RUNNING_THROTTLE_MILLIS = 30000; // 30 secs when heater is running -export const MAINTENANCE_RETRIEVAL_IDLE_THROTTLE_MILLIS = 300000; // 5 mins when heater is idling +export const MAINTENANCE_RETRIEVAL_IDLING_THROTTLE_MILLIS = 600000; // 10 mins when heater is idling export const MANUFACTURER = 'Rinnai'; export const UNKNOWN = 'Unknown'; diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 201201e..6d6aacb 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -14,7 +14,7 @@ import { THERMOSTAT_CURRENT_TEMP_MAX_VALUE, THERMOSTAT_CURRENT_TEMP_MIN_VALUE, UNKNOWN, - MAINTENANCE_RETRIEVAL_IDLE_THROTTLE_MILLIS, + MAINTENANCE_RETRIEVAL_IDLING_THROTTLE_MILLIS, MAINTENANCE_RETRIEVAL_RUNNING_THROTTLE_MILLIS, API_VALUE_FALSE, THERMOSTAT_CURRENT_TEMP_STEP_VALUE, @@ -93,7 +93,7 @@ export class RinnaiControlrPlatformAccessory { this.platform.log.error(`Cannot extract details from ${JSON.stringify(this.accessory.context, null, 2)}`); } - this.platform.log.info(`Water Heater ${this.accessory.context.id}: ` + + this.platform.log.debug(`Water Heater ${this.accessory.context.id}: ` + `targetTemperature = ${this.targetTemperature}, ` + `outletTemperature = ${this.outletTemperature}, ` + `isRunning = ${this.isRunning}`); @@ -229,7 +229,7 @@ export class RinnaiControlrPlatformAccessory { this.extractDeviceInfo(); } - public pollMaintenanceInfoWhenIdling = _.throttle(this.pollMaintenanceInfo, MAINTENANCE_RETRIEVAL_IDLE_THROTTLE_MILLIS); + public pollMaintenanceInfoWhenIdling = _.throttle(this.pollMaintenanceInfo, MAINTENANCE_RETRIEVAL_IDLING_THROTTLE_MILLIS); public pollMaintenanceInfoWhenRunning = _.throttle(this.pollMaintenanceInfo, MAINTENANCE_RETRIEVAL_RUNNING_THROTTLE_MILLIS); async getTargetTemperature(): Promise> {