@switchbot/homebridge-switchbot 5.0.0-beta.42 → 5.0.0-beta.44

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/CHANGELOG.md +2 -0
  2. package/README.md +22 -0
  3. package/config.schema.json +71 -9
  4. package/dist/devices-hap/airpurifier.d.ts.map +1 -1
  5. package/dist/devices-hap/airpurifier.js +12 -6
  6. package/dist/devices-hap/airpurifier.js.map +1 -1
  7. package/dist/devices-hap/blindtilt.js +3 -3
  8. package/dist/devices-hap/bot.d.ts.map +1 -1
  9. package/dist/devices-hap/bot.js +16 -5
  10. package/dist/devices-hap/bot.js.map +1 -1
  11. package/dist/devices-hap/ceilinglight.d.ts.map +1 -1
  12. package/dist/devices-hap/ceilinglight.js +13 -7
  13. package/dist/devices-hap/ceilinglight.js.map +1 -1
  14. package/dist/devices-hap/colorbulb.d.ts.map +1 -1
  15. package/dist/devices-hap/colorbulb.js +49 -9
  16. package/dist/devices-hap/colorbulb.js.map +1 -1
  17. package/dist/devices-hap/contact.js +3 -3
  18. package/dist/devices-hap/curtain.js +2 -2
  19. package/dist/devices-hap/curtain.js.map +1 -1
  20. package/dist/devices-hap/device.d.ts.map +1 -1
  21. package/dist/devices-hap/device.js +20 -1
  22. package/dist/devices-hap/device.js.map +1 -1
  23. package/dist/devices-hap/fan.d.ts.map +1 -1
  24. package/dist/devices-hap/fan.js +12 -6
  25. package/dist/devices-hap/fan.js.map +1 -1
  26. package/dist/devices-hap/hub.d.ts.map +1 -1
  27. package/dist/devices-hap/hub.js +6 -5
  28. package/dist/devices-hap/hub.js.map +1 -1
  29. package/dist/devices-hap/humidifier.d.ts +5 -0
  30. package/dist/devices-hap/humidifier.d.ts.map +1 -1
  31. package/dist/devices-hap/humidifier.js +92 -4
  32. package/dist/devices-hap/humidifier.js.map +1 -1
  33. package/dist/devices-hap/iosensor.d.ts.map +1 -1
  34. package/dist/devices-hap/iosensor.js +36 -21
  35. package/dist/devices-hap/iosensor.js.map +1 -1
  36. package/dist/devices-hap/lightstrip.d.ts.map +1 -1
  37. package/dist/devices-hap/lightstrip.js +38 -8
  38. package/dist/devices-hap/lightstrip.js.map +1 -1
  39. package/dist/devices-hap/lock.d.ts.map +1 -1
  40. package/dist/devices-hap/lock.js +14 -6
  41. package/dist/devices-hap/lock.js.map +1 -1
  42. package/dist/devices-hap/meter.d.ts.map +1 -1
  43. package/dist/devices-hap/meter.js +6 -5
  44. package/dist/devices-hap/meter.js.map +1 -1
  45. package/dist/devices-hap/meterplus.d.ts.map +1 -1
  46. package/dist/devices-hap/meterplus.js +6 -5
  47. package/dist/devices-hap/meterplus.js.map +1 -1
  48. package/dist/devices-hap/meterpro.d.ts.map +1 -1
  49. package/dist/devices-hap/meterpro.js +7 -6
  50. package/dist/devices-hap/meterpro.js.map +1 -1
  51. package/dist/devices-hap/motion.js +3 -3
  52. package/dist/devices-hap/plug.d.ts.map +1 -1
  53. package/dist/devices-hap/plug.js +11 -6
  54. package/dist/devices-hap/plug.js.map +1 -1
  55. package/dist/devices-hap/relayswitch.js +3 -3
  56. package/dist/devices-hap/robotvacuumcleaner.d.ts.map +1 -1
  57. package/dist/devices-hap/robotvacuumcleaner.js +13 -6
  58. package/dist/devices-hap/robotvacuumcleaner.js.map +1 -1
  59. package/dist/devices-hap/waterdetector.js +3 -3
  60. package/dist/homebridge-ui/public/index.html +13 -1
  61. package/dist/platform-hap.d.ts +5 -0
  62. package/dist/platform-hap.d.ts.map +1 -1
  63. package/dist/platform-hap.js +106 -16
  64. package/dist/platform-hap.js.map +1 -1
  65. package/dist/platform-matter.d.ts +5 -0
  66. package/dist/platform-matter.d.ts.map +1 -1
  67. package/dist/platform-matter.js +129 -8
  68. package/dist/platform-matter.js.map +1 -1
  69. package/dist/settings.d.ts +17 -1
  70. package/dist/settings.d.ts.map +1 -1
  71. package/dist/settings.js.map +1 -1
  72. package/dist/test/hap/device-webhook-context.test.d.ts +2 -0
  73. package/dist/test/hap/device-webhook-context.test.d.ts.map +1 -0
  74. package/dist/test/hap/device-webhook-context.test.js +128 -0
  75. package/dist/test/hap/device-webhook-context.test.js.map +1 -0
  76. package/dist/test/matter/platform-matter.webhook.test.d.ts +2 -0
  77. package/dist/test/matter/platform-matter.webhook.test.d.ts.map +1 -0
  78. package/dist/test/matter/platform-matter.webhook.test.js +46 -0
  79. package/dist/test/matter/platform-matter.webhook.test.js.map +1 -0
  80. package/dist/utils.d.ts +8 -0
  81. package/dist/utils.d.ts.map +1 -1
  82. package/dist/utils.js +74 -20
  83. package/dist/utils.js.map +1 -1
  84. package/docs/assets/highlight.css +14 -0
  85. package/docs/index.html +12 -1
  86. package/docs/variables/default.html +1 -1
  87. package/package.json +2 -2
  88. package/src/devices-hap/airpurifier.ts +11 -6
  89. package/src/devices-hap/blindtilt.ts +3 -3
  90. package/src/devices-hap/bot.ts +15 -5
  91. package/src/devices-hap/ceilinglight.ts +12 -7
  92. package/src/devices-hap/colorbulb.ts +46 -10
  93. package/src/devices-hap/contact.ts +3 -3
  94. package/src/devices-hap/curtain.ts +2 -2
  95. package/src/devices-hap/device.ts +20 -1
  96. package/src/devices-hap/fan.ts +11 -6
  97. package/src/devices-hap/hub.ts +6 -5
  98. package/src/devices-hap/humidifier.ts +97 -4
  99. package/src/devices-hap/iosensor.ts +36 -21
  100. package/src/devices-hap/lightstrip.ts +35 -8
  101. package/src/devices-hap/lock.ts +13 -6
  102. package/src/devices-hap/meter.ts +6 -5
  103. package/src/devices-hap/meterplus.ts +6 -5
  104. package/src/devices-hap/meterpro.ts +7 -6
  105. package/src/devices-hap/motion.ts +3 -3
  106. package/src/devices-hap/plug.ts +10 -6
  107. package/src/devices-hap/relayswitch.ts +3 -3
  108. package/src/devices-hap/robotvacuumcleaner.ts +12 -6
  109. package/src/devices-hap/waterdetector.ts +3 -3
  110. package/src/homebridge-ui/public/index.html +13 -1
  111. package/src/platform-hap.ts +110 -16
  112. package/src/platform-matter.ts +137 -8
  113. package/src/settings.ts +17 -1
  114. package/src/test/hap/device-webhook-context.test.ts +136 -0
  115. package/src/test/matter/platform-matter.webhook.test.ts +54 -0
  116. package/src/utils.ts +88 -29
