homebridge-nest-accfactory 0.3.1 → 0.3.2
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/CHANGELOG.md +27 -0
- package/README.md +31 -25
- package/config.schema.json +46 -22
- package/dist/HomeKitDevice.js +495 -262
- package/dist/HomeKitHistory.js +357 -341
- package/dist/config.js +67 -85
- package/dist/consts.js +160 -0
- package/dist/devices.js +35 -48
- package/dist/ffmpeg.js +297 -0
- package/dist/index.js +3 -3
- package/dist/nexustalk.js +157 -124
- package/dist/plugins/camera.js +1153 -924
- package/dist/plugins/doorbell.js +26 -32
- package/dist/plugins/floodlight.js +11 -24
- package/dist/plugins/heatlink.js +411 -5
- package/dist/plugins/lock.js +309 -0
- package/dist/plugins/protect.js +239 -70
- package/dist/plugins/tempsensor.js +158 -34
- package/dist/plugins/thermostat.js +890 -454
- package/dist/plugins/weather.js +128 -33
- package/dist/protobuf/nest/services/apigateway.proto +1 -1
- package/dist/protobuf/nestlabs/gateway/v2.proto +1 -1
- package/dist/protobuf/root.proto +1 -0
- package/dist/rtpmuxer.js +186 -0
- package/dist/streamer.js +486 -244
- package/dist/system.js +1739 -2852
- package/dist/utils.js +327 -0
- package/dist/webrtc.js +354 -225
- package/package.json +19 -16
|
@@ -7,25 +7,32 @@
|
|
|
7
7
|
// Define nodejs module requirements
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import fs from 'node:fs';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
10
|
|
|
12
11
|
// Define our modules
|
|
13
12
|
import HomeKitDevice from '../HomeKitDevice.js';
|
|
13
|
+
import { processCommonData, scaleValue, adjustTemperature } from '../utils.js';
|
|
14
14
|
|
|
15
15
|
// Define constants
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
import {
|
|
17
|
+
DATA_SOURCE,
|
|
18
|
+
THERMOSTAT_MIN_TEMPERATURE,
|
|
19
|
+
THERMOSTAT_MAX_TEMPERATURE,
|
|
20
|
+
LOW_BATTERY_LEVEL,
|
|
21
|
+
PROTOBUF_RESOURCES,
|
|
22
|
+
DAYS_OF_WEEK_FULL,
|
|
23
|
+
DAYS_OF_WEEK_SHORT,
|
|
24
|
+
__dirname,
|
|
25
|
+
DEVICE_TYPE,
|
|
26
|
+
} from '../consts.js';
|
|
20
27
|
|
|
21
28
|
export default class NestThermostat extends HomeKitDevice {
|
|
22
29
|
static TYPE = 'Thermostat';
|
|
23
|
-
static VERSION = '2025.
|
|
30
|
+
static VERSION = '2025.08.07'; // Code version
|
|
24
31
|
|
|
32
|
+
thermostatService = undefined;
|
|
25
33
|
batteryService = undefined;
|
|
26
34
|
occupancyService = undefined;
|
|
27
35
|
humidityService = undefined;
|
|
28
|
-
switchService = undefined; // Hotwater heating boost control
|
|
29
36
|
fanService = undefined; // Fan control
|
|
30
37
|
dehumidifierService = undefined; // dehumidifier (only) control
|
|
31
38
|
externalCool = undefined; // External module function
|
|
@@ -33,18 +40,25 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
33
40
|
externalFan = undefined; // External module function
|
|
34
41
|
externalDehumidifier = undefined; // External module function
|
|
35
42
|
|
|
36
|
-
constructor(accessory, api, log, eventEmitter, deviceData) {
|
|
37
|
-
super(accessory, api, log, eventEmitter, deviceData);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
43
|
// Class functions
|
|
41
|
-
async
|
|
42
|
-
// Setup the thermostat service if not already present on the accessory
|
|
43
|
-
this.thermostatService = this.addHKService(this.hap.Service.Thermostat, '', 1);
|
|
44
|
+
async onAdd() {
|
|
45
|
+
// Setup the thermostat service if not already present on the accessory, and link it to the Eve app if configured to do so
|
|
46
|
+
this.thermostatService = this.addHKService(this.hap.Service.Thermostat, '', 1, { messages: this.message.bind(this) });
|
|
44
47
|
this.thermostatService.setPrimaryService();
|
|
45
48
|
|
|
46
49
|
// Setup set characteristics
|
|
47
50
|
|
|
51
|
+
// Patch to avoid characteristic errors when setting initial property ranges
|
|
52
|
+
this.hap.Characteristic.TargetTemperature.prototype.getDefaultValue = () => {
|
|
53
|
+
return THERMOSTAT_MIN_TEMPERATURE; // start at minimum target temperature
|
|
54
|
+
};
|
|
55
|
+
this.hap.Characteristic.HeatingThresholdTemperature.prototype.getDefaultValue = () => {
|
|
56
|
+
return THERMOSTAT_MIN_TEMPERATURE; // start at minimum heating threshold
|
|
57
|
+
};
|
|
58
|
+
this.hap.Characteristic.CoolingThresholdTemperature.prototype.getDefaultValue = () => {
|
|
59
|
+
return THERMOSTAT_MAX_TEMPERATURE; // start at maximum cooling threshold
|
|
60
|
+
};
|
|
61
|
+
|
|
48
62
|
// Used to indicate active temperature if the thermostat is using its temperature sensor data
|
|
49
63
|
// or an external temperature sensor ie: Nest Temperature Sensor
|
|
50
64
|
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.StatusActive);
|
|
@@ -73,7 +87,7 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
73
87
|
this.setDisplayUnit(value);
|
|
74
88
|
},
|
|
75
89
|
onGet: () => {
|
|
76
|
-
return this.deviceData.temperature_scale === 'C'
|
|
90
|
+
return this.deviceData.temperature_scale.toUpperCase() === 'C'
|
|
77
91
|
? this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS
|
|
78
92
|
: this.hap.Characteristic.TemperatureDisplayUnits.FAHRENHEIT;
|
|
79
93
|
},
|
|
@@ -87,6 +101,11 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
87
101
|
});
|
|
88
102
|
|
|
89
103
|
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.TargetTemperature, {
|
|
104
|
+
props: {
|
|
105
|
+
minStep: 0.5,
|
|
106
|
+
minValue: THERMOSTAT_MIN_TEMPERATURE,
|
|
107
|
+
maxValue: THERMOSTAT_MAX_TEMPERATURE,
|
|
108
|
+
},
|
|
90
109
|
onSet: (value) => {
|
|
91
110
|
this.setTemperature(this.hap.Characteristic.TargetTemperature, value);
|
|
92
111
|
},
|
|
@@ -98,8 +117,8 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
98
117
|
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.CoolingThresholdTemperature, {
|
|
99
118
|
props: {
|
|
100
119
|
minStep: 0.5,
|
|
101
|
-
minValue:
|
|
102
|
-
maxValue:
|
|
120
|
+
minValue: THERMOSTAT_MIN_TEMPERATURE,
|
|
121
|
+
maxValue: THERMOSTAT_MAX_TEMPERATURE,
|
|
103
122
|
},
|
|
104
123
|
onSet: (value) => {
|
|
105
124
|
this.setTemperature(this.hap.Characteristic.CoolingThresholdTemperature, value);
|
|
@@ -112,8 +131,8 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
112
131
|
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.HeatingThresholdTemperature, {
|
|
113
132
|
props: {
|
|
114
133
|
minStep: 0.5,
|
|
115
|
-
minValue:
|
|
116
|
-
maxValue:
|
|
134
|
+
minValue: THERMOSTAT_MIN_TEMPERATURE,
|
|
135
|
+
maxValue: THERMOSTAT_MAX_TEMPERATURE,
|
|
117
136
|
},
|
|
118
137
|
onSet: (value) => {
|
|
119
138
|
this.setTemperature(this.hap.Characteristic.HeatingThresholdTemperature, value);
|
|
@@ -128,11 +147,11 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
128
147
|
validValues:
|
|
129
148
|
this.deviceData?.can_cool === true && this.deviceData?.can_heat === true
|
|
130
149
|
? [
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
150
|
+
this.hap.Characteristic.TargetHeatingCoolingState.OFF,
|
|
151
|
+
this.hap.Characteristic.TargetHeatingCoolingState.HEAT,
|
|
152
|
+
this.hap.Characteristic.TargetHeatingCoolingState.COOL,
|
|
153
|
+
this.hap.Characteristic.TargetHeatingCoolingState.AUTO,
|
|
154
|
+
]
|
|
136
155
|
: this.deviceData?.can_heat === true
|
|
137
156
|
? [this.hap.Characteristic.TargetHeatingCoolingState.OFF, this.hap.Characteristic.TargetHeatingCoolingState.HEAT]
|
|
138
157
|
: this.deviceData?.can_cool === true
|
|
@@ -211,32 +230,6 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
211
230
|
this.humidityService = undefined;
|
|
212
231
|
}
|
|
213
232
|
|
|
214
|
-
// Setup hotwater heating boost service if supported by the thermostat and not already present on the accessory
|
|
215
|
-
if (this.deviceData?.has_hot_water_control === true) {
|
|
216
|
-
this.#setupHotwaterBoost();
|
|
217
|
-
}
|
|
218
|
-
if (this.deviceData?.has_hot_water_control === false) {
|
|
219
|
-
// No longer have hotwater heating boost configured and service present, so removed it
|
|
220
|
-
this.switchService = this.accessory.getService(this.hap.Service.Switch);
|
|
221
|
-
if (this.switchService !== undefined) {
|
|
222
|
-
this.accessory.removeService(this.switchService);
|
|
223
|
-
}
|
|
224
|
-
this.switchService = undefined;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Setup linkage to EveHome app if configured todo so
|
|
228
|
-
if (
|
|
229
|
-
this.deviceData?.eveHistory === true &&
|
|
230
|
-
this.thermostatService !== undefined &&
|
|
231
|
-
typeof this.historyService?.linkToEveHome === 'function'
|
|
232
|
-
) {
|
|
233
|
-
this.historyService.linkToEveHome(this.thermostatService, {
|
|
234
|
-
description: this.deviceData.description,
|
|
235
|
-
getcommand: this.#EveHomeGetcommand.bind(this),
|
|
236
|
-
setcommand: this.#EveHomeSetcommand.bind(this),
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
233
|
// Attempt to load any external modules for this thermostat
|
|
241
234
|
// We support external cool/heat/fan/dehumidifier module functions
|
|
242
235
|
// This is all undocumented on how to use, as its for my specific use case :-)
|
|
@@ -253,263 +246,26 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
253
246
|
this.externalDehumidifier !== undefined && this.postSetupDetail('Using external dehumidification module');
|
|
254
247
|
}
|
|
255
248
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
);
|
|
274
|
-
}
|
|
249
|
+
onRemove() {
|
|
250
|
+
this.accessory.removeService(this.thermostatService);
|
|
251
|
+
this.accessory.removeService(this.batteryService);
|
|
252
|
+
this.accessory.removeService(this.occupancyService);
|
|
253
|
+
this.accessory.removeService(this.humidityService);
|
|
254
|
+
this.accessory.removeService(this.fanService);
|
|
255
|
+
this.accessory.removeService(this.dehumidifierService);
|
|
256
|
+
this.thermostatService = undefined;
|
|
257
|
+
this.batteryService = undefined;
|
|
258
|
+
this.occupancyService = undefined;
|
|
259
|
+
this.humidityService = undefined;
|
|
260
|
+
this.fanService = undefined;
|
|
261
|
+
this.dehumidifierService = undefined;
|
|
262
|
+
this.externalCool = undefined;
|
|
263
|
+
this.externalHeat = undefined;
|
|
264
|
+
this.externalFan = undefined;
|
|
265
|
+
this.externalDehumidifier = undefined;
|
|
275
266
|
}
|
|
276
267
|
|
|
277
|
-
|
|
278
|
-
this.set({
|
|
279
|
-
uuid: this.deviceData.nest_google_uuid,
|
|
280
|
-
dehumidifier_state: dehumidiferState === this.hap.Characteristic.Active.ACTIVE ? true : false,
|
|
281
|
-
});
|
|
282
|
-
this.dehumidifierService.updateCharacteristic(this.hap.Characteristic.Active, dehumidiferState);
|
|
283
|
-
|
|
284
|
-
this?.log?.info?.(
|
|
285
|
-
'Set dehumidifer on thermostat "%s" to "%s"',
|
|
286
|
-
this.deviceData.description,
|
|
287
|
-
dehumidiferState === this.hap.Characteristic.Active.ACTIVE
|
|
288
|
-
? 'On with target humidity level of ' + this.deviceData.target_humidity + '%'
|
|
289
|
-
: 'Off',
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
setHotwaterBoost(hotwaterState) {
|
|
294
|
-
this.set({
|
|
295
|
-
uuid: this.deviceData.nest_google_uuid,
|
|
296
|
-
hot_water_boost_active: { state: hotwaterState === true, time: this.deviceData.hotWaterBoostTime },
|
|
297
|
-
});
|
|
298
|
-
this.switchService.updateCharacteristic(this.hap.Characteristic.On, hotwaterState);
|
|
299
|
-
|
|
300
|
-
this?.log?.info?.(
|
|
301
|
-
'Set hotwater boost heating on thermostat "%s" to "%s"',
|
|
302
|
-
this.deviceData.description,
|
|
303
|
-
hotwaterState === true ? 'On for ' + formatDuration(this.deviceData.hotWaterBoostTime) : 'Off',
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
setDisplayUnit(temperatureUnit) {
|
|
308
|
-
this.set({
|
|
309
|
-
uuid: this.deviceData.nest_google_uuid,
|
|
310
|
-
temperature_scale: temperatureUnit === this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS ? 'C' : 'F',
|
|
311
|
-
});
|
|
312
|
-
this.thermostatService.updateCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits, temperatureUnit);
|
|
313
|
-
|
|
314
|
-
this?.log?.info?.(
|
|
315
|
-
'Set temperature units on thermostat "%s" to "%s"',
|
|
316
|
-
this.deviceData.description,
|
|
317
|
-
temperatureUnit === this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS ? '°C' : '°F',
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
setMode(thermostatMode) {
|
|
322
|
-
if (thermostatMode !== this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).value) {
|
|
323
|
-
// Work out based on the HomeKit requested mode, what can the thermostat really switch too
|
|
324
|
-
// We may over-ride the requested HomeKit mode
|
|
325
|
-
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.HEAT && this.deviceData.can_heat === false) {
|
|
326
|
-
thermostatMode = this.hap.Characteristic.TargetHeatingCoolingState.OFF;
|
|
327
|
-
}
|
|
328
|
-
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.COOL && this.deviceData.can_cool === false) {
|
|
329
|
-
thermostatMode = this.hap.Characteristic.TargetHeatingCoolingState.OFF;
|
|
330
|
-
}
|
|
331
|
-
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.AUTO) {
|
|
332
|
-
// Workaround for 'Hey Siri, turn on my thermostat'
|
|
333
|
-
// Appears to automatically request mode as 'auto', but we need to see what Nest device supports
|
|
334
|
-
if (this.deviceData.can_cool === true && this.deviceData.can_heat === false) {
|
|
335
|
-
thermostatMode = this.hap.Characteristic.TargetHeatingCoolingState.COOL;
|
|
336
|
-
}
|
|
337
|
-
if (this.deviceData.can_cool === false && this.deviceData.can_heat === true) {
|
|
338
|
-
thermostatMode = this.hap.Characteristic.TargetHeatingCoolingState.HEAT;
|
|
339
|
-
}
|
|
340
|
-
if (this.deviceData.can_cool === false && this.deviceData.can_heat === false) {
|
|
341
|
-
thermostatMode = this.hap.Characteristic.TargetHeatingCoolingState.OFF;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
let mode = '';
|
|
346
|
-
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.OFF) {
|
|
347
|
-
this.thermostatService.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.deviceData.target_temperature);
|
|
348
|
-
this.thermostatService.updateCharacteristic(
|
|
349
|
-
this.hap.Characteristic.TargetHeatingCoolingState,
|
|
350
|
-
this.hap.Characteristic.TargetHeatingCoolingState.OFF,
|
|
351
|
-
);
|
|
352
|
-
mode = 'off';
|
|
353
|
-
}
|
|
354
|
-
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.COOL) {
|
|
355
|
-
this.thermostatService.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.deviceData.target_temperature_high);
|
|
356
|
-
this.thermostatService.updateCharacteristic(
|
|
357
|
-
this.hap.Characteristic.TargetHeatingCoolingState,
|
|
358
|
-
this.hap.Characteristic.TargetHeatingCoolingState.COOL,
|
|
359
|
-
);
|
|
360
|
-
mode = 'cool';
|
|
361
|
-
}
|
|
362
|
-
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.HEAT) {
|
|
363
|
-
this.thermostatService.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.deviceData.target_temperature_low);
|
|
364
|
-
this.thermostatService.updateCharacteristic(
|
|
365
|
-
this.hap.Characteristic.TargetHeatingCoolingState,
|
|
366
|
-
this.hap.Characteristic.TargetHeatingCoolingState.HEAT,
|
|
367
|
-
);
|
|
368
|
-
mode = 'heat';
|
|
369
|
-
}
|
|
370
|
-
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.AUTO) {
|
|
371
|
-
this.thermostatService.updateCharacteristic(
|
|
372
|
-
this.hap.Characteristic.TargetTemperature,
|
|
373
|
-
(this.deviceData.target_temperature_low + this.deviceData.target_temperature_high) * 0.5,
|
|
374
|
-
);
|
|
375
|
-
this.thermostatService.updateCharacteristic(
|
|
376
|
-
this.hap.Characteristic.TargetHeatingCoolingState,
|
|
377
|
-
this.hap.Characteristic.TargetHeatingCoolingState.AUTO,
|
|
378
|
-
);
|
|
379
|
-
mode = 'range';
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
this.set({ uuid: this.deviceData.nest_google_uuid, hvac_mode: mode });
|
|
383
|
-
|
|
384
|
-
this?.log?.info?.('Set mode on "%s" to "%s"', this.deviceData.description, mode);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
getMode() {
|
|
389
|
-
let currentMode = null;
|
|
390
|
-
|
|
391
|
-
if (this.deviceData.hvac_mode.toUpperCase() === 'HEAT' || this.deviceData.hvac_mode.toUpperCase() === 'ECOHEAT') {
|
|
392
|
-
// heating mode, either eco or normal;
|
|
393
|
-
currentMode = this.hap.Characteristic.TargetHeatingCoolingState.HEAT;
|
|
394
|
-
}
|
|
395
|
-
if (this.deviceData.hvac_mode.toUpperCase() === 'COOL' || this.deviceData.hvac_mode.toUpperCase() === 'ECOCOOL') {
|
|
396
|
-
// cooling mode, either eco or normal
|
|
397
|
-
currentMode = this.hap.Characteristic.TargetHeatingCoolingState.COOL;
|
|
398
|
-
}
|
|
399
|
-
if (this.deviceData.hvac_mode.toUpperCase() === 'RANGE' || this.deviceData.hvac_mode.toUpperCase() === 'ECORANGE') {
|
|
400
|
-
// range mode, either eco or normal
|
|
401
|
-
currentMode = this.hap.Characteristic.TargetHeatingCoolingState.AUTO;
|
|
402
|
-
}
|
|
403
|
-
if (this.deviceData.hvac_mode.toUpperCase() === 'OFF' || (this.deviceData.can_cool === false && this.deviceData.can_heat === false)) {
|
|
404
|
-
// off mode or no heating or cooling capability
|
|
405
|
-
currentMode = this.hap.Characteristic.TargetHeatingCoolingState.OFF;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return currentMode;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
setTemperature(characteristic, temperature) {
|
|
412
|
-
if (typeof characteristic === 'function' && typeof characteristic?.UUID === 'string') {
|
|
413
|
-
if (
|
|
414
|
-
characteristic.UUID === this.hap.Characteristic.TargetTemperature.UUID &&
|
|
415
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).value !==
|
|
416
|
-
this.hap.Characteristic.TargetHeatingCoolingState.AUTO
|
|
417
|
-
) {
|
|
418
|
-
this.set({ uuid: this.deviceData.nest_google_uuid, target_temperature: temperature });
|
|
419
|
-
|
|
420
|
-
this?.log?.info?.(
|
|
421
|
-
'Set %s%s temperature on "%s" to "%s °C"',
|
|
422
|
-
this.deviceData.hvac_mode.toUpperCase().includes('ECO') ? 'eco mode ' : '',
|
|
423
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).value ===
|
|
424
|
-
this.hap.Characteristic.TargetHeatingCoolingState.HEAT
|
|
425
|
-
? 'heating'
|
|
426
|
-
: 'cooling',
|
|
427
|
-
this.deviceData.description,
|
|
428
|
-
temperature,
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
if (
|
|
432
|
-
characteristic.UUID === this.hap.Characteristic.HeatingThresholdTemperature.UUID &&
|
|
433
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).value ===
|
|
434
|
-
this.hap.Characteristic.TargetHeatingCoolingState.AUTO
|
|
435
|
-
) {
|
|
436
|
-
this.set({ uuid: this.deviceData.nest_google_uuid, target_temperature_low: temperature });
|
|
437
|
-
|
|
438
|
-
this?.log?.info?.(
|
|
439
|
-
'Set %sheating temperature on "%s" to "%s °C"',
|
|
440
|
-
this.deviceData.hvac_mode.toUpperCase().includes('ECO') ? 'eco mode ' : '',
|
|
441
|
-
this.deviceData.description,
|
|
442
|
-
temperature,
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
if (
|
|
446
|
-
characteristic.UUID === this.hap.Characteristic.CoolingThresholdTemperature.UUID &&
|
|
447
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).value ===
|
|
448
|
-
this.hap.Characteristic.TargetHeatingCoolingState.AUTO
|
|
449
|
-
) {
|
|
450
|
-
this.set({ uuid: this.deviceData.nest_google_uuid, target_temperature_high: temperature });
|
|
451
|
-
|
|
452
|
-
this?.log?.info?.(
|
|
453
|
-
'Set %scooling temperature on "%s" to "%s °C"',
|
|
454
|
-
this.deviceData.hvac_mode.toUpperCase().includes('ECO') ? 'eco mode ' : '',
|
|
455
|
-
this.deviceData.description,
|
|
456
|
-
temperature,
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
this.thermostatService.updateCharacteristic(characteristic, temperature); // Update HomeKit with value
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
getTemperature(characteristic) {
|
|
465
|
-
let currentTemperature = null;
|
|
466
|
-
|
|
467
|
-
if (typeof characteristic === 'function' && typeof characteristic?.UUID === 'string') {
|
|
468
|
-
if (characteristic.UUID === this.hap.Characteristic.TargetTemperature.UUID) {
|
|
469
|
-
currentTemperature = this.deviceData.target_temperature;
|
|
470
|
-
}
|
|
471
|
-
if (characteristic.UUID === this.hap.Characteristic.HeatingThresholdTemperature.UUID) {
|
|
472
|
-
currentTemperature = this.deviceData.target_temperature_low;
|
|
473
|
-
}
|
|
474
|
-
if (characteristic.UUID === this.hap.Characteristic.CoolingThresholdTemperature.UUID) {
|
|
475
|
-
currentTemperature = this.deviceData.target_temperature_high;
|
|
476
|
-
}
|
|
477
|
-
if (currentTemperature < MIN_TEMPERATURE) {
|
|
478
|
-
currentTemperature = MIN_TEMPERATURE;
|
|
479
|
-
}
|
|
480
|
-
if (currentTemperature > MAX_TEMPERATURE) {
|
|
481
|
-
currentTemperature = MAX_TEMPERATURE;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
return currentTemperature;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
setChildlock(pin, value) {
|
|
489
|
-
// TODO - pincode setting when turning on.
|
|
490
|
-
// On REST API, writes to device.xxxxxxxx.temperature_lock_pin_hash. How is the hash calculated???
|
|
491
|
-
// Do we set temperature range limits when child lock on??
|
|
492
|
-
|
|
493
|
-
this.thermostatService.updateCharacteristic(this.hap.Characteristic.LockPhysicalControls, value); // Update HomeKit with value
|
|
494
|
-
if (value === this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED) {
|
|
495
|
-
// Set pin hash????
|
|
496
|
-
}
|
|
497
|
-
if (value === this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED) {
|
|
498
|
-
// Clear pin hash????
|
|
499
|
-
}
|
|
500
|
-
this.set({
|
|
501
|
-
uuid: this.deviceData.nest_google_uuid,
|
|
502
|
-
temperature_lock: value === this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED ? true : false,
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
this?.log?.info?.(
|
|
506
|
-
'Setting Childlock on "%s" to "%s"',
|
|
507
|
-
this.deviceData.description,
|
|
508
|
-
value === this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED ? 'Enabled' : 'Disabled',
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
updateDevice(deviceData) {
|
|
268
|
+
onUpdate(deviceData) {
|
|
513
269
|
if (
|
|
514
270
|
typeof deviceData !== 'object' ||
|
|
515
271
|
this.thermostatService === undefined ||
|
|
@@ -545,7 +301,7 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
545
301
|
: this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED,
|
|
546
302
|
);
|
|
547
303
|
|
|
548
|
-
// Update air filter
|
|
304
|
+
// Update air filter status if has been added
|
|
549
305
|
if (this.thermostatService.testCharacteristic(this.hap.Characteristic.FilterChangeIndication) === true) {
|
|
550
306
|
this.thermostatService.updateCharacteristic(
|
|
551
307
|
this.hap.Characteristic.FilterChangeIndication,
|
|
@@ -629,36 +385,6 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
629
385
|
);
|
|
630
386
|
}
|
|
631
387
|
|
|
632
|
-
// Check for hotwater heating boost setup change on thermostat
|
|
633
|
-
if (deviceData.has_hot_water_control !== this.deviceData.has_hot_water_control) {
|
|
634
|
-
if (
|
|
635
|
-
deviceData.has_hot_water_control === true &&
|
|
636
|
-
this.deviceData.has_hot_water_control === false &&
|
|
637
|
-
this.switchService === undefined
|
|
638
|
-
) {
|
|
639
|
-
// hotwater heating boost has been added
|
|
640
|
-
this.switchService = this.accessory.getService(this.hap.Service.Switch);
|
|
641
|
-
if (this.deviceData.has_hot_water_control === true) {
|
|
642
|
-
this.#setupHotwaterBoost();
|
|
643
|
-
}
|
|
644
|
-
if (
|
|
645
|
-
deviceData.has_hot_water_control === false &&
|
|
646
|
-
this.deviceData.has_hot_water_control === true &&
|
|
647
|
-
this.switchService !== undefined
|
|
648
|
-
) {
|
|
649
|
-
// hotwater heating boost has been removed
|
|
650
|
-
this.accessory.removeService(this.switchService);
|
|
651
|
-
this.switchService = undefined;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
this?.log?.info?.(
|
|
656
|
-
'hotwater heating boost setup on thermostat "%s" has changed. Hotwater heating boost was',
|
|
657
|
-
deviceData.description,
|
|
658
|
-
this.switchService === undefined ? 'removed' : 'added',
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
388
|
if (deviceData.can_cool !== this.deviceData.can_cool || deviceData.can_heat !== this.deviceData.can_heat) {
|
|
663
389
|
// Heating and/cooling setup has changed on thermostat
|
|
664
390
|
|
|
@@ -787,7 +513,7 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
787
513
|
this.hap.Characteristic.CurrentHeatingCoolingState,
|
|
788
514
|
this.hap.Characteristic.CurrentHeatingCoolingState.COOL,
|
|
789
515
|
);
|
|
790
|
-
historyEntry.status =
|
|
516
|
+
historyEntry.status = 1; // cooling
|
|
791
517
|
}
|
|
792
518
|
if (deviceData.hvac_state.toUpperCase() === 'OFF') {
|
|
793
519
|
if (this.deviceData.hvac_state.toUpperCase() === 'COOLING' && typeof this.externalCool?.off === 'function') {
|
|
@@ -825,7 +551,12 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
825
551
|
this.hap.Characteristic.Active,
|
|
826
552
|
deviceData.fan_state === true ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE,
|
|
827
553
|
);
|
|
828
|
-
|
|
554
|
+
|
|
555
|
+
this.history(this.fanService, {
|
|
556
|
+
status: deviceData.fan_state === true ? 1 : 0,
|
|
557
|
+
temperature: deviceData.current_temperature,
|
|
558
|
+
humidity: deviceData.current_humidity,
|
|
559
|
+
});
|
|
829
560
|
}
|
|
830
561
|
|
|
831
562
|
if (this.dehumidifierService !== undefined) {
|
|
@@ -853,128 +584,336 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
853
584
|
this.hap.Characteristic.Active,
|
|
854
585
|
deviceData.dehumidifier_state === true ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE,
|
|
855
586
|
);
|
|
856
|
-
historyEntry.status = 4; // dehumidifier
|
|
857
|
-
}
|
|
858
587
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
588
|
+
this.history(this.dehumidifierService, {
|
|
589
|
+
status: deviceData.dehumidifier_state === true ? 1 : 0,
|
|
590
|
+
temperature: deviceData.current_temperature,
|
|
591
|
+
humidity: deviceData.current_humidity,
|
|
592
|
+
});
|
|
862
593
|
}
|
|
863
594
|
|
|
864
595
|
// Log thermostat metrics to history only if changed to previous recording
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
JSON.stringify(tempEntry.target) !== JSON.stringify(historyEntry.target) ||
|
|
872
|
-
tempEntry.humidity !== deviceData.current_humidity
|
|
873
|
-
) {
|
|
874
|
-
this.historyService.addHistory(this.thermostatService, {
|
|
875
|
-
time: Math.floor(Date.now() / 1000),
|
|
876
|
-
status: historyEntry.status,
|
|
877
|
-
temperature: deviceData.current_temperature,
|
|
878
|
-
target: historyEntry.target,
|
|
879
|
-
humidity: deviceData.current_humidity,
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
}
|
|
596
|
+
this.history(this.thermostatService, {
|
|
597
|
+
status: historyEntry.status,
|
|
598
|
+
temperature: deviceData.current_temperature,
|
|
599
|
+
target: historyEntry.target,
|
|
600
|
+
humidity: deviceData.current_humidity,
|
|
601
|
+
});
|
|
883
602
|
|
|
884
|
-
// Notify Eve App of device status changes if linked
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
this.deviceData.removed_from_base = deviceData.removed_from_base;
|
|
893
|
-
this.deviceData.vacation_mode = deviceData.vacation_mode;
|
|
894
|
-
this.deviceData.hvac_mode = deviceData.hvac_mode;
|
|
895
|
-
this.deviceData.schedules = deviceData.schedules;
|
|
896
|
-
this.deviceData.schedule_mode = deviceData.schedule_mode;
|
|
897
|
-
this.historyService.updateEveHome(this.thermostatService, this.#EveHomeGetcommand.bind(this));
|
|
898
|
-
}
|
|
603
|
+
// Update our internal data with properties Eve will need to process then Notify Eve App of device status changes if linked
|
|
604
|
+
this.deviceData.online = deviceData.online;
|
|
605
|
+
this.deviceData.removed_from_base = deviceData.removed_from_base;
|
|
606
|
+
this.deviceData.vacation_mode = deviceData.vacation_mode;
|
|
607
|
+
this.deviceData.hvac_mode = deviceData.hvac_mode;
|
|
608
|
+
this.deviceData.schedules = deviceData.schedules;
|
|
609
|
+
this.deviceData.schedule_mode = deviceData.schedule_mode;
|
|
610
|
+
this.historyService?.updateEveHome?.(this.thermostatService);
|
|
899
611
|
}
|
|
900
612
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
613
|
+
onMessage(type, message) {
|
|
614
|
+
if (typeof type !== 'string' || type === '' || message === null || typeof message !== 'object' || message?.constructor !== Object) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (type === HomeKitDevice?.EVEHOME?.GET) {
|
|
619
|
+
// Extend Eve Thermo GET payload with device state
|
|
620
|
+
message.enableschedule = this.deviceData.schedule_mode === 'heat';
|
|
621
|
+
message.attached = this.deviceData.online === true && this.deviceData.removed_from_base === false;
|
|
622
|
+
message.vacation = this.deviceData.vacation_mode === true;
|
|
623
|
+
message.programs = [];
|
|
624
|
+
|
|
625
|
+
if (['HEAT', 'RANGE'].includes(this.deviceData.schedule_mode?.toUpperCase?.()) === true) {
|
|
626
|
+
Object.entries(this.deviceData.schedules || {}).forEach(([day, entries]) => {
|
|
914
627
|
let tempSchedule = [];
|
|
915
628
|
let tempTemperatures = [];
|
|
916
|
-
Object.values(schedules)
|
|
917
|
-
.reverse()
|
|
918
|
-
.forEach((schedule) => {
|
|
919
|
-
if (schedule.entry_type === 'setpoint' && (schedule.type === 'HEAT' || schedule.type === 'RANGE')) {
|
|
920
|
-
tempSchedule.push({
|
|
921
|
-
start: schedule.time,
|
|
922
|
-
duration: 0,
|
|
923
|
-
temperature: typeof schedule['temp-min'] === 'number' ? schedule['temp-min'] : schedule.temp,
|
|
924
|
-
});
|
|
925
|
-
tempTemperatures.push(typeof schedule['temp-min'] === 'number' ? schedule['temp-min'] : schedule.temp);
|
|
926
|
-
}
|
|
927
|
-
});
|
|
928
629
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
630
|
+
Object.values(entries || {}).forEach((schedule) => {
|
|
631
|
+
if (schedule.entry_type === 'setpoint' && ['HEAT', 'RANGE'].includes(schedule.type)) {
|
|
632
|
+
let temp = typeof schedule['temp-min'] === 'number' ? schedule['temp-min'] : schedule.temp;
|
|
633
|
+
tempSchedule.push({ start: schedule.time, duration: 0, temperature: temp });
|
|
634
|
+
tempTemperatures.push(temp);
|
|
933
635
|
}
|
|
934
636
|
});
|
|
935
637
|
|
|
638
|
+
tempSchedule.sort((a, b) => a.start - b.start);
|
|
639
|
+
|
|
936
640
|
let ecoTemp = tempTemperatures.length === 0 ? 0 : Math.min(...tempTemperatures);
|
|
937
641
|
let comfortTemp = tempTemperatures.length === 0 ? 0 : Math.max(...tempTemperatures);
|
|
938
|
-
|
|
939
|
-
program
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
642
|
+
|
|
643
|
+
let program = {
|
|
644
|
+
id: parseInt(day),
|
|
645
|
+
days: isNaN(day) === false && DAYS_OF_WEEK_SHORT?.[day] !== undefined ? DAYS_OF_WEEK_SHORT[day].toLowerCase() : 'mon',
|
|
646
|
+
schedule: [],
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
let lastTime = 86400;
|
|
650
|
+
[...tempSchedule].reverse().forEach((entry) => {
|
|
651
|
+
if (entry.temperature === comfortTemp) {
|
|
652
|
+
program.schedule.push({
|
|
653
|
+
start: entry.start,
|
|
654
|
+
duration: lastTime - entry.start,
|
|
655
|
+
ecotemp: ecoTemp,
|
|
656
|
+
comforttemp: comfortTemp,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
lastTime = entry.start;
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
message.programs.push(program);
|
|
958
663
|
});
|
|
959
664
|
}
|
|
665
|
+
|
|
666
|
+
return message;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (type === HomeKitDevice?.EVEHOME?.SET) {
|
|
670
|
+
if (typeof message?.vacation?.status === 'boolean') {
|
|
671
|
+
this.message(HomeKitDevice.SET, { uuid: this.deviceData.nest_google_uuid, vacation_mode: message.vacation.status });
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (typeof message?.programs === 'object') {
|
|
675
|
+
// Future: convert to Nest format and apply via .set()
|
|
676
|
+
// this.message(HomeKitDevice.SET, { uuid: ..., days: { ... } });
|
|
677
|
+
}
|
|
960
678
|
}
|
|
961
|
-
return EveHomeGetData;
|
|
962
679
|
}
|
|
963
680
|
|
|
964
|
-
|
|
965
|
-
|
|
681
|
+
setFan(fanState, speed) {
|
|
682
|
+
let currentState = this.fanService.getCharacteristic(this.hap.Characteristic.Active).value;
|
|
683
|
+
let currentSpeed = this.fanService.getCharacteristic(this.hap.Characteristic.RotationSpeed).value;
|
|
684
|
+
|
|
685
|
+
if (fanState !== currentState || speed !== currentSpeed) {
|
|
686
|
+
let isActive = fanState === this.hap.Characteristic.Active.ACTIVE;
|
|
687
|
+
let scaledSpeed = Math.round((speed / 100) * this.deviceData.fan_max_speed);
|
|
688
|
+
|
|
689
|
+
this.message(HomeKitDevice.SET, {
|
|
690
|
+
uuid: this.deviceData.nest_google_uuid,
|
|
691
|
+
fan_state: isActive,
|
|
692
|
+
fan_timer_speed: scaledSpeed,
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
this.fanService.updateCharacteristic(this.hap.Characteristic.Active, fanState);
|
|
696
|
+
this.fanService.updateCharacteristic(this.hap.Characteristic.RotationSpeed, speed);
|
|
697
|
+
|
|
698
|
+
this?.log?.info?.(
|
|
699
|
+
'Set fan on thermostat "%s" to "%s"',
|
|
700
|
+
this.deviceData.description,
|
|
701
|
+
isActive ? 'On with fan speed of ' + speed + '%' : 'Off',
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
setDehumidifier(dehumidiferState) {
|
|
707
|
+
let isActive = dehumidiferState === this.hap.Characteristic.Active.ACTIVE;
|
|
708
|
+
|
|
709
|
+
this.message(HomeKitDevice.SET, {
|
|
710
|
+
uuid: this.deviceData.nest_google_uuid,
|
|
711
|
+
dehumidifier_state: isActive,
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
this.dehumidifierService.updateCharacteristic(this.hap.Characteristic.Active, dehumidiferState);
|
|
715
|
+
|
|
716
|
+
this?.log?.info?.(
|
|
717
|
+
'Set dehumidifer on thermostat "%s" to "%s"',
|
|
718
|
+
this.deviceData.description,
|
|
719
|
+
isActive ? 'On with target humidity level of ' + this.deviceData.target_humidity + '%' : 'Off',
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
setDisplayUnit(temperatureUnit) {
|
|
724
|
+
let unit = temperatureUnit === this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS ? 'C' : 'F';
|
|
725
|
+
|
|
726
|
+
this.message(HomeKitDevice.SET, {
|
|
727
|
+
uuid: this.deviceData.nest_google_uuid,
|
|
728
|
+
temperature_scale: unit,
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
this.thermostatService.updateCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits, temperatureUnit);
|
|
732
|
+
|
|
733
|
+
this?.log?.info?.('Set temperature units on thermostat "%s" to "%s"', this.deviceData.description, unit === 'C' ? '°C' : '°F');
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
setMode(thermostatMode) {
|
|
737
|
+
if (thermostatMode !== this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).value) {
|
|
738
|
+
// Work out based on the HomeKit requested mode, what can the thermostat really switch too
|
|
739
|
+
// We may over-ride the requested HomeKit mode
|
|
740
|
+
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.HEAT && this.deviceData.can_heat === false) {
|
|
741
|
+
thermostatMode = this.hap.Characteristic.TargetHeatingCoolingState.OFF;
|
|
742
|
+
}
|
|
743
|
+
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.COOL && this.deviceData.can_cool === false) {
|
|
744
|
+
thermostatMode = this.hap.Characteristic.TargetHeatingCoolingState.OFF;
|
|
745
|
+
}
|
|
746
|
+
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.AUTO) {
|
|
747
|
+
// Workaround for 'Hey Siri, turn on my thermostat'
|
|
748
|
+
// Appears to automatically request mode as 'auto', but we need to see what Nest device supports
|
|
749
|
+
if (this.deviceData.can_cool === true && this.deviceData.can_heat === false) {
|
|
750
|
+
thermostatMode = this.hap.Characteristic.TargetHeatingCoolingState.COOL;
|
|
751
|
+
}
|
|
752
|
+
if (this.deviceData.can_cool === false && this.deviceData.can_heat === true) {
|
|
753
|
+
thermostatMode = this.hap.Characteristic.TargetHeatingCoolingState.HEAT;
|
|
754
|
+
}
|
|
755
|
+
if (this.deviceData.can_cool === false && this.deviceData.can_heat === false) {
|
|
756
|
+
thermostatMode = this.hap.Characteristic.TargetHeatingCoolingState.OFF;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
let mode = '';
|
|
761
|
+
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.OFF) {
|
|
762
|
+
this.thermostatService.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.deviceData.target_temperature);
|
|
763
|
+
this.thermostatService.updateCharacteristic(
|
|
764
|
+
this.hap.Characteristic.TargetHeatingCoolingState,
|
|
765
|
+
this.hap.Characteristic.TargetHeatingCoolingState.OFF,
|
|
766
|
+
);
|
|
767
|
+
mode = 'off';
|
|
768
|
+
}
|
|
769
|
+
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.COOL) {
|
|
770
|
+
this.thermostatService.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.deviceData.target_temperature_high);
|
|
771
|
+
this.thermostatService.updateCharacteristic(
|
|
772
|
+
this.hap.Characteristic.TargetHeatingCoolingState,
|
|
773
|
+
this.hap.Characteristic.TargetHeatingCoolingState.COOL,
|
|
774
|
+
);
|
|
775
|
+
mode = 'cool';
|
|
776
|
+
}
|
|
777
|
+
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.HEAT) {
|
|
778
|
+
this.thermostatService.updateCharacteristic(this.hap.Characteristic.TargetTemperature, this.deviceData.target_temperature_low);
|
|
779
|
+
this.thermostatService.updateCharacteristic(
|
|
780
|
+
this.hap.Characteristic.TargetHeatingCoolingState,
|
|
781
|
+
this.hap.Characteristic.TargetHeatingCoolingState.HEAT,
|
|
782
|
+
);
|
|
783
|
+
mode = 'heat';
|
|
784
|
+
}
|
|
785
|
+
if (thermostatMode === this.hap.Characteristic.TargetHeatingCoolingState.AUTO) {
|
|
786
|
+
this.thermostatService.updateCharacteristic(
|
|
787
|
+
this.hap.Characteristic.TargetTemperature,
|
|
788
|
+
(this.deviceData.target_temperature_low + this.deviceData.target_temperature_high) * 0.5,
|
|
789
|
+
);
|
|
790
|
+
this.thermostatService.updateCharacteristic(
|
|
791
|
+
this.hap.Characteristic.TargetHeatingCoolingState,
|
|
792
|
+
this.hap.Characteristic.TargetHeatingCoolingState.AUTO,
|
|
793
|
+
);
|
|
794
|
+
mode = 'range';
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
this.message(HomeKitDevice.SET, { uuid: this.deviceData.nest_google_uuid, hvac_mode: mode });
|
|
798
|
+
|
|
799
|
+
this?.log?.info?.('Set mode on "%s" to "%s"', this.deviceData.description, mode);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
getMode() {
|
|
804
|
+
let currentMode = null;
|
|
805
|
+
let mode = this.deviceData.hvac_mode.toUpperCase();
|
|
806
|
+
|
|
807
|
+
if (mode === 'HEAT' || mode === 'ECOHEAT') {
|
|
808
|
+
// heating mode, either eco or normal
|
|
809
|
+
currentMode = this.hap.Characteristic.TargetHeatingCoolingState.HEAT;
|
|
810
|
+
}
|
|
811
|
+
if (mode === 'COOL' || mode === 'ECOCOOL') {
|
|
812
|
+
// cooling mode, either eco or normal
|
|
813
|
+
currentMode = this.hap.Characteristic.TargetHeatingCoolingState.COOL;
|
|
814
|
+
}
|
|
815
|
+
if (mode === 'RANGE' || mode === 'ECORANGE') {
|
|
816
|
+
// range mode, either eco or normal
|
|
817
|
+
currentMode = this.hap.Characteristic.TargetHeatingCoolingState.AUTO;
|
|
818
|
+
}
|
|
819
|
+
if (mode === 'OFF' || (this.deviceData.can_cool === false && this.deviceData.can_heat === false)) {
|
|
820
|
+
// off mode or no heating or cooling capability
|
|
821
|
+
currentMode = this.hap.Characteristic.TargetHeatingCoolingState.OFF;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
return currentMode;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
setTemperature(characteristic, temperature) {
|
|
828
|
+
if (typeof characteristic !== 'function' || typeof characteristic?.UUID !== 'string') {
|
|
966
829
|
return;
|
|
967
830
|
}
|
|
968
831
|
|
|
969
|
-
|
|
970
|
-
|
|
832
|
+
let mode = this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).value;
|
|
833
|
+
let isEco = this.deviceData.hvac_mode?.toUpperCase?.().includes('ECO') === true;
|
|
834
|
+
let scale = this.deviceData.temperature_scale?.toUpperCase?.() === 'F' ? 'F' : 'C';
|
|
835
|
+
let tempDisplay = (scale === 'F' ? (temperature * 9) / 5 + 32 : temperature).toFixed(1);
|
|
836
|
+
let tempUnit = scale === 'F' ? '°F' : '°C';
|
|
837
|
+
let ecoPrefix = isEco ? 'eco mode ' : '';
|
|
838
|
+
|
|
839
|
+
let targetKey = undefined;
|
|
840
|
+
let modeLabel = '';
|
|
841
|
+
|
|
842
|
+
if (
|
|
843
|
+
characteristic.UUID === this.hap.Characteristic.TargetTemperature.UUID &&
|
|
844
|
+
mode !== this.hap.Characteristic.TargetHeatingCoolingState.AUTO
|
|
845
|
+
) {
|
|
846
|
+
targetKey = 'target_temperature';
|
|
847
|
+
modeLabel = mode === this.hap.Characteristic.TargetHeatingCoolingState.HEAT ? 'heating' : 'cooling';
|
|
848
|
+
} else if (
|
|
849
|
+
characteristic.UUID === this.hap.Characteristic.HeatingThresholdTemperature.UUID &&
|
|
850
|
+
mode === this.hap.Characteristic.TargetHeatingCoolingState.AUTO
|
|
851
|
+
) {
|
|
852
|
+
targetKey = 'target_temperature_low';
|
|
853
|
+
modeLabel = 'heating';
|
|
854
|
+
} else if (
|
|
855
|
+
characteristic.UUID === this.hap.Characteristic.CoolingThresholdTemperature.UUID &&
|
|
856
|
+
mode === this.hap.Characteristic.TargetHeatingCoolingState.AUTO
|
|
857
|
+
) {
|
|
858
|
+
targetKey = 'target_temperature_high';
|
|
859
|
+
modeLabel = 'cooling';
|
|
971
860
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
861
|
+
|
|
862
|
+
if (targetKey !== undefined) {
|
|
863
|
+
this.message(HomeKitDevice.SET, { uuid: this.deviceData.nest_google_uuid, [targetKey]: temperature });
|
|
864
|
+
this?.log?.info?.(
|
|
865
|
+
'Set %s%s temperature on "%s" to "%s %s"',
|
|
866
|
+
ecoPrefix,
|
|
867
|
+
modeLabel,
|
|
868
|
+
this.deviceData.description,
|
|
869
|
+
tempDisplay,
|
|
870
|
+
tempUnit,
|
|
871
|
+
);
|
|
977
872
|
}
|
|
873
|
+
|
|
874
|
+
this.thermostatService.updateCharacteristic(characteristic, temperature);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
getTemperature(characteristic) {
|
|
878
|
+
if (typeof characteristic !== 'function' || typeof characteristic?.UUID !== 'string') {
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
let currentTemperature = {
|
|
883
|
+
[this.hap.Characteristic.TargetTemperature.UUID]: this.deviceData.target_temperature,
|
|
884
|
+
[this.hap.Characteristic.HeatingThresholdTemperature.UUID]: this.deviceData.target_temperature_low,
|
|
885
|
+
[this.hap.Characteristic.CoolingThresholdTemperature.UUID]: this.deviceData.target_temperature_high,
|
|
886
|
+
}[characteristic.UUID];
|
|
887
|
+
|
|
888
|
+
if (isNaN(currentTemperature) === false) {
|
|
889
|
+
currentTemperature = Math.min(Math.max(currentTemperature, THERMOSTAT_MIN_TEMPERATURE), THERMOSTAT_MAX_TEMPERATURE);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
return currentTemperature;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
setChildlock(pin, value) {
|
|
896
|
+
// TODO - pincode setting when turning on.
|
|
897
|
+
// On REST API, writes to device.xxxxxxxx.temperature_lock_pin_hash. How is the hash calculated???
|
|
898
|
+
// Do we set temperature range limits when child lock on??
|
|
899
|
+
|
|
900
|
+
this.thermostatService.updateCharacteristic(this.hap.Characteristic.LockPhysicalControls, value); // Update HomeKit with value
|
|
901
|
+
if (value === this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED) {
|
|
902
|
+
// Set pin hash????
|
|
903
|
+
}
|
|
904
|
+
if (value === this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED) {
|
|
905
|
+
// Clear pin hash????
|
|
906
|
+
}
|
|
907
|
+
this.message(HomeKitDevice.SET, {
|
|
908
|
+
uuid: this.deviceData.nest_google_uuid,
|
|
909
|
+
temperature_lock: value === this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED ? true : false,
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
this?.log?.info?.(
|
|
913
|
+
'Setting Childlock on "%s" to "%s"',
|
|
914
|
+
this.deviceData.description,
|
|
915
|
+
value === this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED ? 'Enabled' : 'Disabled',
|
|
916
|
+
);
|
|
978
917
|
}
|
|
979
918
|
|
|
980
919
|
#setupFan() {
|
|
@@ -1020,18 +959,6 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
1020
959
|
});
|
|
1021
960
|
}
|
|
1022
961
|
|
|
1023
|
-
#setupHotwaterBoost() {
|
|
1024
|
-
this.switchService = this.addHKService(this.hap.Service.Switch, '', 1);
|
|
1025
|
-
this.thermostatService.addLinkedService(this.switchService);
|
|
1026
|
-
|
|
1027
|
-
this.addHKCharacteristic(this.switchService, this.hap.Characteristic.On, {
|
|
1028
|
-
onSet: (value) => this.setHotwaterBoost(value),
|
|
1029
|
-
onGet: () => {
|
|
1030
|
-
return this.deviceData?.hot_water_boost_active === true;
|
|
1031
|
-
},
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
962
|
async #loadExternalModule(module, expectedFunctions = []) {
|
|
1036
963
|
if (typeof module !== 'string' || module === '' || Array.isArray(expectedFunctions) === false) {
|
|
1037
964
|
return undefined;
|
|
@@ -1084,9 +1011,9 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
1084
1011
|
let shortName =
|
|
1085
1012
|
typeof module === 'string'
|
|
1086
1013
|
? module
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1014
|
+
.trim()
|
|
1015
|
+
.match(/'[^']*'|[^\s]+/)?.[0]
|
|
1016
|
+
?.replace(/^'(.*)'$/, '$1')
|
|
1090
1017
|
: '';
|
|
1091
1018
|
this?.log?.warn?.('Failed to load external module "%s" for thermostat "%s"', shortName, this.deviceData.description);
|
|
1092
1019
|
}
|
|
@@ -1095,8 +1022,517 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
1095
1022
|
}
|
|
1096
1023
|
}
|
|
1097
1024
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1025
|
+
// Function to process our RAW Nest or Google for this device type
|
|
1026
|
+
export function processRawData(log, rawData, config, deviceType = undefined) {
|
|
1027
|
+
if (
|
|
1028
|
+
rawData === null ||
|
|
1029
|
+
typeof rawData !== 'object' ||
|
|
1030
|
+
rawData?.constructor !== Object ||
|
|
1031
|
+
typeof config !== 'object' ||
|
|
1032
|
+
config?.constructor !== Object
|
|
1033
|
+
) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const process_thermostat_data = (object_key, data) => {
|
|
1038
|
+
let processed = {};
|
|
1039
|
+
try {
|
|
1040
|
+
// Fix up data we need to
|
|
1041
|
+
|
|
1042
|
+
// If we have hot water control, it should be a 'UK/EU' model, so add that after the 'gen' tag in the model name
|
|
1043
|
+
data.model = data.has_hot_water_control === true ? data.model.replace(/\bgen\)/, 'gen, EU)') : data.model;
|
|
1044
|
+
|
|
1045
|
+
data = processCommonData(object_key, data, config);
|
|
1046
|
+
data.target_temperature_high = adjustTemperature(data.target_temperature_high, 'C', 'C', true);
|
|
1047
|
+
data.target_temperature_low = adjustTemperature(data.target_temperature_low, 'C', 'C', true);
|
|
1048
|
+
data.target_temperature = adjustTemperature(data.target_temperature, 'C', 'C', true);
|
|
1049
|
+
data.backplate_temperature = adjustTemperature(data.backplate_temperature, 'C', 'C', true);
|
|
1050
|
+
data.current_temperature = adjustTemperature(data.current_temperature, 'C', 'C', true);
|
|
1051
|
+
data.battery_level = scaleValue(data.battery_level, 3.6, 3.9, 0, 100);
|
|
1052
|
+
|
|
1053
|
+
processed = data;
|
|
1054
|
+
// eslint-disable-next-line no-unused-vars
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
// Empty
|
|
1057
|
+
}
|
|
1058
|
+
return processed;
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
// Process data for any thermostat(s) we have in the raw data
|
|
1062
|
+
let devices = {};
|
|
1063
|
+
Object.entries(rawData)
|
|
1064
|
+
.filter(
|
|
1065
|
+
([key, value]) =>
|
|
1066
|
+
key.startsWith('device.') === true ||
|
|
1067
|
+
(key.startsWith('DEVICE_') === true && PROTOBUF_RESOURCES.THERMOSTAT.includes(value.value?.device_info?.typeName) === true),
|
|
1068
|
+
)
|
|
1069
|
+
.forEach(([object_key, value]) => {
|
|
1070
|
+
let tempDevice = {};
|
|
1071
|
+
try {
|
|
1072
|
+
if (
|
|
1073
|
+
value?.source === DATA_SOURCE.GOOGLE &&
|
|
1074
|
+
value.value?.configuration_done?.deviceReady === true &&
|
|
1075
|
+
rawData?.[value.value?.device_info?.pairerId?.resourceId] !== undefined
|
|
1076
|
+
) {
|
|
1077
|
+
let RESTTypeData = {};
|
|
1078
|
+
RESTTypeData.type = DEVICE_TYPE.THERMOSTAT;
|
|
1079
|
+
RESTTypeData.model = 'Thermostat (unknown)';
|
|
1080
|
+
if (value.value.device_info.typeName === 'nest.resource.NestLearningThermostat1Resource') {
|
|
1081
|
+
RESTTypeData.model = 'Learning Thermostat (1st gen)';
|
|
1082
|
+
}
|
|
1083
|
+
if (
|
|
1084
|
+
value.value.device_info.typeName === 'nest.resource.NestLearningThermostat2Resource' ||
|
|
1085
|
+
value.value.device_info.typeName === 'nest.resource.NestAmber1DisplayResource'
|
|
1086
|
+
) {
|
|
1087
|
+
RESTTypeData.model = 'Learning Thermostat (2nd gen)';
|
|
1088
|
+
}
|
|
1089
|
+
if (
|
|
1090
|
+
value.value.device_info.typeName === 'nest.resource.NestLearningThermostat3Resource' ||
|
|
1091
|
+
value.value.device_info.typeName === 'nest.resource.NestAmber2DisplayResource'
|
|
1092
|
+
) {
|
|
1093
|
+
RESTTypeData.model = 'Learning Thermostat (3rd gen)';
|
|
1094
|
+
}
|
|
1095
|
+
if (value.value.device_info.typeName === 'google.resource.GoogleBismuth1Resource') {
|
|
1096
|
+
RESTTypeData.model = 'Learning Thermostat (4th gen)';
|
|
1097
|
+
}
|
|
1098
|
+
if (
|
|
1099
|
+
value.value.device_info.typeName === 'nest.resource.NestOnyxResource' ||
|
|
1100
|
+
value.value.device_info.typeName === 'nest.resource.NestAgateDisplayResource'
|
|
1101
|
+
) {
|
|
1102
|
+
RESTTypeData.model = 'Thermostat E (1st gen)';
|
|
1103
|
+
}
|
|
1104
|
+
if (value.value.device_info.typeName === 'google.resource.GoogleZirconium1Resource') {
|
|
1105
|
+
RESTTypeData.model = 'Thermostat (2020)';
|
|
1106
|
+
}
|
|
1107
|
+
RESTTypeData.softwareVersion = value.value.device_identity.softwareVersion;
|
|
1108
|
+
RESTTypeData.serialNumber = value.value.device_identity.serialNumber;
|
|
1109
|
+
RESTTypeData.description = String(value.value?.label?.label ?? '');
|
|
1110
|
+
RESTTypeData.location = String(
|
|
1111
|
+
[
|
|
1112
|
+
...Object.values(
|
|
1113
|
+
rawData?.[value.value?.device_info?.pairerId?.resourceId]?.value?.located_annotations?.predefinedWheres || {},
|
|
1114
|
+
),
|
|
1115
|
+
...Object.values(rawData?.[value.value?.device_info?.pairerId?.resourceId]?.value?.located_annotations?.customWheres || {}),
|
|
1116
|
+
].find((where) => where?.whereId?.resourceId === value.value?.device_located_settings?.whereAnnotationRid?.resourceId)?.label
|
|
1117
|
+
?.literal ?? '',
|
|
1118
|
+
);
|
|
1119
|
+
RESTTypeData.current_humidity =
|
|
1120
|
+
isNaN(value.value?.current_humidity?.humidityValue?.humidity?.value) === false
|
|
1121
|
+
? Number(value.value.current_humidity.humidityValue.humidity.value)
|
|
1122
|
+
: 0.0;
|
|
1123
|
+
RESTTypeData.temperature_scale = value.value?.display_settings?.temperatureScale === 'TEMPERATURE_SCALE_F' ? 'F' : 'C';
|
|
1124
|
+
RESTTypeData.removed_from_base =
|
|
1125
|
+
Array.isArray(value.value?.display?.thermostatState) === true && value.value.display.thermostatState.includes('bpd') === true;
|
|
1126
|
+
RESTTypeData.backplate_temperature = parseFloat(value.value.backplate_temperature.temperatureValue.temperature.value);
|
|
1127
|
+
RESTTypeData.current_temperature = parseFloat(value.value.current_temperature.temperatureValue.temperature.value);
|
|
1128
|
+
RESTTypeData.battery_level = parseFloat(value.value.battery_voltage.batteryValue.batteryVoltage.value);
|
|
1129
|
+
RESTTypeData.online = value.value?.liveness?.status === 'LIVENESS_DEVICE_STATUS_ONLINE';
|
|
1130
|
+
RESTTypeData.leaf = value.value?.leaf?.active === true;
|
|
1131
|
+
RESTTypeData.can_cool =
|
|
1132
|
+
value.value?.hvac_equipment_capabilities?.hasStage1Cool === true ||
|
|
1133
|
+
value.value?.hvac_equipment_capabilities?.hasStage2Cool === true ||
|
|
1134
|
+
value.value?.hvac_equipment_capabilities?.hasStage3Cool === true;
|
|
1135
|
+
RESTTypeData.can_heat =
|
|
1136
|
+
value.value?.hvac_equipment_capabilities?.hasStage1Heat === true ||
|
|
1137
|
+
value.value?.hvac_equipment_capabilities?.hasStage2Heat === true ||
|
|
1138
|
+
value.value?.hvac_equipment_capabilities?.hasStage3Heat === true;
|
|
1139
|
+
RESTTypeData.temperature_lock = value.value?.temperature_lock_settings?.enabled === true;
|
|
1140
|
+
RESTTypeData.temperature_lock_pin_hash =
|
|
1141
|
+
value.value?.temperature_lock_settings?.enabled === true ? value.value.temperature_lock_settings.pinHash : '';
|
|
1142
|
+
RESTTypeData.away = value.value?.structure_mode?.structureMode === 'STRUCTURE_MODE_AWAY';
|
|
1143
|
+
RESTTypeData.occupancy = value.value?.structure_mode?.structureMode === 'STRUCTURE_MODE_HOME';
|
|
1144
|
+
//RESTTypeData.occupancy = (value.value.structure_mode.occupancy.activity === 'ACTIVITY_ACTIVE');
|
|
1145
|
+
RESTTypeData.vacation_mode = value.value?.structure_mode?.structureMode === 'STRUCTURE_MODE_VACATION';
|
|
1146
|
+
|
|
1147
|
+
// Work out current mode. ie: off, cool, heat, range and get temperature low/high and target
|
|
1148
|
+
RESTTypeData.hvac_mode =
|
|
1149
|
+
value.value?.target_temperature_settings?.enabled?.value === true &&
|
|
1150
|
+
value.value?.target_temperature_settings?.targetTemperature?.setpointType !== undefined
|
|
1151
|
+
? value.value.target_temperature_settings.targetTemperature.setpointType.split('SET_POINT_TYPE_')[1].toLowerCase()
|
|
1152
|
+
: 'off';
|
|
1153
|
+
RESTTypeData.target_temperature_low =
|
|
1154
|
+
isNaN(value.value?.target_temperature_settings?.targetTemperature?.heatingTarget?.value) === false
|
|
1155
|
+
? Number(value.value.target_temperature_settings.targetTemperature.heatingTarget.value)
|
|
1156
|
+
: 0.0;
|
|
1157
|
+
RESTTypeData.target_temperature_high =
|
|
1158
|
+
isNaN(value.value?.target_temperature_settings?.targetTemperature?.coolingTarget?.value) === false
|
|
1159
|
+
? Number(value.value.target_temperature_settings.targetTemperature.coolingTarget.value)
|
|
1160
|
+
: 0.0;
|
|
1161
|
+
RESTTypeData.target_temperature =
|
|
1162
|
+
value.value?.target_temperature_settings?.targetTemperature?.setpointType === 'SET_POINT_TYPE_COOL' &&
|
|
1163
|
+
isNaN(value.value?.target_temperature_settings?.targetTemperature?.coolingTarget?.value) === false
|
|
1164
|
+
? Number(value.value.target_temperature_settings.targetTemperature.coolingTarget.value)
|
|
1165
|
+
: value.value?.target_temperature_settings?.targetTemperature?.setpointType === 'SET_POINT_TYPE_HEAT' &&
|
|
1166
|
+
isNaN(value.value?.target_temperature_settings?.targetTemperature?.heatingTarget?.value) === false
|
|
1167
|
+
? Number(value.value.target_temperature_settings.targetTemperature.heatingTarget.value)
|
|
1168
|
+
: value.value?.target_temperature_settings?.targetTemperature?.setpointType === 'SET_POINT_TYPE_RANGE' &&
|
|
1169
|
+
isNaN(value.value?.target_temperature_settings?.targetTemperature?.coolingTarget?.value) === false &&
|
|
1170
|
+
isNaN(value.value?.target_temperature_settings?.targetTemperature?.heatingTarget?.value) === false
|
|
1171
|
+
? (Number(value.value.target_temperature_settings.targetTemperature.coolingTarget.value) +
|
|
1172
|
+
Number(value.value.target_temperature_settings.targetTemperature.heatingTarget.value)) *
|
|
1173
|
+
0.5
|
|
1174
|
+
: 0.0;
|
|
1175
|
+
|
|
1176
|
+
// Work out if eco mode is active and adjust temperature low/high and target
|
|
1177
|
+
if (value.value?.eco_mode_state?.ecoMode !== 'ECO_MODE_INACTIVE') {
|
|
1178
|
+
RESTTypeData.target_temperature_low = value.value.eco_mode_settings.ecoTemperatureHeat.value.value;
|
|
1179
|
+
RESTTypeData.target_temperature_high = value.value.eco_mode_settings.ecoTemperatureCool.value.value;
|
|
1180
|
+
if (
|
|
1181
|
+
value.value.eco_mode_settings.ecoTemperatureHeat.enabled === true &&
|
|
1182
|
+
value.value.eco_mode_settings.ecoTemperatureCool.enabled === false
|
|
1183
|
+
) {
|
|
1184
|
+
RESTTypeData.target_temperature = value.value.eco_mode_settings.ecoTemperatureHeat.value.value;
|
|
1185
|
+
RESTTypeData.hvac_mode = 'ecoheat';
|
|
1186
|
+
}
|
|
1187
|
+
if (
|
|
1188
|
+
value.value.eco_mode_settings.ecoTemperatureHeat.enabled === false &&
|
|
1189
|
+
value.value.eco_mode_settings.ecoTemperatureCool.enabled === true
|
|
1190
|
+
) {
|
|
1191
|
+
RESTTypeData.target_temperature = value.value.eco_mode_settings.ecoTemperatureCool.value.value;
|
|
1192
|
+
RESTTypeData.hvac_mode = 'ecocool';
|
|
1193
|
+
}
|
|
1194
|
+
if (
|
|
1195
|
+
value.value.eco_mode_settings.ecoTemperatureHeat.enabled === true &&
|
|
1196
|
+
value.value.eco_mode_settings.ecoTemperatureCool.enabled === true
|
|
1197
|
+
) {
|
|
1198
|
+
RESTTypeData.target_temperature =
|
|
1199
|
+
(value.value.eco_mode_settings.ecoTemperatureCool.value.value +
|
|
1200
|
+
value.value.eco_mode_settings.ecoTemperatureHeat.value.value) *
|
|
1201
|
+
0.5;
|
|
1202
|
+
RESTTypeData.hvac_mode = 'ecorange';
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// Work out current state ie: heating, cooling etc
|
|
1207
|
+
RESTTypeData.hvac_state = 'off'; // By default, we're not heating or cooling
|
|
1208
|
+
if (
|
|
1209
|
+
value.value?.hvac_control?.hvacState?.coolStage1Active === true ||
|
|
1210
|
+
value.value?.hvac_control?.hvacState?.coolStage2Active === true ||
|
|
1211
|
+
value.value?.hvac_control?.hvacState?.coolStage2Active === true
|
|
1212
|
+
) {
|
|
1213
|
+
// A cooling source is on, so we're in cooling mode
|
|
1214
|
+
RESTTypeData.hvac_state = 'cooling';
|
|
1215
|
+
}
|
|
1216
|
+
if (
|
|
1217
|
+
value.value?.hvac_control?.hvacState?.heatStage1Active === true ||
|
|
1218
|
+
value.value?.hvac_control?.hvacState?.heatStage2Active === true ||
|
|
1219
|
+
value.value?.hvac_control?.hvacState?.heatStage3Active === true ||
|
|
1220
|
+
value.value?.hvac_control?.hvacState?.alternateHeatStage1Active === true ||
|
|
1221
|
+
value.value?.hvac_control?.hvacState?.alternateHeatStage2Active === true ||
|
|
1222
|
+
value.value?.hvac_control?.hvacState?.auxiliaryHeatActive === true ||
|
|
1223
|
+
value.value?.hvac_control?.hvacState?.emergencyHeatActive === true
|
|
1224
|
+
) {
|
|
1225
|
+
// A heating source is on, so we're in heating mode
|
|
1226
|
+
RESTTypeData.hvac_state = 'heating';
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Fan details, on or off and max number of speeds supported
|
|
1230
|
+
RESTTypeData.has_fan =
|
|
1231
|
+
typeof value.value?.fan_control_capabilities?.maxAvailableSpeed === 'string' &&
|
|
1232
|
+
value.value.fan_control_capabilities.maxAvailableSpeed !== 'FAN_SPEED_SETTING_OFF';
|
|
1233
|
+
RESTTypeData.fan_state =
|
|
1234
|
+
isNaN(value.value?.fan_control_settings?.timerEnd?.seconds) === false &&
|
|
1235
|
+
Number(value.value.fan_control_settings.timerEnd.seconds) > 0;
|
|
1236
|
+
RESTTypeData.fan_timer_speed =
|
|
1237
|
+
value.value?.fan_control_settings?.timerSpeed?.includes?.('FAN_SPEED_SETTING_STAGE') === true &&
|
|
1238
|
+
isNaN(value.value.fan_control_settings.timerSpeed.split('FAN_SPEED_SETTING_STAGE')[1]) === false
|
|
1239
|
+
? Number(value.value.fan_control_settings.timerSpeed.split('FAN_SPEED_SETTING_STAGE')[1])
|
|
1240
|
+
: 0;
|
|
1241
|
+
RESTTypeData.fan_max_speed =
|
|
1242
|
+
value.value?.fan_control_capabilities?.maxAvailableSpeed?.includes?.('FAN_SPEED_SETTING_STAGE') === true &&
|
|
1243
|
+
isNaN(value.value.fan_control_capabilities.maxAvailableSpeed.split('FAN_SPEED_SETTING_STAGE')[1]) === false
|
|
1244
|
+
? Number(value.value.fan_control_capabilities.maxAvailableSpeed.split('FAN_SPEED_SETTING_STAGE')[1])
|
|
1245
|
+
: 0;
|
|
1246
|
+
|
|
1247
|
+
// Humidifier/dehumidifier details
|
|
1248
|
+
RESTTypeData.has_humidifier = value.value?.hvac_equipment_capabilities?.hasHumidifier === true;
|
|
1249
|
+
RESTTypeData.has_dehumidifier = value.value?.hvac_equipment_capabilities?.hasDehumidifier === true;
|
|
1250
|
+
RESTTypeData.target_humidity =
|
|
1251
|
+
isNaN(value.value?.humidity_control_settings?.targetHumidity?.value) === false
|
|
1252
|
+
? Number(value.value.humidity_control_settings.targetHumidity.value)
|
|
1253
|
+
: 0.0;
|
|
1254
|
+
RESTTypeData.humidifier_state = value.value?.hvac_control?.hvacState?.humidifierActive === true;
|
|
1255
|
+
RESTTypeData.dehumidifier_state = value.value?.hvac_control?.hvacState?.dehumidifierActive === true;
|
|
1256
|
+
|
|
1257
|
+
// Air filter details
|
|
1258
|
+
RESTTypeData.has_air_filter = value.value?.hvac_equipment_capabilities?.hasAirFilter === true;
|
|
1259
|
+
RESTTypeData.filter_replacement_needed = value.value?.filter_reminder?.filterReplacementNeeded?.value === true;
|
|
1260
|
+
|
|
1261
|
+
// Process any temperature sensors associated with this thermostat
|
|
1262
|
+
RESTTypeData.active_rcs_sensor =
|
|
1263
|
+
value.value?.remote_comfort_sensing_settings?.activeRcsSelection?.activeRcsSensor !== undefined
|
|
1264
|
+
? value.value.remote_comfort_sensing_settings.activeRcsSelection.activeRcsSensor.resourceId
|
|
1265
|
+
: '';
|
|
1266
|
+
RESTTypeData.linked_rcs_sensors = [];
|
|
1267
|
+
if (Array.isArray(value.value?.remote_comfort_sensing_settings?.associatedRcsSensors) === true) {
|
|
1268
|
+
value.value.remote_comfort_sensing_settings.associatedRcsSensors.forEach((sensor) => {
|
|
1269
|
+
if (typeof rawData?.[sensor?.deviceId?.resourceId]?.value === 'object') {
|
|
1270
|
+
rawData[sensor.deviceId.resourceId].value.associated_thermostat = object_key; // Sensor is linked to this thermostat
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
RESTTypeData.linked_rcs_sensors.push(sensor.deviceId.resourceId);
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
RESTTypeData.schedule_mode =
|
|
1278
|
+
typeof value.value?.target_temperature_settings?.targetTemperature?.setpointType === 'string' &&
|
|
1279
|
+
value.value.target_temperature_settings.targetTemperature.setpointType.split('SET_POINT_TYPE_')[1].toLowerCase() !== 'off'
|
|
1280
|
+
? value.value.target_temperature_settings.targetTemperature.setpointType.split('SET_POINT_TYPE_')[1].toLowerCase()
|
|
1281
|
+
: '';
|
|
1282
|
+
RESTTypeData.schedules = {};
|
|
1283
|
+
if (
|
|
1284
|
+
value.value[RESTTypeData.schedule_mode + '_schedule_settings']?.setpoints !== undefined &&
|
|
1285
|
+
value.value[RESTTypeData.schedule_mode + '_schedule_settings']?.type ===
|
|
1286
|
+
'SET_POINT_SCHEDULE_TYPE_' + RESTTypeData.schedule_mode.toUpperCase()
|
|
1287
|
+
) {
|
|
1288
|
+
Object.values(value.value[RESTTypeData.schedule_mode + '_schedule_settings'].setpoints).forEach((schedule) => {
|
|
1289
|
+
// Create Nest API schedule entries
|
|
1290
|
+
if (schedule?.dayOfWeek !== undefined) {
|
|
1291
|
+
let dayofWeekIndex = DAYS_OF_WEEK_FULL.indexOf(schedule.dayOfWeek.split('DAY_OF_WEEK_')[1]);
|
|
1292
|
+
|
|
1293
|
+
if (RESTTypeData.schedules?.[dayofWeekIndex] === undefined) {
|
|
1294
|
+
RESTTypeData.schedules[dayofWeekIndex] = {};
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
RESTTypeData.schedules[dayofWeekIndex][Object.entries(RESTTypeData.schedules[dayofWeekIndex]).length] = {
|
|
1298
|
+
'temp-min': adjustTemperature(schedule.heatingTarget.value, 'C', 'C', true),
|
|
1299
|
+
'temp-max': adjustTemperature(schedule.coolingTarget.value, 'C', 'C', true),
|
|
1300
|
+
time: isNaN(schedule?.secondsInDay) === false ? Number(schedule.secondsInDay) : 0,
|
|
1301
|
+
type: RESTTypeData.schedule_mode.toUpperCase(),
|
|
1302
|
+
entry_type: 'setpoint',
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
tempDevice = process_thermostat_data(object_key, RESTTypeData);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (
|
|
1312
|
+
value?.source === DATA_SOURCE.NEST &&
|
|
1313
|
+
rawData?.['track.' + value.value?.serial_number] !== undefined &&
|
|
1314
|
+
rawData?.['link.' + value.value?.serial_number] !== undefined &&
|
|
1315
|
+
rawData?.['shared.' + value.value?.serial_number] !== undefined &&
|
|
1316
|
+
rawData?.['where.' + rawData?.['link.' + value.value?.serial_number]?.value?.structure?.split?.('.')[1]] !== undefined
|
|
1317
|
+
) {
|
|
1318
|
+
let RESTTypeData = {};
|
|
1319
|
+
RESTTypeData.type = DEVICE_TYPE.THERMOSTAT;
|
|
1320
|
+
RESTTypeData.model = 'Thermostat (unknown)';
|
|
1321
|
+
if (value.value.serial_number.substring(0, 2) === '15') {
|
|
1322
|
+
RESTTypeData.model = 'Thermostat E (1st gen)'; // Nest Thermostat E
|
|
1323
|
+
}
|
|
1324
|
+
if (value.value.serial_number.substring(0, 2) === '09' || value.value.serial_number.substring(0, 2) === '10') {
|
|
1325
|
+
RESTTypeData.model = 'Learning Thermostat (3rd gen)'; // Nest Thermostat 3rd gen
|
|
1326
|
+
}
|
|
1327
|
+
if (value.value.serial_number.substring(0, 2) === '02') {
|
|
1328
|
+
RESTTypeData.model = 'Learning Thermostat (2nd gen)'; // Nest Thermostat 2nd gen
|
|
1329
|
+
}
|
|
1330
|
+
if (value.value.serial_number.substring(0, 2) === '01') {
|
|
1331
|
+
RESTTypeData.model = 'Learning Thermostat (1st gen)'; // Nest Thermostat 1st gen
|
|
1332
|
+
}
|
|
1333
|
+
RESTTypeData.softwareVersion = value.value.current_version;
|
|
1334
|
+
RESTTypeData.serialNumber = value.value.serial_number;
|
|
1335
|
+
RESTTypeData.description = String(rawData?.['shared.' + value.value.serial_number]?.value?.name ?? '');
|
|
1336
|
+
RESTTypeData.location = String(
|
|
1337
|
+
rawData?.['where.' + rawData?.['link.' + value.value.serial_number]?.value?.structure?.split?.('.')[1]]?.value?.wheres?.find(
|
|
1338
|
+
(where) => where?.where_id === value.value.where_id,
|
|
1339
|
+
)?.name ?? '',
|
|
1340
|
+
);
|
|
1341
|
+
RESTTypeData.current_humidity = value.value.current_humidity;
|
|
1342
|
+
RESTTypeData.temperature_scale = value.value.temperature_scale.toUpperCase() === 'F' ? 'F' : 'C';
|
|
1343
|
+
RESTTypeData.removed_from_base = value.value.nlclient_state.toUpperCase() === 'BPD';
|
|
1344
|
+
RESTTypeData.backplate_temperature = value.value.backplate_temperature;
|
|
1345
|
+
RESTTypeData.current_temperature = value.value.backplate_temperature;
|
|
1346
|
+
RESTTypeData.battery_level = value.value.battery_level;
|
|
1347
|
+
RESTTypeData.online = rawData?.['track.' + value.value.serial_number]?.value?.online === true;
|
|
1348
|
+
RESTTypeData.leaf = value.value.leaf === true;
|
|
1349
|
+
RESTTypeData.has_humidifier = value.value.has_humidifier === true;
|
|
1350
|
+
RESTTypeData.has_dehumidifier = value.value.has_dehumidifier === true;
|
|
1351
|
+
RESTTypeData.has_fan = value.value.has_fan === true;
|
|
1352
|
+
RESTTypeData.can_cool = rawData?.['shared.' + value.value.serial_number]?.value?.can_cool === true;
|
|
1353
|
+
RESTTypeData.can_heat = rawData?.['shared.' + value.value.serial_number]?.value?.can_heat === true;
|
|
1354
|
+
RESTTypeData.temperature_lock = value.value.temperature_lock === true;
|
|
1355
|
+
RESTTypeData.temperature_lock_pin_hash = value.value.temperature_lock_pin_hash;
|
|
1356
|
+
RESTTypeData.away = rawData?.[rawData?.['link.' + value.value.serial_number]?.value?.structure]?.value?.away === true;
|
|
1357
|
+
RESTTypeData.occupancy = RESTTypeData.away === false; // Occupancy is opposite of away status ie: away is false, then occupied
|
|
1358
|
+
RESTTypeData.vacation_mode =
|
|
1359
|
+
rawData[rawData?.['link.' + value.value.serial_number]?.value?.structure]?.value?.vacation_mode === true;
|
|
1360
|
+
|
|
1361
|
+
// Work out current mode. ie: off, cool, heat, range and get temperature low (heat) and high (cool)
|
|
1362
|
+
RESTTypeData.hvac_mode =
|
|
1363
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_type !== undefined
|
|
1364
|
+
? rawData?.['shared.' + value.value.serial_number].value.target_temperature_type
|
|
1365
|
+
: 'off';
|
|
1366
|
+
RESTTypeData.target_temperature =
|
|
1367
|
+
isNaN(rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature) === false
|
|
1368
|
+
? Number(rawData['shared.' + value.value.serial_number].value.target_temperature)
|
|
1369
|
+
: 0.0;
|
|
1370
|
+
RESTTypeData.target_temperature_low =
|
|
1371
|
+
isNaN(rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_low) === false
|
|
1372
|
+
? Number(rawData['shared.' + value.value.serial_number].value.target_temperature_low)
|
|
1373
|
+
: 0.0;
|
|
1374
|
+
RESTTypeData.target_temperature_high =
|
|
1375
|
+
isNaN(rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_high) === false
|
|
1376
|
+
? Number(rawData['shared.' + value.value.serial_number].value.target_temperature_high)
|
|
1377
|
+
: 0.0;
|
|
1378
|
+
if (rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_type.toUpperCase() === 'COOL') {
|
|
1379
|
+
// Target temperature is the cooling point
|
|
1380
|
+
RESTTypeData.target_temperature =
|
|
1381
|
+
isNaN(rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_high) === false
|
|
1382
|
+
? Number(rawData['shared.' + value.value.serial_number].value.target_temperature_high)
|
|
1383
|
+
: 0.0;
|
|
1384
|
+
}
|
|
1385
|
+
if (rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_type.toUpperCase() === 'HEAT') {
|
|
1386
|
+
// Target temperature is the heating point
|
|
1387
|
+
RESTTypeData.target_temperature =
|
|
1388
|
+
isNaN(rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_low) === false
|
|
1389
|
+
? Number(rawData['shared.' + value.value.serial_number].value.target_temperature_low)
|
|
1390
|
+
: 0.0;
|
|
1391
|
+
}
|
|
1392
|
+
if (rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_type.toUpperCase() === 'RANGE') {
|
|
1393
|
+
// Target temperature is in between the heating and cooling point
|
|
1394
|
+
RESTTypeData.target_temperature =
|
|
1395
|
+
isNaN(rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_low) === false &&
|
|
1396
|
+
isNaN(rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_high) === false
|
|
1397
|
+
? (Number(rawData['shared.' + value.value.serial_number].value.target_temperature_low) +
|
|
1398
|
+
Number(rawData['shared.' + value.value.serial_number].value.target_temperature_high)) *
|
|
1399
|
+
0.5
|
|
1400
|
+
: 0.0;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// Work out if eco mode is active and adjust temperature low/high and target
|
|
1404
|
+
if (value.value.eco.mode.toUpperCase() === 'AUTO-ECO' || value.value.eco.mode.toUpperCase() === 'MANUAL-ECO') {
|
|
1405
|
+
RESTTypeData.target_temperature_low = value.value.away_temperature_low;
|
|
1406
|
+
RESTTypeData.target_temperature_high = value.value.away_temperature_high;
|
|
1407
|
+
if (value.value.away_temperature_high_enabled === true && value.value.away_temperature_low_enabled === false) {
|
|
1408
|
+
RESTTypeData.target_temperature = value.value.away_temperature_low;
|
|
1409
|
+
RESTTypeData.hvac_mode = 'ecoheat';
|
|
1410
|
+
}
|
|
1411
|
+
if (value.value.away_temperature_high_enabled === true && value.value.away_temperature_low_enabled === false) {
|
|
1412
|
+
RESTTypeData.target_temperature = value.value.away_temperature_high;
|
|
1413
|
+
RESTTypeData.hvac_mode = 'ecocool';
|
|
1414
|
+
}
|
|
1415
|
+
if (value.value.away_temperature_high_enabled === true && value.value.away_temperature_low_enabled === true) {
|
|
1416
|
+
RESTTypeData.target_temperature = (value.value.away_temperature_low + value.value.away_temperature_high) * 0.5;
|
|
1417
|
+
RESTTypeData.hvac_mode = 'ecorange';
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// Work out current state ie: heating, cooling etc
|
|
1422
|
+
RESTTypeData.hvac_state = 'off'; // By default, we're not heating or cooling
|
|
1423
|
+
if (
|
|
1424
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.hvac_heater_state === true ||
|
|
1425
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.hvac_heat_x2_state === true ||
|
|
1426
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.hvac_heat_x3_state === true ||
|
|
1427
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.hvac_aux_heater_state === true ||
|
|
1428
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.hvac_alt_heat_x2_state === true ||
|
|
1429
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.hvac_emer_heat_state === true ||
|
|
1430
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.hvac_alt_heat_state === true
|
|
1431
|
+
) {
|
|
1432
|
+
// A heating source is on, so we're in heating mode
|
|
1433
|
+
RESTTypeData.hvac_state = 'heating';
|
|
1434
|
+
}
|
|
1435
|
+
if (
|
|
1436
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.hvac_ac_state === true ||
|
|
1437
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.hvac_cool_x2_state === true ||
|
|
1438
|
+
rawData?.['shared.' + value.value.serial_number]?.value?.hvac_cool_x3_state === true
|
|
1439
|
+
) {
|
|
1440
|
+
// A cooling source is on, so we're in cooling mode
|
|
1441
|
+
RESTTypeData.hvac_state = 'cooling';
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// Update fan status, on or off
|
|
1445
|
+
RESTTypeData.fan_state = isNaN(value.value?.fan_timer_timeout) === false && Number(value.value.fan_timer_timeout) > 0;
|
|
1446
|
+
RESTTypeData.fan_timer_speed =
|
|
1447
|
+
value.value?.fan_timer_speed?.includes?.('stage') === true && isNaN(value.value.fan_timer_speed.split('stage')[1]) === false
|
|
1448
|
+
? Number(value.value.fan_timer_speed.split('stage')[1])
|
|
1449
|
+
: 0;
|
|
1450
|
+
RESTTypeData.fan_max_speed =
|
|
1451
|
+
value.value?.fan_capabilities?.includes?.('stage') === true && isNaN(value.value.fan_capabilities.split('stage')[1]) === false
|
|
1452
|
+
? Number(value.value.fan_capabilities.split('stage')[1])
|
|
1453
|
+
: 0;
|
|
1454
|
+
|
|
1455
|
+
// Humidifier/dehumidifier details
|
|
1456
|
+
RESTTypeData.target_humidity = isNaN(value.value?.target_humidity) === false ? Number(value.value.target_humidity) : 0.0;
|
|
1457
|
+
RESTTypeData.humidifier_state = value.value.humidifier_state === true;
|
|
1458
|
+
RESTTypeData.dehumidifier_state = value.value.dehumidifier_state === true;
|
|
1459
|
+
|
|
1460
|
+
// Air filter details
|
|
1461
|
+
RESTTypeData.has_air_filter = value.value.has_air_filter === true;
|
|
1462
|
+
RESTTypeData.filter_replacement_needed = value.value.filter_replacement_needed === true;
|
|
1463
|
+
|
|
1464
|
+
// Process any temperature sensors associated with this thermostat
|
|
1465
|
+
RESTTypeData.active_rcs_sensor = '';
|
|
1466
|
+
RESTTypeData.linked_rcs_sensors = [];
|
|
1467
|
+
if (rawData?.['rcs_settings.' + value.value.serial_number]?.value?.associated_rcs_sensors !== undefined) {
|
|
1468
|
+
rawData?.['rcs_settings.' + value.value.serial_number].value.associated_rcs_sensors.forEach((sensor) => {
|
|
1469
|
+
if (typeof rawData[sensor]?.value === 'object') {
|
|
1470
|
+
rawData[sensor].value.associated_thermostat = object_key; // Sensor is linked to this thermostat
|
|
1471
|
+
|
|
1472
|
+
// Is this sensor the active one? If so, get some details about it
|
|
1473
|
+
if (
|
|
1474
|
+
rawData?.['rcs_settings.' + value.value.serial_number]?.value?.active_rcs_sensors !== undefined &&
|
|
1475
|
+
rawData?.['rcs_settings.' + value.value.serial_number]?.value?.active_rcs_sensors.includes(sensor)
|
|
1476
|
+
) {
|
|
1477
|
+
RESTTypeData.active_rcs_sensor = rawData[sensor].value.serial_number.toUpperCase();
|
|
1478
|
+
RESTTypeData.current_temperature = rawData[sensor].value.current_temperature;
|
|
1479
|
+
}
|
|
1480
|
+
RESTTypeData.linked_rcs_sensors.push(rawData[sensor].value.serial_number.toUpperCase());
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// Get associated schedules
|
|
1486
|
+
if (rawData?.['schedule.' + value.value.serial_number] !== undefined) {
|
|
1487
|
+
Object.values(rawData['schedule.' + value.value.serial_number].value.days).forEach((schedules) => {
|
|
1488
|
+
Object.values(schedules).forEach((schedule) => {
|
|
1489
|
+
// Fix up temperatures in the schedule
|
|
1490
|
+
if (isNaN(schedule['temp']) === false) {
|
|
1491
|
+
schedule.temp = adjustTemperature(Number(schedule.temp), 'C', 'C', true);
|
|
1492
|
+
}
|
|
1493
|
+
if (isNaN(schedule['temp-min']) === false) {
|
|
1494
|
+
schedule['temp-min'] = adjustTemperature(Number(schedule['temp-min']), 'C', 'C', true);
|
|
1495
|
+
}
|
|
1496
|
+
if (isNaN(schedule['temp-max']) === false) {
|
|
1497
|
+
schedule['temp-max'] = adjustTemperature(Number(schedule['temp-max']), 'C', 'C', true);
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
});
|
|
1501
|
+
RESTTypeData.schedules = rawData['schedule.' + value.value.serial_number].value.days;
|
|
1502
|
+
RESTTypeData.schedule_mode = rawData['schedule.' + value.value.serial_number].value.schedule_mode;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
tempDevice = process_thermostat_data(object_key, RESTTypeData);
|
|
1506
|
+
}
|
|
1507
|
+
// eslint-disable-next-line no-unused-vars
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
log?.debug?.('Error processing thermostat data for "%s"', object_key);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
if (
|
|
1513
|
+
Object.entries(tempDevice).length !== 0 &&
|
|
1514
|
+
typeof devices[tempDevice.serialNumber] === 'undefined' &&
|
|
1515
|
+
(deviceType === undefined || (typeof deviceType === 'string' && deviceType !== '' && tempDevice.type === deviceType))
|
|
1516
|
+
) {
|
|
1517
|
+
let deviceOptions = config?.devices?.find(
|
|
1518
|
+
(device) => device?.serialNumber?.toUpperCase?.() === tempDevice?.serialNumber?.toUpperCase?.(),
|
|
1519
|
+
);
|
|
1520
|
+
// Insert any extra options we've read in from configuration file for this device
|
|
1521
|
+
tempDevice.eveHistory = config.options.eveHistory === true || deviceOptions?.eveHistory === true;
|
|
1522
|
+
tempDevice.humiditySensor = deviceOptions?.humiditySensor === true;
|
|
1523
|
+
tempDevice.externalCool =
|
|
1524
|
+
typeof deviceOptions?.externalCool === 'string' && deviceOptions.externalCool !== '' ? deviceOptions.externalCool : undefined; // Config option for external cooling source
|
|
1525
|
+
tempDevice.externalHeat =
|
|
1526
|
+
typeof deviceOptions?.externalHeat === 'string' && deviceOptions.externalHeat !== '' ? deviceOptions.externalHeat : undefined; // Config option for external heating source
|
|
1527
|
+
tempDevice.externalFan =
|
|
1528
|
+
typeof deviceOptions?.externalFan === 'string' && deviceOptions.externalFan !== '' ? deviceOptions.externalFan : undefined; // Config option for external fan source
|
|
1529
|
+
tempDevice.externalDehumidifier =
|
|
1530
|
+
typeof deviceOptions?.externalDehumidifier === 'string' && deviceOptions.externalDehumidifier !== ''
|
|
1531
|
+
? deviceOptions.externalDehumidifier
|
|
1532
|
+
: undefined; // Config option for external dehumidifier source
|
|
1533
|
+
devices[tempDevice.serialNumber] = tempDevice; // Store processed device
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
return devices;
|
|
1102
1538
|
}
|