@switchbot/homebridge-switchbot 5.0.0-beta.146 → 5.0.0-beta.148

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 (74) hide show
  1. package/.github/workflows/ci.yml +1 -1
  2. package/.github/workflows/manual-e2e.yml +3 -3
  3. package/config.schema.json +6 -0
  4. package/dist/deviceCommandMapper.d.ts +10 -0
  5. package/dist/deviceCommandMapper.d.ts.map +1 -0
  6. package/dist/deviceCommandMapper.js +319 -0
  7. package/dist/deviceCommandMapper.js.map +1 -0
  8. package/dist/deviceFactory.d.ts +2 -3
  9. package/dist/deviceFactory.d.ts.map +1 -1
  10. package/dist/deviceFactory.js +12 -3
  11. package/dist/deviceFactory.js.map +1 -1
  12. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -1
  13. package/dist/homebridge-ui/endpoints/config.js +1 -18
  14. package/dist/homebridge-ui/endpoints/config.js.map +1 -1
  15. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -1
  16. package/dist/homebridge-ui/endpoints/devices.js +1 -350
  17. package/dist/homebridge-ui/endpoints/devices.js.map +1 -1
  18. package/dist/homebridge-ui/public/index.html +6 -0
  19. package/dist/homebridge-ui/public/js/advanced-settings.d.ts.map +1 -1
  20. package/dist/homebridge-ui/public/js/advanced-settings.js +22 -12
  21. package/dist/homebridge-ui/public/js/advanced-settings.js.map +1 -1
  22. package/dist/homebridge-ui/public/js/advanced-settings.ts +36 -25
  23. package/dist/homebridge-ui/public/js/api.d.ts +1 -1
  24. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  25. package/dist/homebridge-ui/public/js/api.js +111 -52
  26. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  27. package/dist/homebridge-ui/public/js/api.ts +112 -53
  28. package/dist/homebridge-ui/public/js/app.js +234 -177
  29. package/dist/homebridge-ui/public/js/app.js.map +4 -4
  30. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -1
  31. package/dist/homebridge-ui/public/js/credentials.js +26 -23
  32. package/dist/homebridge-ui/public/js/credentials.js.map +1 -1
  33. package/dist/homebridge-ui/public/js/credentials.ts +26 -25
  34. package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -1
  35. package/dist/homebridge-ui/public/js/devices-delete.js +3 -10
  36. package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -1
  37. package/dist/homebridge-ui/public/js/devices-delete.ts +3 -12
  38. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
  39. package/dist/homebridge-ui/public/js/devices.js +0 -3
  40. package/dist/homebridge-ui/public/js/devices.js.map +1 -1
  41. package/dist/homebridge-ui/public/js/devices.ts +5 -9
  42. package/dist/homebridge-ui/server.js +0 -2
  43. package/dist/homebridge-ui/server.js.map +1 -1
  44. package/dist/platform.d.ts.map +1 -1
  45. package/dist/platform.js +3 -2
  46. package/dist/platform.js.map +1 -1
  47. package/dist/settings.d.ts +1 -0
  48. package/dist/settings.d.ts.map +1 -1
  49. package/dist/settings.js +1 -0
  50. package/dist/settings.js.map +1 -1
  51. package/dist/switchbotClient.d.ts.map +1 -1
  52. package/dist/switchbotClient.js +42 -55
  53. package/dist/switchbotClient.js.map +1 -1
  54. package/docs/variables/default.html +1 -1
  55. package/package.json +1 -1
  56. package/src/deviceCommandMapper.ts +333 -0
  57. package/src/deviceFactory.ts +14 -6
  58. package/src/homebridge-ui/endpoints/config.ts +2 -18
  59. package/src/homebridge-ui/endpoints/devices.ts +1 -392
  60. package/src/homebridge-ui/public/index.html +6 -0
  61. package/src/homebridge-ui/public/js/advanced-settings.ts +36 -25
  62. package/src/homebridge-ui/public/js/api.ts +112 -53
  63. package/src/homebridge-ui/public/js/credentials.ts +26 -25
  64. package/src/homebridge-ui/public/js/devices-delete.ts +3 -12
  65. package/src/homebridge-ui/public/js/devices.ts +5 -9
  66. package/src/homebridge-ui/server.ts +0 -2
  67. package/src/platform.ts +3 -2
  68. package/src/settings.ts +2 -0
  69. package/src/switchbotClient.ts +43 -54
  70. package/dist/homebridge-ui/endpoints/credentials.d.ts +0 -6
  71. package/dist/homebridge-ui/endpoints/credentials.d.ts.map +0 -1
  72. package/dist/homebridge-ui/endpoints/credentials.js +0 -65
  73. package/dist/homebridge-ui/endpoints/credentials.js.map +0 -1
  74. package/src/homebridge-ui/endpoints/credentials.ts +0 -74
