@homebridge-plugins/homebridge-tado 8.2.0 → 8.3.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.schema.json +6 -0
- package/homebridge-ui/public/js/main.js +3 -0
- package/homebridge-ui/public/js/schema.js +6 -0
- package/package.json +2 -2
- package/src/accessories/contact.js +7 -8
- package/src/accessories/heatercooler.js +7 -8
- package/src/accessories/humidity.js +7 -8
- package/src/accessories/motion.js +8 -9
- package/src/accessories/temperature.js +7 -8
- package/src/accessories/thermostat.js +7 -8
- package/src/helper/handler.js +53 -8
- package/src/platform.js +1 -1
- package/src/tado/tado-api.js +53 -2
package/config.schema.json
CHANGED
|
@@ -563,6 +563,11 @@
|
|
|
563
563
|
"title": "Skip Authentication",
|
|
564
564
|
"type": "boolean",
|
|
565
565
|
"description": "Optional: Skip authentication for tado api."
|
|
566
|
+
},
|
|
567
|
+
"skipFakeGatoHistory": {
|
|
568
|
+
"title": "Skip FakeGato History Service",
|
|
569
|
+
"type": "boolean",
|
|
570
|
+
"description": "Optional: Skip creation of FakeGato history service."
|
|
566
571
|
}
|
|
567
572
|
}
|
|
568
573
|
},
|
|
@@ -571,6 +576,7 @@
|
|
|
571
576
|
"debug",
|
|
572
577
|
"tadoApiUrl",
|
|
573
578
|
"skipAuth",
|
|
579
|
+
"skipFakeGatoHistory",
|
|
574
580
|
{
|
|
575
581
|
"key": "homes",
|
|
576
582
|
"type": "array",
|
|
@@ -134,6 +134,7 @@ async function createCustomSchema(home) {
|
|
|
134
134
|
debug: pluginConfig[0].debug,
|
|
135
135
|
tadoApiUrl: pluginConfig[0].tadoApiUrl,
|
|
136
136
|
skipAuth: pluginConfig[0].skipAuth,
|
|
137
|
+
skipFakeGatoHistory: pluginConfig[0].skipFakeGatoHistory,
|
|
137
138
|
homes: home
|
|
138
139
|
});
|
|
139
140
|
|
|
@@ -143,6 +144,7 @@ async function createCustomSchema(home) {
|
|
|
143
144
|
pluginConfig[0].debug = config.debug;
|
|
144
145
|
pluginConfig[0].tadoApiUrl = config.tadoApiUrl;
|
|
145
146
|
pluginConfig[0].skipAuth = config.skipAuth;
|
|
147
|
+
pluginConfig[0].skipFakeGatoHistory = config.skipFakeGatoHistory;
|
|
146
148
|
pluginConfig[0].homes = pluginConfig[0].homes.map(myHome => {
|
|
147
149
|
if (myHome.name === config.homes.name) {
|
|
148
150
|
myHome = config.homes;
|
|
@@ -285,6 +287,7 @@ async function removeDeviceFromConfig(name) {
|
|
|
285
287
|
delete pluginConfig[0].debug;
|
|
286
288
|
delete pluginConfig[0].tadoApiUrl;
|
|
287
289
|
delete pluginConfig[0].skipAuth;
|
|
290
|
+
delete pluginConfig[0].skipFakeGatoHistory;
|
|
288
291
|
}
|
|
289
292
|
|
|
290
293
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
@@ -555,6 +555,11 @@ const schema = {
|
|
|
555
555
|
'title': 'Skip Authentication',
|
|
556
556
|
'type': 'boolean',
|
|
557
557
|
'description': 'Optional: Skip authentication for tado api.'
|
|
558
|
+
},
|
|
559
|
+
'skipFakeGatoHistory': {
|
|
560
|
+
'title': 'Skip FakeGato History Service',
|
|
561
|
+
'type': 'boolean',
|
|
562
|
+
'description': 'Optional: Skip creation of FakeGato history service.'
|
|
558
563
|
}
|
|
559
564
|
},
|
|
560
565
|
'layout': [
|
|
@@ -562,6 +567,7 @@ const schema = {
|
|
|
562
567
|
'debug',
|
|
563
568
|
'tadoApiUrl',
|
|
564
569
|
'skipAuth',
|
|
570
|
+
'skipFakeGatoHistory',
|
|
565
571
|
'homes.name',
|
|
566
572
|
'homes.polling',
|
|
567
573
|
'homes.temperatureUnit',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homebridge-plugins/homebridge-tado",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.3.0-beta.0",
|
|
4
4
|
"description": "Homebridge plugin for controlling tado° devices.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -51,4 +51,4 @@
|
|
|
51
51
|
"globals": "^16.4.0",
|
|
52
52
|
"prettier": "^3.6.2"
|
|
53
53
|
}
|
|
54
|
-
}
|
|
54
|
+
}
|
|
@@ -86,11 +86,11 @@ export default class ContactAccessory {
|
|
|
86
86
|
.updateValue(this.accessory.context.timesOpened);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
this.historyService = new this.FakeGatoHistoryService('door', this.accessory, {
|
|
89
|
+
this.historyService = this.FakeGatoHistoryService ? new this.FakeGatoHistoryService('door', this.accessory, {
|
|
90
90
|
storage: 'fs',
|
|
91
91
|
path: this.api.user.storagePath(),
|
|
92
92
|
disableTimer: true,
|
|
93
|
-
});
|
|
93
|
+
}) : undefined;
|
|
94
94
|
|
|
95
95
|
await timeout(250); //wait for historyService to load
|
|
96
96
|
|
|
@@ -101,19 +101,18 @@ export default class ContactAccessory {
|
|
|
101
101
|
this.deviceHandler.changedStates.bind(this, this.accessory, this.historyService, this.accessory.displayName)
|
|
102
102
|
);
|
|
103
103
|
|
|
104
|
-
this.
|
|
104
|
+
if (!this.refreshHistoryHandlerRegistered) {
|
|
105
|
+
this.deviceHandler.refreshHistoryHandlers.push(() => this.refreshHistory(service));
|
|
106
|
+
this.refreshHistoryHandlerRegistered = true;
|
|
107
|
+
}
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
refreshHistory(service) {
|
|
108
111
|
let state = service.getCharacteristic(this.api.hap.Characteristic.ContactSensorState).value;
|
|
109
112
|
|
|
110
|
-
this.historyService.addEntry({
|
|
113
|
+
if (this.historyService) this.historyService.addEntry({
|
|
111
114
|
time: moment().unix(),
|
|
112
115
|
status: state ? 1 : 0,
|
|
113
116
|
});
|
|
114
|
-
|
|
115
|
-
setTimeout(() => {
|
|
116
|
-
this.refreshHistory(service);
|
|
117
|
-
}, 10 * 60 * 1000);
|
|
118
117
|
}
|
|
119
118
|
}
|
|
@@ -248,11 +248,11 @@ export default class HeaterCoolerAccessory {
|
|
|
248
248
|
if (service.testCharacteristic(this.api.hap.Characteristic.RotationSpeed))
|
|
249
249
|
service.removeCharacteristic(service.getCharacteristic(this.api.hap.Characteristic.RotationSpeed));
|
|
250
250
|
|
|
251
|
-
this.historyService = new this.FakeGatoHistoryService('thermo', this.accessory, {
|
|
251
|
+
this.historyService = this.FakeGatoHistoryService ? new this.FakeGatoHistoryService('thermo', this.accessory, {
|
|
252
252
|
storage: 'fs',
|
|
253
253
|
path: this.api.user.storagePath(),
|
|
254
254
|
disableTimer: true,
|
|
255
|
-
});
|
|
255
|
+
}) : undefined;
|
|
256
256
|
|
|
257
257
|
await timeout(250); //wait for historyService to load
|
|
258
258
|
|
|
@@ -324,7 +324,10 @@ export default class HeaterCoolerAccessory {
|
|
|
324
324
|
this.deviceHandler.changedStates.bind(this, this.accessory, this.historyService, this.accessory.displayName)
|
|
325
325
|
);
|
|
326
326
|
|
|
327
|
-
this.
|
|
327
|
+
if (!this.refreshHistoryHandlerRegistered) {
|
|
328
|
+
this.deviceHandler.refreshHistoryHandlers.push(() => this.refreshHistory(service));
|
|
329
|
+
this.refreshHistoryHandlerRegistered = true;
|
|
330
|
+
}
|
|
328
331
|
}
|
|
329
332
|
|
|
330
333
|
refreshHistory(service) {
|
|
@@ -338,15 +341,11 @@ export default class HeaterCoolerAccessory {
|
|
|
338
341
|
: 0;
|
|
339
342
|
|
|
340
343
|
//Thermo
|
|
341
|
-
this.historyService.addEntry({
|
|
344
|
+
if (this.historyService) this.historyService.addEntry({
|
|
342
345
|
time: moment().unix(),
|
|
343
346
|
currentTemp: currentTemp,
|
|
344
347
|
setTemp: targetTemp,
|
|
345
348
|
valvePosition: valvePos,
|
|
346
349
|
});
|
|
347
|
-
|
|
348
|
-
setTimeout(() => {
|
|
349
|
-
this.refreshHistory(service);
|
|
350
|
-
}, 10 * 60 * 1000);
|
|
351
350
|
}
|
|
352
351
|
}
|
|
@@ -50,11 +50,11 @@ export default class HumidityAccessory {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
this.historyService = new this.FakeGatoHistoryService('room', this.accessory, {
|
|
53
|
+
this.historyService = this.FakeGatoHistoryService ? new this.FakeGatoHistoryService('room', this.accessory, {
|
|
54
54
|
storage: 'fs',
|
|
55
55
|
path: this.api.user.storagePath(),
|
|
56
56
|
disableTimer: true,
|
|
57
|
-
});
|
|
57
|
+
}) : undefined;
|
|
58
58
|
|
|
59
59
|
await timeout(250); //wait for historyService to load
|
|
60
60
|
|
|
@@ -65,21 +65,20 @@ export default class HumidityAccessory {
|
|
|
65
65
|
this.deviceHandler.changedStates.bind(this, this.accessory, this.historyService, this.accessory.displayName)
|
|
66
66
|
);
|
|
67
67
|
|
|
68
|
-
this.
|
|
68
|
+
if (!this.refreshHistoryHandlerRegistered) {
|
|
69
|
+
this.deviceHandler.refreshHistoryHandlers.push(() => this.refreshHistory(service));
|
|
70
|
+
this.refreshHistoryHandlerRegistered = true;
|
|
71
|
+
}
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
refreshHistory(service) {
|
|
72
75
|
let state = service.getCharacteristic(this.api.hap.Characteristic.CurrentRelativeHumidity).value;
|
|
73
76
|
|
|
74
|
-
this.historyService.addEntry({
|
|
77
|
+
if (this.historyService) this.historyService.addEntry({
|
|
75
78
|
time: moment().unix(),
|
|
76
79
|
temp: 0,
|
|
77
80
|
humidity: state,
|
|
78
81
|
ppm: 0,
|
|
79
82
|
});
|
|
80
|
-
|
|
81
|
-
setTimeout(() => {
|
|
82
|
-
this.refreshHistory(service);
|
|
83
|
-
}, 10 * 60 * 1000);
|
|
84
83
|
}
|
|
85
84
|
}
|
|
@@ -41,11 +41,11 @@ export default class MotionAccessory {
|
|
|
41
41
|
if (!service.testCharacteristic(this.api.hap.Characteristic.LastActivation))
|
|
42
42
|
service.addCharacteristic(this.api.hap.Characteristic.LastActivation);
|
|
43
43
|
|
|
44
|
-
this.historyService = new this.FakeGatoHistoryService('motion', this.accessory, {
|
|
44
|
+
this.historyService = this.FakeGatoHistoryService ? new this.FakeGatoHistoryService('motion', this.accessory, {
|
|
45
45
|
storage: 'fs',
|
|
46
46
|
path: this.api.user.storagePath(),
|
|
47
47
|
disableTimer: true,
|
|
48
|
-
});
|
|
48
|
+
}) : undefined;
|
|
49
49
|
|
|
50
50
|
await timeout(250); //wait for historyService to load
|
|
51
51
|
|
|
@@ -56,19 +56,18 @@ export default class MotionAccessory {
|
|
|
56
56
|
this.deviceHandler.changedStates.bind(this, this.accessory, this.historyService, this.accessory.displayName)
|
|
57
57
|
);
|
|
58
58
|
|
|
59
|
-
this.
|
|
59
|
+
if (!this.refreshHistoryHandlerRegistered) {
|
|
60
|
+
this.deviceHandler.refreshHistoryHandlers.push(() => this.refreshHistory(service));
|
|
61
|
+
this.refreshHistoryHandlerRegistered = true;
|
|
62
|
+
}
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
refreshHistory(service) {
|
|
63
66
|
let state = service.getCharacteristic(this.api.hap.Characteristic.MotionDetected).value;
|
|
64
67
|
|
|
65
|
-
this.historyService.addEntry({
|
|
68
|
+
if (this.historyService) this.historyService.addEntry({
|
|
66
69
|
time: moment().unix(),
|
|
67
70
|
status: state ? 1 : 0,
|
|
68
71
|
});
|
|
69
|
-
|
|
70
|
-
setTimeout(() => {
|
|
71
|
-
this.refreshHistory(service);
|
|
72
|
-
}, 10 * 60 * 1000);
|
|
73
72
|
}
|
|
74
73
|
}
|
|
@@ -55,11 +55,11 @@ export default class TemperatureAccessory {
|
|
|
55
55
|
maxValue: 100,
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
this.historyService = new this.FakeGatoHistoryService('room', this.accessory, {
|
|
58
|
+
this.historyService = this.FakeGatoHistoryService ? new this.FakeGatoHistoryService('room', this.accessory, {
|
|
59
59
|
storage: 'fs',
|
|
60
60
|
path: this.api.user.storagePath(),
|
|
61
61
|
disableTimer: true,
|
|
62
|
-
});
|
|
62
|
+
}) : undefined;
|
|
63
63
|
|
|
64
64
|
await timeout(250); //wait for historyService to load
|
|
65
65
|
|
|
@@ -70,21 +70,20 @@ export default class TemperatureAccessory {
|
|
|
70
70
|
this.deviceHandler.changedStates.bind(this, this.accessory, this.historyService, this.accessory.displayName)
|
|
71
71
|
);
|
|
72
72
|
|
|
73
|
-
this.
|
|
73
|
+
if (!this.refreshHistoryHandlerRegistered) {
|
|
74
|
+
this.deviceHandler.refreshHistoryHandlers.push(() => this.refreshHistory(service));
|
|
75
|
+
this.refreshHistoryHandlerRegistered = true;
|
|
76
|
+
}
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
refreshHistory(service) {
|
|
77
80
|
let state = service.getCharacteristic(this.api.hap.Characteristic.CurrentTemperature).value;
|
|
78
81
|
|
|
79
|
-
this.historyService.addEntry({
|
|
82
|
+
if (this.historyService) this.historyService.addEntry({
|
|
80
83
|
time: moment().unix(),
|
|
81
84
|
temp: state,
|
|
82
85
|
humidity: 0,
|
|
83
86
|
ppm: 0,
|
|
84
87
|
});
|
|
85
|
-
|
|
86
|
-
setTimeout(() => {
|
|
87
|
-
this.refreshHistory(service);
|
|
88
|
-
}, 10 * 60 * 1000);
|
|
89
88
|
}
|
|
90
89
|
}
|
|
@@ -180,11 +180,11 @@ export default class ThermostatAccessory {
|
|
|
180
180
|
if (!service.testCharacteristic(this.api.hap.Characteristic.ValvePosition))
|
|
181
181
|
service.addCharacteristic(this.api.hap.Characteristic.ValvePosition);
|
|
182
182
|
|
|
183
|
-
this.historyService = new this.FakeGatoHistoryService('thermo', this.accessory, {
|
|
183
|
+
this.historyService = this.FakeGatoHistoryService ? new this.FakeGatoHistoryService('thermo', this.accessory, {
|
|
184
184
|
storage: 'fs',
|
|
185
185
|
path: this.api.user.storagePath(),
|
|
186
186
|
disableTimer: true,
|
|
187
|
-
});
|
|
187
|
+
}) : undefined;
|
|
188
188
|
|
|
189
189
|
await timeout(250); //wait for historyService to load
|
|
190
190
|
|
|
@@ -254,7 +254,10 @@ export default class ThermostatAccessory {
|
|
|
254
254
|
this.deviceHandler.changedStates.bind(this, this.accessory, this.historyService, this.accessory.displayName)
|
|
255
255
|
);
|
|
256
256
|
|
|
257
|
-
this.
|
|
257
|
+
if (!this.refreshHistoryHandlerRegistered) {
|
|
258
|
+
this.deviceHandler.refreshHistoryHandlers.push(() => this.refreshHistory(service));
|
|
259
|
+
this.refreshHistoryHandlerRegistered = true;
|
|
260
|
+
}
|
|
258
261
|
}
|
|
259
262
|
|
|
260
263
|
refreshHistory(service) {
|
|
@@ -268,16 +271,12 @@ export default class ThermostatAccessory {
|
|
|
268
271
|
? Math.round(targetTemp - currentTemp >= 5 ? 100 : (targetTemp - currentTemp) * 20)
|
|
269
272
|
: 0;
|
|
270
273
|
|
|
271
|
-
this.historyService.addEntry({
|
|
274
|
+
if (this.historyService) this.historyService.addEntry({
|
|
272
275
|
time: moment().unix(),
|
|
273
276
|
currentTemp: currentTemp,
|
|
274
277
|
setTemp: targetTemp,
|
|
275
278
|
valvePosition: valvePos,
|
|
276
279
|
});
|
|
277
|
-
|
|
278
|
-
setTimeout(() => {
|
|
279
|
-
this.refreshHistory(service);
|
|
280
|
-
}, 10 * 60 * 1000);
|
|
281
280
|
}
|
|
282
281
|
|
|
283
282
|
async changeUnit(service, value) {
|
package/src/helper/handler.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import Logger from '../helper/logger.js';
|
|
2
2
|
import moment from 'moment';
|
|
3
|
+
import { writeFile } from 'fs/promises';
|
|
4
|
+
import { join } from "path";
|
|
3
5
|
|
|
4
6
|
var settingState = false;
|
|
5
7
|
var delayTimer = {};
|
|
6
8
|
|
|
7
9
|
const timeout = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
10
|
+
const aRefreshHistoryHandlers = [];
|
|
8
11
|
|
|
9
12
|
export default (api, accessories, config, tado, telegram) => {
|
|
13
|
+
const storagePath = api.user.storagePath();
|
|
14
|
+
|
|
10
15
|
async function setStates(accessory, accs, target, value) {
|
|
11
16
|
accessories = accs.filter((acc) => acc && acc.context.config.homeName === config.homeName);
|
|
12
17
|
|
|
@@ -567,7 +572,7 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
567
572
|
? Math.round(targetTemp - currentTemp >= 5 ? 100 : (targetTemp - currentTemp) * 20)
|
|
568
573
|
: 0;
|
|
569
574
|
|
|
570
|
-
historyService.addEntry({
|
|
575
|
+
if (historyService) historyService.addEntry({
|
|
571
576
|
time: moment().unix(),
|
|
572
577
|
currentTemp: currentTemp,
|
|
573
578
|
setTemp: targetTemp,
|
|
@@ -595,7 +600,7 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
595
600
|
? Math.round(targetTemp - currentTemp >= 5 ? 100 : (targetTemp - currentTemp) * 20)
|
|
596
601
|
: 0;
|
|
597
602
|
|
|
598
|
-
historyService.addEntry({
|
|
603
|
+
if (historyService) historyService.addEntry({
|
|
599
604
|
time: moment().unix(),
|
|
600
605
|
currentTemp: currentTemp,
|
|
601
606
|
setTemp: targetTemp,
|
|
@@ -636,7 +641,7 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
636
641
|
.updateValue(openDuration);
|
|
637
642
|
}
|
|
638
643
|
|
|
639
|
-
historyService.addEntry({ time: moment().unix(), status: value.newValue ? 1 : 0 });
|
|
644
|
+
if (historyService) historyService.addEntry({ time: moment().unix(), status: value.newValue ? 1 : 0 });
|
|
640
645
|
|
|
641
646
|
let dest = value.newValue ? 'opened' : 'closed';
|
|
642
647
|
|
|
@@ -658,7 +663,7 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
658
663
|
.getCharacteristic(api.hap.Characteristic.LastActivation)
|
|
659
664
|
.updateValue(lastActivation);
|
|
660
665
|
|
|
661
|
-
historyService.addEntry({ time: moment().unix(), status: value.newValue ? 1 : 0 });
|
|
666
|
+
if (historyService) historyService.addEntry({ time: moment().unix(), status: value.newValue ? 1 : 0 });
|
|
662
667
|
}
|
|
663
668
|
|
|
664
669
|
let dest;
|
|
@@ -679,13 +684,13 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
679
684
|
|
|
680
685
|
case 'zone-temperature':
|
|
681
686
|
case 'weather-temperature': {
|
|
682
|
-
historyService.addEntry({ time: moment().unix(), temp: value.newValue, humidity: 0, ppm: 0 });
|
|
687
|
+
if (historyService) historyService.addEntry({ time: moment().unix(), temp: value.newValue, humidity: 0, ppm: 0 });
|
|
683
688
|
|
|
684
689
|
break;
|
|
685
690
|
}
|
|
686
691
|
|
|
687
692
|
case 'zone-humidity': {
|
|
688
|
-
historyService.addEntry({ time: moment().unix(), temp: 0, humidity: value.newValue, ppm: 0 });
|
|
693
|
+
if (historyService) historyService.addEntry({ time: moment().unix(), temp: 0, humidity: value.newValue, ppm: 0 });
|
|
689
694
|
|
|
690
695
|
break;
|
|
691
696
|
}
|
|
@@ -697,7 +702,32 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
697
702
|
}
|
|
698
703
|
}
|
|
699
704
|
|
|
705
|
+
async function refreshHistory(homeId, zoneStates) {
|
|
706
|
+
try {
|
|
707
|
+
const data = {};
|
|
708
|
+
data.counterData = await tado.getCounterData();
|
|
709
|
+
await writeFile(join(storagePath, "tado-counter.json"), JSON.stringify(data, null, 2), "utf-8");
|
|
710
|
+
} catch (error) {
|
|
711
|
+
Logger.error(`Error while updating tado counter file: ${error.message || error}`);
|
|
712
|
+
}
|
|
713
|
+
try {
|
|
714
|
+
const data = {};
|
|
715
|
+
data.zoneStates = zoneStates ?? {};
|
|
716
|
+
await writeFile(join(storagePath, `tado-states-${homeId}.json`), JSON.stringify(data, null, 2), "utf-8");
|
|
717
|
+
} catch (error) {
|
|
718
|
+
Logger.error(`Error while updating tado states file for home id ${homeId}: ${error.message || error}`);
|
|
719
|
+
}
|
|
720
|
+
try {
|
|
721
|
+
for (const fnRefreshHistory of aRefreshHistoryHandlers) {
|
|
722
|
+
fnRefreshHistory();
|
|
723
|
+
}
|
|
724
|
+
} catch (error) {
|
|
725
|
+
Logger.error(`Error while refreshing history: ${error.message || error}`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
700
729
|
async function getStates() {
|
|
730
|
+
let zoneStates = {};
|
|
701
731
|
try {
|
|
702
732
|
//ME
|
|
703
733
|
if (!config.homeId) await updateMe();
|
|
@@ -706,7 +736,7 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
706
736
|
if (!config.temperatureUnit) await updateHome();
|
|
707
737
|
|
|
708
738
|
//Zones
|
|
709
|
-
if (config.zones.length) await updateZones();
|
|
739
|
+
if (config.zones.length) zoneStates = await updateZones();
|
|
710
740
|
|
|
711
741
|
//MobileDevices
|
|
712
742
|
if (config.presence.length) await updateMobileDevices();
|
|
@@ -725,10 +755,23 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
725
755
|
} catch (err) {
|
|
726
756
|
errorHandler(err);
|
|
727
757
|
} finally {
|
|
758
|
+
refreshHistory(config.homeId, zoneStates);
|
|
728
759
|
setTimeout(() => {
|
|
729
760
|
getStates();
|
|
730
761
|
}, Math.max(config.polling, 300) * 1000);
|
|
731
762
|
}
|
|
763
|
+
|
|
764
|
+
//log tado api counter every hour
|
|
765
|
+
async function logCounter() {
|
|
766
|
+
try {
|
|
767
|
+
const iCounter = (await tado.getCounterData()).counter;
|
|
768
|
+
Logger.info(`Tado API counter: ${iCounter.toLocaleString('en-US')}`);
|
|
769
|
+
} catch (error) {
|
|
770
|
+
Logger.warn(`Failed to get Tado API counter: ${error.message || error}`);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
void logCounter();
|
|
774
|
+
setInterval(logCounter, 60 * 60 * 1000);
|
|
732
775
|
}
|
|
733
776
|
|
|
734
777
|
async function updateMe() {
|
|
@@ -773,6 +816,7 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
773
816
|
}
|
|
774
817
|
|
|
775
818
|
async function updateZones(idToUpdate) {
|
|
819
|
+
let zoneStates = {};
|
|
776
820
|
if (!settingState || idToUpdate !== undefined) {
|
|
777
821
|
Logger.debug('Polling Zones...', config.homeName);
|
|
778
822
|
|
|
@@ -823,7 +867,6 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
823
867
|
});
|
|
824
868
|
}
|
|
825
869
|
|
|
826
|
-
let zoneStates = {};
|
|
827
870
|
let zonesToUpdate = [];
|
|
828
871
|
if (idToUpdate !== undefined) {
|
|
829
872
|
zoneStates[idToUpdate] = await tado.getZoneState(config.homeId, idToUpdate);
|
|
@@ -1233,6 +1276,7 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
1233
1276
|
});
|
|
1234
1277
|
}
|
|
1235
1278
|
}
|
|
1279
|
+
return zoneStates = {};
|
|
1236
1280
|
}
|
|
1237
1281
|
|
|
1238
1282
|
async function updateMobileDevices() {
|
|
@@ -1513,5 +1557,6 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
1513
1557
|
getStates: getStates,
|
|
1514
1558
|
setStates: setStates,
|
|
1515
1559
|
changedStates: changedStates,
|
|
1560
|
+
refreshHistoryHandlers: aRefreshHistoryHandlers
|
|
1516
1561
|
};
|
|
1517
1562
|
};
|
package/src/platform.js
CHANGED
|
@@ -41,7 +41,7 @@ class TadoPlatform {
|
|
|
41
41
|
Logger.init(log, config.debug);
|
|
42
42
|
CustomTypes.registerWith(api.hap);
|
|
43
43
|
EveTypes.registerWith(api.hap);
|
|
44
|
-
FakeGatoHistoryService = fakeGatoHistory(api);
|
|
44
|
+
FakeGatoHistoryService = config.skipFakeGatoHistory ? undefined : fakeGatoHistory(api);
|
|
45
45
|
|
|
46
46
|
this.api = api;
|
|
47
47
|
this.accessories = [];
|
package/src/tado/tado-api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Logger from '../helper/logger.js';
|
|
2
2
|
import got from 'got';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import fs from 'fs/promises';
|
|
4
|
+
import fs, { access, readFile } from 'fs/promises';
|
|
5
5
|
|
|
6
6
|
const tado_url = "https://my.tado.com";
|
|
7
7
|
const tado_auth_url = "https://login.tado.com/oauth2";
|
|
@@ -12,6 +12,7 @@ export default class Tado {
|
|
|
12
12
|
this.tadoApiUrl = tadoApiUrl || tado_url;
|
|
13
13
|
this.customTadoApiUrlActive = !!tadoApiUrl;
|
|
14
14
|
this.skipAuth = skipAuth?.toString() === "true";
|
|
15
|
+
this.storagePath = storagePath;
|
|
15
16
|
this.name = name;
|
|
16
17
|
const usesExternalTokenFile = config.username?.toLowerCase().endsWith(".json");
|
|
17
18
|
this._tadoExternalTokenFilePath = usesExternalTokenFile ? config.username : undefined;
|
|
@@ -24,13 +25,59 @@ export default class Tado {
|
|
|
24
25
|
return (hash >>> 0).toString(36).padStart(7, '0');
|
|
25
26
|
};
|
|
26
27
|
this.username = usesExternalTokenFile ? undefined : config.username;
|
|
27
|
-
this._tadoInternalTokenFilePath = usesExternalTokenFile ? undefined : path.join(storagePath, `.tado-token-${fnSimpleHash(config.username)}.json`);
|
|
28
|
+
this._tadoInternalTokenFilePath = usesExternalTokenFile ? undefined : path.join(this.storagePath, `.tado-token-${fnSimpleHash(config.username)}.json`);
|
|
28
29
|
this._tadoApiClientId = tado_client_id;
|
|
29
30
|
this._tadoTokenPromise = undefined;
|
|
30
31
|
this._tadoAuthenticationCallback = undefined;
|
|
32
|
+
this._counterInitPromise = this._initCounter();
|
|
31
33
|
Logger.debug("API successfull initialized", this.name);
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
async _initCounter() {
|
|
37
|
+
let counterData;
|
|
38
|
+
try {
|
|
39
|
+
const sFilePath = path.join(this.storagePath, `tado-counter.json`);
|
|
40
|
+
await access(sFilePath);
|
|
41
|
+
const sData = (await readFile(sFilePath, "utf-8"));
|
|
42
|
+
counterData = JSON.parse(sData)?.counterData;
|
|
43
|
+
} catch (_err) {
|
|
44
|
+
//no counter data => ignore
|
|
45
|
+
}
|
|
46
|
+
this._counter = counterData?.counter ?? 0;
|
|
47
|
+
this._counterTimestamp = counterData?.counterTimestamp ?? new Date().toISOString();
|
|
48
|
+
this._checkCounterMidnightReset();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_checkCounterMidnightReset() {
|
|
52
|
+
const timezone = "Europe/Berlin";
|
|
53
|
+
const now = new Date();
|
|
54
|
+
const last = new Date(this._counterTimestamp || 0);
|
|
55
|
+
const formatDate = (date) => date.toLocaleDateString("en-US", { timeZone: timezone });
|
|
56
|
+
if (formatDate(now) !== formatDate(last)) {
|
|
57
|
+
this._counter = 0;
|
|
58
|
+
this._counterTimestamp = new Date().toISOString();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async _increaseCounter() {
|
|
63
|
+
try {
|
|
64
|
+
await this._counterInitPromise;
|
|
65
|
+
this._checkCounterMidnightReset();
|
|
66
|
+
this._counter++;
|
|
67
|
+
this._counterTimestamp = new Date().toISOString();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
Logger.warn(`Failed to increase tado api counter: ${error.message || error}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async getCounterData() {
|
|
74
|
+
await this._counterInitPromise;
|
|
75
|
+
return {
|
|
76
|
+
counter: this._counter,
|
|
77
|
+
counterTimestamp: this._counterTimestamp
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
34
81
|
async getToken() {
|
|
35
82
|
if (!this._tadoTokenPromise) {
|
|
36
83
|
this._tadoTokenPromise = this._getToken().finally(() => {
|
|
@@ -95,6 +142,7 @@ export default class Tado {
|
|
|
95
142
|
},
|
|
96
143
|
responseType: "json"
|
|
97
144
|
});
|
|
145
|
+
await this._increaseCounter();
|
|
98
146
|
const { access_token, refresh_token } = response.body;
|
|
99
147
|
if (!access_token || !refresh_token) throw new Error("Empty access/refresh token.");
|
|
100
148
|
await fs.writeFile(this._tadoInternalTokenFilePath, JSON.stringify({ access_token, refresh_token }));
|
|
@@ -114,6 +162,7 @@ export default class Tado {
|
|
|
114
162
|
},
|
|
115
163
|
responseType: "json"
|
|
116
164
|
});
|
|
165
|
+
await this._increaseCounter();
|
|
117
166
|
const { device_code, verification_uri_complete } = authResponse.body;
|
|
118
167
|
if (!device_code) throw new Error("Failed to retrieve device code.");
|
|
119
168
|
Logger.info(`Open the following URL in your browser, click "submit" and log in to your tado° account "${this.username}": ${verification_uri_complete}`);
|
|
@@ -131,6 +180,7 @@ export default class Tado {
|
|
|
131
180
|
},
|
|
132
181
|
responseType: "json"
|
|
133
182
|
});
|
|
183
|
+
await this._increaseCounter();
|
|
134
184
|
} catch (_error) {
|
|
135
185
|
//authentication still pending -> response code 400
|
|
136
186
|
}
|
|
@@ -211,6 +261,7 @@ export default class Tado {
|
|
|
211
261
|
|
|
212
262
|
try {
|
|
213
263
|
const response = await got(tadoLink + path, config);
|
|
264
|
+
await this._increaseCounter();
|
|
214
265
|
return response.body;
|
|
215
266
|
} catch (error) {
|
|
216
267
|
Logger.error(`API Request [${method} ${path}] - FAILED: ${error.message}`, this.name);
|