@switchbot/homebridge-switchbot 5.1.0-beta.9 → 5.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +0 -63
- package/.github/scripts/compute-version-override.cjs +51 -0
- package/.github/scripts/normalize-changelog-header.cjs +1 -1
- package/.github/scripts/npm-version-script-esm-auto.js +1 -1
- package/.github/scripts/resolve-version-bump-esm.js +1 -1
- package/CHANGELOG.md +32 -9
- package/MIGRATION.md +0 -5
- package/README.md +0 -25
- package/config.schema.json +0 -11
- package/dist/{Platform.HAP.d.ts → SwitchBotHAPPlatform.d.ts} +16 -2
- package/dist/SwitchBotHAPPlatform.d.ts.map +1 -0
- package/dist/{Platform.HAP.js → SwitchBotHAPPlatform.js} +111 -40
- package/dist/SwitchBotHAPPlatform.js.map +1 -0
- package/dist/{Platform.Matter.d.ts → SwitchBotMatterPlatform.d.ts} +2 -4
- package/dist/SwitchBotMatterPlatform.d.ts.map +1 -0
- package/dist/{Platform.Matter.js → SwitchBotMatterPlatform.js} +6 -62
- package/dist/SwitchBotMatterPlatform.js.map +1 -0
- package/dist/device-types.d.ts.map +1 -1
- package/dist/device-types.js +10 -0
- package/dist/device-types.js.map +1 -1
- package/dist/deviceFactory.d.ts.map +1 -1
- package/dist/deviceFactory.js +10 -16
- package/dist/deviceFactory.js.map +1 -1
- package/dist/devices/genericDevice.d.ts +63 -5
- package/dist/devices/genericDevice.d.ts.map +1 -1
- package/dist/devices/genericDevice.js +251 -11
- package/dist/devices/genericDevice.js.map +1 -1
- package/dist/homebridge-ui/device-types.js +10 -0
- package/dist/homebridge-ui/device-types.js.map +1 -1
- package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -1
- package/dist/homebridge-ui/endpoints/config.js +2 -9
- package/dist/homebridge-ui/endpoints/config.js.map +1 -1
- package/dist/homebridge-ui/homebridge-ui/endpoints/config.js +2 -9
- package/dist/homebridge-ui/homebridge-ui/endpoints/config.js.map +1 -1
- package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js +0 -8
- package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js.map +1 -1
- package/dist/homebridge-ui/public/css/styles.css +1 -61
- package/dist/homebridge-ui/public/index.html +2 -61
- package/dist/homebridge-ui/public/js/advanced-settings.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/advanced-settings.js +3 -4
- package/dist/homebridge-ui/public/js/advanced-settings.js.map +1 -1
- package/dist/homebridge-ui/public/js/advanced-settings.ts +3 -4
- package/dist/homebridge-ui/public/js/api.d.ts +0 -16
- package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/api.js +0 -48
- package/dist/homebridge-ui/public/js/api.js.map +1 -1
- package/dist/homebridge-ui/public/js/api.ts +1 -61
- package/dist/homebridge-ui/public/js/app.js +135 -272
- package/dist/homebridge-ui/public/js/app.js.map +4 -4
- package/dist/homebridge-ui/public/js/app.ts +4 -47
- package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/credentials.js +5 -11
- package/dist/homebridge-ui/public/js/credentials.js.map +1 -1
- package/dist/homebridge-ui/public/js/credentials.ts +5 -11
- package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/devices.js +0 -6
- package/dist/homebridge-ui/public/js/devices.js.map +1 -1
- package/dist/homebridge-ui/public/js/devices.ts +0 -6
- package/dist/homebridge-ui/public/js/modals.d.ts +0 -1
- package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/modals.js +0 -53
- package/dist/homebridge-ui/public/js/modals.js.map +1 -1
- package/dist/homebridge-ui/public/js/modals.ts +0 -58
- package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/render.js +1 -2
- package/dist/homebridge-ui/public/js/render.js.map +1 -1
- package/dist/homebridge-ui/public/js/render.ts +1 -2
- package/dist/homebridge-ui/settings.js.map +1 -1
- package/dist/homebridge-ui/utils/config-parser.d.ts +0 -8
- package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -1
- package/dist/homebridge-ui/utils/config-parser.js +0 -8
- package/dist/homebridge-ui/utils/config-parser.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/settings.d.ts +0 -11
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- package/dist/utils.d.ts +0 -5
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +0 -27
- package/dist/utils.js.map +1 -1
- package/docs/assets/highlight.css +16 -16
- package/docs/assets/main.js +1 -1
- package/docs/assets/style.css +18 -3
- package/docs/hierarchy.html +26 -1
- package/docs/index.html +70 -23
- package/docs/modules.html +33 -1
- package/docs/variables/default.html +46 -3
- package/package.json +22 -15
- package/scripts/build-ui.js +0 -30
- package/src/{Platform.HAP.ts → SwitchBotHAPPlatform.ts} +112 -39
- package/src/{Platform.Matter.ts → SwitchBotMatterPlatform.ts} +8 -63
- package/src/device-types.ts +11 -0
- package/src/deviceFactory.ts +10 -15
- package/src/devices/genericDevice.ts +271 -12
- package/src/homebridge-ui/endpoints/config.ts +2 -10
- package/src/homebridge-ui/public/css/styles.css +1 -61
- package/src/homebridge-ui/public/index.html +0 -59
- package/src/homebridge-ui/public/js/advanced-settings.ts +3 -4
- package/src/homebridge-ui/public/js/api.ts +1 -61
- package/src/homebridge-ui/public/js/app.ts +4 -47
- package/src/homebridge-ui/public/js/credentials.ts +5 -11
- package/src/homebridge-ui/public/js/devices.ts +0 -6
- package/src/homebridge-ui/public/js/modals.ts +0 -58
- package/src/homebridge-ui/public/js/render.ts +1 -2
- package/src/homebridge-ui/utils/config-parser.ts +0 -9
- package/src/index.ts +2 -2
- package/src/settings.ts +0 -12
- package/src/utils.ts +0 -31
- package/test/device/deviceFactory.spec.ts +9 -0
- package/test/device/getstate-cache.spec.ts +62 -0
- package/test/device/getstate-returns-status.spec.ts +89 -0
- package/test/device/model-identities.spec.ts +45 -0
- package/test/device/water-detector-battery.spec.ts +81 -0
- package/test/device/water-detector-leak.spec.ts +43 -0
- package/test/hap-push-updates.spec.ts +150 -0
- package/test/platform/accessory-restore.spec.ts +2 -2
- package/test/platform/matter-childbridge.spec.ts +1 -1
- package/test/platform/matter-integration.spec.ts +1 -51
- package/test/platform/platform-edge.spec.ts +2 -2
- package/test/platform/platform.integration.spec.ts +1 -1
- package/test/utils/utils.spec.ts +0 -15
- package/.github/npm-version-script-esm.js +0 -97
- package/dist/Platform.HAP.d.ts.map +0 -1
- package/dist/Platform.HAP.js.map +0 -1
- package/dist/Platform.Matter.d.ts.map +0 -1
- package/dist/Platform.Matter.js.map +0 -1
- package/dist/homebridge-ui/homebridge-ui/utils/v4-detection.js +0 -23
- package/dist/homebridge-ui/homebridge-ui/utils/v4-detection.js.map +0 -1
- package/dist/homebridge-ui/public/js/legacy-view.d.ts +0 -3
- package/dist/homebridge-ui/public/js/legacy-view.d.ts.map +0 -1
- package/dist/homebridge-ui/public/js/legacy-view.js +0 -45
- package/dist/homebridge-ui/public/js/legacy-view.js.map +0 -1
- package/dist/homebridge-ui/public/js/legacy-view.ts +0 -107
- package/dist/homebridge-ui/utils/v4-detection.d.ts +0 -12
- package/dist/homebridge-ui/utils/v4-detection.d.ts.map +0 -1
- package/dist/homebridge-ui/utils/v4-detection.js +0 -23
- package/dist/homebridge-ui/utils/v4-detection.js.map +0 -1
- package/src/device-types.js +0 -246
- package/src/homebridge-ui/public/js/legacy-view.js +0 -45
- package/src/homebridge-ui/public/js/legacy-view.ts +0 -107
- package/src/homebridge-ui/utils/v4-detection.ts +0 -23
- package/test/homebridge-ui/config-parser.spec.ts +0 -61
|
@@ -1,3 +1,46 @@
|
|
|
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"
|
|
2
|
-
|
|
3
|
-
|
|
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">
|
|
2
|
+
<div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@switchbot/homebridge-switchbot</a>
|
|
3
|
+
<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"/>
|
|
4
|
+
<ul role="listbox" id="tsd-search-results"></ul>
|
|
5
|
+
<div id="tsd-search-status" aria-live="polite" aria-atomic="true">
|
|
6
|
+
<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>
|
|
7
|
+
<div class="container container-main">
|
|
8
|
+
<div class="col-content">
|
|
9
|
+
<div class="tsd-page-title">
|
|
10
|
+
<ul class="tsd-breadcrumb" aria-label="Breadcrumb">
|
|
11
|
+
<li><a href="" aria-current="page">default</a></li></ul>
|
|
12
|
+
<h1>Variable default</h1></div>
|
|
13
|
+
<div class="tsd-signature"><span class="tsd-kind-variable">default</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span> <span class="tsd-signature-symbol">=></span> <span class="tsd-signature-type">void</span></div>
|
|
14
|
+
<div class="tsd-comment tsd-typography"><p>Registers the SwitchBot platform with Homebridge.</p>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="tsd-type-declaration">
|
|
17
|
+
<h4>Type Declaration</h4>
|
|
18
|
+
<ul class="tsd-parameters">
|
|
19
|
+
<li class="tsd-parameter-signature">
|
|
20
|
+
<ul class="tsd-signatures">
|
|
21
|
+
<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>
|
|
22
|
+
<li class="tsd-description">
|
|
23
|
+
<div class="tsd-parameters">
|
|
24
|
+
<h4 class="tsd-parameters-title">Parameters</h4>
|
|
25
|
+
<ul class="tsd-parameter-list">
|
|
26
|
+
<li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span>
|
|
27
|
+
<div class="tsd-comment tsd-typography"><p>The Homebridge API instance.</p>
|
|
28
|
+
</div></li></ul></div>
|
|
29
|
+
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources">
|
|
30
|
+
<ul>
|
|
31
|
+
<li>Defined in <a href="https://github.com/OpenWonderLabs/homebridge-switchbot/blob/64fac398b3b1f3ecf7b59ce9e465622014310354/src/index.ts#L22">index.ts:22</a></li></ul></aside></div>
|
|
32
|
+
<div class="col-sidebar">
|
|
33
|
+
<div class="page-menu">
|
|
34
|
+
<div class="tsd-navigation settings">
|
|
35
|
+
<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>
|
|
36
|
+
<h3>Settings</h3></summary>
|
|
37
|
+
<div class="tsd-accordion-details">
|
|
38
|
+
<div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span>
|
|
39
|
+
<ul id="tsd-filter-options">
|
|
40
|
+
<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>
|
|
41
|
+
<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>
|
|
42
|
+
<div class="site-menu">
|
|
43
|
+
<nav class="tsd-navigation"><a href="../modules.html">@switchbot/homebridge-switchbot</a>
|
|
44
|
+
<ul class="tsd-small-nested-navigation" id="tsd-nav-container">
|
|
45
|
+
<li>Loading...</li></ul></nav></div></div></div><footer></footer>
|
|
46
|
+
<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.
|
|
5
|
+
"version": "5.1.1",
|
|
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",
|
|
@@ -57,7 +56,7 @@
|
|
|
57
56
|
"icon": "https://raw.githubusercontent.com/OpenWonderLabs/homebridge-switchbot/latest/branding/icon.png",
|
|
58
57
|
"engineStrict": true,
|
|
59
58
|
"engines": {
|
|
60
|
-
"homebridge": "^2.0.0
|
|
59
|
+
"homebridge": "^2.0.0",
|
|
61
60
|
"node": "^22 || ^24"
|
|
62
61
|
},
|
|
63
62
|
"directories": {
|
|
@@ -92,22 +91,30 @@
|
|
|
92
91
|
"access": "public"
|
|
93
92
|
},
|
|
94
93
|
"dependencies": {
|
|
95
|
-
"@homebridge/plugin-ui-utils": "^2.2.
|
|
96
|
-
"
|
|
94
|
+
"@homebridge/plugin-ui-utils": "^2.2.6",
|
|
95
|
+
"class-validator": "^0.15.1",
|
|
96
|
+
"node-switchbot": "^5.0.0"
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|
|
99
|
-
"@antfu/eslint-config": "^
|
|
100
|
-
"@types/
|
|
101
|
-
"@
|
|
102
|
-
"
|
|
103
|
-
"
|
|
99
|
+
"@antfu/eslint-config": "^9.5.1",
|
|
100
|
+
"@types/debug": "^4.1.13",
|
|
101
|
+
"@types/fs-extra": "^11.0.4",
|
|
102
|
+
"@types/node": "^26.6.4",
|
|
103
|
+
"@types/semver": "^7.8.0",
|
|
104
|
+
"@types/source-map-support": "^0.5.10",
|
|
105
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
106
|
+
"esbuild": "^0.28.2",
|
|
107
|
+
"eslint": "^10.12.0",
|
|
104
108
|
"eslint-plugin-format": "^2.0.1",
|
|
105
|
-
"eslint-plugin-
|
|
106
|
-
"
|
|
109
|
+
"eslint-plugin-html": "^8.2.1",
|
|
110
|
+
"eslint-plugin-perfectionist": "^5.12.1",
|
|
111
|
+
"homebridge": "^2.4.0",
|
|
112
|
+
"homebridge-config-ui-x": "^5.29.0",
|
|
107
113
|
"nodemon": "^3.1.14",
|
|
108
114
|
"shx": "^0.4.0",
|
|
109
|
-
"typedoc": "^0.28.
|
|
110
|
-
"typescript": "^6.0.
|
|
111
|
-
"
|
|
115
|
+
"typedoc": "^0.28.20",
|
|
116
|
+
"typescript": "^6.0.2",
|
|
117
|
+
"vite": "^7.0.0",
|
|
118
|
+
"vitest": "^5.0.3"
|
|
112
119
|
}
|
|
113
120
|
}
|
package/scripts/build-ui.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
194
|
-
if (!this.api || !(this.api as any).hap || typeof (this.api as any).
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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)
|
|
@@ -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 { createMatterHandlers, DEVICE_MATTER_CLUSTERS, DEVICE_MATTER_SUPPORTED, normalizeTypeForMatter,
|
|
7
|
+
import { createMatterHandlers, DEVICE_MATTER_CLUSTERS, DEVICE_MATTER_SUPPORTED, normalizeTypeForMatter, resolveMatterDeviceType } from './utils.js'
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Homebridge platform class for SwitchBot Matter integration.
|
|
@@ -114,7 +113,7 @@ export class SwitchBotMatterPlatform {
|
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
const devices = (this.config as any)?.devices ?? []
|
|
117
|
-
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 }[] = []
|
|
118
117
|
for (const raw of devices) {
|
|
119
118
|
// Normalize config keys from UI schema to internal shape (for cross-platform consistency)
|
|
120
119
|
const d: any = {
|
|
@@ -126,7 +125,6 @@ export class SwitchBotMatterPlatform {
|
|
|
126
125
|
_raw: raw,
|
|
127
126
|
}
|
|
128
127
|
const type: string = normalizeTypeForMatter(d.type)
|
|
129
|
-
const externalPublishProtocol = resolveExternalPublishProtocol(raw)
|
|
130
128
|
const deviceOpts: any = { id: d.id, type, name: d.name, encryptionKey: d.encryptionKey, keyId: d.keyId, log: this.log }
|
|
131
129
|
this.log.debug(`[Matter/Debug] Device options for ${d.name ?? d.id}:`, JSON.stringify(deviceOpts, null, 2))
|
|
132
130
|
const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()]
|
|
@@ -136,7 +134,7 @@ export class SwitchBotMatterPlatform {
|
|
|
136
134
|
try {
|
|
137
135
|
const created = await createDevice(deviceOpts, this.config, useMatter)
|
|
138
136
|
this.devices.push(created)
|
|
139
|
-
createdDevices.push({ created, d, type, useMatter, matterAvailable
|
|
137
|
+
createdDevices.push({ created, d, type, useMatter, matterAvailable })
|
|
140
138
|
if (useMatter) {
|
|
141
139
|
this.log.info(`Prepared Matter accessory for ${d.id} (${type})${matterAvailable ? ' (auto-detected)' : ' (manually enabled)'}`)
|
|
142
140
|
} else {
|
|
@@ -181,15 +179,14 @@ export class SwitchBotMatterPlatform {
|
|
|
181
179
|
* If the Homebridge Matter API is not available, registration is skipped and a log message is emitted.
|
|
182
180
|
* Accessories that are not enabled for Matter are ignored.
|
|
183
181
|
*/
|
|
184
|
-
async registerMatterAccessories(createdDevices: { created: any, d: any, type: string, useMatter: boolean, matterAvailable: boolean
|
|
182
|
+
async registerMatterAccessories(createdDevices: { created: any, d: any, type: string, useMatter: boolean, matterAvailable: boolean }[]): Promise<void> {
|
|
185
183
|
if (!this.api || !(this.api as any).matter || typeof (this.api as any).matter.registerPlatformAccessories !== 'function') {
|
|
186
184
|
this.log.info('Homebridge Matter API not available; skipping Matter accessory registration')
|
|
187
185
|
return
|
|
188
186
|
}
|
|
189
187
|
const matterApi = (this.api as any).matter
|
|
190
188
|
const accessoriesToRegister: any[] = []
|
|
191
|
-
const
|
|
192
|
-
for (const { created, d, type, useMatter, matterAvailable, externalPublishProtocol } of createdDevices) {
|
|
189
|
+
for (const { created, d, type, useMatter, matterAvailable } of createdDevices) {
|
|
193
190
|
// Only register accessories where Matter is enabled and supported
|
|
194
191
|
if (!useMatter) {
|
|
195
192
|
// Log reason for skipping registration
|
|
@@ -240,11 +237,7 @@ export class SwitchBotMatterPlatform {
|
|
|
240
237
|
handlers: createdDesc.handlers || createMatterHandlers(this.log, d.id, type, (this.config as any)?._client) || undefined,
|
|
241
238
|
context: { deviceId: d.id, type, created: true },
|
|
242
239
|
}
|
|
243
|
-
|
|
244
|
-
externalAccessoriesToPublish.push(accessory)
|
|
245
|
-
} else {
|
|
246
|
-
accessoriesToRegister.push(accessory)
|
|
247
|
-
}
|
|
240
|
+
accessoriesToRegister.push(accessory)
|
|
248
241
|
this.accessories.set(uuid, accessory)
|
|
249
242
|
} else {
|
|
250
243
|
// Ensure context and update properties for restored accessory
|
|
@@ -267,11 +260,7 @@ export class SwitchBotMatterPlatform {
|
|
|
267
260
|
accessory.handlers = createdDesc.handlers || createMatterHandlers(this.log, d.id, type, (this.config as any)?._client) || undefined
|
|
268
261
|
accessory.displayName = createdDesc.name || d.name || type
|
|
269
262
|
accessory.UUID = accessory.UUID || accessory.uuid || uuid
|
|
270
|
-
|
|
271
|
-
externalAccessoriesToPublish.push(accessory)
|
|
272
|
-
} else {
|
|
273
|
-
accessoriesToRegister.push(accessory)
|
|
274
|
-
}
|
|
263
|
+
accessoriesToRegister.push(accessory)
|
|
275
264
|
this.accessories.set(accessory.UUID || uuid, accessory)
|
|
276
265
|
}
|
|
277
266
|
this.log.info(`Created/updated Matter accessory ${d.id} (${type})`)
|
|
@@ -289,49 +278,6 @@ export class SwitchBotMatterPlatform {
|
|
|
289
278
|
} else {
|
|
290
279
|
this.log.info('No Matter accessories to register')
|
|
291
280
|
}
|
|
292
|
-
if (externalAccessoriesToPublish.length > 0) {
|
|
293
|
-
try {
|
|
294
|
-
if (typeof matterApi.publishExternalAccessories === 'function') {
|
|
295
|
-
await matterApi.publishExternalAccessories(PLUGIN_NAME, externalAccessoriesToPublish)
|
|
296
|
-
this.log.info(`Published ${externalAccessoriesToPublish.length} Matter external accessory(ies) with Homebridge`)
|
|
297
|
-
} else if (typeof matterApi.registerExternalAccessories === 'function') {
|
|
298
|
-
await matterApi.registerExternalAccessories(PLUGIN_NAME, PLATFORM_NAME, externalAccessoriesToPublish)
|
|
299
|
-
this.log.info(`Registered ${externalAccessoriesToPublish.length} Matter external accessory(ies) with Homebridge`)
|
|
300
|
-
} else if (await this.publishMatterExternalAccessories(externalAccessoriesToPublish)) {
|
|
301
|
-
this.log.info(`Published ${externalAccessoriesToPublish.length} Matter external accessory(ies) with Homebridge`)
|
|
302
|
-
} else {
|
|
303
|
-
this.log.warn('Matter external accessory publishing API not available; falling back to regular platform registration')
|
|
304
|
-
await matterApi.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, externalAccessoriesToPublish)
|
|
305
|
-
this.log.info(`Registered ${externalAccessoriesToPublish.length} Matter accessory(ies) as fallback (external publishing unavailable)`)
|
|
306
|
-
}
|
|
307
|
-
} catch (e) {
|
|
308
|
-
this.log.warn('Failed to publish/register Matter external accessories', e)
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
private async publishMatterExternalAccessories(accessories: any[]): Promise<boolean> {
|
|
314
|
-
const api = this.api as any
|
|
315
|
-
if (!api || typeof api.emit !== 'function') {
|
|
316
|
-
return false
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const pendingRegistrations = api._pendingExternalRegistrations
|
|
320
|
-
if (!(pendingRegistrations instanceof Map) && pendingRegistrations !== undefined) {
|
|
321
|
-
return false
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const registrationId = `${PLUGIN_NAME}-${Date.now()}-${Math.random()}`
|
|
325
|
-
const registrationPromise = new Promise<void>((resolve) => {
|
|
326
|
-
if (!(api._pendingExternalRegistrations instanceof Map)) {
|
|
327
|
-
api._pendingExternalRegistrations = new Map()
|
|
328
|
-
}
|
|
329
|
-
api._pendingExternalRegistrations.set(registrationId, resolve)
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
api.emit('publishExternalMatterAccessories', accessories, registrationId)
|
|
333
|
-
await registrationPromise
|
|
334
|
-
return true
|
|
335
281
|
}
|
|
336
282
|
|
|
337
283
|
/**
|
|
@@ -366,7 +312,6 @@ export class SwitchBotMatterPlatform {
|
|
|
366
312
|
id: d.deviceId ?? d.id,
|
|
367
313
|
type: d.configDeviceType ?? d.type,
|
|
368
314
|
name: d.configDeviceName ?? d.name,
|
|
369
|
-
externalPublishProtocol: resolveExternalPublishProtocol(d),
|
|
370
315
|
})))
|
|
371
316
|
}
|
|
372
317
|
|
package/src/device-types.ts
CHANGED
|
@@ -191,6 +191,17 @@ export const DEVICE_TYPE_NORMALIZATION_MAP: Record<string, string> = {
|
|
|
191
191
|
|
|
192
192
|
// Additional normalization for new/unknown types from logs
|
|
193
193
|
'woplugus': 'Plug Mini (US)',
|
|
194
|
+
|
|
195
|
+
// Product-specific identities reported by node-switchbot 5 (pySwitchbot 3.0.0 parity)
|
|
196
|
+
'woplugjp': 'Plug Mini (JP)',
|
|
197
|
+
'wocurtain3': 'Curtain3',
|
|
198
|
+
'wometerplus': 'Meter Plus',
|
|
199
|
+
'wothpc': 'Meter Pro (CO2)',
|
|
200
|
+
'woceilingpro': 'Ceiling Light Pro',
|
|
201
|
+
'wolinkmini': 'Hub Mini',
|
|
202
|
+
'woiosensorth': 'WoIOSensor',
|
|
203
|
+
'outdoor meter': 'WoIOSensor',
|
|
204
|
+
'indoor/outdoor thermo-hygrometer': 'WoIOSensor',
|
|
194
205
|
// Removed duplicate keys below, only last occurrence kept
|
|
195
206
|
// 'plug mini us': 'plug mini (us)', // duplicate, removed
|
|
196
207
|
// 'plug us': 'plug mini (us)', // duplicate, removed
|