@@ -172,6 +172,11 @@ export declare class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
172
172
  private batchRefreshAllDevices;
173
173
  /** Refresh a single device with retry and backoff; returns status object if successful */
174
174
  private refreshSingleDeviceWithRetry;
175
+ /**
176
+ * Validate that the user's configuration won't exceed API limits
177
+ * Warn if device count × polling frequency will hit daily limits
178
+ */
179
+ private validateApiUsageConfig;
175
180
  /** Simple concurrency limiter for an array of items */
176
181
  private runWithConcurrency;
177
182
  }
@@ -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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"}
@@ -139,6 +139,7 @@ export class SwitchBotMatterPlatform {
139
139
  dailyLimit: this.config.options?.dailyApiLimit ?? 10000,
140
140
  reserveForCommands: this.config.options?.dailyApiReserveForCommands ?? 1000,
141
141
  pausePollingAtReserve: this.config.options?.webhookOnlyOnReserve ?? false,
142
+ resetAtLocalMidnight: this.config.options?.dailyApiResetAtLocalMidnight ?? false,
142
143
  });
143
144
  this.apiTracker.startHourlyLogging();
144
145
  }
@@ -384,6 +385,15 @@ export class SwitchBotMatterPlatform {
384
385
  // Merge per-device overrides by matching deviceId
385
386
  const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices);
