@switchbot/homebridge-switchbot 5.0.0-beta.134 → 5.0.0-beta.135
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/config.schema.json +0 -11
- package/dist/deviceFactory.d.ts +3 -1
- package/dist/deviceFactory.d.ts.map +1 -1
- package/dist/deviceFactory.js +3 -18
- package/dist/deviceFactory.js.map +1 -1
- package/dist/devices/genericDevice.d.ts +1 -1
- package/dist/devices/genericDevice.d.ts.map +1 -1
- package/dist/devices/genericDevice.js +79 -159
- package/dist/devices/genericDevice.js.map +1 -1
- package/dist/homebridge-ui/public/index.html +107 -124
- package/dist/homebridge-ui/public/js/api.d.ts +1 -4
- package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/api.js +1 -7
- package/dist/homebridge-ui/public/js/api.js.map +1 -1
- package/dist/homebridge-ui/public/js/api.ts +1 -8
- package/dist/homebridge-ui/public/js/app.js +201 -90
- package/dist/homebridge-ui/public/js/app.js.map +2 -2
- package/dist/homebridge-ui/public/js/app.ts +5 -6
- package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/credentials.js +1 -1
- package/dist/homebridge-ui/public/js/credentials.js.map +1 -1
- package/dist/homebridge-ui/public/js/credentials.ts +7 -3
- package/dist/homebridge-ui/public/js/devices-delete.js +1 -1
- package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -1
- package/dist/homebridge-ui/public/js/devices-delete.ts +1 -1
- package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/devices.js +0 -1
- package/dist/homebridge-ui/public/js/devices.js.map +1 -1
- package/dist/homebridge-ui/public/js/devices.ts +0 -2
- package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/discovery.js +6 -3
- package/dist/homebridge-ui/public/js/discovery.js.map +1 -1
- package/dist/homebridge-ui/public/js/discovery.ts +9 -3
- package/dist/homebridge-ui/public/js/logger.d.ts +1 -0
- package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/logger.js +8 -3
- package/dist/homebridge-ui/public/js/logger.js.map +1 -1
- package/dist/homebridge-ui/public/js/logger.ts +12 -6
- package/dist/homebridge-ui/public/js/modals.d.ts +4 -0
- package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/modals.js +152 -59
- package/dist/homebridge-ui/public/js/modals.js.map +1 -1
- package/dist/homebridge-ui/public/js/modals.ts +187 -60
- 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 +63 -24
- package/dist/homebridge-ui/public/js/render.js.map +1 -1
- package/dist/homebridge-ui/public/js/render.ts +102 -51
- package/dist/homebridge-ui/utils/logger.js +1 -1
- package/dist/homebridge-ui/utils/logger.js.map +1 -1
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +38 -30
- package/dist/platform.js.map +1 -1
- package/dist/switchbotClient.d.ts +5 -5
- package/dist/switchbotClient.d.ts.map +1 -1
- package/dist/switchbotClient.js +21 -12
- package/dist/switchbotClient.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/scripts/build-ui.js +4 -3
- package/scripts/generate-matter-maps.js +34 -17
- package/scripts/sync-device-types.mjs +4 -1
- package/src/deviceFactory.ts +3 -18
- package/src/devices/genericDevice.ts +77 -164
- package/src/homebridge-ui/public/index.html +107 -124
- package/src/homebridge-ui/public/js/api.ts +1 -8
- package/src/homebridge-ui/public/js/app.ts +5 -6
- package/src/homebridge-ui/public/js/credentials.ts +7 -3
- package/src/homebridge-ui/public/js/devices-delete.ts +1 -1
- package/src/homebridge-ui/public/js/devices.ts +0 -2
- package/src/homebridge-ui/public/js/discovery.ts +9 -3
- package/src/homebridge-ui/public/js/logger.ts +12 -6
- package/src/homebridge-ui/public/js/modals.ts +187 -60
- package/src/homebridge-ui/public/js/render.ts +102 -51
- package/src/homebridge-ui/utils/logger.ts +1 -1
- package/src/platform.ts +38 -29
- package/src/switchbotClient.ts +42 -27
- package/test/accessory-restore.spec.ts +9 -8
- package/test/device-mapping.spec.ts +4 -3
- package/test/deviceFactory.spec.ts +4 -3
- package/test/e2e/run-e2e.spec.ts +6 -10
- package/test/fan-swing.spec.ts +7 -6
- package/test/helpers/matter-harness.ts +18 -7
- package/test/lock-users.spec.ts +5 -5
- package/test/matter-childbridge.spec.ts +10 -7
- package/test/matter-descriptors.spec.ts +8 -8
- package/test/matter-device-state.spec.ts +52 -36
- package/test/matter-integration.spec.ts +53 -6
- package/test/platform.integration.spec.ts +7 -5
- package/test/switchbot-client-debounce.spec.ts +36 -40
- package/test/switchbot-client-openapi.spec.ts +22 -15
- package/test/switchbotClient.spec.ts +4 -3
- package/test/utils.spec.ts +2 -1
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { DEVICE_TYPES } from './constants.js'
|
|
2
2
|
import { uiLog } from './logger.js'
|
|
3
3
|
|
|
4
|
-
export async function importDiscoveredDevice(device: any): Promise<{
|
|
4
|
+
export async function importDiscoveredDevice(device: any): Promise<{
|
|
5
|
+
configDeviceName: string
|
|
6
|
+
configDeviceType: string
|
|
7
|
+
address?: string
|
|
8
|
+
connectionPreference?: string
|
|
9
|
+
room?: string
|
|
10
|
+
encryptionKey?: string
|
|
11
|
+
keyId?: string
|
|
12
|
+
} | null> {
|
|
5
13
|
return new Promise((resolve) => {
|
|
6
14
|
const div = document.createElement('div')
|
|
7
15
|
div.style.position = 'fixed'
|
|
@@ -91,6 +99,51 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
91
99
|
typeSelect.appendChild(optgroup)
|
|
92
100
|
})
|
|
93
101
|
|
|
102
|
+
// --- Connection Preference ---
|
|
103
|
+
const connectionPrefLabel = document.createElement('label')
|
|
104
|
+
connectionPrefLabel.textContent = 'Connection Preference'
|
|
105
|
+
connectionPrefLabel.style.display = 'block'
|
|
106
|
+
connectionPrefLabel.style.marginBottom = '6px'
|
|
107
|
+
connectionPrefLabel.style.fontWeight = '500'
|
|
108
|
+
connectionPrefLabel.style.fontSize = '12px'
|
|
109
|
+
connectionPrefLabel.style.color = '#6b7280'
|
|
110
|
+
|
|
111
|
+
const connectionPrefSelect = document.createElement('select')
|
|
112
|
+
connectionPrefSelect.style.width = '100%'
|
|
113
|
+
connectionPrefSelect.style.marginBottom = '12px'
|
|
114
|
+
connectionPrefSelect.style.padding = '8px 10px'
|
|
115
|
+
connectionPrefSelect.style.borderRadius = '6px'
|
|
116
|
+
connectionPrefSelect.style.fontSize = '14px'
|
|
117
|
+
connectionPrefSelect.style.boxSizing = 'border-box';
|
|
118
|
+
['auto', 'ble', 'openapi'].forEach((val) => {
|
|
119
|
+
const opt = document.createElement('option')
|
|
120
|
+
opt.value = val
|
|
121
|
+
opt.text = val.charAt(0).toUpperCase() + val.slice(1)
|
|
122
|
+
opt.selected = (device.connectionPreference || 'auto') === val
|
|
123
|
+
connectionPrefSelect.appendChild(opt)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// --- Room ---
|
|
127
|
+
const roomLabel = document.createElement('label')
|
|
128
|
+
roomLabel.textContent = 'Room'
|
|
129
|
+
roomLabel.style.display = 'block'
|
|
130
|
+
roomLabel.style.marginBottom = '6px'
|
|
131
|
+
roomLabel.style.fontWeight = '500'
|
|
132
|
+
roomLabel.style.fontSize = '12px'
|
|
133
|
+
roomLabel.style.color = '#6b7280'
|
|
134
|
+
|
|
135
|
+
const roomInput = document.createElement('input')
|
|
136
|
+
roomInput.type = 'text'
|
|
137
|
+
roomInput.value = device.room || ''
|
|
138
|
+
roomInput.placeholder = 'Optional room/location metadata'
|
|
139
|
+
roomInput.style.width = '100%'
|
|
140
|
+
roomInput.style.marginBottom = '12px'
|
|
141
|
+
roomInput.style.padding = '8px 10px'
|
|
142
|
+
roomInput.style.borderRadius = '6px'
|
|
143
|
+
roomInput.style.fontSize = '14px'
|
|
144
|
+
roomInput.style.boxSizing = 'border-box'
|
|
145
|
+
|
|
146
|
+
// --- BLE MAC Address ---
|
|
94
147
|
const macLabel = document.createElement('label')
|
|
95
148
|
macLabel.textContent = 'BLE MAC Address (optional)'
|
|
96
149
|
macLabel.style.display = 'block'
|
|
@@ -110,7 +163,7 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
110
163
|
macInput.style.fontSize = '14px'
|
|
111
164
|
macInput.style.boxSizing = 'border-box'
|
|
112
165
|
|
|
113
|
-
//
|
|
166
|
+
// --- Encryption Key ---
|
|
114
167
|
const encryptionKeyLabel = document.createElement('label')
|
|
115
168
|
encryptionKeyLabel.textContent = 'BLE Encryption Key (optional)'
|
|
116
169
|
encryptionKeyLabel.style.display = 'block'
|
|
@@ -130,7 +183,7 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
130
183
|
encryptionKeyInput.style.fontSize = '14px'
|
|
131
184
|
encryptionKeyInput.style.boxSizing = 'border-box'
|
|
132
185
|
|
|
133
|
-
//
|
|
186
|
+
// --- Key ID ---
|
|
134
187
|
const keyIdLabel = document.createElement('label')
|
|
135
188
|
keyIdLabel.textContent = 'BLE Key ID (optional)'
|
|
136
189
|
keyIdLabel.style.display = 'block'
|
|
@@ -171,7 +224,15 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
171
224
|
importBtn.style.padding = '8px 20px'
|
|
172
225
|
importBtn.style.fontSize = '13px'
|
|
173
226
|
|
|
174
|
-
const cleanup = (result: {
|
|
227
|
+
const cleanup = (result: {
|
|
228
|
+
configDeviceName: string
|
|
229
|
+
configDeviceType: string
|
|
230
|
+
address?: string
|
|
231
|
+
connectionPreference?: string
|
|
232
|
+
room?: string
|
|
233
|
+
encryptionKey?: string
|
|
234
|
+
keyId?: string
|
|
235
|
+
} | null) => {
|
|
175
236
|
div.remove()
|
|
176
237
|
resolve(result)
|
|
177
238
|
}
|
|
@@ -181,6 +242,8 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
181
242
|
configDeviceName: nameInput.value || device.name || device.id,
|
|
182
243
|
configDeviceType: typeSelect.value || device.type,
|
|
183
244
|
address: macInput.value || undefined,
|
|
245
|
+
connectionPreference: connectionPrefSelect.value || undefined,
|
|
246
|
+
room: roomInput.value || undefined,
|
|
184
247
|
encryptionKey: encryptionKeyInput.value || undefined,
|
|
185
248
|
keyId: keyIdInput.value || undefined,
|
|
186
249
|
})
|
|
@@ -197,6 +260,10 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
197
260
|
contentDiv.appendChild(nameInput)
|
|
198
261
|
contentDiv.appendChild(typeLabel)
|
|
199
262
|
contentDiv.appendChild(typeSelect)
|
|
263
|
+
contentDiv.appendChild(connectionPrefLabel)
|
|
264
|
+
contentDiv.appendChild(connectionPrefSelect)
|
|
265
|
+
contentDiv.appendChild(roomLabel)
|
|
266
|
+
contentDiv.appendChild(roomInput)
|
|
200
267
|
contentDiv.appendChild(macLabel)
|
|
201
268
|
contentDiv.appendChild(macInput)
|
|
202
269
|
contentDiv.appendChild(encryptionKeyLabel)
|
|
@@ -250,58 +317,6 @@ export async function editDevice(device: any): Promise<void> {
|
|
|
250
317
|
typeSelect.appendChild(optgroup)
|
|
251
318
|
})
|
|
252
319
|
|
|
253
|
-
// --- Matter Type Advanced Field (only for window covering types) ---
|
|
254
|
-
const windowCoveringTypes = ['Roller Shade', 'Curtain', 'Curtain3', 'Blind Tilt']
|
|
255
|
-
const showMatterType = windowCoveringTypes.includes(device.configDeviceType || device.deviceType || device.type || '')
|
|
256
|
-
const matterTypeLabel = document.createElement('label')
|
|
257
|
-
matterTypeLabel.textContent = 'Matter Window Covering Type (Advanced)'
|
|
258
|
-
matterTypeLabel.style.display = showMatterType ? 'block' : 'none'
|
|
259
|
-
matterTypeLabel.style.marginBottom = '6px'
|
|
260
|
-
matterTypeLabel.style.fontWeight = '500'
|
|
261
|
-
matterTypeLabel.style.fontSize = '12px'
|
|
262
|
-
matterTypeLabel.style.color = '#6b7280'
|
|
263
|
-
matterTypeLabel.style.marginTop = '2px'
|
|
264
|
-
const matterTypes = [
|
|
265
|
-
{ value: '', label: 'Auto-detect (recommended)' },
|
|
266
|
-
{ value: 0, label: 'Roller Shade (0)' },
|
|
267
|
-
{ value: 1, label: 'Roman Shade (1)' },
|
|
268
|
-
{ value: 2, label: 'Balloon Shade (2)' },
|
|
269
|
-
{ value: 3, label: 'Woven Wood (3)' },
|
|
270
|
-
{ value: 4, label: 'Drapery (4)' },
|
|
271
|
-
{ value: 5, label: 'Awning (5)' },
|
|
272
|
-
{ value: 6, label: 'Shutter (6)' },
|
|
273
|
-
{ value: 7, label: 'Tilt Blind (7)' },
|
|
274
|
-
{ value: 8, label: 'Blind Tilt (8)' },
|
|
275
|
-
{ value: 9, label: 'Projector Screen (9)' },
|
|
276
|
-
{ value: 15, label: 'Other (15)' },
|
|
277
|
-
]
|
|
278
|
-
const matterTypeSelect = document.createElement('select')
|
|
279
|
-
matterTypeSelect.style.width = '100%'
|
|
280
|
-
matterTypeSelect.style.marginBottom = '12px'
|
|
281
|
-
matterTypeSelect.style.padding = '8px 10px'
|
|
282
|
-
matterTypeSelect.style.borderRadius = '6px'
|
|
283
|
-
matterTypeSelect.style.fontSize = '14px'
|
|
284
|
-
matterTypeSelect.style.boxSizing = 'border-box'
|
|
285
|
-
matterTypes.forEach((opt) => {
|
|
286
|
-
const option = document.createElement('option')
|
|
287
|
-
option.value = String(opt.value)
|
|
288
|
-
option.textContent = opt.label
|
|
289
|
-
if (device.matterType != null && String(opt.value) === String(device.matterType)) {
|
|
290
|
-
option.selected = true
|
|
291
|
-
}
|
|
292
|
-
matterTypeSelect.appendChild(option)
|
|
293
|
-
})
|
|
294
|
-
function updateMatterTypeVisibility() {
|
|
295
|
-
if (windowCoveringTypes.includes(typeSelect.value)) {
|
|
296
|
-
matterTypeLabel.style.display = 'block'
|
|
297
|
-
matterTypeSelect.style.display = 'block'
|
|
298
|
-
} else {
|
|
299
|
-
matterTypeLabel.style.display = 'none'
|
|
300
|
-
matterTypeSelect.style.display = 'none'
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
typeSelect.addEventListener('change', updateMatterTypeVisibility)
|
|
304
|
-
updateMatterTypeVisibility()
|
|
305
320
|
// Create modal dialog for editing
|
|
306
321
|
const div = document.createElement('div')
|
|
307
322
|
div.style.position = 'fixed'
|
|
@@ -402,6 +417,90 @@ export async function editDevice(device: any): Promise<void> {
|
|
|
402
417
|
typeSelect.appendChild(optgroup)
|
|
403
418
|
})
|
|
404
419
|
|
|
420
|
+
// --- Connection Preference ---
|
|
421
|
+
const connectionPrefLabel = document.createElement('label')
|
|
422
|
+
connectionPrefLabel.textContent = 'Connection Preference'
|
|
423
|
+
connectionPrefLabel.style.display = 'block'
|
|
424
|
+
connectionPrefLabel.style.marginBottom = '6px'
|
|
425
|
+
connectionPrefLabel.style.fontWeight = '500'
|
|
426
|
+
connectionPrefLabel.style.fontSize = '12px'
|
|
427
|
+
connectionPrefLabel.style.color = '#6b7280'
|
|
428
|
+
|
|
429
|
+
const connectionPrefSelect = document.createElement('select')
|
|
430
|
+
connectionPrefSelect.style.width = '100%'
|
|
431
|
+
connectionPrefSelect.style.marginBottom = '12px'
|
|
432
|
+
connectionPrefSelect.style.padding = '8px 10px'
|
|
433
|
+
connectionPrefSelect.style.borderRadius = '6px'
|
|
434
|
+
connectionPrefSelect.style.fontSize = '14px'
|
|
435
|
+
connectionPrefSelect.style.boxSizing = 'border-box'
|
|
436
|
+
;['auto', 'ble', 'openapi'].forEach((val) => {
|
|
437
|
+
const opt = document.createElement('option')
|
|
438
|
+
opt.value = val
|
|
439
|
+
opt.text = val.charAt(0).toUpperCase() + val.slice(1)
|
|
440
|
+
opt.selected = (device.connectionPreference || 'auto') === val
|
|
441
|
+
connectionPrefSelect.appendChild(opt)
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
// --- Room ---
|
|
445
|
+
const roomLabel = document.createElement('label')
|
|
446
|
+
roomLabel.textContent = 'Room'
|
|
447
|
+
roomLabel.style.display = 'block'
|
|
448
|
+
roomLabel.style.marginBottom = '6px'
|
|
449
|
+
roomLabel.style.fontWeight = '500'
|
|
450
|
+
roomLabel.style.fontSize = '12px'
|
|
451
|
+
roomLabel.style.color = '#6b7280'
|
|
452
|
+
|
|
453
|
+
const roomInput = document.createElement('input')
|
|
454
|
+
roomInput.type = 'text'
|
|
455
|
+
roomInput.value = device.room || ''
|
|
456
|
+
roomInput.placeholder = 'Optional room/location metadata'
|
|
457
|
+
roomInput.style.width = '100%'
|
|
458
|
+
roomInput.style.marginBottom = '12px'
|
|
459
|
+
roomInput.style.padding = '8px 10px'
|
|
460
|
+
roomInput.style.borderRadius = '6px'
|
|
461
|
+
roomInput.style.fontSize = '14px'
|
|
462
|
+
roomInput.style.boxSizing = 'border-box'
|
|
463
|
+
|
|
464
|
+
// --- Encryption Key ---
|
|
465
|
+
const encryptionKeyLabel = document.createElement('label')
|
|
466
|
+
encryptionKeyLabel.textContent = 'BLE Encryption Key (optional)'
|
|
467
|
+
encryptionKeyLabel.style.display = 'block'
|
|
468
|
+
encryptionKeyLabel.style.marginBottom = '6px'
|
|
469
|
+
encryptionKeyLabel.style.fontWeight = '500'
|
|
470
|
+
encryptionKeyLabel.style.fontSize = '12px'
|
|
471
|
+
encryptionKeyLabel.style.color = '#6b7280'
|
|
472
|
+
|
|
473
|
+
const encryptionKeyInput = document.createElement('input')
|
|
474
|
+
encryptionKeyInput.type = 'password'
|
|
475
|
+
encryptionKeyInput.value = device.encryptionKey || ''
|
|
476
|
+
encryptionKeyInput.placeholder = 'Paste device BLE encryption key'
|
|
477
|
+
encryptionKeyInput.style.width = '100%'
|
|
478
|
+
encryptionKeyInput.style.marginBottom = '12px'
|
|
479
|
+
encryptionKeyInput.style.padding = '8px 10px'
|
|
480
|
+
encryptionKeyInput.style.borderRadius = '6px'
|
|
481
|
+
encryptionKeyInput.style.fontSize = '14px'
|
|
482
|
+
encryptionKeyInput.style.boxSizing = 'border-box'
|
|
483
|
+
|
|
484
|
+
// --- Key ID ---
|
|
485
|
+
const keyIdLabel = document.createElement('label')
|
|
486
|
+
keyIdLabel.textContent = 'BLE Key ID (optional)'
|
|
487
|
+
keyIdLabel.style.display = 'block'
|
|
488
|
+
keyIdLabel.style.marginBottom = '6px'
|
|
489
|
+
keyIdLabel.style.fontWeight = '500'
|
|
490
|
+
keyIdLabel.style.fontSize = '12px'
|
|
491
|
+
keyIdLabel.style.color = '#6b7280'
|
|
492
|
+
|
|
493
|
+
const keyIdInput = document.createElement('input')
|
|
494
|
+
keyIdInput.type = 'text'
|
|
495
|
+
keyIdInput.value = device.keyId || ''
|
|
496
|
+
keyIdInput.placeholder = 'e.g. ff'
|
|
497
|
+
keyIdInput.style.width = '100%'
|
|
498
|
+
keyIdInput.style.marginBottom = '12px'
|
|
499
|
+
keyIdInput.style.padding = '8px 10px'
|
|
500
|
+
keyIdInput.style.borderRadius = '6px'
|
|
501
|
+
keyIdInput.style.fontSize = '14px'
|
|
502
|
+
keyIdInput.style.boxSizing = 'border-box'
|
|
503
|
+
|
|
405
504
|
const errorMessage = document.createElement('div')
|
|
406
505
|
errorMessage.style.color = 'var(--switchbot-red, #ef4444)'
|
|
407
506
|
errorMessage.style.marginBottom = '12px'
|
|
@@ -442,10 +541,32 @@ export async function editDevice(device: any): Promise<void> {
|
|
|
442
541
|
deviceId: device.id,
|
|
443
542
|
configDeviceName: nameInput.value || undefined,
|
|
444
543
|
configDeviceType: typeSelect.value,
|
|
445
|
-
|
|
544
|
+
connectionPreference: connectionPrefSelect.value,
|
|
545
|
+
room: roomInput.value || undefined,
|
|
546
|
+
encryptionKey: encryptionKeyInput.value || undefined,
|
|
547
|
+
keyId: keyIdInput.value || undefined,
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Only include defined properties in the options object
|
|
551
|
+
const options: any = {}
|
|
552
|
+
if (params.connectionPreference !== undefined) {
|
|
553
|
+
options.connectionPreference = params.connectionPreference
|
|
554
|
+
}
|
|
555
|
+
if (params.room !== undefined) {
|
|
556
|
+
options.room = params.room
|
|
557
|
+
}
|
|
558
|
+
if (params.encryptionKey !== undefined) {
|
|
559
|
+
options.encryptionKey = params.encryptionKey
|
|
560
|
+
}
|
|
561
|
+
if (params.keyId !== undefined) {
|
|
562
|
+
options.keyId = params.keyId
|
|
446
563
|
}
|
|
447
564
|
|
|
448
|
-
await updateDevice(
|
|
565
|
+
await updateDevice(
|
|
566
|
+
params.deviceId,
|
|
567
|
+
params.configDeviceName,
|
|
568
|
+
params.configDeviceType,
|
|
569
|
+
)
|
|
449
570
|
await syncParentPluginConfigFromDisk()
|
|
450
571
|
|
|
451
572
|
// Refresh device list
|
|
@@ -469,8 +590,14 @@ export async function editDevice(device: any): Promise<void> {
|
|
|
469
590
|
contentDiv.appendChild(apiTypeInput)
|
|
470
591
|
contentDiv.appendChild(typeLabel)
|
|
471
592
|
contentDiv.appendChild(typeSelect)
|
|
472
|
-
contentDiv.appendChild(
|
|
473
|
-
contentDiv.appendChild(
|
|
593
|
+
contentDiv.appendChild(connectionPrefLabel)
|
|
594
|
+
contentDiv.appendChild(connectionPrefSelect)
|
|
595
|
+
contentDiv.appendChild(roomLabel)
|
|
596
|
+
contentDiv.appendChild(roomInput)
|
|
597
|
+
contentDiv.appendChild(encryptionKeyLabel)
|
|
598
|
+
contentDiv.appendChild(encryptionKeyInput)
|
|
599
|
+
contentDiv.appendChild(keyIdLabel)
|
|
600
|
+
contentDiv.appendChild(keyIdInput)
|
|
474
601
|
contentDiv.appendChild(errorMessage)
|
|
475
602
|
contentDiv.appendChild(buttons)
|
|
476
603
|
|
|
@@ -58,6 +58,9 @@ export declare function getDiscoveryPreferences(): {
|
|
|
58
58
|
/**
|
|
59
59
|
* Save filter/sort preferences to localStorage
|
|
60
60
|
* @param preferences Preferences object to save
|
|
61
|
+
* @param preferences.connectionType Connection type filter
|
|
62
|
+
* @param preferences.sortBy Sort criterion
|
|
63
|
+
* @param preferences.searchQuery Search query string
|
|
61
64
|
*/
|
|
62
65
|
export declare function setDiscoveryPreferences(preferences: {
|
|
63
66
|
connectionType: 'all' | 'ble' | 'api' | 'both' | 'ir';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/render.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/render.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG;IAC9D,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb,CA8CA;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CA+BtE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAiB9E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAKpE;AAED,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAmBhF;AAED,wBAAgB,aAAa,IAAI,WAAW,CAK3C;AAgGD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,GAAG,GAAG,WAAW,CA6OjE;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,GAAG,EAAE,EACd,OAAO,GAAE;IACP,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC3B,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;CACrD,GACL,OAAO,CAAC,WAAW,CAAC,CAuPtB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,GAAG,EAAE,EACd,cAAc,GAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAY,EAC7D,WAAW,SAAK,GACf,GAAG,EAAE,CAmCP;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,GAAG,EAAE,EACd,MAAM,GAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAqB,GACzD,GAAG,EAAE,CA8CP;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI;IACzC,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAAA;IACrD,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAA;IACjD,WAAW,EAAE,MAAM,CAAA;CACpB,CAeA;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE;IACnD,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAAA;IACrD,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAA;IACjD,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,IAAI,CAMP;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CA+KlD"}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// Move regexes to module scope to avoid re-compilation on every call
|
|
2
|
+
// import type { DEVICE_TYPES } from './constants.js' // Removed unused import
|
|
3
|
+
const SPACES_REGEX = /\s/g;
|
|
4
|
+
const CAMELCASE_REGEX = /([A-Z])/g;
|
|
5
|
+
const FIRST_CHAR_REGEX = /^./;
|
|
1
6
|
/**
|
|
2
7
|
* Get RSSI signal quality level and color based on dBm value
|
|
3
8
|
* @param rssi Signal strength in dBm (typically -30 to -90)
|
|
@@ -234,23 +239,29 @@ export function renderDeviceDetailsPanel(device) {
|
|
|
234
239
|
let batteryHistory = [];
|
|
235
240
|
try {
|
|
236
241
|
const raw = localStorage.getItem(batteryHistoryKey);
|
|
237
|
-
if (raw)
|
|
242
|
+
if (raw) {
|
|
238
243
|
batteryHistory = JSON.parse(raw);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch (e) {
|
|
247
|
+
// Optionally log or handle error
|
|
239
248
|
}
|
|
240
|
-
catch { }
|
|
241
249
|
const now = Date.now();
|
|
242
250
|
if (typeof device?.battery === 'number') {
|
|
243
251
|
// Only add if different from last or >1h since last
|
|
244
|
-
const last = batteryHistory
|
|
252
|
+
const last = batteryHistory.at(-1);
|
|
245
253
|
if (!last || last.value !== device.battery || now - last.ts > 60 * 60 * 1000) {
|
|
246
254
|
batteryHistory.push({ value: device.battery, ts: now });
|
|
247
255
|
// Keep only last 30 entries (about a month if daily)
|
|
248
|
-
if (batteryHistory.length > 30)
|
|
256
|
+
if (batteryHistory.length > 30) {
|
|
249
257
|
batteryHistory = batteryHistory.slice(-30);
|
|
258
|
+
}
|
|
250
259
|
try {
|
|
251
260
|
localStorage.setItem(batteryHistoryKey, JSON.stringify(batteryHistory));
|
|
252
261
|
}
|
|
253
|
-
catch {
|
|
262
|
+
catch (e) {
|
|
263
|
+
// Optionally log or handle error
|
|
264
|
+
}
|
|
254
265
|
}
|
|
255
266
|
}
|
|
256
267
|
const rows = [
|
|
@@ -329,7 +340,9 @@ export function renderDeviceDetailsPanel(device) {
|
|
|
329
340
|
chart.style.height = '28px';
|
|
330
341
|
chart.style.display = 'flex';
|
|
331
342
|
// SVG sparkline
|
|
332
|
-
const w = 120
|
|
343
|
+
const w = 120;
|
|
344
|
+
const h = 24;
|
|
345
|
+
const pad = 2;
|
|
333
346
|
const min = Math.min(...batteryHistory.map(b => b.value), 100);
|
|
334
347
|
const max = Math.max(...batteryHistory.map(b => b.value), 0);
|
|
335
348
|
const range = max - min || 1;
|
|
@@ -386,11 +399,26 @@ export function renderDeviceDetailsPanel(device) {
|
|
|
386
399
|
}
|
|
387
400
|
// --- Expose advanced/extra features dynamically ---
|
|
388
401
|
const featureKeys = [
|
|
389
|
-
'airQuality',
|
|
390
|
-
'
|
|
391
|
-
'
|
|
402
|
+
'airQuality',
|
|
403
|
+
'pm25',
|
|
404
|
+
'pm10',
|
|
405
|
+
'voc',
|
|
406
|
+
'co2',
|
|
407
|
+
'humidity',
|
|
408
|
+
'temperature',
|
|
409
|
+
'preset',
|
|
410
|
+
'mode',
|
|
411
|
+
'presetMode',
|
|
412
|
+
'direction',
|
|
413
|
+
'calibration',
|
|
414
|
+
'multiCommand',
|
|
415
|
+
'extendedInfo',
|
|
416
|
+
'segmentedControl',
|
|
417
|
+
'features',
|
|
418
|
+
'capabilities',
|
|
419
|
+
'state',
|
|
392
420
|
];
|
|
393
|
-
const shown = new Set(rows.map(r => r.label.toLowerCase().replace(
|
|
421
|
+
const shown = new Set(rows.map(r => r.label.toLowerCase().replace(SPACES_REGEX, '')));
|
|
394
422
|
for (const key of featureKeys) {
|
|
395
423
|
if (device && device[key] !== undefined && !shown.has(key.toLowerCase())) {
|
|
396
424
|
const line = document.createElement('div');
|
|
@@ -402,7 +430,7 @@ export function renderDeviceDetailsPanel(device) {
|
|
|
402
430
|
const label = document.createElement('span');
|
|
403
431
|
label.style.fontWeight = '600';
|
|
404
432
|
label.style.minWidth = '110px';
|
|
405
|
-
label.textContent = key.replace(
|
|
433
|
+
label.textContent = `${key.replace(CAMELCASE_REGEX, ' $1').replace(FIRST_CHAR_REGEX, s => s.toUpperCase())}:`;
|
|
406
434
|
const value = document.createElement('span');
|
|
407
435
|
value.style.fontFamily = 'monospace';
|
|
408
436
|
value.style.fontSize = '11px';
|
|
@@ -485,15 +513,17 @@ export async function renderDiscoveredDevices(devices, options = {}) {
|
|
|
485
513
|
}
|
|
486
514
|
// Show offline/unreachable indicator if device is offline
|
|
487
515
|
let offline = false;
|
|
488
|
-
|
|
516
|
+
const lastSeen = d.lastSeen || d.lastseen || d.updatedAt;
|
|
489
517
|
if (typeof d.offline === 'boolean') {
|
|
490
518
|
offline = d.offline;
|
|
491
519
|
}
|
|
492
520
|
else if (lastSeen) {
|
|
493
521
|
try {
|
|
494
522
|
const last = new Date(lastSeen).getTime();
|
|
495
|
-
if (!isNaN(last)
|
|
496
|
-
|
|
523
|
+
if (!Number.isNaN(last)) {
|
|
524
|
+
if (Date.now() - last > 1000 * 60 * 60) { // 1 hour
|
|
525
|
+
offline = true;
|
|
526
|
+
}
|
|
497
527
|
}
|
|
498
528
|
}
|
|
499
529
|
catch { }
|
|
@@ -549,8 +579,8 @@ export async function renderDiscoveredDevices(devices, options = {}) {
|
|
|
549
579
|
if (typeof d.battery === 'number' && d.battery < 20) {
|
|
550
580
|
const batteryWarn = document.createElement('span');
|
|
551
581
|
batteryWarn.textContent = `⚠️ ${d.battery}%`;
|
|
552
|
-
batteryWarn.style.cssText
|
|
553
|
-
d.battery < 10
|
|
582
|
+
batteryWarn.style.cssText
|
|
583
|
+
= d.battery < 10
|
|
554
584
|
? 'background: #dc2626; color: white; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-left: 6px;'
|
|
555
585
|
: 'background: #fbbf24; color: #111; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-left: 6px;';
|
|
556
586
|
batteryWarn.title = d.battery < 10 ? 'Battery critically low' : 'Battery low';
|
|
@@ -652,7 +682,7 @@ export function filterDevices(devices, connectionType = 'all', searchQuery = '')
|
|
|
652
682
|
let filtered = [...devices];
|
|
653
683
|
// Filter by connection type
|
|
654
684
|
if (connectionType !== 'all') {
|
|
655
|
-
filtered = filtered.filter(d => {
|
|
685
|
+
filtered = filtered.filter((d) => {
|
|
656
686
|
if (connectionType === 'ir') {
|
|
657
687
|
return d.isIR === true;
|
|
658
688
|
}
|
|
@@ -671,7 +701,7 @@ export function filterDevices(devices, connectionType = 'all', searchQuery = '')
|
|
|
671
701
|
// Filter by search query
|
|
672
702
|
if (searchQuery.trim()) {
|
|
673
703
|
const query = searchQuery.toLowerCase();
|
|
674
|
-
filtered = filtered.filter(d => {
|
|
704
|
+
filtered = filtered.filter((d) => {
|
|
675
705
|
const name = (d.name || '').toLowerCase();
|
|
676
706
|
const id = (d.id || '').toLowerCase();
|
|
677
707
|
const type = (d.type || '').toLowerCase();
|
|
@@ -710,11 +740,11 @@ export function sortDevices(devices, sortBy = 'name') {
|
|
|
710
740
|
case 'connection': {
|
|
711
741
|
// Sort by connection type: Both > BLE > OpenAPI > Others
|
|
712
742
|
const connectionOrder = {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
743
|
+
Both: 0,
|
|
744
|
+
BLE: 1,
|
|
745
|
+
OpenAPI: 2,
|
|
746
|
+
API: 2,
|
|
747
|
+
Unknown: 3,
|
|
718
748
|
};
|
|
719
749
|
sorted.sort((a, b) => {
|
|
720
750
|
const aOrder = connectionOrder[a.connectionType || 'Unknown'] ?? 3;
|
|
@@ -755,6 +785,9 @@ export function getDiscoveryPreferences() {
|
|
|
755
785
|
/**
|
|
756
786
|
* Save filter/sort preferences to localStorage
|
|
757
787
|
* @param preferences Preferences object to save
|
|
788
|
+
* @param preferences.connectionType Connection type filter
|
|
789
|
+
* @param preferences.sortBy Sort criterion
|
|
790
|
+
* @param preferences.searchQuery Search query string
|
|
758
791
|
*/
|
|
759
792
|
export function setDiscoveryPreferences(preferences) {
|
|
760
793
|
try {
|
|
@@ -890,7 +923,12 @@ export function renderDeviceList(list) {
|
|
|
890
923
|
}, 1200);
|
|
891
924
|
}
|
|
892
925
|
catch (e) {
|
|
893
|
-
|
|
926
|
+
copyBtn.textContent = 'Failed';
|
|
927
|
+
copyBtn.classList.add('error');
|
|
928
|
+
setTimeout(() => {
|
|
929
|
+
copyBtn.textContent = 'Copy ID';
|
|
930
|
+
copyBtn.classList.remove('error');
|
|
931
|
+
}, 1200);
|
|
894
932
|
}
|
|
895
933
|
});
|
|
896
934
|
const deleteBtn = document.createElement('button');
|
|
@@ -910,5 +948,6 @@ export function renderDeviceList(list) {
|
|
|
910
948
|
li.appendChild(buttons);
|
|
911
949
|
ul.appendChild(li);
|
|
912
950
|
}
|
|
951
|
+
// No return value needed for void function
|
|
913
952
|
}
|
|
914
953
|
//# sourceMappingURL=render.js.map
|