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

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 (97) 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/test/matter/devices-matter/baseMatterAccessory.test.d.ts.map +1 -0
  20. package/dist/test/{devices-matter → matter/devices-matter}/baseMatterAccessory.test.js +1 -1
  21. package/dist/test/matter/devices-matter/baseMatterAccessory.test.js.map +1 -0
  22. package/dist/test/matter/platform-matter.additional.test.d.ts.map +1 -0
  23. package/dist/test/{platform-matter.additional.test.js → matter/platform-matter.additional.test.js} +2 -2
  24. package/dist/test/matter/platform-matter.additional.test.js.map +1 -0
  25. package/dist/test/matter/platform-matter.bleparse.test.d.ts.map +1 -0
  26. package/dist/test/{platform-matter.bleparse.test.js → matter/platform-matter.bleparse.test.js} +2 -2
  27. package/dist/test/matter/platform-matter.bleparse.test.js.map +1 -0
  28. package/dist/test/matter/platform-matter.cleanup.test.d.ts.map +1 -0
  29. package/dist/test/{platform-matter.cleanup.test.js → matter/platform-matter.cleanup.test.js} +3 -3
  30. package/dist/test/matter/platform-matter.cleanup.test.js.map +1 -0
  31. package/dist/test/matter/platform-matter.keepstale.test.d.ts.map +1 -0
  32. package/dist/test/{platform-matter.keepstale.test.js → matter/platform-matter.keepstale.test.js} +2 -2
  33. package/dist/test/matter/platform-matter.keepstale.test.js.map +1 -0
  34. package/dist/test/matter/platform-matter.logging.test.d.ts +2 -0
  35. package/dist/test/matter/platform-matter.logging.test.d.ts.map +1 -0
  36. package/dist/test/matter/platform-matter.logging.test.js +29 -0
  37. package/dist/test/matter/platform-matter.logging.test.js.map +1 -0
  38. package/dist/test/matter/platform-matter.mapping.test.d.ts.map +1 -0
  39. package/dist/test/{platform-matter.mapping.test.js → matter/platform-matter.mapping.test.js} +2 -2
  40. package/dist/test/matter/platform-matter.mapping.test.js.map +1 -0
  41. package/dist/test/matter/platform-matter.openapi-mapping.test.d.ts.map +1 -0
  42. package/dist/test/{platform-matter.openapi-mapping.test.js → matter/platform-matter.openapi-mapping.test.js} +2 -2
  43. package/dist/test/matter/platform-matter.openapi-mapping.test.js.map +1 -0
  44. package/dist/test/matter/platform-matter.test.d.ts.map +1 -0
  45. package/dist/test/{platform-matter.test.js → matter/platform-matter.test.js} +3 -3
  46. package/dist/test/matter/platform-matter.test.js.map +1 -0
  47. package/dist/test/matter/platform-matter.unregister.test.d.ts.map +1 -0
  48. package/dist/test/{platform-matter.unregister.test.js → matter/platform-matter.unregister.test.js} +2 -2
  49. package/dist/test/matter/platform-matter.unregister.test.js.map +1 -0
  50. package/dist/utils.d.ts.map +1 -1
  51. package/dist/utils.js +37 -12
  52. package/dist/utils.js.map +1 -1
  53. package/docs/index.html +19 -1
  54. package/docs/variables/default.html +1 -1
  55. package/package.json +3 -3
  56. package/src/platform-matter.ts +118 -11
  57. package/src/settings.ts +2 -0
  58. package/src/test/hap/platform-hap.logging.test.ts +36 -0
  59. package/src/test/hap/platform-hap.test.ts +70 -0
  60. package/src/test/{devices-matter → matter/devices-matter}/baseMatterAccessory.test.ts +1 -1
  61. package/src/test/{platform-matter.additional.test.ts → matter/platform-matter.additional.test.ts} +2 -2
  62. package/src/test/{platform-matter.bleparse.test.ts → matter/platform-matter.bleparse.test.ts} +2 -2
  63. package/src/test/{platform-matter.cleanup.test.ts → matter/platform-matter.cleanup.test.ts} +3 -3
  64. package/src/test/{platform-matter.keepstale.test.ts → matter/platform-matter.keepstale.test.ts} +2 -2
  65. package/src/test/matter/platform-matter.logging.test.ts +33 -0
  66. package/src/test/{platform-matter.mapping.test.ts → matter/platform-matter.mapping.test.ts} +2 -2
  67. package/src/test/{platform-matter.openapi-mapping.test.ts → matter/platform-matter.openapi-mapping.test.ts} +2 -2
  68. package/src/test/{platform-matter.test.ts → matter/platform-matter.test.ts} +3 -3
  69. package/src/test/{platform-matter.unregister.test.ts → matter/platform-matter.unregister.test.ts} +2 -2
  70. package/src/utils.ts +37 -9
  71. package/dist/test/devices-matter/baseMatterAccessory.test.d.ts.map +0 -1
  72. package/dist/test/devices-matter/baseMatterAccessory.test.js.map +0 -1
  73. package/dist/test/platform-matter.additional.test.d.ts.map +0 -1
  74. package/dist/test/platform-matter.additional.test.js.map +0 -1
  75. package/dist/test/platform-matter.bleparse.test.d.ts.map +0 -1
  76. package/dist/test/platform-matter.bleparse.test.js.map +0 -1
  77. package/dist/test/platform-matter.cleanup.test.d.ts.map +0 -1
  78. package/dist/test/platform-matter.cleanup.test.js.map +0 -1
  79. package/dist/test/platform-matter.keepstale.test.d.ts.map +0 -1
  80. package/dist/test/platform-matter.keepstale.test.js.map +0 -1
  81. package/dist/test/platform-matter.mapping.test.d.ts.map +0 -1
  82. package/dist/test/platform-matter.mapping.test.js.map +0 -1
  83. package/dist/test/platform-matter.openapi-mapping.test.d.ts.map +0 -1
  84. package/dist/test/platform-matter.openapi-mapping.test.js.map +0 -1
  85. package/dist/test/platform-matter.test.d.ts.map +0 -1
  86. package/dist/test/platform-matter.test.js.map +0 -1
  87. package/dist/test/platform-matter.unregister.test.d.ts.map +0 -1
  88. package/dist/test/platform-matter.unregister.test.js.map +0 -1
  89. /package/dist/test/{devices-matter → matter/devices-matter}/baseMatterAccessory.test.d.ts +0 -0
  90. /package/dist/test/{platform-matter.additional.test.d.ts → matter/platform-matter.additional.test.d.ts} +0 -0
  91. /package/dist/test/{platform-matter.bleparse.test.d.ts → matter/platform-matter.bleparse.test.d.ts} +0 -0
  92. /package/dist/test/{platform-matter.cleanup.test.d.ts → matter/platform-matter.cleanup.test.d.ts} +0 -0
  93. /package/dist/test/{platform-matter.keepstale.test.d.ts → matter/platform-matter.keepstale.test.d.ts} +0 -0
  94. /package/dist/test/{platform-matter.mapping.test.d.ts → matter/platform-matter.mapping.test.d.ts} +0 -0
  95. /package/dist/test/{platform-matter.openapi-mapping.test.d.ts → matter/platform-matter.openapi-mapping.test.d.ts} +0 -0
  96. /package/dist/test/{platform-matter.test.d.ts → matter/platform-matter.test.d.ts} +0 -0
  97. /package/dist/test/{platform-matter.unregister.test.d.ts → matter/platform-matter.unregister.test.d.ts} +0 -0
