@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.
Files changed (85) hide show
  1. package/MIGRATION.md +10 -0
  2. package/README.md +43 -0
  3. package/TODO.md +263 -0
  4. package/config.schema.json +27 -10
  5. package/dist/device-types.d.ts.map +1 -1
  6. package/dist/device-types.js +93 -63
  7. package/dist/device-types.js.map +1 -1
  8. package/dist/deviceFactory.d.ts.map +1 -1
  9. package/dist/deviceFactory.js +55 -33
  10. package/dist/deviceFactory.js.map +1 -1
  11. package/dist/devices/genericDevice.d.ts +12 -1
  12. package/dist/devices/genericDevice.d.ts.map +1 -1
  13. package/dist/devices/genericDevice.js +121 -1
  14. package/dist/devices/genericDevice.js.map +1 -1
  15. package/dist/errors.d.ts +5 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +51 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -1
  20. package/dist/homebridge-ui/endpoints/devices.js +80 -1
  21. package/dist/homebridge-ui/endpoints/devices.js.map +1 -1
  22. package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -1
  23. package/dist/homebridge-ui/endpoints/discovery.js +73 -41
  24. package/dist/homebridge-ui/endpoints/discovery.js.map +1 -1
  25. package/dist/homebridge-ui/public/css/styles.css +209 -16
  26. package/dist/homebridge-ui/public/index.html +87 -56
  27. package/dist/homebridge-ui/public/js/api.d.ts +24 -1
  28. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  29. package/dist/homebridge-ui/public/js/api.js +33 -2
  30. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  31. package/dist/homebridge-ui/public/js/api.ts +52 -3
  32. package/dist/homebridge-ui/public/js/app.js +1604 -143
  33. package/dist/homebridge-ui/public/js/app.js.map +3 -3
  34. package/dist/homebridge-ui/public/js/app.ts +2 -1
  35. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
  36. package/dist/homebridge-ui/public/js/devices.js +2 -0
  37. package/dist/homebridge-ui/public/js/devices.js.map +1 -1
  38. package/dist/homebridge-ui/public/js/devices.ts +13 -1
  39. package/dist/homebridge-ui/public/js/discovery.d.ts +1 -0
  40. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -1
  41. package/dist/homebridge-ui/public/js/discovery.js +1244 -124
  42. package/dist/homebridge-ui/public/js/discovery.js.map +1 -1
  43. package/dist/homebridge-ui/public/js/discovery.ts +1344 -142
  44. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
  45. package/dist/homebridge-ui/public/js/modals.js +42 -0
  46. package/dist/homebridge-ui/public/js/modals.js.map +1 -1
  47. package/dist/homebridge-ui/public/js/modals.ts +50 -1
  48. package/dist/homebridge-ui/public/js/render.d.ts +3 -0
  49. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
  50. package/dist/homebridge-ui/public/js/render.js +389 -36
  51. package/dist/homebridge-ui/public/js/render.js.map +1 -1
  52. package/dist/homebridge-ui/public/js/render.ts +421 -38
  53. package/dist/platform.d.ts +1 -0
  54. package/dist/platform.d.ts.map +1 -1
  55. package/dist/platform.js +3 -4
  56. package/dist/platform.js.map +1 -1
  57. package/dist/switchbotClient.d.ts.map +1 -1
  58. package/dist/switchbotClient.js +51 -5
  59. package/dist/switchbotClient.js.map +1 -1
  60. package/docs/assets/highlight.css +9 -2
  61. package/docs/index.html +28 -2
  62. package/docs/variables/default.html +1 -1
  63. package/package.json +9 -8
  64. package/src/device-types.ts +95 -64
  65. package/src/deviceFactory.ts +55 -33
  66. package/src/devices/genericDevice.ts +113 -1
  67. package/src/errors.ts +43 -0
  68. package/src/homebridge-ui/endpoints/devices.ts +82 -1
  69. package/src/homebridge-ui/endpoints/discovery.ts +78 -44
  70. package/src/homebridge-ui/public/css/styles.css +209 -16
  71. package/src/homebridge-ui/public/index.html +87 -56
  72. package/src/homebridge-ui/public/js/api.ts +52 -3
  73. package/src/homebridge-ui/public/js/app.ts +2 -1
  74. package/src/homebridge-ui/public/js/devices.ts +13 -1
  75. package/src/homebridge-ui/public/js/discovery.ts +1344 -142
  76. package/src/homebridge-ui/public/js/modals.ts +50 -1
  77. package/src/homebridge-ui/public/js/render.ts +421 -38
  78. package/src/platform.ts +3 -4
  79. package/src/switchbotClient.ts +44 -9
  80. package/test/e2e/run-e2e.spec.ts +12 -10
  81. package/test/helpers/matter-harness.ts +2 -2
  82. package/test/matter-integration.spec.ts +21 -0
  83. package/todo/COMPLETED.md +313 -0
  84. package/todo/TODO.md +80 -66
  85. 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
- <p>
15
- Configure your SwitchBot API token and secret to enable device discovery
16
- and control.
17
- </p>
18
-
19
- <div class="form-group">
20
- <label for="token">API Token:</label>
21
- <input
22
- type="password"
23
- id="token"
24
- placeholder="Enter your SwitchBot API token"
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
- <p>
46
- Click "Discover" to find all devices available in your SwitchBot account
47
- and add them to your configuration.
48
- </p>
49
- <div style="margin-bottom: 16px">
50
- <label
51
- style="display: flex; align-items: center; gap: 8px; cursor: pointer"
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
- type="checkbox"
55
- id="autoAddAllCheckbox"
56
- style="width: auto; margin: 0"
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>Auto-add all discovered devices</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="discoverBtn"
63
- onclick="discoverDevices()"
64
- style="margin-bottom: 16px"
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
- 🔍 Discover Devices
95
+ Cancel
67
96
  </button>
68
- <div id="discoverStatus" style="margin-bottom: 16px"></div>
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: 16px;
93
- padding-top: 16px;
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: 10px 16px;
103
- font-size: 14px;
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 async function discoverDevices(): Promise<any[]> {
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
- const importValues = await importDiscoveredDevice(device)
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