@switchbot/homebridge-switchbot 5.1.0-beta.9 → 5.1.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 (130) hide show
  1. package/.github/copilot-instructions.md +0 -63
  2. package/.github/scripts/normalize-changelog-header.cjs +1 -1
  3. package/.github/scripts/npm-version-script-esm-auto.js +1 -1
  4. package/.github/scripts/resolve-version-bump-esm.js +1 -1
  5. package/CHANGELOG.md +10 -9
  6. package/MIGRATION.md +0 -5
  7. package/README.md +0 -25
  8. package/config.schema.json +0 -11
  9. package/dist/{Platform.HAP.d.ts → SwitchBotHAPPlatform.d.ts} +16 -2
  10. package/dist/SwitchBotHAPPlatform.d.ts.map +1 -0
  11. package/dist/{Platform.HAP.js → SwitchBotHAPPlatform.js} +111 -40
  12. package/dist/SwitchBotHAPPlatform.js.map +1 -0
  13. package/dist/{Platform.Matter.d.ts → SwitchBotMatterPlatform.d.ts} +2 -4
  14. package/dist/SwitchBotMatterPlatform.d.ts.map +1 -0
  15. package/dist/{Platform.Matter.js → SwitchBotMatterPlatform.js} +6 -62
  16. package/dist/SwitchBotMatterPlatform.js.map +1 -0
  17. package/dist/deviceFactory.d.ts.map +1 -1
  18. package/dist/deviceFactory.js +5 -15
  19. package/dist/deviceFactory.js.map +1 -1
  20. package/dist/devices/genericDevice.d.ts +63 -5
  21. package/dist/devices/genericDevice.d.ts.map +1 -1
  22. package/dist/devices/genericDevice.js +251 -11
  23. package/dist/devices/genericDevice.js.map +1 -1
  24. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -1
  25. package/dist/homebridge-ui/endpoints/config.js +2 -9
  26. package/dist/homebridge-ui/endpoints/config.js.map +1 -1
  27. package/dist/homebridge-ui/homebridge-ui/endpoints/config.js +2 -9
  28. package/dist/homebridge-ui/homebridge-ui/endpoints/config.js.map +1 -1
  29. package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js +0 -8
  30. package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js.map +1 -1
  31. package/dist/homebridge-ui/public/css/styles.css +1 -61
  32. package/dist/homebridge-ui/public/index.html +2 -61
  33. package/dist/homebridge-ui/public/js/advanced-settings.d.ts.map +1 -1
  34. package/dist/homebridge-ui/public/js/advanced-settings.js +3 -4
  35. package/dist/homebridge-ui/public/js/advanced-settings.js.map +1 -1
  36. package/dist/homebridge-ui/public/js/advanced-settings.ts +3 -4
  37. package/dist/homebridge-ui/public/js/api.d.ts +0 -16
  38. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  39. package/dist/homebridge-ui/public/js/api.js +0 -48
  40. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  41. package/dist/homebridge-ui/public/js/api.ts +1 -61
  42. package/dist/homebridge-ui/public/js/app.js +115 -267
  43. package/dist/homebridge-ui/public/js/app.js.map +4 -4
  44. package/dist/homebridge-ui/public/js/app.ts +4 -47
  45. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -1
  46. package/dist/homebridge-ui/public/js/credentials.js +5 -11
  47. package/dist/homebridge-ui/public/js/credentials.js.map +1 -1
  48. package/dist/homebridge-ui/public/js/credentials.ts +5 -11
  49. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
  50. package/dist/homebridge-ui/public/js/devices.js +0 -6
  51. package/dist/homebridge-ui/public/js/devices.js.map +1 -1
  52. package/dist/homebridge-ui/public/js/devices.ts +0 -6
  53. package/dist/homebridge-ui/public/js/modals.d.ts +0 -1
  54. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
  55. package/dist/homebridge-ui/public/js/modals.js +0 -53
  56. package/dist/homebridge-ui/public/js/modals.js.map +1 -1
  57. package/dist/homebridge-ui/public/js/modals.ts +0 -58
  58. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
  59. package/dist/homebridge-ui/public/js/render.js +1 -2
  60. package/dist/homebridge-ui/public/js/render.js.map +1 -1
  61. package/dist/homebridge-ui/public/js/render.ts +1 -2
  62. package/dist/homebridge-ui/settings.js.map +1 -1
  63. package/dist/homebridge-ui/utils/config-parser.d.ts +0 -8
  64. package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -1
  65. package/dist/homebridge-ui/utils/config-parser.js +0 -8
  66. package/dist/homebridge-ui/utils/config-parser.js.map +1 -1
  67. package/dist/index.js +2 -2
  68. package/dist/index.js.map +1 -1
  69. package/dist/settings.d.ts +0 -11
  70. package/dist/settings.d.ts.map +1 -1
  71. package/dist/settings.js.map +1 -1
  72. package/dist/utils.d.ts +0 -5
  73. package/dist/utils.d.ts.map +1 -1
  74. package/dist/utils.js +0 -27
  75. package/dist/utils.js.map +1 -1
  76. package/docs/assets/highlight.css +15 -15
  77. package/docs/index.html +5 -22
  78. package/docs/variables/default.html +1 -1
  79. package/package.json +15 -9
  80. package/scripts/build-ui.js +0 -30
  81. package/src/{Platform.HAP.ts → SwitchBotHAPPlatform.ts} +112 -39
  82. package/src/{Platform.Matter.ts → SwitchBotMatterPlatform.ts} +8 -63
  83. package/src/deviceFactory.ts +5 -14
  84. package/src/devices/genericDevice.ts +271 -12
  85. package/src/homebridge-ui/endpoints/config.ts +2 -10
  86. package/src/homebridge-ui/public/css/styles.css +1 -61
  87. package/src/homebridge-ui/public/index.html +0 -59
  88. package/src/homebridge-ui/public/js/advanced-settings.ts +3 -4
  89. package/src/homebridge-ui/public/js/api.ts +1 -61
  90. package/src/homebridge-ui/public/js/app.ts +4 -47
  91. package/src/homebridge-ui/public/js/credentials.ts +5 -11
  92. package/src/homebridge-ui/public/js/devices.ts +0 -6
  93. package/src/homebridge-ui/public/js/modals.ts +0 -58
  94. package/src/homebridge-ui/public/js/render.ts +1 -2
  95. package/src/homebridge-ui/utils/config-parser.ts +0 -9
  96. package/src/index.ts +2 -2
  97. package/src/settings.ts +0 -12
  98. package/src/utils.ts +0 -31
  99. package/test/device/deviceFactory.spec.ts +9 -0
  100. package/test/device/getstate-cache.spec.ts +62 -0
  101. package/test/device/getstate-returns-status.spec.ts +89 -0
  102. package/test/device/water-detector-battery.spec.ts +81 -0
  103. package/test/device/water-detector-leak.spec.ts +43 -0
  104. package/test/hap-push-updates.spec.ts +150 -0
  105. package/test/platform/accessory-restore.spec.ts +2 -2
  106. package/test/platform/matter-childbridge.spec.ts +1 -1
  107. package/test/platform/matter-integration.spec.ts +1 -51
  108. package/test/platform/platform-edge.spec.ts +2 -2
  109. package/test/platform/platform.integration.spec.ts +1 -1
  110. package/test/utils/utils.spec.ts +0 -15
  111. package/.github/npm-version-script-esm.js +0 -97
  112. package/dist/Platform.HAP.d.ts.map +0 -1
  113. package/dist/Platform.HAP.js.map +0 -1
  114. package/dist/Platform.Matter.d.ts.map +0 -1
  115. package/dist/Platform.Matter.js.map +0 -1
  116. package/dist/homebridge-ui/homebridge-ui/utils/v4-detection.js +0 -23
  117. package/dist/homebridge-ui/homebridge-ui/utils/v4-detection.js.map +0 -1
  118. package/dist/homebridge-ui/public/js/legacy-view.d.ts +0 -3
  119. package/dist/homebridge-ui/public/js/legacy-view.d.ts.map +0 -1
  120. package/dist/homebridge-ui/public/js/legacy-view.js +0 -45
  121. package/dist/homebridge-ui/public/js/legacy-view.js.map +0 -1
  122. package/dist/homebridge-ui/public/js/legacy-view.ts +0 -107
  123. package/dist/homebridge-ui/utils/v4-detection.d.ts +0 -12
  124. package/dist/homebridge-ui/utils/v4-detection.d.ts.map +0 -1
  125. package/dist/homebridge-ui/utils/v4-detection.js +0 -23
  126. package/dist/homebridge-ui/utils/v4-detection.js.map +0 -1
  127. package/src/homebridge-ui/public/js/legacy-view.js +0 -45
  128. package/src/homebridge-ui/public/js/legacy-view.ts +0 -107
  129. package/src/homebridge-ui/utils/v4-detection.ts +0 -23
  130. package/test/homebridge-ui/config-parser.spec.ts +0 -61