386
387
  const merged = mergeByDeviceId(this.config.options?.devices ?? [], devicesWithTemplates ?? [], allowConfigOnly);
388
+ // Apply global webhook setting if not explicitly set on individual devices
389
+ if (this.config.options?.webhook === true) {
390
+ merged.forEach((device) => {
391
+ if (device.webhook === undefined) {
392
+ device.webhook = true;
393
+ this.debugLog(`Applied global webhook setting to Matter device: ${device.deviceId}`);
394
+ }
395
+ });
396
+ }
387
397
  return merged;
388
398
  }
389
399
  /**
@@ -412,6 +422,15 @@ export class SwitchBotMatterPlatform {
412
422
  // Merge per-device overrides by matching deviceId
413
423
  const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices);
414
424
  const merged = mergeByDeviceId(this.config.options?.irdevices ?? [], devicesWithTemplates ?? [], allowConfigOnly);
425
+ // Apply global webhook setting if not explicitly set on individual IR devices
426
+ if (this.config.options?.webhook === true) {
427
+ merged.forEach((device) => {
428
+ if (device.webhook === undefined) {
429
+ device.webhook = true;
430
+ this.debugLog(`Applied global webhook setting to Matter IR device: ${device.deviceId}`);
431
+ }
432
+ });
433
+ }
415
434
  return merged;
416
435
  }
417
436
  /**
@@ -460,8 +479,19 @@ export class SwitchBotMatterPlatform {
460
479
  // so accessories can decide how verbose they should be.
461
480
  deviceLogging: dev?.logging,
462
481
  platformLogging: this.platformLogging,
482
+ // Expose effective webhook setting for parity with HAP base device logic
483
+ // Prefer explicit per-device setting; otherwise fall back to global option
484
+ webhook: dev?.webhook !== undefined ? dev.webhook : (this.config.options?.webhook === true ? true : undefined),
463
485
  },
464
486
  };
487
+ // Log effective webhook for diagnostics
488
+ try {
489
+ const effectiveWebhook = dev?.webhook !== undefined ? dev.webhook : (this.config.options?.webhook === true ? true : undefined);
490
+ this.debugLog(`Effective webhook for Matter device ${displayName} (${dev.deviceId}): ${String(effectiveWebhook)}`);
491
+ }
492
+ catch (e) {
493
+ this.debugLog(`Failed logging effective webhook for Matter device ${displayName} (${dev.deviceId}): ${e?.message ?? e}`);
494
+ }
465
495
  // Build platform-side helpers using shared factories so they can be reused/tested
466
496
  const sendOpenAPI = makeOpenAPISender(this.retryCommand.bind(this), dev, { maxRetries: this.config.options?.maxRetries ?? 1, delayBetweenRetries: this.config.options?.delayBetweenRetries ?? 1000 });
467
497
  const sendBLE = makeBLESender(this.switchBotBLE, dev, { bleRetries: this.config.options?.bleRetries ?? 2, bleRetryDelay: this.config.options?.bleRetryDelay ?? 500 });
@@ -536,6 +566,7 @@ export class SwitchBotMatterPlatform {
536
566
  // Locks
537
567
  'Smart Lock': DoorLockAccessory,
538
568
  'Smart Lock Pro': DoorLockAccessory,
569
+ 'Smart Lock Ultra': DoorLockAccessory,
539
570
  // Sensors
540
571
  'Motion Sensor': OccupancySensorAccessory,
541
572
  'Contact Sensor': ContactSensorAccessory,
@@ -1060,6 +1091,8 @@ export class SwitchBotMatterPlatform {
1060
1091
  for (const d of irDeviceList) {
1061
1092
  this.debugLog(` - ${d.deviceName} (${d.remoteType}) id=${d.deviceId}`);
1062
1093
  }
1094
+ // Diagnostic: warn users if their device count + refresh rate may exceed daily limits
1095
+ this.validateApiUsageConfig(deviceList.length, irDeviceList.length);
1063
1096
  }
1064
1097
  else {
1065
1098
  this.warnLog(`SwitchBot getDevices returned status ${statusCode}`);
@@ -1153,15 +1186,16 @@ export class SwitchBotMatterPlatform {
1153
1186
  * Retry wrapper for control commands using SwitchBot OpenAPI
1154
1187
  */
