From e11b6fe7636b951d936c444c7153fbdf1f74fe19 Mon Sep 17 00:00:00 2001 From: Donavan Becker Date: Thu, 30 May 2024 23:10:17 -0500 Subject: [PATCH] v2.1.2 ## [2.1.2](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v2.1.2) (2024-05-30) ### What's Changes - Housekeeping and updated dependencies. **Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v2.1.1..v2.1.2 --- CHANGELOG.md | 7 + config.schema.json | 5 +- package-lock.json | 108 ++++++------- package.json | 4 +- src/devices/device.ts | 184 ++++++++++++++-------- src/devices/roomsensors.ts | 6 +- src/devices/roomsensorthermostats.ts | 126 ++++++++------- src/devices/thermostats.ts | 125 +++++++++------ src/devices/valve.ts | 2 +- src/platform.ts | 221 ++++++++++++++------------- src/settings.ts | 6 +- src/utils.ts | 15 +- 12 files changed, 452 insertions(+), 357 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b61298..bd1010af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. This project uses [Semantic Versioning](https://semver.org/) +## [2.1.2](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v2.1.2) (2024-05-30) + +### What's Changes +- Housekeeping and updated dependencies. + +**Full Changelog**: https://github.com/donavanbecker/homebridge-resideo/compare/v2.1.1..v2.1.2 + ## [2.1.1](https://github.com/donavanbecker/homebridge-resideo/releases/tag/v2.1.1) (2024-05-29) ### What's Changes diff --git a/config.schema.json b/config.schema.json index b6d74017..6abb12ae 100644 --- a/config.schema.json +++ b/config.schema.json @@ -402,10 +402,9 @@ } }, "pushRate": { - "title": "Device Refresh Rate", + "title": "Device Push Rate", "type": "number", - "minimum": 30, - "placeholder": 360, + "placeholder": 1, "description": "Indicates the number of seconds between pushes to the of Resideo API.", "condition": { "functionBody": "return (model.options && model.options.devices && !model.options.devices[arrayIndices].hide_device && (model.options.devices[arrayIndices].deviceClass === 'Thermostat' || model.options.devices[arrayIndices].deviceClass === 'LeakDetector') && model.options.devices[arrayIndices].deviceID);" diff --git a/package-lock.json b/package-lock.json index a0d59d8e..af3f424a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "homebridge-resideo", - "version": "2.1.1", + "version": "2.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "homebridge-resideo", - "version": "2.1.1", + "version": "2.1.2", "funding": [ { "type": "Paypal", @@ -38,7 +38,7 @@ "rimraf": "^5.0.7", "ts-node": "^10.9.2", "typescript": "^5.4.5", - "typescript-eslint": "^8.0.0-alpha.21" + "typescript-eslint": "^8.0.0-alpha.24" }, "engines": { "homebridge": "^1.8.2", @@ -1843,17 +1843,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.0.0-alpha.21", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0-alpha.21.tgz", - "integrity": "sha512-9oBIsyA4OXpMC8pC/mbuWc2tx1Ve7aOHfNU06o/qry40aki10pAQnJUDZR6VAq65S6kitA3NAqbJiF3P0X2MOA==", + "version": "8.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0-alpha.24.tgz", + "integrity": "sha512-14rK2L+ayITgprWmtaoI7ImzAZtHpnzQ7ujKJDQP6FrLSpd2Xv9ndViiG1XvhXYnwH1ppHGRZDzOkOMmDgp3Mg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.0.0-alpha.21", - "@typescript-eslint/type-utils": "8.0.0-alpha.21", - "@typescript-eslint/utils": "8.0.0-alpha.21", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.21", + "@typescript-eslint/scope-manager": "8.0.0-alpha.24", + "@typescript-eslint/type-utils": "8.0.0-alpha.24", + "@typescript-eslint/utils": "8.0.0-alpha.24", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.24", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1877,16 +1877,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.0.0-alpha.21", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.0-alpha.21.tgz", - "integrity": "sha512-1CXlfw6V0+Br6ILQln77zW+dxYeJcb3ZriGf//5K9GhKlrP6+Anylpz+b7sZCadBo7TVEs+sjHWOHLrFJTJMNQ==", + "version": "8.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.0-alpha.24.tgz", + "integrity": "sha512-Dpt40m3taG7Eidv6F1WLvMmt3mGDNib8l/rYiY+7CwjijgXYmwsX95W/P2+fW6LeBvB8ATIEcbSbdX7TUmcg5Q==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.0.0-alpha.21", - "@typescript-eslint/types": "8.0.0-alpha.21", - "@typescript-eslint/typescript-estree": "8.0.0-alpha.21", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.21", + "@typescript-eslint/scope-manager": "8.0.0-alpha.24", + "@typescript-eslint/types": "8.0.0-alpha.24", + "@typescript-eslint/typescript-estree": "8.0.0-alpha.24", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.24", "debug": "^4.3.4" }, "engines": { @@ -1906,14 +1906,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.0.0-alpha.21", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.21.tgz", - "integrity": "sha512-eDry04O2zfzKJaGSLdubah5FzvDIJHUdBaTUuvAgHpZUGZDez+5YHn1bKBdu9VlL/77VT3Hd2BbyDelTQY+Cwg==", + "version": "8.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.24.tgz", + "integrity": "sha512-ATe1bLKAyJ3alyrAoC0Wel1mnBThFB0/OBoXHp9GKoiTHdqJAhs2cCgZOgQWyJmWLiLFQHLyJj3EIFpoaDOX+Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.0.0-alpha.21", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.21" + "@typescript-eslint/types": "8.0.0-alpha.24", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.24" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1924,14 +1924,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.0.0-alpha.21", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.0-alpha.21.tgz", - "integrity": "sha512-1DDI/f86hRj+81WZqPQdXPOqmjxpcS77rNXhKkPNm/jPgBZxseBHeqUNKOqJfsroPtVYhBImNlCTNhKnggg49g==", + "version": "8.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.0-alpha.24.tgz", + "integrity": "sha512-jNh21K0/2aTekfPQAONImIEi0yTsLLH/dmOjuXLEX56QniTPoMKFrO9JYVUfjTRIlEHl8/F8VDv9yTINNcx/2w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.0.0-alpha.21", - "@typescript-eslint/utils": "8.0.0-alpha.21", + "@typescript-eslint/typescript-estree": "8.0.0-alpha.24", + "@typescript-eslint/utils": "8.0.0-alpha.24", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1949,9 +1949,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.0.0-alpha.21", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0-alpha.21.tgz", - "integrity": "sha512-Y3hfWmz7DMxGqBknJx0vgDflKz1xqzGp55TBm+wrTYSw8WN9U+dyxdImeSWTkP9jQxzMCXPCycVF/T28EF98Tw==", + "version": "8.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0-alpha.24.tgz", + "integrity": "sha512-MwBAoDe8nf1KrquszS586fHp+b9LV4jd2zEzwB6FdfLmJavyHrJGVFmCVSoDNZ40MqCQklgY78px6TXnKulCfg==", "dev": true, "license": "MIT", "engines": { @@ -1963,14 +1963,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.0.0-alpha.21", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.21.tgz", - "integrity": "sha512-SxSAhTSbVukLMARAnDYdRS+SUOjZKODJ2X0Vp2rjrz/ODNe8RR9lFEuRIMPMYYdvmnIzQX3szmv6GhUA1E9rng==", + "version": "8.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.24.tgz", + "integrity": "sha512-d/WTeR5eG9BboB9rPdcv7o8fZV4Jyy643Xxb9s0O9xX2X5oZrj5lqD7O/J+9aT9l/iE4U81sp1bceQKoUDJq0A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.0.0-alpha.21", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.21", + "@typescript-eslint/types": "8.0.0-alpha.24", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.24", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2018,16 +2018,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.0.0-alpha.21", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0-alpha.21.tgz", - "integrity": "sha512-diNDJahJQw0zHb7J03a0lYnuSYB9LZu5/esqFmK+vcd4e44eFE3AhDczGeHd8iwK5IPutlb0fedGv42YDWNEEw==", + "version": "8.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0-alpha.24.tgz", + "integrity": "sha512-Ph3Mvh+KRlf8zPmhyFqSpDVCyfcCfNd7mLujLWzXo/TgJfXbdjjs7CLv8yc+tmB7zwgiv/XIeul1iyYUVKjMEw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.0.0-alpha.21", - "@typescript-eslint/types": "8.0.0-alpha.21", - "@typescript-eslint/typescript-estree": "8.0.0-alpha.21" + "@typescript-eslint/scope-manager": "8.0.0-alpha.24", + "@typescript-eslint/types": "8.0.0-alpha.24", + "@typescript-eslint/typescript-estree": "8.0.0-alpha.24" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2041,13 +2041,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.0.0-alpha.21", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.21.tgz", - "integrity": "sha512-AoU9vC5I80BaZAzfbLIsfndoseqNAn4L1xfj1D7AcaPubmGqUG8A36LXu0VDOq6Yyko/W+G7V1+WjB4q/SoRNg==", + "version": "8.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.24.tgz", + "integrity": "sha512-9gu8YsifuPFXC1ZDPEXroGon1/IbSXD5bYFs6mmE8GwVo++Z1UTaO3tjTp+k/b85d8MBRkhetgBSFKKsIWetTw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.0.0-alpha.21", + "@typescript-eslint/types": "8.0.0-alpha.24", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -6660,9 +6660,9 @@ } }, "node_modules/node-abi": { - "version": "3.62.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", - "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", + "version": "3.63.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.63.0.tgz", + "integrity": "sha512-vAszCsOUrUxjGAmdnM/pq7gUgie0IRteCQMX6d4A534fQCR93EJU5qgzBvU6EkFfK27s0T3HEV3BOyJIr7OMYw==", "dev": true, "license": "MIT", "dependencies": { @@ -9692,15 +9692,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.0.0-alpha.21", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.0.0-alpha.21.tgz", - "integrity": "sha512-+QZQq/63wkxx7qouDAmQGRO/FCGYNe4lmrksXg4LcoO4+ndcrI2UM/PlmrISBgGiByALw97OqHOgLUNUYds6vw==", + "version": "8.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.0.0-alpha.24.tgz", + "integrity": "sha512-YZD7Uz/8oze7Eal4REHRrltl7I7dg6A05sIVzZnkppRXvmUPTSBOPNwKoEkmAursQiVMasjAIYHR166t6IS/2w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.0.0-alpha.21", - "@typescript-eslint/parser": "8.0.0-alpha.21", - "@typescript-eslint/utils": "8.0.0-alpha.21" + "@typescript-eslint/eslint-plugin": "8.0.0-alpha.24", + "@typescript-eslint/parser": "8.0.0-alpha.24", + "@typescript-eslint/utils": "8.0.0-alpha.24" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index 6baa8d64..a2f506ca 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "Resideo", "name": "homebridge-resideo", - "version": "2.1.1", + "version": "2.1.2", "description": "The Resideo plugin allows you to access your Resideo device(s) from HomeKit.", "author": { "name": "donavanbecker", @@ -81,6 +81,6 @@ "rimraf": "^5.0.7", "ts-node": "^10.9.2", "typescript": "^5.4.5", - "typescript-eslint": "^8.0.0-alpha.21" + "typescript-eslint": "^8.0.0-alpha.24" } } diff --git a/src/devices/device.ts b/src/devices/device.ts index 2715e5c5..e803b356 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -4,7 +4,7 @@ */ import type { ResideoPlatform } from '../platform.js'; import type { API, HAP, Logging, PlatformAccessory } from 'homebridge'; -import type { ResideoPlatformConfig, resideoDevice, location, devicesConfig } from '../settings.js'; +import type { ResideoPlatformConfig, resideoDevice, sensorAccessory, T9groups, location, devicesConfig } from '../settings.js'; export abstract class deviceBase { public readonly api: API; @@ -14,8 +14,8 @@ export abstract class deviceBase { // Config protected deviceLogging!: string; - protected deviceUpdateRate!: number; protected deviceRefreshRate!: number; + protected devicePushRate!: number; protected deviceMaxRetries!: number; protected deviceDelayBetweenRetries!: number; @@ -24,6 +24,8 @@ export abstract class deviceBase { protected accessory: PlatformAccessory, protected location: location, protected device: resideoDevice & devicesConfig, + public sensorAccessory?: sensorAccessory, + public readonly group?: T9groups, ) { this.api = this.platform.api; this.log = this.platform.log; @@ -32,17 +34,17 @@ export abstract class deviceBase { this.getDeviceLogSettings(device); - this.getDeviceRefreshRateSettings(device); + this.getDeviceRateSettings(device); this.getDeviceRetry(device); this.getDeviceConfigSettings(device); - this.getDeviceContext(accessory, device); + this.getDeviceContext(accessory, device, sensorAccessory); // Set accessory information accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.Manufacturer, 'Resideo') - .setCharacteristic(this.hap.Characteristic.Name, accessory.displayName) - .setCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName) + .setCharacteristic(this.hap.Characteristic.Name, accessory.context.name) + .setCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.context.name) .setCharacteristic(this.hap.Characteristic.Model, accessory.context.model) .setCharacteristic(this.hap.Characteristic.SerialNumber, accessory.context.deviceID); } @@ -63,23 +65,37 @@ export abstract class deviceBase { } } - async getDeviceRefreshRateSettings(device: resideoDevice & devicesConfig): Promise { + async getDeviceRateSettings(device: resideoDevice & devicesConfig): Promise { // refreshRate - if (device.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate; - this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`); - } else if (this.config.options!.refreshRate) { - this.deviceRefreshRate = this.accessory.context.refreshRate = this.config.options!.refreshRate; - this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`); + if (device.thermostat?.roomsensor?.refreshRate) { + this.deviceRefreshRate = device.thermostat.roomsensor.refreshRate; + this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Room Sensor Config Refresh Rate: ${this.deviceRefreshRate}`); + } else if (device.thermostat?.roompriority?.refreshRate) { + this.deviceRefreshRate = device.thermostat.roompriority.refreshRate; + this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Room Priority Config Refresh Rate: ${this.deviceRefreshRate}`); + } else if (device.refreshRate) { + this.deviceRefreshRate = this.accessory.context.deviceRefreshRate = device.refreshRate; + this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Device Config Refresh Rate: ${this.deviceRefreshRate}`); + } else { + this.deviceRefreshRate = this.config.options?.refreshRate ?? 120; + this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Platform Config Refresh Rate: ${this.deviceRefreshRate}`); } - // updateRate - if (device.updateRate) { - this.deviceUpdateRate = device.updateRate; - this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Device Config updateRate: ${this.deviceUpdateRate}`); + this.accessory.context.deviceRefreshRate = this.deviceRefreshRate; + // pushRate + if (device.thermostat?.roomsensor?.pushRate) { + this.devicePushRate = device.thermostat.roomsensor.pushRate; + this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Room Sensor Config Push Rate: ${this.devicePushRate}`); + } else if (device.thermostat?.roompriority?.pushRate) { + this.devicePushRate = device.thermostat.roompriority.pushRate; + this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Room Priority Config Push Rate: ${this.devicePushRate}`); + } else if (device.pushRate) { + this.devicePushRate = device.pushRate; + this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Device Config Push Rate: ${this.devicePushRate}`); } else { - this.deviceUpdateRate = this.config.options!.pushRate!; - this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Platform pushRate: ${this.deviceUpdateRate}`); + this.devicePushRate = this.config.options?.pushRate ?? 0.1; + this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Using Platform Push Rate: ${this.devicePushRate}`); } + this.accessory.context.devicePushRate = this.devicePushRate; } async getDeviceRetry(device: resideoDevice & devicesConfig): Promise { @@ -112,8 +128,8 @@ export abstract class deviceBase { if (device.refreshRate !== 0) { deviceConfig['refreshRate'] = device.refreshRate; } - if (device.updateRate !== 0) { - deviceConfig['updateRate'] = device.updateRate; + if (device.pushRate !== 0) { + deviceConfig['pushRate'] = device.pushRate; } if (device.retry === true) { deviceConfig['retry'] = device.retry; @@ -142,58 +158,92 @@ export abstract class deviceBase { } } - async getDeviceContext(accessory: PlatformAccessory, device: resideoDevice & devicesConfig): Promise { - accessory.context.name = device.userDefinedDeviceName ? device.userDefinedDeviceName : device.name; - accessory.context.model = device.deviceClass ? device.deviceClass : device.deviceModel; - accessory.context.deviceId = device.deviceID; - accessory.context.deviceType = device.deviceType; + async getDeviceContext(accessory: PlatformAccessory, device: resideoDevice & devicesConfig, sensorAccessory?: sensorAccessory): Promise { + if (sensorAccessory?.accessoryAttribute) { + accessory.context.name = sensorAccessory.accessoryAttribute.name; + accessory.context.model = sensorAccessory.accessoryAttribute.model; + accessory.context.deviceID = sensorAccessory.accessoryAttribute.serialNumber; + accessory.context.deviceType = sensorAccessory.accessoryAttribute.type; + } else if (device.deviceClass) { + accessory.context.name = device.userDefinedDeviceName ?? device.name; + accessory.context.model = device.deviceClass ?? device.deviceModel; + accessory.context.deviceID = device.deviceID; + accessory.context.deviceType = device.deviceType; + } - if (device.firmware) { - accessory.context.version = device.firmware; - } else if (device.firmware === undefined && device.firmwareVersion === undefined && device.thermostatVersion === undefined) { - accessory.context.version = this.platform.version; + // Firmware Version + let deviceFirmwareVersion: string; + if (sensorAccessory?.accessoryAttribute.softwareRevision) { + deviceFirmwareVersion = sensorAccessory.accessoryAttribute.softwareRevision; + } else if (device.firmware) { + deviceFirmwareVersion = device.firmware; + this.debugSuccessLog(`${device.deviceType}: ${accessory.displayName} 1 FirmwareRevision: ${device.firmware}`); + } else if (device.firmwareVersion) { + deviceFirmwareVersion = device.firmwareVersion; + this.debugSuccessLog(`${device.deviceType}: ${accessory.displayName} 2 FirmwareRevision: ${device.firmwareVersion}`); + } else if (device.thermostatVersion) { + deviceFirmwareVersion = device.thermostatVersion; + this.debugSuccessLog(`${device.deviceType}: ${accessory.displayName} 3 FirmwareRevision: ${device.thermostatVersion}`); + } else if (accessory.context.deviceVersion) { + deviceFirmwareVersion = accessory.context.deviceVersion; + this.debugSuccessLog(`${device.deviceType}: ${accessory.displayName} 4 FirmwareRevision: ${accessory.context.deviceVersion}`); } else { - accessory.context.version = device.firmwareVersion ? device.firmwareVersion : device.thermostatVersion; + deviceFirmwareVersion = this.platform.version ?? '0.0.0'; + if (this.platform.version) { + this.debugSuccessLog(`${device.deviceType}: ${accessory.displayName} 5 FirmwareRevision: ${this.platform.version}`); + } else { + this.debugSuccessLog(`${device.deviceType}: ${accessory.displayName} 6 FirmwareRevision: ${deviceFirmwareVersion}`); + } } - this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Firmware Version: ${accessory.context.version}`); - if (accessory.context.version) { - this.accessory - .getService(this.hap.Service.AccessoryInformation)! - .setCharacteristic(this.hap.Characteristic.HardwareRevision, accessory.context.version) - .setCharacteristic(this.hap.Characteristic.FirmwareRevision, accessory.context.version) - .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(this.accessory.context.version); + const version = deviceFirmwareVersion.toString(); + this.debugLog(`${this.device.deviceType}: ${accessory.displayName} Firmware Version: ${version?.replace(/^V|-.*$/g, '')}`); + let deviceVersion: string; + if (version?.includes('.') === false) { + const replace = version?.replace(/^V|-.*$/g, ''); + const match = replace?.match(/.{1,1}/g); + const validVersion = match?.join('.'); + deviceVersion = validVersion ?? '0.0.0'; + } else { + deviceVersion = version?.replace(/^V|-.*$/g, '') ?? '0.0.0'; } - this.debugSuccessLog(`${this.device.deviceClass}: ${this.accessory.displayName} Context: ${JSON.stringify(accessory.context)}`); + accessory + .getService(this.hap.Service.AccessoryInformation)! + .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) + .setCharacteristic(this.hap.Characteristic.SoftwareRevision, deviceVersion) + .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) + .getCharacteristic(this.hap.Characteristic.FirmwareRevision) + .updateValue(deviceVersion); + accessory.context.deviceVersion = deviceVersion; + this.debugSuccessLog(`${device.deviceType}: ${accessory.displayName} deviceVersion: ${accessory.context.deviceVersion}`); } async statusCode(statusCode: number, action: string): Promise { switch (statusCode) { case 200: - this.log.debug(`${this.device.deviceClass}: ${this.accessory.displayName} Standard Response, statusCode: ${statusCode}, Action: ${action}`); + this.debugLog(`${this.device.deviceClass}: ${this.accessory.displayName} Standard Response, statusCode: ${statusCode}, Action: ${action}`); break; case 400: - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Bad Request, statusCode: ${statusCode}, Action: ${action}`); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} Bad Request, statusCode: ${statusCode}, Action: ${action}`); break; case 401: - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Unauthorized, statusCode: ${statusCode}, Action: ${action}`); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} Unauthorized, statusCode: ${statusCode}, Action: ${action}`); break; case 403: this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} Forbidden, The request has been authenticated but does not ` + `have appropriate permissions, or a requested resource is not found, statusCode: ${statusCode}`); break; case 404: - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Not Found, statusCode: ${statusCode}, Action: ${action}`); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} Not Found, statusCode: ${statusCode}, Action: ${action}`); break; case 429: - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Too Many Requests, statusCode: ${statusCode}, Action: ${action}`); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} Too Many Requests, statusCode: ${statusCode}, Action: ${action}`); break; case 500: - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} Internal Server Error (Meater Server), statusCode: ${statusCode}, ` + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} Internal Server Error (Meater Server), statusCode: ${statusCode}, ` + `Action: ${action}`); break; default: - this.log.info(`${this.device.deviceClass}: ${this.accessory.displayName} Unknown statusCode: ${statusCode}, ` + this.infoLog(`${this.device.deviceClass}: ${this.accessory.displayName} Unknown statusCode: ${statusCode}, ` + `Action: ${action}, Report Bugs Here: https://bit.ly/homebridge-resideo-bug-report`); } } @@ -201,39 +251,39 @@ export abstract class deviceBase { async resideoAPIError(e: any, action: string): Promise { if (e.message.includes('400')) { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Bad Request`); - this.log.debug('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Bad Request`); + this.debugLog('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); } else if (e.message.includes('401')) { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Unauthorized Request`); - this.log.debug('Authorization for the API is required, but the request has not been authenticated.'); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Unauthorized Request`); + this.debugLog('Authorization for the API is required, but the request has not been authenticated.'); } else if (e.message.includes('403')) { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Forbidden Request`); - this.log.debug('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Forbidden Request`); + this.debugLog('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); } else if (e.message.includes('404')) { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Requst Not Found`); - this.log.debug('Specifies the requested path does not exist.'); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Requst Not Found`); + this.debugLog('Specifies the requested path does not exist.'); } else if (e.message.includes('406')) { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Request Not Acceptable`); - this.log.debug('The client has requested a MIME type via the Accept header for a value not supported by the server.'); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Request Not Acceptable`); + this.debugLog('The client has requested a MIME type via the Accept header for a value not supported by the server.'); } else if (e.message.includes('415')) { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Unsupported Requst Header`); - this.log.debug('The client has defined a contentType header that is not supported by the server.'); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Unsupported Requst Header`); + this.debugLog('The client has defined a contentType header that is not supported by the server.'); } else if (e.message.includes('422')) { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Unprocessable Entity`); - this.log.debug( + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Unprocessable Entity`); + this.debugLog( 'The client has made a valid request, but the server cannot process it.' + ' This is often used for APIs for which certain limits have been exceeded.'); } else if (e.message.includes('429')) { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Too Many Requests`); - this.log.debug('The client has exceeded the number of requests allowed for a given time window.'); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Too Many Requests`); + this.debugLog('The client has exceeded the number of requests allowed for a given time window.'); } else if (e.message.includes('500')) { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Internal Server Error`); - this.log.debug('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action}, Internal Server Error`); + this.debugLog('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); } else { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action},`); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to ${action},`); } if (this.deviceLogging.includes('debug')) { - this.log.error(`${this.device.deviceClass}: ${this.accessory.displayName} failed to pushChanges, Error Message: ${JSON.stringify(e.message)}`); + this.errorLog(`${this.device.deviceClass}: ${this.accessory.displayName} failed to pushChanges, Error Message: ${JSON.stringify(e.message)}`); } } diff --git a/src/devices/roomsensors.ts b/src/devices/roomsensors.ts index 2c07c7e8..6768a714 100644 --- a/src/devices/roomsensors.ts +++ b/src/devices/roomsensors.ts @@ -59,10 +59,10 @@ export class RoomSensors extends deviceBase { accessory: PlatformAccessory, location: location, device: resideoDevice & devicesConfig, - public sensorAccessory: sensorAccessory, - public readonly group: T9groups, + sensorAccessory: sensorAccessory, + readonly group: T9groups, ) { - super(platform, accessory, location, device); + super(platform, accessory, location, device, sensorAccessory, group); this.accessoryId = sensorAccessory.accessoryId; this.roomId = sensorAccessory.roomId; diff --git a/src/devices/roomsensorthermostats.ts b/src/devices/roomsensorthermostats.ts index 05cc2da5..53279718 100644 --- a/src/devices/roomsensorthermostats.ts +++ b/src/devices/roomsensorthermostats.ts @@ -58,10 +58,10 @@ export class RoomSensorThermostat extends deviceBase { accessory: PlatformAccessory, location: location, device: resideoDevice & devicesConfig, - public sensorAccessory: sensorAccessory, - public readonly group: T9groups, + sensorAccessory: sensorAccessory, + readonly group: T9groups, ) { - super(platform, accessory, location, device); + super(platform, accessory, location, device, sensorAccessory, group); // this is subject we use to track when we need to POST Room Priority changes to the Resideo API for Room Changes - T9 Only this.doRoomUpdate = new Subject(); @@ -77,7 +77,7 @@ export class RoomSensorThermostat extends deviceBase { Service: accessory.getService(this.hap.Service.Thermostat) ?? this.accessory.addService(this.hap.Service.Thermostat) as Service, TargetTemperature: accessory.context.TargetTemperature ?? 20, CurrentTemperature: accessory.context.CurrentTemperature ?? 20, - TemperatureDisplayUnits: accessory.context.TemperatureDisplayUnits ?? this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS, + TemperatureDisplayUnits: accessory.context.TemperatureDisplayUnits, TargetHeatingCoolingState: accessory.context.TargetHeatingCoolingState ?? this.hap.Characteristic.TargetHeatingCoolingState.AUTO, CurrentHeatingCoolingState: accessory.context.CurrentHeatingCoolingState ?? this.hap.Characteristic.CurrentHeatingCoolingState.OFF, CoolingThresholdTemperature: accessory.context.CoolingThresholdTemperature ?? 20, @@ -91,35 +91,46 @@ export class RoomSensorThermostat extends deviceBase { .setCharacteristic(this.hap.Characteristic.CurrentHeatingCoolingState, this.Thermostat.CurrentHeatingCoolingState) .getCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits) .onGet(() => { + this.Thermostat.TemperatureDisplayUnits = device.units === 'Celsius' + ? this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS : this.hap.Characteristic.TemperatureDisplayUnits.FAHRENHEIT; + accessory.context.TemperatureDisplayUnits = this.Thermostat.TemperatureDisplayUnits; + this.debugLog(`${this.device.deviceClass} ${accessory.displayName} TemperatureDisplayUnits: ${this.Thermostat.TemperatureDisplayUnits}`); return this.Thermostat.TemperatureDisplayUnits; }) .onSet(this.setTemperatureDisplayUnits.bind(this));; // Set Min and Max - if (device.changeableValues!.heatCoolMode === 'Heat') { - this.debugLog(`Room Sensor ${this.device.deviceClass} ${accessory.displayName} mode: ${device.changeableValues!.heatCoolMode}`); - this.Thermostat.Service - .getCharacteristic(this.hap.Characteristic.TargetTemperature) - .setProps({ - minValue: toCelsius(device.minHeatSetpoint!, Number(this.Thermostat.TemperatureDisplayUnits)), - maxValue: toCelsius(device.maxHeatSetpoint!, Number(this.Thermostat.TemperatureDisplayUnits)), - minStep: 0.5, - }) - .onGet(() => { - return this.Thermostat.TargetTemperature; - }); - } else { - this.debugLog(`Room Sensor ${this.device.deviceClass} ${accessory.displayName} mode: ${device.changeableValues!.heatCoolMode}`); - this.Thermostat.Service - .getCharacteristic(this.hap.Characteristic.TargetTemperature) - .setProps({ - minValue: toCelsius(device.minCoolSetpoint!, Number(this.Thermostat.TemperatureDisplayUnits)), - maxValue: toCelsius(device.maxCoolSetpoint!, Number(this.Thermostat.TemperatureDisplayUnits)), - minStep: 0.5, - }) - .onGet(() => { - return this.Thermostat.TargetTemperature; - }); + if (device.minHeatSetpoint && device.maxHeatSetpoint) { + this.debugLog(`${this.device.deviceClass} ${accessory.displayName} minHeatSetpoint: ${device.minHeatSetpoint},` + + ` maxHeatSetpoint: ${device.maxHeatSetpoint}, TemperatureDisplayUnits: ${this.Thermostat.TemperatureDisplayUnits}`); + const minValue = toCelsius(device.minHeatSetpoint, Number(this.Thermostat.TemperatureDisplayUnits)); + const maxValue = toCelsius(device.maxHeatSetpoint, Number(this.Thermostat.TemperatureDisplayUnits)); + this.debugLog(`Room Sensor ${this.device.deviceClass} ${accessory.displayName} minValue: ${minValue}, maxValue: ${maxValue}`); + if (device.changeableValues!.heatCoolMode === 'Heat') { + this.debugLog(`Room Sensor ${this.device.deviceClass} ${accessory.displayName} mode: ${device.changeableValues!.heatCoolMode}`); + this.Thermostat.Service + .getCharacteristic(this.hap.Characteristic.TargetTemperature) + .setProps({ + minValue: minValue, + maxValue: maxValue, + minStep: 0.5, + }) + .onGet(() => { + return this.Thermostat.TargetTemperature; + }); + } else { + this.debugLog(`Room Sensor ${this.device.deviceClass} ${accessory.displayName} mode: ${device.changeableValues!.heatCoolMode}`); + this.Thermostat.Service + .getCharacteristic(this.hap.Characteristic.TargetTemperature) + .setProps({ + minValue: minValue, + maxValue: maxValue, + minStep: 0.5, + }) + .onGet(() => { + return this.Thermostat.TargetTemperature; + }); + } } // The value property of TargetHeaterCoolerState must be one of the following: @@ -230,7 +241,7 @@ export class RoomSensorThermostat extends deviceBase { tap(() => { this.roomUpdateInProgress = true; }), - debounceTime(this.deviceUpdateRate * 500), + debounceTime(this.devicePushRate * 500), ) .subscribe(async () => { try { @@ -280,7 +291,7 @@ export class RoomSensorThermostat extends deviceBase { tap(() => { this.thermostatUpdateInProgress = true; }), - debounceTime(this.deviceUpdateRate * 1000)) + debounceTime(this.devicePushRate * 1000)) .subscribe(async () => { try { await this.pushChanges(); @@ -324,7 +335,7 @@ export class RoomSensorThermostat extends deviceBase { // Parse the Sensor Accessory status if (sensorAccessory) { const accessoryValue = sensorAccessory.accessoryValue as accessoryValue - ?? { indoorTemperature: 20, indoorHumidity: 50 }; + ?? { indoorTemperature: 20, indoorHumidity: 50 }; this.Thermostat.CurrentTemperature = toCelsius(accessoryValue.indoorTemperature, Number(this.Thermostat.TemperatureDisplayUnits)); @@ -334,7 +345,7 @@ export class RoomSensorThermostat extends deviceBase { if (this.HumiditySensor) { this.HumiditySensor!.CurrentRelativeHumidity = accessoryValue.indoorHumidity; this.debugLog(`Room Sensor ${this.device.deviceClass} ${this.accessory.displayName}` - + ` CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}`); + + ` CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}`); } } } @@ -392,21 +403,21 @@ export class RoomSensorThermostat extends deviceBase { locationId: this.location.locationID, }, })).data; - /* - const { body, statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}`, { - method: 'GET', - query: { - 'locationId': this.location.locationID, - 'apikey': this.config.credentials?.consumerKey, - }, - headers: { - 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, - 'Content-Type': 'application/json', - }, - }); - const action = 'refreshStatus'; - await this.statusCode(statusCode, action); - const deviceStatus: any = await body.json();*/ + /* + const { body, statusCode } = await request(`${DeviceURL}/thermostats/${this.device.deviceID}`, { + method: 'GET', + query: { + 'locationId': this.location.locationID, + 'apikey': this.config.credentials?.consumerKey, + }, + headers: { + 'Authorization': `Bearer ${this.config.credentials?.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + const action = 'refreshStatus'; + await this.statusCode(statusCode, action); + const deviceStatus: any = await body.json();*/ this.debugLog(`Room Sensor ${device.deviceClass} ${this.accessory.displayName} (refreshStatus) device: ${JSON.stringify(device)}`); this.debugLog(`Room Sensor ${device.deviceClass} ${this.accessory.displayName}` + ` Fetched update for: ${this.device.name} from Resideo API: ${JSON.stringify(this.device.changeableValues)}`); @@ -453,10 +464,10 @@ export class RoomSensorThermostat extends deviceBase { if (sensorAccessory.accessoryAttribute.type.startsWith('IndoorAirSensor')) { this.parseStatus(this.device, sensorAccessory); this.debugLog(`Room Sensor ${this.device.deviceClass} ${this.accessory.displayName}` - + ` accessoryAttribute: ${JSON.stringify(this.sensorAccessory.accessoryAttribute)}`); + + ` accessoryAttribute: ${JSON.stringify(this.sensorAccessory?.accessoryAttribute)}`); this.debugLog(`Room Sensor ${this.device.deviceClass} ${this.accessory.displayName} Name: ` - + `${this.sensorAccessory.accessoryAttribute.name},` - + ` Software Version: ${this.sensorAccessory.accessoryAttribute.softwareRevision}`); + + `${this.sensorAccessory?.accessoryAttribute.name},` + + ` Software Version: ${this.sensorAccessory?.accessoryAttribute.softwareRevision}`); } } } @@ -520,8 +531,8 @@ export class RoomSensorThermostat extends deviceBase { */ async pushRoomChanges(): Promise { this.debugLog(`Room Sensor ${this.device.deviceClass} ${this.accessory.displayName} Room Priority, - Current Room: ${JSON.stringify(this.roomPriorityStatus.currentPriority.selectedRooms)}, Changing Room: [${this.sensorAccessory.accessoryId}]`); - if (`[${this.sensorAccessory.accessoryId}]` !== `[${this.roomPriorityStatus.currentPriority.selectedRooms}]`) { + Current Room: ${JSON.stringify(this.roomPriorityStatus.currentPriority.selectedRooms)}, Changing Room: [${this.sensorAccessory?.accessoryId}]`); + if (`[${this.sensorAccessory?.accessoryId}]` !== `[${this.roomPriorityStatus.currentPriority.selectedRooms}]`) { const payload = { currentPriority: { priorityType: this.device.thermostat?.roompriority?.priorityType, @@ -529,7 +540,7 @@ export class RoomSensorThermostat extends deviceBase { } as any; if (this.device.thermostat?.roompriority?.priorityType === 'PickARoom') { - payload.currentPriority.selectedRooms = [this.sensorAccessory.accessoryId]; + payload.currentPriority.selectedRooms = [this.sensorAccessory?.accessoryId]; } /** @@ -546,8 +557,8 @@ export class RoomSensorThermostat extends deviceBase { this.successLog(`Room Sensor ${this.device.deviceClass} ${this.accessory.displayName} sent request to Resideo API,` + ` Priority Type: ${this.device.thermostat?.roompriority.priorityType}`); } else if (this.device.thermostat?.roompriority.priorityType === 'PickARoom') { - this.successLog(`Room Sensor ${this.device.deviceClass} ${this.accessory.displayName} sent request to Resideo API,` - + ` Room Priority: ${this.sensorAccessory.accessoryAttribute.name}, Priority Type: ${this.device.thermostat?.roompriority.priorityType}`); + this.successLog(`Room Sensor ${this.device.deviceClass} ${this.accessory.displayName} sent request to Resideo API, Room Priority: ` + + `${this.sensorAccessory?.accessoryAttribute.name}, Priority Type: ${this.device.thermostat?.roompriority.priorityType}`); } // Make the API request @@ -806,7 +817,10 @@ export class RoomSensorThermostat extends deviceBase { async setTemperatureDisplayUnits(value: CharacteristicValue): Promise { this.debugLog(`Room Sensor ${this.device.deviceClass} ${this.accessory.displayName} Set TemperatureDisplayUnits: ${value}`); - this.log.warn('Changing the Hardware Display Units from HomeKit is not supported.'); + this.warnLog('Changing the Hardware Display Units from HomeKit is not supported.'); + + this.Thermostat.TemperatureDisplayUnits = this.device.units === 'Celsius' + ? this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS : this.hap.Characteristic.TemperatureDisplayUnits.FAHRENHEIT; // change the temp units back to the one the Resideo API said the thermostat was set to setTimeout(() => { diff --git a/src/devices/thermostats.ts b/src/devices/thermostats.ts index e2370d0b..40bc4721 100644 --- a/src/devices/thermostats.ts +++ b/src/devices/thermostats.ts @@ -97,7 +97,7 @@ export class Thermostats extends deviceBase { Service: accessory.getService(this.hap.Service.Thermostat) ?? this.accessory.addService(this.hap.Service.Thermostat) as Service, TargetTemperature: accessory.context.TargetTemperature ?? 20, CurrentTemperature: accessory.context.CurrentTemperature ?? 20, - TemperatureDisplayUnits: accessory.context.TemperatureDisplayUnits ?? this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS, + TemperatureDisplayUnits: accessory.context.TemperatureDisplayUnits, TargetHeatingCoolingState: accessory.context.TargetHeatingCoolingState ?? this.hap.Characteristic.TargetHeatingCoolingState.AUTO, CurrentHeatingCoolingState: accessory.context.CurrentHeatingCoolingState ?? this.hap.Characteristic.CurrentHeatingCoolingState.OFF, CoolingThresholdTemperature: accessory.context.CoolingThresholdTemperature ?? 20, @@ -111,35 +111,50 @@ export class Thermostats extends deviceBase { .setCharacteristic(this.hap.Characteristic.CurrentHeatingCoolingState, this.Thermostat.CurrentHeatingCoolingState) .getCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits) .onGet(() => { + this.Thermostat.TemperatureDisplayUnits = device.units === 'Celsius' + ? this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS : this.hap.Characteristic.TemperatureDisplayUnits.FAHRENHEIT; + accessory.context.TemperatureDisplayUnits = this.Thermostat.TemperatureDisplayUnits; + this.debugLog(`${this.device.deviceClass} ${accessory.displayName} TemperatureDisplayUnits: ${this.Thermostat.TemperatureDisplayUnits}`); return this.Thermostat.TemperatureDisplayUnits; }) .onSet(this.setTemperatureDisplayUnits.bind(this));; // Set Min and Max - if (device.changeableValues!.heatCoolMode === 'Heat') { - this.debugLog(`Thermostat: ${accessory.displayName} is in "${device.changeableValues!.heatCoolMode}" mode`); - this.Thermostat.Service - .getCharacteristic(this.hap.Characteristic.TargetTemperature) - .setProps({ - minValue: toCelsius(device.minHeatSetpoint!, Number(this.Thermostat.TemperatureDisplayUnits)), - maxValue: toCelsius(device.maxHeatSetpoint!, Number(this.Thermostat.TemperatureDisplayUnits)), - minStep: 0.1, - }) - .onGet(() => { - return this.Thermostat.TargetTemperature; - }); - } else { - this.debugLog(`Thermostat: ${accessory.displayName} is in "${device.changeableValues!.heatCoolMode}" mode`); - this.Thermostat.Service - .getCharacteristic(this.hap.Characteristic.TargetTemperature) - .setProps({ - minValue: toCelsius(device.minCoolSetpoint!, Number(this.Thermostat.TemperatureDisplayUnits)), - maxValue: toCelsius(device.maxCoolSetpoint!, Number(this.Thermostat.TemperatureDisplayUnits)), - minStep: 0.1, - }) - .onGet(() => { - return this.Thermostat.TargetTemperature; - }); + if (device.minHeatSetpoint && device.maxHeatSetpoint) { + this.debugLog(`${this.device.deviceClass} ${accessory.displayName} minHeatSetpoint: ${device.minHeatSetpoint},` + + ` maxHeatSetpoint: ${device.maxHeatSetpoint}, TemperatureDisplayUnits: ${this.Thermostat.TemperatureDisplayUnits}`); + const minValue = toCelsius(device.minHeatSetpoint, Number(this.Thermostat.TemperatureDisplayUnits)); + const maxValue = toCelsius(device.maxHeatSetpoint, Number(this.Thermostat.TemperatureDisplayUnits)); + this.debugLog(`${this.device.deviceClass} ${accessory.displayName} minValue: ${minValue}, maxValue: ${maxValue}`); + if (device.changeableValues!.heatCoolMode === 'Heat') { + this.debugLog(`Thermostat: ${accessory.displayName} is in "${device.changeableValues?.heatCoolMode}" mode`); + this.Thermostat.Service + .getCharacteristic(this.hap.Characteristic.TargetTemperature) + .setProps({ + minValue: minValue, + maxValue: maxValue, + minStep: 0.1, + }) + .onGet(() => { + return this.Thermostat.TargetTemperature; + }); + this.debugWarnLog(`Thermostat: ${accessory.displayName} (HEAT) Min Heat Setpoint: ${device.minHeatSetpoint}. Max Heat Setpoint:` + + ` ${device.maxHeatSetpoint} TemperatureDisplayUnits: ${this.Thermostat.TemperatureDisplayUnits}`); + } else { + this.debugLog(`Thermostat: ${accessory.displayName} is in "${device.changeableValues?.heatCoolMode}" mode`); + this.Thermostat.Service + .getCharacteristic(this.hap.Characteristic.TargetTemperature) + .setProps({ + minValue: minValue, + maxValue: maxValue, + minStep: 0.1, + }) + .onGet(() => { + return this.Thermostat.TargetTemperature; + }); + this.debugWarnLog(`Thermostat: ${accessory.displayName} (NOT HEAT) Min Heat Setpoint: ${device.minHeatSetpoint}. Max Heat Setpoint:` + + ` ${device.maxHeatSetpoint} TemperatureDisplayUnits: ${this.Thermostat.TemperatureDisplayUnits}`); + } } // The value property of TargetHeaterCoolerState must be one of the following: @@ -317,7 +332,7 @@ export class Thermostats extends deviceBase { tap(() => { this.roomUpdateInProgress = true; }), - debounceTime(this.deviceUpdateRate * 500), + debounceTime(this.devicePushRate * 500), ) .subscribe(async () => { try { @@ -352,7 +367,7 @@ export class Thermostats extends deviceBase { tap(() => { this.thermostatUpdateInProgress = true; }), - debounceTime(this.deviceUpdateRate * 1000)) + debounceTime(this.devicePushRate * 1000)) .subscribe(async () => { try { await this.pushChanges(); @@ -386,7 +401,7 @@ export class Thermostats extends deviceBase { tap(() => { this.fanUpdateInProgress = true; }), - debounceTime(this.deviceUpdateRate * 1000), + debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { @@ -434,7 +449,7 @@ export class Thermostats extends deviceBase { ` TemperatureDisplayUnits: ${this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS}`); } - this.Thermostat.CurrentTemperature = toCelsius(device.indoorTemperature!, Number(this.Thermostat.TemperatureDisplayUnits)); + this.Thermostat.CurrentTemperature = await toCelsius(device.indoorTemperature!, Number(this.Thermostat.TemperatureDisplayUnits)); this.debugLog(`${device.deviceClass} ${this.accessory.displayName} parseStatus` + ` CurrentTemperature: ${toCelsius(device.indoorTemperature!, Number(this.Thermostat.TemperatureDisplayUnits))}`); @@ -447,14 +462,15 @@ export class Thermostats extends deviceBase { } if (device.changeableValues!.heatSetpoint > 0) { - this.Thermostat.HeatingThresholdTemperature = toCelsius(this.device.changeableValues!.heatSetpoint, + this.Thermostat.HeatingThresholdTemperature = await toCelsius(this.device.changeableValues!.heatSetpoint, Number(this.Thermostat.TemperatureDisplayUnits)); this.debugLog(`${device.deviceClass} ${this.accessory.displayName} parseStatus` + ` HeatingThresholdTemperature: ${toCelsius(device.changeableValues!.heatSetpoint, Number(this.Thermostat.TemperatureDisplayUnits))}`); } if (device.changeableValues!.coolSetpoint > 0) { - this.Thermostat.CoolingThresholdTemperature = toCelsius(device.changeableValues!.coolSetpoint, Number(this.Thermostat.TemperatureDisplayUnits)); + this.Thermostat.CoolingThresholdTemperature = await toCelsius(device.changeableValues!.coolSetpoint, + Number(this.Thermostat.TemperatureDisplayUnits)); this.debugLog(`${device.deviceClass} ${this.accessory.displayName} parseStatus` + ` CoolingThresholdTemperature: ${toCelsius(device.changeableValues!.coolSetpoint, Number(this.Thermostat.TemperatureDisplayUnits))}`); } @@ -487,7 +503,8 @@ export class Thermostats extends deviceBase { // Set the TargetTemperature value based on the current mode if (this.Thermostat.TargetHeatingCoolingState === this.hap.Characteristic.TargetHeatingCoolingState.HEAT) { if (device.changeableValues!.heatSetpoint > 0) { - this.Thermostat.TargetTemperature = toCelsius(this.device.changeableValues!.heatSetpoint, Number(this.Thermostat.TemperatureDisplayUnits)); + this.Thermostat.TargetTemperature = await toCelsius(this.device.changeableValues!.heatSetpoint, + Number(this.Thermostat.TemperatureDisplayUnits)); this.debugLog( `${device.deviceClass} ${this.accessory.displayName}` + ` parseStatus TargetTemperature (HEAT): ${toCelsius(this.device.changeableValues!.heatSetpoint, @@ -495,13 +512,16 @@ export class Thermostats extends deviceBase { ); } } else { - if (device.changeableValues!.coolSetpoint > 0) { - this.Thermostat.TargetTemperature = toCelsius(this.device.changeableValues!.coolSetpoint, Number(this.Thermostat.TemperatureDisplayUnits)); - this.debugLog( - `${this.device.deviceClass} ${this.accessory.displayName}` + - ` parseStatus TargetTemperature (OFF/COOL): ${toCelsius(this.device.changeableValues!.coolSetpoint, + if (device.changeableValues) { + if (device.changeableValues.coolSetpoint > 0) { + this.Thermostat.TargetTemperature = await toCelsius(device.changeableValues.coolSetpoint, + Number(this.Thermostat.TemperatureDisplayUnits)); + this.debugLog( + `${this.device.deviceClass} ${this.accessory.displayName}` + + ` parseStatus TargetTemperature (OFF/COOL): ${toCelsius(device.changeableValues.coolSetpoint, Number(this.Thermostat.TemperatureDisplayUnits))})`, - ); + ); + } } } @@ -727,7 +747,7 @@ export class Thermostats extends deviceBase { switch (this.device.deviceModel) { case 'Unknown': this.errorLog(JSON.stringify(this.device)); - payload.thermostatSetpoint = toFahrenheit(Number(this.Thermostat.TargetTemperature), Number(this.Thermostat.TemperatureDisplayUnits)); + payload.thermostatSetpoint = await toFahrenheit(Number(this.Thermostat.TargetTemperature), Number(this.Thermostat.TemperatureDisplayUnits)); switch (this.device.units) { case 'Fahrenheit': payload.unit = 'Fahrenheit'; @@ -746,9 +766,9 @@ export class Thermostats extends deviceBase { // Set the heat and cool set point value based on the selected mode switch (this.Thermostat.TargetHeatingCoolingState) { case this.hap.Characteristic.TargetHeatingCoolingState.HEAT: - payload.heatSetpoint = toFahrenheit(Number(this.Thermostat.TargetTemperature), + payload.heatSetpoint = await toFahrenheit(Number(this.Thermostat.TargetTemperature), Number(this.Thermostat.TemperatureDisplayUnits)); - payload.coolSetpoint = toFahrenheit(Number(this.Thermostat.CoolingThresholdTemperature), + payload.coolSetpoint = await toFahrenheit(Number(this.Thermostat.CoolingThresholdTemperature), Number(this.Thermostat.TemperatureDisplayUnits)); this.debugLog(`${this.device.deviceClass} ${this.accessory.displayName} TargetHeatingCoolingState (HEAT):` + ` ${this.Thermostat.TargetHeatingCoolingState}, TargetTemperature: ${toFahrenheit(Number(this.Thermostat.TargetTemperature), @@ -757,9 +777,9 @@ export class Thermostats extends deviceBase { Number(this.Thermostat.TemperatureDisplayUnits))} coolSetpoint`); break; case this.hap.Characteristic.TargetHeatingCoolingState.COOL: - payload.coolSetpoint = toFahrenheit(Number(this.Thermostat.TargetTemperature), + payload.coolSetpoint = await toFahrenheit(Number(this.Thermostat.TargetTemperature), Number(this.Thermostat.TemperatureDisplayUnits)); - payload.heatSetpoint = toFahrenheit(Number(this.Thermostat.HeatingThresholdTemperature), + payload.heatSetpoint = await toFahrenheit(Number(this.Thermostat.HeatingThresholdTemperature), Number(this.Thermostat.TemperatureDisplayUnits)); this.debugLog(`${this.device.deviceClass} ${this.accessory.displayName} TargetHeatingCoolingState (COOL): ` + `${this.Thermostat.TargetHeatingCoolingState}, TargetTemperature: ${toFahrenheit(Number(this.Thermostat.TargetTemperature), @@ -768,9 +788,9 @@ export class Thermostats extends deviceBase { Number(this.Thermostat.TemperatureDisplayUnits))} heatSetpoint`); break; case this.hap.Characteristic.TargetHeatingCoolingState.AUTO: - payload.coolSetpoint = toFahrenheit(Number(this.Thermostat.CoolingThresholdTemperature), + payload.coolSetpoint = await toFahrenheit(Number(this.Thermostat.CoolingThresholdTemperature), Number(this.Thermostat.TemperatureDisplayUnits)); - payload.heatSetpoint = toFahrenheit(Number(this.Thermostat.HeatingThresholdTemperature), + payload.heatSetpoint = await toFahrenheit(Number(this.Thermostat.HeatingThresholdTemperature), Number(this.Thermostat.TemperatureDisplayUnits)); this.debugLog(`${this.device.deviceClass} ${this.accessory.displayName} TargetHeatingCoolingState (AUTO): ` + `${this.Thermostat.TargetHeatingCoolingState}, CoolingThresholdTemperature: ` @@ -780,9 +800,9 @@ export class Thermostats extends deviceBase { Number(this.Thermostat.TemperatureDisplayUnits))} heatSetpoint`); break; default: - payload.coolSetpoint = toFahrenheit(Number(this.Thermostat.CoolingThresholdTemperature), + payload.coolSetpoint = await toFahrenheit(Number(this.Thermostat.CoolingThresholdTemperature), Number(this.Thermostat.TemperatureDisplayUnits)); - payload.heatSetpoint = toFahrenheit(Number(this.Thermostat.HeatingThresholdTemperature), + payload.heatSetpoint = await toFahrenheit(Number(this.Thermostat.HeatingThresholdTemperature), Number(this.Thermostat.TemperatureDisplayUnits)); this.debugLog(`${this.device.deviceClass} ${this.accessory.displayName} TargetHeatingCoolingState (OFF): ` + `${this.Thermostat.TargetHeatingCoolingState}, CoolingThresholdTemperature: ` @@ -993,7 +1013,7 @@ export class Thermostats extends deviceBase { if (!this.device.thermostat?.hide_humidity) { if (this.device.indoorHumidity) { if (this.HumiditySensor?.CurrentRelativeHumidity === undefined) { - this.log.debug(`${this.device.deviceClass} ${this.accessory.displayName}` + this.debugLog(`${this.device.deviceClass} ${this.accessory.displayName}` + ` CurrentRelativeHumidity: ${this.HumiditySensor?.CurrentRelativeHumidity}`); } else { this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, @@ -1058,9 +1078,11 @@ export class Thermostats extends deviceBase { // Set the TargetTemperature value based on the selected mode if (this.Thermostat.TargetHeatingCoolingState === this.hap.Characteristic.TargetHeatingCoolingState.HEAT) { - this.Thermostat.TargetTemperature = toCelsius(this.device.changeableValues!.heatSetpoint, Number(this.Thermostat.TemperatureDisplayUnits)); + this.Thermostat.TargetTemperature = await toCelsius(this.device.changeableValues!.heatSetpoint, + Number(this.Thermostat.TemperatureDisplayUnits)); } else { - this.Thermostat.TargetTemperature = toCelsius(this.device.changeableValues!.coolSetpoint, Number(this.Thermostat.TemperatureDisplayUnits)); + this.Thermostat.TargetTemperature = await toCelsius(this.device.changeableValues!.coolSetpoint, + Number(this.Thermostat.TemperatureDisplayUnits)); } this.Thermostat.Service.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.Thermostat.TargetTemperature); if (this.device.thermostat?.roompriority?.deviceType === 'Thermostat' && this.device.deviceModel === 'T9-T10') { @@ -1093,6 +1115,9 @@ export class Thermostats extends deviceBase { this.debugLog(`${this.device.deviceClass} ${this.accessory.displayName} Set TemperatureDisplayUnits: ${value}`); this.warnLog('Changing the Hardware Display Units from HomeKit is not supported.'); + this.Thermostat.TemperatureDisplayUnits = this.device.units === 'Celsius' + ? this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS : this.hap.Characteristic.TemperatureDisplayUnits.FAHRENHEIT; + // change the temp units back to the one the Resideo API said the thermostat was set to setTimeout(() => { this.Thermostat.Service.updateCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits, Number(this.Thermostat.TemperatureDisplayUnits)); diff --git a/src/devices/valve.ts b/src/devices/valve.ts index a6fce072..bc2e34a2 100644 --- a/src/devices/valve.ts +++ b/src/devices/valve.ts @@ -96,7 +96,7 @@ export class Valve extends deviceBase { tap(() => { this.valveUpdateInProgress = true; }), - debounceTime(this.deviceUpdateRate * 1000)) + debounceTime(this.devicePushRate * 1000)) .subscribe(async () => { try { await this.pushChanges(); diff --git a/src/platform.ts b/src/platform.ts index fb27c305..66dd93f9 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -130,10 +130,10 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.apiError(e); } } else { - this.log.error('Failed to Discover Locations. Re-Link Your Resideo Account.'); + this.errorLog('Failed to Discover Locations. Re-Link Your Resideo Account.'); } } else { - this.log.error('Missing Access Token. Re-Link Your Resideo Account.'); + this.errorLog('Missing Access Token. Re-Link Your Resideo Account.'); } }); } @@ -174,24 +174,16 @@ export class ResideoPlatform implements DynamicPlatformPlugin { throw new Error('Refresh Rate must be above 30 seconds.'); } - if (this.config.disablePlugin) { - this.log.error('Plugin is disabled.'); - } - - if (!this.config.options.refreshRate && !this.config.disablePlugin) { + if (!this.config.options.refreshRate) { // default 120 seconds (2 minutes) this.config.options.refreshRate = 120; - if (this.platformLogging?.includes('debug')) { - this.warnLog('Using Default Refresh Rate of 2 Minutes.'); - } + this.debugWarnLog('Using Default Refresh Rate of 2 Minutes.'); } - if (!this.config.options.pushRate && !this.config.disablePlugin) { + if (!this.config.options.pushRate) { // default 100 milliseconds this.config.options.pushRate = 0.1; - if (this.platformLogging?.includes('debug')) { - this.warnLog('Using Default Push Rate.'); - } + this.debugWarnLog('Using Default Push Rate.'); } if (!this.config.credentials) { @@ -495,7 +487,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } } else { - this.log.error('Failed to Discover Locations. Re-Link Your Resideo Account.'); + this.errorLog('Failed to Discover Locations. Re-Link Your Resideo Account.'); } } @@ -549,7 +541,6 @@ export class ResideoPlatform implements DynamicPlatformPlugin { if (sensorAccessory.accessoryAttribute.model === '0') { sensorAccessory.accessoryAttribute.model = '4352'; } - sensorAccessory.deviceID = `${sensorAccessory.accessoryId}${sensorAccessory.roomId}${sensorAccessory.accessoryAttribute.model}`; this.createRoomSensors(location, device, group, sensorAccessory); this.createRoomSensorThermostat(location, device, group, sensorAccessory); } @@ -568,10 +559,10 @@ export class ResideoPlatform implements DynamicPlatformPlugin { * Room Priority * This will display what room priority option that has been selected. */ - if (device.thermostat?.roompriority.deviceType && !device.hide_device && !this.config.disablePlugin) { + if (device.thermostat?.roompriority.deviceType && !device.hide_device) { this.warnLog('Displaying Thermostat(s) for Each Room Sensor(s).'); } - if (!device.thermostat?.roompriority.deviceType && !device.hide_device && !this.config.disablePlugin) { + if (!device.thermostat?.roompriority.deviceType && !device.hide_device) { this.warnLog('Only Displaying Room Sensor(s).'); } } @@ -589,7 +580,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { if (existingAccessory) { // the accessory already exists - if (!device.hide_device && !this.config.disablePlugin) { + if (await this.registerDevice(device)) { this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} DeviceID: ${device.deviceID}`); // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: @@ -606,7 +597,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } else { this.unregisterPlatformAccessories(existingAccessory); } - } else if (!device.hide_device && !this.config.disablePlugin) { + } else if (await this.registerDevice(device)) { // the accessory does not yet exist, so we need to create it if (!device.external) { this.infoLog(`Adding new accessory: ${device.userDefinedDeviceName} ${device.deviceClass} Device ID: ${device.deviceID}`); @@ -629,14 +620,12 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.externalOrPlatform(device, accessory); this.accessories.push(accessory); } else { - if (this.platformLogging?.includes('debug')) { - this.log.error(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceModel} ` + `DeviceID: ${device.deviceID}`); - this.log.error('Check Config to see if DeviceID is being Hidden.'); - } + this.debugErrorLog(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceModel} DeviceID: ` + + `${device.deviceID}, Check Config to see if DeviceID is being Hidden.`); } } - private createLeak(location: location, device: resideoDevice & devicesConfig) { + private async createLeak(location: location, device: resideoDevice & devicesConfig) { const uuid = this.api.hap.uuid.generate(`${device.deviceID}-${device.deviceClass}`); // see if an accessory with the same uuid has already been registered and restored from @@ -645,7 +634,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { if (existingAccessory) { // the accessory already exists - if (!device.hide_device && !this.config.disablePlugin) { + if (await this.registerDevice(device)) { this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} DeviceID: ${device.deviceID}`); // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: @@ -662,7 +651,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } else { this.unregisterPlatformAccessories(existingAccessory); } - } else if (!device.hide_device && !this.config.disablePlugin) { + } else if (await this.registerDevice(device)) { // the accessory does not yet exist, so we need to create it if (!device.external) { this.infoLog(`Adding new accessory: ${device.userDefinedDeviceName} ${device.deviceClass} Device ID: ${device.deviceID}`); @@ -688,14 +677,12 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.externalOrPlatform(device, accessory); this.accessories.push(accessory); } else { - if (this.platformLogging?.includes('debug')) { - this.log.error(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceType} ` + ` DeviceID: ${device.deviceID}`); - this.log.error('Check Config to see if DeviceID is being Hidden.'); - } + this.debugErrorLog(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceType} DeviceID: ` + + `${device.deviceID}, Check Config to see if DeviceID is being Hidden.`); } } - private createValve(location: location, device: resideoDevice & devicesConfig) { + private async createValve(location: location, device: resideoDevice & devicesConfig) { const uuid = this.api.hap.uuid.generate(`${device.deviceID}-${device.deviceClass}`); // see if an accessory with the same uuid has already been registered and restored from @@ -704,7 +691,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { if (existingAccessory) { // the accessory already exists - if (!device.hide_device && !this.config.disablePlugin) { + if (await this.registerDevice(device)) { this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} DeviceID: ${device.deviceID}`); // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: @@ -721,7 +708,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } else { this.unregisterPlatformAccessories(existingAccessory); } - } else if (!device.hide_device && !this.config.disablePlugin) { + } else if (await this.registerDevice(device)) { // the accessory does not yet exist, so we need to create it if (!device.external) { this.infoLog(`Adding new accessory: ${device.userDefinedDeviceName} ${device.deviceClass} Device ID: ${device.deviceID}`); @@ -747,31 +734,31 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.externalOrPlatform(device, accessory); this.accessories.push(accessory); } else { - if (this.platformLogging?.includes('debug')) { - this.log.error(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceType} ` + ` DeviceID: ${device.deviceID}`); - this.log.error('Check Config to see if DeviceID is being Hidden.'); - } + this.debugErrorLog(`Unable to Register new device: ${device.userDefinedDeviceName} ${device.deviceType} DeviceID: ` + + `${device.deviceID}, Check Config to see if DeviceID is being Hidden.`); } } - private createRoomSensors( + private async createRoomSensors( location: location, device: resideoDevice & devicesConfig, group: T9groups, sensorAccessory: sensorAccessory, ) { // Room Sensor(s) - const uuid = this.api.hap.uuid.generate(`${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryId}-RoomSensor`); + const uuid = + this.api.hap.uuid.generate(`${sensorAccessory.accessoryAttribute.type}-${sensorAccessory.accessoryAttribute.serialNumber}-RoomSensor`); const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); if (existingAccessory) { // the accessory already exists - if (!device.hide_device && !device.thermostat?.roomsensor?.hide_roomsensor && !this.config.disablePlugin) { - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} DeviceID: ${sensorAccessory.deviceID}`); + if (await this.registerDevice(device)) { + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}` + + ` Serial Number: ${sensorAccessory.accessoryAttribute.serialNumber}`); // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: existingAccessory.displayName = sensorAccessory.accessoryAttribute.name; - existingAccessory.context.deviceID = sensorAccessory.deviceID; + existingAccessory.context.deviceID = sensorAccessory.accessoryAttribute.serialNumber; existingAccessory.context.model = sensorAccessory.accessoryAttribute.model; this.roomsensorFirmwareExistingAccessory(existingAccessory, sensorAccessory); this.api.updatePlatformAccessories([existingAccessory]); @@ -786,11 +773,11 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } else { this.unregisterPlatformAccessories(existingAccessory); } - } else if (!device.hide_device && !device.thermostat?.roomsensor?.hide_roomsensor && !this.config.disablePlugin) { + } else if (await this.registerDevice(device)) { // the accessory does not yet exist, so we need to create it if (!device.external) { this.infoLog(`Adding new accessory: ${sensorAccessory.accessoryAttribute.name} ${sensorAccessory.accessoryAttribute.type} ` + - `Device ID: ${sensorAccessory.deviceID}`); + `Device ID: ${sensorAccessory.accessoryAttribute.serialNumber}`); } // create a new accessory @@ -798,7 +785,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.deviceID = sensorAccessory.deviceID; + accessory.context.deviceID = sensorAccessory.accessoryAttribute.serialNumber; accessory.context.model = sensorAccessory.accessoryAttribute.model; this.roomsensorFirmwareNewAccessory(accessory, sensorAccessory); @@ -812,17 +799,12 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.externalOrPlatform(device, accessory); this.accessories.push(accessory); } else { - if (this.platformLogging?.includes('debug')) { - this.log.error( - `Unable to Register new device: ${sensorAccessory.accessoryAttribute.name} ${sensorAccessory.accessoryAttribute.type} ` + - `DeviceID: ${sensorAccessory.deviceID}`, - ); - this.log.error('Check Config to see if DeviceID is being Hidden.'); - } + this.errorLog(`Unable to Register new device: ${sensorAccessory.accessoryAttribute.name} ${sensorAccessory.accessoryAttribute.type} ` + + `Serial Number: ${sensorAccessory.accessoryAttribute.serialNumber}, Check Config to see if DeviceID is being Hidden.`); } } - private createRoomSensorThermostat( + private async createRoomSensorThermostat( location: location, device: resideoDevice & devicesConfig, group: T9groups, @@ -836,12 +818,13 @@ export class ResideoPlatform implements DynamicPlatformPlugin { if (existingAccessory) { // the accessory already exists - if (device.thermostat?.roompriority?.deviceType && !device.hide_device && !this.config.disablePlugin) { - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} DeviceID: ${sensorAccessory.deviceID}`); + if (await this.registerDevice(device)) { + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}` + + ` Serial Number: ${sensorAccessory.accessoryAttribute.serialNumber}`); // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: existingAccessory.displayName = sensorAccessory.accessoryAttribute.name; - existingAccessory.context.deviceID = sensorAccessory.deviceID; + existingAccessory.context.deviceID = sensorAccessory.accessoryAttribute.serialNumber; existingAccessory.context.model = sensorAccessory.accessoryAttribute.model; this.roomsensorFirmwareExistingAccessory(existingAccessory, sensorAccessory); this.api.updatePlatformAccessories([existingAccessory]); @@ -856,11 +839,11 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } else { this.unregisterPlatformAccessories(existingAccessory); } - } else if (device.thermostat?.roompriority?.deviceType && !device.hide_device && !this.config.disablePlugin) { + } else if (await this.registerDevice(device)) { // the accessory does not yet exist, so we need to create it if (!device.external) { this.infoLog(`Adding new accessory: ${sensorAccessory.accessoryAttribute.name} ${sensorAccessory.accessoryAttribute.type} ` + - `Device ID: ${sensorAccessory.deviceID}`); + `Serial Number: ${sensorAccessory.accessoryAttribute.serialNumber}`); } // create a new accessory @@ -868,7 +851,7 @@ export class ResideoPlatform implements DynamicPlatformPlugin { // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.deviceID = sensorAccessory.deviceID; + accessory.context.deviceID = sensorAccessory.accessoryAttribute.serialNumber; accessory.context.model = sensorAccessory.accessoryAttribute.model; this.roomsensorFirmwareNewAccessory(accessory, sensorAccessory); @@ -883,16 +866,36 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.externalOrPlatform(device, accessory); this.accessories.push(accessory); } else { - if (this.platformLogging?.includes('debug')) { - this.log.error( - `Unable to Register new device: ${sensorAccessory.accessoryAttribute.name} ${sensorAccessory.accessoryAttribute.type} ` + - `DeviceID: ${sensorAccessory.deviceID}`, - ); - this.log.error('Check Config to see if DeviceID is being Hidden.'); - } + this.debugErrorLog(`Unable to Register new device: ${sensorAccessory.accessoryAttribute.name} ${sensorAccessory.accessoryAttribute.type} ` + + `Serial Number: ${sensorAccessory.accessoryAttribute.serialNumber}, Check Config to see if DeviceID is being Hidden.`); } } + async registerDevice(device: resideoDevice & devicesConfig) { + let registerDevice: boolean; + this.debugLog(`Device: ${device.userDefinedDeviceName} hide_roomsensor: ${device.thermostat?.roomsensor?.hide_roomsensor},` + + ` roompriority: ${device.thermostat?.roompriority?.deviceType}, hide_device: ${device.hide_device}`); + if (!device.thermostat?.roomsensor?.hide_roomsensor) { + registerDevice = true; + this.debugSuccessLog(`Device: ${device.userDefinedDeviceName} deviceID: ${device.deviceID}, registerDevice: ${registerDevice}`); + } else if (device.thermostat?.roompriority?.deviceType) { + registerDevice = true; + this.debugSuccessLog(`Device: ${device.userDefinedDeviceName} deviceID: ${device.deviceID}, registerDevice: ${registerDevice}`); + } else if (!device.hide_device) { + registerDevice = true; + this.debugSuccessLog(`Device: ${device.userDefinedDeviceName} deviceID: ${device.deviceID}, registerDevice: ${registerDevice}`); + } else { + registerDevice = false; + this.debugSuccessLog(`Device: ${device.userDefinedDeviceName} deviceID: ${device.deviceID}, registerDevice: ${registerDevice}`); + } + if (registerDevice === true) { + this.debugWarnLog(`Device: ${device.userDefinedDeviceName} will display in HomeKit`); + } else { + this.debugErrorLog(`Device: ${device.userDefinedDeviceName} will not display in HomeKit`); + } + return registerDevice; + } + private leaksensorFirmwareNewAccessory(device: resideoDevice & devicesConfig, accessory: PlatformAccessory) { if (device.firmware) { accessory.context.firmwareRevision = device.firmware; @@ -1005,40 +1008,38 @@ export class ResideoPlatform implements DynamicPlatformPlugin { apiError(e: any) { if (e.message.includes('400')) { - this.log.error(`Failed to ${this.action}: Bad Request`); + this.errorLog(`Failed to ${this.action}: Bad Request`); this.debugLog('The client has issued an invalid request. This is commonly used to specify validation errors in a request payload.'); } else if (e.message.includes('401')) { - this.log.error(`Failed to ${this.action}: Unauthorized Request`); + this.errorLog(`Failed to ${this.action}: Unauthorized Request`); this.debugLog('Authorization for the API is required, but the request has not been authenticated.'); } else if (e.message.includes('403')) { - this.log.error(`Failed to ${this.action}: Forbidden Request`); + this.errorLog(`Failed to ${this.action}: Forbidden Request`); this.debugLog('The request has been authenticated but does not have appropriate permissions, or a requested resource is not found.'); } else if (e.message.includes('404')) { - this.log.error(`Failed to ${this.action}: Requst Not Found`); + this.errorLog(`Failed to ${this.action}: Requst Not Found`); this.debugLog('Specifies the requested path does not exist.'); } else if (e.message.includes('406')) { - this.log.error(`Failed to ${this.action}: Request Not Acceptable`); + this.errorLog(`Failed to ${this.action}: Request Not Acceptable`); this.debugLog('The client has requested a MIME type via the Accept header for a value not supported by the server.'); } else if (e.message.includes('415')) { - this.log.error(`Failed to ${this.action}: Unsupported Requst Header`); + this.errorLog(`Failed to ${this.action}: Unsupported Requst Header`); this.debugLog('The client has defined a contentType header that is not supported by the server.'); } else if (e.message.includes('422')) { - this.log.error(`Failed to ${this.action}: Unprocessable Entity`); + this.errorLog(`Failed to ${this.action}: Unprocessable Entity`); this.debugLog( 'The client has made a valid request, but the server cannot process it.' + ' This is often used for APIs for which certain limits have been exceeded.'); } else if (e.message.includes('429')) { - this.log.error(`Failed to ${this.action}: Too Many Requests`); + this.errorLog(`Failed to ${this.action}: Too Many Requests`); this.debugLog('The client has exceeded the number of requests allowed for a given time window.'); } else if (e.message.includes('500')) { - this.log.error(`Failed to ${this.action}: Internal Server Error`); + this.errorLog(`Failed to ${this.action}: Internal Server Error`); this.debugLog('An unexpected error on the SmartThings servers has occurred. These errors should be rare.'); } else { - this.log.error(`Failed to ${this.action}`); - } - if (this.platformLogging?.includes('debug')) { - this.log.error(`Failed to ${this.action}, Error Message: ${stringify(e.message)}`); + this.errorLog(`Failed to ${this.action}`); } + this.debugErrorLog(`Failed to ${this.action}, Error Message: ${stringify(e.message)}`); } async statusCode(statusCode: number, action: string): Promise { @@ -1047,19 +1048,19 @@ export class ResideoPlatform implements DynamicPlatformPlugin { this.debugLog(`Standard Response, statusCode: ${statusCode}, Action: ${action}`); break; case 400: - this.log.error(`Bad Request, statusCode: ${statusCode}, Action: ${action}`); + this.errorLog(`Bad Request, statusCode: ${statusCode}, Action: ${action}`); break; case 401: - this.log.error(`Unauthorized, statusCode: ${statusCode}, Action: ${action}`); + this.errorLog(`Unauthorized, statusCode: ${statusCode}, Action: ${action}`); break; case 404: - this.log.error(`Not Found, statusCode: ${statusCode}, Action: ${action}`); + this.errorLog(`Not Found, statusCode: ${statusCode}, Action: ${action}`); break; case 429: - this.log.error(`Too Many Requests, statusCode: ${statusCode}, Action: ${action}`); + this.errorLog(`Too Many Requests, statusCode: ${statusCode}, Action: ${action}`); break; case 500: - this.log.error(`Internal Server Error (Meater Server), statusCode: ${statusCode}, Action: ${action}`); + this.errorLog(`Internal Server Error (Meater Server), statusCode: ${statusCode}, Action: ${action}`); break; default: this.infoLog(`Unknown statusCode: ${statusCode}, Report Bugs Here: https://bit.ly/homebridge-resideo-bug-report. Action: ${action}`); @@ -1095,14 +1096,10 @@ export class ResideoPlatform implements DynamicPlatformPlugin { } } else if (this.debugMode) { this.platformLogging = 'debugMode'; - if (this.platformLogging?.includes('debug')) { - this.debugWarnLog(`Using ${this.platformLogging} Logging`); - } + this.debugWarnLog(`Using ${this.platformLogging} Logging`); } else { this.platformLogging = 'standard'; - if (this.platformLogging?.includes('debug')) { - this.debugWarnLog(`Using ${this.platformLogging} Logging`); - } + this.debugWarnLog(`Using ${this.platformLogging} Logging`); } if (this.debugMode) { this.platformLogging = 'debugMode'; @@ -1123,51 +1120,65 @@ export class ResideoPlatform implements DynamicPlatformPlugin { * If device level logging is turned on, log to log.warn * Otherwise send debug logs to log.debug */ - infoLog(...log: any[]) { - if (this.enablingPlatfromLogging()) { + infoLog(...log: any[]): void { + if (this.enablingPlatformLogging()) { this.log.info(String(...log)); } } - warnLog(...log: any[]) { - if (this.enablingPlatfromLogging()) { + successLog(...log: any[]): void { + if (this.enablingPlatformLogging()) { + this.log.success(String(...log)); + } + } + + debugSuccessLog(...log: any[]): void { + if (this.enablingPlatformLogging()) { + if (this.platformLogging?.includes('debug')) { + this.log.success('[DEBUG]', String(...log)); + } + } + } + + warnLog(...log: any[]): void { + if (this.enablingPlatformLogging()) { this.log.warn(String(...log)); } } debugWarnLog(...log: any[]): void { - if (this.enablingPlatfromLogging()) { + if (this.enablingPlatformLogging()) { if (this.platformLogging?.includes('debug')) { - this.log.warn('[WARN]', String(...log)); + this.log.warn('[DEBUG]', String(...log)); } } } - errorLog(...log: any[]) { - if (this.enablingPlatfromLogging()) { + errorLog(...log: any[]): void { + if (this.enablingPlatformLogging()) { this.log.error(String(...log)); } } debugErrorLog(...log: any[]): void { - if (this.enablingPlatfromLogging()) { + if (this.enablingPlatformLogging()) { if (this.platformLogging?.includes('debug')) { - this.log.error('[ERROR]', String(...log)); + this.log.error('[DEBUG]', String(...log)); } } } - debugLog(...log: any[]) { - if (this.enablingPlatfromLogging()) { + debugLog(...log: any[]): void { + if (this.enablingPlatformLogging()) { if (this.platformLogging === 'debugMode') { - this.log.debug('[HOMEBRIDGE DEBUGMODE]', String(...log)); + this.log.debug(String(...log)); } else if (this.platformLogging === 'debug') { this.log.info('[DEBUG]', String(...log)); } } } - enablingPlatfromLogging(): boolean { + enablingPlatformLogging(): boolean { return this.platformLogging?.includes('debug') || this.platformLogging === 'standard'; } } diff --git a/src/settings.ts b/src/settings.ts index 9425578b..fe3b8355 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -3,6 +3,7 @@ * settings.ts: homebridge-resideo. */ import type { PlatformConfig } from 'homebridge'; + /** * This is the name of the platform that users will use to register the plugin in the Homebridge config.json */ @@ -65,7 +66,7 @@ export interface devicesConfig extends resideoDevice { external?: boolean; logging?: string; refreshRate?: number; - updateRate?: number; + pushRate?: number; maxRetries?: number; delayBetweenRetries?: number; retry?: boolean; @@ -96,6 +97,7 @@ export type roomsensor = { hide_humidity?: boolean; logging?: string; refreshRate?: number; + pushRate?: number; }; export type roompriority = { @@ -103,6 +105,7 @@ export type roompriority = { priorityType?: string; logging?: string; refreshRate?: number; + pushRate?: number; }; export type valve = { @@ -357,7 +360,6 @@ export type sensorAccessory = { accessoryAttribute: accessoryAttribute; accessoryValue: accessoryValue; roomId: number; - deviceID: string; }; export type accessoryAttribute = { diff --git a/src/utils.ts b/src/utils.ts index fe4de36f..91522cba 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,19 +3,6 @@ * util.ts: homebridge-resideo platform class. */ -/** - * Converts the value to celsius if the temperature units are in Fahrenheit -**/ -export function convertUnits(value: number, unit: string, convert?: string): number { - if (unit === 'CELSIUS' && convert === 'CELSIUS') { - return Math.round((value * 9) / 5 + 32); - } else if (unit === 'FAHRENHEIT' && convert === 'FAHRENHEIT') { - // celsius should be to the nearest 0.5 degree - return Math.round((5 / 9) * (value - 32) * 2) / 2; - } - return value; -} - /** * Converts the value to celsius if the temperature units are in Fahrenheit */ @@ -52,7 +39,7 @@ export enum ResideoModes { Off = 'Off', Heat = 'Heat', Cool = 'Cool', - Auto = 'Auto' + Auto = 'Auto', }; /*