homebridge-salus-cloud 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +176 -0
- package/README.md +112 -0
- package/config.schema.json +148 -0
- package/dist/deviceCatalog.d.ts +10 -0
- package/dist/deviceCatalog.js +237 -0
- package/dist/deviceCatalog.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +27 -0
- package/dist/platform.js +329 -0
- package/dist/platform.js.map +1 -0
- package/dist/platformAccessory.d.ts +62 -0
- package/dist/platformAccessory.js +820 -0
- package/dist/platformAccessory.js.map +1 -0
- package/dist/propertyUtils.d.ts +15 -0
- package/dist/propertyUtils.js +185 -0
- package/dist/propertyUtils.js.map +1 -0
- package/dist/salusCloudClient.d.ts +52 -0
- package/dist/salusCloudClient.js +1447 -0
- package/dist/salusCloudClient.js.map +1 -0
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +3 -0
- package/dist/settings.js.map +1 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -0
- package/scripts/publish.sh +117 -0
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
2
|
+
import { clamp, encodeBooleanLike, encodePercentageLike, findPropertyByBaseName, getBooleanProperty, getNumberProperty, normalizePercentage, normalizeTemperatureFromX100, } from './propertyUtils.js';
|
|
3
|
+
const THERMOSTAT_CURRENT_TEMP = ['LocalTemperature_x100', 'MeasuredValue_x100', 'Temperature_x100', 'MeasuredValue'];
|
|
4
|
+
const THERMOSTAT_HEAT_SETPOINT = ['HeatingSetpoint_x100', 'SetHeatingSetpoint_x100', 'CloudySetpoint_x100'];
|
|
5
|
+
const THERMOSTAT_COOL_SETPOINT = ['CoolingSetpoint_x100', 'SetCoolingSetpoint_x100'];
|
|
6
|
+
const THERMOSTAT_SYSTEM_MODE = ['SystemMode', 'SetSystemMode'];
|
|
7
|
+
const THERMOSTAT_RUNNING_STATE = ['RunningState', 'RunningMode'];
|
|
8
|
+
const THERMOSTAT_HOLD_TYPE = ['HoldType', 'SetHoldType'];
|
|
9
|
+
const THERMOSTAT_HUMIDITY = ['RelativeHumidity', 'Humidity', 'SunnySetpoint_x100'];
|
|
10
|
+
const WRITE_SYSTEM_MODE = ['SetSystemMode', 'SystemMode'];
|
|
11
|
+
const WRITE_HEAT_SETPOINT = ['SetHeatingSetpoint_x100', 'HeatingSetpoint_x100'];
|
|
12
|
+
const WRITE_COOL_SETPOINT = ['SetCoolingSetpoint_x100', 'CoolingSetpoint_x100'];
|
|
13
|
+
const WRITE_AUTO_HEAT_SETPOINT = ['SetAutoHeatingSetpoint_x100', 'SetHeatingSetpoint_x100', 'HeatingSetpoint_x100'];
|
|
14
|
+
const WRITE_AUTO_COOL_SETPOINT = ['SetAutoCoolingSetpoint_x100', 'SetCoolingSetpoint_x100', 'CoolingSetpoint_x100'];
|
|
15
|
+
const WRITE_ON_OFF = ['SetOnOff', 'OnOff', 'ValveStatus', 'ButtonStatus', 'Mode'];
|
|
16
|
+
const WRITE_LEVEL = ['SetLevel', 'CurrentLevel', 'Level', 'Brightness', 'DimLevel'];
|
|
17
|
+
const WRITE_POSITION = ['TargetPosition', 'TargetLevel', 'CurrentLevel', 'LiftPercentage'];
|
|
18
|
+
const WRITE_LOCK = ['SetLockState', 'LockState', 'Lock', 'LockStatus', 'DoorLock'];
|
|
19
|
+
const WRITE_HOLD = ['SetHoldType', 'HoldType'];
|
|
20
|
+
const GENERIC_ONOFF = ['OnOff', 'ValveStatus', 'ButtonStatus', 'Mode'];
|
|
21
|
+
const GENERIC_LEVEL = ['CurrentLevel', 'Level', 'Brightness', 'DimLevel'];
|
|
22
|
+
const GENERIC_POSITION = ['CurrentPosition', 'CurrentLevel', 'LiftPercentage'];
|
|
23
|
+
const GENERIC_LOCK = ['LockState', 'Lock', 'LockStatus', 'DoorLock'];
|
|
24
|
+
const GENERIC_TEMPERATURE = ['MeasuredValue_x100', 'LocalTemperature_x100', 'Temperature_x100', 'MeasuredValue'];
|
|
25
|
+
const GENERIC_HUMIDITY = ['Humidity_x100', 'RelativeHumidity', 'Humidity', 'MeasuredValue'];
|
|
26
|
+
const GENERIC_BOOLEAN = [
|
|
27
|
+
'ErrorIASZSAlarmed1',
|
|
28
|
+
'ErrorIASZSAlarmed2',
|
|
29
|
+
'Alarmed',
|
|
30
|
+
'Leak',
|
|
31
|
+
'Leakage',
|
|
32
|
+
'Smoke',
|
|
33
|
+
'CO',
|
|
34
|
+
'Motion',
|
|
35
|
+
'Occupancy',
|
|
36
|
+
'Open',
|
|
37
|
+
];
|
|
38
|
+
const SALUS_MODE = {
|
|
39
|
+
off: 0,
|
|
40
|
+
auto: 1,
|
|
41
|
+
cool: 3,
|
|
42
|
+
heat: 4,
|
|
43
|
+
};
|
|
44
|
+
export class SalusPlatformAccessory {
|
|
45
|
+
platform;
|
|
46
|
+
accessory;
|
|
47
|
+
device;
|
|
48
|
+
profile;
|
|
49
|
+
service;
|
|
50
|
+
currentKind;
|
|
51
|
+
context;
|
|
52
|
+
latestProperties = new Map();
|
|
53
|
+
writeTargets = {};
|
|
54
|
+
cachedTargetState = 0;
|
|
55
|
+
cachedSystemMode = SALUS_MODE.auto;
|
|
56
|
+
constructor(platform, accessory, device, profile) {
|
|
57
|
+
this.platform = platform;
|
|
58
|
+
this.accessory = accessory;
|
|
59
|
+
this.device = device;
|
|
60
|
+
this.profile = profile;
|
|
61
|
+
this.context = this.accessory.context;
|
|
62
|
+
this.context.device = toContextDevice(device);
|
|
63
|
+
this.context.profile = profile;
|
|
64
|
+
this.currentKind = profile.kind;
|
|
65
|
+
this.setAccessoryInformation();
|
|
66
|
+
this.service = this.ensureService(this.currentKind);
|
|
67
|
+
this.configureHandlersForCurrentKind();
|
|
68
|
+
}
|
|
69
|
+
updateFromCloud(device, profile, properties) {
|
|
70
|
+
this.device = device;
|
|
71
|
+
this.profile = profile;
|
|
72
|
+
this.latestProperties = properties;
|
|
73
|
+
this.context.device = toContextDevice(device);
|
|
74
|
+
this.context.profile = profile;
|
|
75
|
+
this.setAccessoryInformation();
|
|
76
|
+
if (this.currentKind !== profile.kind) {
|
|
77
|
+
this.currentKind = profile.kind;
|
|
78
|
+
this.service = this.ensureService(this.currentKind);
|
|
79
|
+
this.configureHandlersForCurrentKind();
|
|
80
|
+
this.platform.log.info(`Reconfigured ${device.name} (${device.dsn}) as ${profile.kind}`);
|
|
81
|
+
}
|
|
82
|
+
this.service.updateCharacteristic(this.platform.Characteristic.Name, this.device.name);
|
|
83
|
+
this.refreshWriteTargets();
|
|
84
|
+
this.updateServiceCharacteristics();
|
|
85
|
+
}
|
|
86
|
+
getCurrentKind() {
|
|
87
|
+
return this.currentKind;
|
|
88
|
+
}
|
|
89
|
+
setAccessoryInformation() {
|
|
90
|
+
this.accessory.getService(this.platform.Service.AccessoryInformation)
|
|
91
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Salus')
|
|
92
|
+
.setCharacteristic(this.platform.Characteristic.Model, this.device.model || 'Unknown')
|
|
93
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, this.device.dsn);
|
|
94
|
+
}
|
|
95
|
+
ensureService(kind) {
|
|
96
|
+
const infoService = this.accessory.getService(this.platform.Service.AccessoryInformation);
|
|
97
|
+
const existingPrimary = this.accessory.services.find((service) => service !== infoService);
|
|
98
|
+
const targetServiceConstructor = this.getServiceConstructor(kind);
|
|
99
|
+
const desiredServiceDisplay = this.getServiceDisplayName(kind);
|
|
100
|
+
// Always recreate non-information services when kind may have changed.
|
|
101
|
+
// This avoids stale characteristic handlers across service type transitions.
|
|
102
|
+
void existingPrimary;
|
|
103
|
+
for (const service of this.accessory.services) {
|
|
104
|
+
if (service !== infoService) {
|
|
105
|
+
this.accessory.removeService(service);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return this.accessory.addService(targetServiceConstructor, desiredServiceDisplay, 'primary');
|
|
109
|
+
}
|
|
110
|
+
configureHandlersForCurrentKind() {
|
|
111
|
+
switch (this.currentKind) {
|
|
112
|
+
case 'thermostat':
|
|
113
|
+
this.configureThermostat();
|
|
114
|
+
break;
|
|
115
|
+
case 'switch':
|
|
116
|
+
this.configureSwitch();
|
|
117
|
+
break;
|
|
118
|
+
case 'outlet':
|
|
119
|
+
this.configureOutlet();
|
|
120
|
+
break;
|
|
121
|
+
case 'lightbulb':
|
|
122
|
+
this.configureLightbulb();
|
|
123
|
+
break;
|
|
124
|
+
case 'windowCovering':
|
|
125
|
+
this.configureWindowCovering();
|
|
126
|
+
break;
|
|
127
|
+
case 'valve':
|
|
128
|
+
this.configureValve();
|
|
129
|
+
break;
|
|
130
|
+
case 'lock':
|
|
131
|
+
this.configureLock();
|
|
132
|
+
break;
|
|
133
|
+
case 'motionSensor':
|
|
134
|
+
case 'contactSensor':
|
|
135
|
+
case 'leakSensor':
|
|
136
|
+
case 'smokeSensor':
|
|
137
|
+
case 'carbonMonoxideSensor':
|
|
138
|
+
case 'temperatureSensor':
|
|
139
|
+
case 'humiditySensor':
|
|
140
|
+
case 'airQualitySensor':
|
|
141
|
+
case 'occupancySensor':
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
this.configureSwitch();
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
configureThermostat() {
|
|
149
|
+
this.service.getCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits)
|
|
150
|
+
.updateValue(this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS);
|
|
151
|
+
this.service.getCharacteristic(this.platform.Characteristic.CurrentTemperature).setProps({
|
|
152
|
+
minValue: -40,
|
|
153
|
+
maxValue: 100,
|
|
154
|
+
minStep: 0.1,
|
|
155
|
+
});
|
|
156
|
+
this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature).setProps({
|
|
157
|
+
minValue: 4.5,
|
|
158
|
+
maxValue: 35,
|
|
159
|
+
minStep: 0.5,
|
|
160
|
+
}).onSet(this.setTargetTemperature.bind(this));
|
|
161
|
+
this.service.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState).onSet(this.setTargetHeatingCoolingState.bind(this));
|
|
162
|
+
}
|
|
163
|
+
configureSwitch() {
|
|
164
|
+
this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.setOnOff.bind(this));
|
|
165
|
+
}
|
|
166
|
+
configureOutlet() {
|
|
167
|
+
this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.setOnOff.bind(this));
|
|
168
|
+
}
|
|
169
|
+
configureLightbulb() {
|
|
170
|
+
this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.setOnOff.bind(this));
|
|
171
|
+
this.service.getCharacteristic(this.platform.Characteristic.Brightness).setProps({
|
|
172
|
+
minValue: 0,
|
|
173
|
+
maxValue: 100,
|
|
174
|
+
minStep: 1,
|
|
175
|
+
}).onSet(this.setBrightness.bind(this));
|
|
176
|
+
}
|
|
177
|
+
configureWindowCovering() {
|
|
178
|
+
this.service.getCharacteristic(this.platform.Characteristic.TargetPosition).onSet(this.setTargetPosition.bind(this));
|
|
179
|
+
}
|
|
180
|
+
configureValve() {
|
|
181
|
+
this.service.getCharacteristic(this.platform.Characteristic.Active).onSet(this.setValveActive.bind(this));
|
|
182
|
+
}
|
|
183
|
+
configureLock() {
|
|
184
|
+
this.service.getCharacteristic(this.platform.Characteristic.LockTargetState).onSet(this.setLockTargetState.bind(this));
|
|
185
|
+
}
|
|
186
|
+
refreshWriteTargets() {
|
|
187
|
+
this.writeTargets = {
|
|
188
|
+
systemMode: findPropertyByBaseName(this.latestProperties, WRITE_SYSTEM_MODE),
|
|
189
|
+
holdType: findPropertyByBaseName(this.latestProperties, WRITE_HOLD),
|
|
190
|
+
heatSetpoint: findPropertyByBaseName(this.latestProperties, WRITE_HEAT_SETPOINT),
|
|
191
|
+
coolSetpoint: findPropertyByBaseName(this.latestProperties, WRITE_COOL_SETPOINT),
|
|
192
|
+
autoHeatSetpoint: findPropertyByBaseName(this.latestProperties, WRITE_AUTO_HEAT_SETPOINT),
|
|
193
|
+
autoCoolSetpoint: findPropertyByBaseName(this.latestProperties, WRITE_AUTO_COOL_SETPOINT),
|
|
194
|
+
onOff: findPropertyByBaseName(this.latestProperties, WRITE_ON_OFF),
|
|
195
|
+
brightness: findPropertyByBaseName(this.latestProperties, WRITE_LEVEL),
|
|
196
|
+
position: findPropertyByBaseName(this.latestProperties, WRITE_POSITION),
|
|
197
|
+
lock: findPropertyByBaseName(this.latestProperties, WRITE_LOCK),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
updateServiceCharacteristics() {
|
|
201
|
+
switch (this.currentKind) {
|
|
202
|
+
case 'thermostat':
|
|
203
|
+
this.updateThermostatCharacteristics();
|
|
204
|
+
break;
|
|
205
|
+
case 'switch':
|
|
206
|
+
this.updateSwitchCharacteristics();
|
|
207
|
+
break;
|
|
208
|
+
case 'outlet':
|
|
209
|
+
this.updateOutletCharacteristics();
|
|
210
|
+
break;
|
|
211
|
+
case 'lightbulb':
|
|
212
|
+
this.updateLightbulbCharacteristics();
|
|
213
|
+
break;
|
|
214
|
+
case 'windowCovering':
|
|
215
|
+
this.updateWindowCoveringCharacteristics();
|
|
216
|
+
break;
|
|
217
|
+
case 'valve':
|
|
218
|
+
this.updateValveCharacteristics();
|
|
219
|
+
break;
|
|
220
|
+
case 'lock':
|
|
221
|
+
this.updateLockCharacteristics();
|
|
222
|
+
break;
|
|
223
|
+
case 'motionSensor':
|
|
224
|
+
this.updateMotionSensorCharacteristics();
|
|
225
|
+
break;
|
|
226
|
+
case 'contactSensor':
|
|
227
|
+
this.updateContactSensorCharacteristics();
|
|
228
|
+
break;
|
|
229
|
+
case 'leakSensor':
|
|
230
|
+
this.updateLeakSensorCharacteristics();
|
|
231
|
+
break;
|
|
232
|
+
case 'smokeSensor':
|
|
233
|
+
this.updateSmokeSensorCharacteristics();
|
|
234
|
+
break;
|
|
235
|
+
case 'carbonMonoxideSensor':
|
|
236
|
+
this.updateCarbonMonoxideCharacteristics();
|
|
237
|
+
break;
|
|
238
|
+
case 'temperatureSensor':
|
|
239
|
+
this.updateTemperatureSensorCharacteristics();
|
|
240
|
+
break;
|
|
241
|
+
case 'humiditySensor':
|
|
242
|
+
this.updateHumiditySensorCharacteristics();
|
|
243
|
+
break;
|
|
244
|
+
case 'airQualitySensor':
|
|
245
|
+
this.updateAirQualityCharacteristics();
|
|
246
|
+
break;
|
|
247
|
+
case 'occupancySensor':
|
|
248
|
+
this.updateOccupancySensorCharacteristics();
|
|
249
|
+
break;
|
|
250
|
+
default:
|
|
251
|
+
this.updateSwitchCharacteristics();
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
updateThermostatCharacteristics() {
|
|
256
|
+
const currentTempRaw = getNumberProperty(this.latestProperties, THERMOSTAT_CURRENT_TEMP);
|
|
257
|
+
const heatingSetpointRaw = getNumberProperty(this.latestProperties, THERMOSTAT_HEAT_SETPOINT);
|
|
258
|
+
const coolingSetpointRaw = getNumberProperty(this.latestProperties, THERMOSTAT_COOL_SETPOINT);
|
|
259
|
+
const systemModeRaw = getNumberProperty(this.latestProperties, THERMOSTAT_SYSTEM_MODE);
|
|
260
|
+
const runningStateRaw = getNumberProperty(this.latestProperties, THERMOSTAT_RUNNING_STATE);
|
|
261
|
+
const holdTypeRaw = getNumberProperty(this.latestProperties, THERMOSTAT_HOLD_TYPE);
|
|
262
|
+
const humidityRaw = getNumberProperty(this.latestProperties, THERMOSTAT_HUMIDITY);
|
|
263
|
+
if (systemModeRaw !== undefined) {
|
|
264
|
+
this.cachedSystemMode = Math.round(systemModeRaw);
|
|
265
|
+
}
|
|
266
|
+
const currentTemp = currentTempRaw !== undefined
|
|
267
|
+
? clamp(normalizeTemperatureFromX100(currentTempRaw), -40, 100)
|
|
268
|
+
: undefined;
|
|
269
|
+
const heatingSetpoint = heatingSetpointRaw !== undefined
|
|
270
|
+
? clamp(normalizeTemperatureFromX100(heatingSetpointRaw), 4.5, 35)
|
|
271
|
+
: undefined;
|
|
272
|
+
const coolingSetpoint = coolingSetpointRaw !== undefined
|
|
273
|
+
? clamp(normalizeTemperatureFromX100(coolingSetpointRaw), 4.5, 35)
|
|
274
|
+
: undefined;
|
|
275
|
+
const targetState = mapSystemModeToTargetState(this.platform.Characteristic.TargetHeatingCoolingState, this.cachedSystemMode, holdTypeRaw);
|
|
276
|
+
const currentState = mapRunningStateToCurrentState(this.platform.Characteristic.CurrentHeatingCoolingState, runningStateRaw, holdTypeRaw);
|
|
277
|
+
this.cachedTargetState = targetState;
|
|
278
|
+
let targetTemp = heatingSetpoint ?? coolingSetpoint ?? 21;
|
|
279
|
+
if (targetState === this.platform.Characteristic.TargetHeatingCoolingState.COOL && coolingSetpoint !== undefined) {
|
|
280
|
+
targetTemp = coolingSetpoint;
|
|
281
|
+
}
|
|
282
|
+
if (targetState === this.platform.Characteristic.TargetHeatingCoolingState.OFF) {
|
|
283
|
+
targetTemp = heatingSetpoint ?? coolingSetpoint ?? targetTemp;
|
|
284
|
+
}
|
|
285
|
+
if (currentTemp !== undefined) {
|
|
286
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, currentTemp);
|
|
287
|
+
}
|
|
288
|
+
this.service.updateCharacteristic(this.platform.Characteristic.TargetTemperature, targetTemp);
|
|
289
|
+
this.service.updateCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState, targetState);
|
|
290
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, currentState);
|
|
291
|
+
if (humidityRaw !== undefined) {
|
|
292
|
+
const humidity = humidityRaw > 100 ? humidityRaw / 100 : humidityRaw;
|
|
293
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, clamp(humidity, 0, 100));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
updateSwitchCharacteristics() {
|
|
297
|
+
const on = this.resolveOnOffState();
|
|
298
|
+
if (on !== undefined) {
|
|
299
|
+
this.service.updateCharacteristic(this.platform.Characteristic.On, on);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
updateOutletCharacteristics() {
|
|
303
|
+
const on = this.resolveOnOffState();
|
|
304
|
+
if (on !== undefined) {
|
|
305
|
+
this.service.updateCharacteristic(this.platform.Characteristic.On, on);
|
|
306
|
+
this.service.updateCharacteristic(this.platform.Characteristic.OutletInUse, on);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
updateLightbulbCharacteristics() {
|
|
310
|
+
const on = this.resolveOnOffState();
|
|
311
|
+
if (on !== undefined) {
|
|
312
|
+
this.service.updateCharacteristic(this.platform.Characteristic.On, on);
|
|
313
|
+
}
|
|
314
|
+
const brightnessValue = normalizePercentage(this.getPropertyValue(this.writeTargets.brightness))
|
|
315
|
+
?? normalizePercentage(getNumberProperty(this.latestProperties, GENERIC_LEVEL));
|
|
316
|
+
if (brightnessValue !== undefined) {
|
|
317
|
+
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, brightnessValue);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
updateWindowCoveringCharacteristics() {
|
|
321
|
+
const percentage = normalizePercentage(this.getPropertyValue(this.writeTargets.position))
|
|
322
|
+
?? normalizePercentage(getNumberProperty(this.latestProperties, GENERIC_POSITION));
|
|
323
|
+
if (percentage === undefined) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CurrentPosition, percentage);
|
|
327
|
+
this.service.updateCharacteristic(this.platform.Characteristic.TargetPosition, percentage);
|
|
328
|
+
this.service.updateCharacteristic(this.platform.Characteristic.PositionState, this.platform.Characteristic.PositionState.STOPPED);
|
|
329
|
+
}
|
|
330
|
+
updateValveCharacteristics() {
|
|
331
|
+
const active = this.resolveOnOffState();
|
|
332
|
+
if (active === undefined) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const hkActive = active ? this.platform.Characteristic.Active.ACTIVE : this.platform.Characteristic.Active.INACTIVE;
|
|
336
|
+
const hkInUse = active ? this.platform.Characteristic.InUse.IN_USE : this.platform.Characteristic.InUse.NOT_IN_USE;
|
|
337
|
+
this.service.updateCharacteristic(this.platform.Characteristic.Active, hkActive);
|
|
338
|
+
this.service.updateCharacteristic(this.platform.Characteristic.InUse, hkInUse);
|
|
339
|
+
this.service.updateCharacteristic(this.platform.Characteristic.ValveType, this.platform.Characteristic.ValveType.GENERIC_VALVE);
|
|
340
|
+
}
|
|
341
|
+
updateLockCharacteristics() {
|
|
342
|
+
const locked = this.resolveLockState();
|
|
343
|
+
if (locked === undefined) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const current = locked
|
|
347
|
+
? this.platform.Characteristic.LockCurrentState.SECURED
|
|
348
|
+
: this.platform.Characteristic.LockCurrentState.UNSECURED;
|
|
349
|
+
const target = locked
|
|
350
|
+
? this.platform.Characteristic.LockTargetState.SECURED
|
|
351
|
+
: this.platform.Characteristic.LockTargetState.UNSECURED;
|
|
352
|
+
this.service.updateCharacteristic(this.platform.Characteristic.LockCurrentState, current);
|
|
353
|
+
this.service.updateCharacteristic(this.platform.Characteristic.LockTargetState, target);
|
|
354
|
+
}
|
|
355
|
+
updateMotionSensorCharacteristics() {
|
|
356
|
+
const value = this.resolveGenericBoolean();
|
|
357
|
+
if (value !== undefined) {
|
|
358
|
+
this.service.updateCharacteristic(this.platform.Characteristic.MotionDetected, value);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
updateContactSensorCharacteristics() {
|
|
362
|
+
const value = this.resolveGenericBoolean();
|
|
363
|
+
if (value !== undefined) {
|
|
364
|
+
const state = value
|
|
365
|
+
? this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
|
|
366
|
+
: this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED;
|
|
367
|
+
this.service.updateCharacteristic(this.platform.Characteristic.ContactSensorState, state);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
updateLeakSensorCharacteristics() {
|
|
371
|
+
const value = this.resolveGenericBoolean();
|
|
372
|
+
if (value !== undefined) {
|
|
373
|
+
const state = value
|
|
374
|
+
? this.platform.Characteristic.LeakDetected.LEAK_DETECTED
|
|
375
|
+
: this.platform.Characteristic.LeakDetected.LEAK_NOT_DETECTED;
|
|
376
|
+
this.service.updateCharacteristic(this.platform.Characteristic.LeakDetected, state);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
updateSmokeSensorCharacteristics() {
|
|
380
|
+
const value = this.resolveGenericBoolean();
|
|
381
|
+
if (value !== undefined) {
|
|
382
|
+
const state = value
|
|
383
|
+
? this.platform.Characteristic.SmokeDetected.SMOKE_DETECTED
|
|
384
|
+
: this.platform.Characteristic.SmokeDetected.SMOKE_NOT_DETECTED;
|
|
385
|
+
this.service.updateCharacteristic(this.platform.Characteristic.SmokeDetected, state);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
updateCarbonMonoxideCharacteristics() {
|
|
389
|
+
const value = this.resolveGenericBoolean();
|
|
390
|
+
if (value !== undefined) {
|
|
391
|
+
const state = value
|
|
392
|
+
? this.platform.Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL
|
|
393
|
+
: this.platform.Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL;
|
|
394
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CarbonMonoxideDetected, state);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
updateTemperatureSensorCharacteristics() {
|
|
398
|
+
const tempRaw = getNumberProperty(this.latestProperties, GENERIC_TEMPERATURE);
|
|
399
|
+
if (tempRaw !== undefined) {
|
|
400
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, clamp(normalizeTemperatureFromX100(tempRaw), -40, 100));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
updateHumiditySensorCharacteristics() {
|
|
404
|
+
const humidityRaw = getNumberProperty(this.latestProperties, GENERIC_HUMIDITY);
|
|
405
|
+
if (humidityRaw !== undefined) {
|
|
406
|
+
const humidity = humidityRaw > 100 ? humidityRaw / 100 : humidityRaw;
|
|
407
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, clamp(humidity, 0, 100));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
updateAirQualityCharacteristics() {
|
|
411
|
+
const co2 = getNumberProperty(this.latestProperties, ['MeasuredValue', 'CO2', 'CarbonDioxide']);
|
|
412
|
+
if (co2 === undefined) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CarbonDioxideLevel, co2);
|
|
416
|
+
if (co2 >= 1000) {
|
|
417
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CarbonDioxideDetected, this.platform.Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL);
|
|
418
|
+
this.service.updateCharacteristic(this.platform.Characteristic.AirQuality, this.platform.Characteristic.AirQuality.POOR);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CarbonDioxideDetected, this.platform.Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL);
|
|
422
|
+
this.service.updateCharacteristic(this.platform.Characteristic.AirQuality, this.platform.Characteristic.AirQuality.EXCELLENT);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
updateOccupancySensorCharacteristics() {
|
|
426
|
+
const value = this.resolveGenericBoolean();
|
|
427
|
+
if (value !== undefined) {
|
|
428
|
+
const occupancy = value
|
|
429
|
+
? this.platform.Characteristic.OccupancyDetected.OCCUPANCY_DETECTED
|
|
430
|
+
: this.platform.Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED;
|
|
431
|
+
this.service.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, occupancy);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
resolveOnOffState() {
|
|
435
|
+
const direct = getBooleanProperty(this.latestProperties, GENERIC_ONOFF);
|
|
436
|
+
if (direct !== undefined) {
|
|
437
|
+
return direct;
|
|
438
|
+
}
|
|
439
|
+
const brightness = normalizePercentage(getNumberProperty(this.latestProperties, GENERIC_LEVEL));
|
|
440
|
+
if (brightness !== undefined) {
|
|
441
|
+
return brightness > 0;
|
|
442
|
+
}
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
resolveLockState() {
|
|
446
|
+
const bool = getBooleanProperty(this.latestProperties, GENERIC_LOCK);
|
|
447
|
+
if (bool !== undefined) {
|
|
448
|
+
return bool;
|
|
449
|
+
}
|
|
450
|
+
const numeric = getNumberProperty(this.latestProperties, GENERIC_LOCK);
|
|
451
|
+
if (numeric !== undefined) {
|
|
452
|
+
return numeric !== 0;
|
|
453
|
+
}
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
resolveGenericBoolean() {
|
|
457
|
+
const direct = getBooleanProperty(this.latestProperties, GENERIC_BOOLEAN);
|
|
458
|
+
if (direct !== undefined) {
|
|
459
|
+
return direct;
|
|
460
|
+
}
|
|
461
|
+
const fallback = getNumberProperty(this.latestProperties, GENERIC_BOOLEAN);
|
|
462
|
+
if (fallback !== undefined) {
|
|
463
|
+
return fallback !== 0;
|
|
464
|
+
}
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
getPropertyValue(name) {
|
|
468
|
+
if (!name) {
|
|
469
|
+
return undefined;
|
|
470
|
+
}
|
|
471
|
+
return this.latestProperties.get(name)?.value;
|
|
472
|
+
}
|
|
473
|
+
async setTargetTemperature(value) {
|
|
474
|
+
const numericValue = parseCharacteristicNumber(value);
|
|
475
|
+
if (numericValue === undefined) {
|
|
476
|
+
throw this.communicationFailure('Received invalid thermostat target temperature value from HomeKit');
|
|
477
|
+
}
|
|
478
|
+
const targetTemperature = clamp(numericValue, 4.5, 35);
|
|
479
|
+
const scaled = Math.round(targetTemperature * 100);
|
|
480
|
+
const targetState = this.cachedTargetState;
|
|
481
|
+
const writes = [];
|
|
482
|
+
const queuedProperties = new Set();
|
|
483
|
+
const pickFirstProperty = (candidates) => {
|
|
484
|
+
for (const candidate of candidates) {
|
|
485
|
+
if (candidate) {
|
|
486
|
+
return candidate;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return undefined;
|
|
490
|
+
};
|
|
491
|
+
const enqueueWrite = (property) => {
|
|
492
|
+
if (!property || queuedProperties.has(property)) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
queuedProperties.add(property);
|
|
496
|
+
writes.push({ property, value: scaled });
|
|
497
|
+
};
|
|
498
|
+
if (targetState === this.platform.Characteristic.TargetHeatingCoolingState.COOL) {
|
|
499
|
+
enqueueWrite(pickFirstProperty([
|
|
500
|
+
this.writeTargets.coolSetpoint,
|
|
501
|
+
this.writeTargets.autoCoolSetpoint,
|
|
502
|
+
this.writeTargets.heatSetpoint,
|
|
503
|
+
]));
|
|
504
|
+
}
|
|
505
|
+
else if (targetState === this.platform.Characteristic.TargetHeatingCoolingState.AUTO) {
|
|
506
|
+
enqueueWrite(pickFirstProperty([this.writeTargets.autoHeatSetpoint, this.writeTargets.heatSetpoint]));
|
|
507
|
+
enqueueWrite(pickFirstProperty([this.writeTargets.autoCoolSetpoint, this.writeTargets.coolSetpoint]));
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
enqueueWrite(pickFirstProperty([
|
|
511
|
+
this.writeTargets.heatSetpoint,
|
|
512
|
+
this.writeTargets.autoHeatSetpoint,
|
|
513
|
+
this.writeTargets.coolSetpoint,
|
|
514
|
+
this.writeTargets.autoCoolSetpoint,
|
|
515
|
+
]));
|
|
516
|
+
}
|
|
517
|
+
if (writes.length === 0) {
|
|
518
|
+
throw this.communicationFailure('No writable thermostat setpoint property was discovered');
|
|
519
|
+
}
|
|
520
|
+
for (const write of writes) {
|
|
521
|
+
await this.platform.writeDeviceProperty(this.device, write.property, write.value);
|
|
522
|
+
}
|
|
523
|
+
this.platform.log.info(`Set thermostat target for ${this.device.name} to ${targetTemperature.toFixed(1)}°C`);
|
|
524
|
+
}
|
|
525
|
+
async setTargetHeatingCoolingState(value) {
|
|
526
|
+
const hkStateRaw = parseCharacteristicNumber(value);
|
|
527
|
+
if (hkStateRaw === undefined) {
|
|
528
|
+
throw this.communicationFailure('Received invalid thermostat mode value from HomeKit');
|
|
529
|
+
}
|
|
530
|
+
const hkState = Math.round(hkStateRaw);
|
|
531
|
+
let mode = SALUS_MODE.auto;
|
|
532
|
+
if (hkState === this.platform.Characteristic.TargetHeatingCoolingState.OFF) {
|
|
533
|
+
mode = SALUS_MODE.off;
|
|
534
|
+
}
|
|
535
|
+
else if (hkState === this.platform.Characteristic.TargetHeatingCoolingState.COOL) {
|
|
536
|
+
mode = SALUS_MODE.cool;
|
|
537
|
+
}
|
|
538
|
+
else if (hkState === this.platform.Characteristic.TargetHeatingCoolingState.HEAT) {
|
|
539
|
+
mode = SALUS_MODE.heat;
|
|
540
|
+
}
|
|
541
|
+
if (!this.writeTargets.systemMode) {
|
|
542
|
+
throw this.communicationFailure('No writable system mode property was discovered');
|
|
543
|
+
}
|
|
544
|
+
const holdTypeCurrent = getNumberProperty(this.latestProperties, THERMOSTAT_HOLD_TYPE);
|
|
545
|
+
if (mode !== SALUS_MODE.off && this.writeTargets.holdType && holdTypeCurrent !== undefined && Math.round(holdTypeCurrent) === 7) {
|
|
546
|
+
try {
|
|
547
|
+
await this.platform.writeDeviceProperty(this.device, this.writeTargets.holdType, 0);
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
this.platform.log.warn(`Unable to clear thermostat hold type for ${this.device.name}: ${asErrorMessage(error)}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
await this.platform.writeDeviceProperty(this.device, this.writeTargets.systemMode, mode);
|
|
554
|
+
if (mode === SALUS_MODE.off && this.writeTargets.holdType) {
|
|
555
|
+
try {
|
|
556
|
+
await this.platform.writeDeviceProperty(this.device, this.writeTargets.holdType, 7);
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
this.platform.log.warn(`Unable to apply thermostat off hold for ${this.device.name}: ${asErrorMessage(error)}`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
this.cachedTargetState = hkState;
|
|
563
|
+
this.cachedSystemMode = mode;
|
|
564
|
+
this.platform.log.info(`Set thermostat mode for ${this.device.name} to ${hkState}`);
|
|
565
|
+
}
|
|
566
|
+
async setOnOff(value) {
|
|
567
|
+
const on = parseCharacteristicBoolean(value);
|
|
568
|
+
if (on === undefined) {
|
|
569
|
+
throw this.communicationFailure('Received invalid On/Off value from HomeKit');
|
|
570
|
+
}
|
|
571
|
+
const property = this.writeTargets.onOff ?? this.writeTargets.brightness;
|
|
572
|
+
if (!property) {
|
|
573
|
+
throw this.communicationFailure('No writable On/Off property was discovered');
|
|
574
|
+
}
|
|
575
|
+
if (property === this.writeTargets.brightness) {
|
|
576
|
+
await this.setBrightness(on ? 100 : 0);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const sample = this.getPropertyValue(property);
|
|
580
|
+
const outgoingValue = encodeBooleanLike(sample, on);
|
|
581
|
+
await this.platform.writeDeviceProperty(this.device, property, outgoingValue);
|
|
582
|
+
this.platform.log.info(`Set On/Off for ${this.device.name} to ${on}`);
|
|
583
|
+
}
|
|
584
|
+
async setBrightness(value) {
|
|
585
|
+
const numericValue = parseCharacteristicNumber(value);
|
|
586
|
+
if (numericValue === undefined) {
|
|
587
|
+
throw this.communicationFailure('Received invalid brightness value from HomeKit');
|
|
588
|
+
}
|
|
589
|
+
const target = clamp(numericValue, 0, 100);
|
|
590
|
+
const property = this.writeTargets.brightness;
|
|
591
|
+
if (!property) {
|
|
592
|
+
throw this.communicationFailure('No writable brightness property was discovered');
|
|
593
|
+
}
|
|
594
|
+
const sample = this.getPropertyValue(property);
|
|
595
|
+
let outgoingValue = target;
|
|
596
|
+
if (typeof sample === 'number' && sample > 100) {
|
|
597
|
+
outgoingValue = Math.round((target / 100) * 255);
|
|
598
|
+
}
|
|
599
|
+
else if (typeof sample === 'string') {
|
|
600
|
+
outgoingValue = encodePercentageLike(sample, target);
|
|
601
|
+
}
|
|
602
|
+
await this.platform.writeDeviceProperty(this.device, property, outgoingValue);
|
|
603
|
+
this.platform.log.info(`Set brightness for ${this.device.name} to ${target}%`);
|
|
604
|
+
}
|
|
605
|
+
async setTargetPosition(value) {
|
|
606
|
+
const numericValue = parseCharacteristicNumber(value);
|
|
607
|
+
if (numericValue === undefined) {
|
|
608
|
+
throw this.communicationFailure('Received invalid target position value from HomeKit');
|
|
609
|
+
}
|
|
610
|
+
const target = clamp(numericValue, 0, 100);
|
|
611
|
+
const property = this.writeTargets.position;
|
|
612
|
+
if (!property) {
|
|
613
|
+
throw this.communicationFailure('No writable position property was discovered');
|
|
614
|
+
}
|
|
615
|
+
const sample = this.getPropertyValue(property);
|
|
616
|
+
const outgoingValue = encodePercentageLike(sample, target);
|
|
617
|
+
await this.platform.writeDeviceProperty(this.device, property, outgoingValue);
|
|
618
|
+
this.platform.log.info(`Set position for ${this.device.name} to ${target}%`);
|
|
619
|
+
}
|
|
620
|
+
async setValveActive(value) {
|
|
621
|
+
const numericValue = parseCharacteristicNumber(value);
|
|
622
|
+
if (numericValue === undefined) {
|
|
623
|
+
throw this.communicationFailure('Received invalid valve active value from HomeKit');
|
|
624
|
+
}
|
|
625
|
+
const active = Math.round(numericValue) === this.platform.Characteristic.Active.ACTIVE;
|
|
626
|
+
const property = this.writeTargets.onOff;
|
|
627
|
+
if (!property) {
|
|
628
|
+
throw this.communicationFailure('No writable valve property was discovered');
|
|
629
|
+
}
|
|
630
|
+
const sample = this.getPropertyValue(property);
|
|
631
|
+
const outgoingValue = encodeBooleanLike(sample, active);
|
|
632
|
+
await this.platform.writeDeviceProperty(this.device, property, outgoingValue);
|
|
633
|
+
this.platform.log.info(`Set valve for ${this.device.name} to ${active ? 'active' : 'inactive'}`);
|
|
634
|
+
}
|
|
635
|
+
async setLockTargetState(value) {
|
|
636
|
+
const numericValue = parseCharacteristicNumber(value);
|
|
637
|
+
if (numericValue === undefined) {
|
|
638
|
+
throw this.communicationFailure('Received invalid lock target value from HomeKit');
|
|
639
|
+
}
|
|
640
|
+
const shouldLock = Math.round(numericValue) === this.platform.Characteristic.LockTargetState.SECURED;
|
|
641
|
+
const property = this.writeTargets.lock;
|
|
642
|
+
if (!property) {
|
|
643
|
+
throw this.communicationFailure('No writable lock property was discovered');
|
|
644
|
+
}
|
|
645
|
+
const sample = this.getPropertyValue(property);
|
|
646
|
+
const outgoingValue = encodeBooleanLike(sample, shouldLock);
|
|
647
|
+
await this.platform.writeDeviceProperty(this.device, property, outgoingValue);
|
|
648
|
+
this.platform.log.info(`Set lock for ${this.device.name} to ${shouldLock ? 'secured' : 'unsecured'}`);
|
|
649
|
+
}
|
|
650
|
+
communicationFailure(message) {
|
|
651
|
+
this.platform.log.error(`${message} [${this.device.name} / ${this.device.dsn}]`);
|
|
652
|
+
return new this.platform.api.hap.HapStatusError(-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
|
|
653
|
+
}
|
|
654
|
+
getServiceDisplayName(kind) {
|
|
655
|
+
if (kind === 'thermostat') {
|
|
656
|
+
return 'Thermostat';
|
|
657
|
+
}
|
|
658
|
+
if (kind === 'windowCovering') {
|
|
659
|
+
return 'Window Covering';
|
|
660
|
+
}
|
|
661
|
+
if (kind === 'contactSensor') {
|
|
662
|
+
return 'Contact';
|
|
663
|
+
}
|
|
664
|
+
if (kind === 'motionSensor') {
|
|
665
|
+
return 'Motion';
|
|
666
|
+
}
|
|
667
|
+
if (kind === 'leakSensor') {
|
|
668
|
+
return 'Leak';
|
|
669
|
+
}
|
|
670
|
+
if (kind === 'smokeSensor') {
|
|
671
|
+
return 'Smoke';
|
|
672
|
+
}
|
|
673
|
+
if (kind === 'carbonMonoxideSensor') {
|
|
674
|
+
return 'Carbon Monoxide';
|
|
675
|
+
}
|
|
676
|
+
if (kind === 'temperatureSensor') {
|
|
677
|
+
return 'Temperature';
|
|
678
|
+
}
|
|
679
|
+
if (kind === 'humiditySensor') {
|
|
680
|
+
return 'Humidity';
|
|
681
|
+
}
|
|
682
|
+
if (kind === 'airQualitySensor') {
|
|
683
|
+
return 'Air Quality';
|
|
684
|
+
}
|
|
685
|
+
if (kind === 'occupancySensor') {
|
|
686
|
+
return 'Occupancy';
|
|
687
|
+
}
|
|
688
|
+
if (kind === 'lightbulb') {
|
|
689
|
+
return 'Light';
|
|
690
|
+
}
|
|
691
|
+
if (kind === 'outlet') {
|
|
692
|
+
return 'Outlet';
|
|
693
|
+
}
|
|
694
|
+
if (kind === 'valve') {
|
|
695
|
+
return 'Valve';
|
|
696
|
+
}
|
|
697
|
+
if (kind === 'lock') {
|
|
698
|
+
return 'Lock';
|
|
699
|
+
}
|
|
700
|
+
return 'Switch';
|
|
701
|
+
}
|
|
702
|
+
getServiceConstructor(kind) {
|
|
703
|
+
switch (kind) {
|
|
704
|
+
case 'thermostat':
|
|
705
|
+
return this.platform.Service.Thermostat;
|
|
706
|
+
case 'switch':
|
|
707
|
+
return this.platform.Service.Switch;
|
|
708
|
+
case 'outlet':
|
|
709
|
+
return this.platform.Service.Outlet;
|
|
710
|
+
case 'lightbulb':
|
|
711
|
+
return this.platform.Service.Lightbulb;
|
|
712
|
+
case 'windowCovering':
|
|
713
|
+
return this.platform.Service.WindowCovering;
|
|
714
|
+
case 'valve':
|
|
715
|
+
return this.platform.Service.Valve;
|
|
716
|
+
case 'lock':
|
|
717
|
+
return this.platform.Service.LockMechanism;
|
|
718
|
+
case 'motionSensor':
|
|
719
|
+
return this.platform.Service.MotionSensor;
|
|
720
|
+
case 'contactSensor':
|
|
721
|
+
return this.platform.Service.ContactSensor;
|
|
722
|
+
case 'leakSensor':
|
|
723
|
+
return this.platform.Service.LeakSensor;
|
|
724
|
+
case 'smokeSensor':
|
|
725
|
+
return this.platform.Service.SmokeSensor;
|
|
726
|
+
case 'carbonMonoxideSensor':
|
|
727
|
+
return this.platform.Service.CarbonMonoxideSensor;
|
|
728
|
+
case 'temperatureSensor':
|
|
729
|
+
return this.platform.Service.TemperatureSensor;
|
|
730
|
+
case 'humiditySensor':
|
|
731
|
+
return this.platform.Service.HumiditySensor;
|
|
732
|
+
case 'airQualitySensor':
|
|
733
|
+
return this.platform.Service.AirQualitySensor;
|
|
734
|
+
case 'occupancySensor':
|
|
735
|
+
return this.platform.Service.OccupancySensor;
|
|
736
|
+
default:
|
|
737
|
+
return this.platform.Service.Switch;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function toContextDevice(device) {
|
|
742
|
+
return {
|
|
743
|
+
id: device.id,
|
|
744
|
+
dsn: device.dsn,
|
|
745
|
+
key: device.key,
|
|
746
|
+
model: device.model,
|
|
747
|
+
name: device.name,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function mapSystemModeToTargetState(characteristic, systemModeRaw, holdTypeRaw) {
|
|
751
|
+
if (holdTypeRaw !== undefined && Math.round(holdTypeRaw) === 7) {
|
|
752
|
+
return characteristic.OFF;
|
|
753
|
+
}
|
|
754
|
+
const systemMode = Math.round(systemModeRaw);
|
|
755
|
+
if (systemMode === SALUS_MODE.off) {
|
|
756
|
+
return characteristic.OFF;
|
|
757
|
+
}
|
|
758
|
+
if (systemMode === SALUS_MODE.cool) {
|
|
759
|
+
return characteristic.COOL;
|
|
760
|
+
}
|
|
761
|
+
if (systemMode === SALUS_MODE.heat || systemMode === 5) {
|
|
762
|
+
return characteristic.HEAT;
|
|
763
|
+
}
|
|
764
|
+
return characteristic.AUTO;
|
|
765
|
+
}
|
|
766
|
+
function mapRunningStateToCurrentState(characteristic, runningStateRaw, holdTypeRaw) {
|
|
767
|
+
if (holdTypeRaw !== undefined && Math.round(holdTypeRaw) === 7) {
|
|
768
|
+
return characteristic.OFF;
|
|
769
|
+
}
|
|
770
|
+
if (runningStateRaw === undefined) {
|
|
771
|
+
return characteristic.OFF;
|
|
772
|
+
}
|
|
773
|
+
const normalized = Math.round(runningStateRaw);
|
|
774
|
+
if (normalized === 0) {
|
|
775
|
+
return characteristic.OFF;
|
|
776
|
+
}
|
|
777
|
+
if (normalized === 1 || normalized === 4) {
|
|
778
|
+
return characteristic.HEAT;
|
|
779
|
+
}
|
|
780
|
+
if (normalized === 2 || normalized === 3) {
|
|
781
|
+
return characteristic.COOL;
|
|
782
|
+
}
|
|
783
|
+
const asBits = normalized.toString(2).padStart(8, '0').split('').reverse();
|
|
784
|
+
if (asBits[0] === '1' || asBits[4] === '1') {
|
|
785
|
+
return characteristic.HEAT;
|
|
786
|
+
}
|
|
787
|
+
if (asBits[1] === '1' || asBits[3] === '1') {
|
|
788
|
+
return characteristic.COOL;
|
|
789
|
+
}
|
|
790
|
+
return characteristic.OFF;
|
|
791
|
+
}
|
|
792
|
+
function parseCharacteristicNumber(value) {
|
|
793
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
794
|
+
return value;
|
|
795
|
+
}
|
|
796
|
+
if (typeof value === 'string') {
|
|
797
|
+
const parsed = Number(value);
|
|
798
|
+
if (Number.isFinite(parsed)) {
|
|
799
|
+
return parsed;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return undefined;
|
|
803
|
+
}
|
|
804
|
+
function parseCharacteristicBoolean(value) {
|
|
805
|
+
if (typeof value === 'boolean') {
|
|
806
|
+
return value;
|
|
807
|
+
}
|
|
808
|
+
const numeric = parseCharacteristicNumber(value);
|
|
809
|
+
if (numeric !== undefined) {
|
|
810
|
+
return numeric !== 0;
|
|
811
|
+
}
|
|
812
|
+
return undefined;
|
|
813
|
+
}
|
|
814
|
+
function asErrorMessage(error) {
|
|
815
|
+
if (error instanceof Error) {
|
|
816
|
+
return error.message;
|
|
817
|
+
}
|
|
818
|
+
return String(error);
|
|
819
|
+
}
|
|
820
|
+
//# sourceMappingURL=platformAccessory.js.map
|