package/docs/index.html CHANGED
@@ -40,23 +40,6 @@
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
-
60
43
  <ul>
61
44
  <li>
62
45
  <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>
@@ -134,7 +117,7 @@
134
117
  (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>.
135
118
  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>
136
119
  </ol>
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>
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>
138
121
  </code><button>Copy</button></pre>
139
122
 
140
123
  </li>
@@ -362,7 +345,7 @@ Without this step, then you will receive the following error when the swichbot p
362
345
  <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>
363
346
  <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>
364
347
  <p><strong>Example device config excerpt:</strong></p>
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>
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>
366
349
  </code><button type="button">Copy</button></pre>
367
350
 
368
351
  <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>
@@ -511,7 +494,7 @@ Without this step, then you will receive the following error when the swichbot p
511
494
  <li><code>dailyApiResetLocalMidnight</code> (boolean, default false): if true, resets the daily counter at local midnight; when false, resets at UTC midnight.</li>
512
495
  </ul>
513
496
  <p>Example (excerpt):</p>
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>
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>
515
498
  </code><button type="button">Copy</button></pre>
516
499
 
517
500
  <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>
@@ -527,7 +510,7 @@ Without this step, then you will receive the following error when the swichbot p
527
510
  <ul>
528
511
  <li>
529
512
  <p>Run unit tests:</p>
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>
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>
531
514
  </code><button type="button">Copy</button></pre>
532
515
 
533
516
  </li>
@@ -544,4 +527,4 @@ Without this step, then you will receive the following error when the swichbot p
544
527
  <li><a href="https://www.facebook.com/SwitchBotRobot/">Facebook @SwitchBotRobot</a></li>
545
528
  <li><a href="https://twitter.com/switchbot">Twitter @SwitchBot</a></li>
546
529
  </ul>
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>
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>
@@ -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/324ff500a95bfe7d722fc56e5a653b3028ebd446/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/a8567ae7471ac7354c5c3424818c54371239af9b/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.1.0-beta.9",
5
+ "version": "5.1.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": [
@@ -32,7 +32,6 @@
32
32
  },
33
33
  "keywords": [
34
34
  "homebridge-plugin",
35
- "homebridge-matter",
36
35
  "switchbot",
37
36
  "lights",
38
37
  "fan",
@@ -93,21 +92,28 @@
93
92
  },
94
93
  "dependencies": {
95
94
  "@homebridge/plugin-ui-utils": "^2.2.3",
96
- "node-switchbot": "^4.0.2"
95
+ "class-validator": "^0.15.1",
96
+ "node-switchbot": "^4.0.1"
97
97
  },
98
98
  "devDependencies": {
99
99
  "@antfu/eslint-config": "^8.2.0",
100
+ "@types/debug": "^4.1.13",
101
+ "@types/fs-extra": "^11.0.4",
100
102
  "@types/node": "^25.6.0",
101
- "@vitest/coverage-v8": "^4.1.5",
103
+ "@types/semver": "^7.7.1",
104
+ "@types/source-map-support": "^0.5.10",
105
+ "@vitest/coverage-v8": "^4.1.4",
102
106
  "esbuild": "^0.28.0",
103
- "eslint": "^10.3.0",
107
+ "eslint": "^10.2.0",
104
108
  "eslint-plugin-format": "^2.0.1",
105
- "eslint-plugin-perfectionist": "^5.9.0",
106
- "homebridge": "^2.0.1",
109
+ "eslint-plugin-html": "^8.1.4",
110
+ "eslint-plugin-perfectionist": "^5.8.0",
111
+ "homebridge": "^2.0.0-beta.89",
112
+ "homebridge-config-ui-x": "^5.21.0",
107
113
  "nodemon": "^3.1.14",
108
114
  "shx": "^0.4.0",
109
115
  "typedoc": "^0.28.19",
110
- "typescript": "^6.0.3",
111
- "vitest": "^4.1.5"
116
+ "typescript": "^6.0.2",
117
+ "vitest": "^4.1.4"
112
118
  }
113
119
  }
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import esbuild from 'esbuild'
4
- import crypto from 'node:crypto'
5
4
  import fs from 'node:fs'
