@switchbot/homebridge-switchbot 5.0.0-beta.110 → 5.0.0-beta.112
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 +39 -0
- package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -1
- package/dist/homebridge-ui/endpoints/devices.js +10 -1
- package/dist/homebridge-ui/endpoints/devices.js.map +1 -1
- package/dist/homebridge-ui/public/css/styles.css +111 -16
- package/dist/homebridge-ui/public/index.html +34 -60
- package/dist/homebridge-ui/public/js/api.d.ts +5 -1
- package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/api.js +13 -3
- package/dist/homebridge-ui/public/js/api.js.map +1 -1
- package/dist/homebridge-ui/public/js/api.ts +13 -3
- package/dist/homebridge-ui/public/js/app.js +338 -42
- package/dist/homebridge-ui/public/js/app.js.map +3 -3
- package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/devices.js +12 -3
- package/dist/homebridge-ui/public/js/devices.js.map +1 -1
- package/dist/homebridge-ui/public/js/devices.ts +13 -3
- package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/discovery.js +37 -3
- package/dist/homebridge-ui/public/js/discovery.js.map +1 -1
- package/dist/homebridge-ui/public/js/discovery.ts +39 -2
- package/dist/homebridge-ui/public/js/modals.d.ts +5 -0
- package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/modals.js +146 -0
- package/dist/homebridge-ui/public/js/modals.js.map +1 -1
- package/dist/homebridge-ui/public/js/modals.ts +167 -0
- package/dist/homebridge-ui/public/js/render.d.ts +3 -1
- package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/render.js +135 -32
- package/dist/homebridge-ui/public/js/render.js.map +1 -1
- package/dist/homebridge-ui/public/js/render.ts +145 -32
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +64 -109
- package/dist/platform.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +9 -8
- package/src/homebridge-ui/endpoints/devices.ts +5 -2
- package/src/homebridge-ui/public/css/styles.css +111 -16
- package/src/homebridge-ui/public/index.html +34 -60
- package/src/homebridge-ui/public/js/api.ts +13 -3
- package/src/homebridge-ui/public/js/devices.ts +13 -3
- package/src/homebridge-ui/public/js/discovery.ts +39 -2
- package/src/homebridge-ui/public/js/modals.ts +167 -0
- package/src/homebridge-ui/public/js/render.ts +145 -32
- package/src/platform.ts +77 -109
- package/src/utils.ts +2 -1
- package/test/e2e/run-e2e.spec.ts +12 -10
- package/test/helpers/matter-harness.ts +2 -2
- package/test/matter-childbridge.spec.ts +5 -1
- package/test/matter-integration.spec.ts +41 -0
- package/test/utils.spec.ts +18 -2
- package/todo/COMPLETED.md +53 -0
- package/todo/TODO.md +13 -42
- package/switchbot-homebridge-switchbot-5.0.0.tgz +0 -0
|
@@ -154,8 +154,34 @@ export function renderIRBadge(): HTMLElement {
|
|
|
154
154
|
)
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
function normalizeId(value: any): string {
|
|
158
|
+
return String(value ?? '').trim().toLowerCase()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function scrollToConfiguredDevice(deviceId: string): void {
|
|
162
|
+
const normalizedId = normalizeId(deviceId)
|
|
163
|
+
const target = document.querySelector(`[data-device-id="${normalizedId}"]`) as HTMLElement | null
|
|
164
|
+
if (!target) {
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
169
|
+
const originalOutline = target.style.outline
|
|
170
|
+
const originalBackground = target.style.background
|
|
171
|
+
target.style.outline = '2px solid var(--switchbot-red, #ef4444)'
|
|
172
|
+
target.style.background = 'rgba(239, 68, 68, 0.08)'
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
target.style.outline = originalOutline
|
|
175
|
+
target.style.background = originalBackground
|
|
176
|
+
}, 1800)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function renderDiscoveredDevices(
|
|
180
|
+
devices: any[],
|
|
181
|
+
options: { configuredIds?: Set<string> } = {},
|
|
182
|
+
): Promise<HTMLElement> {
|
|
158
183
|
const ul = document.createElement('ul')
|
|
184
|
+
ul.className = 'device-grid'
|
|
159
185
|
ul.style.maxHeight = '400px'
|
|
160
186
|
ul.style.overflowY = 'auto'
|
|
161
187
|
ul.style.marginTop = '12px'
|
|
@@ -163,35 +189,50 @@ export async function renderDiscoveredDevices(devices: any[]): Promise<HTMLEleme
|
|
|
163
189
|
ul.style.listStyle = 'none'
|
|
164
190
|
|
|
165
191
|
const { addDeviceToConfig } = await import('./discovery.js')
|
|
192
|
+
const { loadConfiguredDevices } = await import('./devices.js')
|
|
193
|
+
const configuredIds = options.configuredIds ?? new Set<string>()
|
|
166
194
|
|
|
167
195
|
for (const d of devices) {
|
|
196
|
+
const deviceId = normalizeId(d.id)
|
|
197
|
+
const alreadyAdded = configuredIds.has(deviceId)
|
|
198
|
+
|
|
168
199
|
const li = document.createElement('li')
|
|
169
200
|
li.className = 'device-item'
|
|
170
201
|
li.style.display = 'flex'
|
|
171
|
-
li.style.
|
|
172
|
-
li.style.
|
|
173
|
-
li.style.
|
|
174
|
-
li.style.
|
|
175
|
-
li.style.
|
|
202
|
+
li.style.flexDirection = 'column'
|
|
203
|
+
li.style.alignItems = 'stretch'
|
|
204
|
+
li.style.justifyContent = 'flex-start'
|
|
205
|
+
li.style.padding = '5px 8px'
|
|
206
|
+
li.style.marginBottom = '0'
|
|
207
|
+
li.style.borderRadius = '5px'
|
|
176
208
|
li.style.transition = 'all 0.2s ease'
|
|
177
209
|
|
|
178
210
|
const info = document.createElement('div')
|
|
179
|
-
info.style.flex = '1'
|
|
211
|
+
info.style.flex = '1 1 auto'
|
|
212
|
+
info.style.width = '100%'
|
|
213
|
+
info.style.minWidth = '0'
|
|
180
214
|
|
|
181
215
|
const nameContainer = document.createElement('div')
|
|
182
216
|
nameContainer.style.display = 'flex'
|
|
183
217
|
nameContainer.style.alignItems = 'center'
|
|
184
|
-
nameContainer.style.marginBottom = '
|
|
218
|
+
nameContainer.style.marginBottom = '0'
|
|
185
219
|
nameContainer.style.flexWrap = 'wrap'
|
|
186
|
-
nameContainer.style.gap = '
|
|
220
|
+
nameContainer.style.gap = '4px'
|
|
187
221
|
|
|
188
222
|
const name = document.createElement('div')
|
|
189
223
|
name.style.fontWeight = '500'
|
|
190
|
-
name.style.fontSize = '
|
|
224
|
+
name.style.fontSize = '13px'
|
|
191
225
|
name.textContent = d.name || d.id
|
|
192
226
|
|
|
193
227
|
nameContainer.appendChild(name)
|
|
194
228
|
|
|
229
|
+
const duplicateBadge = document.createElement('span')
|
|
230
|
+
duplicateBadge.textContent = alreadyAdded ? '✓ Already Added' : '➕ New Device'
|
|
231
|
+
duplicateBadge.style.cssText = alreadyAdded
|
|
232
|
+
? 'background: #16a34a; color: white; padding: 1px 5px; border-radius: 3px; font-size: 9px; font-weight: 600;'
|
|
233
|
+
: 'background: #2563eb; color: white; padding: 1px 5px; border-radius: 3px; font-size: 9px; font-weight: 600;'
|
|
234
|
+
nameContainer.appendChild(duplicateBadge)
|
|
235
|
+
|
|
195
236
|
// Add connection type badge
|
|
196
237
|
if (d.connectionType) {
|
|
197
238
|
const badge = renderConnectionBadge(d.connectionType)
|
|
@@ -210,10 +251,13 @@ export async function renderDiscoveredDevices(devices: any[]): Promise<HTMLEleme
|
|
|
210
251
|
}
|
|
211
252
|
|
|
212
253
|
const details = document.createElement('div')
|
|
213
|
-
details.style.fontSize = '
|
|
254
|
+
details.style.fontSize = '10px'
|
|
214
255
|
details.style.opacity = '0.7'
|
|
215
|
-
details.style.marginTop = '
|
|
256
|
+
details.style.marginTop = '0'
|
|
216
257
|
details.style.fontFamily = 'monospace'
|
|
258
|
+
details.style.whiteSpace = 'normal'
|
|
259
|
+
details.style.overflowWrap = 'anywhere'
|
|
260
|
+
details.style.wordBreak = 'break-word'
|
|
217
261
|
|
|
218
262
|
let detailsText = `ID: ${d.id} | Type: ${d.type} | Model: ${d.model || 'N/A'}`
|
|
219
263
|
if (d.hubDeviceId) { detailsText += ` | Hub: ${d.hubDeviceId}` }
|
|
@@ -224,18 +268,68 @@ export async function renderDiscoveredDevices(devices: any[]): Promise<HTMLEleme
|
|
|
224
268
|
info.appendChild(details)
|
|
225
269
|
|
|
226
270
|
const addBtn = document.createElement('button')
|
|
227
|
-
addBtn.textContent = '
|
|
228
|
-
addBtn.style.marginLeft = '
|
|
229
|
-
addBtn.style.
|
|
230
|
-
addBtn.style.
|
|
271
|
+
addBtn.textContent = alreadyAdded ? 'Already Added' : 'Add to Config'
|
|
272
|
+
addBtn.style.marginLeft = '0'
|
|
273
|
+
addBtn.style.marginTop = '2px'
|
|
274
|
+
addBtn.style.padding = '4px 9px'
|
|
275
|
+
addBtn.style.fontSize = '11px'
|
|
231
276
|
addBtn.style.whiteSpace = 'nowrap'
|
|
232
277
|
addBtn.style.flexShrink = '0'
|
|
278
|
+
addBtn.disabled = alreadyAdded
|
|
279
|
+
if (alreadyAdded) {
|
|
280
|
+
addBtn.style.opacity = '0.65'
|
|
281
|
+
addBtn.style.cursor = 'not-allowed'
|
|
282
|
+
addBtn.style.background = '#6b7280'
|
|
283
|
+
}
|
|
233
284
|
addBtn.onclick = async () => {
|
|
285
|
+
if (alreadyAdded) {
|
|
286
|
+
return
|
|
287
|
+
}
|
|
234
288
|
await addDeviceToConfig(d)
|
|
235
289
|
}
|
|
236
290
|
|
|
291
|
+
if (alreadyAdded) {
|
|
292
|
+
const viewBtn = document.createElement('button')
|
|
293
|
+
viewBtn.textContent = 'View in Config'
|
|
294
|
+
viewBtn.className = 'secondary'
|
|
295
|
+
viewBtn.style.marginLeft = '0'
|
|
296
|
+
viewBtn.style.padding = '4px 9px'
|
|
297
|
+
viewBtn.style.fontSize = '11px'
|
|
298
|
+
viewBtn.onclick = async () => {
|
|
299
|
+
await loadConfiguredDevices()
|
|
300
|
+
scrollToConfiguredDevice(d.id)
|
|
301
|
+
}
|
|
302
|
+
li.appendChild(info)
|
|
303
|
+
const actions = document.createElement('div')
|
|
304
|
+
actions.className = 'device-actions'
|
|
305
|
+
actions.style.display = 'flex'
|
|
306
|
+
actions.style.alignItems = 'center'
|
|
307
|
+
actions.style.flexWrap = 'wrap'
|
|
308
|
+
actions.style.justifyContent = 'flex-start'
|
|
309
|
+
actions.style.marginLeft = '0'
|
|
310
|
+
actions.style.width = '100%'
|
|
311
|
+
actions.style.marginTop = '2px'
|
|
312
|
+
actions.style.gap = '5px'
|
|
313
|
+
actions.appendChild(viewBtn)
|
|
314
|
+
actions.appendChild(addBtn)
|
|
315
|
+
li.appendChild(actions)
|
|
316
|
+
ul.appendChild(li)
|
|
317
|
+
continue
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const actions = document.createElement('div')
|
|
321
|
+
actions.className = 'device-actions'
|
|
322
|
+
actions.style.display = 'flex'
|
|
323
|
+
actions.style.flexWrap = 'wrap'
|
|
324
|
+
actions.style.justifyContent = 'flex-start'
|
|
325
|
+
actions.style.marginLeft = '0'
|
|
326
|
+
actions.style.width = '100%'
|
|
327
|
+
actions.style.marginTop = '2px'
|
|
328
|
+
actions.style.gap = '5px'
|
|
329
|
+
actions.appendChild(addBtn)
|
|
330
|
+
|
|
237
331
|
li.appendChild(info)
|
|
238
|
-
li.appendChild(
|
|
332
|
+
li.appendChild(actions)
|
|
239
333
|
ul.appendChild(li)
|
|
240
334
|
}
|
|
241
335
|
|
|
@@ -406,6 +500,7 @@ export function renderDeviceList(list: any[]): void {
|
|
|
406
500
|
}
|
|
407
501
|
|
|
408
502
|
status.textContent = `Found ${list.length} device(s)`
|
|
503
|
+
ul.classList.add('device-grid')
|
|
409
504
|
ul.innerHTML = ''
|
|
410
505
|
|
|
411
506
|
// Show remove all button when devices exist
|
|
@@ -416,25 +511,37 @@ export function renderDeviceList(list: any[]): void {
|
|
|
416
511
|
for (const d of list) {
|
|
417
512
|
const li = document.createElement('li')
|
|
418
513
|
li.className = 'device-item'
|
|
514
|
+
li.setAttribute('data-device-id', normalizeId(d.id))
|
|
515
|
+
li.style.display = 'flex'
|
|
516
|
+
li.style.flexDirection = 'column'
|
|
517
|
+
li.style.alignItems = 'stretch'
|
|
518
|
+
li.style.marginBottom = '0'
|
|
419
519
|
|
|
420
520
|
const info = document.createElement('div')
|
|
421
|
-
info.style.flex = '1'
|
|
521
|
+
info.style.flex = '1 1 auto'
|
|
522
|
+
info.style.width = '100%'
|
|
523
|
+
info.style.minWidth = '0'
|
|
422
524
|
|
|
423
525
|
const nameContainer = document.createElement('div')
|
|
424
526
|
nameContainer.style.display = 'flex'
|
|
425
|
-
nameContainer.style.
|
|
426
|
-
nameContainer.style.
|
|
427
|
-
nameContainer.style.gap = '
|
|
527
|
+
nameContainer.style.flexDirection = 'column'
|
|
528
|
+
nameContainer.style.alignItems = 'flex-start'
|
|
529
|
+
nameContainer.style.gap = '0'
|
|
428
530
|
|
|
429
531
|
const name = document.createElement('div')
|
|
430
532
|
name.style.fontWeight = '500'
|
|
533
|
+
name.style.fontSize = '13px'
|
|
431
534
|
name.textContent = d.name || d.id
|
|
432
535
|
|
|
433
536
|
const code = document.createElement('code')
|
|
434
537
|
code.textContent = d.id
|
|
435
|
-
code.style.fontSize = '
|
|
538
|
+
code.style.fontSize = '10px'
|
|
436
539
|
code.style.opacity = '0.75'
|
|
437
|
-
code.style.marginLeft = '
|
|
540
|
+
code.style.marginLeft = '0'
|
|
541
|
+
code.style.whiteSpace = 'normal'
|
|
542
|
+
code.style.overflowWrap = 'anywhere'
|
|
543
|
+
code.style.wordBreak = 'break-word'
|
|
544
|
+
code.style.maxWidth = '100%'
|
|
438
545
|
|
|
439
546
|
nameContainer.appendChild(name)
|
|
440
547
|
nameContainer.appendChild(code)
|
|
@@ -447,8 +554,8 @@ export function renderDeviceList(list: any[]): void {
|
|
|
447
554
|
|
|
448
555
|
const meta = document.createElement('div')
|
|
449
556
|
meta.style.opacity = '0.75'
|
|
450
|
-
meta.style.marginTop = '
|
|
451
|
-
meta.style.fontSize = '
|
|
557
|
+
meta.style.marginTop = '0'
|
|
558
|
+
meta.style.fontSize = '11px'
|
|
452
559
|
|
|
453
560
|
const typeText = d.type ? `type: ${d.type}` : ''
|
|
454
561
|
const connText = d.connectionPreference ? `conn: ${d.connectionPreference}` : ''
|
|
@@ -459,13 +566,19 @@ export function renderDeviceList(list: any[]): void {
|
|
|
459
566
|
info.appendChild(meta)
|
|
460
567
|
|
|
461
568
|
const buttons = document.createElement('div')
|
|
569
|
+
buttons.className = 'device-actions'
|
|
462
570
|
buttons.style.display = 'flex'
|
|
463
|
-
buttons.style.
|
|
571
|
+
buttons.style.flexWrap = 'wrap'
|
|
572
|
+
buttons.style.justifyContent = 'flex-start'
|
|
573
|
+
buttons.style.marginLeft = '0'
|
|
574
|
+
buttons.style.width = '100%'
|
|
575
|
+
buttons.style.marginTop = '2px'
|
|
576
|
+
buttons.style.gap = '5px'
|
|
464
577
|
|
|
465
578
|
const editBtn = document.createElement('button')
|
|
466
579
|
editBtn.textContent = '✏️ Edit'
|
|
467
|
-
editBtn.style.padding = '
|
|
468
|
-
editBtn.style.fontSize = '
|
|
580
|
+
editBtn.style.padding = '4px 9px'
|
|
581
|
+
editBtn.style.fontSize = '11px'
|
|
469
582
|
editBtn.onclick = async () => {
|
|
470
583
|
const { editDevice } = await import('./modals.js')
|
|
471
584
|
await editDevice(d)
|
|
@@ -473,8 +586,8 @@ export function renderDeviceList(list: any[]): void {
|
|
|
473
586
|
|
|
474
587
|
const copyBtn = document.createElement('button')
|
|
475
588
|
copyBtn.textContent = 'Copy ID'
|
|
476
|
-
copyBtn.style.padding = '
|
|
477
|
-
copyBtn.style.fontSize = '
|
|
589
|
+
copyBtn.style.padding = '4px 9px'
|
|
590
|
+
copyBtn.style.fontSize = '11px'
|
|
478
591
|
copyBtn.addEventListener('click', async () => {
|
|
479
592
|
try {
|
|
480
593
|
await navigator.clipboard.writeText(d.id)
|
|
@@ -491,8 +604,8 @@ export function renderDeviceList(list: any[]): void {
|
|
|
491
604
|
|
|
492
605
|
const deleteBtn = document.createElement('button')
|
|
493
606
|
deleteBtn.textContent = '🗑️ Delete'
|
|
494
|
-
deleteBtn.style.padding = '
|
|
495
|
-
deleteBtn.style.fontSize = '
|
|
607
|
+
deleteBtn.style.padding = '4px 9px'
|
|
608
|
+
deleteBtn.style.fontSize = '11px'
|
|
496
609
|
deleteBtn.style.background = '#ef4444'
|
|
497
610
|
deleteBtn.onclick = async () => {
|
|
498
611
|
const { deleteDeviceFromConfig } = await import('./devices-delete.js')
|
package/dist/platform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAq/B7D,qBAAa,oBAAoB;IAC/B,GAAG,EAAE,GAAG,GAAG,SAAS,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,qBAAqB,CAAA;IAC7B,OAAO,EAAE,GAAG,EAAE,CAAK;IAEnB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE7B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,oBAAoB,CAA8B;gBAE9C,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,GAAG;IA2C1D,OAAO,CAAC,aAAa;YAUP,qBAAqB;IAU7B,WAAW;IAoKX,kBAAkB,CAAC,SAAS,EAAE,GAAG;IAYvC,wBAAwB,CAAC,CAAC,SAAS,EAAE,GAAG;CASzC;AAGD,qBAAa,uBAAuB;IAClC,GAAG,EAAE,GAAG,GAAG,SAAS,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,qBAAqB,CAAA;IAC7B,OAAO,EAAE,GAAG,EAAE,CAAK;IACnB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE7B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,oBAAoB,CAA8B;gBAE9C,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,GAAG;IA8CpD,WAAW;IAwCjB,OAAO,CAAC,aAAa;YAUP,qBAAqB;IAU7B,kBAAkB,CAAC,SAAS,EAAE,GAAG;IAWvC,wBAAwB,CAAC,SAAS,EAAE,GAAG;IAWjC,yBAAyB;CAqHhC;AAED,eAAe,oBAAoB,CAAA"}
|
package/dist/platform.js
CHANGED
|
@@ -87,7 +87,9 @@ const DEVICE_MATTER_CLUSTERS = {
|
|
|
87
87
|
}, // Switch in HAP, RobotVacuumCleaner in Matter
|
|
88
88
|
curtain: {
|
|
89
89
|
windowCovering: {
|
|
90
|
-
|
|
90
|
+
// Use a lift-only/non-tilt covering type to satisfy Matter conformance rules.
|
|
91
|
+
// Drapery (4) can fail validation in some stacks when TL (tilt) is not present.
|
|
92
|
+
type: 0, // Rollershade
|
|
91
93
|
},
|
|
92
94
|
}, // WindowCovering → WindowCovering (includes curtain3, rollershade variants via normalization)
|
|
93
95
|
blindtilt: {
|
|
@@ -120,12 +122,7 @@ const DEVICE_MATTER_CLUSTERS = {
|
|
|
120
122
|
minLevel: 0,
|
|
121
123
|
maxLevel: 254,
|
|
122
124
|
},
|
|
123
|
-
colorControl: {
|
|
124
|
-
currentHue: 0,
|
|
125
|
-
currentSaturation: 0,
|
|
126
|
-
colorTemperatureMireds: 400,
|
|
127
|
-
colorMode: 0,
|
|
128
|
-
},
|
|
125
|
+
colorControl: {},
|
|
129
126
|
}, // Lightbulb with color → OnOff + LevelControl + ColorControl
|
|
130
127
|
lock: {
|
|
131
128
|
doorLock: {
|
|
@@ -212,6 +209,63 @@ const DEVICE_MATTER_DEVICE_TYPE_KEYS = {
|
|
|
212
209
|
meter: 'TemperatureSensor',
|
|
213
210
|
waterdetector: 'LeakSensor',
|
|
214
211
|
};
|
|
212
|
+
function normalizeTypeForMatter(typeValue) {
|
|
213
|
+
const raw = String(typeValue || '').trim().toLowerCase();
|
|
214
|
+
if (!raw) {
|
|
215
|
+
return 'unknown';
|
|
216
|
+
}
|
|
217
|
+
// Vacuum variants
|
|
218
|
+
if (['wosweeper', 'wosweepermini', 'wosweeperminipro', 'k10+', 'k10+ pro'].includes(raw)) {
|
|
219
|
+
return 'vacuum';
|
|
220
|
+
}
|
|
221
|
+
// Window covering variants
|
|
222
|
+
if (['blindtilt', 'blind tilt', 'curtain3', 'rollershade', 'roller shade', 'worollershade', 'wo rollershade'].includes(raw)) {
|
|
223
|
+
return 'curtain';
|
|
224
|
+
}
|
|
225
|
+
// Plug variants
|
|
226
|
+
if (['plug mini (jp)', 'plug mini (us)', 'plug mini (eu)'].includes(raw)) {
|
|
227
|
+
return 'plug';
|
|
228
|
+
}
|
|
229
|
+
// Meter variants
|
|
230
|
+
if (['meterplus', 'meter plus', 'meter plus (jp)', 'meterpro', 'meter pro', 'meterpro(co2)', 'meter pro (co2)'].includes(raw)) {
|
|
231
|
+
return 'meter';
|
|
232
|
+
}
|
|
233
|
+
// Relay switch variants
|
|
234
|
+
if (['relay switch 1', 'relay switch 1pm'].includes(raw)) {
|
|
235
|
+
return 'relay';
|
|
236
|
+
}
|
|
237
|
+
// Water detector variants
|
|
238
|
+
if (['water detector', 'waterdetector'].includes(raw)) {
|
|
239
|
+
return 'waterdetector';
|
|
240
|
+
}
|
|
241
|
+
// Fan variants
|
|
242
|
+
if (['smart fan', 'circulator fan', 'battery circulator fan', 'standing circulator fan'].includes(raw)) {
|
|
243
|
+
return 'fan';
|
|
244
|
+
}
|
|
245
|
+
// Light variants
|
|
246
|
+
if (['strip light', 'strip light 3', 'rgbic neon rope light', 'rgbic neon wire rope light', 'rgbicww floor lamp', 'rgbicww strip light'].includes(raw)) {
|
|
247
|
+
return 'lightstrip';
|
|
248
|
+
}
|
|
249
|
+
if (['color bulb', 'ceiling light', 'ceiling light pro', 'candle warmer lamp', 'floor lamp'].includes(raw)) {
|
|
250
|
+
return 'light';
|
|
251
|
+
}
|
|
252
|
+
// Sensor variants
|
|
253
|
+
if (raw === 'motion sensor') {
|
|
254
|
+
return 'motion';
|
|
255
|
+
}
|
|
256
|
+
if (['contact sensor', 'presence sensor'].includes(raw)) {
|
|
257
|
+
return 'contact';
|
|
258
|
+
}
|
|
259
|
+
// Lock variants
|
|
260
|
+
if (['smart lock', 'smart lock pro', 'smart lock ultra', 'lock lite', 'keypad', 'keypad touch', 'keypad vision', 'keypad vision pro', 'lock vision pro'].includes(raw)) {
|
|
261
|
+
return 'lock';
|
|
262
|
+
}
|
|
263
|
+
// Climate variant
|
|
264
|
+
if (raw === 'humidifier2') {
|
|
265
|
+
return 'humidifier';
|
|
266
|
+
}
|
|
267
|
+
return raw;
|
|
268
|
+
}
|
|
215
269
|
// Factory function to create Matter handlers with Homebridge logger integration
|
|
216
270
|
function createMatterHandlers(log, deviceId, type, client) {
|
|
217
271
|
const lowerType = type.toLowerCase();
|
|
@@ -1026,40 +1080,7 @@ export class SwitchBotHAPPlatform {
|
|
|
1026
1080
|
type: raw.configDeviceType ?? raw.type ?? raw.deviceType ?? 'unknown',
|
|
1027
1081
|
_raw: raw,
|
|
1028
1082
|
};
|
|
1029
|
-
|
|
1030
|
-
// Normalize device type variants for consistent Matter cluster lookup
|
|
1031
|
-
// Vacuum variants
|
|
1032
|
-
if (['wosweeper', 'wosweepermini', 'wosweeperminipro', 'k10+', 'k10+ pro'].includes((type || '').toLowerCase())) {
|
|
1033
|
-
type = 'vacuum';
|
|
1034
|
-
}
|
|
1035
|
-
// Window covering variants
|
|
1036
|
-
if (['blindtilt', 'blind tilt', 'curtain3', 'rollershade', 'roller shade', 'worollershade', 'wo rollershade'].includes((type || '').toLowerCase())) {
|
|
1037
|
-
type = 'curtain';
|
|
1038
|
-
}
|
|
1039
|
-
// Plug variants
|
|
1040
|
-
if (['plug mini (jp)', 'plug mini (us)'].includes((type || '').toLowerCase())) {
|
|
1041
|
-
type = 'plug';
|
|
1042
|
-
}
|
|
1043
|
-
// Meter variants
|
|
1044
|
-
if (['meterplus', 'meter plus (jp)', 'meterpro', 'meterpro(co2)'].includes((type || '').toLowerCase())) {
|
|
1045
|
-
type = 'meter';
|
|
1046
|
-
}
|
|
1047
|
-
// Relay switch variants
|
|
1048
|
-
if (['relay switch 1', 'relay switch 1pm'].includes((type || '').toLowerCase())) {
|
|
1049
|
-
type = 'relay';
|
|
1050
|
-
}
|
|
1051
|
-
// Water detector variants
|
|
1052
|
-
if (['water detector'].includes((type || '').toLowerCase())) {
|
|
1053
|
-
type = 'waterdetector';
|
|
1054
|
-
}
|
|
1055
|
-
// Fan variants
|
|
1056
|
-
if (['smart fan'].includes((type || '').toLowerCase())) {
|
|
1057
|
-
type = 'fan';
|
|
1058
|
-
}
|
|
1059
|
-
// Light variants
|
|
1060
|
-
if (['strip light'].includes((type || '').toLowerCase())) {
|
|
1061
|
-
type = 'lightstrip';
|
|
1062
|
-
}
|
|
1083
|
+
const type = normalizeTypeForMatter(d.type);
|
|
1063
1084
|
const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()];
|
|
1064
1085
|
// Auto-detect Matter from Homebridge API, allow manual override via config
|
|
1065
1086
|
const matterAvailable = !!(this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.());
|
|
@@ -1312,40 +1333,7 @@ export class SwitchBotMatterPlatform {
|
|
|
1312
1333
|
type: raw.configDeviceType ?? raw.type ?? raw.deviceType ?? 'unknown',
|
|
1313
1334
|
_raw: raw,
|
|
1314
1335
|
};
|
|
1315
|
-
|
|
1316
|
-
// Normalize device type variants for consistent Matter cluster lookup
|
|
1317
|
-
// Vacuum variants
|
|
1318
|
-
if (['wosweeper', 'wosweepermini', 'wosweeperminipro', 'k10+', 'k10+ pro'].includes((type || '').toLowerCase())) {
|
|
1319
|
-
type = 'vacuum';
|
|
1320
|
-
}
|
|
1321
|
-
// Window covering variants
|
|
1322
|
-
if (['blindtilt', 'blind tilt', 'curtain3', 'rollershade', 'roller shade', 'worollershade', 'wo rollershade'].includes((type || '').toLowerCase())) {
|
|
1323
|
-
type = 'curtain';
|
|
1324
|
-
}
|
|
1325
|
-
// Plug variants
|
|
1326
|
-
if (['plug mini (jp)', 'plug mini (us)'].includes((type || '').toLowerCase())) {
|
|
1327
|
-
type = 'plug';
|
|
1328
|
-
}
|
|
1329
|
-
// Meter variants
|
|
1330
|
-
if (['meterplus', 'meter plus (jp)', 'meterpro', 'meterpro(co2)'].includes((type || '').toLowerCase())) {
|
|
1331
|
-
type = 'meter';
|
|
1332
|
-
}
|
|
1333
|
-
// Relay switch variants
|
|
1334
|
-
if (['relay switch 1', 'relay switch 1pm'].includes((type || '').toLowerCase())) {
|
|
1335
|
-
type = 'relay';
|
|
1336
|
-
}
|
|
1337
|
-
// Water detector variants
|
|
1338
|
-
if (['water detector'].includes((type || '').toLowerCase())) {
|
|
1339
|
-
type = 'waterdetector';
|
|
1340
|
-
}
|
|
1341
|
-
// Fan variants
|
|
1342
|
-
if (['smart fan'].includes((type || '').toLowerCase())) {
|
|
1343
|
-
type = 'fan';
|
|
1344
|
-
}
|
|
1345
|
-
// Light variants
|
|
1346
|
-
if (['strip light'].includes((type || '').toLowerCase())) {
|
|
1347
|
-
type = 'lightstrip';
|
|
1348
|
-
}
|
|
1336
|
+
const type = normalizeTypeForMatter(d.type);
|
|
1349
1337
|
const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()];
|
|
1350
1338
|
// Auto-detect Matter from Homebridge API, allow manual override via config
|
|
1351
1339
|
const matterAvailable = this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.();
|
|
@@ -1439,40 +1427,7 @@ export class SwitchBotMatterPlatform {
|
|
|
1439
1427
|
if (!d.id) {
|
|
1440
1428
|
continue;
|
|
1441
1429
|
}
|
|
1442
|
-
|
|
1443
|
-
// Normalize device type variants for consistent Matter cluster lookup
|
|
1444
|
-
// Vacuum variants
|
|
1445
|
-
if (['wosweeper', 'wosweepermini', 'wosweeperminipro', 'k10+', 'k10+ pro'].includes((type || '').toLowerCase())) {
|
|
1446
|
-
type = 'vacuum';
|
|
1447
|
-
}
|
|
1448
|
-
// Window covering variants
|
|
1449
|
-
if (['blindtilt', 'blind tilt', 'curtain3', 'rollershade', 'roller shade', 'worollershade', 'wo rollershade'].includes((type || '').toLowerCase())) {
|
|
1450
|
-
type = 'curtain';
|
|
1451
|
-
}
|
|
1452
|
-
// Plug variants
|
|
1453
|
-
if (['plug mini (jp)', 'plug mini (us)'].includes((type || '').toLowerCase())) {
|
|
1454
|
-
type = 'plug';
|
|
1455
|
-
}
|
|
1456
|
-
// Meter variants
|
|
1457
|
-
if (['meterplus', 'meter plus (jp)', 'meterpro', 'meterpro(co2)'].includes((type || '').toLowerCase())) {
|
|
1458
|
-
type = 'meter';
|
|
1459
|
-
}
|
|
1460
|
-
// Relay switch variants
|
|
1461
|
-
if (['relay switch 1', 'relay switch 1pm'].includes((type || '').toLowerCase())) {
|
|
1462
|
-
type = 'relay';
|
|
1463
|
-
}
|
|
1464
|
-
// Water detector variants
|
|
1465
|
-
if (['water detector'].includes((type || '').toLowerCase())) {
|
|
1466
|
-
type = 'waterdetector';
|
|
1467
|
-
}
|
|
1468
|
-
// Fan variants
|
|
1469
|
-
if (['smart fan'].includes((type || '').toLowerCase())) {
|
|
1470
|
-
type = 'fan';
|
|
1471
|
-
}
|
|
1472
|
-
// Light variants
|
|
1473
|
-
if (['strip light'].includes((type || '').toLowerCase())) {
|
|
1474
|
-
type = 'lightstrip';
|
|
1475
|
-
}
|
|
1430
|
+
const type = normalizeTypeForMatter(d.type);
|
|
1476
1431
|
const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()];
|
|
1477
1432
|
const useMatter = matterEnabled && matterSupported;
|
|
1478
1433
|
if (!useMatter) {
|