@@ -0,0 +1,333 @@
1
+ // deviceCommandMapper.ts
2
+ // Maps HAP/Matter commands to node-switchbot device methods for all supported device types
3
+
4
+ // This is a starter mapping. Expand as needed for new device types and commands.
5
+ // Each device type maps to a set of commands, each command maps to a function that takes (device, body) and returns a Promise.
6
+
7
+ export type DeviceCommandHandler = (device: any, body: any) => Promise<any>
8
+
9
+ export interface DeviceCommandMap {
10
+ [command: string]: DeviceCommandHandler
11
+ }
12
+
13
+ export interface DeviceTypeCommandMap {
14
+ [deviceType: string]: DeviceCommandMap
15
+ }
16
+
17
+ export const deviceTypeCommandMap: DeviceTypeCommandMap = {
18
+ // SwitchBot AI Hub (read-only/generic)
19
+ 'ai hub': {},
20
+ // SwitchBot Cameras (read-only/generic)
21
+ 'indoor cam': {},
22
+ 'pan/tilt cam': {},
23
+ 'pan/tilt cam 2k': {},
24
+ 'pan/tilt cam plus 2k': {},
25
+ 'pan/tilt cam plus 3k': {},
26
+ // SwitchBot Air Purifiers (read-only/generic)
27
+ 'air purifier pm2.5': {},
28
+ 'air purifier table pm2.5': {},
29
+ 'air purifier voc': {},
30
+ 'air purifier table voc': {},
31
+ // SwitchBot Home Climate Panel (read-only/generic)
32
+ 'home climate panel': {},
33
+ // SwitchBot WoIOSensor (read-only/generic)
34
+ 'woiosensor': {},
35
+ // SwitchBot Candle Warmer Lamp
36
+ 'candle warmer lamp': {
37
+ turnOn: async device => device.turnOn(),
38
+ turnOff: async device => device.turnOff(),
39
+ },
40
+ // SwitchBot Hubs (read-only/generic)
41
+ 'hub': {},
42
+ 'hub 2': {},
43
+ 'hub 3': {},
44
+ 'hub mini': {},
45
+ 'hub plus': {},
46
+ // SwitchBot Robot Vacuum Cleaner K20 Plus Pro
47
+ 'robot vacuum cleaner k20 plus pro': {
48
+ start: async device => device.start(),
49
+ stop: async device => device.stop(),
50
+ pause: async device => device.pause(),
51
+ dock: async device => device.dock(),
52
+ resume: async device => device.resume(),
53
+ locate: async device => device.locate(),
54
+ },
55
+ // SwitchBot Robot Vacuum Cleaner S1
56
+ 'robot vacuum cleaner s1': {
57
+ start: async device => device.start(),
58
+ stop: async device => device.stop(),
59
+ pause: async device => device.pause(),
60
+ dock: async device => device.dock(),
61
+ resume: async device => device.resume(),
62
+ locate: async device => device.locate(),
63
+ },
64
+ // SwitchBot Robot Vacuum Cleaner S1 Plus
65
+ 'robot vacuum cleaner s1 plus': {
66
+ start: async device => device.start(),
67
+ stop: async device => device.stop(),
68
+ pause: async device => device.pause(),
69
+ dock: async device => device.dock(),
70
+ resume: async device => device.resume(),
71
+ locate: async device => device.locate(),
72
+ },
73
+ // SwitchBot Robot Vacuum Cleaner S10
74
+ 'robot vacuum cleaner s10': {
75
+ start: async device => device.start(),
76
+ stop: async device => device.stop(),
77
+ pause: async device => device.pause(),
78
+ dock: async device => device.dock(),
79
+ resume: async device => device.resume(),
80
+ locate: async device => device.locate(),
81
+ },
82
+ // SwitchBot Robot Vacuum Cleaner S20
83
+ 'robot vacuum cleaner s20': {
84
+ start: async device => device.start(),
85
+ stop: async device => device.stop(),
86
+ pause: async device => device.pause(),
87
+ dock: async device => device.dock(),
88
+ resume: async device => device.resume(),
89
+ locate: async device => device.locate(),
90
+ },
91
+ // SwitchBot Relay Switch 2PM
92
+ 'relay switch 2pm': {
93
+ turnOn: async device => device.turnOn(),
94
+ turnOff: async device => device.turnOff(),
95
+ },
96
+ // SwitchBot K10+
97
+ 'k10+': {
98
+ start: async device => device.start(),
99
+ stop: async device => device.stop(),
100
+ pause: async device => device.pause(),
101
+ dock: async device => device.dock(),
102
+ resume: async device => device.resume(),
103
+ locate: async device => device.locate(),
104
+ },
105
+ // SwitchBot K10+ Pro
106
+ 'k10+ pro': {
107
+ start: async device => device.start(),
108
+ stop: async device => device.stop(),
109
+ pause: async device => device.pause(),
110
+ dock: async device => device.dock(),
111
+ resume: async device => device.resume(),
112
+ locate: async device => device.locate(),
113
+ },
114
+ // SwitchBot Robot Vacuum Cleaner K10+ Pro Combo
115
+ 'robot vacuum cleaner k10+ pro combo': {
116
+ start: async device => device.start(),
117
+ stop: async device => device.stop(),
118
+ pause: async device => device.pause(),
119
+ dock: async device => device.dock(),
120
+ resume: async device => device.resume(),
121
+ locate: async device => device.locate(),
122
+ },
123
+ // SwitchBot Robot Vacuum Cleaner K11+
124
+ 'robot vacuum cleaner k11+': {
125
+ start: async device => device.start(),
126
+ stop: async device => device.stop(),
127
+ pause: async device => device.pause(),
128
+ dock: async device => device.dock(),
129
+ resume: async device => device.resume(),
130
+ locate: async device => device.locate(),
131
+ },
132
+ // SwitchBot MeterPro(CO2) (read-only)
133
+ 'meterpro(co2)': {},
134
+ // SwitchBot Water Detector (read-only)
135
+ 'water detector': {},
136
+ // SwitchBot Garage Door Opener
137
+ 'garage door opener': {
138
+ open: async device => device.open(),
139
+ close: async device => device.close(),
140
+ stop: async device => device.stop(),
141
+ },
142
+ // SwitchBot Relay Switch 1
143
+ 'relay switch 1': {
144
+ turnOn: async device => device.turnOn(),
145
+ turnOff: async device => device.turnOff(),
146
+ },
147
+ // SwitchBot Relay Switch 1PM
148
+ 'relay switch 1pm': {
149
+ turnOn: async device => device.turnOn(),
150
+ turnOff: async device => device.turnOff(),
151
+ },
152
+ // SwitchBot Smart Radiator Thermostat (read-only or not directly controllable)
153
+ 'smart radiator thermostat': {},
154
+ // SwitchBot Humidifier2
155
+ 'humidifier2': {
156
+ turnOn: async device => device.turnOn(),
157
+ turnOff: async device => device.turnOff(),
158
+ setMode: async (device, body) => device.setMode(body?.parameter),
159
+ setHumidity: async (device, body) => device.setHumidity(body?.parameter),
160
+ },
161
+ // SwitchBot MeterPlus (read-only)
162
+ 'meterplus': {},
163
+ // SwitchBot Meter Pro (read-only)
164
+ 'meter pro': {},
165
+ // SwitchBot MeterPro (read-only)
166
+ 'meterpro': {},
167
+ // SwitchBot RGBICWW Floor Lamp
168
+ 'rgbicww floor lamp': {
169
+ turnOn: async device => device.turnOn(),
170
+ turnOff: async device => device.turnOff(),
171
+ setBrightness: async (device, body) => device.setBrightness(body?.parameter),
172
+ setColor: async (device, body) => device.setColor(body?.parameter),
173
+ },
174
+ // SwitchBot RGBICWW Strip Light
175
+ 'rgbicww strip light': {
176
+ turnOn: async device => device.turnOn(),
177
+ turnOff: async device => device.turnOff(),
178
+ setBrightness: async (device, body) => device.setBrightness(body?.parameter),
179
+ setColor: async (device, body) => device.setColor(body?.parameter),
180
+ },
181
+ // SwitchBot Circulator Fan
182
+ 'circulator fan': {
183
+ turnOn: async device => device.turnOn(),
184
+ turnOff: async device => device.turnOff(),
185
+ setSpeed: async (device, body) => device.setSpeed(body?.parameter),
186
+ swing: async device => device.swing(),
187
+ },
188
+ // SwitchBot Battery Circulator Fan
189
+ 'battery circulator fan': {
190
+ turnOn: async device => device.turnOn(),
191
+ turnOff: async device => device.turnOff(),
192
+ setSpeed: async (device, body) => device.setSpeed(body?.parameter),
193
+ swing: async device => device.swing(),
194
+ },
195
+ // SwitchBot Standing Circulator Fan
196
+ 'standing circulator fan': {
197
+ turnOn: async device => device.turnOn(),
198
+ turnOff: async device => device.turnOff(),
199
+ setSpeed: async (device, body) => device.setSpeed(body?.parameter),
200
+ swing: async device => device.swing(),
201
+ },
202
+ // SwitchBot Bot
203
+ 'bot': {
204
+ turnOn: async device => device.turnOn(),
205
+ turnOff: async device => device.turnOff(),
206
+ press: async device => device.press(),
207
+ },
208
+ // SwitchBot Curtain
209
+ 'curtain': {
210
+ open: async device => device.open(),
211
+ close: async device => device.close(),
212
+ pause: async device => device.pause(),
213
+ setPosition: async (device, body) => device.setPosition(body?.parameter),
214
+ },
215
+ // SwitchBot Blind Tilt
216
+ 'blind tilt': {
217
+ open: async device => device.open(),
218
+ close: async device => device.close(),
219
+ pause: async device => device.pause(),
220
+ setPosition: async (device, body) => device.setPosition(body?.parameter),
221
+ },
222
+ // SwitchBot Roller Shade
223
+ 'roller shade': {
224
+ open: async device => device.open(),
225
+ close: async device => device.close(),
226
+ pause: async device => device.pause(),
227
+ setPosition: async (device, body) => device.setPosition(body?.parameter),
228
+ },
229
+ // SwitchBot Plug
230
+ 'plug': {
231
+ turnOn: async device => device.turnOn(),
232
+ turnOff: async device => device.turnOff(),
233
+ },
234
+ // SwitchBot Strip Light 3
235
+ 'strip light 3': {
236
+ turnOn: async device => device.turnOn(),
237
+ turnOff: async device => device.turnOff(),
238
+ setBrightness: async (device, body) => device.setBrightness(body?.parameter),
239
+ setColor: async (device, body) => device.setColor(body?.parameter),
240
+ },
241
+ // SwitchBot Floor Lamp
242
+ 'floor lamp': {
243
+ turnOn: async device => device.turnOn(),
244
+ turnOff: async device => device.turnOff(),
245
+ setBrightness: async (device, body) => device.setBrightness(body?.parameter),
246
+ setColor: async (device, body) => device.setColor(body?.parameter),
247
+ },
248
+ // SwitchBot RGBIC Neon Rope Light
249
+ 'rgbic neon rope light': {
250
+ turnOn: async device => device.turnOn(),
251
+ turnOff: async device => device.turnOff(),
252
+ setBrightness: async (device, body) => device.setBrightness(body?.parameter),
253
+ setColor: async (device, body) => device.setColor(body?.parameter),
254
+ },
255
+ // SwitchBot RGBIC Neon Wire Rope Light
256
+ 'rgbic neon wire rope light': {
257
+ turnOn: async device => device.turnOn(),
258
+ turnOff: async device => device.turnOff(),
259
+ setBrightness: async (device, body) => device.setBrightness(body?.parameter),
260
+ setColor: async (device, body) => device.setColor(body?.parameter),
261
+ },
262
+ // SwitchBot Plug Mini
263
+ 'plug mini': {
264
+ turnOn: async device => device.turnOn(),
265
+ turnOff: async device => device.turnOff(),
266
+ },
267
+ // SwitchBot Color Bulb
268
+ 'color bulb': {
269
+ turnOn: async device => device.turnOn(),
270
+ turnOff: async device => device.turnOff(),
271
+ setBrightness: async (device, body) => device.setBrightness(body?.parameter),
272
+ setColor: async (device, body) => device.setColor(body?.parameter),
273
+ },
274
+ // SwitchBot Light Strip
275
+ 'light strip': {
276
+ turnOn: async device => device.turnOn(),
277
+ turnOff: async device => device.turnOff(),
278
+ setBrightness: async (device, body) => device.setBrightness(body?.parameter),
279
+ setColor: async (device, body) => device.setColor(body?.parameter),
280
+ },
281
+ // SwitchBot Fan
282
+ 'fan': {
283
+ turnOn: async device => device.turnOn(),
284
+ turnOff: async device => device.turnOff(),
285
+ setSpeed: async (device, body) => device.setSpeed(body?.parameter),
286
+ swing: async device => device.swing(),
287
+ },
288
+ // SwitchBot Lock
289
+ 'lock': {
290
+ lock: async device => device.lock(),
291
+ unlock: async device => device.unlock(),
292
+ },
293
+ // SwitchBot Curtain 3
294
+ 'curtain3': {
295
+ open: async device => device.open(),
296
+ close: async device => device.close(),
297
+ pause: async device => device.pause(),
298
+ setPosition: async (device, body) => device.setPosition(body?.parameter),
299
+ },
300
+ // SwitchBot Meter
301
+ 'meter': {}, // Read-only
302
+ // SwitchBot Motion Sensor
303
+ 'motion sensor': {}, // Read-only
304
+ // SwitchBot Contact Sensor
305
+ 'contact sensor': {}, // Read-only
306
+ // SwitchBot Humidifier
307
+ 'humidifier': {
308
+ turnOn: async device => device.turnOn(),
309
+ turnOff: async device => device.turnOff(),
310
+ setMode: async (device, body) => device.setMode(body?.parameter),
311
+ setHumidity: async (device, body) => device.setHumidity(body?.parameter),
312
+ },
313
+ // SwitchBot Vacuum
314
+ 'vacuum': {
315
+ start: async device => device.start(),
316
+ stop: async device => device.stop(),
317
+ pause: async device => device.pause(),
318
+ dock: async device => device.dock(),
319
+ resume: async device => device.resume(),
320
+ locate: async device => device.locate(),
321
+ },
322
+ // Add more device types and commands as needed
323
+ }
324
+
325
+ // Helper to get the handler for a device type and command
326
+ export function getDeviceCommandHandler(deviceType: string, command: string): DeviceCommandHandler | undefined {
327
+ const typeKey = deviceType.toLowerCase()
328
+ const typeMap = deviceTypeCommandMap[typeKey]
329
+ if (!typeMap) {
330
+ return undefined
331
+ }
332
+ return typeMap[command]
333
+ }
@@ -1,4 +1,5 @@
1
1
  import type { DeviceType, SwitchBotPluginConfig } from './settings.js'