1155
1188
  async retryCommand(deviceObj, bodyChange, maxRetries = 1, delayBetweenRetries = 1000) {
1189
+ // Check API budget BEFORE attempting any retries
1190
+ if (!this.apiTracker?.trySpend('command')) {
1191
+ return { response: {}, statusCode: 429 };
1192
+ }
1156
1193
  let retryCount = 0;
1157
1194
  while (retryCount < maxRetries) {
1158
1195
  try {
1159
1196
  if (!this.switchBotAPI) {
1160
1197
  throw new Error('SwitchBot OpenAPI not initialized');
1161
1198
  }
1162
- if (!this.apiTracker?.trySpend('command')) {
1163
- return { response: {}, statusCode: 429 };
1164
- }
1165
1199
  const { response, statusCode } = await this.switchBotAPI.controlDevice(deviceObj.deviceId, bodyChange.command, bodyChange.parameter, bodyChange.commandType, this.config.credentials?.token, this.config.credentials?.secret);
1166
1200
  return { response, statusCode };
1167
1201
  }
@@ -1412,6 +1446,41 @@ export class SwitchBotMatterPlatform {
1412
1446
  this.debugLog(`Failed to apply runState for ${dev.deviceId}: ${e?.message ?? e}`);
1413
1447
  }
1414
1448
  }
1449
+ // Robot vacuum: some firmwares expose 'taskType' or 'task' with similar semantics
1450
+ // to runState (e.g., 'cleaning', 'mapping', 'idle'). Map to rvcRunMode accordingly.
1451
+ if (status?.taskType !== undefined || status?.task_type !== undefined || status?.task !== undefined) {
1452
+ try {
1453
+ const raw = status?.taskType ?? status?.task_type ?? status?.task;
1454
+ let mode;
1455
+ if (typeof raw === 'number') {
1456
+ mode = Number(raw);
1457
+ }
1458
+ else if (typeof raw === 'string') {
1459
+ const s = raw.toLowerCase();
1460
+ if (s.includes('clean')) {
1461
+ mode = 1; // Cleaning
1462
+ }
1463
+ else if (s.includes('map')) {
1464
+ mode = 2; // Mapping
1465
+ }
1466
+ else if (s.includes('idle') || s.includes('stop') || s.includes('dock') || s.includes('charge') || s.includes('docked')) {
1467
+ mode = 0; // Idle
1468
+ }
1469
+ else {
1470
+ const n = Number(raw);
1471
+ if (!Number.isNaN(n)) {
1472
+ mode = n;
1473
+ }
1474
+ }
1475
+ }
1476
+ if (mode !== undefined) {
1477
+ await safeUpdate('rvcRunMode', { currentMode: Number(mode) }, 'updateRunMode');
1478
+ }
1479
+ }
1480
+ catch (e) {
1481
+ this.debugLog(`Failed to apply taskType for ${dev.deviceId}: ${e?.message ?? e}`);
1482
+ }
1483
+ }
1415
1484
  // Brightness
1416
1485
  if (status?.brightness !== undefined) {
1417
1486
  const rawBrightness = Number(status.brightness);
@@ -1473,7 +1542,8 @@ export class SwitchBotMatterPlatform {
1473
1542
  }
1474
1543
  // Battery/powerSource (support many possible field names)
1475
1544
  // Note: Some device types like WindowBlind don't support PowerSource cluster
1476
- if (status?.battery !== undefined || status?.batt !== undefined || status?.batteryLevel !== undefined || status?.batteryPercentage !== undefined || status?.battery_level !== undefined) {
1545
+ if (status?.battery !== undefined || status?.batt !== undefined || status?.batteryLevel !== undefined || status?.batteryPercentage !== undefined || status?.battery_level !== undefined
1546
+ || status?.baseBattery !== undefined || status?.base_battery !== undefined || status?.stationBattery !== undefined || status?.waterBaseBattery !== undefined || status?.dockBattery !== undefined) {
1477
1547
  // Skip battery updates for device types that don't support PowerSource cluster
1478
1548
  const deviceType = String(status?.deviceType ?? dev?.deviceType ?? '');
1479
1549
  const unsupportedTypes = ['Curtain', 'Curtain2', 'Curtain3', 'Curtain 2', 'Blind Tilt'];
@@ -1482,7 +1552,8 @@ export class SwitchBotMatterPlatform {
1482
1552
  }
1483
1553
  else {
1484
1554
  try {
1485
- const percentage = Number(status?.battery ?? status?.batt ?? status?.batteryPercentage ?? status?.batteryLevel ?? status?.battery_level);
1555
+ const percentage = Number(status?.battery ?? status?.batt ?? status?.batteryPercentage ?? status?.batteryLevel ?? status?.battery_level
1556
+ ?? status?.baseBattery ?? status?.base_battery ?? status?.stationBattery ?? status?.waterBaseBattery ?? status?.dockBattery);
1486
1557
  const batPercentRemaining = Math.max(0, Math.min(200, Math.round(percentage * 2)));
1487
1558
  let batChargeLevel = 0;
1488
1559
  if (percentage < 20) {
@@ -2250,12 +2321,14 @@ export class SwitchBotMatterPlatform {
2250
2321
  /** Refresh a single device with retry and backoff; returns status object if successful */
2251
2322
  async refreshSingleDeviceWithRetry(dev, retries = 3, baseDelayMs = 500) {
2252
2323
  const deviceId = dev.deviceId;
2324
+ // Check API budget BEFORE attempting any retries - don't waste cycles on blocked requests
2325
+ if (!this.apiTracker?.trySpend('poll')) {
2326
+ // Don't log on every blocked request - the ApiRequestTracker handles periodic warnings
2327
+ return null;
2328
+ }
2253
2329
  let attempt = 0;
2254
2330
  while (attempt <= retries) {
2255
2331
  try {
2256
- if (!this.apiTracker?.trySpend('poll')) {
2257
- return null;
2258
- }
2259
2332
  const { response, statusCode } = await this.switchBotAPI.getDeviceStatus(deviceId, this.config.credentials?.token, this.config.credentials?.secret);
2260
2333
  const respAny = response;
2261
2334
  const body = respAny?.body ?? respAny;
@@ -2287,6 +2360,54 @@ export class SwitchBotMatterPlatform {
2287
2360
  catch { }
2288
2361
  return null;
2289
2362
  }
2363
+ /**
2364
+ * Validate that the user's configuration won't exceed API limits
2365
+ * Warn if device count × polling frequency will hit daily limits
2366
+ */
2367
+ validateApiUsageConfig(deviceCount, irDeviceCount) {
2368
+ try {
2369
+ const totalDevices = deviceCount + irDeviceCount;
2370
+ if (totalDevices === 0) {
2371
+ return;
2372
+ }
2373
+ // For Matter platform, use matterBatchRefreshRate or fallback to refreshRate
2374
+ const refreshRate = this.getPlatformBatchInterval(); // seconds
2375
+ const dailyLimit = this.config.options?.dailyApiLimit ?? 10000;
2376
+ const reserveForCommands = this.config.options?.dailyApiReserveForCommands ?? 1000;
2377
+ // Calculate polls per day (86400 seconds in a day)
2378
+ const pollsPerDevicePerDay = Math.floor(86400 / refreshRate);
2379
+ const totalPollsPerDay = pollsPerDevicePerDay * totalDevices;
2380
+ // Add discovery calls (typically 1-2 per day)
2381
+ const estimatedDiscoveryCalls = 2;
2382
+ const totalEstimatedCalls = totalPollsPerDay + estimatedDiscoveryCalls;
2383
+ const usableLimit = dailyLimit - reserveForCommands;
2384
+ const percentOfLimit = Math.round((totalEstimatedCalls / usableLimit) * 100);
2385
+ this.debugLog(`[API Usage Diagnostic] ${totalDevices} devices × ${pollsPerDevicePerDay} polls/day = ${totalPollsPerDay} estimated daily polls`);
2386
+ this.debugLog(`[API Usage Diagnostic] With ${reserveForCommands} reserved for commands, usable limit is ${usableLimit}`);
2387
+ if (totalEstimatedCalls > dailyLimit) {
2388
+ this.errorLog(`⚠️ API LIMIT WARNING: Your configuration will exceed the daily API limit!`);
2389
+ this.errorLog(` Devices: ${totalDevices} | Refresh rate: ${refreshRate}s | Estimated daily polls: ${totalEstimatedCalls}`);
2390
+ this.errorLog(` Daily limit: ${dailyLimit} | You will use ${percentOfLimit}% of available budget`);
2391
+ this.errorLog(` SOLUTION: Increase matterBatchRefreshRate to ${Math.ceil((totalDevices * 86400) / usableLimit)} seconds or higher`);
2392
+ this.errorLog(` OR: Enable webhooks and set 'webhookOnlyOnReserve: true' to reduce polling`);
2393
+ }
2394
+ else if (totalEstimatedCalls > usableLimit) {
2395
+ this.warnLog(`⚠️ API USAGE WARNING: Configuration may exceed usable daily API budget`);
2396
+ this.warnLog(` Devices: ${totalDevices} | Refresh rate: ${refreshRate}s | Estimated daily polls: ${totalEstimatedCalls}`);
2397
+ this.warnLog(` Usable limit (after reserve): ${usableLimit} | You will use ${percentOfLimit}% of budget`);
2398
+ this.warnLog(` Polling may pause when approaching limit. Consider increasing matterBatchRefreshRate to ${Math.ceil((totalDevices * 86400) / usableLimit)}s`);
2399
+ }
2400
+ else if (percentOfLimit > 75) {
2401
+ this.infoLog(`[API Usage] Using ${percentOfLimit}% of daily budget (${totalEstimatedCalls}/${usableLimit} calls). Monitor usage if adding more devices.`);
2402
+ }
2403
+ else {
2404
+ this.debugLog(`[API Usage] Configuration looks good: ${percentOfLimit}% of daily budget (${totalEstimatedCalls}/${usableLimit} calls)`);
2405
+ }
2406
+ }
2407
+ catch (e) {
2408
+ this.debugLog(`Failed to validate API usage config: ${e?.message ?? e}`);
2409
+ }
2410
+ }
2290
2411
  /** Simple concurrency limiter for an array of items */
2291
2412
  async runWithConcurrency(items, worker, concurrency) {
2292
2413
  const queue = items.slice();