@switchbot/homebridge-switchbot 5.0.0-beta.128 → 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 +55 -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/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/devices/genericDevice.ts +64 -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
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>default | @switchbot/homebridge-switchbot</title><meta name="description" content="Documentation for @switchbot/homebridge-switchbot"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@switchbot/homebridge-switchbot</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">default</a></li></ul><h1>Variable default</h1></div><div class="tsd-signature"><span class="tsd-kind-variable">default</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span> <span class="tsd-signature-symbol">=></span> <span class="tsd-signature-type">void</span></div><div class="tsd-type-declaration"><h4>Type Declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures"><li class="tsd-signature" id="__type"><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">void</span></li><li class="tsd-description"><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/OpenWonderLabs/homebridge-switchbot/blob/
|
|
1
|
+
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>default | @switchbot/homebridge-switchbot</title><meta name="description" content="Documentation for @switchbot/homebridge-switchbot"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@switchbot/homebridge-switchbot</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">default</a></li></ul><h1>Variable default</h1></div><div class="tsd-signature"><span class="tsd-kind-variable">default</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span> <span class="tsd-signature-symbol">=></span> <span class="tsd-signature-type">void</span></div><div class="tsd-type-declaration"><h4>Type Declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures"><li class="tsd-signature" id="__type"><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">void</span></li><li class="tsd-description"><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/OpenWonderLabs/homebridge-switchbot/blob/5f565afdb9bcc9ffeda470cd1b77f03cb155fff4/src/index.ts#L12">index.ts:12</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">@switchbot/homebridge-switchbot</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@switchbot/homebridge-switchbot",
|
|
3
3
|
"displayName": "SwitchBot",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "5.0.0-beta.
|
|
5
|
+
"version": "5.0.0-beta.129",
|
|
6
6
|
"description": "The SwitchBot plugin allows you to access your SwitchBot device(s) from HomeKit.",
|
|
7
7
|
"author": "SwitchBot <support@wondertechlabs.com> (https://github.com/SwitchBot)",
|
|
8
8
|
"contributors": [
|
|
@@ -470,15 +470,70 @@ export class CurtainDevice extends GenericDevice {
|
|
|
470
470
|
return undefined
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
+
return {
|
|
499
|
+
id: this.opts.id,
|
|
500
|
+
name: this.opts.name ?? this.opts.type,
|
|
501
|
+
protocol: 'matter',
|
|
502
|
+
clusters: [
|
|
503
|
+
{
|
|
504
|
+
type: 'WindowCovering',
|
|
505
|
+
clusterId: MATTER_CLUSTER_IDS.WindowCovering,
|
|
506
|
+
attributes,
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Only try allowed types for each feature set (per Matter spec Table 8.2)
|
|
513
|
+
// - supportsLift && !supportsTilt: RollerShade (0), NOT Drapery (4)
|
|
514
|
+
// - !supportsLift && supportsTilt: BlindTilt (8)
|
|
515
|
+
// - supportsLift && supportsTilt: RollerShade (0) (or Blind, but 0 is safe)
|
|
516
|
+
// - supportsLift && supportsTilt && typeKey.includes('drapery'): allow Drapery (4)
|
|
517
|
+
// - !supportsLift && !supportsTilt: skip
|
|
518
|
+
interface MatterCombo {
|
|
519
|
+
matterType: number
|
|
520
|
+
supportsLift: boolean
|
|
521
|
+
supportsTilt: boolean
|
|
522
|
+
}
|
|
523
|
+
const combos: MatterCombo[] = []
|
|
524
|
+
if (supportsLift && supportsTilt) {
|
|
525
|
+
// Both supported: allow RollerShade (0), optionally Drapery (4) if typeKey includes 'drapery'
|
|
526
|
+
combos.push({ matterType: 0, supportsLift: true, supportsTilt: true })
|
|
527
|
+
if (typeKey.includes('drapery')) {
|
|
528
|
+
combos.push({ matterType: 4, supportsLift: true, supportsTilt: true })
|
|
529
|
+
}
|
|
530
|
+
} else if (supportsLift && !supportsTilt) {
|
|
531
|
+
// Only lift: allow RollerShade (0)
|
|
532
|
+
combos.push({ matterType: 0, supportsLift: true, supportsTilt: false })
|
|
533
|
+
} else if (!supportsLift && supportsTilt) {
|
|
534
|
+
// Only tilt: allow BlindTilt (8)
|
|
535
|
+
combos.push({ matterType: 8, supportsLift: false, supportsTilt: true })
|
|
536
|
+
}
|
|
482
537
|
let lastError
|
|
483
538
|
for (const combo of combos) {
|
|
484
539
|
try {
|
|
@@ -1276,4 +1331,3 @@ export class WalletFinderDevice extends GenericDevice {}
|
|
|
1276
1331
|
export class WoSweeperDevice extends VacuumDevice {}
|
|
1277
1332
|
export class WoSweeperMiniDevice extends VacuumDevice {}
|
|
1278
1333
|
export class WoSweeperMiniProDevice extends VacuumDevice {}
|
|
1279
|
-
// Add missing closing braces for MeterDevice and WaterDetectorDevice
|
|
@@ -218,7 +218,15 @@ export function registerDiscoveryEndpoint(server: HomebridgePluginUiServer) {
|
|
|
218
218
|
uiLog.info(`GET /discover - Total: ${allDiscovered.length} devices (BLE: ${bleCount}, OpenAPI: ${apiCount}, Both: ${bothCount})`)
|
|
219
219
|
return { success: true, data: allDiscovered }
|
|
220
220
|
} catch (e) {
|
|
221
|
-
|
|
221
|
+
if (e instanceof Error) {
|
|
222
|
+
uiLog.error(`Error in /discover: ${e.message}`)
|
|
223
|
+
if (e.stack) {
|
|
224
|
+
uiLog.error(`[Stack] ${e.stack}`)
|
|
225
|
+
}
|
|
226
|
+
uiLog.error(`[Object]`, e)
|
|
227
|
+
} else {
|
|
228
|
+
uiLog.error(`Error in /discover: ${String(e)}`)
|
|
229
|
+
}
|
|
222
230
|
throw new RequestError(`Failed to discover devices: ${e instanceof Error ? e.message : String(e)}`, e)
|
|
223
231
|
}
|
|
224
232
|
})
|
|
@@ -192,7 +192,7 @@ export async function addDevice(
|
|
|
192
192
|
deviceId: string,
|
|
193
193
|
name: string,
|
|
194
194
|
type: string,
|
|
195
|
-
options?: { address?: string, model?: string, rssi?: number, encryptionKey?: string, keyId?: string },
|
|
195
|
+
options?: { address?: string, model?: string, rssi?: number, encryptionKey?: string, keyId?: string, matterType?: number },
|
|
196
196
|
): Promise<any> {
|
|
197
197
|
const payload: any = { deviceId, name, type }
|
|
198
198
|
if (options?.address) {
|
|
@@ -210,6 +210,9 @@ export async function addDevice(
|
|
|
210
210
|
if (options?.keyId) {
|
|
211
211
|
payload.keyId = options.keyId
|
|
212
212
|
}
|
|
213
|
+
if (options?.matterType !== undefined) {
|
|
214
|
+
payload.matterType = options.matterType
|
|
215
|
+
}
|
|
213
216
|
uiLog.info('Adding device to config:', payload)
|
|
214
217
|
const resp = await homebridge.request('/add-device', payload)
|
|
215
218
|
uiLog.info('Add device response:', resp)
|
|
@@ -234,6 +237,7 @@ export async function updateDevice(
|
|
|
234
237
|
deviceId: string,
|
|
235
238
|
configDeviceName?: string,
|
|
236
239
|
configDeviceType?: string,
|
|
240
|
+
options?: { matterType?: number },
|
|
237
241
|
): Promise<any> {
|
|
238
242
|
const params: any = { deviceId }
|
|
239
243
|
if (configDeviceName) {
|
|
@@ -242,6 +246,9 @@ export async function updateDevice(
|
|
|
242
246
|
if (configDeviceType) {
|
|
243
247
|
params.configDeviceType = configDeviceType
|
|
244
248
|
}
|
|
249
|
+
if (options?.matterType !== undefined) {
|
|
250
|
+
params.matterType = options.matterType
|
|
251
|
+
}
|
|
245
252
|
uiLog.info('[Update Device] Sending update request with params:', params)
|
|
246
253
|
const resp = await homebridge.request('/update-device', params)
|
|
247
254
|
uiLog.info('[Update Device] Update response:', resp)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import { addDevice, fetchDevices, syncParentPluginConfigFromDisk } from './api.js'
|
|
3
2
|
import { uiLog } from './logger.js'
|
|
4
3
|
import { hideBusyUi, showBusyUi } from './modal.js'
|
|
@@ -11,6 +10,7 @@ type ImportDiscoveredDeviceResult = {
|
|
|
11
10
|
address?: string
|
|
12
11
|
encryptionKey?: string
|
|
13
12
|
keyId?: string
|
|
13
|
+
matterType?: number
|
|
14
14
|
} | null
|
|
15
15
|
|
|
16
16
|
export async function addDeviceToConfig(device: any, options: { refresh?: boolean, showStatus?: boolean } = {}): Promise<{ added: boolean }> {
|
|
@@ -32,6 +32,7 @@ export async function addDeviceToConfig(device: any, options: { refresh?: boolea
|
|
|
32
32
|
rssi: device.rssi,
|
|
33
33
|
encryptionKey: importValues.encryptionKey,
|
|
34
34
|
keyId: importValues.keyId,
|
|
35
|
+
matterType: importValues.matterType,
|
|
35
36
|
})
|
|
36
37
|
uiLog.info('Add device response:', resp)
|
|
37
38
|
|
|
@@ -97,7 +98,9 @@ export async function addDeviceToConfig(device: any, options: { refresh?: boolea
|
|
|
97
98
|
|
|
98
99
|
export async function initRemoveAllButton(): Promise<void> {
|
|
99
100
|
const removeAllBtn = document.getElementById('removeAllBtn')
|
|
100
|
-
if (!removeAllBtn) {
|
|
101
|
+
if (!removeAllBtn) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
101
104
|
|
|
102
105
|
removeAllBtn.addEventListener('click', async () => {
|
|
103
106
|
const { deleteAllDevicesFromConfig } = await import('./devices-delete.js')
|
|
@@ -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
|
|