@switchbot/homebridge-switchbot 5.1.0-beta.9 → 5.1.1

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 (143) hide show
  1. package/.github/copilot-instructions.md +0 -63
  2. package/.github/scripts/compute-version-override.cjs +51 -0
  3. package/.github/scripts/normalize-changelog-header.cjs +1 -1
  4. package/.github/scripts/npm-version-script-esm-auto.js +1 -1
  5. package/.github/scripts/resolve-version-bump-esm.js +1 -1
  6. package/CHANGELOG.md +32 -9
  7. package/MIGRATION.md +0 -5
  8. package/README.md +0 -25
  9. package/config.schema.json +0 -11
  10. package/dist/{Platform.HAP.d.ts → SwitchBotHAPPlatform.d.ts} +16 -2
  11. package/dist/SwitchBotHAPPlatform.d.ts.map +1 -0
  12. package/dist/{Platform.HAP.js → SwitchBotHAPPlatform.js} +111 -40
  13. package/dist/SwitchBotHAPPlatform.js.map +1 -0
  14. package/dist/{Platform.Matter.d.ts → SwitchBotMatterPlatform.d.ts} +2 -4
  15. package/dist/SwitchBotMatterPlatform.d.ts.map +1 -0
  16. package/dist/{Platform.Matter.js → SwitchBotMatterPlatform.js} +6 -62
  17. package/dist/SwitchBotMatterPlatform.js.map +1 -0
  18. package/dist/device-types.d.ts.map +1 -1
  19. package/dist/device-types.js +10 -0
  20. package/dist/device-types.js.map +1 -1
  21. package/dist/deviceFactory.d.ts.map +1 -1
  22. package/dist/deviceFactory.js +10 -16
  23. package/dist/deviceFactory.js.map +1 -1
  24. package/dist/devices/genericDevice.d.ts +63 -5
  25. package/dist/devices/genericDevice.d.ts.map +1 -1
  26. package/dist/devices/genericDevice.js +251 -11
  27. package/dist/devices/genericDevice.js.map +1 -1
  28. package/dist/homebridge-ui/device-types.js +10 -0
  29. package/dist/homebridge-ui/device-types.js.map +1 -1
  30. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -1
  31. package/dist/homebridge-ui/endpoints/config.js +2 -9
  32. package/dist/homebridge-ui/endpoints/config.js.map +1 -1
  33. package/dist/homebridge-ui/homebridge-ui/endpoints/config.js +2 -9
  34. package/dist/homebridge-ui/homebridge-ui/endpoints/config.js.map +1 -1
  35. package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js +0 -8
  36. package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js.map +1 -1
  37. package/dist/homebridge-ui/public/css/styles.css +1 -61
  38. package/dist/homebridge-ui/public/index.html +2 -61
  39. package/dist/homebridge-ui/public/js/advanced-settings.d.ts.map +1 -1
  40. package/dist/homebridge-ui/public/js/advanced-settings.js +3 -4
  41. package/dist/homebridge-ui/public/js/advanced-settings.js.map +1 -1
  42. package/dist/homebridge-ui/public/js/advanced-settings.ts +3 -4
  43. package/dist/homebridge-ui/public/js/api.d.ts +0 -16
  44. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  45. package/dist/homebridge-ui/public/js/api.js +0 -48
  46. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  47. package/dist/homebridge-ui/public/js/api.ts +1 -61
  48. package/dist/homebridge-ui/public/js/app.js +135 -272
  49. package/dist/homebridge-ui/public/js/app.js.map +4 -4
  50. package/dist/homebridge-ui/public/js/app.ts +4 -47
  51. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -1
  52. package/dist/homebridge-ui/public/js/credentials.js +5 -11
  53. package/dist/homebridge-ui/public/js/credentials.js.map +1 -1
  54. package/dist/homebridge-ui/public/js/credentials.ts +5 -11
  55. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
  56. package/dist/homebridge-ui/public/js/devices.js +0 -6
  57. package/dist/homebridge-ui/public/js/devices.js.map +1 -1
  58. package/dist/homebridge-ui/public/js/devices.ts +0 -6
  59. package/dist/homebridge-ui/public/js/modals.d.ts +0 -1
  60. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
  61. package/dist/homebridge-ui/public/js/modals.js +0 -53
  62. package/dist/homebridge-ui/public/js/modals.js.map +1 -1
  63. package/dist/homebridge-ui/public/js/modals.ts +0 -58
  64. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
  65. package/dist/homebridge-ui/public/js/render.js +1 -2
  66. package/dist/homebridge-ui/public/js/render.js.map +1 -1
  67. package/dist/homebridge-ui/public/js/render.ts +1 -2
  68. package/dist/homebridge-ui/settings.js.map +1 -1
  69. package/dist/homebridge-ui/utils/config-parser.d.ts +0 -8
  70. package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -1
  71. package/dist/homebridge-ui/utils/config-parser.js +0 -8
  72. package/dist/homebridge-ui/utils/config-parser.js.map +1 -1
  73. package/dist/index.js +2 -2
  74. package/dist/index.js.map +1 -1
  75. package/dist/settings.d.ts +0 -11
  76. package/dist/settings.d.ts.map +1 -1
  77. package/dist/settings.js.map +1 -1
  78. package/dist/utils.d.ts +0 -5
  79. package/dist/utils.d.ts.map +1 -1
  80. package/dist/utils.js +0 -27
  81. package/dist/utils.js.map +1 -1
  82. package/docs/assets/highlight.css +16 -16
  83. package/docs/assets/main.js +1 -1
  84. package/docs/assets/style.css +18 -3
  85. package/docs/hierarchy.html +26 -1
  86. package/docs/index.html +70 -23
  87. package/docs/modules.html +33 -1
  88. package/docs/variables/default.html +46 -3
  89. package/package.json +22 -15
  90. package/scripts/build-ui.js +0 -30
  91. package/src/{Platform.HAP.ts → SwitchBotHAPPlatform.ts} +112 -39
  92. package/src/{Platform.Matter.ts → SwitchBotMatterPlatform.ts} +8 -63
  93. package/src/device-types.ts +11 -0
  94. package/src/deviceFactory.ts +10 -15
  95. package/src/devices/genericDevice.ts +271 -12
  96. package/src/homebridge-ui/endpoints/config.ts +2 -10
  97. package/src/homebridge-ui/public/css/styles.css +1 -61
  98. package/src/homebridge-ui/public/index.html +0 -59
  99. package/src/homebridge-ui/public/js/advanced-settings.ts +3 -4
  100. package/src/homebridge-ui/public/js/api.ts +1 -61
  101. package/src/homebridge-ui/public/js/app.ts +4 -47
  102. package/src/homebridge-ui/public/js/credentials.ts +5 -11
  103. package/src/homebridge-ui/public/js/devices.ts +0 -6
  104. package/src/homebridge-ui/public/js/modals.ts +0 -58
  105. package/src/homebridge-ui/public/js/render.ts +1 -2
  106. package/src/homebridge-ui/utils/config-parser.ts +0 -9
  107. package/src/index.ts +2 -2
  108. package/src/settings.ts +0 -12
  109. package/src/utils.ts +0 -31
  110. package/test/device/deviceFactory.spec.ts +9 -0
  111. package/test/device/getstate-cache.spec.ts +62 -0
  112. package/test/device/getstate-returns-status.spec.ts +89 -0
  113. package/test/device/model-identities.spec.ts +45 -0
  114. package/test/device/water-detector-battery.spec.ts +81 -0
  115. package/test/device/water-detector-leak.spec.ts +43 -0
  116. package/test/hap-push-updates.spec.ts +150 -0
  117. package/test/platform/accessory-restore.spec.ts +2 -2
  118. package/test/platform/matter-childbridge.spec.ts +1 -1
  119. package/test/platform/matter-integration.spec.ts +1 -51
  120. package/test/platform/platform-edge.spec.ts +2 -2
  121. package/test/platform/platform.integration.spec.ts +1 -1
  122. package/test/utils/utils.spec.ts +0 -15
  123. package/.github/npm-version-script-esm.js +0 -97
  124. package/dist/Platform.HAP.d.ts.map +0 -1
  125. package/dist/Platform.HAP.js.map +0 -1
  126. package/dist/Platform.Matter.d.ts.map +0 -1
  127. package/dist/Platform.Matter.js.map +0 -1
  128. package/dist/homebridge-ui/homebridge-ui/utils/v4-detection.js +0 -23
  129. package/dist/homebridge-ui/homebridge-ui/utils/v4-detection.js.map +0 -1
  130. package/dist/homebridge-ui/public/js/legacy-view.d.ts +0 -3
  131. package/dist/homebridge-ui/public/js/legacy-view.d.ts.map +0 -1
  132. package/dist/homebridge-ui/public/js/legacy-view.js +0 -45
  133. package/dist/homebridge-ui/public/js/legacy-view.js.map +0 -1
  134. package/dist/homebridge-ui/public/js/legacy-view.ts +0 -107
  135. package/dist/homebridge-ui/utils/v4-detection.d.ts +0 -12
  136. package/dist/homebridge-ui/utils/v4-detection.d.ts.map +0 -1
  137. package/dist/homebridge-ui/utils/v4-detection.js +0 -23
  138. package/dist/homebridge-ui/utils/v4-detection.js.map +0 -1
  139. package/src/device-types.js +0 -246
  140. package/src/homebridge-ui/public/js/legacy-view.js +0 -45
  141. package/src/homebridge-ui/public/js/legacy-view.ts +0 -107
  142. package/src/homebridge-ui/utils/v4-detection.ts +0 -23
  143. package/test/homebridge-ui/config-parser.spec.ts +0 -61
