@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.
- package/README.md +23 -3
- package/config.schema.json +28 -0
- package/dist/platform-matter.d.ts +2 -0
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +116 -7
- package/dist/platform-matter.js.map +1 -1
- package/dist/settings.d.ts +2 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- package/dist/test/hap/platform-hap.logging.test.d.ts +2 -0
- package/dist/test/hap/platform-hap.logging.test.d.ts.map +1 -0
- package/dist/test/hap/platform-hap.logging.test.js +33 -0
- package/dist/test/hap/platform-hap.logging.test.js.map +1 -0
- package/dist/test/hap/platform-hap.test.d.ts +2 -0
- package/dist/test/hap/platform-hap.test.d.ts.map +1 -0
- package/dist/test/hap/platform-hap.test.js +62 -0
- package/dist/test/hap/platform-hap.test.js.map +1 -0
- package/dist/test/helpers/platform-fixtures.d.ts +5 -5
- package/dist/test/matter/devices-matter/baseMatterAccessory.test.d.ts.map +1 -0
- package/dist/test/{devices-matter → matter/devices-matter}/baseMatterAccessory.test.js +1 -1
- package/dist/test/matter/devices-matter/baseMatterAccessory.test.js.map +1 -0
- package/dist/test/matter/platform-matter.additional.test.d.ts.map +1 -0
- package/dist/test/{platform-matter.additional.test.js → matter/platform-matter.additional.test.js} +2 -2
- package/dist/test/matter/platform-matter.additional.test.js.map +1 -0
- package/dist/test/matter/platform-matter.bleparse.test.d.ts.map +1 -0
- package/dist/test/{platform-matter.bleparse.test.js → matter/platform-matter.bleparse.test.js} +2 -2
- package/dist/test/matter/platform-matter.bleparse.test.js.map +1 -0
- package/dist/test/matter/platform-matter.cleanup.test.d.ts.map +1 -0
- package/dist/test/{platform-matter.cleanup.test.js → matter/platform-matter.cleanup.test.js} +3 -3
- package/dist/test/matter/platform-matter.cleanup.test.js.map +1 -0
- package/dist/test/matter/platform-matter.keepstale.test.d.ts.map +1 -0
- package/dist/test/{platform-matter.keepstale.test.js → matter/platform-matter.keepstale.test.js} +2 -2
- package/dist/test/matter/platform-matter.keepstale.test.js.map +1 -0
- package/dist/test/matter/platform-matter.logging.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.logging.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.logging.test.js +29 -0
- package/dist/test/matter/platform-matter.logging.test.js.map +1 -0
- package/dist/test/matter/platform-matter.mapping.test.d.ts.map +1 -0
- package/dist/test/{platform-matter.mapping.test.js → matter/platform-matter.mapping.test.js} +2 -2
- package/dist/test/matter/platform-matter.mapping.test.js.map +1 -0
- package/dist/test/matter/platform-matter.openapi-mapping.test.d.ts.map +1 -0
- package/dist/test/{platform-matter.openapi-mapping.test.js → matter/platform-matter.openapi-mapping.test.js} +2 -2
- package/dist/test/matter/platform-matter.openapi-mapping.test.js.map +1 -0
- package/dist/test/matter/platform-matter.test.d.ts.map +1 -0
- package/dist/test/{platform-matter.test.js → matter/platform-matter.test.js} +3 -3
- package/dist/test/matter/platform-matter.test.js.map +1 -0
- package/dist/test/matter/platform-matter.unregister.test.d.ts.map +1 -0
- package/dist/test/{platform-matter.unregister.test.js → matter/platform-matter.unregister.test.js} +2 -2
- package/dist/test/matter/platform-matter.unregister.test.js.map +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +37 -12
- package/dist/utils.js.map +1 -1
- package/docs/index.html +19 -1
- package/docs/variables/default.html +1 -1
- package/package.json +3 -3
- package/src/platform-matter.ts +118 -11
- package/src/settings.ts +2 -0
- package/src/test/hap/platform-hap.logging.test.ts +36 -0
- package/src/test/hap/platform-hap.test.ts +70 -0
- package/src/test/{devices-matter → matter/devices-matter}/baseMatterAccessory.test.ts +1 -1
- package/src/test/{platform-matter.additional.test.ts → matter/platform-matter.additional.test.ts} +2 -2
- package/src/test/{platform-matter.bleparse.test.ts → matter/platform-matter.bleparse.test.ts} +2 -2
- package/src/test/{platform-matter.cleanup.test.ts → matter/platform-matter.cleanup.test.ts} +3 -3
- package/src/test/{platform-matter.keepstale.test.ts → matter/platform-matter.keepstale.test.ts} +2 -2
- package/src/test/matter/platform-matter.logging.test.ts +33 -0
- package/src/test/{platform-matter.mapping.test.ts → matter/platform-matter.mapping.test.ts} +2 -2
- package/src/test/{platform-matter.openapi-mapping.test.ts → matter/platform-matter.openapi-mapping.test.ts} +2 -2
- package/src/test/{platform-matter.test.ts → matter/platform-matter.test.ts} +3 -3
- package/src/test/{platform-matter.unregister.test.ts → matter/platform-matter.unregister.test.ts} +2 -2
- package/src/utils.ts +37 -9
- package/dist/test/devices-matter/baseMatterAccessory.test.d.ts.map +0 -1
- package/dist/test/devices-matter/baseMatterAccessory.test.js.map +0 -1
- package/dist/test/platform-matter.additional.test.d.ts.map +0 -1
- package/dist/test/platform-matter.additional.test.js.map +0 -1
- package/dist/test/platform-matter.bleparse.test.d.ts.map +0 -1
- package/dist/test/platform-matter.bleparse.test.js.map +0 -1
- package/dist/test/platform-matter.cleanup.test.d.ts.map +0 -1
- package/dist/test/platform-matter.cleanup.test.js.map +0 -1
- package/dist/test/platform-matter.keepstale.test.d.ts.map +0 -1
- package/dist/test/platform-matter.keepstale.test.js.map +0 -1
- package/dist/test/platform-matter.mapping.test.d.ts.map +0 -1
- package/dist/test/platform-matter.mapping.test.js.map +0 -1
- package/dist/test/platform-matter.openapi-mapping.test.d.ts.map +0 -1
- package/dist/test/platform-matter.openapi-mapping.test.js.map +0 -1
- package/dist/test/platform-matter.test.d.ts.map +0 -1
- package/dist/test/platform-matter.test.js.map +0 -1
- package/dist/test/platform-matter.unregister.test.d.ts.map +0 -1
- package/dist/test/platform-matter.unregister.test.js.map +0 -1
- /package/dist/test/{devices-matter → matter/devices-matter}/baseMatterAccessory.test.d.ts +0 -0
- /package/dist/test/{platform-matter.additional.test.d.ts → matter/platform-matter.additional.test.d.ts} +0 -0
- /package/dist/test/{platform-matter.bleparse.test.d.ts → matter/platform-matter.bleparse.test.d.ts} +0 -0
- /package/dist/test/{platform-matter.cleanup.test.d.ts → matter/platform-matter.cleanup.test.d.ts} +0 -0
- /package/dist/test/{platform-matter.keepstale.test.d.ts → matter/platform-matter.keepstale.test.d.ts} +0 -0
- /package/dist/test/{platform-matter.mapping.test.d.ts → matter/platform-matter.mapping.test.d.ts} +0 -0
- /package/dist/test/{platform-matter.openapi-mapping.test.d.ts → matter/platform-matter.openapi-mapping.test.d.ts} +0 -0
- /package/dist/test/{platform-matter.test.d.ts → matter/platform-matter.test.d.ts} +0 -0
- /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">=></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/
|
|
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">=></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.
|
|
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.
|
|
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.
|
|
108
|
+
"vitest": "^4.0.6"
|
|
109
109
|
},
|
|
110
110
|
"directories": {
|
|
111
111
|
"doc": "docs"
|
package/src/platform-matter.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
}
|
|
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
|
-
|
|
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 '
|
|
3
|
+
import { BaseMatterAccessory } from '../../../devices-matter/BaseMatterAccessory.js'
|
|
4
4
|
|
|
5
5
|
// Minimal concrete subclass for testing
|
|
6
6
|
class TestAccessory extends BaseMatterAccessory {
|
package/src/test/{platform-matter.additional.test.ts → matter/platform-matter.additional.test.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import { SwitchBotMatterPlatform } from '
|
|
4
|
-
import { makeApiStub, makeLogStub } from '
|
|
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 () => {
|
package/src/test/{platform-matter.bleparse.test.ts → matter/platform-matter.bleparse.test.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import { SwitchBotMatterPlatform } from '
|
|
4
|
-
import { makeApiStub, makeLogStub } from '
|
|
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 '
|
|
4
|
-
import { formatDeviceIdAsMac } from '
|
|
5
|
-
import { makeApiStub, makeLogStub } from '
|
|
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 () => {
|
package/src/test/{platform-matter.keepstale.test.ts → matter/platform-matter.keepstale.test.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import { SwitchBotMatterPlatform } from '
|
|
4
|
-
import { makeApiStub, makeLogStub } from '
|
|
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 '
|
|
4
|
-
import { makeApiStub, makeLogStub } from '
|
|
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 '
|
|
4
|
-
import { makeApiStub, makeLogStub } from '
|
|
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 '
|
|
4
|
-
import { PLATFORM_NAME, PLUGIN_NAME } from '
|
|
5
|
-
import { makeApiStub, makeLogStub } from '
|
|
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 () => {
|
package/src/test/{platform-matter.unregister.test.ts → matter/platform-matter.unregister.test.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import { SwitchBotMatterPlatform } from '
|
|
4
|
-
import { makeApiStub, makeLogStub } from '
|
|
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 () => {
|