@switchbot/homebridge-switchbot 5.0.0-beta.16 → 5.0.0-beta.18

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.
@@ -7,7 +7,7 @@ import type {
7
7
  } from 'homebridge'
8
8
  import type { bodyChange, device } from 'node-switchbot'
9
9
 
10
- import type { SwitchBotPlatformConfig } from './settings.js'
10
+ import type { devicesConfig, SwitchBotPlatformConfig } from './settings.js'
11
11
 
12
12
  import { SwitchBotBLE, SwitchBotOpenAPI } from 'node-switchbot'
13
13
 
@@ -174,10 +174,24 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
174
174
  })()
175
175
  }
176
176
 
177
- // perform device discovery from SwitchBot OpenAPI (if configured)
178
- void this.discoverDevices()
177
+ // perform device discovery from SwitchBot OpenAPI (if configured) and
178
+ // register Matter accessories after discovery completes. Previously we
179
+ // called discoverDevices() without awaiting it which caused registration
180
+ // to run before discovery finished and only example accessories were
181
+ // created. Use an async IIFE to sequentially await discovery then register.
182
+ ;(async () => {
183
+ try {
184
+ await this.discoverDevices()
185
+ } catch (e: any) {
186
+ this.debugLog('Device discovery failed during startup: %s', e?.message ?? e)
187
+ }
179
188
 
180
- this.registerMatterAccessories()
189
+ try {
190
+ await this.registerMatterAccessories()
191
+ } catch (e: any) {
192
+ this.errorLog('Failed to register Matter accessories: %s', e?.message ?? e)
193
+ }
194
+ })()
181
195
  })
182
196
  }
183
197
 
@@ -193,10 +207,19 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
193
207
  * find matching item in a2 (discovered devices) and merge them with user overrides last.
194
208
  */
195
209
  private mergeByDeviceId(a1: { deviceId: string }[], a2: any[]) {
196
- return a1.map((itm) => {
197
- const matchingItem = a2.find(item => this.normalizeDeviceId(item.deviceId) === this.normalizeDeviceId(itm.deviceId))
198
- return Object.assign({}, matchingItem, itm)
199
- })
210
+ const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices)
211
+ const result: any[] = []
212
+ for (const itm of (a1 || [])) {
213
+ const matchingItem = (a2 || []).find(item => this.normalizeDeviceId(item.deviceId) === this.normalizeDeviceId(itm.deviceId))
214
+ if (matchingItem) {
215
+ result.push(Object.assign({}, matchingItem, itm))
216
+ } else if (allowConfigOnly) {
217
+ // include config-only device as-is when explicitly allowed
218
+ result.push(Object.assign({}, itm))
219
+ }
220
+ // otherwise skip config-only entries
221
+ }
222
+ return result
200
223
  }
201
224
 
202
225
  /**
@@ -225,7 +248,8 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
225
248
  // For any entries in merged (which are based on config.options.devices), ensure final per-device merges include deviceId-specific config
226
249
  const final: any[] = []
227
250
  for (const device of merged) {
228
- const deviceIdConfig = this.config.options?.devices?.[device.deviceId] || {}
251
+ // Find per-device config entry by deviceId (config.options.devices is an array)
252
+ const deviceIdConfig = (this.config.options?.devices || []).find((d: any) => this.normalizeDeviceId(d.deviceId) === this.normalizeDeviceId(device.deviceId)) || {}
229
253
  const deviceWithConfig = Object.assign({}, device, deviceIdConfig)
230
254
  final.push(deviceWithConfig)
231
255
  }
@@ -260,12 +284,12 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
260
284
  * Map a SwitchBot device object to a MatterAccessory using the device-specific
261
285
  * Matter accessory classes in `src/devices-matter`.
262
286
  */
