@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.
Files changed (33) hide show
  1. package/config.schema.json +11 -0
  2. package/dist/devices/genericDevice.d.ts.map +1 -1
  3. package/dist/devices/genericDevice.js +56 -10
  4. package/dist/devices/genericDevice.js.map +1 -1
  5. package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -1
  6. package/dist/homebridge-ui/endpoints/discovery.js +10 -1
  7. package/dist/homebridge-ui/endpoints/discovery.js.map +1 -1
  8. package/dist/homebridge-ui/public/js/api.d.ts +4 -1
  9. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  10. package/dist/homebridge-ui/public/js/api.js +7 -1
  11. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  12. package/dist/homebridge-ui/public/js/api.ts +8 -1
  13. package/dist/homebridge-ui/public/js/app.js +149 -24
  14. package/dist/homebridge-ui/public/js/app.js.map +2 -2
  15. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
  16. package/dist/homebridge-ui/public/js/devices.js +1 -0
  17. package/dist/homebridge-ui/public/js/devices.js.map +1 -1
  18. package/dist/homebridge-ui/public/js/devices.ts +5 -2
  19. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
  20. package/dist/homebridge-ui/public/js/modals.js +149 -20
  21. package/dist/homebridge-ui/public/js/modals.js.map +1 -1
  22. package/dist/homebridge-ui/public/js/modals.ts +156 -26
  23. package/dist/platform.d.ts.map +1 -1
  24. package/dist/platform.js +0 -4
  25. package/dist/platform.js.map +1 -1
  26. package/docs/variables/default.html +1 -1
  27. package/package.json +1 -1
  28. package/src/devices/genericDevice.ts +65 -10
  29. package/src/homebridge-ui/endpoints/discovery.ts +9 -1
  30. package/src/homebridge-ui/public/js/api.ts +8 -1
  31. package/src/homebridge-ui/public/js/devices.ts +5 -2
  32. package/src/homebridge-ui/public/js/modals.ts +156 -26
  33. 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}`)