@homebridge-plugins/homebridge-tado 8.1.1 → 8.2.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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## v8.2.0 - 2025-10-23
4
+ - Query all zone states in a single API request to significantly reduce the number of API calls to tado during polling
5
+ - Update zone state after clearing a zone overlay
6
+ - Skip getRunningTime call when a custom URL is used and remove obsolete getWeatherAirComfort (#176)
7
+ - Add example for tadoApiUrl in config (#176)
8
+
9
+ ## v8.1.2 - 2025-10-20
10
+ - Fix: Skip auth also for config endpoints if enabled (#176)
11
+
3
12
  ## v8.1.1 - 2025-10-20
4
13
  - Fix config UI not working on HOOBS 5 (#177)
5
14
 
@@ -557,7 +557,7 @@
557
557
  "tadoApiUrl": {
558
558
  "title": "Tado API URL",
559
559
  "type": "string",
560
- "description": "Optional: Use a custom tado api url."
560
+ "description": "Optional: Use a custom tado api url (e.g. http://localhost:8080)."
561
561
  },
562
562
  "skipAuth": {
563
563
  "title": "Skip Authentication",
@@ -549,7 +549,7 @@ const schema = {
549
549
  'tadoApiUrl': {
550
550
  'title': 'Tado API URL',
551
551
  'type': 'string',
552
- 'description': 'Optional: Use a custom tado api url.'
552
+ 'description': 'Optional: Use a custom tado api url (e.g. http://localhost:8080).'
553
553
  },
554
554
  'skipAuth': {
555
555
  'title': 'Skip Authentication',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebridge-plugins/homebridge-tado",
3
- "version": "8.1.1",
3
+ "version": "8.2.0",
4
4
  "description": "Homebridge plugin for controlling tado° devices.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -40,8 +40,8 @@
40
40
  "moment": "^2.30.1"
41
41
  },
42
42
  "devDependencies": {
43
- "@babel/core": "7.28.4",
44
- "@babel/eslint-parser": "7.28.4",
43
+ "@babel/core": "7.28.5",
44
+ "@babel/eslint-parser": "7.28.5",
45
45
  "@babel/eslint-plugin": "7.27.1",
46
46
  "@eslint/js": "^9.38.0",
47
47
  "eslint": "^9.38.0",
@@ -57,7 +57,7 @@ export default (api, accessories, config, tado, telegram) => {
57
57
 
58
58
  // Use AC-specific overlay for AIR_CONDITIONING zones
59
59
  if (accessory.context.config.type === 'AIR_CONDITIONING') {
60
-
60
+
61
61
  await tado.setACZoneOverlay(
62
62
  config.homeId,
63
63
  accessory.context.config.zoneId,
@@ -110,6 +110,7 @@ export default (api, accessories, config, tado, telegram) => {
110
110
  accessory.context.config.subtype.includes('heatercooler'))
111
111
  ) {
112
112
  await tado.clearZoneOverlay(config.homeId, accessory.context.config.zoneId);
113
+ await updateZones(accessory.context.config.zoneId);
113
114
  return;
114
115
  }
115
116
 
@@ -123,7 +124,7 @@ export default (api, accessories, config, tado, telegram) => {
123
124
  // Use AC-specific overlay for AIR_CONDITIONING zones
124
125
  if (accessory.context.config.type === 'AIR_CONDITIONING') {
125
126
  let acMode = value === 1 ? 'HEAT' : value === 2 ? 'COOL' : 'COOL';
126
-
127
+
127
128
  await tado.setACZoneOverlay(
128
129
  config.homeId,
129
130
  accessory.context.config.zoneId,
@@ -170,6 +171,7 @@ export default (api, accessories, config, tado, telegram) => {
170
171
  accessory.context.config.subtype.includes('heatercooler'))
171
172
  ) {
172
173
  await tado.clearZoneOverlay(config.homeId, accessory.context.config.zoneId);
174
+ await updateZones(accessory.context.config.zoneId);
173
175
  return;
174
176
  }
175
177
 
@@ -187,15 +189,15 @@ export default (api, accessories, config, tado, telegram) => {
187
189
 
188
190
  // Use AC-specific overlay for AIR_CONDITIONING zones
189
191
  if (accessory.context.config.type === 'AIR_CONDITIONING') {
190
-
192
+
191
193
  // Map HomeKit target state to Tado AC mode
192
194
  let acMode = 'COOL'; // Default to COOL
193
-
195
+
194
196
  // Get current target state from HeaterCooler service for proper mode detection
195
197
  let heaterCoolerService = accessory.getService(api.hap.Service.HeaterCooler);
196
198
  if (heaterCoolerService) {
197
199
  let targetState = heaterCoolerService.getCharacteristic(api.hap.Characteristic.TargetHeaterCoolerState).value;
198
-
200
+
199
201
  // Map HomeKit target states to Tado AC modes
200
202
  // 1 = Heat, 2 = Cool, 3 = Auto
201
203
  switch (targetState) {
@@ -210,7 +212,7 @@ export default (api, accessories, config, tado, telegram) => {
210
212
  acMode = 'AUTO';
211
213
  break;
212
214
  }
213
-
215
+
214
216
  // For temperature changes, use the appropriate threshold characteristic
215
217
  if (![0, 1, 3].includes(value)) {
216
218
  // This is a temperature change, use the appropriate temperature based on mode
@@ -230,7 +232,7 @@ export default (api, accessories, config, tado, telegram) => {
230
232
  // This is a state change - use the mapped AC mode
231
233
  }
232
234
  }
233
-
235
+
234
236
  await tado.setACZoneOverlay(
235
237
  config.homeId,
236
238
  accessory.context.config.zoneId,
@@ -273,7 +275,7 @@ export default (api, accessories, config, tado, telegram) => {
273
275
 
274
276
  // Use AC-specific overlay for AIR_CONDITIONING zones
275
277
  if (accessory.context.config.type === 'AIR_CONDITIONING') {
276
-
278
+
277
279
  await tado.setACZoneOverlay(
278
280
  config.homeId,
279
281
  accessory.context.config.zoneId,
@@ -725,7 +727,7 @@ export default (api, accessories, config, tado, telegram) => {
725
727
  } finally {
726
728
  setTimeout(() => {
727
729
  getStates();
728
- }, config.polling * 1000);
730
+ }, Math.max(config.polling, 300) * 1000);
729
731
  }
730
732
  }
731
733
 
@@ -770,8 +772,8 @@ export default (api, accessories, config, tado, telegram) => {
770
772
  return;
771
773
  }
772
774
 
773
- async function updateZones() {
774
- if (!settingState) {
775
+ async function updateZones(idToUpdate) {
776
+ if (!settingState || idToUpdate !== undefined) {
775
777
  Logger.debug('Polling Zones...', config.homeName);
776
778
 
777
779
  //CentralSwitch
@@ -821,8 +823,18 @@ export default (api, accessories, config, tado, telegram) => {
821
823
  });
822
824
  }
823
825
 
824
- for (const zone of config.zones) {
825
- const zoneState = await tado.getZoneState(config.homeId, zone.id);
826
+ let zoneStates = {};
827
+ let zonesToUpdate = [];
828
+ if (idToUpdate !== undefined) {
829
+ zoneStates[idToUpdate] = await tado.getZoneState(config.homeId, idToUpdate);
830
+ zonesToUpdate = config.zones.filter(zone => zone.id === idToUpdate);
831
+ } else {
832
+ zoneStates = (await tado.getZoneStates(config.homeId))["zoneStates"];
833
+ zonesToUpdate = config.zones;
834
+ }
835
+
836
+ for (const zone of zonesToUpdate) {
837
+ const zoneState = zoneStates[zone.id];
826
838
 
827
839
  let currentState, targetState, currentTemp, targetTemp, humidity, active, battery, tempEqual;
828
840
 
@@ -867,9 +879,9 @@ export default (api, accessories, config, tado, telegram) => {
867
879
  const thermoAccessory = accessories.filter(
868
880
  (acc) =>
869
881
  acc &&
870
- (acc.context.config.subtype === 'zone-thermostat' ||
871
- acc.context.config.subtype === 'zone-heatercooler' ||
872
- acc.context.config.subtype === 'zone-heatercooler-ac')
882
+ (acc.context.config.subtype === 'zone-thermostat' ||
883
+ acc.context.config.subtype === 'zone-heatercooler' ||
884
+ acc.context.config.subtype === 'zone-heatercooler-ac')
873
885
  );
874
886
 
875
887
  if (thermoAccessory.length) {
@@ -974,7 +986,7 @@ export default (api, accessories, config, tado, telegram) => {
974
986
 
975
987
  if (zoneState.setting.power === 'ON') {
976
988
  active = 1;
977
-
989
+
978
990
  // Get target temperature from setting
979
991
  targetTemp =
980
992
  zoneState.setting.temperature !== null && zoneState.setting.temperature
@@ -987,7 +999,7 @@ export default (api, accessories, config, tado, telegram) => {
987
999
  if (zone.type === 'AIR_CONDITIONING') {
988
1000
  const acMode = zoneState.setting.mode || 'COOL';
989
1001
  tempEqual = currentTemp && targetTemp ? Math.abs(currentTemp - targetTemp) < 0.5 : false;
990
-
1002
+
991
1003
  // Map AC modes to HomeKit states
992
1004
  switch (acMode.toUpperCase()) {
993
1005
  case 'HEAT':
@@ -1015,8 +1027,8 @@ export default (api, accessories, config, tado, telegram) => {
1015
1027
 
1016
1028
  //Thermostat/HeaterCooler
1017
1029
  const heaterAccessory = accessories.filter(
1018
- (acc) => acc && (acc.context.config.subtype === 'zone-heatercooler-boiler' ||
1019
- acc.context.config.subtype === 'zone-heatercooler-ac')
1030
+ (acc) => acc && (acc.context.config.subtype === 'zone-heatercooler-boiler' ||
1031
+ acc.context.config.subtype === 'zone-heatercooler-ac')
1020
1032
  );
1021
1033
  const switchAccessory = accessories.filter((acc) => acc && acc.context.config.subtype === 'zone-switch');
1022
1034
  const faucetAccessory = accessories.filter((acc) => acc && acc.context.config.subtype === 'zone-faucet');
@@ -10,6 +10,7 @@ const tado_client_id = "1bb50063-6b0c-4d11-bd99-387f4a91cc46";
10
10
  export default class Tado {
11
11
  constructor(name, config, storagePath, tadoApiUrl, skipAuth) {
12
12
  this.tadoApiUrl = tadoApiUrl || tado_url;
13
+ this.customTadoApiUrlActive = !!tadoApiUrl;
13
14
  this.skipAuth = skipAuth?.toString() === "true";
14
15
  this.name = name;
15
16
  const usesExternalTokenFile = config.username?.toLowerCase().endsWith(".json");
@@ -224,6 +225,7 @@ export default class Tado {
224
225
  }
225
226
 
226
227
  async fullAuthentication() {
228
+ if (this.skipAuth) return "";
227
229
  let instructions = "";
228
230
  let resolve;
229
231
  const oPromise = new Promise((res, _) => {
@@ -245,7 +247,7 @@ export default class Tado {
245
247
  }
246
248
 
247
249
  async waitForAuthentication() {
248
- await this.getToken();
250
+ if (!this.skipAuth) await this.getToken();
249
251
  return "Authentication successful!";
250
252
  }
251
253
 
@@ -307,6 +309,10 @@ export default class Tado {
307
309
  return this.apiCall(`/api/v2/homes/${home_id}/zones/${zone_id}/state`);
308
310
  }
309
311
 
312
+ async getZoneStates(home_id) {
313
+ return this.apiCall(`/api/v2/homes/${home_id}/zoneStates`);
314
+ }
315
+
310
316
  async getZoneCapabilities(home_id, zone_id) {
311
317
  return this.apiCall(`/api/v2/homes/${home_id}/zones/${zone_id}/capabilities`);
312
318
  }
@@ -542,20 +548,6 @@ export default class Tado {
542
548
  return this.apiCall(`/api/v2/homes/${home_id}/airComfort`);
543
549
  }
544
550
 
545
- async getWeatherAirComfort(home_id, longitude, latitude) {
546
- let geoLocation = {
547
- longitude: longitude,
548
- latitude: latitude,
549
- };
550
-
551
- if (!geoLocation.longitude || !geoLocation.latitude) {
552
- const data = await this.getHome(home_id);
553
- geoLocation = data.geolocation;
554
- }
555
-
556
- return this.apiCall(`/v1/homes/${home_id}/airComfort`, 'GET', {}, geoLocation, 'https://acme.tado.com');
557
- }
558
-
559
551
  async setChildLock(serialNumber, state) {
560
552
  if (!serialNumber) {
561
553
  throw new Error('Cannot change child lock state. No serialNumber is given.');
@@ -613,6 +605,8 @@ export default class Tado {
613
605
  }
614
606
 
615
607
  async getRunningTime(home_id, time, from, to) {
608
+ if (this.customTadoApiUrlActive) return;
609
+
616
610
  const period = {
617
611
  aggregate: time || 'day',
618
612
  summary_only: true,
@@ -622,6 +616,6 @@ export default class Tado {
622
616
 
623
617
  if (to) period.to = to;
624
618
 
625
- return this.apiCall(`/v1/homes/${home_id}/runningTimes`, 'GET', {}, period, 'https://minder.tado.com', false);
619
+ return this.apiCall(`/v1/homes/${home_id}/runningTimes`, 'GET', {}, period, 'https://minder.tado.com');
626
620
  }
627
621
  }
@@ -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 < 30 ? 30 : home.polling) : 300,
522
+ polling: Number.isInteger(home.polling) ? (home.polling < 300 ? 300 : home.polling) : 300,
523
523
  };
524
524
 
525
525
  if (home.zones && home.zones.length) {