From 300f28e5988fbd9ac11ec2000264640a9bf2391c Mon Sep 17 00:00:00 2001 From: David Kerr Date: Wed, 17 Jul 2024 17:27:02 -0400 Subject: [PATCH] Add support for newer WaterMeterController devices as a HomeKit valve --- README.md | 23 ++- config.schema.json | 89 +++++++++-- package.json | 2 +- src/deviceHandlers.ts | 5 +- src/garageDoor.ts | 2 +- src/platformAccessory.ts | 2 + src/valveDevice.ts | 330 ++++++++++++++++++++++++++++++++++----- src/yolinkAPI.ts | 10 +- 8 files changed, 395 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 3ffba41..554b18d 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Currently supports the following devices: * Switch * Temperature and Humidity Sensor * Vibration Sensor (as a motion sensor) +* Water Meter Controller (as a valve) The plugin registers as a MQTT client and subscribes to reports published by YoLink for real time alerts and status updates. @@ -103,6 +104,7 @@ If you see an error message in the log similar to the following then you are lik "doublePress": 800, "powerFailureSensorAs": "Outlet", "deviceTemperatures": false, + "useWaterFlowing": false, "devices": [ { "deviceId": "0123456789abcdef", @@ -113,6 +115,7 @@ If you see an error message in the log similar to the following then you are lik "doublePress": 800, "nOutlets": 5, "temperature": false, + "useWaterFlowing": false, "enableExperimental": false } } @@ -147,6 +150,7 @@ If you see an error message in the log similar to the following then you are lik * **doublePress** *(optional)*: Duration in milliseconds to trigger a double-press event on two button presses on a stateless device. Defaults to 800ms and a value of zero disables double-press feature. See notes below for YoLink FlexFob remote. * **powerFailureSensorAs** *(optional)*: How to represent the YoLink power failure alarm sensor in HomeKit, can be either *Outlet* or *Contact*, defaults to Outlet. * **deviceTemperatures** *(optional)*: If set to true then create a temperature service for those devices that report temperature in addition to their main function. See Device Notes below. + * **useWaterFlowing** *(optional)*: If set to true then the plugin will use the *waterFlowing* status from YoLink *WaterMeterController* valves to report the *InUse* status to HomeKit. See Device Notes below. * **devices** *(optional)*: Optional array of device settings, see below. * **garageDoors** *(optional)*: Optional array of sensor/controller pairs, see below. @@ -158,7 +162,8 @@ If you see an error message in the log similar to the following then you are lik * **refreshAfter** *(optional)*: Device specific override of global *refreshAfter*, see above. Defaults to global setting. * **doublePress** *(optional)*: Device specific override of global *doublePress*, see above. Defaults to global setting. * **nOutlets** *(optional)*: For power strip or multi-outlet devices, number of controllable outlets. See device notes below. - * **temperature** *(optional)*: If set to true then create a temperature service in addition to the main function. See device notes below. + * **temperature** *(optional)*: If set to true then create a temperature service in addition to the main function. See Device Notes below. + * **useWaterFlowing** *(optional)*: Device specific override of global *useWaterFlowing*, see above. Defaults to global setting. * **enableExperimental** *(optional)*: Device specific override of global *enableExperimental*, see above. Defaults to global setting. * **garageDoors** are an array of objects that allow you to pair two devices, either a *GarageDoor* or *Finger* controller with a *DoorSensor* that together represent a single garage door. The garage door inherits properties of the individual devices. The garage door *name* is taken from the controller device. See device notes below. @@ -232,9 +237,7 @@ The YoLink Smart Lock M1 can be locked and unlocked from Homebridge/HomeKit and ### Manipulator / Water Valve Controller -YoLink water valve controllers report as a *Manipulator* device, the plugin registers this as a HomeKit generic valve. HomeKit has the concept of both open/close and in use where in use means that fluid is actually flowing through the device. Presumably this allows for a valve to be open, but no fluid to flow. YoLink only reports open/close and so the plugin uses this state for both valve position and in use (fluid flowing). Normal status reporting occurs every 4 hours. If you want to check on device status more frequently then set *refreshAfter* to desired interval. - -I have observed *Can't connect to Device* errors from YoLink when trying to retrieve device status. When these occur the plugin attempts to connect again, up to 5 times, before giving up. +YoLink water valve controllers report as a *Manipulator* device, the plugin registers this as a HomeKit generic valve. HomeKit has the concept of both open/close and in-use where in-use means that fluid is actually flowing through the device. Presumably this allows for a valve to be open, but no fluid to flow. YoLink only reports open/close and so the plugin uses this state for both valve position and in-use (fluid flowing). Normal status reporting occurs every 4 hours. If you want to check on device status more frequently then set *refreshAfter* to desired interval. ### Motion Sensor @@ -283,6 +286,16 @@ HomeKit does not have a vibration sensor device type so this plugin registers th YoLink vibration sensors also report device temperature. If you set the *temperature* configuration setting to true then a Homebridge/HomeKit service is created to make this visible to the app. The name has "Temperature" appended to the end. +### Water Meter Controller + +YoLink water meter and valve controllers report as a *WaterMeterController* device, the plugin registers this as a HomeKit generic valve. HomeKit has the concept of both open/close and in-use where in-use means that fluid is actually flowing through the device. This allows for a valve to be open, but no fluid to flow. + +The *YS-5008-UC* valve does report whether water is flowing. However these devices currently do not update this status in real time and this can confuse Apple Home which will repport "Waiting..." after you open the valve -- as it waits for the in-use characteristic to indicate water flowing. Therefore, by default, this plugin will report in-use based on whether the value is open or closed. You can set the config *useWaterFlowing* setting to change this to use the water flowing status from the device. + +This plugin expects to receive status updates from YoLink devices when status changes. As this device is not currently doing this for *waterFlowing*, an alternative would be to change the *refreshAfter* config setting for this device only (do not change it globally) and the plugin will poll the device for status more regularly. The minimum time you can set this to is 60 seconds. However this is *not recommended* as it places additional load on the LoRa protocol and the affects on device battery life are unknown. + +A Leak Sensor and, if supported, a Temperature Sensor service is added with this device. The name of each has "Leak" and "Temperature" appended to the end. *YS-5006-UC* is known not to report temperature. + ### Unsupported Devices If you have a device not supported by the plugin then useful information will be logged as warnings, including callback messages received for any alerts or status changes triggered by the device. Please capture these logs and report to the author by opening a [issue](https://github.com/dkerr64/homebridge-yolink/issues). @@ -324,7 +337,7 @@ Many log messages carry two timestamps. The first is current time and is logged ## License -(c) Copyright 2022-2023 David A. Kerr +(c) Copyright 2022-2024 David A. Kerr Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/config.schema.json b/config.schema.json index 722b7a3..d2b7ccd 100644 --- a/config.schema.json +++ b/config.schema.json @@ -19,7 +19,7 @@ "required": true, "default": "YoLink" }, - "tokenURL": { + "tokenURL": { "title": "YoLink authentication server URL address", "type": "string", "required": true, @@ -124,7 +124,8 @@ "SmartRemoter", "Switch", "THSensor", - "VibrationSensor" + "VibrationSensor", + "WaterMeterController" ] }, "default": [ @@ -160,7 +161,8 @@ "SmartRemoter", "Switch", "THSensor", - "VibrationSensor" + "VibrationSensor", + "WaterMeterController" ] } }, @@ -185,15 +187,30 @@ "type": "boolean", "required": false, "description": "Some YoLink devices report temperature in addition to their main function. Currently these are LeakSensors, Vibration & Motion Sensors, Flex Fob and Smoke & CO Alarms. This will create a temperature sensor for those." - + }, + "useWaterFlowing": { + "title": "Use waterFlowing status from YoLink water valves", + "type": "boolean", + "required": false, + "description": "YoLink water meter controller valves can report whether water is flowing. This sets whether to use this when informing HomeKit that a valve is InUse. Default is to assume InUse if valve is open." }, "powerFailureSensorAs": { "title": "Represent YoLink Power Failure Alarm as", "type": "string", "required": false, "oneOf": [ - { "title": "Outlet device", "enum": ["Outlet"] }, - { "title": "Contact sensor", "enum": ["Contact"] } + { + "title": "Outlet device", + "enum": [ + "Outlet" + ] + }, + { + "title": "Contact sensor", + "enum": [ + "Contact" + ] + } ], "description": "HomeKit does not have the concept of a power failure sensor so we much choose either an Outlet or Contact sensor to represent the YoLink device." }, @@ -216,7 +233,7 @@ "type": "string", "required": true, "default": "", - "description":"You can find the Device ID in the Homebridge log, in the YoLink mobile app (as Device EUI) or in the Homebridge Config UI by clicking on an accessory's settings and copying the Serial Number field." + "description": "You can find the Device ID in the Homebridge log, in the YoLink mobile app (as Device EUI) or in the Homebridge Config UI by clicking on an accessory's settings and copying the Serial Number field." }, "config": { "title": "Device Configuration", @@ -227,12 +244,42 @@ "type": "string", "required": false, "oneOf": [ - { "title": "false", "enum": ["false"] }, - { "title": "true", "enum": ["true"] }, - { "title": "Hydrometer", "enum": ["hydro"] }, - { "title": "Thermometer", "enum": ["thermo"] }, - { "title": "CO Sensor", "enum": ["co"] }, - { "title": "Smoke Sensor", "enum": ["smoke"] } + { + "title": "false", + "enum": [ + "false" + ] + }, + { + "title": "true", + "enum": [ + "true" + ] + }, + { + "title": "Hydrometer", + "enum": [ + "hydro" + ] + }, + { + "title": "Thermometer", + "enum": [ + "thermo" + ] + }, + { + "title": "CO Sensor", + "enum": [ + "co" + ] + }, + { + "title": "Smoke Sensor", + "enum": [ + "smoke" + ] + } ], "description": "Hide this device from Homebridge / HomeKit. You might want to do this to suppress the 'device not supported' warning message in the log. As there is no accessory type in HomeKit for a hub, you can set this to true for the YoLink hub." }, @@ -241,7 +288,7 @@ "type": "string", "required": false, "default": "", - "description":"This will override the device name returned by YoLink for display in the Homebridge accessories page" + "description": "This will override the device name returned by YoLink for display in the Homebridge accessories page" }, "refreshAfter": { "title": "Refresh time for data from YoLink server (override global setting)", @@ -269,8 +316,14 @@ "type": "boolean", "required": false, "description": "Some YoLink devices report temperature in addition to their main function. Currently these are LeakSensors, Vibration & Motion Sensors, Flex Fob and Smoke & CO Alarms. This will create a temperature sensor for those." + }, + "useWaterFlowing": { + "title": "Use waterFlowing status from YoLink water valves", + "type": "boolean", + "required": false, + "description": "YoLink water meter controller valves can report whether water is flowing. This sets whether to use this when informing HomeKit that a valve is InUse. Default is to assume InUse if valve is open." } - } + } } } } @@ -304,7 +357,7 @@ } } }, - "description":"Pair two devices together into a single Garage Door accessory. You can find the Device ID in the Homebridge log, in the YoLink mobile app (as Device EUI) or in the Homebridge Config UI by clicking on an accessory's settings and copying the Serial Number field." + "description": "Pair two devices together into a single Garage Door accessory. You can find the Device ID in the Homebridge log, in the YoLink mobile app (as Device EUI) or in the Homebridge Config UI by clicking on an accessory's settings and copying the Serial Number field." } } }, @@ -337,6 +390,7 @@ "refreshAfter", "doublePress", "deviceTemperatures", + "useWaterFlowing", "powerFailureSensorAs", "verboseLog", "liteLog", @@ -373,7 +427,8 @@ "devices[].config.refreshAfter", "devices[].config.doublePress", "devices[].config.nOutlets", - "devices[].config.temperature" + "devices[].config.temperature", + "devices[].config.useWaterFlowing" ] } ] diff --git a/package.json b/package.json index fb6c984..63bf4f2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "Homebridge YoLink", "name": "homebridge-yolink", - "version": "1.5.7", + "version": "1.6.0", "description": "Connect to YoLink.", "license": "Apache-2.0", "repository": { diff --git a/src/deviceHandlers.ts b/src/deviceHandlers.ts index fbf4780..cb0eccf 100644 --- a/src/deviceHandlers.ts +++ b/src/deviceHandlers.ts @@ -3,7 +3,7 @@ /*********************************************************************** * YoLink device list * - * Copyright (c) 2022 David Kerr + * Copyright (c) 2022-2024 David Kerr * */ import { initUnknownDevice, mqttUnknownDevice } from './unknownDevice'; @@ -47,6 +47,7 @@ export const deviceFeatures = { Dimmer: { hasBattery: false }, InfraredRemoter: { hasBattery: true }, COSmokeSensor: { hasBattery: true }, + WaterMeterController: { hasBattery: true }, }; export const initDeviceService = { @@ -72,6 +73,7 @@ export const initDeviceService = { Dimmer(this: YoLinkPlatformAccessory) { initLightbulb.bind(this)('open', 'open', 'close'); }, InfraredRemoter(this: YoLinkPlatformAccessory) { initInfraredRemoter.bind(this)(); }, COSmokeSensor(this: YoLinkPlatformAccessory) { initCoSmokeDetector.bind(this)(); }, + WaterMeterController(this: YoLinkPlatformAccessory) { initValveDevice.bind(this)(); }, }; export const mqttHandler = { @@ -97,4 +99,5 @@ export const mqttHandler = { Dimmer(this: YoLinkPlatformAccessory, data) { mqttLightbulb.bind(this)(data); }, InfraredRemoter(this: YoLinkPlatformAccessory, data) { mqttInfraredRemoter.bind(this)(data); }, COSmokeSensor(this: YoLinkPlatformAccessory, data) { mqttCoSmokeDetector.bind(this)(data); }, + WaterMeterController(this: YoLinkPlatformAccessory, data) { mqttValveDevice.bind(this)(data); }, }; diff --git a/src/garageDoor.ts b/src/garageDoor.ts index 15c9433..7e9fb9f 100644 --- a/src/garageDoor.ts +++ b/src/garageDoor.ts @@ -1,7 +1,7 @@ /*********************************************************************** * YoLink Garage Door device support * - * Copyright (c) 2022-2023 David Kerr + * Copyright (c) 2022-2024 David Kerr * */ diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 57112bb..61b4a5f 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -115,6 +115,8 @@ export class YoLinkPlatformAccessory { device.timeout ??= 45; // targetState used to track if garage door has been requested to open or close device.targetState = ''; + // Use waterFlowing status from waterMeterControllers? + device.config.useWaterFlowing = platform.makeBoolean(device.config.useWaterFlowing, platform.config.useWaterFlowing); } /********************************************************************* diff --git a/src/valveDevice.ts b/src/valveDevice.ts index adfdc19..9f49b1e 100644 --- a/src/valveDevice.ts +++ b/src/valveDevice.ts @@ -1,7 +1,7 @@ /*********************************************************************** * YoLink manipulator (e.g. water valve) device support * - * Copyright (c) 2022 David Kerr + * Copyright (c) 2022-2024 David Kerr * */ @@ -24,16 +24,46 @@ export async function initValveDevice(this: YoLinkPlatformAccessory): Promise { +async function handleGet(this: YoLinkPlatformAccessory, devSensor = 'valve'): Promise { // wrapping the semaphone blocking function so that we return to Homebridge immediately // even if semaphore not available. const platform: YoLinkHomebridgePlatform = this.platform; const device: YoLinkDevice = this.accessory.context.device; - handleGetBlocking.bind(this, request)() + handleGetBlocking.bind(this, devSensor)() .then((v) => { - if (request === 'Active') { - this.valveService.updateCharacteristic(platform.Characteristic.Active, v); - } else { - this.valveService.updateCharacteristic(platform.Characteristic.InUse, v); + switch (devSensor) { + case 'valve': + this.valveService.updateCharacteristic(platform.Characteristic.Active, v); + break; + case 'thermo': + this.thermoService.updateCharacteristic(platform.Characteristic.CurrentTemperature, v); + break; + case 'leak': + this.leakService.updateCharacteristic(platform.Characteristic.LeakDetected, v); + break; + case 'flowing': + this.valveService.updateCharacteristic(platform.Characteristic.InUse, v); + break; + default: + platform.log.error(`Unexpected device sensor type '${devSensor}' for ${device.deviceMsgName}`); + break; } }); + // Return current state of the device pending completion of the blocking function - return ((device.data.state === 'open') - ? platform.api.hap.Characteristic.Active.ACTIVE - : platform.api.hap.Characteristic.Active.INACTIVE); + const valveOpen = device.data.state?.valve === 'open' || device.data.state === 'open'; + const waterFlowing = (this.useWaterFlowing) ? device.data.state.waterFlowing : valveOpen; + switch (devSensor) { + case 'valve': + return ((valveOpen) + ? platform.api.hap.Characteristic.Active.ACTIVE + : platform.api.hap.Characteristic.Active.INACTIVE); + case 'thermo': + return ((device.data?.temperature) ? device.data.temperature : 0); + case 'leak': + return ((device.data.alarm?.leak) + ? platform.api.hap.Characteristic.LeakDetected.LEAK_DETECTED + : platform.api.hap.Characteristic.LeakDetected.LEAK_NOT_DETECTED); + case 'flowing': + return ((waterFlowing) + ? platform.api.hap.Characteristic.InUse.IN_USE + : platform.api.hap.Characteristic.InUse.NOT_IN_USE); + default: + platform.log.error(`Unexpected device sensor type '${devSensor}' for ${device.deviceMsgName}`); + return ((device.data.state.valve === 'open') + ? platform.api.hap.Characteristic.Active.ACTIVE + : platform.api.hap.Characteristic.Active.INACTIVE); + } } -async function handleGetBlocking(this: YoLinkPlatformAccessory, request = 'Active'): Promise { +async function handleGetBlocking(this: YoLinkPlatformAccessory, devSensor = 'valve'): Promise { const platform: YoLinkHomebridgePlatform = this.platform; const device: YoLinkDevice = this.accessory.context.device; // serialize access to device data. const releaseSemaphore = await device.semaphore.acquire(); - let rc = platform.api.hap.Characteristic.Active.INACTIVE; + // 'thermo' use -270 as the minimum accepted value for default + let rc = (devSensor === 'thermo') ? -270 : platform.api.hap.Characteristic.Active.INACTIVE; // also == NOT_IN_USE try { if (await this.checkDeviceState(platform, device)) { + // YoLink manipulator data does not return a 'online' value. We will assume that if + // we got this far then it is working normally... this.valveService - // YoLink manipulator data does not return a 'online' value. We will assume that if - // we got this far then it is working normally... .updateCharacteristic(platform.Characteristic.StatusFault, false); - if (device.data.state === 'open') { - rc = platform.api.hap.Characteristic.Active.ACTIVE; + + const valveOpen = device.data.state?.valve === 'open' || device.data.state === 'open'; + const waterFlowing = (this.useWaterFlowing) ? device.data.state.waterFlowing : valveOpen; + switch (devSensor) { + case 'valve': + rc = (valveOpen) + ? platform.api.hap.Characteristic.Active.ACTIVE + : platform.api.hap.Characteristic.Active.INACTIVE; + this.logDeviceState(device, `Valve open: ${valveOpen}, Battery: ${device.data.battery}`); + break; + case 'thermo': + rc = (device.data?.temperature) ? device.data.temperature : 0; + this.logDeviceState(device, `Valve (${devSensor}): Temperature: ${device.data?.temperature}\u00B0C,` + + ` Battery: ${device.data.battery}`); + break; + case 'leak': + rc = (device.data?.alarm?.leak) + ? platform.api.hap.Characteristic.LeakDetected.LEAK_DETECTED + : platform.api.hap.Characteristic.LeakDetected.LEAK_NOT_DETECTED; + this.logDeviceState(device, `Valve (${devSensor}): ${device.data.alarm?.leak}, Battery: ${device.data.battery}`); + break; + case 'flowing': + rc = (waterFlowing) + ? platform.api.hap.Characteristic.InUse.IN_USE + : platform.api.hap.Characteristic.InUse.NOT_IN_USE; + this.logDeviceState(device, `Valve (${devSensor}): ${waterFlowing}, Battery: ${device.data.battery}`); + break; + default: + platform.log.error(`Unexpected device sensor type '${devSensor}' for ${device.deviceMsgName}`); + break; } - this.logDeviceState(device, `Valve (${request}): ${device.data.state}, Battery: ${device.data.battery}`); } else { platform.log.error(`Device offline or other error for ${device.deviceMsgName}`); this.valveService @@ -114,7 +259,7 @@ async function handleGetBlocking(this: YoLinkPlatformAccessory, request = 'Activ async function handleInUse(this: YoLinkPlatformAccessory): Promise { // Apple HomeKit documentation defines In Use as fluid is flowing through valve. // We will assume that if the valve is open, then fluid is flowing... - return await handleGet.bind(this)('InUse'); + return await handleGet.bind(this)('flowing'); } /*********************************************************************** @@ -125,30 +270,59 @@ async function handleInUse(this: YoLinkPlatformAccessory): Promise { +async function handleSet(this: YoLinkPlatformAccessory, key: string, value: CharacteristicValue): Promise { const platform: YoLinkHomebridgePlatform = this.platform; const device: YoLinkDevice = this.accessory.context.device; // serialize access to device data. const releaseSemaphore = await device.semaphore.acquire(); try { const newState = (value === platform.api.hap.Characteristic.Active.ACTIVE) ? 'open' : 'close'; - const data = (await platform.yolinkAPI.setDeviceState(platform, device, { 'state': newState }))?.data; + // type can be 'Manipulator' or 'WaterMeterController' and each have different key values to use... + const data = (await platform.yolinkAPI.setDeviceState(platform, device, { [key]: newState }))?.data; if (data) { - device.data.state = data.state; + if (typeof device.data.state === 'object') { + Object.assign(device.data.state, data.state); + } else { + device.data.state = data.state; + } } // Calling updateCharacteristic within set handler seems to fail, new value is not accepted. Workaround is // to request the update after short delay (say 50ms) to allow homebridge/homekit to complete the set handler. setTimeout(() => { + const valveOpen = device.data.state?.valve === 'open' || device.data.state === 'open'; + const waterFlowing = (this.useWaterFlowing) ? device.data.state.waterFlowing : valveOpen; this.valveService - .updateCharacteristic(platform.Characteristic.Active, (device.data.state === 'open') - ? platform.api.hap.Characteristic.Active.ACTIVE : platform.api.hap.Characteristic.Active.INACTIVE) - .updateCharacteristic(platform.Characteristic.InUse, (device.data.state === 'open') - ? platform.api.hap.Characteristic.InUse.IN_USE : platform.api.hap.Characteristic.InUse.NOT_IN_USE); + .updateCharacteristic(platform.Characteristic.Active, (valveOpen) + ? platform.api.hap.Characteristic.Active.ACTIVE + : platform.api.hap.Characteristic.Active.INACTIVE) + .updateCharacteristic(platform.Characteristic.InUse, (waterFlowing) + ? platform.api.hap.Characteristic.InUse.IN_USE + : platform.api.hap.Characteristic.InUse.NOT_IN_USE); }, 50); } catch (e) { const msg = (e instanceof Error) ? e.stack : e; @@ -214,6 +388,67 @@ async function handleType(this: YoLinkPlatformAccessory): Promise { const platform: YoLinkHomebridgePlatform = this.platform; @@ -226,6 +461,8 @@ export async function mqttValveDevice(this: YoLinkPlatformAccessory, message): P const event = message.event.split('.'); switch (event[1]) { + case 'Alert': + // falls through case 'Report': // falls through case 'getState': @@ -233,7 +470,6 @@ export async function mqttValveDevice(this: YoLinkPlatformAccessory, message): P case 'setState': // falls through case 'StatusChange': - // falls through if (!device.data) { // in rare conditions (error conditions returned from YoLink) data object will be undefined or null. platform.log.warn(`Device ${device.deviceMsgName} has no data field, is device offline?`); @@ -242,15 +478,33 @@ export async function mqttValveDevice(this: YoLinkPlatformAccessory, message): P } // Merge received data into existing data object Object.assign(device.data, message.data); - this.logDeviceState(device, `Valve: ${device.data.state}, Battery: ${device.data.battery} (MQTT: ${message.event})`); - this.valveService - .updateCharacteristic(platform.Characteristic.Active, (message.data.state === 'open') - ? platform.api.hap.Characteristic.Active.ACTIVE - : platform.api.hap.Characteristic.Active.INACTIVE) - .updateCharacteristic(platform.Characteristic.InUse, (message.data.state === 'open') - ? platform.api.hap.Characteristic.InUse.IN_USE - : platform.api.hap.Characteristic.InUse.NOT_IN_USE) - .updateCharacteristic(platform.Characteristic.StatusFault, false); + { + const valveOpen = device.data.state?.valve === 'open' || device.data.state === 'open'; + const waterFlowing = (this.useWaterFlowing) ? device.data.state.waterFlowing : valveOpen; + + this.logDeviceState(device, `Valve open: ${valveOpen}, Water flowing: ${waterFlowing},` + + ` Battery: ${device.data.battery} (MQTT: ${message.event})`); + + this.valveService + .updateCharacteristic(platform.Characteristic.Active, (valveOpen) + ? platform.api.hap.Characteristic.Active.ACTIVE + : platform.api.hap.Characteristic.Active.INACTIVE) + .updateCharacteristic(platform.Characteristic.InUse, (waterFlowing) + ? platform.api.hap.Characteristic.InUse.IN_USE + : platform.api.hap.Characteristic.InUse.NOT_IN_USE); + this.thermoService + ?.updateCharacteristic(platform.Characteristic.CurrentTemperature, (device.data?.temperature) + ? device.data.temperature + : 0); + this.leakService + ?.updateCharacteristic(platform.Characteristic.LeakDetected, (device.data.alarm?.leak) + ? platform.api.hap.Characteristic.LeakDetected.LEAK_DETECTED + : platform.api.hap.Characteristic.LeakDetected.LEAK_NOT_DETECTED); + this.valveService + .updateCharacteristic(platform.Characteristic.StatusFault, (device.data.alarm?.valveError) + ? platform.api.hap.Characteristic.StatusFault.GENERAL_FAULT + : platform.api.hap.Characteristic.StatusFault.NO_FAULT); + } break; case 'setTimeZone': // nothing to update in HomeKit diff --git a/src/yolinkAPI.ts b/src/yolinkAPI.ts index 0c151bf..92328f7 100644 --- a/src/yolinkAPI.ts +++ b/src/yolinkAPI.ts @@ -1,7 +1,7 @@ /*********************************************************************** * YoLink API class * - * Copyright (c) 2022-2023 David Kerr + * Copyright (c) 2022-2024 David Kerr * * Based on documentation at http://doc.yosmart.com * @@ -333,7 +333,7 @@ export class YoLinkAPI { targetDevice: device.deviceId, token: device.token, }; - platform.verboseLog('SENDING:\n' + JSON.stringify(bddp)); + platform.verboseLog('SENDING:\n' + JSON.stringify(bddp, null, 2)); const response = await fetch(platform.config.apiURL, { method: 'POST', body: JSON.stringify(bddp), @@ -344,7 +344,7 @@ export class YoLinkAPI { }); checkHttpStatus(response); budp = await response.json(); - platform.verboseLog('RECEIVED:\n' + JSON.stringify(budp)); + platform.verboseLog('RECEIVED:\n' + JSON.stringify(budp, null, 2)); checkBudpStatus(platform, budp, device); return budp; } @@ -392,7 +392,7 @@ export class YoLinkAPI { if (state) { bddp.params = state; } - platform.verboseLog('SENDING:\n' + JSON.stringify(bddp)); + platform.verboseLog('SENDING:\n' + JSON.stringify(bddp, null, 2)); const response = await fetch(platform.config.apiURL, { method: 'POST', body: JSON.stringify(bddp), @@ -403,7 +403,7 @@ export class YoLinkAPI { }); checkHttpStatus(response); budp = await response.json(); - platform.verboseLog('RECEIVED:\n' + JSON.stringify(budp)); + platform.verboseLog('RECEIVED:\n' + JSON.stringify(budp, null, 2)); checkBudpStatus(platform, budp, device); return budp; }