@homebridge-plugins/homebridge-tado 8.7.8 → 8.8.1
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 +12 -0
- package/README.md +9 -0
- package/config.schema.json +14 -0
- package/homebridge-ui/public/js/main.js +5 -0
- package/homebridge-ui/public/js/schema.js +14 -0
- package/package.json +6 -12
- package/src/accessories/lightsensor.js +2 -1
- package/src/helper/handler.js +33 -8
- package/src/tado/tado-api.js +8 -6
- package/src/tado/tado-config.js +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v8.8.1 - 2026-02-14
|
|
4
|
+
- Update dependencies
|
|
5
|
+
- Update qs due to vulnerability
|
|
6
|
+
- Update eslint and drop obsolete dev dependencies
|
|
7
|
+
|
|
8
|
+
## v8.8.0 - 2026-01-31
|
|
9
|
+
- Add `addJitter` option to introduce a ±10% random variation in the polling interval
|
|
10
|
+
- Add `nightPolling` option to optionally use a longer interval overnight, lowering polling activity between 00:00–06:00
|
|
11
|
+
- Change: Schedule the next polling cycle after the previous poll completes, using the configured interval
|
|
12
|
+
- Change: Set timeout for tado api requests to 15 seconds
|
|
13
|
+
- Update dependencies
|
|
14
|
+
|
|
3
15
|
## v8.7.8 - 2026-01-13
|
|
4
16
|
- Fix: Match zones by name and type when updating zone states (#192)
|
|
5
17
|
- Add more debug logs for accessory state updates
|
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ After [Homebridge](https://github.com/nfarina/homebridge) has been installed:
|
|
|
38
38
|
- [Configuration](#configuration)
|
|
39
39
|
- [Authentication](#authentication)
|
|
40
40
|
- [Custom API Configuration](#custom-api-configuration)
|
|
41
|
+
- [Polling Behavior Configuration](#polling-behavior-configuration)
|
|
41
42
|
- [Thermostat](#thermostat)
|
|
42
43
|
- [Open Window](#open-window)
|
|
43
44
|
- [Air Conditioning](#air-conditioning)
|
|
@@ -124,6 +125,14 @@ Additionally, it is possible to disable the built-in authentication flow entirel
|
|
|
124
125
|
For further details and configuration examples, refer to the related discussion:
|
|
125
126
|
[homebridge-tado issue #176 – Authentication Options](https://github.com/homebridge-plugins/homebridge-tado/issues/176#issuecomment-3419839118)
|
|
126
127
|
|
|
128
|
+
## Polling Behavior Configuration
|
|
129
|
+
|
|
130
|
+
To better control API usage and reduce unnecessary load, the plugin provides additional polling-related options:
|
|
131
|
+
|
|
132
|
+
When `addJitter` is enabled, a random ±10% jitter is applied to the polling interval.
|
|
133
|
+
|
|
134
|
+
The setting `nightPolling` optionally defines a longer polling interval during nighttime hours (00:00–06:00, local time). This reduces overnight API activity while maintaining normal polling behavior during the day.
|
|
135
|
+
|
|
127
136
|
## Thermostat
|
|
128
137
|
|
|
129
138
|
Each zone in the config.json with ``"type": "HEATING"`` and ``"easyMode": false`` is exposed to Apple Home as a thermostat accessory with the following features:
|
package/config.schema.json
CHANGED
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
"type": "boolean",
|
|
52
52
|
"description": "Optional: Skip authentication for tado API."
|
|
53
53
|
},
|
|
54
|
+
"addJitter": {
|
|
55
|
+
"title": "Add Jitter",
|
|
56
|
+
"type": "boolean",
|
|
57
|
+
"description": "Optional: Add a ±10% random variation in the polling interval."
|
|
58
|
+
},
|
|
54
59
|
"polling": {
|
|
55
60
|
"title": "Polling",
|
|
56
61
|
"description": "The polling interval in seconds (recommended value: 300).",
|
|
@@ -58,6 +63,13 @@
|
|
|
58
63
|
"default": 300,
|
|
59
64
|
"minimum": 30
|
|
60
65
|
},
|
|
66
|
+
"nightPolling": {
|
|
67
|
+
"title": "Night Polling",
|
|
68
|
+
"description": "Optional: The polling interval in seconds used during night between 00:00 - 06:00.",
|
|
69
|
+
"type": "integer",
|
|
70
|
+
"default": 300,
|
|
71
|
+
"minimum": 30
|
|
72
|
+
},
|
|
61
73
|
"temperatureUnit": {
|
|
62
74
|
"title": "Temperature Unit",
|
|
63
75
|
"description": "Temperature Unit for accessories in Apple Home exposed by this plugin.",
|
|
@@ -595,6 +607,7 @@
|
|
|
595
607
|
"items": [
|
|
596
608
|
"homes[].name",
|
|
597
609
|
"homes[].polling",
|
|
610
|
+
"homes[].nightPolling",
|
|
598
611
|
"homes[].temperatureUnit",
|
|
599
612
|
{
|
|
600
613
|
"key": "homes[]",
|
|
@@ -607,6 +620,7 @@
|
|
|
607
620
|
"homes[].id",
|
|
608
621
|
"homes[].tadoApiUrl",
|
|
609
622
|
"homes[].skipAuth",
|
|
623
|
+
"homes[].addJitter",
|
|
610
624
|
"homes[].username"
|
|
611
625
|
]
|
|
612
626
|
},
|
|
@@ -1107,7 +1107,9 @@ async function fetchDevices(auth, refresh, resync) {
|
|
|
1107
1107
|
username: foundHome.username,
|
|
1108
1108
|
tadoApiUrl: foundHome.tadoApiUrl,
|
|
1109
1109
|
skipAuth: foundHome.skipAuth,
|
|
1110
|
+
addJitter: false,
|
|
1110
1111
|
polling: 300,
|
|
1112
|
+
nightPolling: 300,
|
|
1111
1113
|
zones: [],
|
|
1112
1114
|
presence: {
|
|
1113
1115
|
anyone: false,
|
|
@@ -1265,7 +1267,9 @@ async function fetchDevices(auth, refresh, resync) {
|
|
|
1265
1267
|
username: auth.username,
|
|
1266
1268
|
tadoApiUrl: auth.tadoApiUrl,
|
|
1267
1269
|
skipAuth: auth.skipAuth,
|
|
1270
|
+
addJitter: false,
|
|
1268
1271
|
polling: 300,
|
|
1272
|
+
nightPolling: 300,
|
|
1269
1273
|
zones: [],
|
|
1270
1274
|
presence: {
|
|
1271
1275
|
anyone: false,
|
|
@@ -1435,6 +1439,7 @@ async function fetchDevices(auth, refresh, resync) {
|
|
|
1435
1439
|
try {
|
|
1436
1440
|
|
|
1437
1441
|
//check version before load ui
|
|
1442
|
+
//eslint-disable-next-line no-undef
|
|
1438
1443
|
if (window.homebridge.serverEnv.env && window.compareVersions(window.homebridge.serverEnv.env.packageVersion, '4.34.0') < 0) {
|
|
1439
1444
|
await showOldSchema(true);
|
|
1440
1445
|
return;
|
|
@@ -44,6 +44,11 @@ const schema = {
|
|
|
44
44
|
'type': 'boolean',
|
|
45
45
|
'description': 'Optional: Skip authentication for tado API.'
|
|
46
46
|
},
|
|
47
|
+
'addJitter': {
|
|
48
|
+
'title': 'Add Jitter',
|
|
49
|
+
'type': 'boolean',
|
|
50
|
+
'description': 'Optional: Add a ±10% random variation in the polling interval.'
|
|
51
|
+
},
|
|
47
52
|
'polling': {
|
|
48
53
|
'title': 'Polling',
|
|
49
54
|
'description': 'The polling interval in seconds (recommended value: 300).',
|
|
@@ -51,6 +56,13 @@ const schema = {
|
|
|
51
56
|
'default': 300,
|
|
52
57
|
'minimum': 30
|
|
53
58
|
},
|
|
59
|
+
'nightPolling': {
|
|
60
|
+
'title': 'Night Polling',
|
|
61
|
+
'description': 'Optional: The polling interval in seconds used during night between 00:00 - 06:00.',
|
|
62
|
+
'type': 'integer',
|
|
63
|
+
'default': 300,
|
|
64
|
+
'minimum': 30
|
|
65
|
+
},
|
|
54
66
|
'temperatureUnit': {
|
|
55
67
|
'title': 'Temperature Unit',
|
|
56
68
|
'description': 'Temperature Unit for accessories in Apple Home exposed by this plugin.',
|
|
@@ -580,6 +592,7 @@ const schema = {
|
|
|
580
592
|
'preferSiriTemperature',
|
|
581
593
|
'homes.name',
|
|
582
594
|
'homes.polling',
|
|
595
|
+
'homes.nightPolling',
|
|
583
596
|
'homes.temperatureUnit',
|
|
584
597
|
{
|
|
585
598
|
'key': 'homes',
|
|
@@ -592,6 +605,7 @@ const schema = {
|
|
|
592
605
|
'homes.id',
|
|
593
606
|
'homes.tadoApiUrl',
|
|
594
607
|
'homes.skipAuth',
|
|
608
|
+
'homes.addJitter',
|
|
595
609
|
'homes.username'
|
|
596
610
|
]
|
|
597
611
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homebridge-plugins/homebridge-tado",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.8.1",
|
|
4
4
|
"description": "Homebridge plugin for controlling tado° devices.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"homebridge": "^1.6.0||^2.0.0-beta.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@homebridge/plugin-ui-utils": "^2.
|
|
36
|
+
"@homebridge/plugin-ui-utils": "^2.2.0",
|
|
37
37
|
"fakegato-history": "^0.6.7",
|
|
38
38
|
"form-data": "^4.0.5",
|
|
39
39
|
"fs-extra": "^11.3.3",
|
|
@@ -41,15 +41,9 @@
|
|
|
41
41
|
"moment": "^2.30.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"
|
|
45
|
-
"@
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"eslint": "^9.39.2",
|
|
49
|
-
"eslint-config-prettier": "^10.1.8",
|
|
50
|
-
"eslint-plugin-import": "^2.32.0",
|
|
51
|
-
"eslint-plugin-prettier": "^5.5.4",
|
|
52
|
-
"globals": "^17.0.0",
|
|
53
|
-
"prettier": "^3.7.4"
|
|
44
|
+
"eslint": "^10.0.0",
|
|
45
|
+
"@eslint/js": "^10.0.1",
|
|
46
|
+
"globals": "^17.3.0",
|
|
47
|
+
"prettier": "^3.8.1"
|
|
54
48
|
}
|
|
55
49
|
}
|
|
@@ -27,6 +27,7 @@ export default class SolarLightsensorAccessory {
|
|
|
27
27
|
|
|
28
28
|
if (!service) {
|
|
29
29
|
Logger.info('Adding LightSensor service', this.accessory.displayName);
|
|
30
|
+
// eslint-disable-next-line no-useless-assignment
|
|
30
31
|
service = this.accessory.addService(
|
|
31
32
|
this.api.hap.Service.LightSensor,
|
|
32
33
|
this.accessory.displayName,
|
|
@@ -34,4 +35,4 @@ export default class SolarLightsensorAccessory {
|
|
|
34
35
|
);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
}
|
|
38
|
+
}
|
package/src/helper/handler.js
CHANGED
|
@@ -13,7 +13,6 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
13
13
|
helpers[config.homeId] = {
|
|
14
14
|
activeSettingStateRuns: {},
|
|
15
15
|
tasksInitialized: false,
|
|
16
|
-
lastGetStates: 0,
|
|
17
16
|
lastPersistZoneStates: 0,
|
|
18
17
|
persistPromise: Promise.resolve(),
|
|
19
18
|
updateZonesRunning: false,
|
|
@@ -21,6 +20,8 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
21
20
|
delayTimer: {},
|
|
22
21
|
refreshHistoryHandlers: [],
|
|
23
22
|
statesIntervalTime: Math.max(config.polling, 30) * 1000,
|
|
23
|
+
statesIntervalTimeNight: config.nightPolling ? Math.max(config.nightPolling, config.polling, 30) * 1000 : undefined,
|
|
24
|
+
nextPollingTime: 0,
|
|
24
25
|
storagePath: api.user.storagePath(),
|
|
25
26
|
}
|
|
26
27
|
}
|
|
@@ -568,9 +569,7 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
568
569
|
} finally {
|
|
569
570
|
delete helpers[config.homeId].activeSettingStateRuns[runId];
|
|
570
571
|
//update zones to ensure correct state in Apple Home
|
|
571
|
-
|
|
572
|
-
const statesIntervalTimeLeft = helpers[config.homeId].statesIntervalTime - timeSinceLastGetStates;
|
|
573
|
-
if (!settingStates() && zoneUpdated && statesIntervalTimeLeft > (10 * 1000)) await updateZones();
|
|
572
|
+
if (!settingStates() && zoneUpdated && _getTimeUntilNextPolling() > (10 * 1000)) await updateZones();
|
|
574
573
|
}
|
|
575
574
|
}
|
|
576
575
|
|
|
@@ -761,16 +760,42 @@ export default (api, accessories, config, tado, telegram) => {
|
|
|
761
760
|
}
|
|
762
761
|
}
|
|
763
762
|
|
|
763
|
+
function _getTimeUntilNextPolling() {
|
|
764
|
+
return Math.max(0, (helpers[config.homeId].nextPollingTime ?? 0) - Date.now());
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function _getPollingInterval() {
|
|
768
|
+
const hour = helpers[config.homeId].statesIntervalTimeNight ? new Date().getHours() : undefined;
|
|
769
|
+
let interval;
|
|
770
|
+
if (helpers[config.homeId].statesIntervalTimeNight && hour >= 0 && hour < 6) {
|
|
771
|
+
interval = helpers[config.homeId].statesIntervalTimeNight;
|
|
772
|
+
} else {
|
|
773
|
+
interval = helpers[config.homeId].statesIntervalTime;
|
|
774
|
+
}
|
|
775
|
+
const addJitter = config.addJitter?.toString() === "true";
|
|
776
|
+
if (!addJitter) return interval;
|
|
777
|
+
//use 10 percent jitter
|
|
778
|
+
const jitter = interval * 0.1;
|
|
779
|
+
const randomOffset = Math.round((Math.random() * 2 - 1) * jitter);
|
|
780
|
+
return interval + randomOffset;
|
|
781
|
+
}
|
|
782
|
+
|
|
764
783
|
function initTasks() {
|
|
765
784
|
if (helpers[config.homeId].tasksInitialized) return;
|
|
766
785
|
helpers[config.homeId].tasksInitialized = true;
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
786
|
+
async function poll() {
|
|
787
|
+
try {
|
|
788
|
+
await getStates();
|
|
789
|
+
} finally {
|
|
790
|
+
const interval = _getPollingInterval();
|
|
791
|
+
helpers[config.homeId].nextPollingTime = Date.now() + interval;
|
|
792
|
+
setTimeout(poll, interval);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
void poll();
|
|
770
796
|
}
|
|
771
797
|
|
|
772
798
|
async function getStates() {
|
|
773
|
-
helpers[config.homeId].lastGetStates = Date.now();
|
|
774
799
|
try {
|
|
775
800
|
//ME
|
|
776
801
|
if (!config.homeId) await updateMe();
|
package/src/tado/tado-api.js
CHANGED
|
@@ -258,13 +258,15 @@ export default class Tado {
|
|
|
258
258
|
const access_token = this.skipAuth ? undefined : await this.getToken();
|
|
259
259
|
const url = `${tado_url_dif || this.tadoApiUrl}${path}`;
|
|
260
260
|
|
|
261
|
-
const
|
|
261
|
+
const options = {
|
|
262
262
|
method,
|
|
263
263
|
responseType: 'json',
|
|
264
264
|
headers: access_token ?
|
|
265
265
|
{ Authorization: `Bearer ${access_token}` } :
|
|
266
266
|
undefined,
|
|
267
|
-
timeout: {
|
|
267
|
+
timeout: {
|
|
268
|
+
request: 15000
|
|
269
|
+
},
|
|
268
270
|
retry: {
|
|
269
271
|
limit: 2,
|
|
270
272
|
statusCodes: [408, 429, 503, 504],
|
|
@@ -272,12 +274,12 @@ export default class Tado {
|
|
|
272
274
|
},
|
|
273
275
|
};
|
|
274
276
|
|
|
275
|
-
//
|
|
277
|
+
//only add data to options for non-GET methods and when data has content
|
|
276
278
|
if (data && typeof data === 'object' && Object.keys(data).length > 0 && method !== 'GET') {
|
|
277
|
-
|
|
279
|
+
options.json = data;
|
|
278
280
|
}
|
|
279
281
|
|
|
280
|
-
if (Object.keys(params).length)
|
|
282
|
+
if (Object.keys(params).length) options.searchParams = params;
|
|
281
283
|
|
|
282
284
|
Logger.debug('API request start', {
|
|
283
285
|
name: this.name,
|
|
@@ -288,7 +290,7 @@ export default class Tado {
|
|
|
288
290
|
});
|
|
289
291
|
|
|
290
292
|
try {
|
|
291
|
-
const response = await got(url,
|
|
293
|
+
const response = await got(url, options);
|
|
292
294
|
await this._increaseCounter();
|
|
293
295
|
Logger.debug('API request success', {
|
|
294
296
|
name: this.name,
|
package/src/tado/tado-config.js
CHANGED
|
@@ -42,7 +42,9 @@ export default {
|
|
|
42
42
|
username: auth.username,
|
|
43
43
|
tadoApiUrl: auth.tadoApiUrl,
|
|
44
44
|
skipAuth: auth.skipAuth,
|
|
45
|
+
addJitter: false,
|
|
45
46
|
polling: 300,
|
|
47
|
+
nightPolling: 300,
|
|
46
48
|
zones: [],
|
|
47
49
|
presence: {
|
|
48
50
|
anyone: false,
|
|
@@ -513,6 +515,7 @@ export default {
|
|
|
513
515
|
username: home.username,
|
|
514
516
|
tadoApiUrl: home.tadoApiUrl,
|
|
515
517
|
skipAuth: home.skipAuth,
|
|
518
|
+
addJitter: home.addJitter,
|
|
516
519
|
temperatureUnit: home.temperatureUnit || 'CELSIUS',
|
|
517
520
|
geolocation: home.geolocation,
|
|
518
521
|
tado: tado,
|
|
@@ -527,7 +530,8 @@ export default {
|
|
|
527
530
|
home.extras && home.extras.childLockSwitches
|
|
528
531
|
? home.extras.childLockSwitches.filter((childLockSwitch) => childLockSwitch && childLockSwitch.active)
|
|
529
532
|
: [],
|
|
530
|
-
polling: Number.isInteger(home.polling) ? (home.polling
|
|
533
|
+
polling: Number.isInteger(home.polling) ? Math.max(home.polling, 30) : 300,
|
|
534
|
+
nightPolling: home.nightPolling && Number.isInteger(home.nightPolling) ? Math.max(home.nightPolling, home.polling, 30) : undefined
|
|
531
535
|
};
|
|
532
536
|
|
|
533
537
|
if (home.zones && home.zones.length) {
|