@homebridge-plugins/homebridge-tado 6.0.14

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +152 -0
  2. package/LICENSE +21 -0
  3. package/README.md +560 -0
  4. package/config.schema.json +878 -0
  5. package/homebridge-ui/public/css/style.css +25 -0
  6. package/homebridge-ui/public/images/tado_logo.png +0 -0
  7. package/homebridge-ui/public/index.html +118 -0
  8. package/homebridge-ui/public/js/main.js +1582 -0
  9. package/homebridge-ui/public/js/modules/compareVersions.min.js +1 -0
  10. package/homebridge-ui/public/js/modules/jquery.min.js +2 -0
  11. package/homebridge-ui/public/js/modules/progressbar.min.js +6 -0
  12. package/homebridge-ui/public/js/progressbars.js +48 -0
  13. package/homebridge-ui/public/js/schema.js +864 -0
  14. package/homebridge-ui/server.js +80 -0
  15. package/images/tado_logo.png +0 -0
  16. package/index.js +14 -0
  17. package/package.json +66 -0
  18. package/src/accessories/airquality.js +56 -0
  19. package/src/accessories/contact.js +124 -0
  20. package/src/accessories/faucet.js +63 -0
  21. package/src/accessories/heatercooler.js +333 -0
  22. package/src/accessories/humidity.js +90 -0
  23. package/src/accessories/lightbulb.js +59 -0
  24. package/src/accessories/lightsensor.js +40 -0
  25. package/src/accessories/motion.js +79 -0
  26. package/src/accessories/occupancy.js +45 -0
  27. package/src/accessories/security.js +79 -0
  28. package/src/accessories/switch.js +261 -0
  29. package/src/accessories/temperature.js +95 -0
  30. package/src/accessories/thermostat.js +337 -0
  31. package/src/helper/handler.js +1467 -0
  32. package/src/helper/logger.js +51 -0
  33. package/src/helper/telegram.js +60 -0
  34. package/src/platform.js +337 -0
  35. package/src/tado/tado-api.js +404 -0
  36. package/src/tado/tado-config.js +1032 -0
  37. package/src/types/custom.js +264 -0
  38. package/src/types/eve.js +337 -0
