@switchbot/homebridge-switchbot 5.0.0-beta.111 → 5.0.0-beta.113
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/MIGRATION.md +10 -0
- package/README.md +43 -0
- package/TODO.md +263 -0
- package/config.schema.json +27 -10
- package/dist/device-types.d.ts.map +1 -1
- package/dist/device-types.js +93 -63
- package/dist/device-types.js.map +1 -1
- package/dist/deviceFactory.d.ts.map +1 -1
- package/dist/deviceFactory.js +55 -33
- package/dist/deviceFactory.js.map +1 -1
- package/dist/devices/genericDevice.d.ts +12 -1
- package/dist/devices/genericDevice.d.ts.map +1 -1
- package/dist/devices/genericDevice.js +121 -1
- package/dist/devices/genericDevice.js.map +1 -1
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +51 -0
- package/dist/errors.js.map +1 -0
- package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -1
- package/dist/homebridge-ui/endpoints/devices.js +80 -1
- package/dist/homebridge-ui/endpoints/devices.js.map +1 -1
- package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -1
- package/dist/homebridge-ui/endpoints/discovery.js +73 -41
- package/dist/homebridge-ui/endpoints/discovery.js.map +1 -1
- package/dist/homebridge-ui/public/css/styles.css +209 -16
- package/dist/homebridge-ui/public/index.html +87 -56
- package/dist/homebridge-ui/public/js/api.d.ts +24 -1
- package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/api.js +33 -2
- package/dist/homebridge-ui/public/js/api.js.map +1 -1
- package/dist/homebridge-ui/public/js/api.ts +52 -3
- package/dist/homebridge-ui/public/js/app.js +1604 -143
- package/dist/homebridge-ui/public/js/app.js.map +3 -3
- package/dist/homebridge-ui/public/js/app.ts +2 -1
- package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/devices.js +2 -0
- package/dist/homebridge-ui/public/js/devices.js.map +1 -1
- package/dist/homebridge-ui/public/js/devices.ts +13 -1
- package/dist/homebridge-ui/public/js/discovery.d.ts +1 -0
- package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/discovery.js +1244 -124
- package/dist/homebridge-ui/public/js/discovery.js.map +1 -1
- package/dist/homebridge-ui/public/js/discovery.ts +1344 -142
- package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/modals.js +42 -0
- package/dist/homebridge-ui/public/js/modals.js.map +1 -1
- package/dist/homebridge-ui/public/js/modals.ts +50 -1
- package/dist/homebridge-ui/public/js/render.d.ts +3 -0
- package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/render.js +389 -36
- package/dist/homebridge-ui/public/js/render.js.map +1 -1
- package/dist/homebridge-ui/public/js/render.ts +421 -38
- package/dist/platform.d.ts +1 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +3 -4
- package/dist/platform.js.map +1 -1
- package/dist/switchbotClient.d.ts.map +1 -1
- package/dist/switchbotClient.js +51 -5
- package/dist/switchbotClient.js.map +1 -1
- package/docs/assets/highlight.css +9 -2
- package/docs/index.html +28 -2
- package/docs/variables/default.html +1 -1
- package/package.json +9 -8
- package/src/device-types.ts +95 -64
- package/src/deviceFactory.ts +55 -33
- package/src/devices/genericDevice.ts +113 -1
- package/src/errors.ts +43 -0
- package/src/homebridge-ui/endpoints/devices.ts +82 -1
- package/src/homebridge-ui/endpoints/discovery.ts +78 -44
- package/src/homebridge-ui/public/css/styles.css +209 -16
- package/src/homebridge-ui/public/index.html +87 -56
- package/src/homebridge-ui/public/js/api.ts +52 -3
- package/src/homebridge-ui/public/js/app.ts +2 -1
- package/src/homebridge-ui/public/js/devices.ts +13 -1
- package/src/homebridge-ui/public/js/discovery.ts +1344 -142
- package/src/homebridge-ui/public/js/modals.ts +50 -1
- package/src/homebridge-ui/public/js/render.ts +421 -38
- package/src/platform.ts +3 -4
- package/src/switchbotClient.ts +44 -9
- package/test/e2e/run-e2e.spec.ts +12 -10
- package/test/helpers/matter-harness.ts +2 -2
- package/test/matter-integration.spec.ts +21 -0
- package/todo/COMPLETED.md +313 -0
- package/todo/TODO.md +80 -66
- package/switchbot-homebridge-switchbot-5.0.0.tgz +0 -0
|
@@ -9,65 +9,101 @@
|
|
|
9
9
|
<body>
|
|
10
10
|
<h1>🤖 SwitchBot Configuration</h1>
|
|
11
11
|
|
|
12
|
-
<div class="card">
|
|
12
|
+
<div class="card compact">
|
|
13
13
|
<h2>API Credentials</h2>
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
type="password"
|
|
23
|
-
id="
|
|
24
|
-
|
|
25
|
-
/>
|
|
26
|
-
<div class="status" id="tokenStatus"></div>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<div class="form-group">
|
|
30
|
-
<label for="secret">API Secret:</label>
|
|
31
|
-
<input
|
|
32
|
-
type="password"
|
|
33
|
-
id="secret"
|
|
34
|
-
placeholder="Enter your SwitchBot API secret"
|
|
35
|
-
/>
|
|
36
|
-
<div class="status" id="secretStatus"></div>
|
|
14
|
+
<div class="credentials-row">
|
|
15
|
+
<div class="form-group">
|
|
16
|
+
<label for="token">Token:</label>
|
|
17
|
+
<input type="password" id="token" placeholder="Enter token" />
|
|
18
|
+
<div class="status" id="tokenStatus"></div>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="form-group">
|
|
21
|
+
<label for="secret">Secret:</label>
|
|
22
|
+
<input type="password" id="secret" placeholder="Enter secret" />
|
|
23
|
+
<div class="status" id="secretStatus"></div>
|
|
24
|
+
</div>
|
|
37
25
|
</div>
|
|
38
|
-
|
|
39
|
-
<button id="saveBtn" onclick="saveCredentials()">Save Credentials</button>
|
|
26
|
+
<button id="saveBtn" onclick="saveCredentials()">Save</button>
|
|
40
27
|
<div id="saveStatus"></div>
|
|
41
28
|
</div>
|
|
42
29
|
|
|
43
|
-
<div class="card">
|
|
30
|
+
<div class="card compact">
|
|
44
31
|
<h2>Discover Devices</h2>
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
32
|
+
<div style="margin-bottom: 6px">
|
|
33
|
+
<label style="display: flex; align-items: center; gap: 4px; cursor: pointer; font-size: 11px">
|
|
34
|
+
<input type="checkbox" id="autoAddAllCheckbox" style="width: auto; margin: 0" />
|
|
35
|
+
<span>Auto-add all</span>
|
|
36
|
+
<span
|
|
37
|
+
title="When enabled, Discover immediately adds all found devices to your config. Existing devices are skipped automatically."
|
|
38
|
+
style="font-size: 11px; opacity: 0.7"
|
|
39
|
+
>ⓘ</span>
|
|
40
|
+
</label>
|
|
41
|
+
<div style="margin-top: 2px; margin-left: 22px; font-size: 10px; opacity: 0.75; line-height: 1.25">
|
|
42
|
+
Adds all discovered devices automatically; already configured devices are skipped.
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="discovery-settings-row" style="margin-bottom: 2px">
|
|
46
|
+
<label id="bleScanSetting" style="display: inline-flex; align-items: center; gap: 4px; font-size: 11px">
|
|
47
|
+
<span>Scan:</span>
|
|
48
|
+
<select id="bleScanDurationSelect" style="font-size: 11px; padding: 2px 6px">
|
|
49
|
+
<option value="3">3s</option>
|
|
50
|
+
<option value="5" selected>5s</option>
|
|
51
|
+
<option value="10">10s</option>
|
|
52
|
+
<option value="15">15s</option>
|
|
53
|
+
</select>
|
|
54
|
+
</label>
|
|
55
|
+
<label id="bleTimeoutSetting" style="display: inline-flex; align-items: center; gap: 4px; font-size: 11px">
|
|
56
|
+
<span>Timeout:</span>
|
|
53
57
|
<input
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
id="bleTimeoutInput"
|
|
59
|
+
type="number"
|
|
60
|
+
min="3"
|
|
61
|
+
max="30"
|
|
62
|
+
step="1"
|
|
63
|
+
value="8"
|
|
64
|
+
style="width: 56px; font-size: 11px; padding: 2px 4px"
|
|
57
65
|
/>
|
|
58
|
-
<span>
|
|
66
|
+
<span>s</span>
|
|
67
|
+
</label>
|
|
68
|
+
<label style="display: flex; align-items: center; gap: 4px; cursor: pointer; font-size: 11px">
|
|
69
|
+
<input type="checkbox" id="disableBleScanCheckbox" style="width: auto; margin: 0" />
|
|
70
|
+
<span>Disable BLE scan</span>
|
|
71
|
+
</label>
|
|
72
|
+
</div>
|
|
73
|
+
<div id="bluetoothStatus" style="margin-bottom: 4px; font-size: 10px; opacity: 0.8">Bluetooth: checking...</div>
|
|
74
|
+
<div class="discovery-settings-row" style="margin-bottom: 8px">
|
|
75
|
+
<div id="lastScannedStatus" style="font-size: 10px; opacity: 0.8; flex: 1 1 auto">Last scanned: never</div>
|
|
76
|
+
<label style="font-size: 11px">
|
|
77
|
+
Auto-refresh:
|
|
78
|
+
<select id="autoRefreshIntervalSelect" style="margin-left: 4px; font-size: 11px; padding: 2px 6px">
|
|
79
|
+
<option value="0" selected>Off</option>
|
|
80
|
+
<option value="30">30s</option>
|
|
81
|
+
<option value="60">1m</option>
|
|
82
|
+
<option value="300">5m</option>
|
|
83
|
+
</select>
|
|
59
84
|
</label>
|
|
85
|
+
<button id="refreshDiscoverBtn" class="secondary" style="padding: 4px 9px; font-size: 11px">Refresh</button>
|
|
60
86
|
</div>
|
|
87
|
+
<button id="discoverBtn" onclick="discoverDevices()" style="margin-bottom: 8px">
|
|
88
|
+
🔍 Discover
|
|
89
|
+
</button>
|
|
61
90
|
<button
|
|
62
|
-
id="
|
|
63
|
-
|
|
64
|
-
|
|
91
|
+
id="cancelDiscoverBtn"
|
|
92
|
+
style="margin-left: 6px; margin-bottom: 8px; display: none; padding: 6px 10px; font-size: 11px"
|
|
93
|
+
class="secondary"
|
|
65
94
|
>
|
|
66
|
-
|
|
95
|
+
Cancel
|
|
67
96
|
</button>
|
|
68
|
-
<div id="discoverStatus" style="margin-bottom:
|
|
97
|
+
<div id="discoverStatus" style="margin-bottom: 6px; font-size: 11px"></div>
|
|
98
|
+
<div id="discoverDevicesFound" style="margin-bottom: 6px; font-size: 11px; display: none"></div>
|
|
99
|
+
<div id="discoverPhaseProgress" class="discover-phase-progress" style="display: none">
|
|
100
|
+
<div class="discover-phase-track">
|
|
101
|
+
<div id="discoverPhaseFill" class="discover-phase-fill"></div>
|
|
102
|
+
</div>
|
|
103
|
+
<div id="discoverPhaseLabel" class="discover-phase-label"></div>
|
|
104
|
+
</div>
|
|
69
105
|
<div id="discoveredList" style="display: none">
|
|
70
|
-
<h3 style="margin-top: 0">Available Devices</h3>
|
|
106
|
+
<h3 style="margin-top: 0; font-size: 12px; margin-bottom: 6px; font-weight: 600">Available Devices</h3>
|
|
71
107
|
<ul
|
|
72
108
|
id="discoveredDevices"
|
|
73
109
|
style="max-height: 300px; overflow-y: auto"
|
|
@@ -75,22 +111,17 @@
|
|
|
75
111
|
</div>
|
|
76
112
|
</div>
|
|
77
113
|
|
|
78
|
-
<div class="card">
|
|
114
|
+
<div class="card compact">
|
|
79
115
|
<h2>Configured Devices</h2>
|
|
80
|
-
<p>
|
|
81
|
-
This page lists devices found in your Homebridge config for the
|
|
82
|
-
SwitchBot platform. Use the copy button to insert device IDs into the
|
|
83
|
-
plugin configuration. Connection preference (BLE/OpenAPI) is shown when
|
|
84
|
-
available.
|
|
85
|
-
</p>
|
|
86
116
|
<div id="status">Loading…</div>
|
|
87
117
|
<ul id="devices"></ul>
|
|
118
|
+
<!-- BLE encryption key fields will be rendered dynamically per device in the UI (see app.ts/devices.js) -->
|
|
88
119
|
<div
|
|
89
120
|
id="removeAllContainer"
|
|
90
121
|
style="
|
|
91
122
|
display: none;
|
|
92
|
-
margin-top:
|
|
93
|
-
padding-top:
|
|
123
|
+
margin-top: 12px;
|
|
124
|
+
padding-top: 10px;
|
|
94
125
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
|
95
126
|
"
|
|
96
127
|
>
|
|
@@ -99,8 +130,8 @@
|
|
|
99
130
|
style="
|
|
100
131
|
background: #dc2626;
|
|
101
132
|
width: 100%;
|
|
102
|
-
padding:
|
|
103
|
-
font-size:
|
|
133
|
+
padding: 7px 12px;
|
|
134
|
+
font-size: 12px;
|
|
104
135
|
font-weight: 500;
|
|
105
136
|
"
|
|
106
137
|
>
|
|
@@ -100,9 +100,18 @@ export async function fetchDevices(): Promise<any[]> {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
export
|
|
103
|
+
export type DiscoverRequestOptions = {
|
|
104
|
+
bleEnabled?: boolean
|
|
105
|
+
bleScanDurationSeconds?: number
|
|
106
|
+
bleTimeoutSeconds?: number
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function discoverDevices(
|
|
110
|
+
mode: 'all' | 'ble' | 'openapi' = 'all',
|
|
111
|
+
options?: DiscoverRequestOptions,
|
|
112
|
+
): Promise<any[]> {
|
|
104
113
|
try {
|
|
105
|
-
const resp = await homebridge.request('/discover', {})
|
|
114
|
+
const resp = await homebridge.request('/discover', { mode, ...options })
|
|
106
115
|
uiLog.info('Discover response:', resp)
|
|
107
116
|
|
|
108
117
|
if (!resp || resp.success === false) {
|
|
@@ -115,17 +124,57 @@ export async function discoverDevices(): Promise<any[]> {
|
|
|
115
124
|
}
|
|
116
125
|
}
|
|
117
126
|
|
|
127
|
+
export async function fetchBluetoothStatus(): Promise<{ available: boolean, message: string }> {
|
|
128
|
+
try {
|
|
129
|
+
const resp = await homebridge.request('/ble-status', {})
|
|
130
|
+
if (!resp || resp.success === false) {
|
|
131
|
+
return { available: false, message: 'Bluetooth status unavailable' }
|
|
132
|
+
}
|
|
133
|
+
return resp.data || { available: false, message: 'Bluetooth status unavailable' }
|
|
134
|
+
} catch (_e) {
|
|
135
|
+
return { available: false, message: 'Bluetooth status unavailable' }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function testDeviceConnection(payload: {
|
|
140
|
+
deviceId: string
|
|
141
|
+
connectionType?: string
|
|
142
|
+
address?: string
|
|
143
|
+
}): Promise<{
|
|
144
|
+
success: boolean
|
|
145
|
+
deviceId: string
|
|
146
|
+
method: string
|
|
147
|
+
latencyMs: number
|
|
148
|
+
message: string
|
|
149
|
+
state?: Record<string, any>
|
|
150
|
+
}> {
|
|
151
|
+
const resp = await homebridge.request('/test-connection', payload)
|
|
152
|
+
if (!resp || resp.success === false) {
|
|
153
|
+
throw new Error(resp?.data?.message || 'Connection test failed')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return resp.data || {
|
|
157
|
+
success: false,
|
|
158
|
+
deviceId: payload.deviceId,
|
|
159
|
+
method: 'Auto',
|
|
160
|
+
latencyMs: 0,
|
|
161
|
+
message: 'Connection test failed',
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
118
165
|
export async function addDevice(
|
|
119
166
|
deviceId: string,
|
|
120
167
|
name: string,
|
|
121
168
|
type: string,
|
|
122
|
-
options?: { address?: string, model?: string, rssi?: number },
|
|
169
|
+
options?: { address?: string, model?: string, rssi?: number, encryptionKey?: string, keyId?: string },
|
|
123
170
|
): Promise<any> {
|
|
124
171
|
try {
|
|
125
172
|
const payload: any = { deviceId, name, type }
|
|
126
173
|
if (options?.address) { payload.address = options.address }
|
|
127
174
|
if (options?.model) { payload.model = options.model }
|
|
128
175
|
if (options?.rssi !== undefined && options?.rssi !== null && options?.rssi !== 0) { payload.rssi = options.rssi }
|
|
176
|
+
if (options?.encryptionKey) { payload.encryptionKey = options.encryptionKey }
|
|
177
|
+
if (options?.keyId) { payload.keyId = options.keyId }
|
|
129
178
|
|
|
130
179
|
uiLog.info('Adding device to config:', payload)
|
|
131
180
|
const resp = await homebridge.request('/add-device', payload)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadCredentialStatus, saveCredentials } from './credentials.js'
|
|
2
2
|
import { loadConfiguredDevices, initRemoveAllButton } from './devices.js'
|
|
3
3
|
// Wire up global event handlers
|
|
4
|
-
import { discoverDevices } from './discovery.js';
|
|
4
|
+
import { discoverDevices, initializeDiscoverySettings } from './discovery.js';
|
|
5
5
|
|
|
6
6
|
(window as any).loadCredentialStatus = loadCredentialStatus
|
|
7
7
|
;(window as any).saveCredentials = saveCredentials
|
|
@@ -10,6 +10,7 @@ import { discoverDevices } from './discovery.js';
|
|
|
10
10
|
// Initialize on page load
|
|
11
11
|
async function init(): Promise<void> {
|
|
12
12
|
await loadCredentialStatus()
|
|
13
|
+
await initializeDiscoverySettings()
|
|
13
14
|
const list = await loadConfiguredDevices()
|
|
14
15
|
await initRemoveAllButton()
|
|
15
16
|
}
|
|
@@ -1,15 +1,25 @@
|
|
|
1
|
+
|
|
1
2
|
import { addDevice, fetchDevices, syncParentPluginConfigFromDisk } from './api.js'
|
|
2
3
|
import { uiLog } from './logger.js'
|
|
3
4
|
import { hideBusyUi, showBusyUi } from './modal.js'
|
|
4
5
|
import { renderDeviceList } from './render.js'
|
|
5
6
|
import { toastError, toastInfo, toastSuccess, toastWarning } from './toast.js'
|
|
6
7
|
|
|
8
|
+
type ImportDiscoveredDeviceResult = {
|
|
9
|
+
configDeviceName: string
|
|
10
|
+
configDeviceType: string
|
|
11
|
+
address?: string
|
|
12
|
+
encryptionKey?: string
|
|
13
|
+
keyId?: string
|
|
14
|
+
} | null
|
|
15
|
+
|
|
7
16
|
export async function addDeviceToConfig(device: any, options: { refresh?: boolean, showStatus?: boolean } = {}): Promise<{ added: boolean }> {
|
|
8
17
|
const { refresh = true, showStatus = true } = options
|
|
9
18
|
|
|
10
19
|
try {
|
|
11
20
|
const { importDiscoveredDevice } = await import('./modals.js')
|
|
12
|
-
|
|
21
|
+
|
|
22
|
+
const importValues: ImportDiscoveredDeviceResult = await importDiscoveredDevice(device)
|
|
13
23
|
if (!importValues) {
|
|
14
24
|
return { added: false }
|
|
15
25
|
}
|
|
@@ -20,6 +30,8 @@ export async function addDeviceToConfig(device: any, options: { refresh?: boolea
|
|
|
20
30
|
address: importValues.address,
|
|
21
31
|
model: device.model,
|
|
22
32
|
rssi: device.rssi,
|
|
33
|
+
encryptionKey: importValues.encryptionKey,
|
|
34
|
+
keyId: importValues.keyId,
|
|
23
35
|
})
|
|
24
36
|
uiLog.info('Add device response:', resp)
|
|
25
37
|
|