package/docs/index.html CHANGED
@@ -373,6 +373,24 @@ Without this step, then you will receive the following error when the swichbot p
373
373
  </ul>
374
374
  </li>
375
375
  </ul>
376
+ <h2 id="matter-platform" class="tsd-anchor-link">Matter Platform<a href="#matter-platform" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><h3 id="batched-refresh-and-api-load-control" class="tsd-anchor-link">Batched refresh and API load control<a href="#batched-refresh-and-api-load-control" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><p>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 <code>options</code>:</p>
377
+ <ul>
378
+ <li><code>matterBatchEnabled</code> (boolean, default true): enable/disable platform-level batched refresh. Devices with a per-device <code>refreshRate</code> still run their own timers.</li>
379
+ <li><code>matterBatchRefreshRate</code> (number, seconds): batch interval (falls back to <code>options.refreshRate</code>, then 300 if not set).</li>
380
+ <li><code>matterBatchConcurrency</code> (number): limit of parallel OpenAPI status calls during a batch (default 5).</li>
381
+ <li><code>matterBatchJitter</code> (number, seconds): random startup delay before the first batch to reduce synchronized spikes.</li>
382
+ </ul>
383
+ <p>Device-level override:</p>
384
+ <ul>
385
+ <li>If a device sets <code>refreshRate</code> in its config, it uses a per-device timer and is excluded from the platform batch.</li>
386
+ </ul>
387
+ <p>Reliability and rate-limiting:</p>
388
+ <ul>
389
+ <li>Each device status call retries with exponential backoff on non-success responses.</li>
390
+ <li>After exhausting retries, the device enters a short cooldown before being retried; cooldowns are persisted across restarts.</li>
391
+ <li>The batch worklist is randomized every cycle to further distribute API load.</li>
392
+ </ul>
393
+ <p>These controls keep API usage smooth and predictable while preserving per-device control when needed.</p>
376
394
  <h2 id="switchbot-apis" class="tsd-anchor-link">SwitchBot APIs<a href="#switchbot-apis" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><ul>
377
395
  <li><a href="https://github.com/OpenWonderLabs/node-switchbot">OpenWonderLabs/node-switchbot</a>
378
396
  <ul>
