@switchbot/homebridge-switchbot 5.0.0-beta.127 → 5.0.0-beta.129
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 +11 -0
- package/dist/devices/genericDevice.d.ts.map +1 -1
- package/dist/devices/genericDevice.js +56 -10
- package/dist/devices/genericDevice.js.map +1 -1
- package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -1
- package/dist/homebridge-ui/endpoints/discovery.js +10 -1
- package/dist/homebridge-ui/endpoints/discovery.js.map +1 -1
- package/dist/homebridge-ui/public/js/api.d.ts +4 -1
- package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/api.js +7 -1
- package/dist/homebridge-ui/public/js/api.js.map +1 -1
- package/dist/homebridge-ui/public/js/api.ts +8 -1
- package/dist/homebridge-ui/public/js/app.js +149 -24
- package/dist/homebridge-ui/public/js/app.js.map +2 -2
- package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/devices.js +1 -0
- package/dist/homebridge-ui/public/js/devices.js.map +1 -1
- package/dist/homebridge-ui/public/js/devices.ts +5 -2
- package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/modals.js +149 -20
- package/dist/homebridge-ui/public/js/modals.js.map +1 -1
- package/dist/homebridge-ui/public/js/modals.ts +156 -26
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +0 -4
- package/dist/platform.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/devices/genericDevice.ts +65 -10
- package/src/homebridge-ui/endpoints/discovery.ts +9 -1
- package/src/homebridge-ui/public/js/api.ts +8 -1
- package/src/homebridge-ui/public/js/devices.ts +5 -2
- package/src/homebridge-ui/public/js/modals.ts +156 -26
- package/src/platform.ts +0 -4
|
@@ -57,6 +57,7 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
57
57
|
nameInput.style.fontSize = '14px'
|
|
58
58
|
nameInput.style.boxSizing = 'border-box'
|
|
59
59
|
|
|
60
|
+
// --- Device Type Select ---
|
|
60
61
|
const typeLabel = document.createElement('label')
|
|
61
62
|
typeLabel.textContent = 'Config Device Type'
|
|
62
63
|
typeLabel.style.display = 'block'
|
|
@@ -79,7 +80,6 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
79
80
|
Object.keys(DEVICE_TYPES).forEach((categoryName) => {
|
|
80
81
|
const optgroup = document.createElement('optgroup')
|
|
81
82
|
optgroup.label = categoryName
|
|
82
|
-
|
|
83
83
|
DEVICE_TYPES[categoryName as keyof typeof DEVICE_TYPES].forEach((deviceType) => {
|
|
84
84
|
const opt = document.createElement('option')
|
|
85
85
|
opt.value = deviceType
|
|
@@ -88,10 +88,69 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
88
88
|
opt.selected = deviceType.toLowerCase() === detectedType
|
|
89
89
|
optgroup.appendChild(opt)
|
|
90
90
|
})
|
|
91
|
-
|
|
92
91
|
typeSelect.appendChild(optgroup)
|
|
93
92
|
})
|
|
94
93
|
|
|
94
|
+
// --- Matter Type Advanced Field (only for window covering types) ---
|
|
95
|
+
|
|
96
|
+
// Matter Type advanced field (only for window covering types)
|
|
97
|
+
const windowCoveringTypes = ['Roller Shade', 'Curtain', 'Curtain3', 'Blind Tilt']
|
|
98
|
+
|
|
99
|
+
const matterTypeLabel = document.createElement('label')
|
|
100
|
+
matterTypeLabel.textContent = 'Matter Window Covering Type (Advanced)'
|
|
101
|
+
matterTypeLabel.style.display = 'block'
|
|
102
|
+
matterTypeLabel.style.marginBottom = '6px'
|
|
103
|
+
matterTypeLabel.style.fontWeight = '500'
|
|
104
|
+
matterTypeLabel.style.fontSize = '12px'
|
|
105
|
+
matterTypeLabel.style.color = '#6b7280'
|
|
106
|
+
matterTypeLabel.style.marginTop = '2px'
|
|
107
|
+
|
|
108
|
+
// Table 8.2 from Matter spec (partial, most common types)
|
|
109
|
+
const matterTypes = [
|
|
110
|
+
{ value: '', label: 'Auto-detect (recommended)' },
|
|
111
|
+
{ value: 0, label: 'Roller Shade (0)' },
|
|
112
|
+
{ value: 1, label: 'Roman Shade (1)' },
|
|
113
|
+
{ value: 2, label: 'Balloon Shade (2)' },
|
|
114
|
+
{ value: 3, label: 'Woven Wood (3)' },
|
|
115
|
+
{ value: 4, label: 'Drapery (4)' },
|
|
116
|
+
{ value: 5, label: 'Awning (5)' },
|
|
117
|
+
{ value: 6, label: 'Shutter (6)' },
|
|
118
|
+
{ value: 7, label: 'Tilt Blind (7)' },
|
|
119
|
+
{ value: 8, label: 'Blind Tilt (8)' },
|
|
120
|
+
{ value: 9, label: 'Projector Screen (9)' },
|
|
121
|
+
{ value: 15, label: 'Other (15)' },
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
const matterTypeSelect = document.createElement('select')
|
|
125
|
+
matterTypeSelect.style.width = '100%'
|
|
126
|
+
matterTypeSelect.style.marginBottom = '12px'
|
|
127
|
+
matterTypeSelect.style.padding = '8px 10px'
|
|
128
|
+
matterTypeSelect.style.borderRadius = '6px'
|
|
129
|
+
matterTypeSelect.style.fontSize = '14px'
|
|
130
|
+
matterTypeSelect.style.boxSizing = 'border-box'
|
|
131
|
+
matterTypes.forEach((opt) => {
|
|
132
|
+
const option = document.createElement('option')
|
|
133
|
+
option.value = String(opt.value)
|
|
134
|
+
option.textContent = opt.label
|
|
135
|
+
if (device.matterType != null && String(opt.value) === String(device.matterType)) {
|
|
136
|
+
option.selected = true
|
|
137
|
+
}
|
|
138
|
+
matterTypeSelect.appendChild(option)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Show/hide Matter Type field based on type
|
|
142
|
+
function updateMatterTypeVisibility() {
|
|
143
|
+
if (windowCoveringTypes.includes(typeSelect.value)) {
|
|
144
|
+
matterTypeLabel.style.display = 'block'
|
|
145
|
+
matterTypeSelect.style.display = 'block'
|
|
146
|
+
} else {
|
|
147
|
+
matterTypeLabel.style.display = 'none'
|
|
148
|
+
matterTypeSelect.style.display = 'none'
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
typeSelect.addEventListener('change', updateMatterTypeVisibility)
|
|
152
|
+
updateMatterTypeVisibility()
|
|
153
|
+
|
|
95
154
|
const macLabel = document.createElement('label')
|
|
96
155
|
macLabel.textContent = 'BLE MAC Address (optional)'
|
|
97
156
|
macLabel.style.display = 'block'
|
|
@@ -111,7 +170,6 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
111
170
|
macInput.style.fontSize = '14px'
|
|
112
171
|
macInput.style.boxSizing = 'border-box'
|
|
113
172
|
|
|
114
|
-
|
|
115
173
|
// BLE Encryption Key
|
|
116
174
|
const encryptionKeyLabel = document.createElement('label')
|
|
117
175
|
encryptionKeyLabel.textContent = 'BLE Encryption Key (optional)'
|
|
@@ -173,7 +231,6 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
173
231
|
importBtn.style.padding = '8px 20px'
|
|
174
232
|
importBtn.style.fontSize = '13px'
|
|
175
233
|
|
|
176
|
-
|
|
177
234
|
const cleanup = (result: { configDeviceName: string, configDeviceType: string, address?: string, encryptionKey?: string, keyId?: string } | null) => {
|
|
178
235
|
div.remove()
|
|
179
236
|
resolve(result)
|
|
@@ -186,6 +243,7 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
186
243
|
address: macInput.value || undefined,
|
|
187
244
|
encryptionKey: encryptionKeyInput.value || undefined,
|
|
188
245
|
keyId: keyIdInput.value || undefined,
|
|
246
|
+
...(windowCoveringTypes.includes(typeSelect.value) && matterTypeSelect.value !== '' ? { matterType: Number(matterTypeSelect.value) } : {}),
|
|
189
247
|
})
|
|
190
248
|
|
|
191
249
|
div.addEventListener('click', (event) => {
|
|
@@ -194,13 +252,14 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
194
252
|
}
|
|
195
253
|
})
|
|
196
254
|
|
|
197
|
-
|
|
198
255
|
buttons.appendChild(cancelBtn)
|
|
199
256
|
buttons.appendChild(importBtn)
|
|
200
257
|
contentDiv.appendChild(nameLabel)
|
|
201
258
|
contentDiv.appendChild(nameInput)
|
|
202
259
|
contentDiv.appendChild(typeLabel)
|
|
203
260
|
contentDiv.appendChild(typeSelect)
|
|
261
|
+
contentDiv.appendChild(matterTypeLabel)
|
|
262
|
+
contentDiv.appendChild(matterTypeSelect)
|
|
204
263
|
contentDiv.appendChild(macLabel)
|
|
205
264
|
contentDiv.appendChild(macInput)
|
|
206
265
|
contentDiv.appendChild(encryptionKeyLabel)
|
|
@@ -218,6 +277,94 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
218
277
|
}
|
|
219
278
|
|
|
220
279
|
export async function editDevice(device: any): Promise<void> {
|
|
280
|
+
// --- Device Type Select ---
|
|
281
|
+
const typeLabel = document.createElement('label')
|
|
282
|
+
typeLabel.textContent = 'Config Device Type'
|
|
283
|
+
typeLabel.style.display = 'block'
|
|
284
|
+
typeLabel.style.marginBottom = '6px'
|
|
285
|
+
typeLabel.style.fontWeight = '500'
|
|
286
|
+
typeLabel.style.fontSize = '12px'
|
|
287
|
+
typeLabel.style.color = '#6b7280'
|
|
288
|
+
|
|
289
|
+
const typeSelect = document.createElement('select')
|
|
290
|
+
typeSelect.style.width = '100%'
|
|
291
|
+
typeSelect.style.padding = '8px 10px'
|
|
292
|
+
typeSelect.style.marginBottom = '12px'
|
|
293
|
+
typeSelect.style.borderRadius = '6px'
|
|
294
|
+
typeSelect.style.fontSize = '14px'
|
|
295
|
+
typeSelect.style.background = getComputedStyle(document.body).backgroundColor
|
|
296
|
+
typeSelect.style.color = getComputedStyle(document.body).color
|
|
297
|
+
typeSelect.style.border = '1px solid #ccc'
|
|
298
|
+
typeSelect.style.boxSizing = 'border-box'
|
|
299
|
+
|
|
300
|
+
// Add option groups with all API device types
|
|
301
|
+
Object.keys(DEVICE_TYPES).forEach((categoryName) => {
|
|
302
|
+
const optgroup = document.createElement('optgroup')
|
|
303
|
+
optgroup.label = categoryName
|
|
304
|
+
DEVICE_TYPES[categoryName as keyof typeof DEVICE_TYPES].forEach((deviceType) => {
|
|
305
|
+
const opt = document.createElement('option')
|
|
306
|
+
opt.value = deviceType
|
|
307
|
+
opt.text = deviceType
|
|
308
|
+
// Select current configDeviceType if set, otherwise match API deviceType
|
|
309
|
+
const currentType = device.configDeviceType || device.deviceType || device.type || ''
|
|
310
|
+
opt.selected = currentType === deviceType
|
|
311
|
+
optgroup.appendChild(opt)
|
|
312
|
+
})
|
|
313
|
+
typeSelect.appendChild(optgroup)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
// --- Matter Type Advanced Field (only for window covering types) ---
|
|
317
|
+
const windowCoveringTypes = ['Roller Shade', 'Curtain', 'Curtain3', 'Blind Tilt']
|
|
318
|
+
const showMatterType = windowCoveringTypes.includes(device.configDeviceType || device.deviceType || device.type || '')
|
|
319
|
+
const matterTypeLabel = document.createElement('label')
|
|
320
|
+
matterTypeLabel.textContent = 'Matter Window Covering Type (Advanced)'
|
|
321
|
+
matterTypeLabel.style.display = showMatterType ? 'block' : 'none'
|
|
322
|
+
matterTypeLabel.style.marginBottom = '6px'
|
|
323
|
+
matterTypeLabel.style.fontWeight = '500'
|
|
324
|
+
matterTypeLabel.style.fontSize = '12px'
|
|
325
|
+
matterTypeLabel.style.color = '#6b7280'
|
|
326
|
+
matterTypeLabel.style.marginTop = '2px'
|
|
327
|
+
const matterTypes = [
|
|
328
|
+
{ value: '', label: 'Auto-detect (recommended)' },
|
|
329
|
+
{ value: 0, label: 'Roller Shade (0)' },
|
|
330
|
+
{ value: 1, label: 'Roman Shade (1)' },
|
|
331
|
+
{ value: 2, label: 'Balloon Shade (2)' },
|
|
332
|
+
{ value: 3, label: 'Woven Wood (3)' },
|
|
333
|
+
{ value: 4, label: 'Drapery (4)' },
|
|
334
|
+
{ value: 5, label: 'Awning (5)' },
|
|
335
|
+
{ value: 6, label: 'Shutter (6)' },
|
|
336
|
+
{ value: 7, label: 'Tilt Blind (7)' },
|
|
337
|
+
{ value: 8, label: 'Blind Tilt (8)' },
|
|
338
|
+
{ value: 9, label: 'Projector Screen (9)' },
|
|
339
|
+
{ value: 15, label: 'Other (15)' },
|
|
340
|
+
]
|
|
341
|
+
const matterTypeSelect = document.createElement('select')
|
|
342
|
+
matterTypeSelect.style.width = '100%'
|
|
343
|
+
matterTypeSelect.style.marginBottom = '12px'
|
|
344
|
+
matterTypeSelect.style.padding = '8px 10px'
|
|
345
|
+
matterTypeSelect.style.borderRadius = '6px'
|
|
346
|
+
matterTypeSelect.style.fontSize = '14px'
|
|
347
|
+
matterTypeSelect.style.boxSizing = 'border-box'
|
|
348
|
+
matterTypes.forEach((opt) => {
|
|
349
|
+
const option = document.createElement('option')
|
|
350
|
+
option.value = String(opt.value)
|
|
351
|
+
option.textContent = opt.label
|
|
352
|
+
if (device.matterType != null && String(opt.value) === String(device.matterType)) {
|
|
353
|
+
option.selected = true
|
|
354
|
+
}
|
|
355
|
+
matterTypeSelect.appendChild(option)
|
|
356
|
+
})
|
|
357
|
+
function updateMatterTypeVisibility() {
|
|
358
|
+
if (windowCoveringTypes.includes(typeSelect.value)) {
|
|
359
|
+
matterTypeLabel.style.display = 'block'
|
|
360
|
+
matterTypeSelect.style.display = 'block'
|
|
361
|
+
} else {
|
|
362
|
+
matterTypeLabel.style.display = 'none'
|
|
363
|
+
matterTypeSelect.style.display = 'none'
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
typeSelect.addEventListener('change', updateMatterTypeVisibility)
|
|
367
|
+
updateMatterTypeVisibility()
|
|
221
368
|
// Create modal dialog for editing
|
|
222
369
|
const div = document.createElement('div')
|
|
223
370
|
div.style.position = 'fixed'
|
|
@@ -298,26 +445,6 @@ export async function editDevice(device: any): Promise<void> {
|
|
|
298
445
|
apiTypeInput.style.backgroundColor = '#f9fafb'
|
|
299
446
|
|
|
300
447
|
// Editable config device type dropdown
|
|
301
|
-
const typeLabel = document.createElement('label')
|
|
302
|
-
typeLabel.textContent = 'Config Device Type (Override)'
|
|
303
|
-
typeLabel.style.display = 'block'
|
|
304
|
-
typeLabel.style.marginBottom = '6px'
|
|
305
|
-
typeLabel.style.fontWeight = '500'
|
|
306
|
-
typeLabel.style.fontSize = '12px'
|
|
307
|
-
typeLabel.style.color = '#6b7280'
|
|
308
|
-
|
|
309
|
-
const typeSelect = document.createElement('select')
|
|
310
|
-
typeSelect.style.width = '100%'
|
|
311
|
-
typeSelect.style.padding = '8px 10px'
|
|
312
|
-
typeSelect.style.marginBottom = '12px'
|
|
313
|
-
typeSelect.style.borderRadius = '6px'
|
|
314
|
-
typeSelect.style.fontSize = '14px'
|
|
315
|
-
typeSelect.style.background = getComputedStyle(nameInput).background
|
|
316
|
-
typeSelect.style.color = getComputedStyle(nameInput).color
|
|
317
|
-
typeSelect.style.border = getComputedStyle(nameInput).border
|
|
318
|
-
typeSelect.style.boxSizing = 'border-box'
|
|
319
|
-
typeSelect.style.cursor = 'pointer'
|
|
320
|
-
typeSelect.style.transition = 'border-color 0.2s ease'
|
|
321
448
|
|
|
322
449
|
// Add option groups with all API device types
|
|
323
450
|
Object.keys(DEVICE_TYPES).forEach((categoryName) => {
|
|
@@ -378,9 +505,10 @@ export async function editDevice(device: any): Promise<void> {
|
|
|
378
505
|
deviceId: device.id,
|
|
379
506
|
configDeviceName: nameInput.value || undefined,
|
|
380
507
|
configDeviceType: typeSelect.value,
|
|
508
|
+
matterType: windowCoveringTypes.includes(typeSelect.value) && matterTypeSelect.value !== '' ? Number(matterTypeSelect.value) : undefined,
|
|
381
509
|
}
|
|
382
510
|
|
|
383
|
-
await updateDevice(params.deviceId, params.configDeviceName, params.configDeviceType)
|
|
511
|
+
await updateDevice(params.deviceId, params.configDeviceName, params.configDeviceType, { matterType: params.matterType })
|
|
384
512
|
await syncParentPluginConfigFromDisk()
|
|
385
513
|
|
|
386
514
|
// Refresh device list
|
|
@@ -404,6 +532,8 @@ export async function editDevice(device: any): Promise<void> {
|
|
|
404
532
|
contentDiv.appendChild(apiTypeInput)
|
|
405
533
|
contentDiv.appendChild(typeLabel)
|
|
406
534
|
contentDiv.appendChild(typeSelect)
|
|
535
|
+
contentDiv.appendChild(matterTypeLabel)
|
|
536
|
+
contentDiv.appendChild(matterTypeSelect)
|
|
407
537
|
contentDiv.appendChild(errorMessage)
|
|
408
538
|
contentDiv.appendChild(buttons)
|
|
409
539
|
|
package/src/platform.ts
CHANGED
|
@@ -1141,10 +1141,8 @@ export class SwitchBotHAPPlatform {
|
|
|
1141
1141
|
// Prefer Matter: try registering to the Matter child bridge first.
|
|
1142
1142
|
let matterRegistered = false
|
|
1143
1143
|
if (useMatter) {
|
|
1144
|
-
// Only attempt Matter registration if api.matter is truly available
|
|
1145
1144
|
const matterApi = (this.api as any)?.matter
|
|
1146
1145
|
if (this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.() && matterApi && typeof matterApi.registerPlatformAccessories === 'function') {
|
|
1147
|
-
this.log.info(`Attempting Matter registration for ${d.id} (${type})`)
|
|
1148
1146
|
try {
|
|
1149
1147
|
const createdDesc = await created.createAccessory(this.api)
|
|
1150
1148
|
const uuid = matterApi.uuid.generate(`${d.id}`)
|
|
@@ -1165,7 +1163,6 @@ export class SwitchBotHAPPlatform {
|
|
|
1165
1163
|
handlers: createdDesc.handlers || createMatterHandlers(this.log, d.id, type, (this.config as any)?._client) || undefined,
|
|
1166
1164
|
context: { deviceId: d.id, type, _created: true },
|
|
1167
1165
|
}
|
|
1168
|
-
// Log the full accessory descriptor before registration
|
|
1169
1166
|
this.log.info(`[MatterDebug] Accessory descriptor for ${d.id}:`, JSON.stringify({
|
|
1170
1167
|
UUID: accessory.UUID,
|
|
1171
1168
|
displayName: accessory.displayName,
|
|
@@ -1179,7 +1176,6 @@ export class SwitchBotHAPPlatform {
|
|
|
1179
1176
|
this.log.info(`[MatterDebug] Registered Matter accessory ${d.id} (${type}) with uuid=${uuid}`)
|
|
1180
1177
|
} catch (e: any) {
|
|
1181
1178
|
this.log.error(`[MatterDebug] Failed to register Matter accessory for ${d.id} (${type}):`, e && (e.stack || e.message || e))
|
|
1182
|
-
// If this is a conformance error, attempt to remove the cached accessory so it can be re-registered cleanly
|
|
1183
1179
|
if (e && (e.message?.includes('Conformance') || e.message?.includes('enum value Rollershade') || e.message?.includes('Behaviors have errors'))) {
|
|
1184
1180
|
try {
|
|
1185
1181
|
const uuid = matterApi.uuid.generate(`${d.id}`)
|