homekit: refresh on user interaction. add fan service to thermostat when possible.

This commit is contained in:
Koushik Dutta
2021-10-30 19:59:45 -07:00
parent 0012fc4aa5
commit 8adfbec2f7
12 changed files with 129 additions and 179 deletions

View File

@@ -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",

View File

@@ -40,5 +40,5 @@
"@types/qrcode": "^1.4.1",
"@types/url-parse": "^1.4.3"
},
"version": "0.0.101"
"version": "0.0.102"
}

View File

@@ -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);
}

View File

@@ -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)));
}

View File

@@ -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,

View File

@@ -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<CharacteristicValue>) => {
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<string, ContactSensor>();
const peopleSensorMap = new Map<string, ContactSensor>();
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;

View File

@@ -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;
}

View File

@@ -35,9 +35,6 @@ addSupportedType({
callback();
h = value as number;
delaySet();
})
.on(CharacteristicEventTypes.GET, (callback: NodeCallback<CharacteristicValue>) => {
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<CharacteristicValue>) => {
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;

View File

@@ -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<CharacteristicValue>) => {
callback(null, toCurrentState(device.lockState));
});
let targetState = toTargetState(device.lockState);
service.getCharacteristic(Characteristic.LockTargetState)
.on(CharacteristicEventTypes.GET, (callback: NodeCallback<CharacteristicValue>) => {
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;
}

View File

@@ -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<string>();
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<CharacteristicValue>) => {
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);
}

View File

@@ -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<CharacteristicValue>) => {
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.

View File

@@ -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<CharacteristicValue>) => {
callback(null, toCurrentMode(device.thermostatActiveMode));
});
bindCharacteristic(device, ScryptedInterface.TemperatureSetting, service, Characteristic.CurrentHeatingCoolingState,
() => toCurrentMode(device.thermostatActiveMode));
service.getCharacteristic(Characteristic.TargetHeatingCoolingState)
.on(CharacteristicEventTypes.GET, (callback: NodeCallback<CharacteristicValue>) => {
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<CharacteristicValue>) => {
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;
}
});