@@ -386,4 +404,4 @@ Without this step, then you will receive the following error when the swichbot p
386
404
  <li><a href="https://www.facebook.com/SwitchBotRobot/">Facebook @SwitchBotRobot</a></li>
387
405
  <li><a href="https://twitter.com/switchbot">Twitter @SwitchBot</a></li>
388
406
  </ul>
389
- </div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#switchbothomebridge-switchbot"><span>@switchbot/homebridge-<wbr/>switchbot</span></a><ul><li><a href="#installation"><span>Installation</span></a></li><li><a href="#configuration"><span>Configuration</span></a></li><li><ul><li><a href="#if-using-openapi-connection"><span>If using <wbr/>Open<wbr/>API <wbr/>Connection</span></a></li><li><a href="#if-using-ble-connection"><span>If using <wbr/>BLE <wbr/>Connection</span></a></li></ul></li><li><a href="#troubleshooting"><span>Troubleshooting</span></a></li><li><ul><li><a href="#if-using-linux-raspberry-pi-os"><span>If using <wbr/>Linux / <wbr/>Raspberry <wbr/>Pi <wbr/>OS</span></a></li><li><a href="#if-using-macos"><span>If using <wbr/>Mac<wbr/>OS</span></a></li></ul></li><li><a href="#supported-switchbot-devices"><span>Supported <wbr/>Switch<wbr/>Bot <wbr/>Devices</span></a></li><li><a href="#supported-ir-devices"><span>Supported <wbr/>IR <wbr/>Devices</span></a></li><li><ul><li><a href="#all-ir-devices-require-switchbot-hub-2-switchbot-hub-3-or-hub-mini"><span>(<wbr/>All <wbr/>IR <wbr/>Devices require <wbr/>Switch<wbr/>Bot <wbr/>Hub 2, <wbr/>Switch<wbr/>Bot <wbr/>Hub 3, or <wbr/>Hub <wbr/>Mini)</span></a></li></ul></li><li><a href="#switchbot-apis"><span>Switch<wbr/>Bot <wbr/>AP<wbr/>Is</span></a></li><li><a href="#community"><span>Community</span></a></li></ul></div></details></div><div class="site-menu"><nav class="tsd-navigation"><a href="modules.html">@switchbot/homebridge-switchbot</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
407
+ </div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#switchbothomebridge-switchbot"><span>@switchbot/homebridge-<wbr/>switchbot</span></a><ul><li><a href="#installation"><span>Installation</span></a></li><li><a href="#configuration"><span>Configuration</span></a></li><li><ul><li><a href="#if-using-openapi-connection"><span>If using <wbr/>Open<wbr/>API <wbr/>Connection</span></a></li><li><a href="#if-using-ble-connection"><span>If using <wbr/>BLE <wbr/>Connection</span></a></li></ul></li><li><a href="#troubleshooting"><span>Troubleshooting</span></a></li><li><ul><li><a href="#if-using-linux-raspberry-pi-os"><span>If using <wbr/>Linux / <wbr/>Raspberry <wbr/>Pi <wbr/>OS</span></a></li><li><a href="#if-using-macos"><span>If using <wbr/>Mac<wbr/>OS</span></a></li></ul></li><li><a href="#supported-switchbot-devices"><span>Supported <wbr/>Switch<wbr/>Bot <wbr/>Devices</span></a></li><li><a href="#supported-ir-devices"><span>Supported <wbr/>IR <wbr/>Devices</span></a></li><li><ul><li><a href="#all-ir-devices-require-switchbot-hub-2-switchbot-hub-3-or-hub-mini"><span>(<wbr/>All <wbr/>IR <wbr/>Devices require <wbr/>Switch<wbr/>Bot <wbr/>Hub 2, <wbr/>Switch<wbr/>Bot <wbr/>Hub 3, or <wbr/>Hub <wbr/>Mini)</span></a></li></ul></li><li><a href="#matter-platform"><span>Matter <wbr/>Platform</span></a></li><li><ul><li><a href="#batched-refresh-and-api-load-control"><span>Batched refresh and <wbr/>API load control</span></a></li></ul></li><li><a href="#switchbot-apis"><span>Switch<wbr/>Bot <wbr/>AP<wbr/>Is</span></a></li><li><a href="#community"><span>Community</span></a></li></ul></div></details></div><div class="site-menu"><nav class="tsd-navigation"><a href="modules.html">@switchbot/homebridge-switchbot</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>default | @switchbot/homebridge-switchbot</title><meta name="description" content="Documentation for @switchbot/homebridge-switchbot"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@switchbot/homebridge-switchbot</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">default</a></li></ul><h1>Variable default</h1></div><div class="tsd-signature"><span class="tsd-kind-variable">default</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span> <span class="tsd-signature-symbol">=&gt;</span> <span class="tsd-signature-type">void</span></div><div class="tsd-type-declaration"><h4>Type Declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures"><li class="tsd-signature" id="__type"><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">void</span></li><li class="tsd-description"><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/OpenWonderLabs/homebridge-switchbot/blob/7ef570a83c474096eb9ab1d68acdbf9681c65204/src/index.ts#L13">index.ts:13</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">@switchbot/homebridge-switchbot</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>default | @switchbot/homebridge-switchbot</title><meta name="description" content="Documentation for @switchbot/homebridge-switchbot"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@switchbot/homebridge-switchbot</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">default</a></li></ul><h1>Variable default</h1></div><div class="tsd-signature"><span class="tsd-kind-variable">default</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span> <span class="tsd-signature-symbol">=&gt;</span> <span class="tsd-signature-type">void</span></div><div class="tsd-type-declaration"><h4>Type Declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures"><li class="tsd-signature" id="__type"><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">void</span></li><li class="tsd-description"><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/OpenWonderLabs/homebridge-switchbot/blob/43cdf5e76dd30e281e39cf30d76122f4297960db/src/index.ts#L13">index.ts:13</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">@switchbot/homebridge-switchbot</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@switchbot/homebridge-switchbot",
3
3
  "displayName": "SwitchBot",
