@homebridge-plugins/homebridge-tado 8.2.0 → 8.3.0-beta.1

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.
@@ -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.2.0",
3
+ "version": "8.3.0-beta.1",
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.refreshHistory(service);
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.refreshHistory(service);
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.refreshHistory(service);
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.refreshHistory(service);
59
+ if (!this.refreshHistoryHandlerRegistered) {
60
+ this.deviceHandler.refreshHistoryHandlers.push(() => this.refreshHistory(service));
61
+ this.refreshHistoryHandlerRegistered = true;
62
+ }
60
63
  }
61
64
 
62
- async refreshHistory(service) {
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.refreshHistory(service);
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.refreshHistory(service);
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) {
@@ -1,12 +1,21 @@
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
 
9
+ let statesInterval;
10
+ let counterInterval;
11
+
7
12
  const timeout = (ms) => new Promise((res) => setTimeout(res, ms));
13
+ const aRefreshHistoryHandlers = [];
8
14
 
9
15
  export default (api, accessories, config, tado, telegram) => {
16
+ const storagePath = api.user.storagePath();
17
+ initTasks();
18
+
10
19
  async function setStates(accessory, accs, target, value) {
11
20
  accessories = accs.filter((acc) => acc && acc.context.config.homeName === config.homeName);
12
21
 
@@ -567,7 +576,7 @@ export default (api, accessories, config, tado, telegram) => {
567
576
  ? Math.round(targetTemp - currentTemp >= 5 ? 100 : (targetTemp - currentTemp) * 20)
568
577
  : 0;
569
578
 
570
- historyService.addEntry({
579
+ if (historyService) historyService.addEntry({
571
580
  time: moment().unix(),
572
581
  currentTemp: currentTemp,
573
582
  setTemp: targetTemp,
@@ -595,7 +604,7 @@ export default (api, accessories, config, tado, telegram) => {
595
604
  ? Math.round(targetTemp - currentTemp >= 5 ? 100 : (targetTemp - currentTemp) * 20)
596
605
  : 0;
597
606
 
598
- historyService.addEntry({
607
+ if (historyService) historyService.addEntry({
599
608
  time: moment().unix(),
600
609
  currentTemp: currentTemp,
601
610
  setTemp: targetTemp,
@@ -636,7 +645,7 @@ export default (api, accessories, config, tado, telegram) => {
636
645
  .updateValue(openDuration);
637
646
  }
638
647
 
639
- historyService.addEntry({ time: moment().unix(), status: value.newValue ? 1 : 0 });
648
+ if (historyService) historyService.addEntry({ time: moment().unix(), status: value.newValue ? 1 : 0 });
640
649
 
641
650
  let dest = value.newValue ? 'opened' : 'closed';
642
651
 
@@ -658,7 +667,7 @@ export default (api, accessories, config, tado, telegram) => {
658
667
  .getCharacteristic(api.hap.Characteristic.LastActivation)
659
668
  .updateValue(lastActivation);
660
669
 
661
- historyService.addEntry({ time: moment().unix(), status: value.newValue ? 1 : 0 });
670
+ if (historyService) historyService.addEntry({ time: moment().unix(), status: value.newValue ? 1 : 0 });
662
671
  }
663
672
 
664
673
  let dest;
@@ -679,13 +688,13 @@ export default (api, accessories, config, tado, telegram) => {
679
688
 
680
689
  case 'zone-temperature':
681
690
  case 'weather-temperature': {
682
- historyService.addEntry({ time: moment().unix(), temp: value.newValue, humidity: 0, ppm: 0 });
691
+ if (historyService) historyService.addEntry({ time: moment().unix(), temp: value.newValue, humidity: 0, ppm: 0 });
683
692
 
684
693
  break;
685
694
  }
686
695
 
687
696
  case 'zone-humidity': {
688
- historyService.addEntry({ time: moment().unix(), temp: 0, humidity: value.newValue, ppm: 0 });
697
+ if (historyService) historyService.addEntry({ time: moment().unix(), temp: 0, humidity: value.newValue, ppm: 0 });
689
698
 
690
699
  break;
691
700
  }
@@ -697,6 +706,49 @@ export default (api, accessories, config, tado, telegram) => {
697
706
  }
698
707
  }
699
708
 
709
+ async function refreshHistory(homeId, zoneStates) {
710
+ try {
711
+ const data = {};
712
+ data.counterData = await tado.getCounterData();
713
+ await writeFile(join(storagePath, "tado-counter.json"), JSON.stringify(data, null, 2), "utf-8");
714
+ } catch (error) {
715
+ Logger.error(`Error while updating tado counter file: ${error.message || error}`);
716
+ }
717
+ try {
718
+ const data = {};
719
+ data.zoneStates = zoneStates ?? {};
720
+ await writeFile(join(storagePath, `tado-states-${homeId}.json`), JSON.stringify(data, null, 2), "utf-8");
721
+ } catch (error) {
722
+ Logger.error(`Error while updating tado states file for home id ${homeId}: ${error.message || error}`);
723
+ }
724
+ try {
725
+ for (const fnRefreshHistory of aRefreshHistoryHandlers) {
726
+ fnRefreshHistory();
727
+ }
728
+ } catch (error) {
729
+ Logger.error(`Error while refreshing history: ${error.message || error}`);
730
+ }
731
+ }
732
+
733
+ async function logCounter() {
734
+ try {
735
+ const iCounter = (await tado.getCounterData()).counter;
736
+ Logger.info(`Tado API counter: ${iCounter.toLocaleString('en-US')}`);
737
+ } catch (error) {
738
+ Logger.warn(`Failed to get Tado API counter: ${error.message || error}`);
739
+ }
740
+ }
741
+
742
+ function initTasks() {
743
+ if (statesInterval) clearInterval(statesInterval);
744
+ void getStates();
745
+ statesInterval = setInterval(() => getStates(), Math.max(config.polling, 300) * 1000);
746
+
747
+ if (counterInterval) clearInterval(counterInterval);
748
+ void logCounter();
749
+ counterInterval = setInterval(() => logCounter(), 60 * 60 * 1000);
750
+ }
751
+
700
752
  async function getStates() {
701
753
  try {
702
754
  //ME
@@ -706,7 +758,12 @@ export default (api, accessories, config, tado, telegram) => {
706
758
  if (!config.temperatureUnit) await updateHome();
707
759
 
708
760
  //Zones
709
- if (config.zones.length) await updateZones();
761
+ let zoneStates = {};
762
+ try {
763
+ if (config.zones.length) zoneStates = await updateZones();
764
+ } finally {
765
+ void refreshHistory(config.homeId, zoneStates);
766
+ }
710
767
 
711
768
  //MobileDevices
712
769
  if (config.presence.length) await updateMobileDevices();
@@ -724,10 +781,6 @@ export default (api, accessories, config, tado, telegram) => {
724
781
  if (config.childLock.length) await updateDevices();
725
782
  } catch (err) {
726
783
  errorHandler(err);
727
- } finally {
728
- setTimeout(() => {
729
- getStates();
730
- }, Math.max(config.polling, 300) * 1000);
731
784
  }
732
785
  }
733
786
 
@@ -773,6 +826,7 @@ export default (api, accessories, config, tado, telegram) => {
773
826
  }
774
827
 
775
828
  async function updateZones(idToUpdate) {
829
+ let zoneStates = {};
776
830
  if (!settingState || idToUpdate !== undefined) {
777
831
  Logger.debug('Polling Zones...', config.homeName);
778
832
 
@@ -823,7 +877,6 @@ export default (api, accessories, config, tado, telegram) => {
823
877
  });
824
878
  }
825
879
 
826
- let zoneStates = {};
827
880
  let zonesToUpdate = [];
828
881
  if (idToUpdate !== undefined) {
829
882
  zoneStates[idToUpdate] = await tado.getZoneState(config.homeId, idToUpdate);
@@ -1233,6 +1286,7 @@ export default (api, accessories, config, tado, telegram) => {
1233
1286
  });
1234
1287
  }
1235
1288
  }
1289
+ return zoneStates;
1236
1290
  }
1237
1291
 
1238
1292
  async function updateMobileDevices() {
@@ -1513,5 +1567,6 @@ export default (api, accessories, config, tado, telegram) => {
1513
1567
  getStates: getStates,
1514
1568
  setStates: setStates,
1515
1569
  changedStates: changedStates,
1570
+ refreshHistoryHandlers: aRefreshHistoryHandlers
1516
1571
  };
1517
1572
  };
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 = [];
@@ -192,8 +192,7 @@ class TadoPlatform {
192
192
 
193
193
  let accessories = this.accessories.filter((acc) => acc && acc.context.config.homeName === name);
194
194
 
195
- const deviceHandler = DeviceHandler(this.api, accessories, config, tado, this.telegram);
196
- deviceHandler.getStates();
195
+ DeviceHandler(this.api, accessories, config, tado, this.telegram);
197
196
  }
198
197
  }
199
198
 
@@ -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);