@switchbot/homebridge-switchbot 5.0.0-beta.30 → 5.0.0-beta.32

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.
Files changed (116) hide show
  1. package/README.md +23 -3
  2. package/config.schema.json +28 -0
  3. package/dist/platform-matter.d.ts +2 -0
  4. package/dist/platform-matter.d.ts.map +1 -1
  5. package/dist/platform-matter.js +116 -7
  6. package/dist/platform-matter.js.map +1 -1
  7. package/dist/settings.d.ts +2 -0
  8. package/dist/settings.d.ts.map +1 -1
  9. package/dist/settings.js.map +1 -1
  10. package/dist/test/hap/platform-hap.logging.test.d.ts +2 -0
  11. package/dist/test/hap/platform-hap.logging.test.d.ts.map +1 -0
  12. package/dist/test/hap/platform-hap.logging.test.js +33 -0
  13. package/dist/test/hap/platform-hap.logging.test.js.map +1 -0
  14. package/dist/test/hap/platform-hap.test.d.ts +2 -0
  15. package/dist/test/hap/platform-hap.test.d.ts.map +1 -0
  16. package/dist/test/hap/platform-hap.test.js +62 -0
  17. package/dist/test/hap/platform-hap.test.js.map +1 -0
  18. package/dist/test/helpers/platform-fixtures.d.ts +5 -5
  19. package/dist/{index.test.d.ts.map → test/index.test.d.ts.map} +1 -1
  20. package/dist/{index.test.js → test/index.test.js} +2 -2
  21. package/dist/test/index.test.js.map +1 -0
  22. package/dist/test/matter/devices-matter/baseMatterAccessory.test.d.ts.map +1 -0
  23. package/dist/test/{devices-matter → matter/devices-matter}/baseMatterAccessory.test.js +1 -1
  24. package/dist/test/matter/devices-matter/baseMatterAccessory.test.js.map +1 -0
  25. package/dist/test/matter/platform-matter.additional.test.d.ts.map +1 -0
  26. package/dist/test/{platform-matter.additional.test.js → matter/platform-matter.additional.test.js} +2 -2
  27. package/dist/test/matter/platform-matter.additional.test.js.map +1 -0
  28. package/dist/test/matter/platform-matter.bleparse.test.d.ts.map +1 -0
  29. package/dist/test/{platform-matter.bleparse.test.js → matter/platform-matter.bleparse.test.js} +2 -2
  30. package/dist/test/matter/platform-matter.bleparse.test.js.map +1 -0
  31. package/dist/test/matter/platform-matter.cleanup.test.d.ts.map +1 -0
  32. package/dist/test/{platform-matter.cleanup.test.js → matter/platform-matter.cleanup.test.js} +3 -3
  33. package/dist/test/matter/platform-matter.cleanup.test.js.map +1 -0
  34. package/dist/test/matter/platform-matter.keepstale.test.d.ts.map +1 -0
  35. package/dist/test/{platform-matter.keepstale.test.js → matter/platform-matter.keepstale.test.js} +2 -2
  36. package/dist/test/matter/platform-matter.keepstale.test.js.map +1 -0
  37. package/dist/test/matter/platform-matter.logging.test.d.ts +2 -0
  38. package/dist/test/matter/platform-matter.logging.test.d.ts.map +1 -0
  39. package/dist/test/matter/platform-matter.logging.test.js +29 -0
  40. package/dist/test/matter/platform-matter.logging.test.js.map +1 -0
  41. package/dist/test/matter/platform-matter.mapping.test.d.ts.map +1 -0
  42. package/dist/test/{platform-matter.mapping.test.js → matter/platform-matter.mapping.test.js} +2 -2
  43. package/dist/test/matter/platform-matter.mapping.test.js.map +1 -0
  44. package/dist/test/matter/platform-matter.openapi-mapping.test.d.ts.map +1 -0
  45. package/dist/test/{platform-matter.openapi-mapping.test.js → matter/platform-matter.openapi-mapping.test.js} +2 -2
  46. package/dist/test/matter/platform-matter.openapi-mapping.test.js.map +1 -0
  47. package/dist/test/matter/platform-matter.test.d.ts.map +1 -0
  48. package/dist/test/{platform-matter.test.js → matter/platform-matter.test.js} +3 -3
  49. package/dist/test/matter/platform-matter.test.js.map +1 -0
  50. package/dist/test/matter/platform-matter.unregister.test.d.ts.map +1 -0
  51. package/dist/test/{platform-matter.unregister.test.js → matter/platform-matter.unregister.test.js} +2 -2
  52. package/dist/test/matter/platform-matter.unregister.test.js.map +1 -0
  53. package/dist/{utils.test.d.ts.map → test/utils.test.d.ts.map} +1 -1
  54. package/dist/{utils.test.js → test/utils.test.js} +1 -1
  55. package/dist/test/utils.test.js.map +1 -0
  56. package/dist/test/verifyconfig.test.d.ts.map +1 -0
  57. package/dist/test/verifyconfig.test.js.map +1 -0
  58. package/dist/utils.d.ts.map +1 -1
  59. package/dist/utils.js +37 -12
  60. package/dist/utils.js.map +1 -1
  61. package/docs/index.html +19 -1
  62. package/docs/variables/default.html +1 -1
  63. package/package.json +3 -3
  64. package/src/platform-matter.ts +118 -11
  65. package/src/settings.ts +2 -0
  66. package/src/test/hap/platform-hap.logging.test.ts +36 -0
  67. package/src/test/hap/platform-hap.test.ts +70 -0
  68. package/src/{index.test.ts → test/index.test.ts} +2 -2
  69. package/src/test/{devices-matter → matter/devices-matter}/baseMatterAccessory.test.ts +1 -1
  70. package/src/test/{platform-matter.additional.test.ts → matter/platform-matter.additional.test.ts} +2 -2
  71. package/src/test/{platform-matter.bleparse.test.ts → matter/platform-matter.bleparse.test.ts} +2 -2
  72. package/src/test/{platform-matter.cleanup.test.ts → matter/platform-matter.cleanup.test.ts} +3 -3
  73. package/src/test/{platform-matter.keepstale.test.ts → matter/platform-matter.keepstale.test.ts} +2 -2
  74. package/src/test/matter/platform-matter.logging.test.ts +33 -0
  75. package/src/test/{platform-matter.mapping.test.ts → matter/platform-matter.mapping.test.ts} +2 -2
  76. package/src/test/{platform-matter.openapi-mapping.test.ts → matter/platform-matter.openapi-mapping.test.ts} +2 -2
  77. package/src/test/{platform-matter.test.ts → matter/platform-matter.test.ts} +3 -3
  78. package/src/test/{platform-matter.unregister.test.ts → matter/platform-matter.unregister.test.ts} +2 -2
  79. package/src/{utils.test.ts → test/utils.test.ts} +1 -1
  80. package/src/{verifyconfig.test.ts → test/verifyconfig.test.ts} +1 -1
  81. package/src/utils.ts +37 -9
  82. package/dist/index.test.js.map +0 -1
  83. package/dist/test/devices-matter/baseMatterAccessory.test.d.ts.map +0 -1
  84. package/dist/test/devices-matter/baseMatterAccessory.test.js.map +0 -1
  85. package/dist/test/platform-matter.additional.test.d.ts.map +0 -1
  86. package/dist/test/platform-matter.additional.test.js.map +0 -1
  87. package/dist/test/platform-matter.bleparse.test.d.ts.map +0 -1
  88. package/dist/test/platform-matter.bleparse.test.js.map +0 -1
  89. package/dist/test/platform-matter.cleanup.test.d.ts.map +0 -1
  90. package/dist/test/platform-matter.cleanup.test.js.map +0 -1
  91. package/dist/test/platform-matter.keepstale.test.d.ts.map +0 -1
  92. package/dist/test/platform-matter.keepstale.test.js.map +0 -1
  93. package/dist/test/platform-matter.mapping.test.d.ts.map +0 -1
  94. package/dist/test/platform-matter.mapping.test.js.map +0 -1
  95. package/dist/test/platform-matter.openapi-mapping.test.d.ts.map +0 -1
  96. package/dist/test/platform-matter.openapi-mapping.test.js.map +0 -1
  97. package/dist/test/platform-matter.test.d.ts.map +0 -1
  98. package/dist/test/platform-matter.test.js.map +0 -1
  99. package/dist/test/platform-matter.unregister.test.d.ts.map +0 -1
  100. package/dist/test/platform-matter.unregister.test.js.map +0 -1
  101. package/dist/utils.test.js.map +0 -1
  102. package/dist/verifyconfig.test.d.ts.map +0 -1
  103. package/dist/verifyconfig.test.js.map +0 -1
  104. /package/dist/{index.test.d.ts → test/index.test.d.ts} +0 -0
  105. /package/dist/test/{devices-matter → matter/devices-matter}/baseMatterAccessory.test.d.ts +0 -0
  106. /package/dist/test/{platform-matter.additional.test.d.ts → matter/platform-matter.additional.test.d.ts} +0 -0
  107. /package/dist/test/{platform-matter.bleparse.test.d.ts → matter/platform-matter.bleparse.test.d.ts} +0 -0
  108. /package/dist/test/{platform-matter.cleanup.test.d.ts → matter/platform-matter.cleanup.test.d.ts} +0 -0
  109. /package/dist/test/{platform-matter.keepstale.test.d.ts → matter/platform-matter.keepstale.test.d.ts} +0 -0
  110. /package/dist/test/{platform-matter.mapping.test.d.ts → matter/platform-matter.mapping.test.d.ts} +0 -0
  111. /package/dist/test/{platform-matter.openapi-mapping.test.d.ts → matter/platform-matter.openapi-mapping.test.d.ts} +0 -0
  112. /package/dist/test/{platform-matter.test.d.ts → matter/platform-matter.test.d.ts} +0 -0
  113. /package/dist/test/{platform-matter.unregister.test.d.ts → matter/platform-matter.unregister.test.d.ts} +0 -0
  114. /package/dist/{utils.test.d.ts → test/utils.test.d.ts} +0 -0
  115. /package/dist/{verifyconfig.test.d.ts → test/verifyconfig.test.d.ts} +0 -0
  116. /package/dist/{verifyconfig.test.js → test/verifyconfig.test.js} +0 -0