6
5
  import path from 'node:path'
7
6
  import process from 'node:process'
@@ -11,38 +10,10 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
11
10
 
12
11
  const srcDir = path.resolve(__dirname, '../src/homebridge-ui/public/js')
13
12
  const outputDir = path.resolve(__dirname, '../dist/homebridge-ui/public/js')
14
- const publicDir = path.resolve(__dirname, '../dist/homebridge-ui/public')
15
13
 
16
14
  // Ensure output directory exists
17
15
  fs.mkdirSync(outputDir, { recursive: true })
18
16
 
19
- function assetHash(filePath) {
20
- const contents = fs.readFileSync(filePath)
21
- return crypto.createHash('sha256').update(contents).digest('hex').slice(0, 8)
22
- }
23
-
24
- function cacheBustBuiltAssets() {
25
- const indexPath = path.join(publicDir, 'index.html')
26
- if (!fs.existsSync(indexPath)) {
27
- return
28
- }
29
-
30
- let html = fs.readFileSync(indexPath, 'utf8')
31
- const assets = ['app.js', 'advanced-settings.js']
32
-
33
- for (const assetName of assets) {
34
- const assetPath = path.join(outputDir, assetName)
35
- if (!fs.existsSync(assetPath)) {
36
- continue
37
- }
38
-
39
- const hashedSrc = `js/${assetName}?v=${assetHash(assetPath)}`
40
- html = html.replace(new RegExp(`js/${assetName}(\\?v=[^\"']+)?`, 'g'), hashedSrc)
41
- }
42
-
43
- fs.writeFileSync(indexPath, html)
44
- }
45
-
46
17
  // Transpile TypeScript to JavaScript with esbuild
