@switchbot/homebridge-switchbot 5.0.0 → 5.1.0-beta.0

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 (53) hide show
  1. package/.github/npm-version-script-esm.js +97 -0
  2. package/CHANGELOG.md +21 -8
  3. package/MIGRATION.md +5 -0
  4. package/README.md +25 -0
  5. package/config.schema.json +11 -0
  6. package/dist/SwitchBotHAPPlatform.d.ts.map +1 -1
  7. package/dist/SwitchBotHAPPlatform.js +33 -4
  8. package/dist/SwitchBotHAPPlatform.js.map +1 -1
  9. package/dist/SwitchBotMatterPlatform.d.ts +1 -0
  10. package/dist/SwitchBotMatterPlatform.d.ts.map +1 -1
  11. package/dist/SwitchBotMatterPlatform.js +38 -5
  12. package/dist/SwitchBotMatterPlatform.js.map +1 -1
  13. package/dist/homebridge-ui/public/js/api.d.ts +6 -0
  14. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  15. package/dist/homebridge-ui/public/js/api.js +18 -0
  16. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  17. package/dist/homebridge-ui/public/js/api.ts +31 -1
  18. package/dist/homebridge-ui/public/js/app.js +78 -2
  19. package/dist/homebridge-ui/public/js/app.js.map +2 -2
  20. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
  21. package/dist/homebridge-ui/public/js/devices.js +6 -0
  22. package/dist/homebridge-ui/public/js/devices.js.map +1 -1
  23. package/dist/homebridge-ui/public/js/devices.ts +6 -0
  24. package/dist/homebridge-ui/public/js/modals.d.ts +1 -0
  25. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
  26. package/dist/homebridge-ui/public/js/modals.js +53 -0
  27. package/dist/homebridge-ui/public/js/modals.js.map +1 -1
  28. package/dist/homebridge-ui/public/js/modals.ts +58 -0
  29. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
  30. package/dist/homebridge-ui/public/js/render.js +2 -1
  31. package/dist/homebridge-ui/public/js/render.js.map +1 -1
  32. package/dist/homebridge-ui/public/js/render.ts +2 -1
  33. package/dist/homebridge-ui/settings.js.map +1 -1
  34. package/dist/settings.d.ts +11 -0
  35. package/dist/settings.d.ts.map +1 -1
  36. package/dist/settings.js.map +1 -1
  37. package/dist/utils.d.ts +5 -0
  38. package/dist/utils.d.ts.map +1 -1
  39. package/dist/utils.js +27 -0
  40. package/dist/utils.js.map +1 -1
  41. package/docs/assets/highlight.css +15 -15
  42. package/docs/index.html +22 -5
  43. package/docs/variables/default.html +1 -1
  44. package/package.json +1 -1
  45. package/src/SwitchBotHAPPlatform.ts +31 -6
  46. package/src/SwitchBotMatterPlatform.ts +35 -7
  47. package/src/homebridge-ui/public/js/api.ts +31 -1
  48. package/src/homebridge-ui/public/js/devices.ts +6 -0
  49. package/src/homebridge-ui/public/js/modals.ts +58 -0
  50. package/src/homebridge-ui/public/js/render.ts +2 -1
  51. package/src/settings.ts +12 -0
  52. package/src/utils.ts +31 -0
  53. package/test/utils/utils.spec.ts +15 -0
package/docs/index.html CHANGED
@@ -40,6 +40,23 @@
40
40
  </ul>
41
41
  <p>Click <strong>Save Advanced Settings</strong> to apply changes. These settings match the options available in <code>config.schema.json</code> and can be overridden per device.</p>
42
42
  <!-- Optionally add a screenshot here -->
43
+ <h3 id="per-device-external-publishing-hap-or-matter" class="tsd-anchor-link">Per-Device External Publishing (HAP or Matter)<a href="#per-device-external-publishing-hap-or-matter" 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>
44
+ <p>Each device now supports an <code>externalPublishProtocol</code> option to control whether it is published as an external accessory.</p>
45
+ <ul>
46
+ <li><code>none</code> (default): normal platform registration behavior.</li>
47
+ <li><code>hap</code>: publish this device as a HAP external accessory.</li>
48
+ <li><code>matter</code>: publish this device as a Matter external accessory.</li>
49
+ </ul>
50
+ <p>Behavior notes:</p>
51
+ <ul>
52
+ <li>This is configured per device in the Homebridge UI under <strong>Publish as External Device</strong> when adding or editing a device.</li>
53
+ <li>If the selected external publish API is not available on your Homebridge runtime, the plugin falls back to normal platform registration for that device.</li>
54
+ <li>Use <code>hap</code> for HAP-focused setups and <code>matter</code> for Matter-focused setups; leave as <code>none</code> unless you specifically want external accessory publishing.</li>
55
+ </ul>
56
+ <p>Example device config excerpt:</p>
57
+ <pre><code class="json"><span class="hl-0">{</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;deviceId&quot;</span><span class="hl-0">: </span><span class="hl-2">&quot;E7F8A1B2C3D4&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;configDeviceName&quot;</span><span class="hl-0">: </span><span class="hl-2">&quot;Bedroom Bot&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;configDeviceType&quot;</span><span class="hl-0">: </span><span class="hl-2">&quot;Bot&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;externalPublishProtocol&quot;</span><span class="hl-0">: </span><span class="hl-2">&quot;hap&quot;</span><br/><span class="hl-0">}</span>
58
+ </code><button type="button">Copy</button></pre>
59
+
43
60
  <ul>
44
61
  <li>
45
62
  <h3 id="if-using-openapi-connection" class="tsd-anchor-link">If using OpenAPI Connection<a href="#if-using-openapi-connection" 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>