package/README.md CHANGED
@@ -52,16 +52,13 @@
52
52
  ## Troubleshooting
53
53
 
54
54
  - ### If using Linux / Raspberry Pi OS
55
-
56
55
  1. `bluetoothctl` must be installed on the device, otherwise it cannot communicate via Bluetooth. Enable it with `sudo bluetoothctl power on`.
57
56
 
58
57
  2. If errors occur, while enabling it, restart the process:
59
-
60
58
  - `rfkill block bluetooth`
61
59
  - `rfkill unblock bluetooth`
62
60
 
63
61
  3. Also make sure, that the computer can discover the SwitchBot device:
64
-
65
62
  - `sudo bluetoothctl`
66
63
  - `scan on`
67
64
 
@@ -218,6 +215,29 @@
218
215
  - Supports Fan Device Type
219
216
  - Allows for On/Off Controls
220
217
 
218
+ ## Matter Platform
219
+
220
+ ### Batched refresh and API load control
221
+
222
+ By default, the Matter platform uses a single batched refresh to update device status. You can tune or override this behavior with the following options under `options`:
223
+
224
+ - `matterBatchEnabled` (boolean, default true): enable/disable platform-level batched refresh. Devices with a per-device `refreshRate` still run their own timers.
225
+ - `matterBatchRefreshRate` (number, seconds): batch interval (falls back to `options.refreshRate`, then 300 if not set).
226
+ - `matterBatchConcurrency` (number): limit of parallel OpenAPI status calls during a batch (default 5).
227
+ - `matterBatchJitter` (number, seconds): random startup delay before the first batch to reduce synchronized spikes.
228
+
229
+ Device-level override:
230
+
231
+ - If a device sets `refreshRate` in its config, it uses a per-device timer and is excluded from the platform batch.
232
+
233
+ Reliability and rate-limiting:
234
+
235
+ - Each device status call retries with exponential backoff on non-success responses.
236
+ - After exhausting retries, the device enters a short cooldown before being retried; cooldowns are persisted across restarts.
237
+ - The batch worklist is randomized every cycle to further distribute API load.
238
+
239
+ These controls keep API usage smooth and predictable while preserving per-device control when needed.
240
+
221
241
  ## SwitchBot APIs
