@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 +9 -0
- package/config.schema.json +1 -1
- package/homebridge-ui/public/js/schema.js +1 -1
- package/package.json +3 -3
- package/src/helper/handler.js +32 -20
- package/src/tado/tado-api.js +10 -16
- package/src/tado/tado-config.js +1 -1
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
|
|
package/config.schema.json
CHANGED
|
@@ -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.
|
|
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.
|
|
44
|
-
"@babel/eslint-parser": "7.28.
|
|
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",
|
package/src/helper/handler.js
CHANGED
|
@@ -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
|
-
|
|
825
|
-
|
|
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
|
-
|
|
872
|
-
|
|
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
|
-
|
|
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');
|
package/src/tado/tado-api.js
CHANGED
|
@@ -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'
|
|
619
|
+
return this.apiCall(`/v1/homes/${home_id}/runningTimes`, 'GET', {}, period, 'https://minder.tado.com');
|
|
626
620
|
}
|
|
627
621
|
}
|
package/src/tado/tado-config.js
CHANGED
|
@@ -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 <
|
|
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) {
|