From 8adfbec2f706dfed60b6067fd27a2d354b7668f8 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Sat, 30 Oct 2021 19:59:45 -0700 Subject: [PATCH] homekit: refresh on user interaction. add fan service to thermostat when possible. --- plugins/homekit/package-lock.json | 4 +- plugins/homekit/package.json | 2 +- plugins/homekit/src/battery.ts | 7 +- plugins/homekit/src/common.ts | 20 ++++- plugins/homekit/src/main.ts | 18 ++--- plugins/homekit/src/types/camera.ts | 94 ++++++++---------------- plugins/homekit/src/types/irrigation.ts | 2 +- plugins/homekit/src/types/light.ts | 11 ++- plugins/homekit/src/types/lock.ts | 27 ++----- plugins/homekit/src/types/mediaplayer.ts | 62 +++++++--------- plugins/homekit/src/types/sensor.ts | 16 +--- plugins/homekit/src/types/thermostat.ts | 45 +++++------- 12 files changed, 129 insertions(+), 179 deletions(-) diff --git a/plugins/homekit/package-lock.json b/plugins/homekit/package-lock.json index 367b40c7e..9489e5e05 100644 --- a/plugins/homekit/package-lock.json +++ b/plugins/homekit/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/homekit", - "version": "0.0.101", + "version": "0.0.102", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/homekit", - "version": "0.0.101", + "version": "0.0.102", "dependencies": { "hap-nodejs": "file:../../external/HAP-NodeJS", "lodash": "^4.17.21", diff --git a/plugins/homekit/package.json b/plugins/homekit/package.json index 7ecf675fb..d8c512ef0 100644 --- a/plugins/homekit/package.json +++ b/plugins/homekit/package.json @@ -40,5 +40,5 @@ "@types/qrcode": "^1.4.1", "@types/url-parse": "^1.4.3" }, - "version": "0.0.101" + "version": "0.0.102" } diff --git a/plugins/homekit/src/battery.ts b/plugins/homekit/src/battery.ts index 96ac36508..1cd209168 100644 --- a/plugins/homekit/src/battery.ts +++ b/plugins/homekit/src/battery.ts @@ -8,10 +8,13 @@ export function maybeAddBatteryService(device: ScryptedDevice & Battery, accesso return; const battery = new HAPBattery(); - bindCharacteristic(device, ScryptedInterface.Battery, battery, BatteryLevel, () => { - battery.updateCharacteristic(Characteristic.StatusLowBattery, device.batteryLevel >= 20 ? StatusLowBattery.BATTERY_LEVEL_NORMAL : StatusLowBattery.BATTERY_LEVEL_LOW); + bindCharacteristic(device, ScryptedInterface.Battery, battery, Characteristic.BatteryLevel, () => { return device.batteryLevel || 0; }); + bindCharacteristic(device, ScryptedInterface.Battery, battery, Characteristic.StatusLowBattery, () => { + return device.batteryLevel >= 20 ? StatusLowBattery.BATTERY_LEVEL_NORMAL : StatusLowBattery.BATTERY_LEVEL_LOW; + }); + accessory.addService(battery); } diff --git a/plugins/homekit/src/common.ts b/plugins/homekit/src/common.ts index c9801bb4d..645fbba7d 100644 --- a/plugins/homekit/src/common.ts +++ b/plugins/homekit/src/common.ts @@ -1,5 +1,6 @@ -import { EventListenerRegister, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from '@scrypted/sdk'; +import { EventDetails, EventListener, EventListenerRegister, Refresh, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from '@scrypted/sdk'; +import { CharacteristicEventTypes } from 'hap-nodejs'; import { Accessory, Service, SnapshotRequest } from './hap'; export interface DummyDevice { @@ -29,11 +30,24 @@ export function addSupportedType(type: SupportedType) { supportedTypes[type.type] = type; } -export function bindCharacteristic(device: ScryptedDevice, event: ScryptedInterface, service: Service, characteristic: any, map: () => any, refresh?: boolean): EventListenerRegister { +export function bindCharacteristic(device: ScryptedDevice, event: ScryptedInterface, service: Service, characteristic: any, map: (eventSource?: any, eventDetails?: EventDetails, eventData?: any) => any, refresh?: boolean): EventListenerRegister { service.updateCharacteristic(characteristic, map()); + service.getCharacteristic(characteristic).on(CharacteristicEventTypes.GET, callback => { + try { + if (device.interfaces.includes(ScryptedInterface.Refresh)) { + console.log('refreshing', device.name); + (device as ScryptedDevice & Refresh).refresh(event, true); + } + callback(null, map()); + } + catch (e) { + callback(e); + } + }); + return device.listen({ event, watch: !refresh, - }, () => service.updateCharacteristic(characteristic, map())); + }, (source, details, data) => service.updateCharacteristic(characteristic, map(source, details, data))); } diff --git a/plugins/homekit/src/main.ts b/plugins/homekit/src/main.ts index 9067041ee..c82566a0d 100644 --- a/plugins/homekit/src/main.ts +++ b/plugins/homekit/src/main.ts @@ -184,15 +184,15 @@ class HomeKit extends ScryptedDeviceBase implements MixinProvider, Settings, Hom if (deviceInfo) { const info = accessory.getService(Service.AccessoryInformation)!; if (deviceInfo.manufacturer) - info.setCharacteristic(Characteristic.Manufacturer, deviceInfo.manufacturer); + info.updateCharacteristic(Characteristic.Manufacturer, deviceInfo.manufacturer); if (deviceInfo.model) - info.setCharacteristic(Characteristic.Model, deviceInfo.model); + info.updateCharacteristic(Characteristic.Model, deviceInfo.model); if (deviceInfo.serialNumber) - info.setCharacteristic(Characteristic.SerialNumber, deviceInfo.serialNumber); + info.updateCharacteristic(Characteristic.SerialNumber, deviceInfo.serialNumber); if (deviceInfo.firmware) - info.setCharacteristic(Characteristic.FirmwareRevision, deviceInfo.firmware); + info.updateCharacteristic(Characteristic.FirmwareRevision, deviceInfo.firmware); if (deviceInfo.version) - info.setCharacteristic(Characteristic.HardwareRevision, deviceInfo.version); + info.updateCharacteristic(Characteristic.HardwareRevision, deviceInfo.version); } if (supportedType.noBridge) { @@ -214,10 +214,10 @@ class HomeKit extends ScryptedDeviceBase implements MixinProvider, Settings, Hom const username = this.getUsername(); const info = this.bridge.getService(Service.AccessoryInformation)!; - info.setCharacteristic(Characteristic.Manufacturer, "scrypted.app"); - info.setCharacteristic(Characteristic.Model, "scrypted"); - info.setCharacteristic(Characteristic.SerialNumber, username); - info.setCharacteristic(Characteristic.FirmwareRevision, packageJson.version); + info.updateCharacteristic(Characteristic.Manufacturer, "scrypted.app"); + info.updateCharacteristic(Characteristic.Model, "scrypted"); + info.updateCharacteristic(Characteristic.SerialNumber, username); + info.updateCharacteristic(Characteristic.FirmwareRevision, packageJson.version); const publishInfo: PublishInfo = { username: username, diff --git a/plugins/homekit/src/types/camera.ts b/plugins/homekit/src/types/camera.ts index b16cd5f4b..440c99f39 100644 --- a/plugins/homekit/src/types/camera.ts +++ b/plugins/homekit/src/types/camera.ts @@ -1,8 +1,7 @@ import { Camera, FFMpegInput, MotionSensor, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, VideoCamera, AudioSensor, Intercom, MediaStreamOptions, ObjectDetection } from '@scrypted/sdk' -import { addSupportedType, DummyDevice, HomeKitSession } from '../common' -import { AudioStreamingCodec, AudioStreamingCodecType, AudioStreamingSamplerate, CameraController, CameraStreamingDelegate, CameraStreamingOptions, Characteristic, H264Level, H264Profile, PrepareStreamCallback, PrepareStreamRequest, PrepareStreamResponse, SRTPCryptoSuites, StartStreamRequest, StreamingRequest, StreamRequestCallback, StreamRequestTypes, Resolution } from '../hap'; +import { addSupportedType, bindCharacteristic, DummyDevice, HomeKitSession } from '../common' +import { AudioStreamingCodec, AudioStreamingCodecType, AudioStreamingSamplerate, CameraController, CameraStreamingDelegate, CameraStreamingOptions, Characteristic, H264Level, H264Profile, PrepareStreamCallback, PrepareStreamRequest, PrepareStreamResponse, SRTPCryptoSuites, StartStreamRequest, StreamingRequest, StreamRequestCallback, StreamRequestTypes } from '../hap'; import { makeAccessory } from './common'; -import { fitHeightToWidth } from "../../../../common/src/resolution-utils"; import sdk from '@scrypted/sdk'; import child_process from 'child_process'; @@ -11,7 +10,7 @@ import dgram, { SocketType } from 'dgram'; import { once } from 'events'; import debounce from 'lodash/debounce'; -import { CameraRecordingDelegate, CharacteristicEventTypes, CharacteristicValue, NodeCallback } from 'hap-nodejs'; +import { CameraRecordingDelegate } from 'hap-nodejs'; import { AudioRecordingCodec, AudioRecordingCodecType, AudioRecordingSamplerate, CameraRecordingOptions } from 'hap-nodejs/dist/lib/camera/RecordingManagement'; import { ffmpegLogInitialOutput } from '@scrypted/common/src/media-helpers'; import { RtpDemuxer } from '../rtp/rtp-demuxer'; @@ -517,25 +516,18 @@ addSupportedType({ () => !!motionDevice.motionDetected; const service = controller.motionService; - service.getCharacteristic(Characteristic.MotionDetected) - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - callback(null, motionDetected()); - }); - - motionDevice.listen({ - event: ScryptedInterface.MotionSensor, - watch: false, - }, (eventSource, eventDetails, data) => { - service.updateCharacteristic(Characteristic.MotionDetected, motionDetected()); - }); + bindCharacteristic(motionDevice, + ScryptedInterface.MotionSensor, + service, + Characteristic.MotionDetected, + () => motionDetected(), true) if (needAudioMotionService) { - motionDevice.listen({ - event: ScryptedInterface.AudioSensor, - watch: false, - }, (eventSource, eventDetails, data) => { - service.updateCharacteristic(Characteristic.MotionDetected, motionDetected()); - }); + bindCharacteristic(motionDevice, + ScryptedInterface.AudioSensor, + service, + Characteristic.MotionDetected, + () => motionDetected(), true) } } @@ -547,55 +539,33 @@ addSupportedType({ catch (e) { } - const objectSensorMap = new Map(); - const peopleSensorMap = new Map(); for (const ojs of new Set(objectDetectionContactSensors)) { const sensor = new ContactSensor(`${device.name}: ` + ojs, ojs); - sensor?.updateCharacteristic(Characteristic.ContactSensorState, Characteristic.ContactSensorState.CONTACT_DETECTED); accessory.addService(sensor); - if (ojs.startsWith('Person: ')) { - peopleSensorMap.set(ojs, sensor); - } - else { - objectSensorMap.set(ojs, sensor); - } - } + const isPerson = ojs.startsWith('Person: '); - if (objectSensorMap.size || peopleSensorMap.size) { - device.listen(ScryptedInterface.ObjectDetector, (eventSource, eventDetails, eventData: ObjectDetection) => { - if (eventData.detections) { - const unset = new Set(objectSensorMap.keys()); - const objects: string[] = []; - objects.push(...eventData.detections.map(d => d.className)); - for (const type of objects) { - const sensor = objectSensorMap.get(type); - sensor?.updateCharacteristic(Characteristic.ContactSensorState, Characteristic.ContactSensorState.CONTACT_NOT_DETECTED); - unset.delete(type); - } + let contactState = Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; + bindCharacteristic(device, ScryptedInterface.ObjectDetector, sensor, Characteristic.ContactSensorState, (source, details, data) => { + if (!source) + return contactState; - for (const type of unset) { - const sensor = objectSensorMap.get(type); - sensor?.updateCharacteristic(Characteristic.ContactSensorState, Characteristic.ContactSensorState.CONTACT_DETECTED); - } + const ed: ObjectDetection = data; + if (!isPerson) { + if (!ed.detections) + return contactState; + const objects = ed.detections.map(d => d.className); + contactState = objects.includes(ojs) ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED + return contactState; } - if (eventData.people) { - const unset = new Set(peopleSensorMap.keys()); - const people: string[] = []; - people.push(...eventData.people.map(p => p.label)); - for (const type of people) { - const personType = 'Person: ' + type; - const sensor = peopleSensorMap.get(personType); - sensor?.updateCharacteristic(Characteristic.ContactSensorState, Characteristic.ContactSensorState.CONTACT_NOT_DETECTED); - unset.delete(personType); - } + if (!ed.people) + return contactState; - for (const type of unset) { - const sensor = peopleSensorMap.get(type); - sensor?.updateCharacteristic(Characteristic.ContactSensorState, Characteristic.ContactSensorState.CONTACT_DETECTED); - } - } - }); + const people = ed.people.map(d => 'Person: ' + d.label); + contactState = people.includes(ojs) ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; + + return contactState; + }, true); } return accessory; diff --git a/plugins/homekit/src/types/irrigation.ts b/plugins/homekit/src/types/irrigation.ts index 10d3a9da1..d3d93f775 100644 --- a/plugins/homekit/src/types/irrigation.ts +++ b/plugins/homekit/src/types/irrigation.ts @@ -28,7 +28,7 @@ addSupportedType({ () => !!device.running ? Characteristic.InUse.IN_USE : Characteristic.InUse.NOT_IN_USE); // todo: fix this. - service.setCharacteristic(Characteristic.RemainingDuration, 1800) + service.updateCharacteristic(Characteristic.RemainingDuration, 1800) return accessory; } diff --git a/plugins/homekit/src/types/light.ts b/plugins/homekit/src/types/light.ts index 9d4baaa18..9f8245aee 100644 --- a/plugins/homekit/src/types/light.ts +++ b/plugins/homekit/src/types/light.ts @@ -35,9 +35,6 @@ addSupportedType({ callback(); h = value as number; delaySet(); - }) - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - callback(null, device.hsv?.h || 0); }); service.addCharacteristic(Characteristic.Saturation) @@ -45,11 +42,13 @@ addSupportedType({ callback(); s = value as number; delaySet(); - }) - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - callback(null, (device.hsv?.s || 0) * 100); }); + bindCharacteristic(device, ScryptedInterface.ColorSettingHsv, service, Characteristic.Hue, + () => device.hsv?.h || 0); + + bindCharacteristic(device, ScryptedInterface.ColorSettingHsv, service, Characteristic.Saturation, + () => (device.hsv?.s || 0) * 100); } return accessory; diff --git a/plugins/homekit/src/types/lock.ts b/plugins/homekit/src/types/lock.ts index 023d7efa9..2ccbf7c07 100644 --- a/plugins/homekit/src/types/lock.ts +++ b/plugins/homekit/src/types/lock.ts @@ -1,6 +1,6 @@ import { Lock, LockState, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from '@scrypted/sdk' -import { addSupportedType, DummyDevice } from '../common' +import { addSupportedType, bindCharacteristic, DummyDevice } from '../common' import { Characteristic, CharacteristicEventTypes, CharacteristicSetCallback, CharacteristicValue, NodeCallback, Service } from '../hap'; import { makeAccessory } from './common'; @@ -33,18 +33,9 @@ addSupportedType({ } } - service.getCharacteristic(Characteristic.LockCurrentState) - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - callback(null, toCurrentState(device.lockState)); - }); - - let targetState = toTargetState(device.lockState); service.getCharacteristic(Characteristic.LockTargetState) - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - callback(null, targetState); - }) .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { targetState = value as number; callback(); @@ -56,17 +47,15 @@ addSupportedType({ device.lock(); break; } - }) + }); + bindCharacteristic(device, ScryptedInterface.Lock, service, Characteristic.LockTargetState, () => { + targetState = toTargetState(device.lockState); + return targetState; + }) - device.listen({ - event: ScryptedInterface.Lock, - watch: true, - }, (source, details, data) => { - targetState = toTargetState(data); - service.updateCharacteristic(Characteristic.LockTargetState, targetState); - service.updateCharacteristic(Characteristic.LockCurrentState, toCurrentState(data)); - }); + bindCharacteristic(device, ScryptedInterface.Lock, service, Characteristic.LockCurrentState, + () => toCurrentState(device.lockState)); return accessory; } diff --git a/plugins/homekit/src/types/mediaplayer.ts b/plugins/homekit/src/types/mediaplayer.ts index 98f1b7a2d..f5257e216 100644 --- a/plugins/homekit/src/types/mediaplayer.ts +++ b/plugins/homekit/src/types/mediaplayer.ts @@ -1,6 +1,6 @@ import { VideoCamera, MediaPlayer, MediaPlayerState, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from '@scrypted/sdk' -import { addSupportedType, DummyDevice } from '../common' +import { addSupportedType, bindCharacteristic, DummyDevice } from '../common' import { Categories, Characteristic, CharacteristicEventTypes, CharacteristicSetCallback, CharacteristicValue, NodeCallback, Service } from '../hap'; import { makeAccessory } from './common'; import sdk from '@scrypted/sdk'; @@ -18,35 +18,27 @@ addSupportedType({ const service = accessory.addService(Service.Television, "Television", "Television"); // service.setPrimaryService(true); - let active = false; let activeIdentifier = 0; const allowedIdentifiers = new Set(); service.getCharacteristic(Characteristic.Active) .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - active = value === Characteristic.Active.ACTIVE; - if (!active) + if (value !== Characteristic.Active.ACTIVE) device.stop(); callback(); - }) - .on(CharacteristicEventTypes.GET, async (callback: NodeCallback) => { - try { - if (active) { - callback(null, Characteristic.Active.ACTIVE); - return; - } - const mediaStatus = await device.getMediaStatus(); - if (!mediaStatus || mediaStatus.mediaPlayerState === MediaPlayerState.Idle) { - callback(null, Characteristic.Active.INACTIVE); - return; - } - active = true; - callback(null, Characteristic.Active.ACTIVE); - } - catch (e) { - callback(e); - } }); + let active = false; + bindCharacteristic(device, ScryptedInterface.MediaPlayer, service, Characteristic.Active, + () => { + // trigger an actual fetch here but return something cached immediately. + (async() => { + const mediaStatus = await device.getMediaStatus(); + active = mediaStatus && mediaStatus.mediaPlayerState !== MediaPlayerState.Idle; + service.updateCharacteristic(Characteristic.Active, active ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE); + })(); + return active ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE; + }) + service.getCharacteristic(Characteristic.ActiveIdentifier) .on(CharacteristicEventTypes.SET, async (value: CharacteristicValue, callback: CharacteristicSetCallback) => { activeIdentifier = value as number; @@ -76,18 +68,17 @@ addSupportedType({ callback(null, activeIdentifier); }); - - service.setCharacteristic(Characteristic.ConfiguredName, device.name); + service.updateCharacteristic(Characteristic.ConfiguredName, device.name); service.getCharacteristic(Characteristic.RemoteKey) .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { callback(); }); - service.setCharacteristic(Characteristic.SleepDiscoveryMode, Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE); + service.updateCharacteristic(Characteristic.SleepDiscoveryMode, Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE); const speaker = accessory.addService(Service.TelevisionSpeaker); - speaker.setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE) + speaker.updateCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE) speaker.getCharacteristic(Characteristic.Mute) .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { @@ -97,16 +88,13 @@ addSupportedType({ callback(null, false); }); - - const idle = accessory.addService(Service.InputSource, 'idle', 'Idle'); - idle.setCharacteristic(Characteristic.Identifier, 0) - .setCharacteristic(Characteristic.ConfiguredName, 'Idle') - .setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED) - .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION); + idle.updateCharacteristic(Characteristic.Identifier, 0) + .updateCharacteristic(Characteristic.ConfiguredName, 'Idle') + .updateCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED) + .updateCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION); service.addLinkedService(idle); - for (const id of Object.keys(systemManager.getSystemState())) { const check = systemManager.getDeviceById(id); if (check.type !== ScryptedDeviceType.Camera) @@ -115,10 +103,10 @@ addSupportedType({ allowedIdentifiers.add(check.id); const input = accessory.addService(Service.InputSource, check.name, `input-${check.id}`); - input.setCharacteristic(Characteristic.Identifier, check.id) - .setCharacteristic(Characteristic.ConfiguredName, check.name) - .setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED) - .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION); + input.updateCharacteristic(Characteristic.Identifier, check.id) + .updateCharacteristic(Characteristic.ConfiguredName, check.name) + .updateCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED) + .updateCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION); service.addLinkedService(input); } diff --git a/plugins/homekit/src/types/sensor.ts b/plugins/homekit/src/types/sensor.ts index 56aa38cbc..facb17c3b 100644 --- a/plugins/homekit/src/types/sensor.ts +++ b/plugins/homekit/src/types/sensor.ts @@ -15,24 +15,16 @@ addSupportedType({ if (device.interfaces.includes(ScryptedInterface.BinarySensor)) { const contactSensorService = accessory.addService(Service.ContactSensor, device.name); contactSensorService.getCharacteristic(Characteristic.ContactSensorState) - + bindCharacteristic(device, ScryptedInterface.BinarySensor, contactSensorService, Characteristic.ContactSensorState, () => !!device.binaryState); } if (device.interfaces.includes(ScryptedInterface.MotionSensor)) { const motionSensorService = accessory.addService(Service.MotionSensor, device.name); - motionSensorService.getCharacteristic(Characteristic.MotionDetected) - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - callback(null, !!device.motionDetected); - }); - - device.listen({ - event: ScryptedInterface.MotionSensor, - watch: false, - }, (eventSource, eventDetails, data) => { - motionSensorService.updateCharacteristic(Characteristic.MotionDetected, !!device.motionDetected); - }); + + bindCharacteristic(device, ScryptedInterface.MotionSensor, motionSensorService, Characteristic.MotionDetected, + () => !!device.motionDetected, true); } // todo: more sensors. diff --git a/plugins/homekit/src/types/thermostat.ts b/plugins/homekit/src/types/thermostat.ts index 4e601f128..fe64b45e0 100644 --- a/plugins/homekit/src/types/thermostat.ts +++ b/plugins/homekit/src/types/thermostat.ts @@ -1,5 +1,7 @@ -import { HumiditySensor, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceProperty, TemperatureSetting, TemperatureUnit, Thermometer, ThermostatMode } from '@scrypted/sdk' +import { HumiditySensor, OnOff, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceProperty, TemperatureSetting, TemperatureUnit, Thermometer, ThermostatMode } from '@scrypted/sdk' +import { access } from 'fs'; +import { Fanv2 } from 'hap-nodejs/dist/lib/definitions'; import { addSupportedType, bindCharacteristic, DummyDevice } from '../common' import { Characteristic, CharacteristicEventTypes, CharacteristicSetCallback, CharacteristicValue, NodeCallback, Service } from '../hap'; import { makeAccessory } from './common'; @@ -11,7 +13,7 @@ addSupportedType({ return false; return true; }, - getAccessory: async (device: ScryptedDevice & TemperatureSetting & Thermometer & HumiditySensor) => { + getAccessory: async (device: ScryptedDevice & TemperatureSetting & Thermometer & HumiditySensor & OnOff) => { const accessory = makeAccessory(device); const service = accessory.addService(Service.Thermostat, device.name); @@ -54,21 +56,17 @@ addSupportedType({ } } - service.getCharacteristic(Characteristic.CurrentHeatingCoolingState) - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - callback(null, toCurrentMode(device.thermostatActiveMode)); - }); - + bindCharacteristic(device, ScryptedInterface.TemperatureSetting, service, Characteristic.CurrentHeatingCoolingState, + () => toCurrentMode(device.thermostatActiveMode)); service.getCharacteristic(Characteristic.TargetHeatingCoolingState) - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - callback(null, toTargetMode(device.thermostatMode)); - }) .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { callback(); device.setThermostatMode(fromTargetMode(value as number)); }) + bindCharacteristic(device, ScryptedInterface.TemperatureSetting, service, Characteristic.TargetHeatingCoolingState, + () => toTargetMode(device.thermostatActiveMode)); function getTargetTemperature() { return device.thermostatSetpoint || @@ -77,26 +75,17 @@ addSupportedType({ } service.getCharacteristic(Characteristic.TargetTemperature) - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - callback(null, getTargetTemperature()); - }) .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { callback(); device.setThermostatSetpoint(value as number); - }) + }); - service.setCharacteristic(Characteristic.TemperatureDisplayUnits, - device.temperatureUnit === TemperatureUnit.C ? Characteristic.TemperatureDisplayUnits.CELSIUS : Characteristic.TemperatureDisplayUnits.FAHRENHEIT); + bindCharacteristic(device, ScryptedInterface.TemperatureSetting, service, Characteristic.TargetTemperature, + () => getTargetTemperature()); - device.listen({ - event: ScryptedInterface.TemperatureSetting, - watch: true, - }, (source, details, data) => { - service.updateCharacteristic(Characteristic.TargetHeatingCoolingState, toTargetMode(device.thermostatMode)); - service.updateCharacteristic(Characteristic.CurrentHeatingCoolingState, toCurrentMode(device.thermostatActiveMode)); - service.updateCharacteristic(Characteristic.TargetTemperature, getTargetTemperature()); - service.updateCharacteristic(Characteristic.TemperatureDisplayUnits, device.temperatureUnit === TemperatureUnit.C ? Characteristic.TemperatureDisplayUnits.CELSIUS : Characteristic.TemperatureDisplayUnits.FAHRENHEIT); - }); + + bindCharacteristic(device, ScryptedInterface.TemperatureSetting, service, Characteristic.TemperatureDisplayUnits, + () => device.temperatureUnit === TemperatureUnit.C ? Characteristic.TemperatureDisplayUnits.CELSIUS : Characteristic.TemperatureDisplayUnits.FAHRENHEIT); bindCharacteristic(device, ScryptedInterface.Thermometer, service, Characteristic.CurrentTemperature, () => device.temperature || 0); @@ -106,6 +95,12 @@ addSupportedType({ () => device.humidity || 0); } + if (device.interfaces.includes(ScryptedInterface.OnOff)) { + const fanService = accessory.addService(Fanv2); + bindCharacteristic(device, ScryptedInterface.OnOff, fanService, Characteristic.Active, + () => device.on ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE); + } + return accessory; } });