@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 +6 -0
- package/config.schema.json +1 -1
- package/homebridge-ui/public/js/schema.js +1 -1
- package/package.json +1 -1
- package/src/accessories/thermostat.js +8 -28
- package/src/helper/handler.js +53 -35
- package/src/tado/tado-api.js +1 -1
- package/src/tado/tado-config.js +1 -1
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
|
|
package/config.schema.json
CHANGED
package/package.json
CHANGED
|
@@ -199,17 +199,11 @@ export default class ThermostatAccessory {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
this.waitForEndValue = setTimeout(() => {
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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',
|
package/src/helper/handler.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
878
|
+
if (idToUpdate === undefined) {
|
|
879
|
+
const allZones = (await tado.getZones(config.homeId)) || [];
|
|
865
880
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/tado/tado-api.js
CHANGED
|
@@ -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;
|
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 < 30 ? 30 : home.polling) : 300,
|
|
523
523
|
};
|
|
524
524
|
|
|
525
525
|
if (home.zones && home.zones.length) {
|