@@ -117,7 +134,7 @@
117
134
  (This is what is intended in documentation for the noble bluetooth package <a href="https://github.com/abandonware/noble#prerequisites">prerequisites</a> by &quot;Add terminal app&quot;, however for HomeBridge it is <code>node</code> that needs the permission granted, not <code>terminal</code>.
118
135
  Without this step, then you will receive the following error when the swichbot plugin launches, which will cause Homebridge or the child bridge process to restart:</li>
119
136
  </ol>
120
- <pre><code><span class="hl-0">Error</span><span class="hl-1">: </span><span class="hl-2">Failed</span><span class="hl-1"> </span><span class="hl-2">to</span><span class="hl-1"> </span><span class="hl-2">initialize</span><span class="hl-1"> </span><span class="hl-2">the</span><span class="hl-1"> </span><span class="hl-2">Noble</span><span class="hl-1"> </span><span class="hl-0">object</span><span class="hl-1">: </span><span class="hl-2">unauthorized</span><br/><span class="hl-1"> </span><span class="hl-2">at</span><span class="hl-1"> </span><span class="hl-2">Noble</span><span class="hl-1">.&lt;</span><span class="hl-3">anonymous</span><span class="hl-1">&gt; (</span><span class="hl-2">file</span><span class="hl-1">:</span><span class="hl-4">///usr/local/lib/node_modules/@switchbot/homebridge-switchbot/node_modules/node-switchbot/src/switchbot.ts:244:19)</span><br/><span class="hl-1"> </span><span class="hl-3">at</span><span class="hl-1"> </span><span class="hl-3">Object</span><span class="hl-1">.</span><span class="hl-3">onceWrapper</span><span class="hl-1"> (</span><span class="hl-2">node</span><span class="hl-1">:</span><span class="hl-3">events</span><span class="hl-1">:</span><span class="hl-5">629</span><span class="hl-1">:</span><span class="hl-5">26</span><span class="hl-1">)</span><br/><span class="hl-1"> </span><span class="hl-3">at</span><span class="hl-1"> </span><span class="hl-3">Noble</span><span class="hl-1">.</span><span class="hl-3">emit</span><span class="hl-1"> (</span><span class="hl-2">node</span><span class="hl-1">:</span><span class="hl-3">events</span><span class="hl-1">:</span><span class="hl-5">514</span><span class="hl-1">:</span><span class="hl-5">28</span><span class="hl-1">)</span><br/><span class="hl-1"> </span><span class="hl-3">at</span><span class="hl-1"> </span><span class="hl-3">Noble</span><span class="hl-1">.</span><span class="hl-3">onStateChange</span><span class="hl-1"> (/</span><span class="hl-3">usr</span><span class="hl-1">/</span><span class="hl-3">local</span><span class="hl-1">/</span><span class="hl-3">lib</span><span class="hl-1">/</span><span class="hl-3">node_modules</span><span class="hl-1">/@</span><span class="hl-3">switchbot</span><span class="hl-1">/</span><span class="hl-3">homebridge</span><span class="hl-1">-</span><span class="hl-3">switchbot</span><span class="hl-1">/</span><span class="hl-3">node_modules</span><span class="hl-1">/@</span><span class="hl-3">stoprocent</span><span class="hl-1">/</span><span class="hl-3">noble</span><span class="hl-1">/</span><span class="hl-3">lib</span><span class="hl-1">/</span><span class="hl-3">noble</span><span class="hl-1">.</span><span class="hl-3">js</span><span class="hl-1">:</span><span class="hl-5">92</span><span class="hl-1">:</span><span class="hl-5">8</span><span class="hl-1">)</span><br/><span class="hl-1"> </span><span class="hl-3">at</span><span class="hl-1"> </span><span class="hl-3">NobleMac</span><span class="hl-1">.</span><span class="hl-3">emit</span><span class="hl-1"> (</span><span class="hl-2">node</span><span class="hl-1">:</span><span class="hl-3">events</span><span class="hl-1">:</span><span class="hl-5">514</span><span class="hl-1">:</span><span class="hl-5">28</span><span class="hl-1">)</span>
137
+ <pre><code><span class="hl-3">Error</span><span class="hl-0">: </span><span class="hl-4">Failed</span><span class="hl-0"> </span><span class="hl-4">to</span><span class="hl-0"> </span><span class="hl-4">initialize</span><span class="hl-0"> </span><span class="hl-4">the</span><span class="hl-0"> </span><span class="hl-4">Noble</span><span class="hl-0"> </span><span class="hl-3">object</span><span class="hl-0">: </span><span class="hl-4">unauthorized</span><br/><span class="hl-0"> </span><span class="hl-4">at</span><span class="hl-0"> </span><span class="hl-4">Noble</span><span class="hl-0">.&lt;</span><span class="hl-5">anonymous</span><span class="hl-0">&gt; (</span><span class="hl-4">file</span><span class="hl-0">:</span><span class="hl-6">///usr/local/lib/node_modules/@switchbot/homebridge-switchbot/node_modules/node-switchbot/src/switchbot.ts:244:19)</span><br/><span class="hl-0"> </span><span class="hl-5">at</span><span class="hl-0"> </span><span class="hl-5">Object</span><span class="hl-0">.</span><span class="hl-5">onceWrapper</span><span class="hl-0"> (</span><span class="hl-4">node</span><span class="hl-0">:</span><span class="hl-5">events</span><span class="hl-0">:</span><span class="hl-7">629</span><span class="hl-0">:</span><span class="hl-7">26</span><span class="hl-0">)</span><br/><span class="hl-0"> </span><span class="hl-5">at</span><span class="hl-0"> </span><span class="hl-5">Noble</span><span class="hl-0">.</span><span class="hl-5">emit</span><span class="hl-0"> (</span><span class="hl-4">node</span><span class="hl-0">:</span><span class="hl-5">events</span><span class="hl-0">:</span><span class="hl-7">514</span><span class="hl-0">:</span><span class="hl-7">28</span><span class="hl-0">)</span><br/><span class="hl-0"> </span><span class="hl-5">at</span><span class="hl-0"> </span><span class="hl-5">Noble</span><span class="hl-0">.</span><span class="hl-5">onStateChange</span><span class="hl-0"> (/</span><span class="hl-5">usr</span><span class="hl-0">/</span><span class="hl-5">local</span><span class="hl-0">/</span><span class="hl-5">lib</span><span class="hl-0">/</span><span class="hl-5">node_modules</span><span class="hl-0">/@</span><span class="hl-5">switchbot</span><span class="hl-0">/</span><span class="hl-5">homebridge</span><span class="hl-0">-</span><span class="hl-5">switchbot</span><span class="hl-0">/</span><span class="hl-5">node_modules</span><span class="hl-0">/@</span><span class="hl-5">stoprocent</span><span class="hl-0">/</span><span class="hl-5">noble</span><span class="hl-0">/</span><span class="hl-5">lib</span><span class="hl-0">/</span><span class="hl-5">noble</span><span class="hl-0">.</span><span class="hl-5">js</span><span class="hl-0">:</span><span class="hl-7">92</span><span class="hl-0">:</span><span class="hl-7">8</span><span class="hl-0">)</span><br/><span class="hl-0"> </span><span class="hl-5">at</span><span class="hl-0"> </span><span class="hl-5">NobleMac</span><span class="hl-0">.</span><span class="hl-5">emit</span><span class="hl-0"> (</span><span class="hl-4">node</span><span class="hl-0">:</span><span class="hl-5">events</span><span class="hl-0">:</span><span class="hl-7">514</span><span class="hl-0">:</span><span class="hl-7">28</span><span class="hl-0">)</span>
121
138
  </code><button>Copy</button></pre>
122
139
 
123
140
  </li>
@@ -345,7 +362,7 @@ Without this step, then you will receive the following error when the swichbot p
345
362
  <h4 id="how-to-configure-in-homebridge" class="tsd-anchor-link">How to Configure in Homebridge<a href="#how-to-configure-in-homebridge" 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></h4>
346
363
  <p>In the Homebridge UI, when adding or editing a SwitchBot device, enter the <strong>Encryption Key</strong> and <strong>Key ID</strong> in the provided fields. These values will be securely used for BLE communication with your device.</p>
347
364
  <p><strong>Example device config excerpt:</strong></p>
348
- <pre><code class="json"><span class="hl-1">{</span><br/><span class="hl-1"> </span><span class="hl-6">&quot;deviceId&quot;</span><span class="hl-1">: </span><span class="hl-7">&quot;E7F8A1B2C3D4&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-6">&quot;deviceName&quot;</span><span class="hl-1">: </span><span class="hl-7">&quot;SwitchBot Lock&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-6">&quot;enableBLE&quot;</span><span class="hl-1">: </span><span class="hl-8">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-6">&quot;encryptionKey&quot;</span><span class="hl-1">: </span><span class="hl-7">&quot;0123456789abcdef0123456789abcdef&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-6">&quot;keyId&quot;</span><span class="hl-1">: </span><span class="hl-7">&quot;01&quot;</span><br/><span class="hl-1">}</span>
365
+ <pre><code class="json"><span class="hl-0">{</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;deviceId&quot;</span><span class="hl-0">: </span><span class="hl-2">&quot;E7F8A1B2C3D4&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;deviceName&quot;</span><span class="hl-0">: </span><span class="hl-2">&quot;SwitchBot Lock&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;enableBLE&quot;</span><span class="hl-0">: </span><span class="hl-8">true</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;encryptionKey&quot;</span><span class="hl-0">: </span><span class="hl-2">&quot;0123456789abcdef0123456789abcdef&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;keyId&quot;</span><span class="hl-0">: </span><span class="hl-2">&quot;01&quot;</span><br/><span class="hl-0">}</span>
349
366
  </code><button type="button">Copy</button></pre>
350
367
 
351
368
  <h4 id="which-devices-require-ble-encryption" class="tsd-anchor-link">Which Devices Require BLE Encryption?<a href="#which-devices-require-ble-encryption" 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></h4>
@@ -494,7 +511,7 @@ Without this step, then you will receive the following error when the swichbot p
494
511
  <li><code>dailyApiResetLocalMidnight</code> (boolean, default false): if true, resets the daily counter at local midnight; when false, resets at UTC midnight.</li>
495
512
  </ul>
496
513
  <p>Example (excerpt):</p>
497
- <pre><code class="json"><span class="hl-1">{</span><br/><span class="hl-1"> </span><span class="hl-6">&quot;options&quot;</span><span class="hl-1">: {</span><br/><span class="hl-1"> </span><span class="hl-6">&quot;dailyApiLimit&quot;</span><span class="hl-1">: </span><span class="hl-5">10000</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-6">&quot;dailyApiReserveForCommands&quot;</span><span class="hl-1">: </span><span class="hl-5">1000</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-6">&quot;webhookOnlyOnReserve&quot;</span><span class="hl-1">: </span><span class="hl-8">false</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-6">&quot;dailyApiResetLocalMidnight&quot;</span><span class="hl-1">: </span><span class="hl-8">true</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
514
+ <pre><code class="json"><span class="hl-0">{</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;options&quot;</span><span class="hl-0">: {</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;dailyApiLimit&quot;</span><span class="hl-0">: </span><span class="hl-7">10000</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;dailyApiReserveForCommands&quot;</span><span class="hl-0">: </span><span class="hl-7">1000</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;webhookOnlyOnReserve&quot;</span><span class="hl-0">: </span><span class="hl-8">false</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-1">&quot;dailyApiResetLocalMidnight&quot;</span><span class="hl-0">: </span><span class="hl-8">true</span><br/><span class="hl-0"> }</span><br/><span class="hl-0">}</span>
498
515
  </code><button type="button">Copy</button></pre>
499
516
 
500
517
  <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>
@@ -510,7 +527,7 @@ Without this step, then you will receive the following error when the swichbot p
510
527
  <ul>
511
528
  <li>
512
529
  <p>Run unit tests:</p>
513
- <pre><code class="bash"><span class="hl-9">npm</span><span class="hl-1"> </span><span class="hl-7">run</span><span class="hl-1"> </span><span class="hl-7">test</span>
530
+ <pre><code class="bash"><span class="hl-9">npm</span><span class="hl-0"> </span><span class="hl-2">run</span><span class="hl-0"> </span><span class="hl-2">test</span>
514
531
  </code><button type="button">Copy</button></pre>
515
532
 
516
533
  </li>
@@ -527,4 +544,4 @@ Without this step, then you will receive the following error when the swichbot p
527
544
  <li><a href="https://www.facebook.com/SwitchBotRobot/">Facebook @SwitchBotRobot</a></li>
528
545
  <li><a href="https://twitter.com/switchbot">Twitter @SwitchBot</a></li>
529
546
  </ul>
530
- </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="#openapi-pollingrate-advanced-settings-ui"><span>Open<wbr/>API <wbr/>Polling/<wbr/>Rate <wbr/>Advanced <wbr/>Settings (<wbr/>UI)</span></a></li><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="#ble-encryption-support"><span>BLE <wbr/>Encryption <wbr/>Support</span></a></li><li><ul><li><a href="#ble-encryption-key-and-key-id"><span>BLE <wbr/>Encryption <wbr/>Key and <wbr/>Key <wbr/>ID</span></a></li><li><ul><li><a href="#how-to-obtain-ble-encryption-key-and-key-id"><span>How to <wbr/>Obtain <wbr/>BLE <wbr/>Encryption <wbr/>Key and <wbr/>Key <wbr/>ID</span></a></li><li><a href="#how-to-configure-in-homebridge"><span>How to <wbr/>Configure in <wbr/>Homebridge</span></a></li><li><a href="#which-devices-require-ble-encryption"><span>Which <wbr/>Devices <wbr/>Require <wbr/>BLE <wbr/>Encryption?</span></a></li></ul></li></ul></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-mini-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 <wbr/>Mini 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="#whats-new-with-node-switchbot-v400"><span>What&#39;s new with node-<wbr/>switchbot v4.0.0</span></a></li><li><a href="#openapi-rate-limiting-and-daily-budget"><span>Open<wbr/>API rate limiting and daily budget</span></a></li><li><a href="#switchbot-apis"><span>Switch<wbr/>Bot <wbr/>AP<wbr/>Is</span></a></li><li><a href="#development-tests"><span>Development / <wbr/>Tests</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>
547
+ </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="#openapi-pollingrate-advanced-settings-ui"><span>Open<wbr/>API <wbr/>Polling/<wbr/>Rate <wbr/>Advanced <wbr/>Settings (<wbr/>UI)</span></a></li><li><a href="#per-device-external-publishing-hap-or-matter"><span>Per-<wbr/>Device <wbr/>External <wbr/>Publishing (<wbr/>HAP or <wbr/>Matter)</span></a></li><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="#ble-encryption-support"><span>BLE <wbr/>Encryption <wbr/>Support</span></a></li><li><ul><li><a href="#ble-encryption-key-and-key-id"><span>BLE <wbr/>Encryption <wbr/>Key and <wbr/>Key <wbr/>ID</span></a></li><li><ul><li><a href="#how-to-obtain-ble-encryption-key-and-key-id"><span>How to <wbr/>Obtain <wbr/>BLE <wbr/>Encryption <wbr/>Key and <wbr/>Key <wbr/>ID</span></a></li><li><a href="#how-to-configure-in-homebridge"><span>How to <wbr/>Configure in <wbr/>Homebridge</span></a></li><li><a href="#which-devices-require-ble-encryption"><span>Which <wbr/>Devices <wbr/>Require <wbr/>BLE <wbr/>Encryption?</span></a></li></ul></li></ul></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-mini-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 <wbr/>Mini 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="#whats-new-with-node-switchbot-v400"><span>What&#39;s new with node-<wbr/>switchbot v4.0.0</span></a></li><li><a href="#openapi-rate-limiting-and-daily-budget"><span>Open<wbr/>API rate limiting and daily budget</span></a></li><li><a href="#switchbot-apis"><span>Switch<wbr/>Bot <wbr/>AP<wbr/>Is</span></a></li><li><a href="#development-tests"><span>Development / <wbr/>Tests</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,3 +1,3 @@
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-comment tsd-typography"><p>Registers the SwitchBot platform with Homebridge.</p>
2
2
  </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><div class="tsd-comment tsd-typography"><p>The Homebridge API instance.</p>
3
- </div></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/50cea4b3bf59e48befc35e5599a830888265844b/src/index.ts#L22">index.ts:22</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>
3
+ </div></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/b3af65f017e09fd27d158673c1c37935372c3fd8/src/index.ts#L22">index.ts:22</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",
5
+ "version": "5.1.0-beta.0",
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": [
@@ -4,7 +4,7 @@ import type { API, Logger, PlatformConfig } from 'homebridge'
4
4
  import { createDevice } from './deviceFactory.js'
5
5
  import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'
6
6
  import { SwitchBotClient } from './switchbotClient.js'
7
- import { normalizeTypeForMatter } from './utils.js'
7
+ import { normalizeTypeForMatter, resolveExternalPublishProtocol } from './utils.js'
8
8
 
9
9
  /**
10
10
  * Homebridge platform class for SwitchBot HAP (HomeKit Accessory Protocol) integration.
@@ -140,7 +140,7 @@ export class SwitchBotHAPPlatform {
140
140
  }
141
141
 
142
142
  const devices = (this.config as any)?.devices ?? []
143
- const createdDevices: { created: any, d: any, type: string }[] = []
143
+ const createdDevices: { created: any, d: any, type: string, externalPublishProtocol: 'none' | 'hap' | 'matter' }[] = []
144
144
  for (const raw of devices) {
145
145
  // Normalize config keys from UI schema to internal shape (for cross-platform consistency)
146
146
  const d: any = {
@@ -152,12 +152,13 @@ export class SwitchBotHAPPlatform {
152
152
  _raw: raw,
153
153
  }
154
154
  const type: string = normalizeTypeForMatter(d.type)
155
+ const externalPublishProtocol = resolveExternalPublishProtocol(raw)
155
156
  const deviceOpts: any = { id: d.id, type, name: d.name, encryptionKey: d.encryptionKey, keyId: d.keyId, log: this.log }
156
157
  this.log.debug(`[HAP/Debug] Device options for ${d.name ?? d.id}:`, JSON.stringify(deviceOpts, null, 2))
157
158
  try {
158
159
  const created = await createDevice(deviceOpts, this.config, false)
159
160
  this.devices.push(created)
160
- createdDevices.push({ created, d, type })
161
+ createdDevices.push({ created, d, type, externalPublishProtocol })
161
162
  } catch (e: any) {
162
163
  this.log.error(`Failed to create HAP device ${d.id}:`, e instanceof Error ? e.stack || e.message : e)
163
164
  }
@@ -188,14 +189,15 @@ export class SwitchBotHAPPlatform {
188
189
  *
189
190
  * If the Homebridge HAP API is not available, registration is skipped and a log message is emitted.
190
191
  */
191
- private async registerHAPAccessories(createdDevices: { created: any, d: any, type: string }[]): Promise<void> {
192
+ private async registerHAPAccessories(createdDevices: { created: any, d: any, type: string, externalPublishProtocol: 'none' | 'hap' | 'matter' }[]): Promise<void> {
192
193
  if (!this.api || !(this.api as any).hap || typeof (this.api as any).hap.registerPlatformAccessories !== 'function') {
193
194
  this.log.info('HAP API not available to register accessories')
194
195
  return
195
196
  }
196
197
  const hap = (this.api as any).hap
197
198
  const accessoriesToRegister: any[] = []
198
- for (const { created, d, type } of createdDevices) {
199
+ const externalAccessoriesToPublish: any[] = []
200
+ for (const { created, d, type, externalPublishProtocol } of createdDevices) {
199
201
  try {
200
202
  const uuid = hap.uuid.generate(`${d.id}`)
201
203
  let accessory: any = this.accessories.get(uuid)
@@ -220,7 +222,11 @@ export class SwitchBotHAPPlatform {
220
222
  } catch (e) {
221
223
  // ignore context failures
222
224
  }
223
- accessoriesToRegister.push(accessory)
225
+ if (externalPublishProtocol === 'hap') {
226
+ externalAccessoriesToPublish.push(accessory)
227
+ } else {
228
+ accessoriesToRegister.push(accessory)
229
+ }
224
230
  this.accessories.set(uuid, accessory)
225
231
  } else {
226
232
  try {
@@ -298,6 +304,24 @@ export class SwitchBotHAPPlatform {
298
304
  } else {
299
305
  this.log.info('No HAP accessories to register')
300
306
  }
307
+ if (externalAccessoriesToPublish.length > 0) {
308
+ if (typeof (this.api as any).publishExternalAccessories === 'function') {
309
+ try {
310
+ (this.api as any).publishExternalAccessories(PLUGIN_NAME, externalAccessoriesToPublish)
311
+ this.log.info(`Published ${externalAccessoriesToPublish.length} HAP external accessory(ies) with Homebridge`)
312
+ } catch (e) {
313
+ this.log.warn('Failed to publish HAP external accessories', e)
314
+ }
315
+ } else {
316
+ this.log.warn('HAP external accessory publishing API not available; falling back to regular platform registration')
317
+ try {
318
+ (this.api as any).registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, externalAccessoriesToPublish)
319
+ this.log.info(`Registered ${externalAccessoriesToPublish.length} HAP accessory(ies) as fallback (external publishing unavailable)`)
320
+ } catch (e) {
321
+ this.log.warn('Failed to register fallback HAP accessories', e)
322
+ }
323
+ }
324
+ }
301
325
  }
302
326
 
303
327
  /**
@@ -332,6 +356,7 @@ export class SwitchBotHAPPlatform {
332
356
  id: d.deviceId ?? d.id,
333
357
  type: d.configDeviceType ?? d.type,
334
358
  name: d.configDeviceName ?? d.name,
359
+ externalPublishProtocol: resolveExternalPublishProtocol(d),
335
360
  })))
336
361
  }
337
362
 
@@ -4,7 +4,7 @@ import type { API, Logger, PlatformConfig } from 'homebridge'
4
4
  import { createDevice } from './deviceFactory.js'
5
5
  import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'
6
6
  import { SwitchBotClient } from './switchbotClient.js'
7
- import { createMatterHandlers, DEVICE_MATTER_CLUSTERS, DEVICE_MATTER_SUPPORTED, normalizeTypeForMatter, resolveMatterDeviceType } from './utils.js'
7
+ import { createMatterHandlers, DEVICE_MATTER_CLUSTERS, DEVICE_MATTER_SUPPORTED, normalizeTypeForMatter, resolveExternalPublishProtocol, resolveMatterDeviceType } from './utils.js'
8
8
 
9
9
  /**
10
10
  * Homebridge platform class for SwitchBot Matter integration.
@@ -113,7 +113,7 @@ export class SwitchBotMatterPlatform {
113
113
  }
114
114
 
115
115
  const devices = (this.config as any)?.devices ?? []
116
- const createdDevices: { created: any, d: any, type: string, useMatter: boolean, matterAvailable: boolean }[] = []
116
+ const createdDevices: { created: any, d: any, type: string, useMatter: boolean, matterAvailable: boolean, externalPublishProtocol: 'none' | 'hap' | 'matter' }[] = []
117
117
  for (const raw of devices) {
118
118
  // Normalize config keys from UI schema to internal shape (for cross-platform consistency)
119
119
  const d: any = {
@@ -125,6 +125,7 @@ export class SwitchBotMatterPlatform {
125
125
  _raw: raw,
126
126
  }
127
127
  const type: string = normalizeTypeForMatter(d.type)
128
+ const externalPublishProtocol = resolveExternalPublishProtocol(raw)
128
129
  const deviceOpts: any = { id: d.id, type, name: d.name, encryptionKey: d.encryptionKey, keyId: d.keyId, log: this.log }
129
130
  this.log.debug(`[Matter/Debug] Device options for ${d.name ?? d.id}:`, JSON.stringify(deviceOpts, null, 2))
130
131
  const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()]
@@ -134,7 +135,7 @@ export class SwitchBotMatterPlatform {
134
135
  try {
135
136
  const created = await createDevice(deviceOpts, this.config, useMatter)
136
137
  this.devices.push(created)
137
- createdDevices.push({ created, d, type, useMatter, matterAvailable })
138
+ createdDevices.push({ created, d, type, useMatter, matterAvailable, externalPublishProtocol })
138
139
  if (useMatter) {
139
140
  this.log.info(`Prepared Matter accessory for ${d.id} (${type})${matterAvailable ? ' (auto-detected)' : ' (manually enabled)'}`)
140
141
  } else {
@@ -179,14 +180,15 @@ export class SwitchBotMatterPlatform {
179
180
  * If the Homebridge Matter API is not available, registration is skipped and a log message is emitted.
180
181
  * Accessories that are not enabled for Matter are ignored.
181
182
  */
182
- async registerMatterAccessories(createdDevices: { created: any, d: any, type: string, useMatter: boolean, matterAvailable: boolean }[]): Promise<void> {
183
+ async registerMatterAccessories(createdDevices: { created: any, d: any, type: string, useMatter: boolean, matterAvailable: boolean, externalPublishProtocol: 'none' | 'hap' | 'matter' }[]): Promise<void> {
183
184
  if (!this.api || !(this.api as any).matter || typeof (this.api as any).matter.registerPlatformAccessories !== 'function') {
184
185
  this.log.info('Homebridge Matter API not available; skipping Matter accessory registration')
185
186
  return
186
187
  }
187
188
  const matterApi = (this.api as any).matter
188
189
  const accessoriesToRegister: any[] = []
189
- for (const { created, d, type, useMatter, matterAvailable } of createdDevices) {
190
+ const externalAccessoriesToPublish: any[] = []
191
+ for (const { created, d, type, useMatter, matterAvailable, externalPublishProtocol } of createdDevices) {
190
192
  // Only register accessories where Matter is enabled and supported
191
193
  if (!useMatter) {
192
194
  // Log reason for skipping registration
@@ -237,7 +239,11 @@ export class SwitchBotMatterPlatform {
237
239
  handlers: createdDesc.handlers || createMatterHandlers(this.log, d.id, type, (this.config as any)?._client) || undefined,
238
240
  context: { deviceId: d.id, type, created: true },
239
241
  }
240
- accessoriesToRegister.push(accessory)
242
+ if (externalPublishProtocol === 'matter') {
243
+ externalAccessoriesToPublish.push(accessory)
244
+ } else {
245
+ accessoriesToRegister.push(accessory)
246
+ }
241
247
  this.accessories.set(uuid, accessory)
242
248
  } else {
243
249
  // Ensure context and update properties for restored accessory
@@ -260,7 +266,11 @@ export class SwitchBotMatterPlatform {
260
266
  accessory.handlers = createdDesc.handlers || createMatterHandlers(this.log, d.id, type, (this.config as any)?._client) || undefined
261
267
  accessory.displayName = createdDesc.name || d.name || type
262
268
  accessory.UUID = accessory.UUID || accessory.uuid || uuid
263
- accessoriesToRegister.push(accessory)
269
+ if (externalPublishProtocol === 'matter') {
270
+ externalAccessoriesToPublish.push(accessory)
271
+ } else {
272
+ accessoriesToRegister.push(accessory)
273
+ }
264
274
  this.accessories.set(accessory.UUID || uuid, accessory)
265
275
  }
266
276
  this.log.info(`Created/updated Matter accessory ${d.id} (${type})`)
@@ -278,6 +288,23 @@ export class SwitchBotMatterPlatform {
278
288
  } else {
279
289
  this.log.info('No Matter accessories to register')
280
290
  }
291
+ if (externalAccessoriesToPublish.length > 0) {
292
+ try {
293
+ if (typeof matterApi.publishExternalAccessories === 'function') {
294
+ await matterApi.publishExternalAccessories(PLUGIN_NAME, externalAccessoriesToPublish)
295
+ this.log.info(`Published ${externalAccessoriesToPublish.length} Matter external accessory(ies) with Homebridge`)
296
+ } else if (typeof matterApi.registerExternalAccessories === 'function') {
297
+ await matterApi.registerExternalAccessories(PLUGIN_NAME, PLATFORM_NAME, externalAccessoriesToPublish)
298
+ this.log.info(`Registered ${externalAccessoriesToPublish.length} Matter external accessory(ies) with Homebridge`)
299
+ } else {
300
+ this.log.warn('Matter external accessory publishing API not available; falling back to regular platform registration')
301
+ await matterApi.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, externalAccessoriesToPublish)
302
+ this.log.info(`Registered ${externalAccessoriesToPublish.length} Matter accessory(ies) as fallback (external publishing unavailable)`)
303
+ }
304
+ } catch (e) {
305
+ this.log.warn('Failed to publish/register Matter external accessories', e)
306
+ }
307
+ }
281
308
  }
282
309
 
283
310
  /**
@@ -312,6 +339,7 @@ export class SwitchBotMatterPlatform {
312
339
  id: d.deviceId ?? d.id,
313
340
  type: d.configDeviceType ?? d.type,
314
341
  name: d.configDeviceName ?? d.name,
342
+ externalPublishProtocol: resolveExternalPublishProtocol(d),
315
343
  })))
316
344
  }
317
345
 
@@ -189,7 +189,19 @@ export async function addDevice(
189
189
  deviceId: string,
190
190
  name: string,
191
191
  type: string,
192
- options?: { address?: string, model?: string, rssi?: number, encryptionKey?: string, keyId?: string },
192
+ options?: {
193
+ address?: string
194
+ model?: string
195
+ rssi?: number
196
+ connectionPreference?: string
197
+ externalPublishProtocol?: string
198
+ room?: string
199
+ encryptionKey?: string
200
+ keyId?: string
201
+ refreshRate?: number
202
+ blePollingEnabled?: boolean
203
+ blePollIntervalMs?: number
204
+ },
193
205
  ): Promise<any> {
194
206
  if (typeof homebridge.getPluginConfig !== 'function' || typeof homebridge.updatePluginConfig !== 'function') {
195
207
  throw new TypeError('Homebridge UI API not available')
@@ -224,6 +236,24 @@ export async function addDevice(
224
236
  if (options?.keyId) {
225
237
  newDevice.keyId = options.keyId
226
238
  }
239
+ if (options?.connectionPreference) {
240
+ newDevice.connectionPreference = options.connectionPreference
241
+ }
242
+ if (options?.externalPublishProtocol) {
243
+ newDevice.externalPublishProtocol = options.externalPublishProtocol
244
+ }
245
+ if (options?.room) {
246
+ newDevice.room = options.room
247
+ }
248
+ if (typeof options?.refreshRate === 'number' && Number.isFinite(options.refreshRate)) {
249
+ newDevice.refreshRate = options.refreshRate
250
+ }
251
+ if (typeof options?.blePollingEnabled === 'boolean') {
252
+ newDevice.blePollingEnabled = options.blePollingEnabled
253
+ }
254
+ if (typeof options?.blePollIntervalMs === 'number' && Number.isFinite(options.blePollIntervalMs)) {
255
+ newDevice.blePollIntervalMs = options.blePollIntervalMs
256
+ }
227
257
  config.devices.push(newDevice)
228
258
  await homebridge.updatePluginConfig(configArr)
229
259
  if (typeof homebridge.savePluginConfig === 'function') {
@@ -26,8 +26,14 @@ export async function addDeviceToConfig(device: any, options: { refresh?: boolea
26
26
  address: importValues.address,
27
27
  model: device.model,
28
28
  rssi: device.rssi,
29
+ connectionPreference: importValues.connectionPreference,
30
+ externalPublishProtocol: importValues.externalPublishProtocol,
31
+ room: importValues.room,
29
32
  encryptionKey: importValues.encryptionKey,
30
33
  keyId: importValues.keyId,
34
+ refreshRate: importValues.refreshRate,
35
+ blePollingEnabled: importValues.blePollingEnabled,
36
+ blePollIntervalMs: importValues.blePollIntervalMs,
31
37
  })
32
38
  uiLog.info('Add device response:', resp)
33
39
 
@@ -6,6 +6,7 @@ export type ImportDiscoveredDeviceResult = {
6
6
  configDeviceType: string
7
7
  address?: string
8
8
  connectionPreference?: string
9
+ externalPublishProtocol?: string
9
10
  room?: string
10
11
  encryptionKey?: string
11
12
  keyId?: string
@@ -154,6 +155,30 @@ export async function importDiscoveredDevice(device: any): Promise<ImportDiscove
154
155
  connectionPrefSelect.appendChild(opt)
155
156
  })
156
157
 
158
+ // --- External Publish Protocol ---
159
+ const externalPublishLabel = document.createElement('label')
160
+ externalPublishLabel.textContent = 'Publish as External Device'
161
+ externalPublishLabel.style.display = 'block'
162
+ externalPublishLabel.style.marginBottom = '6px'
163
+ externalPublishLabel.style.fontWeight = '500'
164
+ externalPublishLabel.style.fontSize = '12px'
165
+ externalPublishLabel.style.color = '#6b7280'
166
+
167
+ const externalPublishSelect = document.createElement('select')
168
+ externalPublishSelect.style.width = '100%'
169
+ externalPublishSelect.style.marginBottom = '12px'
170
+ externalPublishSelect.style.padding = '8px 10px'
171
+ externalPublishSelect.style.borderRadius = '6px'
172
+ externalPublishSelect.style.fontSize = '14px'
173
+ externalPublishSelect.style.boxSizing = 'border-box'
174
+ ;['none', 'hap', 'matter'].forEach((val) => {
175
+ const opt = document.createElement('option')
176
+ opt.value = val
177
+ opt.text = val === 'none' ? 'None' : val.toUpperCase()
178
+ opt.selected = (device.externalPublishProtocol || 'none') === val
179
+ externalPublishSelect.appendChild(opt)
180
+ })
181
+
157
182
  // --- Room ---
158
183
  const roomLabel = document.createElement('label')
159
184
  roomLabel.textContent = 'Room'
@@ -308,6 +333,7 @@ export async function importDiscoveredDevice(device: any): Promise<ImportDiscove
308
333
  configDeviceType: typeSelect.value || device.type,
309
334
  address: macInput.value || undefined,
310
335
  connectionPreference: connectionPrefSelect.value || undefined,
336
+ externalPublishProtocol: externalPublishSelect.value || 'none',
311
337
  room: roomInput.value || undefined,
312
338
  encryptionKey: encryptionKeyInput.value || undefined,
313
339
  keyId: keyIdInput.value || undefined,
@@ -331,6 +357,8 @@ export async function importDiscoveredDevice(device: any): Promise<ImportDiscove
331
357
  contentDiv.appendChild(typeSelect)
332
358
  contentDiv.appendChild(connectionPrefLabel)
333
359
  contentDiv.appendChild(connectionPrefSelect)
360
+ contentDiv.appendChild(externalPublishLabel)
361
+ contentDiv.appendChild(externalPublishSelect)
334
362
  contentDiv.appendChild(roomLabel)
335
363
  contentDiv.appendChild(roomInput)
336
364
  contentDiv.appendChild(macLabel)
@@ -573,6 +601,30 @@ export async function editDevice(device: any): Promise<void> {
573
601
  connectionPrefSelect.appendChild(opt)
574
602
  })
575
603
 
604
+ // --- External Publish Protocol ---
605
+ const externalPublishLabel = document.createElement('label')
606
+ externalPublishLabel.textContent = 'Publish as External Device'
607
+ externalPublishLabel.style.display = 'block'
608
+ externalPublishLabel.style.marginBottom = '6px'
609
+ externalPublishLabel.style.fontWeight = '500'
610
+ externalPublishLabel.style.fontSize = '12px'
611
+ externalPublishLabel.style.color = '#6b7280'
612
+
613
+ const externalPublishSelect = document.createElement('select')
614
+ externalPublishSelect.style.width = '100%'
615
+ externalPublishSelect.style.marginBottom = '12px'
616
+ externalPublishSelect.style.padding = '8px 10px'
617
+ externalPublishSelect.style.borderRadius = '6px'
618
+ externalPublishSelect.style.fontSize = '14px'
619
+ externalPublishSelect.style.boxSizing = 'border-box'
620
+ ;['none', 'hap', 'matter'].forEach((val) => {
621
+ const opt = document.createElement('option')
622
+ opt.value = val
623
+ opt.text = val === 'none' ? 'None' : val.toUpperCase()
624
+ opt.selected = (device.externalPublishProtocol || 'none') === val
625
+ externalPublishSelect.appendChild(opt)
626
+ })
627
+
576
628
  // --- Room ---
577
629
  const roomLabel = document.createElement('label')
578
630
  roomLabel.textContent = 'Room'
@@ -674,6 +726,7 @@ export async function editDevice(device: any): Promise<void> {
674
726
  configDeviceName: nameInput.value || undefined,
675
727
  configDeviceType: typeSelect.value,
676
728
  connectionPreference: connectionPrefSelect.value,
729
+ externalPublishProtocol: externalPublishSelect.value,
677
730
  room: roomInput.value || undefined,
678
731
  encryptionKey: encryptionKeyInput.value || undefined,
679
732
  keyId: keyIdInput.value || undefined,
@@ -690,6 +743,9 @@ export async function editDevice(device: any): Promise<void> {
690
743
  if (params.room !== undefined) {
691
744
  options.room = params.room
692
745
  }
746
+ if (params.externalPublishProtocol !== undefined) {
747
+ options.externalPublishProtocol = params.externalPublishProtocol
748
+ }
693
749
  if (params.encryptionKey !== undefined) {
694
750
  options.encryptionKey = params.encryptionKey
695
751
  }
@@ -739,6 +795,8 @@ export async function editDevice(device: any): Promise<void> {
739
795
  contentDiv.appendChild(typeSelect)
740
796
  contentDiv.appendChild(connectionPrefLabel)
741
797
  contentDiv.appendChild(connectionPrefSelect)
798
+ contentDiv.appendChild(externalPublishLabel)
799
+ contentDiv.appendChild(externalPublishSelect)
742
800
  contentDiv.appendChild(roomLabel)
743
801
  contentDiv.appendChild(roomInput)
744
802
  contentDiv.appendChild(encryptionKeyLabel)
@@ -1011,8 +1011,9 @@ export function renderDeviceList(list: any[]): void {
1011
1011
 
1012
1012
  const typeText = d.type ? `type: ${d.type}` : ''
1013
1013
  const connText = d.connectionPreference ? `conn: ${d.connectionPreference}` : ''
1014
+ const extText = d.externalPublishProtocol && d.externalPublishProtocol !== 'none' ? `external: ${d.externalPublishProtocol}` : ''
1014
1015
  const roomText = d.room ? `room: ${d.room}` : ''
1015
- meta.textContent = [typeText, connText, roomText].filter(Boolean).join(' | ')
1016
+ meta.textContent = [typeText, connText, extText, roomText].filter(Boolean).join(' | ')
1016
1017
 
1017
1018
  info.appendChild(nameContainer)
1018
1019
  info.appendChild(meta)
package/src/settings.ts CHANGED
@@ -1,12 +1,24 @@
1
1
  export const PLUGIN_NAME = '@switchbot/homebridge-switchbot'
2
2
  export const PLATFORM_NAME = 'SwitchBot'
3
3
 
4
+ export interface SwitchBotDeviceConfig {
5
+ deviceId?: string
6
+ id?: string
7
+ configDeviceName?: string
8
+ name?: string
9
+ configDeviceType?: string
10
+ type?: string
11
+ externalPublishProtocol?: 'none' | 'hap' | 'matter'
12
+ [key: string]: any
13
+ }
14
+
4
15
  export interface SwitchBotPluginConfig {
5
16
  openApiToken?: string
6
17
  openApiSecret?: string
7
18
  preferMatter?: boolean
8
19
  enableMatter?: boolean
9
20
  enableBLE?: boolean // Enable or disable BLE support
21
+ devices?: SwitchBotDeviceConfig[]
10
22
  // other plugin-specific configuration
11
23
  [key: string]: any
12
24
  }