4
4
  "type": "module",
5
- "version": "5.0.0-beta.30",
5
+ "version": "5.0.0-beta.31",
6
6
  "description": "The SwitchBot plugin allows you to access your SwitchBot device(s) from HomeKit.",
7
7
  "author": "SwitchBot <support@wondertechlabs.com> (https://github.com/SwitchBot)",
8
8
  "contributors": [
@@ -94,7 +94,7 @@
94
94
  "@types/node": "^24.9.2",
95
95
  "@types/semver": "^7.7.1",
96
96
  "@types/source-map-support": "^0.5.10",
97
- "@vitest/coverage-v8": "^4.0.5",
97
+ "@vitest/coverage-v8": "^4.0.6",
98
98
  "eslint": "^9.38.0",
99
99
  "eslint-plugin-format": "^1.0.2",
100
100
  "eslint-plugin-import": "^2.32.0",
@@ -105,7 +105,7 @@
105
105
  "ts-node": "^10.9.2",
106
106
  "typedoc": "^0.28.14",
107
107
  "typescript": "^5.9.3",
108
- "vitest": "^4.0.5"
108
+ "vitest": "^4.0.6"
109
109
  },
110
110
  "directories": {
111
111
  "doc": "docs"
@@ -12,6 +12,9 @@ import type { bodyChange, device } from 'node-switchbot'
12
12
 
13
13
  import type { devicesConfig, SwitchBotPlatformConfig } from './settings.js'
14
14
 
15
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs'
16
+ import { join } from 'node:path'
17
+
15
18
  import asyncmqtt from 'async-mqtt'
16
19
  import { SwitchBotBLE, SwitchBotOpenAPI } from 'node-switchbot'
17
20
 
@@ -61,6 +64,9 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
61
64
  private platformRefreshTimer?: NodeJS.Timeout
62
65
  // Device status cache from last refresh
63
66
  private deviceStatusCache: Map<string, any> = new Map()
67
+ // Backoff cooldowns persisted across restarts: normalized deviceId -> nextAllowedAt(ms)
68
+ private backoffCooldowns: Map<string, number> = new Map()
69
+ private backoffFilePath?: string
64
70
  // Devices that have explicit per-device refresh timers (normalized deviceId)
65
71
  private perDeviceRefreshSet: Set<string> = new Set()
66
72
  // BLE event handlers keyed by device MAC (formatted)
@@ -279,6 +285,22 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
279
285
  } catch (e: any) {
280
286
  this.debugLog(`Failed to stop BLE scanning: ${e?.message ?? e}`)
281
287
  }
288
+
289
+ // Persist backoff cooldowns to disk
290
+ try {
291
+ if (this.backoffFilePath) {
292
+ const obj: Record<string, number> = {}
293
+ for (const [k, v] of this.backoffCooldowns.entries()) {
294
+ if (Number.isFinite(v)) {
295
+ obj[k] = v
296
+ }
297
+ }
298
+ writeFileSync(this.backoffFilePath, JSON.stringify(obj, null, 2))
299
+ this.debugLog(`Saved ${Object.keys(obj).length} backoff cooldown entries`)
300
+ }
301
+ } catch (e: any) {
302
+ this.debugLog(`Failed to save backoff state: ${e?.message ?? e}`)
303
+ }
282
304
  } catch (e: any) {
283
305
  this.debugLog('Shutdown cleanup failed: %s', e?.message ?? e)
284
306
  }