263
- private async createAccessoryFromDevice(dev: device): Promise<MatterAccessory<Record<string, unknown>> | undefined> {
287
+ private async createAccessoryFromDevice(dev: device & devicesConfig): Promise<MatterAccessory<Record<string, unknown>> | undefined> {
264
288
  // Basic metadata
265
289
  const displayName = dev.deviceName ?? dev.deviceId ?? 'SwitchBot Device'
266
290
  const serial = dev.deviceId ?? 'unknown'
267
291
  const manufacturer = 'SwitchBot'
268
- const model = dev.deviceType ?? 'SwitchBot'
292
+ const model = dev.model ?? dev.deviceType ?? 'SwitchBot'
269
293
  const firmware = (dev as any).firmware ?? (dev as any).version ?? '0.0.0'
270
294
 
271
295
  // Helper to build a default opts object consumed by the matter device classes
@@ -780,41 +804,77 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
780
804
  // If we discovered real SwitchBot devices via OpenAPI, map and register them
781
805
  if (this.discoveredDevices && this.discoveredDevices.length > 0) {
782
806
  this.infoLog(`Registering ${this.discoveredDevices.length} discovered SwitchBot device(s) as Matter accessories`)
783
- const accessories: Array<MatterAccessory<Record<string, unknown>>> = []
784
807
 
785
808
  // Merge device config (deviceConfig per deviceType and per-device overrides) to match HAP behavior
786
809
  const devicesToProcess = await this.mergeDiscoveredDevices(this.discoveredDevices)
787
810
 
811
+ // We'll separate discovered devices into two buckets:
812
+ // - platformAccessories: accessories that will be hosted under the plugin's Matter bridge
813
+ // - roboticAccessories: robot vacuum devices which require standalone commissioning behaviour
814
+ const platformAccessories: Array<MatterAccessory<Record<string, unknown>>> = []
815
+ const roboticAccessories: Array<MatterAccessory<Record<string, unknown>>> = []
816
+
817
+ // Known robot vacuum deviceType names (matches mapping in createAccessoryFromDevice)
818
+ const robotTypes = new Set([
819
+ 'K10+',
820
+ 'K10+ Pro',
821
+ 'WoSweeper',
822
+ 'WoSweeperMini',
823
+ 'Robot Vacuum Cleaner S1',
824
+ 'Robot Vacuum Cleaner S1 Plus',
825
+ 'Robot Vacuum Cleaner S10',
826
+ 'Robot Vacuum Cleaner S1 Pro',
827
+ 'Robot Vacuum Cleaner S1 Mini',
828
+ ])
829
+
788
830
  for (const dev of devicesToProcess) {
789
831
  try {
790
832
  const acc = await this.createAccessoryFromDevice(dev)
791
- if (acc) {
792
- accessories.push(acc)
833
+ if (!acc) {
834
+ continue
835
+ }
836
+ if (robotTypes.has(dev.deviceType ?? '')) {
837
+ roboticAccessories.push(acc)
838
+ } else {
839
+ platformAccessories.push(acc)
793
840
  }
794
841
  } catch (e: any) {
795
842
  this.errorLog(`Failed to create Matter accessory for ${dev.deviceId}: ${e?.message ?? e}`)
796
843
  }
797
844
  }
798
- if (accessories.length > 0) {
799
- this.infoLog(`✓ Registered ${accessories.length} discovered device(s)`)
800
- for (const acc of accessories) {
845
+
846
+ // Register platform-hosted accessories (most devices)
847
+ if (platformAccessories.length > 0) {
848
+ this.infoLog(`✓ Registered ${platformAccessories.length} discovered platform-hosted device(s)`)
849
+ for (const acc of platformAccessories) {
801
850
  this.infoLog(` - ${acc.displayName}`)
802
851
  }
803
- await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories)
804
- return
852
+ await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, platformAccessories)
805
853
  }
806
- this.infoLog('No discovered devices were mapped to Matter accessories; falling back to example sections')
854
+
855
+ // Register robotic accessories (robot vacuums) separately so they can be
856
+ // commissioned in the way Apple Home expects (these devices often require
857
+ // standalone commissioning flow). We still call registerPlatformAccessories
858
+ // because the accessory implementations manage their commissioning behavior.
859
+ if (roboticAccessories.length > 0) {
860
+ this.infoLog(`✓ Registered ${roboticAccessories.length} discovered robot vacuum device(s)`)
861
+ for (const acc of roboticAccessories) {
862
+ this.infoLog(` - ${acc.displayName} (standalone for Apple Home compatibility)`)
863
+ }
864
+ await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, roboticAccessories)
865
+ }
866
+
867
+ // Debug/info: how many discovered vs example accessories were registered.
868
+ // Example accessories are disabled — we intentionally do NOT register them.
869
+ const discoveredRegistered = platformAccessories.length + roboticAccessories.length
870
+ const exampleRegistered = 0
871
+ this.debugLog(`Discovered accessories registered: ${discoveredRegistered}; Example accessories registered: ${exampleRegistered}`)
872
+
873
+ return
807
874
  }
808
875
 
809
- // Register example/demo devices by Matter specification sections
810
- await this.registerSection4Lighting()
811
- await this.registerSection5SmartPlugs()
812
- await this.registerSection6Switches()
813
- await this.registerSection7Sensors()
814
- await this.registerSection8Closure()
815
- await this.registerSection9HVAC()
816
- await this.registerSection12Robotic()
817
- await this.registerCustomDevices()
876
+ // If no discovered devices are available, do not register example/demo accessories.
877
+ this.infoLog('No discovered SwitchBot devices found; not registering example Matter accessories by default.')
818
878
 
819
879
  this.debugLog('═'.repeat(80))
820
880
  this.debugLog('Finished registering Matter accessories')
package/src/settings.ts CHANGED
@@ -36,6 +36,10 @@ export interface options {
36
36
  irdevices?: irDevicesConfig[]
37
37
  irdeviceConfig?: { [remoteType: string]: irDevicesConfig }
38
38
  allowInvalidCharacters?: boolean
39
+ // When true, devices declared in config.options.devices that are not
40
+ // discovered via the SwitchBot OpenAPI will still be included (config-only
41
+ // devices). Default: false.
42
+ allowConfigOnlyDevices?: boolean
39
43
  mqttURL?: string
40
44
  mqttOptions?: IClientOptions
41
45
  mqttPubOptions?: IClientOptions
@@ -1 +0,0 @@
1
- {"version":3,"file":"baseMatterAccessory.test.d.ts","sourceRoot":"","sources":["../src/baseMatterAccessory.test.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"baseMatterAccessory.test.js","sourceRoot":"","sources":["../src/baseMatterAccessory.test.ts"],"names":[],"mappings":"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"}