Skip to content

Commit 0873a72

Browse files
authored
homekit: moved humidity settings to common and added characteristics to expose settings Home Assistant (#1699)
1 parent 145c66e commit 0873a72

File tree

2 files changed

+196
-71
lines changed

2 files changed

+196
-71
lines changed

plugins/homekit/src/types/common.ts

Lines changed: 150 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import sdk, { AirQuality, AirQualitySensor, CO2Sensor, DeviceProvider, Fan, FanMode, NOXSensor, OnOff, PM10Sensor, PM25Sensor, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, VOCSensor } from "@scrypted/sdk";
1+
import sdk, { AirQuality, AirQualitySensor, CO2Sensor, DeviceProvider, Fan, FanMode, HumidityMode, HumiditySensor, HumiditySetting, HumiditySettingStatus, NOXSensor, OnOff, PM10Sensor, PM25Sensor, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, VOCSensor } from "@scrypted/sdk";
22
import { bindCharacteristic } from "../common";
33
import { Accessory, Characteristic, CharacteristicEventTypes, Service, uuid } from '../hap';
44
import type { HomeKitPlugin } from "../main";
@@ -96,24 +96,161 @@ export function addCarbonDioxideSensor(device: ScryptedDevice & CO2Sensor, acces
9696
return co2Service;
9797
}
9898

99-
export function addFan(device: ScryptedDevice & Fan & OnOff, accessory: Accessory): Service {
100-
if (!device.interfaces.includes(ScryptedInterface.OnOff) && !device.interfaces.includes(ScryptedInterface.Fan))
99+
function commonHumidifierDehumidifier(mode: HumidityMode, subtype: string, name: string, device: ScryptedDevice & HumiditySetting & HumiditySensor, accessory: Accessory): Service {
100+
function currentState(mode: HumidityMode) {
101+
switch(mode) {
102+
case HumidityMode.Humidify:
103+
return Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING;
104+
case HumidityMode.Dehumidify:
105+
return Characteristic.CurrentHumidifierDehumidifierState.DEHUMIDIFYING;
106+
case HumidityMode.Off:
107+
return Characteristic.CurrentHumidifierDehumidifierState.INACTIVE;
108+
default:
109+
return Characteristic.CurrentHumidifierDehumidifierState.IDLE;
110+
}
111+
}
112+
113+
function targetState(mode: HumidityMode) {
114+
switch(mode) {
115+
case HumidityMode.Humidify:
116+
return Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER;
117+
case HumidityMode.Dehumidify:
118+
return Characteristic.TargetHumidifierDehumidifierState.DEHUMIDIFIER;
119+
default:
120+
return Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER;
121+
}
122+
}
123+
124+
const service = accessory.addService(Service.HumidifierDehumidifier, name, subtype);
125+
126+
bindCharacteristic(device, ScryptedInterface.HumiditySetting, service, Characteristic.Active,
127+
() => {
128+
if (!device.humiditySetting?.mode)
129+
return false;
130+
if (device.humiditySetting.mode === mode)
131+
return true;
132+
if (device.humiditySetting.mode === HumidityMode.Auto)
133+
return true;
134+
return false;
135+
});
136+
service.getCharacteristic(Characteristic.Active).on(CharacteristicEventTypes.SET, (value, callback) => {
137+
callback();
138+
device.setHumidity({
139+
mode: value ? mode : HumidityMode.Off
140+
});
141+
});
142+
143+
bindCharacteristic(device, ScryptedInterface.HumiditySensor, service, Characteristic.CurrentRelativeHumidity,
144+
() => device.humidity);
145+
146+
bindCharacteristic(device, ScryptedInterface.HumiditySetting, service, Characteristic.CurrentHumidifierDehumidifierState,
147+
() => currentState(device.humiditySetting?.activeMode));
148+
149+
bindCharacteristic(device, ScryptedInterface.HumiditySetting, service, Characteristic.TargetHumidifierDehumidifierState,
150+
() => targetState(device.humiditySetting?.mode));
151+
152+
service.getCharacteristic(Characteristic.TargetHumidifierDehumidifierState).on(CharacteristicEventTypes.SET, (value, callback) => {
153+
callback();
154+
device.setHumidity({
155+
mode: value === Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER
156+
? HumidityMode.Humidify
157+
: value === Characteristic.TargetHumidifierDehumidifierState.DEHUMIDIFIER
158+
? HumidityMode.Dehumidify
159+
: HumidityMode.Auto
160+
});
161+
});
162+
163+
function targetHumidity(setting: HumiditySettingStatus) {
164+
if (!setting)
165+
return 0;
166+
167+
if (setting?.availableModes.includes(HumidityMode.Humidify)
168+
&& setting?.availableModes.includes(HumidityMode.Dehumidify)) {
169+
if (setting?.activeMode === HumidityMode.Humidify)
170+
return setting?.humidifierSetpoint;
171+
if (setting?.activeMode === HumidityMode.Dehumidify)
172+
return setting?.dehumidifierSetpoint;
173+
174+
return 0;
175+
}
176+
177+
if (setting?.availableModes.includes(HumidityMode.Humidify))
178+
return setting?.humidifierSetpoint;
179+
180+
if (setting?.availableModes.includes(HumidityMode.Dehumidify))
181+
return setting?.dehumidifierSetpoint;
182+
183+
return 0;
184+
}
185+
186+
bindCharacteristic(device, ScryptedInterface.HumiditySetting, service, Characteristic.TargetRelativeHumidity,
187+
() => targetHumidity(device.humiditySetting));
188+
189+
return service;
190+
}
191+
192+
export function addHumidifier(device: ScryptedDevice & HumiditySetting & HumiditySensor, accessory: Accessory): Service {
193+
var service = commonHumidifierDehumidifier(HumidityMode.Humidify, "humidifier", device.name + " Humidifier", device, accessory);
194+
195+
bindCharacteristic(device, ScryptedInterface.HumiditySetting, service, Characteristic.RelativeHumidityHumidifierThreshold,
196+
() => device.humiditySetting?.humidifierSetpoint);
197+
service.getCharacteristic(Characteristic.RelativeHumidityHumidifierThreshold).on(CharacteristicEventTypes.SET, (value, callback) => {
198+
callback();
199+
device.setHumidity({
200+
humidifierSetpoint: value as number,
201+
});
202+
});
203+
204+
return service;
205+
}
206+
207+
export function addDehumidifer(device: ScryptedDevice & HumiditySetting & HumiditySensor, accessory: Accessory): Service {
208+
var service = commonHumidifierDehumidifier(HumidityMode.Dehumidify, "dehumidifier", device.name + " Dehumidifier", device, accessory);
209+
210+
bindCharacteristic(device, ScryptedInterface.HumiditySetting, service, Characteristic.RelativeHumidityDehumidifierThreshold,
211+
() => device.humiditySetting?.dehumidifierSetpoint);
212+
service.getCharacteristic(Characteristic.RelativeHumidityDehumidifierThreshold).on(CharacteristicEventTypes.SET, (value, callback) => {
213+
callback();
214+
device.setHumidity({
215+
dehumidifierSetpoint: value as number,
216+
});
217+
});
218+
219+
return service;
220+
}
221+
222+
export function addHumiditySetting(device: ScryptedDevice & HumiditySetting & HumiditySensor, accessory: Accessory): Service {
223+
if (!device.interfaces.includes(ScryptedInterface.HumiditySetting) && !device.interfaces.includes(ScryptedInterface.HumiditySensor))
224+
return undefined;
225+
226+
var service;
227+
228+
if (device.humiditySetting?.availableModes.includes(HumidityMode.Humidify)) {
229+
service = addHumidifier(device, accessory);
230+
}
231+
232+
if (device.humiditySetting?.availableModes.includes(HumidityMode.Dehumidify)) {
233+
service = addDehumidifer(device, accessory);
234+
}
235+
236+
return service;
237+
}
238+
239+
export function addFan(device: ScryptedDevice & Fan, accessory: Accessory): Service {
240+
if (!device.interfaces.includes(ScryptedInterface.Fan))
101241
return undefined;
102242

103243
const service = accessory.addService(Service.Fanv2, device.name);
104244

105-
if (device.interfaces.includes(ScryptedInterface.OnOff)) {
106-
bindCharacteristic(device, ScryptedInterface.OnOff, service, Characteristic.Active,
107-
() => !!device.on);
245+
bindCharacteristic(device, ScryptedInterface.OnOff, service, Characteristic.Active,
246+
() => device.fan?.active);
108247

109-
service.getCharacteristic(Characteristic.Active).on(CharacteristicEventTypes.SET, (value, callback) => {
110-
callback();
111-
if (value)
112-
device.turnOn();
113-
else
114-
device.turnOff();
248+
service.getCharacteristic(Characteristic.Active).on(CharacteristicEventTypes.SET, (value, callback) => {
249+
callback();
250+
device.setFan({
251+
mode: value ? FanMode.Auto : FanMode.Manual,
115252
});
116-
}
253+
});
117254

118255
if (device.fan?.counterClockwise !== undefined) {
119256
bindCharacteristic(device, ScryptedInterface.Fan, service, Characteristic.RotationDirection,

plugins/homekit/src/types/thermostat.ts

Lines changed: 46 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Fan, FanMode, HumidityMode, HumiditySensor, HumiditySetting, OnOff, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, TemperatureSetting, TemperatureUnit, Thermometer, ThermostatMode, AirQualitySensor, AirQuality, PM10Sensor, PM25Sensor, VOCSensor, NOXSensor, CO2Sensor } from '@scrypted/sdk';
1+
import { Fan, FanMode, HumidityMode, HumiditySensor, HumiditySetting, OnOff, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, TemperatureSetting, TemperatureUnit, Thermometer, ThermostatMode, AirQualitySensor, AirQuality, PM10Sensor, PM25Sensor, VOCSensor, NOXSensor, CO2Sensor, HumiditySettingStatus } from '@scrypted/sdk';
22
import { addSupportedType, bindCharacteristic, DummyDevice, } from '../common';
33
import { Characteristic, CharacteristicEventTypes, CharacteristicSetCallback, CharacteristicValue, Service } from '../hap';
4-
import { addAirQualitySensor, addCarbonDioxideSensor, addFan, makeAccessory } from './common';
4+
import { addAirQualitySensor, addCarbonDioxideSensor, addFan, addHumiditySetting, makeAccessory } from './common';
55
import type { HomeKitPlugin } from "../main";
66

77
addSupportedType({
@@ -178,72 +178,60 @@ addSupportedType({
178178
() => device.humidity || 0);
179179
}
180180

181-
if (device.interfaces.includes(ScryptedInterface.HumiditySetting) && device.interfaces.includes(ScryptedInterface.HumiditySensor)) {
182-
const humidityService = accessory.addService(Service.HumidifierDehumidifier);
181+
// add fan state to thermostat service even though it is not required or optional,
182+
// in order to expose to Home Assistant HomeKit Controller under their climate entity
183+
if (device.interfaces.includes(ScryptedInterface.Fan)) {
184+
bindCharacteristic(device, ScryptedInterface.Fan, service, Characteristic.TargetFanState,
185+
() => device.fan?.mode === FanMode.Manual
186+
? Characteristic.TargetFanState.MANUAL
187+
: Characteristic.TargetFanState.AUTO);
183188

184-
bindCharacteristic(device, ScryptedInterface.HumiditySetting, humidityService, Characteristic.Active,
185-
() => {
186-
if (!device.humiditySetting?.mode)
187-
return false;
188-
if (device.humiditySetting.mode === HumidityMode.Off)
189-
return false;
190-
return true;
191-
});
192-
humidityService.getCharacteristic(Characteristic.Active).on(CharacteristicEventTypes.SET, (value, callback) => {
189+
service.getCharacteristic(Characteristic.TargetFanState).on(CharacteristicEventTypes.SET, (value, callback) => {
193190
callback();
194-
device.setHumidity({
195-
mode: value ? HumidityMode.Auto : HumidityMode.Off
191+
device.setFan({
192+
mode: value === Characteristic.TargetFanState.MANUAL ? FanMode.Manual : FanMode.Auto,
196193
});
197194
});
198195

199-
bindCharacteristic(device, ScryptedInterface.HumiditySensor, humidityService, Characteristic.CurrentRelativeHumidity,
200-
() => device.humidity || 0);
196+
bindCharacteristic(device, ScryptedInterface.Fan, service, Characteristic.CurrentFanState,
197+
() => !device.fan?.active
198+
? Characteristic.CurrentFanState.INACTIVE
199+
: !device.fan.speed
200+
? Characteristic.CurrentFanState.IDLE
201+
: Characteristic.CurrentFanState.BLOWING_AIR);
202+
}
201203

202-
bindCharacteristic(device, ScryptedInterface.HumiditySetting, humidityService, Characteristic.CurrentHumidifierDehumidifierState,
203-
() => !device.humiditySetting?.activeMode
204-
? Characteristic.CurrentHumidifierDehumidifierState.INACTIVE
205-
: device.humiditySetting.activeMode === HumidityMode.Dehumidify
206-
? Characteristic.CurrentHumidifierDehumidifierState.DEHUMIDIFYING
207-
: device.humiditySetting.activeMode === HumidityMode.Humidify
208-
? Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING
209-
: Characteristic.CurrentHumidifierDehumidifierState.IDLE);
210-
211-
bindCharacteristic(device, ScryptedInterface.HumiditySetting, humidityService, Characteristic.TargetHumidifierDehumidifierState,
212-
() => !device.humiditySetting?.mode || device.humiditySetting?.mode === HumidityMode.Auto
213-
? Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER
214-
: device.humiditySetting?.mode === HumidityMode.Dehumidify
215-
? Characteristic.TargetHumidifierDehumidifierState.DEHUMIDIFIER
216-
: Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER);
217-
humidityService.getCharacteristic(Characteristic.TargetHumidifierDehumidifierState).on(CharacteristicEventTypes.SET, (value, callback) => {
218-
callback();
219-
device.setHumidity({
220-
mode: value === Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER
221-
? HumidityMode.Humidify
222-
: value === Characteristic.TargetHumidifierDehumidifierState.DEHUMIDIFIER
223-
? HumidityMode.Dehumidify
224-
: HumidityMode.Auto
225-
});
226-
});
204+
// add relataive target humidity to thermostat service even though it is not required or optional,
205+
// in order to expose to Home Assistant HomeKit Controller under their climate entity
206+
if (device.interfaces.includes(ScryptedInterface.HumiditySetting)) {
207+
function targetHumidity(setting: HumiditySettingStatus) {
208+
if (!setting)
209+
return 0;
227210

228-
bindCharacteristic(device, ScryptedInterface.HumiditySetting, humidityService, Characteristic.RelativeHumidityHumidifierThreshold,
229-
() => device.humiditySetting?.humidifierSetpoint || 0);
230-
humidityService.getCharacteristic(Characteristic.RelativeHumidityHumidifierThreshold).on(CharacteristicEventTypes.SET, (value, callback) => {
231-
callback();
232-
device.setHumidity({
233-
humidifierSetpoint: value as number,
234-
});
235-
});
211+
if (setting?.availableModes.includes(HumidityMode.Humidify)
212+
&& setting?.availableModes.includes(HumidityMode.Dehumidify)) {
213+
if (setting?.activeMode === HumidityMode.Humidify)
214+
return setting?.humidifierSetpoint;
215+
if (setting?.activeMode === HumidityMode.Dehumidify)
216+
return setting?.dehumidifierSetpoint;
236217

237-
bindCharacteristic(device, ScryptedInterface.HumiditySetting, humidityService, Characteristic.RelativeHumidityDehumidifierThreshold,
238-
() => device.humiditySetting?.dehumidifierSetpoint || 0);
239-
humidityService.getCharacteristic(Characteristic.RelativeHumidityDehumidifierThreshold).on(CharacteristicEventTypes.SET, (value, callback) => {
240-
callback();
241-
device.setHumidity({
242-
dehumidifierSetpoint: value as number,
243-
});
244-
});
218+
return 0;
219+
}
220+
221+
if (setting?.availableModes.includes(HumidityMode.Humidify))
222+
return setting?.humidifierSetpoint;
223+
224+
if (setting?.availableModes.includes(HumidityMode.Dehumidify))
225+
return setting?.dehumidifierSetpoint;
226+
227+
return 0;
228+
}
229+
230+
bindCharacteristic(device, ScryptedInterface.HumiditySetting, service, Characteristic.TargetRelativeHumidity,
231+
() => targetHumidity(device.humiditySetting));
245232
}
246233

234+
addHumiditySetting(device, accessory);
247235
addFan(device, accessory);
248236
addAirQualitySensor(device, accessory);
249237
addCarbonDioxideSensor(device, accessory);

0 commit comments

Comments
 (0)