@homebridge-plugins/homebridge-tado 8.3.1 → 8.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## v8.5.0 - 2025-10-27
4
+ - Change minimum polling interval to 30s due to improvements made in v8.2.0
5
+
6
+ ## v8.4.0 - 2025-10-26
7
+ - Improve state handling to ensure Apple Home states always reflect current tado states
8
+
3
9
  ## v8.3.1 - 2025-10-25
4
10
  - Fix: Persist zone states only if not empty
5
11
 
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "polling": {
45
45
  "title": "Polling",
46
- "description": "The polling interval in seconds.",
46
+ "description": "The polling interval in seconds (recommended value: 300).",
47
47
  "type": "integer",
48
48
  "default": 300,
49
49
  "minimum": 30
@@ -36,7 +36,7 @@ const schema = {
36
36
  },
37
37
  'polling': {
38
38
  'title': 'Polling',
39
- 'description': 'The polling interval in seconds.',
39
+ 'description': 'The polling interval in seconds (recommended value: 300).',
40
40
  'type': 'integer',
41
41
  'default': 300,
42
42
  'minimum': 30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebridge-plugins/homebridge-tado",
3
- "version": "8.3.1",
3
+ "version": "8.5.0",
4
4
  "description": "Homebridge plugin for controlling tado° devices.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -199,17 +199,11 @@ export default class ThermostatAccessory {
199
199
  }
200
200
 
201
201
  this.waitForEndValue = setTimeout(() => {
202
- if (value === 3) {
203
- if (this.timeoutAuto) {
204
- this.deviceHandler.setStates(this.accessory, this.accessories, 'State', value);
205
- clearTimeout(this.timeoutAuto);
206
- this.timeoutAuto = null;
207
- } else {
208
- this.deviceHandler.setStates(this.accessory, this.accessories, 'State', value);
209
- }
210
- } else {
211
- this.deviceHandler.setStates(this.accessory, this.accessories, 'State', value);
202
+ if (this.settingTemperature) {
203
+ this.settingTemperature = false;
204
+ return;
212
205
  }
206
+ this.deviceHandler.setStates(this.accessory, this.accessories, 'State', value);
213
207
  }, 500);
214
208
  });
215
209
 
