@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.
- package/CHANGELOG.md +152 -0
- package/LICENSE +21 -0
- package/README.md +560 -0
- package/config.schema.json +878 -0
- package/homebridge-ui/public/css/style.css +25 -0
- package/homebridge-ui/public/images/tado_logo.png +0 -0
- package/homebridge-ui/public/index.html +118 -0
- package/homebridge-ui/public/js/main.js +1582 -0
- package/homebridge-ui/public/js/modules/compareVersions.min.js +1 -0
- package/homebridge-ui/public/js/modules/jquery.min.js +2 -0
- package/homebridge-ui/public/js/modules/progressbar.min.js +6 -0
- package/homebridge-ui/public/js/progressbars.js +48 -0
- package/homebridge-ui/public/js/schema.js +864 -0
- package/homebridge-ui/server.js +80 -0
- package/images/tado_logo.png +0 -0
- package/index.js +14 -0
- package/package.json +66 -0
- package/src/accessories/airquality.js +56 -0
- package/src/accessories/contact.js +124 -0
- package/src/accessories/faucet.js +63 -0
- package/src/accessories/heatercooler.js +333 -0
- package/src/accessories/humidity.js +90 -0
- package/src/accessories/lightbulb.js +59 -0
- package/src/accessories/lightsensor.js +40 -0
- package/src/accessories/motion.js +79 -0
- package/src/accessories/occupancy.js +45 -0
- package/src/accessories/security.js +79 -0
- package/src/accessories/switch.js +261 -0
- package/src/accessories/temperature.js +95 -0
- package/src/accessories/thermostat.js +337 -0
- package/src/helper/handler.js +1467 -0
- package/src/helper/logger.js +51 -0
- package/src/helper/telegram.js +60 -0
- package/src/platform.js +337 -0
- package/src/tado/tado-api.js +404 -0
- package/src/tado/tado-config.js +1032 -0
- package/src/types/custom.js +264 -0
- 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
|
+
};
|