@@ -314,6 +336,31 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
314
336
  } catch (e: any) {
315
337
  this.errorLog(`Setup Webhook, Error Message: ${e?.message ?? e}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug')
316
338
  }
339
+
340
+ // Initialize backoff persistence file path and load any saved cooldowns
341
+ try {
342
+ const storagePath = this.api.user.storagePath()
343
+ this.backoffFilePath = join(storagePath, `${PLUGIN_NAME.replace('@', '').replace('/', '-')}-matter-backoff.json`)
344
+ if (existsSync(this.backoffFilePath)) {
345
+ try {
346
+ const raw = readFileSync(this.backoffFilePath, 'utf8')
347
+ const parsed = JSON.parse(raw)
348
+ if (parsed && typeof parsed === 'object') {
349
+ for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
350
+ const ts = Number(v)
351
+ if (Number.isFinite(ts)) {
352
+ this.backoffCooldowns.set(String(k), ts)
353
+ }
354
+ }
355
+ this.debugLog(`Loaded ${this.backoffCooldowns.size} backoff cooldown entries`)
356
+ }
357
+ } catch (e: any) {
358
+ this.debugLog(`Failed to load backoff state: ${e?.message ?? e}`)
359
+ }
360
+ }
361
+ } catch (e: any) {
362
+ this.debugLog(`Failed to initialize backoff persistence: ${e?.message ?? e}`)
363
+ }
317
364
  }
318
365
 
319
366
  /**
@@ -1006,6 +1053,12 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
1006
1053
  this.perDeviceRefreshSet.add(nid)
1007
1054
  const timer = setInterval(async () => {
1008
1055
  try {
1056
+ // Skip if device is under cooldown
1057
+ const now = Date.now()
1058
+ const nextAllowed = this.backoffCooldowns.get(nid) ?? 0
1059
+ if (now < nextAllowed) {
1060
+ return
1061
+ }
1009
1062
  await this.refreshSingleDeviceWithRetry(dev)
1010
1063
  } catch (e: any) {
1011
1064
  this.debugLog(`Per-device refresh failed for ${dev.deviceId}: ${e?.message ?? e}`)
@@ -1315,12 +1368,37 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
1315
1368
  // Helper to safely call instance methods or fallback to api.matter.updateAccessoryState
1316
1369
  const safeUpdate = async (cluster: string, attributes: Record<string, unknown>, methodName?: string) => {
1317
1370
  try {
1318
- if (instance && methodName && typeof instance[methodName] === 'function') {
1371
+ // Special-case: powerSource cluster is optional on many devices (e.g., Curtains/Blinds).
1372
+ // To avoid noisy Matter server errors ("Behavior ID powerSource does not exist"),
1373
+ // always use the direct updateAccessoryState path wrapped in a guard for this cluster,
1374
+ // even when an accessory instance is present.
1375
+ const powerClusterName = (this.api.matter?.clusterNames && (this.api.matter.clusterNames as any).PowerSource)
1376
+ ? (this.api.matter.clusterNames as any).PowerSource
1377
+ : 'powerSource'
1378
+ const isPowerSourceCluster = cluster === powerClusterName || cluster === 'powerSource'
1379
+
1380
+ // If the accessory instance declares supported clusters, skip updates for clusters
1381
+ // not present to avoid triggering Matter server errors and logs.
1382
+ let clusterSupported = true
1383
+ if (instance && (instance as any).clusters) {
1384
+ const declared = (instance as any).clusters
1385
+ if (Array.isArray(declared)) {
1386
+ clusterSupported = declared.includes(cluster)
1387
+ } else if (typeof declared === 'object') {
1388
+ clusterSupported = Object.prototype.hasOwnProperty.call(declared, cluster)
1389
+ }
1390
+ if (!clusterSupported) {
1391
+ this.debugLog(`Cluster ${cluster} not declared on accessory for ${dev.deviceId}, skipping update`)
1392
+ return
1393
+ }
1394
+ }
1395
+
1396
+ if (instance && methodName && typeof (instance as any)[methodName] === 'function') {
1319
1397
  // prefer device-specific update helpers when available
1320
- await instance[methodName](...(Object.values(attributes)))
1321
- } else if (instance && typeof instance.updateState === 'function') {
1398
+ await (instance as any)[methodName](...(Object.values(attributes)))
1399
+ } else if (!isPowerSourceCluster && instance && typeof (instance as any).updateState === 'function') {
1322
1400
  // some accessories expose updateState that accepts cluster and attributes
1323
- await instance.updateState(cluster, attributes)
1401
+ await (instance as any).updateState(cluster, attributes)
1324
1402
  } else {
1325
1403
  try {
1326
1404
  await this.api.matter.updateAccessoryState(uuidLocal, cluster, attributes)
@@ -1784,7 +1862,7 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
1784
1862
  }
1785
1863
 
1786
1864
  // If no discovered devices are available, do not register example/demo accessories.
1787
- this.infoLog('No discovered SwitchBot devices found; not registering example Matter accessories by default.')
1865
+ this.infoLog('No discovered SwitchBot devices found.')
1788
1866
 
1789
1867
  this.debugLog('═'.repeat(80))
1790
1868
  this.debugLog('Finished registering Matter accessories')
@@ -2133,11 +2211,25 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
2133
2211
  if (this.platformRefreshTimer) {
2134
2212
  return
2135
2213
  }
2136
-
2137
- this.debugLog(`Starting platform-level refresh timer with interval ${refreshRateSec}s`)
2138
- this.platformRefreshTimer = setInterval(async () => {
2139
- await this.batchRefreshAllDevices()
2140
- }, Number(refreshRateSec) * 1000)
2214
+ // Respect user toggle
2215
+ if (this.config.options?.matterBatchEnabled === false) {
2216
+ this.infoLog('Matter batch refresh is disabled by configuration')
2217
+ return
2218
+ }
2219
+ const jitterSec = Number(this.config.options?.matterBatchJitter ?? 0)
2220
+ const intervalMs = Number(refreshRateSec) * 1000
2221
+ const jitterMs = Number.isFinite(jitterSec) && jitterSec > 0 ? Math.floor(Math.random() * jitterSec * 1000) : 0
2222
+ // Start after optional jitter, then schedule recurring interval
2223
+ setTimeout(async () => {
2224
+ try {
2225
+ await this.batchRefreshAllDevices()
2226
+ } catch (e: any) {
2227
+ this.debugLog(`Initial batch refresh failed: ${e?.message ?? e}`)
2228
+ }
2229
+ this.platformRefreshTimer = setInterval(async () => {
2230
+ await this.batchRefreshAllDevices()
2231
+ }, intervalMs)
2232
+ }, jitterMs)
2141
2233
  }
2142
2234
 
2143
2235
  /**
@@ -2160,7 +2252,10 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
2160
2252
  continue
2161
2253
  }
2162
2254
  const dev = this.discoveredDevices.find(d => this.normalizeDeviceId(d.deviceId) === nid)
2163
- if (dev && !this.perDeviceRefreshSet.has(nid)) {
2255
+ // Skip devices with per-device timers and those in cooldown
2256
+ const now = Date.now()
2257
+ const nextAllowed = this.backoffCooldowns.get(nid) ?? 0
2258
+ if (dev && !this.perDeviceRefreshSet.has(nid) && now >= nextAllowed) {
2164
2259
  devicesToRefresh.push({ uuid, dev })
2165
2260
  }
2166
2261
  }
@@ -2176,6 +2271,11 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
2176
2271
 
2177
2272
  this.debugLog(`Refreshing ${devicesToRefresh.length} devices in parallel batch`)
2178
2273
 
2274
+ // Randomize order to reduce synchronized spikes
2275
+ for (let i = devicesToRefresh.length - 1; i > 0; i--) {
2276
+ const j = Math.floor(Math.random() * (i + 1))
2277
+ ;[devicesToRefresh[i], devicesToRefresh[j]] = [devicesToRefresh[j], devicesToRefresh[i]]
2278
+ }
2179
2279
  const concurrency = Number(this.config.options?.matterBatchConcurrency ?? 5)
2180
2280
  await this.runWithConcurrency(devicesToRefresh, async ({ uuid, dev }) => {
2181
2281
  try {
@@ -2217,6 +2317,13 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
2217
2317
  await sleep(delay)
2218
2318
  }
2219
2319
  }
2320
+ // Set a cooldown after exhausting retries to avoid hammering problematic devices
2321
+ try {
2322
+ const nid = this.normalizeDeviceId(deviceId)
2323
+ const cooldownMs = Math.max(60_000, baseDelayMs * (2 ** retries) * 10)
2324
+ this.backoffCooldowns.set(nid, Date.now() + cooldownMs)
2325
+ this.debugLog(`Applied cooldown for ${deviceId}: ${Math.round(cooldownMs / 1000)}s`)
2326
+ } catch {}
2220
2327
  return null
2221
2328
  }
2222
2329
 
package/src/settings.ts CHANGED
@@ -64,6 +64,8 @@ export interface options {
64
64
  // Matter platform batch refresh options
65
65
  matterBatchRefreshRate?: number
66
66
  matterBatchConcurrency?: number
67
+ matterBatchEnabled?: boolean
68
+ matterBatchJitter?: number
67
69
  };
68
70
 
69
71
  export type devicesConfig = botConfig | relaySwitch1Config | relaySwitch1PMConfig | meterConfig | meterProConfig | indoorOutdoorSensorConfig | humidifierConfig | curtainConfig | blindTiltConfig | contactConfig | motionConfig | waterDetectorConfig | plugConfig | colorBulbConfig | stripLightConfig | ceilingLightConfig | lockConfig | hubConfig
@@ -0,0 +1,36 @@
1
+ /* eslint-disable import/first */
2
+ import { describe, expect, it, vi } from 'vitest'
3
+ // Mock modules used by the HAP platform constructor to avoid requiring a real Homebridge API
4
+ vi.mock('fakegato-history', () => ({ default: () => ({}) }))
5
+ vi.mock('homebridge-lib/EveHomeKitTypes', () => ({ EveHomeKitTypes: class { constructor() {} } }))
6
+
7
+ import { SwitchBotHAPPlatform } from '../../platform-hap.js'
8
+ import { makeLogStub } from '../helpers/platform-fixtures.js'
9
+
10
+ /**
11
+ * Verifies that HAP platform debug logger includes the accessory displayName
12
+ * when loading an accessory from the cache.
13
+ */
14
+ describe('platform-hap logging', () => {
15
+ it('prints accessory name when loading HAP cached accessory', async () => {
16
+ const api: any = { on: vi.fn() }
17
+ const log: any = makeLogStub()
18
+
19
+ const platform = new SwitchBotHAPPlatform(log as any, {
20
+ name: 'SwitchBot',
21
+ credentials: {},
22
+ options: { logging: 'debug' },
23
+ devices: [],
24
+ } as any, api)
25
+
26
+ const accessory: any = { displayName: 'Test HAP Device' }
27
+ await (platform as any).configureAccessory(accessory)
28
+
29
+ // Allow async logger to flush
30
+ await new Promise(resolve => setTimeout(resolve, 0))
31
+
32
+ const calls = (log.info as any).mock.calls as Array<string[]>
33
+ const hasLine = calls.some(args => String(args[0]).includes('Loading accessory from cache: Test HAP Device'))
34
+ expect(hasLine).toBe(true)
35
+ })
36
+ })
@@ -0,0 +1,70 @@
1
+ /* eslint-disable import/first */
2
+ import { describe, expect, it, vi } from 'vitest'
3
+ // Mock modules used by the HAP platform constructor to avoid requiring a real Homebridge API
4
+ vi.mock('fakegato-history', () => ({ default: () => ({}) }))
5
+ vi.mock('homebridge-lib/EveHomeKitTypes', () => ({ EveHomeKitTypes: class { constructor() {} } }))
6
+
7
+ import { SwitchBotHAPPlatform } from '../../platform-hap.js'
8
+ import { makeLogStub } from '../helpers/platform-fixtures.js'
9
+
10
+ /**
11
+ * High-level smoke tests for the HAP platform to ensure it initializes,
12
+ * subscribes to lifecycle events, and handles cached accessories.
13
+ */
14
+ describe('platform-hap (smoke)', () => {
15
+ it('initializes and subscribes to didFinishLaunching', async () => {
16
+ const api: any = { on: vi.fn() }
17
+ const log: any = makeLogStub()
18
+
19
+ // Enable debug so debug* logs print via our shared logger
20
+ // Construct the platform (smoke)
21
+ new SwitchBotHAPPlatform(log as any, {
22
+ name: 'SwitchBot',
23
+ credentials: {},
24
+ options: { logging: 'debug' },
25
+ devices: [],
26
+ } as any, api)
27
+
28
+ // Should register didFinishLaunching handler
29
+ expect(api.on).toHaveBeenCalled()
30
+ const calledWithDL = (api.on as any).mock.calls.some((args: any[]) => args[0] === 'didFinishLaunching' && typeof args[1] === 'function')
31
+ expect(calledWithDL).toBe(true)
32
+
33
+ // Should log effective platform logging at startup
34
+ const debugCalls = (log.debug as any).mock.calls as Array<string[]>
35
+ const hasStartupLine = debugCalls.some(args => String(args[0]).includes('[SwitchBot HAP] effective platformLogging='))
36
+ expect(hasStartupLine).toBe(true)
37
+
38
+ // Missing credentials results in a debug error log
39
+ // Allow async platform logger to flush
40
+ await new Promise(resolve => setTimeout(resolve, 0))
41
+ const errorCalls = (log.error as any).mock.calls as Array<string[]>
42
+ const hasMissingCreds = errorCalls.some(args => String(args[0]).includes('Missing SwitchBot API credentials'))
43
+ expect(hasMissingCreds).toBe(true)
44
+ })
45
+
46
+ it('adds cached accessories via configureAccessory', async () => {
47
+ const api: any = { on: vi.fn() }
48
+ const log: any = makeLogStub()
49
+
50
+ const platform = new SwitchBotHAPPlatform(log as any, {
51
+ name: 'SwitchBot',
52
+ credentials: {},
53
+ options: { logging: 'debug' },
54
+ devices: [],
55
+ } as any, api)
56
+
57
+ const accessory: any = { displayName: 'Cached Device' }
58
+ await (platform as any).configureAccessory(accessory)
59
+
60
+ // Should be stored in platform.accessories
61
+ expect(Array.isArray((platform as any).accessories)).toBe(true)
62
+ expect((platform as any).accessories.length).toBe(1)
63
+
64
+ // And should log the cache load message including device name
65
+ await new Promise(resolve => setTimeout(resolve, 0))
66
+ const infoCalls = (log.info as any).mock.calls as Array<string[]>
67
+ const hasLine = infoCalls.some(args => String(args[0]).includes('Loading accessory from cache: Cached Device'))
68
+ expect(hasLine).toBe(true)
69
+ })
70
+ })
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { BaseMatterAccessory } from '../../devices-matter/BaseMatterAccessory.js'
3
+ import { BaseMatterAccessory } from '../../../devices-matter/BaseMatterAccessory.js'
4
4
 
5
5
  // Minimal concrete subclass for testing
6
6
  class TestAccessory extends BaseMatterAccessory {
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { SwitchBotMatterPlatform } from '../platform-matter.js'
4
- import { makeApiStub, makeLogStub } from './helpers/platform-fixtures.js'
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
5
5
 
6
6
  describe('additional platform-matter mapping tests', () => {
7
7
  it('parses OpenAPI color strings and triggers an update', async () => {
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { SwitchBotMatterPlatform } from '../platform-matter.js'
4
- import { makeApiStub, makeLogStub } from './helpers/platform-fixtures.js'
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
5
5
 
6
6
  describe('platform-matter BLE advertisement parser', () => {
7
7
  it('parses extended serviceData fields correctly', () => {
@@ -1,8 +1,8 @@
1
1
  import { describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { SwitchBotMatterPlatform } from '../platform-matter.js'
4
- import { formatDeviceIdAsMac } from '../utils.js'
5
- import { makeApiStub, makeLogStub } from './helpers/platform-fixtures.js'
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { formatDeviceIdAsMac } from '../../utils.js'
5
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
6
6
 
7
7
  describe('platform-matter lifecycle cleanup', () => {
8
8
  it('clearDeviceResources removes timers, instances and BLE handler entries', async () => {
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { SwitchBotMatterPlatform } from '../platform-matter.js'
4
- import { makeApiStub, makeLogStub } from './helpers/platform-fixtures.js'
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
5
5
 
6
6
  describe('keepStaleAccessories config flag behavior', () => {
7
7
  it('keeps previously-registered accessories when options.keepStaleAccessories=true', async () => {
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
5
+
6
+ /**
7
+ * Verifies that debug logger formats arguments so that accessory displayName
8
+ * appears in the output for cached accessory load logs.
9
+ */
10
+ describe('platform-matter logging', () => {
11
+ it('prints accessory name when loading cached Matter accessory', async () => {
12
+ const api: any = makeApiStub()
13
+ const log: any = makeLogStub()
14
+
15
+ const platform = new SwitchBotMatterPlatform(log as any, {
16
+ name: 'SwitchBot',
17
+ credentials: {},
18
+ options: { logging: 'debug' },
19
+ } as any, api)
20
+
21
+ // Simulate Homebridge restoring a cached Matter accessory
22
+ const acc: any = { uuid: 'uuid-TEST', displayName: 'Test Device', context: { deviceId: 'DEV1' } }
23
+ ;(platform as any).configureMatterAccessory(acc)
24
+ // debugLog is async and gated; yield to allow the logger to run
25
+ await new Promise(resolve => setTimeout(resolve, 0))
26
+
27
+ // In 'debug' mode, debugLog uses log.info with a [DEBUG] prefix
28
+ // Ensure one of the info calls contains the message with the device name
29
+ const calls = (log.info as any).mock.calls as Array<string[]>
30
+ const hasLine = calls.some(args => String(args[0]).includes('Loading cached Matter accessory: Test Device'))
31
+ expect(hasLine).toBe(true)
32
+ })
33
+ })
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { SwitchBotMatterPlatform } from '../platform-matter.js'
4
- import { makeApiStub, makeLogStub } from './helpers/platform-fixtures.js'
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
5
5
 
6
6
  describe('platform-matter mapping helper', () => {
7
7
  it('prefers accessory instance update methods for battery and falls back to api.matter.updateAccessoryState', async () => {
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { SwitchBotMatterPlatform } from '../platform-matter.js'
4
- import { makeApiStub, makeLogStub } from './helpers/platform-fixtures.js'
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
5
5
 
6
6
  describe('platform-matter OpenAPI -> Matter mapping', () => {
7
7
  it('maps light OpenAPI status (on/off, brightness, color, battery) to accessory instance helpers', async () => {
@@ -1,8 +1,8 @@
1
1
  import { describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { SwitchBotMatterPlatform } from '../platform-matter.js'
4
- import { PLATFORM_NAME, PLUGIN_NAME } from '../settings.js'
5
- import { makeApiStub, makeLogStub } from './helpers/platform-fixtures.js'
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { PLATFORM_NAME, PLUGIN_NAME } from '../../settings.js'
5
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
6
6
 
7
7
  describe('platform-matter discovered devices', () => {
8
8
  it('uses discovered devices and registers them (platform + robotic)', async () => {
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { SwitchBotMatterPlatform } from '../platform-matter.js'
4
- import { makeApiStub, makeLogStub } from './helpers/platform-fixtures.js'
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
5
5
 
6
6
  describe('removeDisabledAccessories and unregister edge cases', () => {
7
7
  it('clears resources and unregisters accessory even with invalid timer and non-MAC deviceId', async () => {