@@ -223,24 +217,10 @@ export default class ThermostatAccessory {
223
217
  service
224
218
  .getCharacteristic(this.api.hap.Characteristic.TargetTemperature)
225
219
  .onSet((value) => {
226
- /*
227
- *let tempUnit = service.getCharacteristic(this.api.hap.Characteristic.TemperatureDisplayUnits).value;
228
- *
229
- *let cToF = (c) => Math.round((c * 9) / 5 + 32);
230
- *let fToC = (f) => Math.round(((f - 32) * 5) / 9);
231
- *
232
- *let newValue = tempUnit ? (value <= 25 ? cToF(value) : value) : value > 25 ? fToC(value) : value;
233
- *
234
- *service.getCharacteristic(this.api.hap.Characteristic.CurrentTemperature).updateValue(newValue);
235
- */
236
-
237
- this.timeoutAuto = setTimeout(() => {
238
- let targetState = service.getCharacteristic(this.api.hap.Characteristic.TargetHeatingCoolingState).value;
239
-
240
- if (targetState) this.deviceHandler.setStates(this.accessory, this.accessories, 'Temperature', value);
241
-
242
- this.timeoutAuto = null;
243
- }, 600);
220
+ this.settingTemperature = true;
221
+ const targetState = service.getCharacteristic(this.api.hap.Characteristic.TargetHeatingCoolingState).value;
222
+ if (targetState) this.deviceHandler.setStates(this.accessory, this.accessories, 'Temperature', value);
223
+ setTimeout(() => this.settingTemperature = false, 1000);
244
224
  })
245
225
  .on(
246
226
  'change',
@@ -26,6 +26,9 @@ export default (api, accessories, config, tado, telegram) => {
26
26
  const storagePath = api.user.storagePath();
27
27
 
28
28
  async function setStates(accessory, accs, target, value) {
29
+ let zoneUpdated = false;
30
+ let allZonesUpdated = false;
31
+
29
32
  accessories = accs.filter((acc) => acc && acc.context.config.homeName === config.homeName);
30
33
 
31
34
  try {
@@ -75,7 +78,7 @@ export default (api, accessories, config, tado, telegram) => {
75
78
 
76
79
  // Use AC-specific overlay for AIR_CONDITIONING zones
77
80
  if (accessory.context.config.type === 'AIR_CONDITIONING') {
78
-
81
+ zoneUpdated = true;
79
82
  await tado.setACZoneOverlay(
80
83
  config.homeId,
81
84
  accessory.context.config.zoneId,
@@ -88,6 +91,7 @@ export default (api, accessories, config, tado, telegram) => {
88
91
  accessory.context.config.temperatureUnit
89
92
  );
90
93
  } else {
94
+ zoneUpdated = true;
91
95
  await tado.setZoneOverlay(
92
96
  config.homeId,
93
97
  accessory.context.config.zoneId,
@@ -127,8 +131,8 @@ export default (api, accessories, config, tado, telegram) => {
127
131
  accessory.context.config.mode === 'AUTO' &&
128
132
  accessory.context.config.subtype.includes('heatercooler'))
129
133
  ) {
134
+ zoneUpdated = true;
130
135
  await tado.clearZoneOverlay(config.homeId, accessory.context.config.zoneId);
131
- await updateZones(accessory.context.config.zoneId);
132
136
  return;
133
137
  }
134
138
 
@@ -143,6 +147,7 @@ export default (api, accessories, config, tado, telegram) => {
143
147
  if (accessory.context.config.type === 'AIR_CONDITIONING') {
144
148
  let acMode = value === 1 ? 'HEAT' : value === 2 ? 'COOL' : 'COOL';
145
149
 
150
+ zoneUpdated = true;
146
151
  await tado.setACZoneOverlay(
147
152
  config.homeId,
148
153
  accessory.context.config.zoneId,
@@ -155,6 +160,7 @@ export default (api, accessories, config, tado, telegram) => {
155
160
  accessory.context.config.temperatureUnit
156
161
  );
157
162
  } else {
163
+ zoneUpdated = true;
158
164
  await tado.setZoneOverlay(
159
165
  config.homeId,
160
166
  accessory.context.config.zoneId,
@@ -188,8 +194,8 @@ export default (api, accessories, config, tado, telegram) => {
188
194
  accessory.context.config.mode === 'CUSTOM' &&
189
195
  accessory.context.config.subtype.includes('heatercooler'))
190
196
  ) {
197
+ zoneUpdated = true;
191
198
  await tado.clearZoneOverlay(config.homeId, accessory.context.config.zoneId);
192
- await updateZones(accessory.context.config.zoneId);
193
199
  return;
194
200
  }
195
201
 
@@ -251,6 +257,7 @@ export default (api, accessories, config, tado, telegram) => {
251
257
  }
252
258
  }
253
259
 
260
+ zoneUpdated = true;
254
261
  await tado.setACZoneOverlay(
255
262
  config.homeId,
256
263
  accessory.context.config.zoneId,
@@ -263,6 +270,7 @@ export default (api, accessories, config, tado, telegram) => {
263
270
  accessory.context.config.temperatureUnit
264
271
  );
265
272
  } else {
273
+ zoneUpdated = true;
266
274
  await tado.setZoneOverlay(
267
275
  config.homeId,
268
276
  accessory.context.config.zoneId,
@@ -293,7 +301,7 @@ export default (api, accessories, config, tado, telegram) => {
293
301
 
294
302
  // Use AC-specific overlay for AIR_CONDITIONING zones
295
303
  if (accessory.context.config.type === 'AIR_CONDITIONING') {
296
-
304
+ zoneUpdated = true;
297
305
  await tado.setACZoneOverlay(
298
306
  config.homeId,
299
307
  accessory.context.config.zoneId,
@@ -306,6 +314,7 @@ export default (api, accessories, config, tado, telegram) => {
306
314
  accessory.context.config.temperatureUnit
307
315
  );
308
316
  } else {
317
+ zoneUpdated = true;
309
318
  await tado.setZoneOverlay(
310
319
  config.homeId,
311
320
  accessory.context.config.zoneId,
@@ -367,6 +376,7 @@ export default (api, accessories, config, tado, telegram) => {
367
376
  }
368
377
  }
369
378
 
379
+ allZonesUpdated = true;
370
380
  await tado.setPresenceLock(config.homeId, targetState);
371
381
 
372
382
  break;
@@ -418,6 +428,7 @@ export default (api, accessories, config, tado, telegram) => {
418
428
  })
419
429
  .filter((id) => id);
420
430
 
431
+ allZonesUpdated = true;
421
432
  await tado.resumeShedule(config.homeId, roomIds);
422
433
 
423
434
  //Turn all back to AUTO/ON
@@ -542,6 +553,7 @@ export default (api, accessories, config, tado, telegram) => {
542
553
  .updateValue(false);
543
554
  }
544
555
 
556
+ allZonesUpdated = true;
545
557
  await tado.switchAll(config.homeId, rooms);
546
558
 
547
559
  break;
@@ -557,6 +569,8 @@ export default (api, accessories, config, tado, telegram) => {
557
569
  } catch (err) {
558
570
  errorHandler(err);
559
571
  } finally {
572
+ //always update zone to set correct state in Apple Home
573
+ if (zoneUpdated || allZonesUpdated) await updateZones(allZonesUpdated ? undefined : accessory.context.config.zoneId);
560
574
  settingState = false;
561
575
  }
562
576
  }
@@ -759,7 +773,7 @@ export default (api, accessories, config, tado, telegram) => {
759
773
  tasksInitialized = true;
760
774
 
761
775
  void getStates();
762
- setInterval(() => getStates(), Math.max(config.polling, 300) * 1000);
776
+ setInterval(() => getStates(), Math.max(config.polling, 30) * 1000);
763
777
 
764
778
  void logCounter();
765
779
  setInterval(() => logCounter(), 60 * 60 * 1000);
@@ -861,34 +875,36 @@ export default (api, accessories, config, tado, telegram) => {
861
875
  }
862
876
  }
863
877
 
864
- const allZones = (await tado.getZones(config.homeId)) || [];
878
+ if (idToUpdate === undefined) {
879
+ const allZones = (await tado.getZones(config.homeId)) || [];
865
880
 
866
- for (const [index, zone] of config.zones.entries()) {
867
- allZones.forEach((zoneWithID) => {
868
- if (zoneWithID.name === zone.name) {
869
- const heatAccessory = accessories.filter(
870
- (acc) => acc && acc.displayName === config.homeName + ' ' + zone.name + ' Heater'
871
- );
881
+ for (const [index, zone] of config.zones.entries()) {
882
+ allZones.forEach((zoneWithID) => {
883
+ if (zoneWithID.name === zone.name) {
884
+ const heatAccessory = accessories.filter(
885
+ (acc) => acc && acc.displayName === config.homeName + ' ' + zone.name + ' Heater'
886
+ );
872
887
 
873
- if (heatAccessory.length) heatAccessory[0].context.config.zoneId = zoneWithID.id;
874
-
875
- config.zones[index].id = zoneWithID.id;
876
- config.zones[index].battery = !config.zones[index].noBattery
877
- ? zoneWithID.devices.filter(
878
- (device) =>
879
- device &&
880
- (zone.type === 'HEATING' || zone.type === 'AIR_CONDITIONING') &&
881
- typeof device.batteryState === 'string' &&
882
- !device.batteryState.includes('NORMAL')
883
- ).length
884
- ? zoneWithID.devices.filter((device) => device && !device.batteryState.includes('NORMAL'))[0]
885
- .batteryState
886
- : zoneWithID.devices.filter((device) => device && device.duties.includes('ZONE_LEADER'))[0].batteryState
887
- : false;
888
- config.zones[index].openWindowEnabled =
889
- zoneWithID.openWindowDetection && zoneWithID.openWindowDetection.enabled ? true : false;
890
- }
891
- });
888
+ if (heatAccessory.length) heatAccessory[0].context.config.zoneId = zoneWithID.id;
889
+
890
+ config.zones[index].id = zoneWithID.id;
891
+ config.zones[index].battery = !config.zones[index].noBattery
892
+ ? zoneWithID.devices.filter(
893
+ (device) =>
894
+ device &&
895
+ (zone.type === 'HEATING' || zone.type === 'AIR_CONDITIONING') &&
896
+ typeof device.batteryState === 'string' &&
897
+ !device.batteryState.includes('NORMAL')
898
+ ).length
899
+ ? zoneWithID.devices.filter((device) => device && !device.batteryState.includes('NORMAL'))[0]
900
+ .batteryState
901
+ : zoneWithID.devices.filter((device) => device && device.duties.includes('ZONE_LEADER'))[0].batteryState
902
+ : false;
903
+ config.zones[index].openWindowEnabled =
904
+ zoneWithID.openWindowDetection && zoneWithID.openWindowDetection.enabled ? true : false;
905
+ }
906
+ });
907
+ }
892
908
  }
893
909
 
894
910
  let zonesToUpdate = [];
@@ -925,19 +941,21 @@ export default (api, accessories, config, tado, telegram) => {
925
941
 
926
942
  tempEqual = Math.round(currentTemp) === Math.round(targetTemp);
927
943
 
928
- currentState = currentTemp <= targetTemp ? 1 : 2;
944
+ //show as currently heating if current temp is lower than target temp, otherwise show as temp set
945
+ currentState = currentTemp < targetTemp ? 1 : 0;
929
946
 
930
- targetState = 1;
947
+ //check if auto mode is enabled
948
+ targetState = zoneState.overlayType === null ? 3 : 1;
931
949
 
932
950
  active = 1;
933
951
  } else {
952
+ //heating is switched off
934
953
  currentState = 0;
935
954
  targetState = 0;
936
955
  active = 0;
937
956
  }
938
957
 
939
- if (zoneState.overlayType === null) {
940
- currentState = 0;
958
+ if (targetState === undefined && zoneState.overlayType === null) {
941
959
  targetState = 3;
942
960
  }
943
961
  }
@@ -72,6 +72,7 @@ export default class Tado {
72
72
  }
73
73
 
74
74
  async getToken() {
75
+ Logger.debug('Get access token...', this.name);
75
76
  if (!this._tadoTokenPromise) {
76
77
  this._tadoTokenPromise = this._getToken().finally(() => {
77
78
  this._tadoTokenPromise = undefined;
@@ -212,7 +213,6 @@ export default class Tado {
212
213
  }
213
214
 
214
215
  async apiCall(path, method = 'GET', data = {}, params = {}, tado_url_dif) {
215
- Logger.debug('Get access token...', this.name);
216
216
  const access_token = this.skipAuth ? undefined : await this.getToken();
217
217
 
218
218
  let tadoLink = tado_url_dif || this.tadoApiUrl;
@@ -519,7 +519,7 @@ export default {
519
519
  home.extras && home.extras.childLockSwitches
520
520
  ? home.extras.childLockSwitches.filter((childLockSwitch) => childLockSwitch && childLockSwitch.active)
521
521
  : [],
522
- polling: Number.isInteger(home.polling) ? (home.polling < 300 ? 300 : home.polling) : 300,
522
+ polling: Number.isInteger(home.polling) ? (home.polling < 30 ? 30 : home.polling) : 300,
523
523
  };
524
524
 
525
525
  if (home.zones && home.zones.length) {