@switchbot/homebridge-switchbot 5.0.0-beta.131 → 5.0.0-beta.133
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/dist/devices/genericDevice.d.ts.map +1 -1
- package/dist/devices/genericDevice.js +25 -153
- package/dist/devices/genericDevice.js.map +1 -1
- package/dist/homebridge-ui/public/js/app.js +1 -54
- package/dist/homebridge-ui/public/js/app.js.map +2 -2
- package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/modals.js +0 -58
- package/dist/homebridge-ui/public/js/modals.js.map +1 -1
- package/dist/homebridge-ui/public/js/modals.ts +0 -63
- package/dist/platform.js +3 -3
- package/dist/platform.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/devices/genericDevice.ts +22 -157
- package/src/homebridge-ui/public/js/modals.ts +0 -63
- package/src/platform.ts +3 -3
|
@@ -433,166 +433,31 @@ export class CurtainDevice extends GenericDevice {
|
|
|
433
433
|
|
|
434
434
|
// Matter-specific descriptor for Curtain (Shade cluster) with conformance type
|
|
435
435
|
async createMatterAccessory(api: any): Promise<any> {
|
|
436
|
-
//
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
try {
|
|
446
|
-
state = await this.getState()
|
|
447
|
-
} catch (e) {
|
|
448
|
-
state = undefined
|
|
449
|
-
}
|
|
450
|
-
if (state) {
|
|
451
|
-
hasLiftAttrs = typeof state.position === 'number'
|
|
452
|
-
hasTiltAttrs = typeof state.tilt === 'number'
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
// For SwitchBot Curtain, always set supportsLift = true
|
|
456
|
-
let supportsLift = hasConfigLift || hasLiftAttrs
|
|
457
|
-
if (typeKey.includes('curtain')) {
|
|
458
|
-
supportsLift = true
|
|
459
|
-
} else if (!supportsLift && (typeKey.includes('rollershade') || typeKey.includes('blindtilt'))) {
|
|
460
|
-
supportsLift = true
|
|
461
|
-
}
|
|
462
|
-
const supportsTilt = hasConfigTilt || hasTiltAttrs || typeKey.includes('blindtilt') || typeKey.includes('blind tilt')
|
|
463
|
-
|
|
464
|
-
// Debug: Log feature detection variables
|
|
465
|
-
this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} feature detection: typeKey='${typeKey}', hasConfigLift=${hasConfigLift}, hasLiftAttrs=${hasLiftAttrs}, hasConfigTilt=${hasConfigTilt}, hasTiltAttrs=${hasTiltAttrs}, supportsLift=${supportsLift}, supportsTilt=${supportsTilt}`)
|
|
466
|
-
|
|
467
|
-
// If neither lift nor tilt is supported, do not register as Matter window covering
|
|
468
|
-
if (!supportsLift && !supportsTilt) {
|
|
469
|
-
this.log.warn(`[Matter] Skipping Matter registration for window covering device '${this.opts.name ?? this.opts.id}' (id: ${this.opts.id}): no lift or tilt support detected.`)
|
|
470
|
-
return undefined
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Allow user to override Matter type via config (opts.matterType)
|
|
474
|
-
if (typeof this.opts.matterType === 'number') {
|
|
475
|
-
const matterType = this.opts.matterType
|
|
476
|
-
this.log.info(`[Matter/Config] Using user-specified Matter type for ${this.opts.name ?? this.opts.id}: matterType=${matterType}`)
|
|
477
|
-
// Build attributes for Matter WindowCovering
|
|
478
|
-
const attributes: Record<string, any> = {
|
|
479
|
-
type: {
|
|
480
|
-
read: async () => {
|
|
481
|
-
this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} returning config-overridden type attribute value:`, matterType)
|
|
482
|
-
return matterType
|
|
483
|
-
},
|
|
484
|
-
write: undefined,
|
|
485
|
-
},
|
|
486
|
-
currentPosition: { read: async () => { const s = await this.getState(); return typeof s.position === 'number' ? s.position : 0 }, write: undefined },
|
|
487
|
-
targetPosition: { read: async () => { const s = await this.getState(); return typeof s.position === 'number' ? s.position : 0 }, write: async (v: any) => this.setState({ position: Number(v) }) },
|
|
488
|
-
}
|
|
489
|
-
if (supportsTilt) {
|
|
490
|
-
attributes.currentTilt = { read: async () => { const s = await this.getState(); return typeof s.tilt === 'number' ? s.tilt : 0 }, write: undefined }
|
|
491
|
-
attributes.targetTilt = { read: async () => { const s = await this.getState(); return typeof s.tilt === 'number' ? s.tilt : 0 }, write: async (v: any) => this.setState({ tilt: Number(v) }) }
|
|
492
|
-
}
|
|
493
|
-
try {
|
|
494
|
-
this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} attributes:`, JSON.stringify(attributes, null, 2))
|
|
495
|
-
} catch (e) {
|
|
496
|
-
this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} attributes: [unserializable]`)
|
|
497
|
-
}
|
|
498
|
-
const windowCoveringCluster = {
|
|
499
|
-
type: 'WindowCovering', // for test compatibility
|
|
500
|
-
matterType, // actual Matter type number
|
|
501
|
-
clusterId: MATTER_CLUSTER_IDS.WindowCovering,
|
|
502
|
-
attributes,
|
|
503
|
-
}
|
|
504
|
-
const clustersArr: any = [windowCoveringCluster]
|
|
505
|
-
// Always set clusters.windowCovering to the WindowCovering cluster by clusterId
|
|
506
|
-
const foundWC = clustersArr.find((c: any) => c && c.clusterId === MATTER_CLUSTER_IDS.WindowCovering)
|
|
507
|
-
clustersArr.windowCovering = foundWC || null
|
|
508
|
-
return {
|
|
509
|
-
id: this.opts.id,
|
|
510
|
-
name: this.opts.name ?? this.opts.type,
|
|
511
|
-
protocol: 'matter',
|
|
512
|
-
clusters: clustersArr,
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Only try allowed types for each feature set (per Matter spec Table 8.2)
|
|
517
|
-
// - supportsLift && !supportsTilt: RollerShade (0), NOT Drapery (4)
|
|
518
|
-
// - !supportsLift && supportsTilt: BlindTilt (8)
|
|
519
|
-
// - supportsLift && supportsTilt: RollerShade (0) (or Blind, but 0 is safe)
|
|
520
|
-
// - supportsLift && supportsTilt && typeKey.includes('drapery'): allow Drapery (4)
|
|
521
|
-
// - !supportsLift && !supportsTilt: skip
|
|
522
|
-
interface MatterCombo {
|
|
523
|
-
matterType: number
|
|
524
|
-
supportsLift: boolean
|
|
525
|
-
supportsTilt: boolean
|
|
436
|
+
// Always use the platform-provided Matter type (e.g., 5 for roller shade)
|
|
437
|
+
const matterType = 5
|
|
438
|
+
const attributes: Record<string, any> = {
|
|
439
|
+
type: {
|
|
440
|
+
read: async () => matterType,
|
|
441
|
+
write: undefined,
|
|
442
|
+
},
|
|
443
|
+
currentPosition: { read: async () => { const s = await this.getState(); return typeof s.position === 'number' ? s.position : 0 }, write: undefined },
|
|
444
|
+
targetPosition: { read: async () => { const s = await this.getState(); return typeof s.position === 'number' ? s.position : 0 }, write: async (v: any) => this.setState({ position: Number(v) }) },
|
|
526
445
|
}
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
combos.push({ matterType: 4, supportsLift: true, supportsTilt: true })
|
|
533
|
-
}
|
|
534
|
-
} else if (supportsLift && !supportsTilt) {
|
|
535
|
-
// Only lift: for SwitchBot Curtain, use Drapery (4) for conformance
|
|
536
|
-
if (typeKey.includes('curtain')) {
|
|
537
|
-
combos.push({ matterType: 4, supportsLift: true, supportsTilt: false })
|
|
538
|
-
} else {
|
|
539
|
-
combos.push({ matterType: 0, supportsLift: true, supportsTilt: false })
|
|
540
|
-
}
|
|
541
|
-
} else if (!supportsLift && supportsTilt) {
|
|
542
|
-
// Only tilt: allow BlindTilt (8)
|
|
543
|
-
combos.push({ matterType: 8, supportsLift: false, supportsTilt: true })
|
|
446
|
+
const windowCoveringCluster = {
|
|
447
|
+
type: 'WindowCovering',
|
|
448
|
+
matterType,
|
|
449
|
+
clusterId: MATTER_CLUSTER_IDS.WindowCovering,
|
|
450
|
+
attributes,
|
|
544
451
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
read: async () => {
|
|
554
|
-
this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} returning type attribute value:`, combo.matterType)
|
|
555
|
-
return combo.matterType
|
|
556
|
-
},
|
|
557
|
-
write: undefined,
|
|
558
|
-
},
|
|
559
|
-
currentPosition: { read: async () => { const s = await this.getState(); return typeof s.position === 'number' ? s.position : 0 }, write: undefined },
|
|
560
|
-
targetPosition: { read: async () => { const s = await this.getState(); return typeof s.position === 'number' ? s.position : 0 }, write: async (v: any) => this.setState({ position: Number(v) }) },
|
|
561
|
-
}
|
|
562
|
-
if (combo.supportsTilt) {
|
|
563
|
-
attributes.currentTilt = { read: async () => { const s = await this.getState(); return typeof s.tilt === 'number' ? s.tilt : 0 }, write: undefined }
|
|
564
|
-
attributes.targetTilt = { read: async () => { const s = await this.getState(); return typeof s.tilt === 'number' ? s.tilt : 0 }, write: async (v: any) => this.setState({ tilt: Number(v) }) }
|
|
565
|
-
}
|
|
566
|
-
// Log the final attributes object
|
|
567
|
-
try {
|
|
568
|
-
this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} attributes:`, JSON.stringify(attributes, null, 2))
|
|
569
|
-
} catch (e) {
|
|
570
|
-
this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} attributes: [unserializable]`)
|
|
571
|
-
}
|
|
572
|
-
// Try to register with this combo
|
|
573
|
-
const windowCoveringCluster = {
|
|
574
|
-
type: 'WindowCovering', // for test compatibility
|
|
575
|
-
matterType: combo.matterType, // actual Matter type number
|
|
576
|
-
clusterId: MATTER_CLUSTER_IDS.WindowCovering,
|
|
577
|
-
attributes,
|
|
578
|
-
}
|
|
579
|
-
const clustersArr: any = [windowCoveringCluster]
|
|
580
|
-
// Always set clusters.windowCovering to the WindowCovering cluster by clusterId
|
|
581
|
-
const foundWC = clustersArr.find((c: any) => c && c.clusterId === MATTER_CLUSTER_IDS.WindowCovering)
|
|
582
|
-
clustersArr.windowCovering = foundWC || null
|
|
583
|
-
return {
|
|
584
|
-
id: this.opts.id,
|
|
585
|
-
name: this.opts.name ?? this.opts.type,
|
|
586
|
-
protocol: 'matter',
|
|
587
|
-
clusters: clustersArr,
|
|
588
|
-
}
|
|
589
|
-
} catch (err) {
|
|
590
|
-
lastError = err
|
|
591
|
-
this.log.warn(`[Matter/Retry] Failed combo matterType=${combo.matterType}, supportsLift=${combo.supportsLift}, supportsTilt=${combo.supportsTilt}: ${err}`)
|
|
592
|
-
}
|
|
452
|
+
const clustersArr: any = [windowCoveringCluster]
|
|
453
|
+
const foundWC = clustersArr.find((c: any) => c && c.clusterId === MATTER_CLUSTER_IDS.WindowCovering)
|
|
454
|
+
clustersArr.windowCovering = foundWC || null
|
|
455
|
+
return {
|
|
456
|
+
id: this.opts.id,
|
|
457
|
+
name: this.opts.name ?? this.opts.type,
|
|
458
|
+
protocol: 'matter',
|
|
459
|
+
clusters: clustersArr,
|
|
593
460
|
}
|
|
594
|
-
// If all combos fail, throw last error
|
|
595
|
-
throw lastError || new Error('All Matter window covering type combos failed')
|
|
596
461
|
}
|
|
597
462
|
}
|
|
598
463
|
|
|
@@ -91,66 +91,6 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
91
91
|
typeSelect.appendChild(optgroup)
|
|
92
92
|
})
|
|
93
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
|
-
|
|
154
94
|
const macLabel = document.createElement('label')
|
|
155
95
|
macLabel.textContent = 'BLE MAC Address (optional)'
|
|
156
96
|
macLabel.style.display = 'block'
|
|
@@ -243,7 +183,6 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
243
183
|
address: macInput.value || undefined,
|
|
244
184
|
encryptionKey: encryptionKeyInput.value || undefined,
|
|
245
185
|
keyId: keyIdInput.value || undefined,
|
|
246
|
-
...(windowCoveringTypes.includes(typeSelect.value) && matterTypeSelect.value !== '' ? { matterType: Number(matterTypeSelect.value) } : {}),
|
|
247
186
|
})
|
|
248
187
|
|
|
249
188
|
div.addEventListener('click', (event) => {
|
|
@@ -258,8 +197,6 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
|
|
|
258
197
|
contentDiv.appendChild(nameInput)
|
|
259
198
|
contentDiv.appendChild(typeLabel)
|
|
260
199
|
contentDiv.appendChild(typeSelect)
|
|
261
|
-
contentDiv.appendChild(matterTypeLabel)
|
|
262
|
-
contentDiv.appendChild(matterTypeSelect)
|
|
263
200
|
contentDiv.appendChild(macLabel)
|
|
264
201
|
contentDiv.appendChild(macInput)
|
|
265
202
|
contentDiv.appendChild(encryptionKeyLabel)
|
package/src/platform.ts
CHANGED
|
@@ -96,9 +96,9 @@ export const DEVICE_MATTER_CLUSTERS: Record<string, any> = {
|
|
|
96
96
|
},
|
|
97
97
|
}, // Switch in HAP, RobotVacuumCleaner in Matter
|
|
98
98
|
curtain: {
|
|
99
|
-
|
|
100
|
-
type:
|
|
101
|
-
},
|
|
99
|
+
windowCovering: {
|
|
100
|
+
type: 5, // GenericWindowCovering - supports lift feature (covers curtain3, rollershade variants via normalization)
|
|
101
|
+
},
|
|
102
102
|
}, // WindowCovering → WindowCovering (includes curtain3, rollershade variants via normalization)
|
|
103
103
|
blindtilt: {
|
|
104
104
|
windowCovering: {
|