222
242
 
223
243
  - [OpenWonderLabs/node-switchbot](https://github.com/OpenWonderLabs/node-switchbot)
@@ -52,6 +52,18 @@
52
52
  "placeholder": 5,
53
53
  "description": "Maximum number of parallel OpenAPI status calls during a batch refresh."
54
54
  },
55
+ "matterBatchEnabled": {
56
+ "title": "Enable Matter Batch Refresh",
57
+ "type": "boolean",
58
+ "default": true,
59
+ "description": "When true (default), the platform runs a single batched refresh timer. Devices with a per-device refreshRate still use their own timers."
60
+ },
61
+ "matterBatchJitter": {
62
+ "title": "Matter Batch Start Jitter (seconds)",
63
+ "type": "number",
64
+ "placeholder": 30,
65
+ "description": "Random delay added before starting the platform-level batch timer to reduce synchronized spikes."
66
+ },
55
67
  "devices": {
56
68
  "type": "array",
57
69
  "items": {
@@ -14834,6 +14846,22 @@
14834
14846
  "options.mqttPubOptions",
14835
14847
  "options.maxRetries",
14836
14848
  "options.delayBetweenRetries",
14849
+ {
14850
+ "key": "options.matterBatchRefreshRate",
14851
+ "description": "<em class='primary-text'>Specifies the interval, in seconds, for retrieving the latest device status from the SwitchBot API when batching Matter requests.</em>"
14852
+ },
14853
+ {
14854
+ "key": "options.matterBatchConcurrency",
14855
+ "description": "<em class='primary-text'>Specifies the maximum number of Matter requests to process concurrently.</em>"
14856
+ },
14857
+ {
14858
+ "key": "options.matterBatchEnabled",
14859
+ "description": "<em class='primary-text'>If true, enables batching of Matter requests to optimize performance. Default: false.</em>"
14860
+ },
14861
+ {
14862
+ "key": "options.matterBatchJitter",
14863
+ "description": "<em class='primary-text'>Specifies the maximum random jitter, in seconds, to add to the batch processing interval to prevent synchronization issues. Default: 0.</em>"
14864
+ },
14837
14865
  {
14838
14866
  "key": "options.refreshRate",
14839
14867
  "description": "<em class='primary-text'>Specifies the interval, in seconds, for retrieving the latest device status from the SwitchBot API.</em>"
@@ -13,6 +13,8 @@ export declare class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
13
13
  private refreshTimers;
14
14
  private platformRefreshTimer?;
15
15
  private deviceStatusCache;
16
+ private backoffCooldowns;
17
+ private backoffFilePath?;
16
18
  private perDeviceRefreshSet;
17
19
  private bleEventHandler;
18
20
  private mqttClient;
@@ -1 +1 @@
1
- {"version":3,"file":"platform-matter.d.ts","sourceRoot":"","sources":["../src/platform-matter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform-matter.d.ts","sourceRoot":"","sources":["../src/platform-matter.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
1
3
  import asyncmqtt from 'async-mqtt';
2
4
  import { SwitchBotBLE, SwitchBotOpenAPI } from 'node-switchbot';
3
5
  import { ColorLightAccessory, ColorTemperatureLightAccessory, ContactSensorAccessory, DimmableLightAccessory, DoorLockAccessory, ExtendedColorLightAccessory, FanAccessory, HumiditySensorAccessory, LeakSensorAccessory, LightSensorAccessory, OccupancySensorAccessory, OnOffLightAccessory, OnOffOutletAccessory, OnOffSwitchAccessory, PowerStripAccessory, RoboticVacuumAccessory, SmokeCOAlarmAccessory, TemperatureSensorAccessory, ThermostatAccessory, VenetianBlindAccessory, WindowBlindAccessory, } from './devices-matter/index.js';
@@ -25,6 +27,9 @@ export class SwitchBotMatterPlatform {
25
27
  platformRefreshTimer;
26
28
  // Device status cache from last refresh
27
29
  deviceStatusCache = new Map();
30
+ // Backoff cooldowns persisted across restarts: normalized deviceId -> nextAllowedAt(ms)
31
+ backoffCooldowns = new Map();
32
+ backoffFilePath;
28
33
  // Devices that have explicit per-device refresh timers (normalized deviceId)
29
34
  perDeviceRefreshSet = new Set();
30
35
  // BLE event handlers keyed by device MAC (formatted)
@@ -236,6 +241,22 @@ export class SwitchBotMatterPlatform {
236
241
  catch (e) {
237
242
  this.debugLog(`Failed to stop BLE scanning: ${e?.message ?? e}`);
238
243
  }
244
+ // Persist backoff cooldowns to disk
245
+ try {
246
+ if (this.backoffFilePath) {
247
+ const obj = {};
248
+ for (const [k, v] of this.backoffCooldowns.entries()) {
249
+ if (Number.isFinite(v)) {
250
+ obj[k] = v;
251
+ }
252
+ }
253
+ writeFileSync(this.backoffFilePath, JSON.stringify(obj, null, 2));
254
+ this.debugLog(`Saved ${Object.keys(obj).length} backoff cooldown entries`);
255
+ }
256
+ }
257
+ catch (e) {
258
+ this.debugLog(`Failed to save backoff state: ${e?.message ?? e}`);
259
+ }
239
260
  }
240
261
  catch (e) {
241
262
  this.debugLog('Shutdown cleanup failed: %s', e?.message ?? e);
@@ -268,6 +289,32 @@ export class SwitchBotMatterPlatform {
268
289
  catch (e) {
269
290
  this.errorLog(`Setup Webhook, Error Message: ${e?.message ?? e}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
270
291
  }
292
+ // Initialize backoff persistence file path and load any saved cooldowns
293
+ try {
294
+ const storagePath = this.api.user.storagePath();
295
+ this.backoffFilePath = join(storagePath, `${PLUGIN_NAME.replace('@', '').replace('/', '-')}-matter-backoff.json`);
296
+ if (existsSync(this.backoffFilePath)) {
297
+ try {
298
+ const raw = readFileSync(this.backoffFilePath, 'utf8');
299
+ const parsed = JSON.parse(raw);
300
+ if (parsed && typeof parsed === 'object') {
301
+ for (const [k, v] of Object.entries(parsed)) {
302
+ const ts = Number(v);
303
+ if (Number.isFinite(ts)) {
304
+ this.backoffCooldowns.set(String(k), ts);
305
+ }
306
+ }
307
+ this.debugLog(`Loaded ${this.backoffCooldowns.size} backoff cooldown entries`);
308
+ }
309
+ }
310
+ catch (e) {
311
+ this.debugLog(`Failed to load backoff state: ${e?.message ?? e}`);
312
+ }
313
+ }
314
+ }
315
+ catch (e) {
316
+ this.debugLog(`Failed to initialize backoff persistence: ${e?.message ?? e}`);
317
+ }
271
318
  }
272
319
  /**
273
320
  * Normalize a deviceId for matching (uppercase alphanumerics only)
@@ -946,6 +993,12 @@ export class SwitchBotMatterPlatform {
946
993
  this.perDeviceRefreshSet.add(nid);
947
994
  const timer = setInterval(async () => {
948
995
  try {
996
+ // Skip if device is under cooldown
997
+ const now = Date.now();
998
+ const nextAllowed = this.backoffCooldowns.get(nid) ?? 0;
999
+ if (now < nextAllowed) {
1000
+ return;
1001
+ }
949
1002
  await this.refreshSingleDeviceWithRetry(dev);
950
1003
  }
951
1004
  catch (e) {
@@ -1234,11 +1287,35 @@ export class SwitchBotMatterPlatform {
1234
1287
  // Helper to safely call instance methods or fallback to api.matter.updateAccessoryState
1235
1288
  const safeUpdate = async (cluster, attributes, methodName) => {
1236
1289
  try {
1290
+ // Special-case: powerSource cluster is optional on many devices (e.g., Curtains/Blinds).
1291
+ // To avoid noisy Matter server errors ("Behavior ID powerSource does not exist"),
1292
+ // always use the direct updateAccessoryState path wrapped in a guard for this cluster,
1293
+ // even when an accessory instance is present.
1294
+ const powerClusterName = (this.api.matter?.clusterNames && this.api.matter.clusterNames.PowerSource)
1295
+ ? this.api.matter.clusterNames.PowerSource
1296
+ : 'powerSource';
1297
+ const isPowerSourceCluster = cluster === powerClusterName || cluster === 'powerSource';
1298
+ // If the accessory instance declares supported clusters, skip updates for clusters
1299
+ // not present to avoid triggering Matter server errors and logs.
1300
+ let clusterSupported = true;
1301
+ if (instance && instance.clusters) {
1302
+ const declared = instance.clusters;
1303
+ if (Array.isArray(declared)) {
1304
+ clusterSupported = declared.includes(cluster);
1305
+ }
1306
+ else if (typeof declared === 'object') {
1307
+ clusterSupported = Object.prototype.hasOwnProperty.call(declared, cluster);
1308
+ }
1309
+ if (!clusterSupported) {
1310
+ this.debugLog(`Cluster ${cluster} not declared on accessory for ${dev.deviceId}, skipping update`);
1311
+ return;
1312
+ }
1313
+ }
1237
1314
  if (instance && methodName && typeof instance[methodName] === 'function') {
1238
1315
  // prefer device-specific update helpers when available
1239
1316
  await instance[methodName](...(Object.values(attributes)));
1240
1317
  }
1241
- else if (instance && typeof instance.updateState === 'function') {
1318
+ else if (!isPowerSourceCluster && instance && typeof instance.updateState === 'function') {
1242
1319
  // some accessories expose updateState that accepts cluster and attributes
1243
1320
  await instance.updateState(cluster, attributes);
1244
1321
  }
@@ -1708,7 +1785,7 @@ export class SwitchBotMatterPlatform {
1708
1785
  return;
1709
1786
  }
1710
1787
  // If no discovered devices are available, do not register example/demo accessories.
1711
- this.infoLog('No discovered SwitchBot devices found; not registering example Matter accessories by default.');
1788
+ this.infoLog('No discovered SwitchBot devices found.');
1712
1789
  this.debugLog('═'.repeat(80));
1713
1790
  this.debugLog('Finished registering Matter accessories');
1714
1791
  this.debugLog('═'.repeat(80));
@@ -2009,10 +2086,26 @@ export class SwitchBotMatterPlatform {
2009
2086
  if (this.platformRefreshTimer) {
2010
2087
  return;
2011
2088
  }
2012
- this.debugLog(`Starting platform-level refresh timer with interval ${refreshRateSec}s`);
2013
- this.platformRefreshTimer = setInterval(async () => {
2014
- await this.batchRefreshAllDevices();
2015
- }, Number(refreshRateSec) * 1000);
2089
+ // Respect user toggle
2090
+ if (this.config.options?.matterBatchEnabled === false) {
2091
+ this.infoLog('Matter batch refresh is disabled by configuration');
2092
+ return;
2093
+ }
2094
+ const jitterSec = Number(this.config.options?.matterBatchJitter ?? 0);
2095
+ const intervalMs = Number(refreshRateSec) * 1000;
2096
+ const jitterMs = Number.isFinite(jitterSec) && jitterSec > 0 ? Math.floor(Math.random() * jitterSec * 1000) : 0;
2097
+ // Start after optional jitter, then schedule recurring interval
2098
+ setTimeout(async () => {
2099
+ try {
2100
+ await this.batchRefreshAllDevices();
2101
+ }
2102
+ catch (e) {
2103
+ this.debugLog(`Initial batch refresh failed: ${e?.message ?? e}`);
2104
+ }
2105
+ this.platformRefreshTimer = setInterval(async () => {
2106
+ await this.batchRefreshAllDevices();
2107
+ }, intervalMs);
2108
+ }, jitterMs);
2016
2109
  }
2017
2110
  /**
2018
2111
  * Batch refresh all devices - still makes individual API calls but batches them together
@@ -2032,7 +2125,10 @@ export class SwitchBotMatterPlatform {
2032
2125
  continue;
2033
2126
  }
2034
2127
  const dev = this.discoveredDevices.find(d => this.normalizeDeviceId(d.deviceId) === nid);
2035
- if (dev && !this.perDeviceRefreshSet.has(nid)) {
2128
+ // Skip devices with per-device timers and those in cooldown
2129
+ const now = Date.now();
2130
+ const nextAllowed = this.backoffCooldowns.get(nid) ?? 0;
2131
+ if (dev && !this.perDeviceRefreshSet.has(nid) && now >= nextAllowed) {
2036
2132
  devicesToRefresh.push({ uuid, dev });
2037
2133
  }
2038
2134
  }
@@ -2046,6 +2142,11 @@ export class SwitchBotMatterPlatform {
2046
2142
  return;
2047
2143
  }
2048
2144
  this.debugLog(`Refreshing ${devicesToRefresh.length} devices in parallel batch`);
2145
+ // Randomize order to reduce synchronized spikes
2146
+ for (let i = devicesToRefresh.length - 1; i > 0; i--) {
2147
+ const j = Math.floor(Math.random() * (i + 1));
2148
+ [devicesToRefresh[i], devicesToRefresh[j]] = [devicesToRefresh[j], devicesToRefresh[i]];
2149
+ }
2049
2150
  const concurrency = Number(this.config.options?.matterBatchConcurrency ?? 5);
2050
2151
  await this.runWithConcurrency(devicesToRefresh, async ({ uuid, dev }) => {
2051
2152
  try {
@@ -2088,6 +2189,14 @@ export class SwitchBotMatterPlatform {
2088
2189
  await sleep(delay);
2089
2190
  }
2090
2191
  }
2192
+ // Set a cooldown after exhausting retries to avoid hammering problematic devices
2193
+ try {
2194
+ const nid = this.normalizeDeviceId(deviceId);
2195
+ const cooldownMs = Math.max(60_000, baseDelayMs * (2 ** retries) * 10);
2196
+ this.backoffCooldowns.set(nid, Date.now() + cooldownMs);
2197
+ this.debugLog(`Applied cooldown for ${deviceId}: ${Math.round(cooldownMs / 1000)}s`);
2198
+ }
2199
+ catch { }
2091
2200
  return null;
2092
2201
  }
2093
2202
  /** Simple concurrency limiter for an array of items */