2
+ import type { Logger } from 'homebridge'
2
3
 
3
4
  import { DEVICE_TYPE_NORMALIZATION_MAP } from './device-types.js'
4
5
  import {
@@ -139,10 +140,11 @@ function classForType(type: string) {
139
140
  return DEVICE_CLASS_MAP[key] ?? GenericDevice
140
141
  }
141
142
 
142
- export async function createDevice(opts: DeviceOptions, cfg: SwitchBotPluginConfig, useMatter: boolean, log?: { info: (...args: any[]) => void }) {
143
- // Debug: Log the options passed to the device constructor
144
- if (opts && opts.name && log && typeof log.info === 'function') {
145
- log.info(`[Matter/Debug] createDevice: Passing opts for ${opts.name}:`, JSON.stringify(opts, null, 2))
143
+ export async function createDevice(opts: DeviceOptions, cfg: SwitchBotPluginConfig, useMatter: boolean, log?: Logger) {
144
+ // Always pass the logger to both device opts and config
145
+ const logger = log || (cfg as any)?.logger || (cfg as any)?.log
146
+ if (opts && opts.name && logger && typeof logger.info === 'function') {
147
+ logger.info(`[Matter/Debug] createDevice: Passing opts for ${opts.name}:`, JSON.stringify(opts, null, 2))
146
148
  }
147
149
  // Reuse existing client when provided via config to avoid creating
148
150
  // a new SwitchBotClient per device (which can be expensive).
@@ -151,9 +153,12 @@ export async function createDevice(opts: DeviceOptions, cfg: SwitchBotPluginConf
151
153
  client = new SwitchBotClient(cfg)
152
154
  await client.init()
153
155
  }
154
-
155
156
  // Pass client via config so devices can access it
156
157
  const mergedCfg = { ...(cfg as any), _client: client }
158
+ // Always pass logger to mergedCfg
159
+ if (logger) {
160
+ mergedCfg.log = logger
161
+ }
157
162
  // Pass encryptionKey and keyId to device opts if present
158
163
  const deviceOpts = { ...opts }
159
164
  if (opts.encryptionKey) {
@@ -162,7 +167,10 @@ export async function createDevice(opts: DeviceOptions, cfg: SwitchBotPluginConf
162
167
  if (opts.keyId) {
163
168
  deviceOpts.keyId = opts.keyId
164
169
  }
165
-
170
+ // Always pass logger to deviceOpts
171
+ if (logger) {
172
+ deviceOpts.log = logger
173
+ }
166
174
  const DeviceCtor = classForType(opts.type)
167
175
  const device = new DeviceCtor(deviceOpts, mergedCfg)
168
176
  await device.init()
@@ -4,7 +4,7 @@ import { RequestError } from '@homebridge/plugin-ui-utils'
4
4
  import fs from 'node:fs/promises'
5
5
 
6
6
  import { isValidDeviceType } from '../../device-types.js'
7
- import { getAllDevices, getSwitchBotPlatformConfig, SWITCHBOT_PLATFORM_REGEX } from '../utils/config-parser.js'
7
+ import { getAllDevices, SWITCHBOT_PLATFORM_REGEX } from '../utils/config-parser.js'
8
8
  import { validateAndMigrateDeviceType } from '../utils/device-migration.js'
9
9
  import { uiLog } from '../utils/logger.js'
10
10
 
@@ -12,6 +12,7 @@ export function registerConfigEndpoints(server: HomebridgePluginUiServer) {
12
12
  /**
13
13
  * GET /devices - List all configured devices from Homebridge config
14
14
  */
15
+
15
16
  server.onRequest('/devices', async () => {
16
17
  try {
17
18
  const cfgPath = server.homebridgeConfigPath
@@ -106,21 +107,4 @@ export function registerConfigEndpoints(server: HomebridgePluginUiServer) {
106
107
  throw new RequestError(msg || 'Failed to read Homebridge config', e)
107
108
  }
108
109
  })
109
-
110
- /**
111
- * GET /platform-config - Get SwitchBot platform configuration
112
- */
113
- server.onRequest('/platform-config', async () => {
114
- try {
115
- const { platform } = await getSwitchBotPlatformConfig(server)
116
- return {
117
- success: true,
118
- data: JSON.parse(JSON.stringify(platform)),
119
- }
120
- } catch (e) {
121
- const msg = e instanceof Error ? e.message : String(e)
122
- uiLog.error(`Error in /platform-config: ${msg}`)
123
- throw new RequestError(msg || 'Failed to read platform config', e)
124
- }
125
- })
126
110
  }