@@ -0,0 +1,1467 @@
1
+ 'use strict';
2
+
3
+ const Logger = require('../helper/logger.js');
4
+
5
+ const moment = require('moment');
6
+
7
+ var settingState = false;
8
+ var delayTimer = {};
9
+
10
+ const timeout = (ms) => new Promise((res) => setTimeout(res, ms));
11
+
12
+ module.exports = (api, accessories, config, tado, telegram) => {
13
+ async function setStates(accessory, accs, target, value) {
14
+ accessories = accs.filter((acc) => acc && acc.context.config.homeName === config.homeName);
15
+
16
+ try {
17
+ settingState = true;
18
+
19
+ value = typeof value === 'number' ? parseFloat(value.toFixed(2)) : value;
20
+
21
+ Logger.info(target + ': ' + value, accessory.displayName);
22
+
23
+ switch (accessory.context.config.subtype) {
24
+ case 'zone-thermostat':
25
+ case 'zone-heatercooler':
26
+ case 'zone-heatercooler-boiler': {
27
+ let power, temp, clear;
28
+
29
+ let service =
30
+ accessory.getService(api.hap.Service.HeaterCooler) || accessory.getService(api.hap.Service.Thermostat);
31
+
32
+ let targetTempCharacteristic = accessory.getService(api.hap.Service.HeaterCooler)
33
+ ? api.hap.Characteristic.HeatingThresholdTemperature
34
+ : api.hap.Characteristic.TargetTemperature;
35
+
36
+ if (
37
+ accessory.context.config.subtype !== 'zone-heatercooler-boiler' &&
38
+ !accessory.context.config.autoOffDelay &&
39
+ accessory.context.config.delaySwitch &&
40
+ accessory.context.delaySwitch &&
41
+ accessory.context.delayTimer &&
42
+ value < 5
43
+ ) {
44
+ if (value === 0) {
45
+ if (delayTimer[accessory.displayName]) {
46
+ Logger.info('Resetting delay timer', accessory.displayName);
47
+ clearTimeout(delayTimer[accessory.displayName]);
48
+ delayTimer[accessory.displayName] = null;
49
+ }
50
+
51
+ power = 'OFF';
52
+ temp = parseFloat(service.getCharacteristic(targetTempCharacteristic).value.toFixed(2));
53
+
54
+ let mode =
55
+ accessory.context.config.mode === 'TIMER'
56
+ ? (accessory.context.config.modeTimer || 30) * 60
57
+ : accessory.context.config.mode;
58
+
59
+ await tado.setZoneOverlay(
60
+ config.homeId,
61
+ accessory.context.config.zoneId,
62
+ power,
63
+ temp,
64
+ mode,
65
+ accessory.context.config.temperatureUnit
66
+ );
67
+ } else {
68
+ let mode =
69
+ accessory.context.config.mode === 'TIMER'
70
+ ? (accessory.context.config.modeTimer || 30) * 60
71
+ : accessory.context.config.mode;
72
+
73
+ let timer = accessory.context.delayTimer;
74
+ let tarState = value === 1 ? 'HEAT' : 'AUTO';
75
+
76
+ if (delayTimer[accessory.displayName]) {
77
+ clearTimeout(delayTimer[accessory.displayName]);
78
+ delayTimer[accessory.displayName] = null;
79
+ }
80
+
81
+ Logger.info('Wait ' + timer + ' seconds before switching state', accessory.displayName);
82
+
83
+ delayTimer[accessory.displayName] = setTimeout(async () => {
84
+ Logger.info('Delay timer finished, switching state to ' + tarState, accessory.displayName);
85
+
86
+ //targetState
87
+ clear = value === 3;
88
+ power = 'ON';
89
+ temp = parseFloat(service.getCharacteristic(targetTempCharacteristic).value.toFixed(2));
90
+
91
+ if (
92
+ clear ||
93
+ (value &&
94
+ accessory.context.config.mode === 'AUTO' &&
95
+ accessory.context.config.subtype.includes('heatercooler'))
96
+ ) {
97
+ await tado.clearZoneOverlay(config.homeId, accessory.context.config.zoneId);
98
+ return;
99
+ }
100
+
101
+ mode =
102
+ !value &&
103
+ accessory.context.config.mode === 'AUTO' &&
104
+ accessory.context.config.subtype.includes('heatercooler')
105
+ ? 'MANUAL'
106
+ : mode;
107
+
108
+ await tado.setZoneOverlay(
109
+ config.homeId,
110
+ accessory.context.config.zoneId,
111
+ power,
112
+ temp,
113
+ mode,
114
+ accessory.context.config.temperatureUnit
115
+ );
116
+
117
+ delayTimer[accessory.displayName] = null;
118
+ }, timer * 1000);
119
+ }
120
+ } else {
121
+ let mode =
122
+ accessory.context.config.mode === 'TIMER'
123
+ ? (accessory.context.config.modeTimer || 30) * 60
124
+ : accessory.context.config.mode;
125
+
126
+ if ([0, 1, 3].includes(value)) {
127
+ //targetState
128
+ clear = value === 3;
129
+
130
+ power = value ? 'ON' : 'OFF';
131
+
132
+ temp = parseFloat(service.getCharacteristic(targetTempCharacteristic).value.toFixed(2));
133
+
134
+ if (
135
+ clear ||
136
+ (value &&
137
+ accessory.context.config.mode === 'CUSTOM' &&
138
+ accessory.context.config.subtype.includes('heatercooler'))
139
+ ) {
140
+ await tado.clearZoneOverlay(config.homeId, accessory.context.config.zoneId);
141
+ return;
142
+ }
143
+
144
+ mode =
145
+ !value &&
146
+ accessory.context.config.mode === 'CUSTOM' &&
147
+ accessory.context.config.subtype.includes('heatercooler')
148
+ ? 'MANUAL'
149
+ : mode;
150
+ } else {
151
+ //temp
152
+ power = 'ON';
153
+ temp = parseFloat(value.toFixed(2));
154
+ }
155
+
156
+ await tado.setZoneOverlay(
157
+ config.homeId,
158
+ accessory.context.config.zoneId,
159
+ power,
160
+ temp,
161
+ mode,
162
+ accessory.context.config.temperatureUnit
163
+ );
164
+ }
165
+
166
+ break;
167
+ }
168
+
169
+ case 'zone-switch':
170
+ case 'zone-faucet': {
171
+ let faucetService = accessory.getService(api.hap.Service.Faucet);
172
+
173
+ let temp = null;
174
+ let power = value ? 'ON' : 'OFF';
175
+
176
+ if (faucetService) faucetService.getCharacteristic(this.api.hap.Characteristic.InUse).updateValue(value);
177
+
178
+ let mode =
179
+ accessory.context.config.mode === 'TIMER'
180
+ ? (accessory.context.config.modeTimer || 30) * 60
181
+ : accessory.context.config.mode;
182
+
183
+ await tado.setZoneOverlay(
184
+ config.homeId,
185
+ accessory.context.config.zoneId,
186
+ power,
187
+ temp,
188
+ mode,
189
+ accessory.context.config.temperatureUnit
190
+ );
191
+
192
+ break;
193
+ }
194
+
195
+ case 'extra-plock':
196
+ case 'extra-plockswitch': {
197
+ let targetState;
198
+
199
+ if (accessory.context.config.subtype === 'extra-plockswitch') {
200
+ let serviceHomeSwitch = accessory.getServiceById(api.hap.Service.Switch, 'HomeSwitch');
201
+ let serviceAwaySwitch = accessory.getServiceById(api.hap.Service.Switch, 'AwaySwitch');
202
+
203
+ let characteristic = api.hap.Characteristic.On;
204
+
205
+ if (value) {
206
+ if (target === 'Home') {
207
+ targetState = 'HOME';
208
+
209
+ serviceAwaySwitch.getCharacteristic(characteristic).updateValue(false);
210
+ } else {
211
+ targetState = 'AWAY';
212
+
213
+ serviceHomeSwitch.getCharacteristic(characteristic).updateValue(false);
214
+ }
215
+ } else {
216
+ targetState = 'AUTO';
217
+
218
+ serviceAwaySwitch.getCharacteristic(characteristic).updateValue(false);
219
+
220
+ serviceHomeSwitch.getCharacteristic(characteristic).updateValue(false);
221
+ }
222
+ } else {
223
+ let serviceSecurity = accessory.getService(api.hap.Service.SecuritySystem);
224
+ let characteristicCurrent = api.hap.Characteristic.SecuritySystemCurrentState;
225
+
226
+ serviceSecurity.getCharacteristic(characteristicCurrent).updateValue(value);
227
+
228
+ if (value === 1) {
229
+ //away
230
+
231
+ targetState = 'AWAY';
232
+ } else if (value === 3) {
233
+ //off
234
+
235
+ targetState = 'AUTO';
236
+ } else {
237
+ //at home
238
+
239
+ targetState = 'HOME';
240
+ }
241
+ }
242
+
243
+ await tado.setPresenceLock(config.homeId, targetState);
244
+
245
+ break;
246
+ }
247
+
248
+ case 'zone-window-switch': {
249
+ let zoneId = target.split('-');
250
+ zoneId = zoneId[zoneId.length - 1];
251
+
252
+ await tado.setWindowDetection(config.homeId, zoneId, value, 3600);
253
+ await tado.setOpenWindowMode(config.homeId, zoneId, value);
254
+
255
+ break;
256
+ }
257
+
258
+ case 'extra-childswitch': {
259
+ await tado.setChildLock(target, value);
260
+
261
+ break;
262
+ }
263
+
264
+ case 'extra-cntrlswitch': {
265
+ if (target === 'Dummy') return;
266
+
267
+ const heatAccessories = accessories.filter((acc) => acc && acc.context.config.type === 'HEATING');
268
+
269
+ const rooms = accessory.context.config.rooms
270
+ .map((room) => {
271
+ return {
272
+ id: room.id,
273
+ power: target === 'Central' ? (value ? 'ON' : 'OFF') : target === 'Off' ? 'OFF' : 'ON',
274
+ maxTempInCelsius: target === 'Central' ? (value ? 25 : 0) : target === 'Off' ? false : 25,
275
+ termination: ['MANUAL', 'AUTO', 'TIMER'].includes(room.mode) ? room.mode : 'MANUAL',
276
+ timer:
277
+ ['MANUAL', 'AUTO', 'TIMER'].includes(room.mode) && room.mode === 'TIMER'
278
+ ? room.modeTimer && room.modeTimer >= 1
279
+ ? room.modeTimer * 60
280
+ : 1800 //30min
281
+ : false,
282
+ };
283
+ })
284
+ .filter((room) => room);
285
+
286
+ if (value) {
287
+ if (target === 'Central' || target === 'Shedule') {
288
+ const roomIds = accessory.context.config.rooms
289
+ .map((room) => {
290
+ return room.id;
291
+ })
292
+ .filter((id) => id);
293
+
294
+ await tado.resumeShedule(config.homeId, roomIds);
295
+
296
+ //Turn all back to AUTO/ON
297
+ heatAccessories.forEach((acc) => {
298
+ let serviceThermostat = acc.getService(api.hap.Service.Thermostat);
299
+ let serviceHeaterCooler = acc.getService(api.hap.Service.HeaterCooler);
300
+
301
+ if (serviceThermostat) {
302
+ let characteristicTarget = api.hap.Characteristic.TargetHeatingCoolingState;
303
+
304
+ serviceThermostat.getCharacteristic(characteristicTarget).updateValue(3);
305
+ } else if (serviceHeaterCooler) {
306
+ let characteristicActive = api.hap.Characteristic.Active;
307
+
308
+ serviceHeaterCooler.getCharacteristic(characteristicActive).updateValue(1);
309
+ }
310
+ });
311
+
312
+ accessory
313
+ .getServiceById(api.hap.Service.Switch, 'Central')
314
+ .getCharacteristic(api.hap.Characteristic.On)
315
+ .updateValue(true);
316
+
317
+ return;
318
+ }
319
+
320
+ if (target === 'Boost') {
321
+ //Turn On All & Max temp & Central Switch
322
+ heatAccessories.forEach((acc) => {
323
+ let serviceThermostat = acc.getService(api.hap.Service.Thermostat);
324
+ let serviceHeaterCooler = acc.getService(api.hap.Service.HeaterCooler);
325
+
326
+ if (serviceThermostat) {
327
+ let characteristicCurrent = api.hap.Characteristic.CurrentHeatingCoolingState;
328
+ let characteristicTarget = api.hap.Characteristic.TargetHeatingCoolingState;
329
+ let characteristicTargetTemp = api.hap.Characteristic.TargetTemperature;
330
+
331
+ let maxTemp = serviceThermostat.getCharacteristic(characteristicTargetTemp).props.maxValue;
332
+
333
+ serviceThermostat.getCharacteristic(characteristicCurrent).updateValue(1);
334
+
335
+ serviceThermostat.getCharacteristic(characteristicTarget).updateValue(1);
336
+
337
+ serviceThermostat.getCharacteristic(characteristicTargetTemp).updateValue(maxTemp);
338
+ } else if (serviceHeaterCooler) {
339
+ let characteristicActive = api.hap.Characteristic.Active;
340
+ let characteristicTargetTempHeat = api.hap.Characteristic.HeatingThresholdTemperature;
341
+
342
+ let maxTemp = serviceHeaterCooler.getCharacteristic(characteristicTargetTempHeat).props.maxValue; //same for cool
343
+
344
+ serviceHeaterCooler.getCharacteristic(characteristicActive).updateValue(1);
345
+
346
+ serviceHeaterCooler.getCharacteristic(characteristicTargetTempHeat).updateValue(maxTemp);
347
+ }
348
+ });
349
+
350
+ accessory
351
+ .getServiceById(api.hap.Service.Switch, 'Central')
352
+ .getCharacteristic(api.hap.Characteristic.On)
353
+ .updateValue(true);
354
+ }
355
+
356
+ if (target === 'Off') {
357
+ //Turn Off All && Central Switch
358
+ heatAccessories.forEach((acc) => {
359
+ let serviceThermostat = acc.getService(api.hap.Service.Thermostat);
360
+ let serviceHeaterCooler = acc.getService(api.hap.Service.HeaterCooler);
361
+
362
+ if (serviceThermostat) {
363
+ let characteristicCurrent = api.hap.Characteristic.CurrentHeatingCoolingState;
364
+ let characteristicTarget = api.hap.Characteristic.TargetHeatingCoolingState;
365
+
366
+ serviceThermostat.getCharacteristic(characteristicCurrent).updateValue(0);
367
+
368
+ serviceThermostat.getCharacteristic(characteristicTarget).updateValue(0);
369
+ } else if (serviceHeaterCooler) {
370
+ let characteristicActive = api.hap.Characteristic.Active;
371
+
372
+ serviceHeaterCooler.getCharacteristic(characteristicActive).updateValue(0);
373
+ }
374
+ });
375
+
376
+ accessory
377
+ .getServiceById(api.hap.Service.Switch, 'Central')
378
+ .getCharacteristic(api.hap.Characteristic.On)
379
+ .updateValue(false);
380
+ }
381
+
382
+ if (target !== 'Central') {
383
+ setTimeout(() => {
384
+ accessory
385
+ .getServiceById(api.hap.Service.Switch, 'Central' + target)
386
+ .getCharacteristic(api.hap.Characteristic.On)
387
+ .updateValue(false);
388
+ }, 500);
389
+ }
390
+ } else {
391
+ if (target !== 'Central') return;
392
+
393
+ //Turn Off All && Central Switch
394
+ heatAccessories.forEach((acc) => {
395
+ let serviceThermostat = acc.getService(api.hap.Service.Thermostat);
396
+ let serviceHeaterCooler = acc.getService(api.hap.Service.HeaterCooler);
397
+
398
+ if (serviceThermostat) {
399
+ let characteristicCurrent = api.hap.Characteristic.CurrentHeatingCoolingState;
400
+ let characteristicTarget = api.hap.Characteristic.TargetHeatingCoolingState;
401
+
402
+ serviceThermostat.getCharacteristic(characteristicCurrent).updateValue(0);
403
+
404
+ serviceThermostat.getCharacteristic(characteristicTarget).updateValue(0);
405
+ } else if (serviceHeaterCooler) {
406
+ let characteristicActive = api.hap.Characteristic.Active;
407
+
408
+ serviceHeaterCooler.getCharacteristic(characteristicActive).updateValue(0);
409
+ }
410
+ });
411
+
412
+ accessory
413
+ .getServiceById(api.hap.Service.Switch, 'Central')
414
+ .getCharacteristic(api.hap.Characteristic.On)
415
+ .updateValue(false);
416
+ }
417
+
418
+ await tado.switchAll(config.homeId, rooms);
419
+
420
+ break;
421
+ }
422
+
423
+ default:
424
+ Logger.warn(
425
+ 'Unknown accessory type! [' + accessory.context.config.subtype + '] ' + target + ': ' + value,
426
+ accessory.displayName
427
+ );
428
+ break;
429
+ }
430
+ } catch (err) {
431
+ errorHandler(err);
432
+ } finally {
433
+ settingState = false;
434
+ }
435
+ }
436
+
437
+ async function changedStates(accessory, historyService, replacer, value) {
438
+ if (value.oldValue !== value.newValue) {
439
+ switch (accessory.context.config.subtype) {
440
+ case 'zone-thermostat': {
441
+ let currentState = accessory
442
+ .getService(api.hap.Service.Thermostat)
443
+ .getCharacteristic(api.hap.Characteristic.CurrentHeatingCoolingState).value;
444
+ let targetState = accessory
445
+ .getService(api.hap.Service.Thermostat)
446
+ .getCharacteristic(api.hap.Characteristic.TargetHeatingCoolingState).value;
447
+ let currentTemp = accessory
448
+ .getService(api.hap.Service.Thermostat)
449
+ .getCharacteristic(api.hap.Characteristic.CurrentTemperature).value;
450
+ let targetTemp = accessory
451
+ .getService(api.hap.Service.Thermostat)
452
+ .getCharacteristic(api.hap.Characteristic.TargetTemperature).value;
453
+
454
+ let valvePos =
455
+ currentTemp <= targetTemp &&
456
+ currentState !== api.hap.Characteristic.CurrentHeatingCoolingState.OFF &&
457
+ targetState !== api.hap.Characteristic.TargetHeatingCoolingState.OFF
458
+ ? Math.round(targetTemp - currentTemp >= 5 ? 100 : (targetTemp - currentTemp) * 20)
459
+ : 0;
460
+
461
+ historyService.addEntry({
462
+ time: moment().unix(),
463
+ currentTemp: currentTemp,
464
+ setTemp: targetTemp,
465
+ valvePosition: valvePos,
466
+ });
467
+
468
+ break;
469
+ }
470
+
471
+ case 'zone-heatercooler':
472
+ case 'zone-heatercooler-boiler': {
473
+ let currentState = accessory
474
+ .getService(api.hap.Service.HeaterCooler)
475
+ .getCharacteristic(api.hap.Characteristic.CurrentHeaterCoolerState).value;
476
+ let currentTemp = accessory
477
+ .getService(api.hap.Service.HeaterCooler)
478
+ .getCharacteristic(api.hap.Characteristic.CurrentTemperature).value;
479
+ let targetTemp = accessory
480
+ .getService(api.hap.Service.HeaterCooler)
481
+ .getCharacteristic(api.hap.Characteristic.HeatingThresholdTemperature).value;
482
+
483
+ let valvePos =
484
+ currentTemp <= targetTemp && currentState !== 0
485
+ ? Math.round(targetTemp - currentTemp >= 5 ? 100 : (targetTemp - currentTemp) * 20)
486
+ : 0;
487
+
488
+ historyService.addEntry({
489
+ time: moment().unix(),
490
+ currentTemp: currentTemp,
491
+ setTemp: targetTemp,
492
+ valvePosition: valvePos,
493
+ });
494
+
495
+ break;
496
+ }
497
+
498
+ case 'zone-window-contact': {
499
+ if (value.newValue) {
500
+ accessory.context.timesOpened = accessory.context.timesOpened || 0;
501
+ accessory.context.timesOpened += 1;
502
+
503
+ let lastActivation = moment().unix() - historyService.getInitialTime();
504
+ let closeDuration = moment().unix() - historyService.getInitialTime();
505
+
506
+ accessory
507
+ .getService(api.hap.Service.ContactSensor)
508
+ .getCharacteristic(api.hap.Characteristic.LastActivation)
509
+ .updateValue(lastActivation);
510
+
511
+ accessory
512
+ .getService(api.hap.Service.ContactSensor)
513
+ .getCharacteristic(api.hap.Characteristic.TimesOpened)
514
+ .updateValue(accessory.context.timesOpened);
515
+
516
+ accessory
517
+ .getService(api.hap.Service.ContactSensor)
518
+ .getCharacteristic(api.hap.Characteristic.ClosedDuration)
519
+ .updateValue(closeDuration);
520
+ } else {
521
+ let openDuration = moment().unix() - historyService.getInitialTime();
522
+
523
+ accessory
524
+ .getService(api.hap.Service.ContactSensor)
525
+ .getCharacteristic(api.hap.Characteristic.ClosedDuration)
526
+ .updateValue(openDuration);
527
+ }
528
+
529
+ historyService.addEntry({ time: moment().unix(), status: value.newValue ? 1 : 0 });
530
+
531
+ let dest = value.newValue ? 'opened' : 'closed';
532
+
533
+ replacer = replacer.split(accessory.context.config.homeName + ' ')[1];
534
+ let additional = accessory.context.config.homeName;
535
+
536
+ if (telegram) telegram.send('openWindow', dest, replacer, additional);
537
+
538
+ break;
539
+ }
540
+
541
+ case 'presence-occupancy':
542
+ case 'presence-motion': {
543
+ if (historyService) {
544
+ let lastActivation = moment().unix() - historyService.getInitialTime();
545
+
546
+ accessory
547
+ .getService(api.hap.Service.MotionSensor)
548
+ .getCharacteristic(api.hap.Characteristic.LastActivation)
549
+ .updateValue(lastActivation);
550
+
551
+ historyService.addEntry({ time: moment().unix(), status: value.newValue ? 1 : 0 });
552
+ }
553
+
554
+ let dest;
555
+
556
+ replacer = replacer.split(accessory.context.config.homeName + ' ')[1];
557
+ let additional = accessory.context.config.homeName;
558
+
559
+ if (value.newValue) {
560
+ dest = accessory.displayName === accessory.context.config.homeName + ' Anyone' ? 'anyone_in' : 'user_in';
561
+ } else {
562
+ dest = accessory.displayName === accessory.context.config.homeName + ' Anyone' ? 'anyone_out' : 'user_out';
563
+ }
564
+
565
+ if (telegram) telegram.send('presence', dest, replacer === 'Anyone' ? false : replacer, additional);
566
+
567
+ break;
568
+ }
569
+
570
+ case 'zone-temperature':
571
+ case 'weather-temperature': {
572
+ historyService.addEntry({ time: moment().unix(), temp: value.newValue, humidity: 0, ppm: 0 });
573
+
574
+ break;
575
+ }
576
+
577
+ case 'zone-humidity': {
578
+ historyService.addEntry({ time: moment().unix(), temp: 0, humidity: value.newValue, ppm: 0 });
579
+
580
+ break;
581
+ }
582
+
583
+ default:
584
+ Logger.warn('Accessory with unknown subtype wanted to store history data', accessory.displayName);
585
+ break;
586
+ }
587
+ }
588
+ }
589
+
590
+ async function getStates() {
591
+ try {
592
+ //ME
593
+ if (!config.homeId) await updateMe();
594
+
595
+ //Home
596
+ if (
597
+ !config.temperatureUnit ||
598
+ (config.extras &&
599
+ config.weather.airQuality &&
600
+ (!config.geolocation ||
601
+ (config.geolocation && !config.geolocation.longitude) ||
602
+ !config.geolocation.latitude))
603
+ )
604
+ await updateHome();
605
+
606
+ //Zones
607
+ if (config.zones.length) await updateZones();
608
+
609
+ //Zones Room Air Quality
610
+ if (config.zones.length && config.zones.find((zone) => zone && zone.airQuality)) await updateRoomAirQuality();
611
+
612
+ //MobileDevices
613
+ if (config.presence.length) await updateMobileDevices();
614
+
615
+ //Weather
616
+ if (config.weather.temperatureSensor || config.weather.solarIntensity) await updateWeather();
617
+
618
+ //AirQuality
619
+ if (
620
+ config.weather.airQuality &&
621
+ config.geolocation &&
622
+ config.geolocation.longitude &&
623
+ config.geolocation.latitude
624
+ )
625
+ await updateAirQuality();
626
+
627
+ //RunningTime
628
+ if (config.extras.centralSwitch && config.extras.runningInformation) await updateRunningTime();
629
+
630
+ //Presence Lock
631
+ if (config.extras.presenceLock) await updatePresence();
632
+
633
+ //Child Lock
634
+ if (config.childLock.length) await updateDevices();
635
+ } catch (err) {
636
+ errorHandler(err);
637
+ } finally {
638
+ setTimeout(() => {
639
+ getStates();
640
+ }, config.polling * 1000);
641
+ }
642
+ }
643
+
644
+ async function updateMe() {
645
+ if (!settingState) {
646
+ Logger.debug('Polling User Info...', config.homeName);
647
+
648
+ const me = await tado.getMe();
649
+
650
+ if (config.homeName !== me.homes[0].name) throw ('Cannot find requested home in the API!', config.homeName);
651
+
652
+ config.homeId = me.homes[0].id;
653
+ }
654
+
655
+ return;
656
+ }
657
+
658
+ async function updateHome() {
659
+ if (!settingState) {
660
+ Logger.debug('Polling Home Info...', config.homeName);
661
+
662
+ const home = await tado.getHome(config.homeId);
663
+
664
+ if (!config.temperatureUnit) config.temperatureUnit = home.temperatureUnit || 'CELSIUS';
665
+
666
+ //config.skills = home.skills || []; //do we need this?
667
+
668
+ if (
669
+ !config.geolocation ||
670
+ (config.geolocation && !config.geolocation.longitude) ||
671
+ !config.geolocation.latitude
672
+ ) {
673
+ if (!home.geolocation) home.geolocation = {};
674
+
675
+ config.geolocation = {
676
+ longitude: (home.geolocation.longitude || '').toString() || false,
677
+ latitude: (home.geolocation.latitude || '').toString() || false,
678
+ };
679
+ }
680
+ }
681
+
682
+ return;
683
+ }
684
+
685
+ async function updateZones() {
686
+ if (!settingState) {
687
+ Logger.debug('Polling Zones...', config.homeName);
688
+
689
+ //CentralSwitch
690
+ let inManualMode = 0;
691
+ let inOffMode = 0;
692
+ let inAutoMode = 0;
693
+
694
+ let zonesWithoutID = config.zones.filter((zone) => zone && !zone.id);
695
+
696
+ if (zonesWithoutID.length) {
697
+ const allZones = (await tado.getZones(config.homeId)) || [];
698
+
699
+ for (const [index, zone] of config.zones.entries()) {
700
+ allZones.forEach((zoneWithID) => {
701
+ if (zoneWithID.name === zone.name) config.zones[index].id = zoneWithID.id;
702
+ });
703
+ }
704
+ }
705
+
706
+ const allZones = (await tado.getZones(config.homeId)) || [];
707
+
708
+ for (const [index, zone] of config.zones.entries()) {
709
+ allZones.forEach((zoneWithID) => {
710
+ if (zoneWithID.name === zone.name) {
711
+ const heatAccessory = accessories.filter(
712
+ (acc) => acc && acc.displayName === config.homeName + ' ' + zone.name + ' Heater'
713
+ );
714
+
715
+ if (heatAccessory.length) heatAccessory[0].context.config.zoneId = zoneWithID.id;
716
+
717
+ config.zones[index].id = zoneWithID.id;
718
+ config.zones[index].battery = !config.zones[index].noBattery
719
+ ? zoneWithID.devices.filter(
720
+ (device) =>
721
+ device &&
722
+ zone.type === 'HEATING' &&
723
+ typeof device.batteryState === 'string' &&
724
+ !device.batteryState.includes('NORMAL')
725
+ ).length
726
+ ? zoneWithID.devices.filter((device) => device && !device.batteryState.includes('NORMAL'))[0]
727
+ .batteryState
728
+ : zoneWithID.devices.filter((device) => device && device.duties.includes('ZONE_LEADER'))[0].batteryState
729
+ : false;
730
+ config.zones[index].openWindowEnabled =
731
+ zoneWithID.openWindowDetection && zoneWithID.openWindowDetection.enabled ? true : false;
732
+ }
733
+ });
734
+ }
735
+
736
+ for (const zone of config.zones) {
737
+ const zoneState = await tado.getZoneState(config.homeId, zone.id);
738
+
739
+ let currentState, targetState, currentTemp, targetTemp, humidity, active, battery, tempEqual;
740
+
741
+ if (zoneState.setting.type === 'HEATING') {
742
+ battery = zone.battery === 'NORMAL' ? 100 : 10;
743
+
744
+ if (zoneState.sensorDataPoints.humidity) humidity = zoneState.sensorDataPoints.humidity.percentage;
745
+
746
+ //HEATING
747
+ if (zoneState.sensorDataPoints.insideTemperature) {
748
+ currentTemp =
749
+ config.temperatureUnit === 'FAHRENHEIT'
750
+ ? zoneState.sensorDataPoints.insideTemperature.fahrenheit
751
+ : zoneState.sensorDataPoints.insideTemperature.celsius;
752
+
753
+ if (zoneState.setting.power === 'ON') {
754
+ targetTemp =
755
+ config.temperatureUnit === 'FAHRENHEIT'
756
+ ? zoneState.setting.temperature.fahrenheit
757
+ : zoneState.setting.temperature.celsius;
758
+
759
+ tempEqual = Math.round(currentTemp) === Math.round(targetTemp);
760
+
761
+ currentState = currentTemp <= targetTemp ? 1 : 2;
762
+
763
+ targetState = 1;
764
+
765
+ active = 1;
766
+ } else {
767
+ currentState = 0;
768
+ targetState = 0;
769
+ active = 0;
770
+ }
771
+
772
+ if (zoneState.overlayType === null) {
773
+ currentState = 0;
774
+ targetState = 3;
775
+ }
776
+ }
777
+
778
+ //Thermostat/HeaterCooler
779
+ const thermoAccessory = accessories.filter(
780
+ (acc) =>
781
+ acc &&
782
+ (acc.context.config.subtype === 'zone-thermostat' || acc.context.config.subtype === 'zone-heatercooler')
783
+ );
784
+
785
+ if (thermoAccessory.length) {
786
+ thermoAccessory.forEach((acc) => {
787
+ if (acc.displayName.includes(zone.name)) {
788
+ let serviceThermostat = acc.getService(api.hap.Service.Thermostat);
789
+ let serviceHeaterCooler = acc.getService(api.hap.Service.HeaterCooler);
790
+
791
+ let serviceBattery = acc.getService(api.hap.Service.BatteryService);
792
+ let characteristicBattery = api.hap.Characteristic.BatteryLevel;
793
+
794
+ if (serviceBattery && zone.battery) {
795
+ serviceBattery.getCharacteristic(characteristicBattery).updateValue(battery);
796
+ }
797
+
798
+ if (serviceThermostat) {
799
+ let characteristicCurrentTemp = api.hap.Characteristic.CurrentTemperature;
800
+ let characteristicTargetTemp = api.hap.Characteristic.TargetTemperature;
801
+ let characteristicCurrentState = api.hap.Characteristic.CurrentHeatingCoolingState;
802
+ let characteristicTargetState = api.hap.Characteristic.TargetHeatingCoolingState;
803
+ let characteristicHumidity = api.hap.Characteristic.CurrentRelativeHumidity;
804
+ let characteristicUnit = api.hap.Characteristic.TemperatureDisplayUnits;
805
+
806
+ if (!isNaN(currentTemp)) {
807
+ acc.context.config.temperatureUnit = acc.context.config.temperatureUnit || config.temperatureUnit;
808
+
809
+ let isFahrenheit = serviceThermostat.getCharacteristic(characteristicUnit).value === 1;
810
+ let unitChanged = config.temperatureUnit !== acc.context.config.temperatureUnit;
811
+
812
+ let cToF = (c) => Math.round((c * 9) / 5 + 32);
813
+ let fToC = (f) => Math.round(((f - 32) * 5) / 9);
814
+
815
+ let newValue = unitChanged ? (isFahrenheit ? cToF(currentTemp) : fToC(currentTemp)) : currentTemp;
816
+
817
+ serviceThermostat.getCharacteristic(characteristicCurrentTemp).updateValue(newValue);
818
+ }
819
+
820
+ if (!isNaN(targetTemp))
821
+ serviceThermostat.getCharacteristic(characteristicTargetTemp).updateValue(targetTemp);
822
+
823
+ if (!isNaN(currentState))
824
+ serviceThermostat.getCharacteristic(characteristicCurrentState).updateValue(currentState);
825
+
826
+ if (!isNaN(targetState))
827
+ serviceThermostat.getCharacteristic(characteristicTargetState).updateValue(targetState);
828
+
829
+ if (!isNaN(humidity) && serviceThermostat.testCharacteristic(characteristicHumidity))
830
+ serviceThermostat.getCharacteristic(characteristicHumidity).updateValue(humidity);
831
+ }
832
+
833
+ if (serviceHeaterCooler) {
834
+ let characteristicHumidity = api.hap.Characteristic.CurrentRelativeHumidity;
835
+ let characteristicCurrentTemp = api.hap.Characteristic.CurrentTemperature;
836
+ let characteristicTargetTempHeating = api.hap.Characteristic.HeatingThresholdTemperature;
837
+ let characteristicTargetTempCooling = api.hap.Characteristic.CoolingThresholdTemperature;
838
+ let characteristicCurrentState = api.hap.Characteristic.CurrentHeaterCoolerState;
839
+ let characteristicTargetState = api.hap.Characteristic.TargetHeaterCoolerState;
840
+ let characteristicActive = api.hap.Characteristic.Active;
841
+
842
+ currentState = active ? (targetState === 3 || tempEqual ? 1 : currentState + 1) : 0;
843
+
844
+ targetState = 1;
845
+
846
+ if (!isNaN(active)) serviceHeaterCooler.getCharacteristic(characteristicActive).updateValue(active);
847
+
848
+ if (!isNaN(currentTemp))
849
+ serviceHeaterCooler.getCharacteristic(characteristicCurrentTemp).updateValue(currentTemp);
850
+
851
+ if (!isNaN(targetTemp)) {
852
+ serviceHeaterCooler.getCharacteristic(characteristicTargetTempHeating).updateValue(targetTemp);
853
+
854
+ serviceHeaterCooler.getCharacteristic(characteristicTargetTempCooling).updateValue(targetTemp);
855
+ }
856
+
857
+ if (!isNaN(currentState))
858
+ serviceHeaterCooler.getCharacteristic(characteristicCurrentState).updateValue(currentState);
859
+
860
+ if (!isNaN(targetState))
861
+ serviceHeaterCooler.getCharacteristic(characteristicTargetState).updateValue(targetState);
862
+
863
+ if (!isNaN(humidity) && serviceHeaterCooler.testCharacteristic(characteristicHumidity))
864
+ serviceHeaterCooler.getCharacteristic(characteristicHumidity).updateValue(humidity);
865
+ }
866
+ }
867
+ });
868
+ }
869
+ } else {
870
+ if (zoneState.setting.power === 'ON') {
871
+ active = 1;
872
+ currentState = zoneState.overlayType === null ? 1 : 2;
873
+ } else {
874
+ active = 0;
875
+ currentState = 0;
876
+ }
877
+
878
+ targetState = 1;
879
+
880
+ currentTemp =
881
+ zoneState.setting.temperature !== null
882
+ ? config.temperatureUnit === 'FAHRENHEIT'
883
+ ? zoneState.setting.temperature.fahrenheit
884
+ : zoneState.setting.temperature.celsius
885
+ : undefined;
886
+
887
+ targetTemp = active && currentTemp ? currentTemp : undefined;
888
+
889
+ //Thermostat/HeaterCooler
890
+ const heaterAccessory = accessories.filter(
891
+ (acc) => acc && acc.context.config.subtype === 'zone-heatercooler-boiler'
892
+ );
893
+ const switchAccessory = accessories.filter((acc) => acc && acc.context.config.subtype === 'zone-switch');
894
+ const faucetAccessory = accessories.filter((acc) => acc && acc.context.config.subtype === 'zone-faucet');
895
+
896
+ if (heaterAccessory.length) {
897
+ heaterAccessory.forEach((acc) => {
898
+ if (acc.displayName.includes(zone.name)) {
899
+ let service = acc.getService(api.hap.Service.HeaterCooler);
900
+
901
+ let characteristicCurrentTemp = api.hap.Characteristic.CurrentTemperature;
902
+ let characteristicActive = api.hap.Characteristic.Active;
903
+ let characteristicCurrentState = api.hap.Characteristic.CurrentHeaterCoolerState;
904
+ let characteristicTargetState = api.hap.Characteristic.TargetHeaterCoolerState;
905
+ let characteristicTargetTempHeating = api.hap.Characteristic.HeatingThresholdTemperature;
906
+
907
+ service.getCharacteristic(characteristicActive).updateValue(active);
908
+
909
+ service.getCharacteristic(characteristicCurrentState).updateValue(currentState);
910
+
911
+ service.getCharacteristic(characteristicTargetState).updateValue(targetState);
912
+
913
+ if (!isNaN(currentTemp) || acc.context.currentTemp) {
914
+ if (!isNaN(currentTemp)) acc.context.currentTemp = currentTemp; //store current temp in config
915
+
916
+ service.getCharacteristic(characteristicCurrentTemp).updateValue(acc.context.currentTemp);
917
+ }
918
+
919
+ if (!isNaN(targetTemp)) {
920
+ service.getCharacteristic(characteristicTargetTempHeating).updateValue(targetTemp);
921
+ }
922
+ }
923
+ });
924
+ }
925
+
926
+ if (switchAccessory.length) {
927
+ switchAccessory.forEach((acc) => {
928
+ if (acc.displayName.includes(zone.name)) {
929
+ let service = acc.getService(api.hap.Service.Switch);
930
+
931
+ let characteristic = api.hap.Characteristic.On;
932
+
933
+ service.getCharacteristic(characteristic).updateValue(active ? true : false);
934
+ }
935
+ });
936
+ }
937
+
938
+ if (faucetAccessory.length) {
939
+ faucetAccessory.forEach((acc) => {
940
+ if (acc.displayName.includes(zone.name)) {
941
+ let service = acc.getService(api.hap.Service.Valve);
942
+
943
+ let characteristicActive = api.hap.Characteristic.Active;
944
+ let characteristicInUse = api.hap.Characteristic.InUse;
945
+
946
+ service.getCharacteristic(characteristicActive).updateValue(active ? 1 : 0);
947
+
948
+ service.getCharacteristic(characteristicInUse).updateValue(active ? 1 : 0);
949
+ }
950
+ });
951
+ }
952
+ }
953
+
954
+ //TemperatureSensor
955
+ const tempAccessory = accessories.filter((acc) => acc && acc.context.config.subtype === 'zone-temperature');
956
+
957
+ if (tempAccessory.length) {
958
+ tempAccessory.forEach((acc) => {
959
+ if (acc.displayName.includes(zone.name)) {
960
+ let serviceBattery = acc.getService(api.hap.Service.BatteryService);
961
+ let characteristicBattery = api.hap.Characteristic.BatteryLevel;
962
+
963
+ if (serviceBattery && !isNaN(battery)) {
964
+ serviceBattery.getCharacteristic(characteristicBattery).updateValue(battery);
965
+ }
966
+
967
+ if (!isNaN(currentTemp)) {
968
+ let service = acc.getService(api.hap.Service.TemperatureSensor);
969
+ let characteristic = api.hap.Characteristic.CurrentTemperature;
970
+
971
+ service.getCharacteristic(characteristic).updateValue(currentTemp);
972
+ }
973
+ }
974
+ });
975
+ }
976
+
977
+ //HumiditySensor
978
+ const humidityAccessory = accessories.filter((acc) => acc && acc.context.config.subtype === 'zone-humidity');
979
+
980
+ humidityAccessory.forEach((acc) => {
981
+ if (acc.displayName.includes(zone.name)) {
982
+ let serviceBattery = acc.getService(api.hap.Service.BatteryService);
983
+ let characteristicBattery = api.hap.Characteristic.BatteryLevel;
984
+
985
+ if (serviceBattery && !isNaN(battery)) {
986
+ serviceBattery.getCharacteristic(characteristicBattery).updateValue(battery);
987
+ }
988
+
989
+ if (!isNaN(humidity)) {
990
+ let service = acc.getService(api.hap.Service.HumiditySensor);
991
+ let characteristic = api.hap.Characteristic.CurrentRelativeHumidity;
992
+
993
+ service.getCharacteristic(characteristic).updateValue(humidity);
994
+ }
995
+ }
996
+ });
997
+
998
+ //WindowSensor
999
+ const windowContactAccessory = accessories.filter(
1000
+ (acc) => acc && acc.context.config.subtype === 'zone-window-contact'
1001
+ );
1002
+ const windowSwitchAccessory = accessories.filter(
1003
+ (acc) => acc && acc.displayName === acc.context.config.homeName + ' Open Window'
1004
+ );
1005
+
1006
+ if (windowContactAccessory.length) {
1007
+ windowContactAccessory.forEach((acc) => {
1008
+ if (acc.displayName.includes(zone.name)) {
1009
+ let serviceBattery = acc.getService(api.hap.Service.BatteryService);
1010
+ let characteristicBattery = api.hap.Characteristic.BatteryLevel;
1011
+
1012
+ if (serviceBattery && !isNaN(battery)) {
1013
+ serviceBattery.getCharacteristic(characteristicBattery).updateValue(battery);
1014
+ }
1015
+
1016
+ let service = acc.getService(api.hap.Service.ContactSensor);
1017
+ let characteristic = api.hap.Characteristic.ContactSensorState;
1018
+
1019
+ let state = zoneState.openWindow || zoneState.openWindowDetected ? 1 : 0;
1020
+
1021
+ service.getCharacteristic(characteristic).updateValue(state);
1022
+ }
1023
+ });
1024
+ }
1025
+
1026
+ if (windowSwitchAccessory.length) {
1027
+ windowSwitchAccessory[0].services.forEach((switchService) => {
1028
+ if (switchService.subtype && switchService.subtype.includes(zone.name)) {
1029
+ let service = windowSwitchAccessory[0].getServiceById(api.hap.Service.Switch, switchService.subtype);
1030
+ let characteristic = api.hap.Characteristic.On;
1031
+
1032
+ let state = zone.openWindowEnabled ? true : false;
1033
+
1034
+ service.getCharacteristic(characteristic).updateValue(state);
1035
+ }
1036
+ });
1037
+ }
1038
+
1039
+ if (zoneState.setting.type === 'HEATING') {
1040
+ //CentralSwitch
1041
+ if (zoneState.overlayType === null) inAutoMode += 1;
1042
+
1043
+ if (zoneState.overlayType !== null && zoneState.setting.power === 'OFF') inOffMode += 1;
1044
+
1045
+ if (zoneState.overlayType !== null && zoneState.setting.power === 'ON' && zoneState.overlay.termination)
1046
+ inManualMode += 1;
1047
+ }
1048
+ }
1049
+
1050
+ //CentralSwitch
1051
+ const centralSwitchAccessory = accessories.filter(
1052
+ (acc) => acc && acc.displayName === acc.context.config.homeName + ' Central Switch'
1053
+ );
1054
+
1055
+ if (centralSwitchAccessory.length) {
1056
+ centralSwitchAccessory[0].services.forEach((service) => {
1057
+ if (service.subtype === 'Central') {
1058
+ let serviceSwitch = centralSwitchAccessory[0].getServiceById(api.hap.Service.Switch, service.subtype);
1059
+ let characteristicOn = api.hap.Characteristic.On;
1060
+ let characteristicAuto = api.hap.Characteristic.AutoThermostats;
1061
+ let characteristicOff = api.hap.Characteristic.OfflineThermostats;
1062
+ let characteristicManual = api.hap.Characteristic.ManualThermostats;
1063
+
1064
+ let state = (inManualMode || inAutoMode) !== 0;
1065
+
1066
+ serviceSwitch.getCharacteristic(characteristicAuto).updateValue(inAutoMode);
1067
+
1068
+ serviceSwitch.getCharacteristic(characteristicManual).updateValue(inManualMode);
1069
+
1070
+ serviceSwitch.getCharacteristic(characteristicOff).updateValue(inOffMode);
1071
+
1072
+ serviceSwitch.getCharacteristic(characteristicOn).updateValue(state);
1073
+ }
1074
+ });
1075
+ }
1076
+ }
1077
+ }
1078
+
1079
+ async function updateMobileDevices() {
1080
+ if (!settingState) {
1081
+ Logger.debug('Polling MobileDevices...', config.homeName);
1082
+
1083
+ const mobileDevices = await tado.getMobileDevices(config.homeId);
1084
+
1085
+ const userAccessories = accessories.filter(
1086
+ (user) =>
1087
+ user &&
1088
+ user.context.config.subtype.includes('presence') &&
1089
+ user.displayName !== user.context.config.homeName + ' Anyone'
1090
+ );
1091
+
1092
+ const anyone = accessories.filter(
1093
+ (user) =>
1094
+ user &&
1095
+ user.context.config.subtype.includes('presence') &&
1096
+ user.displayName === user.context.config.homeName + ' Anyone'
1097
+ );
1098
+
1099
+ let activeUser = 0;
1100
+
1101
+ mobileDevices.forEach((device) => {
1102
+ userAccessories.forEach((acc) => {
1103
+ if (acc.context.config.homeName + ' ' + device.name === acc.displayName) {
1104
+ let atHome = device.location && device.location.atHome ? 1 : 0;
1105
+
1106
+ if (atHome) activeUser += 1;
1107
+
1108
+ let service =
1109
+ acc.getService(api.hap.Service.MotionSensor) || acc.getService(api.hap.Service.OccupancySensor);
1110
+
1111
+ let characteristic = service.testCharacteristic(api.hap.Characteristic.MotionDetected)
1112
+ ? api.hap.Characteristic.MotionDetected
1113
+ : api.hap.Characteristic.OccupancyDetected;
1114
+
1115
+ service.getCharacteristic(characteristic).updateValue(atHome);
1116
+ }
1117
+ });
1118
+ });
1119
+
1120
+ if (anyone.length) {
1121
+ let service =
1122
+ anyone[0].getService(api.hap.Service.MotionSensor) || anyone[0].getService(api.hap.Service.OccupancySensor);
1123
+
1124
+ let characteristic = service.testCharacteristic(api.hap.Characteristic.MotionDetected)
1125
+ ? api.hap.Characteristic.MotionDetected
1126
+ : api.hap.Characteristic.OccupancyDetected;
1127
+
1128
+ service.getCharacteristic(characteristic).updateValue(activeUser ? 1 : 0);
1129
+ }
1130
+ }
1131
+
1132
+ return;
1133
+ }
1134
+
1135
+ async function updateWeather() {
1136
+ if (!settingState) {
1137
+ const weatherTemperatureAccessory = accessories.filter(
1138
+ (acc) => acc && acc.displayName === acc.context.config.homeName + ' Weather'
1139
+ );
1140
+
1141
+ const solarIntensityAccessory = accessories.filter(
1142
+ (acc) => acc && acc.displayName === acc.context.config.homeName + ' Solar Intensity'
1143
+ );
1144
+
1145
+ if (weatherTemperatureAccessory.length || solarIntensityAccessory.length) {
1146
+ Logger.debug('Polling Weather...', config.homeName);
1147
+
1148
+ const weather = await tado.getWeather(config.homeId);
1149
+
1150
+ if (weatherTemperatureAccessory.length && weather.outsideTemperature) {
1151
+ let tempUnit = config.temperatureUnit;
1152
+ let service = weatherTemperatureAccessory[0].getService(api.hap.Service.TemperatureSensor);
1153
+ let characteristic = api.hap.Characteristic.CurrentTemperature;
1154
+
1155
+ let temp =
1156
+ tempUnit === 'FAHRENHEIT' ? weather.outsideTemperature.fahrenheit : weather.outsideTemperature.celsius;
1157
+
1158
+ service.getCharacteristic(characteristic).updateValue(temp);
1159
+ }
1160
+
1161
+ if (solarIntensityAccessory.length && weather.solarIntensity) {
1162
+ let state = weather.solarIntensity.percentage !== 0;
1163
+ let brightness = weather.solarIntensity.percentage;
1164
+
1165
+ solarIntensityAccessory[0].context.lightBulbState = state;
1166
+ solarIntensityAccessory[0].context.lightBulbBrightness = brightness;
1167
+
1168
+ let serviceLightbulb = solarIntensityAccessory[0].getService(api.hap.Service.Lightbulb);
1169
+ let serviceLightsensor = solarIntensityAccessory[0].getService(api.hap.Service.LightSensor);
1170
+
1171
+ if (serviceLightbulb) {
1172
+ let characteristicOn = api.hap.Characteristic.On;
1173
+ let characteristicBrightness = api.hap.Characteristic.Brightness;
1174
+
1175
+ serviceLightbulb.getCharacteristic(characteristicOn).updateValue(state);
1176
+
1177
+ serviceLightbulb.getCharacteristic(characteristicBrightness).updateValue(brightness);
1178
+ } else {
1179
+ let characteristicLux = api.hap.Characteristic.CurrentAmbientLightLevel;
1180
+
1181
+ serviceLightsensor
1182
+ .getCharacteristic(characteristicLux)
1183
+ .updateValue(brightness ? brightness * 1000 : 0.0001);
1184
+ }
1185
+ }
1186
+ }
1187
+ }
1188
+
1189
+ return;
1190
+ }
1191
+
1192
+ async function updateRoomAirQuality() {
1193
+ if (!settingState) {
1194
+ Logger.debug('Polling Room AirQuality...', config.homeName);
1195
+
1196
+ const airQuality = await tado.getAirComfort(config.homeId);
1197
+
1198
+ const heatAccessories = accessories.filter(
1199
+ (acc) =>
1200
+ (acc && acc.context.config.subtype === 'zone-thermostat') ||
1201
+ acc.context.config.subtype === 'zone-heatercooler'
1202
+ );
1203
+
1204
+ heatAccessories.forEach((acc) => {
1205
+ if (acc.context.config.airQuality) {
1206
+ airQuality.comfort.forEach((room) => {
1207
+ if (room.roomId === acc.context.config.zoneId && room.coordinate) {
1208
+ let state =
1209
+ room.coordinate.radial >= 0.8
1210
+ ? 5
1211
+ : room.coordinate.radial >= 0.6
1212
+ ? 4
1213
+ : room.coordinate.radial >= 0.4
1214
+ ? 3
1215
+ : room.coordinate.radial >= 0.2
1216
+ ? 2
1217
+ : room.coordinate.radial >= 0
1218
+ ? 1
1219
+ : 0;
1220
+
1221
+ let service = acc.getService(api.hap.Service.Thermostat) || acc.getService(api.hap.Service.HeaterCooler);
1222
+ let characteristic = api.hap.Characteristic.AirQuality;
1223
+
1224
+ service.getCharacteristic(characteristic).updateValue(state);
1225
+ }
1226
+ });
1227
+ }
1228
+ });
1229
+ }
1230
+ }
1231
+
1232
+ async function updateAirQuality() {
1233
+ if (!settingState) {
1234
+ const airQualityAccessory = accessories.filter(
1235
+ (acc) => acc && acc.displayName === acc.context.config.homeName + ' Air Quality'
1236
+ );
1237
+
1238
+ if (airQualityAccessory.length) {
1239
+ Logger.debug('Polling AirQuality...', config.homeName);
1240
+
1241
+ const airQuality = await tado.getWeatherAirComfort(
1242
+ config.homeId,
1243
+ config.geolocation.longitude,
1244
+ config.geolocation.latitude
1245
+ );
1246
+
1247
+ let service = airQualityAccessory[0].getService(api.hap.Service.AirQualitySensor);
1248
+
1249
+ let characteristicAqi = api.hap.Characteristic.AirQuality;
1250
+ let characteristicPm10 = api.hap.Characteristic.PM10Density;
1251
+ let characteristicPm25 = api.hap.Characteristic.PM2_5Density;
1252
+ let characteristicNdd = api.hap.Characteristic.NitrogenDioxideDensity;
1253
+ let characteristicOd = api.hap.Characteristic.OzoneDensity;
1254
+ let characteristicSdd = api.hap.Characteristic.SulphurDioxideDensity;
1255
+ let characteristicCo = api.hap.Characteristic.CarbonMonoxideLevel;
1256
+
1257
+ if (airQuality.outdoorQuality) {
1258
+ let returnPol = (target) =>
1259
+ airQuality.outdoorQuality.pollutants.filter((pol) => pol && pol.scientificName.includes(target));
1260
+
1261
+ let aqi =
1262
+ airQuality.outdoorQuality.aqi.value >= 80
1263
+ ? 1
1264
+ : airQuality.outdoorQuality.aqi.value >= 60
1265
+ ? 2
1266
+ : airQuality.outdoorQuality.aqi.value >= 40
1267
+ ? 3
1268
+ : airQuality.outdoorQuality.aqi.value >= 20
1269
+ ? 4
1270
+ : airQuality.outdoorQuality.aqi.value >= 0
1271
+ ? 5
1272
+ : 0;
1273
+
1274
+ let pm10 = returnPol('PM<sub>10</sub>')[0].concentration.value;
1275
+ let pm25 = returnPol('PM<sub>2.5</sub>')[0].concentration.value;
1276
+ let ndd = returnPol('NO<sub>2</sub>')[0].concentration.value;
1277
+ let od = returnPol('O<sub>3</sub>')[0].concentration.value;
1278
+ let sdd = returnPol('SO<sub>2</sub>')[0].concentration.value;
1279
+ let co = returnPol('CO')[0].concentration.value;
1280
+
1281
+ if (!isNaN(aqi)) service.getCharacteristic(characteristicAqi).updateValue(aqi);
1282
+
1283
+ if (!isNaN(pm10)) service.getCharacteristic(characteristicPm10).updateValue(pm10);
1284
+
1285
+ if (!isNaN(pm25)) service.getCharacteristic(characteristicPm25).updateValue(pm25);
1286
+
1287
+ if (!isNaN(ndd)) service.getCharacteristic(characteristicNdd).updateValue(ndd * 1.9123);
1288
+
1289
+ if (!isNaN(od)) service.getCharacteristic(characteristicOd).updateValue(od * 1.9954);
1290
+
1291
+ if (!isNaN(sdd)) service.getCharacteristic(characteristicSdd).updateValue(sdd * 2.6647);
1292
+
1293
+ if (!isNaN(co)) service.getCharacteristic(characteristicCo).updateValue(co / 1000); //ppb to ppm
1294
+ }
1295
+ }
1296
+ }
1297
+
1298
+ return;
1299
+ }
1300
+
1301
+ async function updatePresence() {
1302
+ if (!settingState) {
1303
+ const presenceLockAccessory = accessories.filter(
1304
+ (acc) => acc && acc.displayName === acc.context.config.homeName + ' Presence Lock'
1305
+ );
1306
+
1307
+ if (presenceLockAccessory.length) {
1308
+ Logger.debug('Polling PresenceLock...', config.homeName);
1309
+
1310
+ const presenceLock = await tado.getState(config.homeId);
1311
+
1312
+ /*
1313
+ 0: Home | true
1314
+ 1: Away | true
1315
+ 3: Off | false
1316
+ */
1317
+
1318
+ let state = presenceLock.presenceLocked ? (presenceLock.presence === 'AWAY' ? 1 : 0) : 3;
1319
+
1320
+ let serviceSecurity = presenceLockAccessory[0].getService(api.hap.Service.SecuritySystem);
1321
+ let serviceHomeSwitch = presenceLockAccessory[0].getServiceById(api.hap.Service.Switch, 'HomeSwitch');
1322
+ let serviceAwaySwitch = presenceLockAccessory[0].getServiceById(api.hap.Service.Switch, 'AwaySwitch');
1323
+
1324
+ if (serviceSecurity) {
1325
+ let characteristicCurrent = api.hap.Characteristic.SecuritySystemCurrentState;
1326
+ let characteristicTarget = api.hap.Characteristic.SecuritySystemTargetState;
1327
+
1328
+ serviceSecurity.getCharacteristic(characteristicCurrent).updateValue(state);
1329
+
1330
+ serviceSecurity.getCharacteristic(characteristicTarget).updateValue(state);
1331
+ } else if (serviceHomeSwitch || serviceAwaySwitch) {
1332
+ let characteristicOn = api.hap.Characteristic.On;
1333
+
1334
+ let homeState = !state ? true : false;
1335
+
1336
+ let awayState = state === 1 ? true : false;
1337
+
1338
+ serviceAwaySwitch.getCharacteristic(characteristicOn).updateValue(awayState);
1339
+
1340
+ serviceHomeSwitch.getCharacteristic(characteristicOn).updateValue(homeState);
1341
+ }
1342
+ }
1343
+ }
1344
+ }
1345
+
1346
+ async function updateRunningTime() {
1347
+ if (!settingState) {
1348
+ const centralSwitchAccessory = accessories.filter(
1349
+ (acc) => acc && acc.displayName === acc.context.config.homeName + ' Central Switch'
1350
+ );
1351
+
1352
+ if (centralSwitchAccessory.length) {
1353
+ Logger.debug('Polling RunningTime...', config.homeName);
1354
+
1355
+ let periods = ['days', 'months', 'years'];
1356
+
1357
+ for (const period of periods) {
1358
+ let fromDate =
1359
+ period === 'days'
1360
+ ? moment().format('YYYY-MM-DD')
1361
+ : period === 'months'
1362
+ ? moment().subtract(1, 'days').subtract(1, period).format('YYYY-MM-DD')
1363
+ : moment().add(1, 'months').startOf('month').subtract(1, period).format('YYYY-MM-DD');
1364
+
1365
+ let toDate = period === 'years' ? moment().format('YYYY-MM-DD') : false;
1366
+
1367
+ let time = period.substring(0, period.length - 1);
1368
+
1369
+ const runningTime = await tado.getRunningTime(config.homeId, time, fromDate, toDate);
1370
+
1371
+ if (runningTime && runningTime.summary) {
1372
+ let summaryInHours = runningTime.summary.totalRunningTimeInSeconds / 3600;
1373
+
1374
+ let serviceSwitch = centralSwitchAccessory[0].getServiceById(api.hap.Service.Switch, 'Central');
1375
+ let characteristic =
1376
+ period === 'years'
1377
+ ? api.hap.Characteristic.OverallHeatYear
1378
+ : period === 'months'
1379
+ ? api.hap.Characteristic.OverallHeatMonth
1380
+ : api.hap.Characteristic.OverallHeatDay;
1381
+
1382
+ serviceSwitch.getCharacteristic(characteristic).updateValue(summaryInHours);
1383
+ }
1384
+
1385
+ await timeout(500);
1386
+ }
1387
+ }
1388
+ }
1389
+
1390
+ return;
1391
+ }
1392
+
1393
+ async function updateDevices() {
1394
+ if (!settingState) {
1395
+ Logger.debug('Polling Devices...', config.homeName);
1396
+
1397
+ const devices = await tado.getDevices(config.homeId);
1398
+
1399
+ const childLockAccessories = accessories.filter(
1400
+ (acc) => acc && acc.context.config.subtype === 'extra-childswitch'
1401
+ );
1402
+
1403
+ devices.forEach((device) => {
1404
+ childLockAccessories[0].services.forEach((service) => {
1405
+ if (device.serialNo === service.subtype) {
1406
+ let serviceChildLock = childLockAccessories[0].getServiceById(api.hap.Service.Switch, service.subtype);
1407
+ let characteristic = api.hap.Characteristic.On;
1408
+
1409
+ let childLockEnabled = device.childLockEnabled || false;
1410
+
1411
+ serviceChildLock.getCharacteristic(characteristic).updateValue(childLockEnabled);
1412
+ }
1413
+ });
1414
+ });
1415
+ }
1416
+
1417
+ return;
1418
+ }
1419
+
1420
+ function errorHandler(err) {
1421
+ let error;
1422
+
1423
+ if (err.options)
1424
+ Logger.debug(
1425
+ 'API request ' + err.options.method + ' ' + err.options.url.pathname + ' <error> ' + err.message,
1426
+ config.homeName
1427
+ );
1428
+
1429
+ if (err.response) {
1430
+ // The request was made and the server responded with a status code
1431
+ // that falls out of the range of 2xx
1432
+ if (err.response.data) {
1433
+ error = {
1434
+ status: err.response.status,
1435
+ message: err.response.statusText,
1436
+ data: err.response.data,
1437
+ };
1438
+ } else {
1439
+ error = {
1440
+ status: err.response.status,
1441
+ message: err.response.statusText,
1442
+ };
1443
+ }
1444
+ } else if (err.request) {
1445
+ error = {
1446
+ code: err.code,
1447
+ message: 'Cannot reach Tado. No response received.',
1448
+ };
1449
+ } else if (err.output && err.output.payload && Object.keys(err.output.payload).length) {
1450
+ //simple-oauth2 boom error
1451
+ error = err.output.payload;
1452
+ } else {
1453
+ // Something happened in setting up the request that triggered an Error
1454
+ error = err;
1455
+ }
1456
+
1457
+ Logger.error(error, config.homeName);
1458
+
1459
+ return;
1460
+ }
1461
+
1462
+ return {
1463
+ getStates: getStates,
1464
+ setStates: setStates,
1465
+ changedStates: changedStates,
1466
+ };
1467
+ };