@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.
@@ -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.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.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,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 = [];
@@ -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);