47
18
  esbuild
48
19
  .build({
@@ -58,7 +29,6 @@ esbuild
58
29
  logLevel: 'info',
59
30
  })
60
31
  .then(() => {
61
- cacheBustBuiltAssets()
62
32
  console.log('✓ UI bundling complete')
63
33
  })
64
34
  .catch((error) => {
@@ -1,11 +1,10 @@
1
- import type { API, Logger, PlatformConfig } from 'homebridge'
2
-
3
1
  import type { SwitchBotPluginConfig } from './settings.js'
2
+ import type { API, Logger, PlatformConfig } from 'homebridge'
4
3
 
5
4
  import { createDevice } from './deviceFactory.js'
6
5
  import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'
7
6
  import { SwitchBotClient } from './switchbotClient.js'
8
- import { normalizeTypeForMatter, resolveExternalPublishProtocol } from './utils.js'
7
+ import { normalizeTypeForMatter } from './utils.js'
9
8
 
10
9
  /**
11
10
  * Homebridge platform class for SwitchBot HAP (HomeKit Accessory Protocol) integration.
@@ -42,6 +41,9 @@ export class SwitchBotHAPPlatform {
42
41
  private lastConfigHash: string = ''
43
42
  /** Interval for periodic config reload */
44
43
  private configReloadInterval: NodeJS.Timeout | null = null
44
+ /** Readable characteristics per device id, so a poll can push new values */
45
+ private hapPushTargets: Map<string, { device: any, entries: Array<{ service: any, characteristic: any, get: () => Promise<any> }> }> = new Map()
46
+
45
47
  /** Timers for per-device OpenAPI polling */
46
48
  private openApiPollTimers: Map<string, NodeJS.Timeout> = new Map()
47
49
  /** Timer for batched OpenAPI polling */
@@ -141,7 +143,7 @@ export class SwitchBotHAPPlatform {
141
143
  }
142
144
 
143
145
  const devices = (this.config as any)?.devices ?? []
144
- const createdDevices: { created: any, d: any, type: string, externalPublishProtocol: 'none' | 'hap' | 'matter' }[] = []
146
+ const createdDevices: { created: any, d: any, type: string }[] = []
145
147
  for (const raw of devices) {
146
148
  // Normalize config keys from UI schema to internal shape (for cross-platform consistency)
147
149
  const d: any = {
@@ -153,19 +155,19 @@ export class SwitchBotHAPPlatform {
153
155
  _raw: raw,
154
156
  }
155
157
  const type: string = normalizeTypeForMatter(d.type)
156
- const externalPublishProtocol = resolveExternalPublishProtocol(raw)
157
158
  const deviceOpts: any = { id: d.id, type, name: d.name, encryptionKey: d.encryptionKey, keyId: d.keyId, log: this.log }
158
159
  this.log.debug(`[HAP/Debug] Device options for ${d.name ?? d.id}:`, JSON.stringify(deviceOpts, null, 2))
159
160
  try {
160
161
  const created = await createDevice(deviceOpts, this.config, false)
161
162
  this.devices.push(created)
162
- createdDevices.push({ created, d, type, externalPublishProtocol })
163
+ createdDevices.push({ created, d, type })
163
164
  } catch (e: any) {
164
165
  this.log.error(`Failed to create HAP device ${d.id}:`, e instanceof Error ? e.stack || e.message : e)
165
166
  }
166
167
  }
167
168
  // Register HAP accessories after all devices are created for symmetry with Matter platform
168
169
  await this.registerHAPAccessories(createdDevices)
170
+ this.primeHapValues()
169
171
  this.lastConfigHash = newHash
170
172
  }
171
173
 
@@ -190,15 +192,14 @@ export class SwitchBotHAPPlatform {
190
192
  *
191
193
  * If the Homebridge HAP API is not available, registration is skipped and a log message is emitted.
192
194
  */
193
- private async registerHAPAccessories(createdDevices: { created: any, d: any, type: string, externalPublishProtocol: 'none' | 'hap' | 'matter' }[]): Promise<void> {
194
- if (!this.api || !(this.api as any).hap || typeof (this.api as any).hap.registerPlatformAccessories !== 'function') {
195
+ private async registerHAPAccessories(createdDevices: { created: any, d: any, type: string }[]): Promise<void> {
196
+ if (!this.api || !(this.api as any).hap || typeof (this.api as any).registerPlatformAccessories !== 'function') {
195
197
  this.log.info('HAP API not available to register accessories')
196
198
  return
197
199
  }
198
200
  const hap = (this.api as any).hap
199
201
  const accessoriesToRegister: any[] = []
200
- const externalAccessoriesToPublish: any[] = []
201
- for (const { created, d, type, externalPublishProtocol } of createdDevices) {
202
+ for (const { created, d, type } of createdDevices) {
202
203
  try {
203
204
  const uuid = hap.uuid.generate(`${d.id}`)
204
205
  let accessory: any = this.accessories.get(uuid)
@@ -215,9 +216,7 @@ export class SwitchBotHAPPlatform {
215
216
  }
216
217
  }
217
218
  if (!accessory) {
218
- // Use the PlatformAccessory constructor from the HAP API
219
- const PlatformAccessory = (this.api as any).hap?.PlatformAccessory || (this.api as any).platformAccessory
220
- accessory = new PlatformAccessory(d.name || type, uuid)
219
+ accessory = new (this.api as any).platformAccessory(d.name || type, uuid)
221
220
  try {
222
221
  accessory.context = accessory.context || {}
223
222
  accessory.context.deviceId = d.id
@@ -225,11 +224,7 @@ export class SwitchBotHAPPlatform {
225
224
  } catch (e) {
226
225
  // ignore context failures
227
226
  }
228
- if (externalPublishProtocol === 'hap') {
229
- externalAccessoriesToPublish.push(accessory)
230
- } else {
231
- accessoriesToRegister.push(accessory)
232
- }
227
+ accessoriesToRegister.push(accessory)
233
228
  this.accessories.set(uuid, accessory)
234
229
  } else {
235
230
  try {
@@ -249,12 +244,38 @@ export class SwitchBotHAPPlatform {
249
244
  // ignore
250
245
  }
251
246
  }
247
+ // Ensure AccessoryInformation service exists - required for cache deserialization.
248
+ // Without it, hap-nodejs _sideloadServices crashes on next start.
249
+ try {
250
+ const InfoService = hap.Service.AccessoryInformation
251
+ if (InfoService) {
252
+ const info = accessory.getService(InfoService) || accessory.addService(InfoService)
253
+ const accDescForInfo = await created.createAccessory?.(this.api).catch?.(() => undefined)
254
+ const manufacturer = (accDescForInfo && accDescForInfo.manufacturer) || 'SwitchBot'
255
+ const model = (accDescForInfo && accDescForInfo.model) || type || 'SwitchBot Device'
256
+ const serial = (accDescForInfo && accDescForInfo.serialNumber) || d.id
257
+ const firmware = (accDescForInfo && accDescForInfo.firmwareRevision) || '1.0.0'
258
+ try {
259
+ info.setCharacteristic(hap.Characteristic.Manufacturer, manufacturer)
260
+ info.setCharacteristic(hap.Characteristic.Model, model)
261
+ info.setCharacteristic(hap.Characteristic.SerialNumber, serial)
262
+ info.setCharacteristic(hap.Characteristic.FirmwareRevision, firmware)
263
+ } catch (e) {
264
+ // ignore
265
+ }
266
+ }
267
+ } catch (e) {
268
+ // ignore
269
+ }
252
270
  // Add basic service descriptor from device (symmetrical to Matter: remove stale services/chars)
253
271
  const accDesc = await created.createAccessory?.(this.api)
254
272
  if (accDesc && accDesc.services) {
255
273
  const serviceTypes = accDesc.services.map((s: any) => s.type)
256
274
  for (const existingService of accessory.services.slice()) {
257
- if (existingService.constructor && existingService.constructor.name && !serviceTypes.includes(existingService.constructor.name)) {
275
+ // Never remove AccessoryInformation - required for cache deserialization
276
+ if (existingService.constructor && existingService.constructor.name
277
+ && existingService.constructor.name !== 'AccessoryInformation'
278
+ && !serviceTypes.includes(existingService.constructor.name)) {
258
279
  accessory.removeService(existingService)
259
280
  }
260
281
  }
@@ -285,9 +306,32 @@ export class SwitchBotHAPPlatform {
285
306
  }
286
307
  if (getterSetter && typeof getterSetter.get === 'function') {
287
308
  service.getCharacteristic(Characteristic).onGet(getterSetter.get)
309
+ // Remember it so a poll can push the new value.
310
+ const target = this.hapPushTargets.get(d.id) ?? { device: created?.instance ?? created, entries: [] }
311
+ target.entries.push({ service, characteristic: Characteristic, get: getterSetter.get })
312
+ this.hapPushTargets.set(d.id, target)
288
313
  }
289
314
  if (getterSetter && typeof getterSetter.set === 'function') {
290
- service.getCharacteristic(Characteristic).onSet(getterSetter.set)
315
+ service.getCharacteristic(Characteristic).onSet(async (value: any) => {
316
+ await getterSetter.set(value)
317
+
318
+ const refreshAfterSet = Array.isArray(getterSetter.refreshAfterSet) ? getterSetter.refreshAfterSet : []
319
+ for (const refreshCharName of refreshAfterSet) {
320
+ const refreshGetterSetter: any = (s.characteristics || {})[refreshCharName]
321
+ if (!refreshGetterSetter || typeof refreshGetterSetter.get !== 'function') {
322
+ continue
323
+ }
324
+ const RefreshCharacteristic = (hap.Characteristic as any)[refreshCharName]
325
+ if (!RefreshCharacteristic) {
326
+ continue
327
+ }
328
+ try {
329
+ service.getCharacteristic(RefreshCharacteristic).updateValue(await refreshGetterSetter.get())
330
+ } catch (e) {
331
+ this.log.debug?.(`Failed to refresh ${refreshCharName} after setting ${charName}`, e)
332
+ }
333
+ }
334
+ })
291
335
  }
292
336
  }
293
337
  }
@@ -307,24 +351,6 @@ export class SwitchBotHAPPlatform {
307
351
  } else {
308
352
  this.log.info('No HAP accessories to register')
309
353
  }
310
- if (externalAccessoriesToPublish.length > 0) {
311
- if (typeof (this.api as any).publishExternalAccessories === 'function') {
312
- try {
313
- (this.api as any).publishExternalAccessories(PLUGIN_NAME, externalAccessoriesToPublish)
314
- this.log.info(`Published ${externalAccessoriesToPublish.length} HAP external accessory(ies) with Homebridge`)
315
- } catch (e) {
316
- this.log.warn('Failed to publish HAP external accessories', e)
317
- }
318
- } else {
319
- this.log.warn('HAP external accessory publishing API not available; falling back to regular platform registration')
320
- try {
321
- (this.api as any).registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, externalAccessoriesToPublish)
322
- this.log.info(`Registered ${externalAccessoriesToPublish.length} HAP accessory(ies) as fallback (external publishing unavailable)`)
323
- } catch (e) {
324
- this.log.warn('Failed to register fallback HAP accessories', e)
325
- }
326
- }
327
- }
328
354
  }
329
355
 
330
356
  /**
@@ -359,7 +385,6 @@ export class SwitchBotHAPPlatform {
359
385
  id: d.deviceId ?? d.id,
360
386
  type: d.configDeviceType ?? d.type,
361
387
  name: d.configDeviceName ?? d.name,
362
- externalPublishProtocol: resolveExternalPublishProtocol(d),
363
388
  })))
364
389
  }
365
390
 
@@ -422,6 +447,52 @@ export class SwitchBotHAPPlatform {
422
447
  *
423
448
  * @returns {void}
424
449
  */
450
+ /**
451
+ * Read every registered characteristic for a device and notify HomeKit.
452
+ *
453
+ * The device state is cached and de-duplicated, so one call costs a single
454
+ * API request however many characteristics the accessory has.
455
+ */
456
+ private async _pushDeviceState(id: string): Promise<void> {
457
+ const target = this.hapPushTargets.get(id)
458
+ if (!target?.entries.length) {
459
+ return
460
+ }
461
+ // Read once first. A device that could not be read must push nothing: the
462
+ // getters report a fallback for a missing field, and pushing that would
463
+ // replace a good value in HomeKit with one that looks like a measurement.
464
+ try {
465
+ const state = await target.device?.getState?.()
466
+ if (state?.unreadable) {
467
+ this.log.debug?.(`Skipped pushing updates for ${id}: device not readable`)
468
+ return
469
+ }
470
+ } catch (e) {
471
+ this.log.debug?.(`Skipped pushing updates for ${id}:`, (e as Error)?.message)
472
+ return
473
+ }
474
+ for (const { service, characteristic, get } of target.entries) {
475
+ try {
476
+ const value = await get()
477
+ if (value !== undefined && value !== null) {
478
+ service.getCharacteristic(characteristic).updateValue(value)
479
+ }
480
+ } catch (e) {
481
+ this.log.debug?.(`Failed to push an update for ${id}:`, (e as Error)?.message)
482
+ }
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Push once now, so the Home app shows a value without waiting for the first
488
+ * poll interval.
489
+ */
490
+ private primeHapValues(): void {
491
+ for (const id of this.hapPushTargets.keys()) {
492
+ void this._pushDeviceState(id)
493
+ }
494
+ }
495
+
425
496
  private _setupOpenApiPolling(): void {
426
497
  // Clear any existing timers
427
498
  for (const t of this.openApiPollTimers.values()) clearInterval(t)
@@ -497,6 +568,7 @@ export class SwitchBotHAPPlatform {
497
568
  if (client && typeof client.getDevice === 'function') {
498
569
  await client.getDevice(id)
499
570
  this.openApiRequestsToday++
571
+ await this._pushDeviceState(id)
500
572
  this.log.debug(`[OpenAPI] Polled device ${id} (per-device interval ${perDeviceRate}s) [${this.openApiRequestsToday}/${dailyLimit}]`)
501
573
  }
502
574
  } catch (e) {
@@ -536,6 +608,7 @@ export class SwitchBotHAPPlatform {
536
608
  try {
537
609
  await client.getDevice(dev.deviceId ?? dev.id)
538
610
  this.openApiRequestsToday++
611
+ await this._pushDeviceState(dev.deviceId ?? dev.id)
539
612
  this.log.debug(`[OpenAPI] Batched poll device ${dev.deviceId ?? dev.id} [${this.openApiRequestsToday}/${dailyLimit}]`)
540
613
  } catch (e) {
541
614
  this.log.debug(`[OpenAPI] Batched polling failed for device ${dev.deviceId ?? dev.id}:`, (e as Error)?.message)