@@ -1,107 +0,0 @@
1
- // legacy-view.ts
2
- // Renders the config UI from config.schema.json and toggles between legacy and dynamic UI
3
-
4
- import schema from '../../../../config.schema.json'
5
-
6
- function renderLegacyView() {
7
- const container = document.getElementById('legacyConfigForm')
8
- if (!container) {
9
- const legacyContainer = document.getElementById('legacyViewContainer')
10
- if (legacyContainer) {
11
- legacyContainer.innerHTML = '<div style="color:red;font-weight:bold">[SwitchBot UI] Error: legacyConfigForm container not found</div>'
12
- }
13
- console.error('[SwitchBot UI] Error: legacyConfigForm container not found')
14
- return
15
- }
16
- container.innerHTML = ''
17
- const { properties } = schema.schema
18
- const layout = schema.layout || []
19
- const form = document.createElement('form')
20
- for (const section of layout) {
21
- const fieldset = document.createElement('fieldset')
22
- if (section.title) {
23
- const legend = document.createElement('legend')
24
- legend.textContent = section.title
25
- fieldset.appendChild(legend)
26
- }
27
- for (const key of section.items) {
28
- const prop = properties[key]
29
- if (!prop) {
30
- continue
31
- }
32
- if (prop.type === 'array' && prop.items && prop.items.properties) {
33
- // Render array of devices as a table
34
- const table = document.createElement('table')
35
- table.style.marginBottom = '12px'
36
- const thead = document.createElement('thead')
37
- const headerRow = document.createElement('tr')
38
- for (const colKey of Object.keys(prop.items.properties)) {
39
- const th = document.createElement('th')
40
- th.textContent = prop.items.properties[colKey].title || colKey
41
- headerRow.appendChild(th)
42
- }
43
- thead.appendChild(headerRow)
44
- table.appendChild(thead)
45
- // No data rows (read-only legacy view)
46
- fieldset.appendChild(table)
47
- continue
48
- }
49
- const label = document.createElement('label')
50
- label.textContent = prop.title || key
51
- label.style.display = 'block'
52
- let input: HTMLElement
53
- if (prop.enum) {
54
- const select = document.createElement('select')
55
- select.name = key
56
- for (const opt of prop.enum) {
57
- const option = document.createElement('option')
58
- option.value = opt
59
- option.textContent = opt
60
- select.appendChild(option)
61
- }
62
- input = select
63
- } else if (prop.type === 'boolean') {
64
- const checkbox = document.createElement('input')
65
- checkbox.type = 'checkbox'
66
- checkbox.name = key
67
- if (prop.default) {
68
- checkbox.checked = true
69
- }
70
- input = checkbox
71
- } else {
72
- const text = document.createElement('input')
73
- text.name = key
74
- text.type = prop.format === 'password' ? 'password' : 'text'
75
- if (prop.default) {
76
- text.value = prop.default
77
- }
78
- input = text
79
- }
80
- label.appendChild(input)
81
- fieldset.appendChild(label)
82
- }
83
- form.appendChild(fieldset)
84
- }
85
- container.appendChild(form)
86
- }
87
-
88
- export function enableLegacyView() {
89
- document.getElementById('dynamicUiContainer')!.style.display = 'none'
90
- document.getElementById('legacyViewContainer')!.style.display = 'block'
91
- renderLegacyView()
92
- }
93
-
94
- // DEBUG: assign to window for browser access
95
- // eslint-disable-next-line ts/ban-ts-comment
96
- // @ts-ignore
97
- window.enableLegacyView = enableLegacyView
98
- // eslint-disable-next-line ts/ban-ts-comment
99
- // @ts-ignore
100
- window.disableLegacyView = disableLegacyView
101
-
102
- export function disableLegacyView() {
103
- document.getElementById('dynamicUiContainer')!.style.display = 'block'
104
- document.getElementById('legacyViewContainer')!.style.display = 'none'
105
- }
106
-
107
- // (window assignment removed; handled in .js entry point)
@@ -1,12 +0,0 @@
1
- /**
2
- * Detect whether a platform config block is in the legacy v4 format.
3
- *
4
- * v4 configs stored credentials under a `credentials` sub-object and devices
5
- * under `options.devices`. v5+ flattened these to `openApiToken`,
6
- * `openApiSecret`, and a root-level `devices` array.
7
- *
8
- * This helper has no Node.js-specific dependencies so it can be imported from
9
- * both the Homebridge server-side code and the browser-side plugin UI bundle.
10
- */
11
- export declare function isV4Config(platform: any): boolean;
12
- //# sourceMappingURL=v4-detection.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"v4-detection.d.ts","sourceRoot":"","sources":["../../../src/homebridge-ui/utils/v4-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO,CAYjD"}
@@ -1,23 +0,0 @@
1
- /**
2
- * Detect whether a platform config block is in the legacy v4 format.
3
- *
4
- * v4 configs stored credentials under a `credentials` sub-object and devices
5
- * under `options.devices`. v5+ flattened these to `openApiToken`,
6
- * `openApiSecret`, and a root-level `devices` array.
7
- *
8
- * This helper has no Node.js-specific dependencies so it can be imported from
9
- * both the Homebridge server-side code and the browser-side plugin UI bundle.
10
- */
11
- export function isV4Config(platform) {
12
- if (!platform || typeof platform !== 'object') {
13
- return false;
14
- }
15
- // Presence of a `credentials` sub-object is the clearest v4 signal
16
- const hasCredentials = platform.credentials !== undefined && typeof platform.credentials === 'object';
17
- // Presence of `options.devices` is the other v4 signal
18
- const hasOptionsDevices = platform.options !== undefined
19
- && typeof platform.options === 'object'
20
- && Array.isArray(platform.options.devices);
21
- return hasCredentials || hasOptionsDevices;
22
- }
23
- //# sourceMappingURL=v4-detection.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"v4-detection.js","sourceRoot":"","sources":["../../../src/homebridge-ui/utils/v4-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,QAAa;IACtC,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAA;IACd,CAAC;IACD,mEAAmE;IACnE,MAAM,cAAc,GAAG,QAAQ,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAA;IACrG,uDAAuD;IACvD,MAAM,iBAAiB,GACnB,QAAQ,CAAC,OAAO,KAAK,SAAS;WAC7B,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ;WACpC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5C,OAAO,cAAc,IAAI,iBAAiB,CAAA;AAC5C,CAAC"}
@@ -1,246 +0,0 @@
1
- export const DEVICE_TYPES = {
2
- 'Window Coverings': ['Blind Tilt', 'Curtain', 'Curtain3', 'Roller Shade'],
3
- 'Locks & Access': [
4
- 'Keypad',
5
- 'Keypad Touch',
6
- 'Keypad Vision',
7
- 'Keypad Vision Pro',
8
- 'Lock Vision Pro',
9
- 'Lock Lite',
10
- 'Smart Lock',
11
- 'Smart Lock Pro',
12
- 'Smart Lock Ultra',
13
- 'Video Doorbell',
14
- ],
15
- 'Sensors': ['Contact Sensor', 'Motion Sensor', 'Presence Sensor', 'Water Detector'],
16
- 'Lighting': [
17
- 'Candle Warmer Lamp',
18
- 'Ceiling Light',
19
- 'Ceiling Light Pro',
20
- 'Color Bulb',
21
- 'Floor Lamp',
22
- 'RGBIC Neon Rope Light',
23
- 'RGBIC Neon Wire Rope Light',
24
- 'RGBICWW Floor Lamp',
25
- 'RGBICWW Strip Light',
26
- 'Strip Light',
27
- 'Strip Light 3',
28
- ],
29
- 'Climate Control': [
30
- 'Air Purifier PM2.5',
31
- 'Air Purifier Table PM2.5',
32
- 'Air Purifier VOC',
33
- 'Air Purifier Table VOC',
34
- 'Battery Circulator Fan',
35
- 'Circulator Fan',
36
- 'Humidifier',
37
- 'Humidifier2',
38
- 'Meter',
39
- 'MeterPlus',
40
- 'Meter Plus',
41
- 'MeterPro',
42
- 'Meter Pro',
43
- 'MeterPro(CO2)',
44
- 'Meter Pro (CO2)',
45
- 'Smart Radiator Thermostat',
46
- 'Standing Circulator Fan',
47
- 'WoIOSensor',
48
- ],
49
- 'Plugs & Switches': [
50
- 'Garage Door Opener',
51
- 'Plug',
52
- 'Plug Mini (EU)',
53
- 'Plug Mini (JP)',
54
- 'Plug Mini (US)',
55
- 'Relay Switch 1',
56
- 'Relay Switch 1PM',
57
- 'Relay Switch 2PM',
58
- ],
59
- 'Robot Vacuums': [
60
- 'K10+',
61
- 'K10+ Pro',
62
- 'Robot Vacuum Cleaner K10+ Pro Combo',
63
- 'Robot Vacuum Cleaner K11+',
64
- 'Robot Vacuum Cleaner K20 Plus Pro',
65
- 'Robot Vacuum Cleaner S1',
66
- 'Robot Vacuum Cleaner S1 Plus',
67
- 'Robot Vacuum Cleaner S10',
68
- 'Robot Vacuum Cleaner S20',
69
- ],
70
- 'Hubs': ['AI Hub', 'Hub', 'Hub 2', 'Hub 3', 'Hub Mini', 'Hub Plus'],
71
- 'Cameras': [
72
- 'Indoor Cam',
73
- 'Pan/Tilt Cam',
74
- 'Pan/Tilt Cam 2K',
75
- 'Pan/Tilt Cam Plus 2K',
76
- 'Pan/Tilt Cam Plus 3K',
77
- ],
78
- 'IR Devices': [
79
- 'Air Conditioner',
80
- 'Air Purifier',
81
- 'Camera',
82
- 'DVD',
83
- 'Fan',
84
- 'Light',
85
- 'Others',
86
- 'Projector',
87
- 'Set Top Box',
88
- 'Speaker',
89
- 'Streamer',
90
- 'TV',
91
- 'Vacuum Cleaner',
92
- 'Water Heater',
93
- ],
94
- 'Other Devices': ['AI Art Frame', 'Bot', 'Home Climate Panel', 'Remote', 'remote with screen'],
95
- };
96
- export const DEVICE_TYPE_NORMALIZATION_MAP = {
97
- // --- node-switchbot v4 normalization additions ---
98
- 'hub mini': 'Hub Mini',
99
- 'hub 3': 'Hub 3',
100
- 'keypad': 'Keypad',
101
- 'plug mini': 'Plug Mini (US)', // fallback to US if region not specified
102
- 'art frame': 'AI Art Frame',
103
- 'rgbicww': 'RGBICWW Strip Light',
104
- 'lock vision': 'Lock Vision Pro', // alias for new lock vision
105
- 'lock pro': 'Smart Lock Pro',
106
- 'lock lite': 'Lock Lite',
107
- 'circulator fan': 'Circulator Fan',
108
- 'smart thermostat radiator': 'Smart Radiator Thermostat',
109
- 'climate panel': 'Home Climate Panel',
110
- 'evaporative humidifier': 'Humidifier',
111
- // --- end node-switchbot v4 additions ---
112
- // Only keep the last occurrence for each key, all values canonical
113
- 'air purifier pm2.5': 'Air Purifier PM2.5',
114
- 'pan/tilt cam plus 3k': 'Pan/Tilt Cam Plus 3K',
115
- 'remote with screen': 'Remote with Screen',
116
- 'ai hub': 'AI Hub',
117
- 'water detector': 'Water Detector',
118
- 'video doorbell': 'Video Doorbell',
119
- 'smart radiator thermostat': 'Smart Radiator Thermostat',
120
- 'woiosensor': 'WoIOSensor',
121
- 'garage door opener': 'Garage Door Opener',
122
- 'air purifier table pm2.5': 'Air Purifier Table PM2.5',
123
- 'air purifier voc': 'Air Purifier VOC',
124
- 'air purifier table voc': 'Air Purifier Table VOC',
125
- 'plug mini (eu)': 'Plug Mini (EU)',
126
- // Only last occurrence for each key is kept above. Removed duplicates here.
127
- // Climate control conversions
128
- 'humidifier2': 'Humidifier2',
129
- 'battery circulator fan': 'Battery Circulator Fan',
130
- 'standing circulator fan': 'Standing Circulator Fan',
131
- // Lock/keypad conversions
132
- 'smart lock': 'Smart Lock',
133
- 'smart lock pro': 'Smart Lock Pro',
134
- 'smart lock ultra': 'Smart Lock Ultra',
135
- 'keypad touch': 'Keypad Touch',
136
- 'keypad vision': 'Keypad Vision',
137
- 'keypad vision pro': 'Keypad Vision Pro',
138
- // Light conversions
139
- 'color bulb': 'Color Bulb',
140
- 'ceiling light': 'Ceiling Light',
141
- 'ceiling light pro': 'Ceiling Light Pro',
142
- 'candle warmer lamp': 'Candle Warmer Lamp',
143
- 'floor lamp': 'Floor Lamp',
144
- 'rgbic neon rope light': 'RGBIC Neon Rope Light',
145
- 'rgbic neon wire rope light': 'RGBIC Neon Wire Rope Light',
146
- 'rgbicww floor lamp': 'RGBICWW Floor Lamp',
147
- 'rgbicww strip light': 'RGBICWW Strip Light',
148
- 'strip light': 'Strip Light',
149
- 'strip light 3': 'Strip Light 3',
150
- // Vacuum conversions
151
- 'robot vacuum cleaner s1': 'Robot Vacuum Cleaner S1',
152
- 'robot vacuum cleaner s1 plus': 'Robot Vacuum Cleaner S1 Plus',
153
- 'robot vacuum cleaner s10': 'Robot Vacuum Cleaner S10',
154
- 'robot vacuum cleaner s20': 'Robot Vacuum Cleaner S20',
155
- 'robot vacuum cleaner k10+ pro combo': 'Robot Vacuum Cleaner K10+ Pro Combo',
156
- 'robot vacuum cleaner k11+': 'Robot Vacuum Cleaner K11+',
157
- 'robot vacuum cleaner k20 plus pro': 'Robot Vacuum Cleaner K20 Plus Pro',
158
- // Exact device type mappings (API format → canonical format)
159
- 'relay switch 1': 'Relay Switch 1',
160
- 'blind tilt': 'Blind Tilt',
161
- 'roller shade': 'Roller Shade',
162
- 'curtain3': 'Curtain3',
163
- 'hub 2': 'Hub 2',
164
- 'meterplus': 'MeterPlus',
165
- 'meterpro': 'MeterPro',
166
- 'meterpro(co2)': 'MeterPro(CO2)',
167
- 'walletfinder': 'WalletFinder',
168
- 'k10+': 'K10+',
169
- 'k10+ pro (wosweeperminipro)': 'K10+ Pro (wosweeperminipro)',
170
- // Handle spaced variants from config files (normalize back to canonical type)
171
- 'meter pro': 'Meter Pro',
172
- 'meter pro (co2)': 'Meter Pro (CO2)',
173
- 'meter plus': 'Meter Plus',
174
- 'relay switch 1 pm': 'Relay Switch 1PM',
175
- 'relay switch 2 pm': 'Relay Switch 2PM',
176
- 'plug mini eu': 'Plug Mini (EU)',
177
- 'plug mini jp': 'Plug Mini (JP)',
178
- 'plug mini us': 'Plug Mini (US)',
179
- // Migration mappings for invalid/legacy device types
180
- 'lock vision pro': 'Lock Vision Pro', // Valid alias; map to canonical
181
- // 'lock vision': 'Keypad Vision', // Invalid type (removed, now alias above)
182
- 'lock touch': 'Keypad Touch', // Invalid type
183
- // Additional normalization for new/unknown types from logs
184
- 'woplugus': 'Plug Mini (US)',
185
- // Removed duplicate keys below, only last occurrence kept
186
- // 'plug mini us': 'plug mini (us)', // duplicate, removed
187
- // 'plug us': 'plug mini (us)', // duplicate, removed
188
- // 'plug': 'plug', // duplicate, removed
189
- // 'air purifier pm2.5': 'air purifier pm2.5', // duplicate, removed
190
- // 'rgbic neon wire rope light': 'rgbic neon wire rope light', // duplicate, removed
191
- // 'candle warmer lamp': 'candle warmer lamp', // duplicate, removed
192
- // 'pan/tilt cam plus 3k': 'pan/tilt cam plus 3k', // duplicate, removed
193
- // 'remote with screen': 'remote with screen', // duplicate, removed
194
- // 'ai hub': 'ai hub', // duplicate, removed
195
- // 'lock vision pro': 'lock vision pro', // duplicate, remove this line
196
- // Add any other device types from logs as needed
197
- };
198
- /**
199
- * Get all valid device types as a flat set for validation
200
- */
201
- export function getValidDeviceTypes() {
202
- const validTypes = new Set();
203
- for (const category of Object.values(DEVICE_TYPES)) {
204
- for (const type of category) {
205
- validTypes.add(type);
206
- }
207
- }
208
- return validTypes;
209
- }
210
- /**
211
- * Normalize and validate a device type, returning a valid type or null
212
- * @param deviceType The device type to validate/normalize
213
- * @returns Valid device type string or null if no valid mapping found
214
- */
215
- export function normalizeDeviceType(deviceType) {
216
- if (!deviceType || typeof deviceType !== 'string') {
217
- return null;
218
- }
219
- const trimmed = deviceType.trim();
220
- const lowercase = trimmed.toLowerCase();
221
- // Check if already valid
222
- const validTypes = getValidDeviceTypes();
223
- if (validTypes.has(trimmed)) {
224
- return trimmed;
225
- }
226
- // Check normalization map
227
- const normalized = DEVICE_TYPE_NORMALIZATION_MAP[lowercase];
228
- if (normalized && validTypes.has(normalized)) {
229
- return normalized;
230
- }
231
- // No valid mapping found
232
- return null;
233
- }
234
- /**
235
- * Check if a device type is valid
236
- * @param deviceType The device type to check
237
- * @returns true if device type is in DEVICE_TYPES
238
- */
239
- export function isValidDeviceType(deviceType) {
240
- if (!deviceType || typeof deviceType !== 'string') {
241
- return false;
242
- }
243
- const validTypes = getValidDeviceTypes();
244
- return validTypes.has(deviceType.trim());
245
- }
246
- //# sourceMappingURL=device-types.js.map
@@ -1,45 +0,0 @@
1
- import { disableLegacyView, enableLegacyView } from './legacy-view.js'
2
-
3
- // Toggle button visibility and view containers
4
- function updateViewButtons() {
5
- const legacyBtn = document.getElementById('legacyViewBtn')
6
- const dynamicBtn = document.getElementById('dynamicViewBtn')
7
- const legacyContainer = document.getElementById('legacyViewContainer')
8
- if (legacyContainer && legacyContainer.style.display === 'block') {
9
- if (legacyBtn) {
10
- legacyBtn.style.display = 'none'
11
- }
12
- if (dynamicBtn) {
13
- dynamicBtn.style.display = 'inline-block'
14
- }
15
- } else {
16
- if (legacyBtn) {
17
- legacyBtn.style.display = 'inline-block'
18
- }
19
- if (dynamicBtn) {
20
- dynamicBtn.style.display = 'none'
21
- }
22
- }
23
- }
24
-
25
- document.addEventListener('DOMContentLoaded', () => {
26
- const legacyBtn = document.getElementById('legacyViewBtn')
27
- const dynamicBtn = document.getElementById('dynamicViewBtn')
28
- if (legacyBtn) {
29
- legacyBtn.addEventListener('click', () => {
30
- // eslint-disable-next-line no-console
31
- console.log('[SwitchBot UI] Legacy View button clicked')
32
- enableLegacyView()
33
- updateViewButtons()
34
- })
35
- }
36
- if (dynamicBtn) {
37
- dynamicBtn.addEventListener('click', () => {
38
- // eslint-disable-next-line no-console
39
- console.log('[SwitchBot UI] Dynamic View button clicked')
40
- disableLegacyView()
41
- updateViewButtons()
42
- })
43
- }
44
- updateViewButtons()
45
- })
@@ -1,107 +0,0 @@
1
- // legacy-view.ts
2
- // Renders the config UI from config.schema.json and toggles between legacy and dynamic UI
3
-
4
- import schema from '../../../../config.schema.json'
5
-
6
- function renderLegacyView() {
7
- const container = document.getElementById('legacyConfigForm')
8
- if (!container) {
9
- const legacyContainer = document.getElementById('legacyViewContainer')
10
- if (legacyContainer) {
11
- legacyContainer.innerHTML = '<div style="color:red;font-weight:bold">[SwitchBot UI] Error: legacyConfigForm container not found</div>'
12
- }
13
- console.error('[SwitchBot UI] Error: legacyConfigForm container not found')
14
- return
15
- }
16
- container.innerHTML = ''
17
- const { properties } = schema.schema
18
- const layout = schema.layout || []
19
- const form = document.createElement('form')
20
- for (const section of layout) {
21
- const fieldset = document.createElement('fieldset')
22
- if (section.title) {
23
- const legend = document.createElement('legend')
24
- legend.textContent = section.title
25
- fieldset.appendChild(legend)
26
- }
27
- for (const key of section.items) {
28
- const prop = properties[key]
29
- if (!prop) {
30
- continue
31
- }
32
- if (prop.type === 'array' && prop.items && prop.items.properties) {
33
- // Render array of devices as a table
34
- const table = document.createElement('table')
35
- table.style.marginBottom = '12px'
36
- const thead = document.createElement('thead')
37
- const headerRow = document.createElement('tr')
38
- for (const colKey of Object.keys(prop.items.properties)) {
39
- const th = document.createElement('th')
40
- th.textContent = prop.items.properties[colKey].title || colKey
41
- headerRow.appendChild(th)
42
- }
43
- thead.appendChild(headerRow)
44
- table.appendChild(thead)
45
- // No data rows (read-only legacy view)
46
- fieldset.appendChild(table)
47
- continue
48
- }
49
- const label = document.createElement('label')
50
- label.textContent = prop.title || key
51
- label.style.display = 'block'
52
- let input: HTMLElement
53
- if (prop.enum) {
54
- const select = document.createElement('select')
55
- select.name = key
56
- for (const opt of prop.enum) {
57
- const option = document.createElement('option')
58
- option.value = opt
59
- option.textContent = opt
60
- select.appendChild(option)
61
- }
62
- input = select
63
- } else if (prop.type === 'boolean') {
64
- const checkbox = document.createElement('input')
65
- checkbox.type = 'checkbox'
66
- checkbox.name = key
67
- if (prop.default) {
68
- checkbox.checked = true
69
- }
70
- input = checkbox
71
- } else {
72
- const text = document.createElement('input')
73
- text.name = key
74
- text.type = prop.format === 'password' ? 'password' : 'text'
75
- if (prop.default) {
76
- text.value = prop.default
77
- }
78
- input = text
79
- }
80
- label.appendChild(input)
81
- fieldset.appendChild(label)
82
- }
83
- form.appendChild(fieldset)
84
- }
85
- container.appendChild(form)
86
- }
87
-
88
- export function enableLegacyView() {
89
- document.getElementById('dynamicUiContainer')!.style.display = 'none'
90
- document.getElementById('legacyViewContainer')!.style.display = 'block'
91
- renderLegacyView()
92
- }
93
-
94
- // DEBUG: assign to window for browser access
95
- // eslint-disable-next-line ts/ban-ts-comment
96
- // @ts-ignore
97
- window.enableLegacyView = enableLegacyView
98
- // eslint-disable-next-line ts/ban-ts-comment
99
- // @ts-ignore
100
- window.disableLegacyView = disableLegacyView
101
-
102
- export function disableLegacyView() {
103
- document.getElementById('dynamicUiContainer')!.style.display = 'block'
104
- document.getElementById('legacyViewContainer')!.style.display = 'none'
105
- }
106
-
107
- // (window assignment removed; handled in .js entry point)
@@ -1,23 +0,0 @@
1
- /**
2
- * Detect whether a platform config block is in the legacy v4 format.
3
- *
4
- * v4 configs stored credentials under a `credentials` sub-object and devices
5
- * under `options.devices`. v5+ flattened these to `openApiToken`,
6
- * `openApiSecret`, and a root-level `devices` array.
7
- *
8
- * This helper has no Node.js-specific dependencies so it can be imported from
9
- * both the Homebridge server-side code and the browser-side plugin UI bundle.
10
- */
11
- export function isV4Config(platform: any): boolean {
12
- if (!platform || typeof platform !== 'object') {
13
- return false
14
- }
15
- // Presence of a `credentials` sub-object is the clearest v4 signal
16
- const hasCredentials = platform.credentials !== undefined && typeof platform.credentials === 'object'
17
- // Presence of `options.devices` is the other v4 signal
18
- const hasOptionsDevices
19
- = platform.options !== undefined
20
- && typeof platform.options === 'object'
21
- && Array.isArray(platform.options.devices)
22
- return hasCredentials || hasOptionsDevices
23
- }
@@ -1,61 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { isV4Config } from '../../src/homebridge-ui/utils/v4-detection.js'
4
-
5
- describe('isV4Config', () => {
6
- it('returns false for null/undefined input', () => {
7
- expect(isV4Config(null)).toBe(false)
8
- expect(isV4Config(undefined)).toBe(false)
9
- })
10
-
11
- it('returns false for an empty v5 config block', () => {
12
- expect(isV4Config({ platform: 'SwitchBot', devices: [] })).toBe(false)
13
- })
14
-
15
- it('returns false for a v5 config with credentials omitted', () => {
16
- expect(isV4Config({ platform: 'SwitchBot', openApiToken: 'tok', openApiSecret: 'sec', devices: [] })).toBe(false)
17
- })
18
-
19
- it('returns true when a credentials sub-object is present (v4 token location)', () => {
20
- const v4 = {
21
- platform: 'SwitchBot',
22
- credentials: { token: 'tok', secret: 'sec', notice: 'Keep your token a secret!' },
23
- options: { devices: [] },
24
- }
25
- expect(isV4Config(v4)).toBe(true)
26
- })
27
-
28
- it('returns true when options.devices array is present without credentials (partial v4)', () => {
29
- const partialV4 = {
30
- platform: 'SwitchBot',
31
- options: {
32
- devices: [{ deviceId: 'aabbcc', configDeviceName: 'Curtain', configDeviceType: 'Curtain', connectionType: 'BLE' }],
33
- },
34
- }
35
- expect(isV4Config(partialV4)).toBe(true)
36
- })
37
-
38
- it('returns true for the exact config from the bug report', () => {
39
- const bugReportConfig = {
40
- name: 'SwitchBot',
41
- credentials: { notice: 'Keep your token a secret!' },
42
- options: {
43
- devices: [
44
- {
45
- deviceId: 'xxxxxxxx',
46
- configDeviceName: 'Curtain',
47
- configDeviceType: 'Curtain',
48
- connectionType: 'BLE',
49
- hide_lightsensor: false,
50
- set_min: 96,
51
- set_max: 12,
52
- maxRetry: 0,
53
- logging: 'standard',
54
- },
55
- ],
56
- },
57
- platform: 'SwitchBot',
58
- }
59
- expect(isV4Config(bugReportConfig)).toBe(true)
60
- })
61
- })