@switchbot/homebridge-switchbot 5.0.0-beta.2 → 5.0.0-beta.21

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 (107) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/config.schema.json +17 -2
  3. package/dist/devices-hap/device.d.ts +1 -0
  4. package/dist/devices-hap/device.d.ts.map +1 -1
  5. package/dist/devices-hap/device.js +70 -30
  6. package/dist/devices-hap/device.js.map +1 -1
  7. package/dist/devices-matter/BaseMatterAccessory.d.ts +23 -0
  8. package/dist/devices-matter/BaseMatterAccessory.d.ts.map +1 -1
  9. package/dist/devices-matter/BaseMatterAccessory.js +167 -5
  10. package/dist/devices-matter/BaseMatterAccessory.js.map +1 -1
  11. package/dist/devices-matter/ColorLightAccessory.d.ts.map +1 -1
  12. package/dist/devices-matter/ColorLightAccessory.js +12 -12
  13. package/dist/devices-matter/ColorLightAccessory.js.map +1 -1
  14. package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts.map +1 -1
  15. package/dist/devices-matter/ColorTemperatureLightAccessory.js +5 -7
  16. package/dist/devices-matter/ColorTemperatureLightAccessory.js.map +1 -1
  17. package/dist/devices-matter/DimmableLightAccessory.js +9 -9
  18. package/dist/devices-matter/DimmableLightAccessory.js.map +1 -1
  19. package/dist/devices-matter/ExtendedColorLightAccessory.d.ts.map +1 -1
  20. package/dist/devices-matter/ExtendedColorLightAccessory.js +14 -15
  21. package/dist/devices-matter/ExtendedColorLightAccessory.js.map +1 -1
  22. package/dist/devices-matter/OnOffLightAccessory.d.ts.map +1 -1
  23. package/dist/devices-matter/OnOffLightAccessory.js +8 -16
  24. package/dist/devices-matter/OnOffLightAccessory.js.map +1 -1
  25. package/dist/devices-matter/OnOffOutletAccessory.d.ts +2 -0
  26. package/dist/devices-matter/OnOffOutletAccessory.d.ts.map +1 -1
  27. package/dist/devices-matter/OnOffOutletAccessory.js +10 -7
  28. package/dist/devices-matter/OnOffOutletAccessory.js.map +1 -1
  29. package/dist/devices-matter/OnOffSwitchAccessory.js +2 -2
  30. package/dist/devices-matter/OnOffSwitchAccessory.js.map +1 -1
  31. package/dist/devices-matter/baseMatterAccessory.test.d.ts +2 -0
  32. package/dist/devices-matter/baseMatterAccessory.test.d.ts.map +1 -0
  33. package/dist/devices-matter/baseMatterAccessory.test.js +71 -0
  34. package/dist/devices-matter/baseMatterAccessory.test.js.map +1 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +4 -5
  37. package/dist/index.js.map +1 -1
  38. package/dist/index.test.js +7 -2
  39. package/dist/index.test.js.map +1 -1
  40. package/dist/irdevice/irdevice.d.ts +11 -10
  41. package/dist/irdevice/irdevice.d.ts.map +1 -1
  42. package/dist/irdevice/irdevice.js +76 -35
  43. package/dist/irdevice/irdevice.js.map +1 -1
  44. package/dist/platform-hap.d.ts +10 -14
  45. package/dist/platform-hap.d.ts.map +1 -1
  46. package/dist/platform-hap.js +38 -64
  47. package/dist/platform-hap.js.map +1 -1
  48. package/dist/platform-matter.cleanup.test.d.ts +2 -0
  49. package/dist/platform-matter.cleanup.test.d.ts.map +1 -0
  50. package/dist/platform-matter.cleanup.test.js +85 -0
  51. package/dist/platform-matter.cleanup.test.js.map +1 -0
  52. package/dist/platform-matter.d.ts +68 -6
  53. package/dist/platform-matter.d.ts.map +1 -1
  54. package/dist/platform-matter.js +1250 -70
  55. package/dist/platform-matter.js.map +1 -1
  56. package/dist/platform-matter.mapping.test.d.ts +2 -0
  57. package/dist/platform-matter.mapping.test.d.ts.map +1 -0
  58. package/dist/platform-matter.mapping.test.js +50 -0
  59. package/dist/platform-matter.mapping.test.js.map +1 -0
  60. package/dist/platform-matter.test.d.ts +2 -0
  61. package/dist/platform-matter.test.d.ts.map +1 -0
  62. package/dist/platform-matter.test.js +127 -0
  63. package/dist/platform-matter.test.js.map +1 -0
  64. package/dist/platform-matter.unregister.test.d.ts +2 -0
  65. package/dist/platform-matter.unregister.test.d.ts.map +1 -0
  66. package/dist/platform-matter.unregister.test.js +37 -0
  67. package/dist/platform-matter.unregister.test.js.map +1 -0
  68. package/dist/settings.d.ts +1 -0
  69. package/dist/settings.d.ts.map +1 -1
  70. package/dist/settings.js.map +1 -1
  71. package/dist/utils.d.ts +87 -0
  72. package/dist/utils.d.ts.map +1 -1
  73. package/dist/utils.js +254 -0
  74. package/dist/utils.js.map +1 -1
  75. package/dist/utils.test.d.ts +2 -0
  76. package/dist/utils.test.d.ts.map +1 -0
  77. package/dist/utils.test.js +95 -0
  78. package/dist/utils.test.js.map +1 -0
  79. package/dist/verifyconfig.test.js +2 -2
  80. package/dist/verifyconfig.test.js.map +1 -1
  81. package/docs/assets/main.js +2 -2
  82. package/docs/index.html +2 -2
  83. package/docs/variables/default.html +1 -1
  84. package/package.json +14 -14
  85. package/src/devices-hap/device.ts +68 -30
  86. package/src/devices-matter/BaseMatterAccessory.ts +168 -5
  87. package/src/devices-matter/ColorLightAccessory.ts +12 -12
  88. package/src/devices-matter/ColorTemperatureLightAccessory.ts +5 -7
  89. package/src/devices-matter/DimmableLightAccessory.ts +9 -9
  90. package/src/devices-matter/ExtendedColorLightAccessory.ts +14 -15
  91. package/src/devices-matter/OnOffLightAccessory.ts +8 -16
  92. package/src/devices-matter/OnOffOutletAccessory.ts +12 -7
  93. package/src/devices-matter/OnOffSwitchAccessory.ts +2 -2
  94. package/src/devices-matter/baseMatterAccessory.test.ts +88 -0
  95. package/src/index.test.ts +7 -2
  96. package/src/index.ts +4 -5
  97. package/src/irdevice/irdevice.ts +74 -35
  98. package/src/platform-hap.ts +39 -73
  99. package/src/platform-matter.cleanup.test.ts +101 -0
  100. package/src/platform-matter.mapping.test.ts +60 -0
  101. package/src/platform-matter.test.ts +155 -0
  102. package/src/platform-matter.ts +1286 -73
  103. package/src/platform-matter.unregister.test.ts +48 -0
  104. package/src/settings.ts +4 -0
  105. package/src/utils.test.ts +96 -0
  106. package/src/utils.ts +255 -0
  107. package/src/verifyconfig.test.ts +11 -10
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { SwitchBotMatterPlatform } from './platform-matter.js'
4
+ import { formatDeviceIdAsMac } from './utils.js'
5
+
6
+ describe('removeDisabledAccessories and unregister edge cases', () => {
7
+ it('clears resources and unregisters accessory even with invalid timer and non-MAC deviceId', async () => {
8
+ // Prepare API stub
9
+ const unregister = vi.fn()
10
+ const api: any = {
11
+ matter: {
12
+ uuid: { generate: (s: string) => `uuid-${s}` },
13
+ unregisterPlatformAccessories: unregister,
14
+ },
15
+ isMatterAvailable: () => true,
16
+ isMatterEnabled: () => true,
17
+ on: () => {},
18
+ }
19
+
20
+ const log: any = { info: vi.fn(), warn: vi.fn(), debug: vi.fn(), error: vi.fn(), success: vi.fn() }
21
+ const platform = new SwitchBotMatterPlatform(log as any, { enableOnOffLight: false } as any, api)
22
+
23
+ // Build a fake serialized accessory matching the generated UUID for OnOff Light
24
+ const uuid = api.matter.uuid.generate('matter-onoff-light')
25
+ const accessory: any = { uuid, displayName: 'OnOff Light', context: { deviceId: 'NOT-A-MAC' } }
26
+
27
+ // Put invalid timer object into refreshTimers to ensure code handles it
28
+ const nid = (platform as any).normalizeDeviceId(accessory.context.deviceId)
29
+ ;(platform as any).refreshTimers.set(nid, null as any)
30
+ ;(platform as any).accessoryInstances.set(nid, { dummy: true })
31
+
32
+ // Insert accessory into matterAccessories so removeDisabledAccessories will see it
33
+ ;(platform as any).matterAccessories.set(uuid, accessory)
34
+
35
+ // Spy on clearDeviceResources
36
+ const spyClear = vi.spyOn(platform as any, 'clearDeviceResources')
37
+
38
+ // Call removeDisabledAccessories directly
39
+ await (platform as any).removeDisabledAccessories()
40
+
41
+ // Expect unregister was called and matterAccessories cleared
42
+ expect(unregister).toHaveBeenCalled()
43
+ expect((platform as any).matterAccessories.get(uuid)).toBeUndefined()
44
+
45
+ // clearDeviceResources should have been called with the accessory.deviceId
46
+ expect(spyClear).toHaveBeenCalledWith(accessory.context.deviceId)
47
+ })
48
+ })
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
@@ -0,0 +1,96 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { createPlatformProxy, detectMatter } from './utils.js'
4
+
5
+ describe('detectMatter', () => {
6
+ it('returns enabled true when api.isMatterEnabled is a function that returns true', () => {
7
+ const api: any = { isMatterEnabled: () => true }
8
+ const info = detectMatter(api)
9
+ expect(info.enabled).toBe(true)
10
+ expect(info.reason).toMatch(/isMatterEnabled\(\)/)
11
+ })
12
+
13
+ it('returns enabled true when api.isMatterEnabled is a truthy property', () => {
14
+ const api: any = { isMatterEnabled: true }
15
+ const info = detectMatter(api)
16
+ expect(info.enabled).toBe(true)
17
+ expect(info.reason).toMatch(/property present/)
18
+ })
19
+
20
+ it('uses server fallback when present', () => {
21
+ const api: any = { server: { isMatterEnabled: () => false } }
22
+ const info = detectMatter(api)
23
+ expect(info.enabled).toBe(false)
24
+ expect(info.reason).toMatch(/server.isMatterEnabled\(\)/)
25
+ })
26
+
27
+ it('returns enabled false with a reason when no API is present', () => {
28
+ const api: any = {}
29
+ const info = detectMatter(api)
30
+ expect(info.enabled).toBe(false)
31
+ expect(typeof info.reason).toBe('string')
32
+ })
33
+ })
34
+
35
+ describe('createPlatformProxy', () => {
36
+ it('selects HAP platform when Matter is disabled and delegates configureAccessory', () => {
37
+ class HapStub {
38
+ public static created = false
39
+ public configured: any = undefined
40
+ constructor(public log: any, public config: any, public api: any) {
41
+ HapStub.created = true
42
+ }
43
+
44
+ configureAccessory(acc: any) {
45
+ this.configured = acc
46
+ }
47
+ }
48
+
49
+ class MatterStub {
50
+ constructor() {
51
+ throw new Error('should not be constructed')
52
+ }
53
+ }
54
+
55
+ const ProxyCtor = createPlatformProxy(HapStub as any, MatterStub as any)
56
+ const log = { info: vi.fn() }
57
+ const api = { isMatterEnabled: false }
58
+ const proxy = new (ProxyCtor as any)(log, {}, api)
59
+
60
+ // delegate should be instance of HapStub
61
+ expect(HapStub.created).toBe(true)
62
+ expect(typeof proxy.delegate.configureAccessory).toBe('function')
63
+ proxy.configureAccessory('accessory')
64
+ expect(proxy.delegate.configured).toBe('accessory')
65
+ // log should mention HAP
66
+ expect((log.info as any).mock.calls[0][0]).toMatch(/HAP/)
67
+ })
68
+
69
+ it('selects Matter platform when isMatterEnabled is true', () => {
70
+ class HapStub2 {
71
+ constructor() {
72
+ throw new Error('should not be constructed')
73
+ }
74
+ }
75
+
76
+ class MatterStub2 {
77
+ public configured: any = undefined
78
+ constructor(public log: any, public config: any, public api: any) {}
79
+
80
+ configureMatterAccessory(acc: any) {
81
+ this.configured = acc
82
+ }
83
+ }
84
+
85
+ const ProxyCtor = createPlatformProxy(HapStub2 as any, MatterStub2 as any)
86
+ const log = { info: vi.fn() }
87
+ const api = { isMatterEnabled: () => true }
88
+ const proxy = new (ProxyCtor as any)(log, {}, api)
89
+
90
+ // delegate should be instance of MatterStub2
91
+ expect(typeof proxy.delegate.configureMatterAccessory).toBe('function')
92
+ ;(proxy as any).configureMatterAccessory('macc')
93
+ expect(proxy.delegate.configured).toBe('macc')
94
+ expect((log.info as any).mock.calls[0][0]).toMatch(/Matter/)
95
+ })
96
+ })
package/src/utils.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  *
3
3
  * util.ts: @switchbot/homebridge-switchbot platform class.
4
4
  */
5
+ import type { API, Logging } from 'homebridge'
5
6
  import type { blindTilt, curtain, curtain3, device } from 'node-switchbot'
6
7
 
7
8
  import type { devicesConfig } from './settings.js'
@@ -716,3 +717,257 @@ export function cleanDeviceConfig(deviceConfig?: Record<string, Record<string, a
716
717
  }
717
718
  return undefined
718
719
  }
720
+
721
+ /**
722
+ * Factory that returns a function to send OpenAPI commands using a retry wrapper.
723
+ *
724
+ * @param retryCommandFunc - bound function that calls platform.retryCommand(device, bodyChange, maxRetries, delay)
725
+ * @param deviceObj - the device object to operate on
726
+ * @param opts - optional overrides for maxRetries and delayBetweenRetries
727
+ * @param opts.maxRetries - override for maxRetries
728
+ * @param opts.delayBetweenRetries - override for delayBetweenRetries
729
+ */
730
+ export function makeOpenAPISender(retryCommandFunc: any, deviceObj: any, opts?: { maxRetries?: number, delayBetweenRetries?: number }) {
731
+ return async (command: string, parameter = 'default') => {
732
+ const bodyChange: any = { command, parameter, commandType: 'command' }
733
+ return retryCommandFunc(deviceObj, bodyChange, opts?.maxRetries, opts?.delayBetweenRetries)
734
+ }
735
+ }
736
+
737
+ /**
738
+ * Factory that returns a function to perform BLE actions using a SwitchBotBLE client.
739
+ * Handles discovery retries and method invocation on the discovered device instance.
740
+ *
741
+ * @param switchBotBLE - instance of SwitchBotBLE (may be undefined)
742
+ * @param deviceObj - the device object (used to obtain bleModel/deviceId)
743
+ * @param opts - optional retry settings
744
+ * @param opts.bleRetries - number of BLE discovery retries
745
+ * @param opts.bleRetryDelay - delay between BLE retries in ms
746
+ */
747
+ export function makeBLESender(switchBotBLE: any, deviceObj: any, opts?: { bleRetries?: number, bleRetryDelay?: number }) {
748
+ return async (methodName: string, ...args: any[]) => {
749
+ if (!switchBotBLE) {
750
+ throw new Error('Platform BLE not available')
751
+ }
752
+ const id = formatDeviceIdAsMac(deviceObj.deviceId)
753
+ const maxRetries = opts?.bleRetries ?? 2
754
+ const retryDelay = opts?.bleRetryDelay ?? 500
755
+ let attempt = 0
756
+ while (attempt < maxRetries) {
757
+ try {
758
+ const list = await switchBotBLE.discover({ model: (deviceObj as any).bleModel, id })
759
+ if (!Array.isArray(list) || list.length === 0) {
760
+ throw new Error('BLE device not found')
761
+ }
762
+ const deviceInst: any = list[0]
763
+ if (typeof deviceInst[methodName] !== 'function') {
764
+ throw new TypeError(`BLE method ${methodName} not available on device`)
765
+ }
766
+ return await deviceInst[methodName](...args)
767
+ } catch (e: any) {
768
+ attempt++
769
+ if (attempt >= maxRetries) {
770
+ throw e
771
+ }
772
+ await sleep(retryDelay)
773
+ }
774
+ }
775
+ throw new Error('BLE operation failed')
776
+ }
777
+ }
778
+
779
+ /**
780
+ * Decide effective connection type for a device given platform options.
781
+ * Mirrors the logic previously in platform-matter.
782
+ */
783
+ export function chooseConnectionType(platformOptions: any, deviceObj: any): 'BLE' | 'OpenAPI' {
784
+ if (deviceObj?.connectionType) {
785
+ return deviceObj.connectionType === 'BLE' ? 'BLE' : 'OpenAPI'
786
+ }
787
+ if (platformOptions?.BLE && (deviceObj?.bleModel || (typeof deviceObj?.deviceId === 'string' && deviceObj.deviceId.length > 0))) {
788
+ return 'BLE'
789
+ }
790
+ return 'OpenAPI'
791
+ }
792
+
793
+ /**
794
+ * Detect whether Matter is enabled/available on the provided Homebridge API object.
795
+ * This encapsulates the multi-fallback detection used across the project.
796
+ */
797
+ /**
798
+ * Detect whether Matter is enabled on the provided Homebridge API object.
799
+ * Returns an object with an `enabled` boolean and an optional `reason` string
800
+ * describing which check matched (useful for diagnostics).
801
+ */
802
+ export function detectMatter(apiObj: API): { enabled: boolean, reason?: string } {
803
+ try {
804
+ const maybe = (apiObj as any).isMatterEnabled
805
+ if (typeof maybe === 'function') {
806
+ return { enabled: Boolean(maybe.call(apiObj)), reason: 'api.isMatterEnabled() returned truthy' }
807
+ }
808
+ if (typeof maybe !== 'undefined') {
809
+ return { enabled: Boolean(maybe), reason: 'api.isMatterEnabled property present' }
810
+ }
811
+
812
+ const server = (apiObj as any).server ?? (apiObj as any).homebridgeServer ?? (apiObj as any).homebridge_server
813
+ const serverMaybe = server?.isMatterEnabled
814
+ if (typeof serverMaybe === 'function') {
815
+ return { enabled: Boolean(serverMaybe.call(server)), reason: 'server.isMatterEnabled() returned truthy' }
816
+ }
817
+ if (typeof server?.isMatterEnabled !== 'undefined') {
818
+ return { enabled: Boolean(server.isMatterEnabled), reason: 'server.isMatterEnabled property present' }
819
+ }
820
+ } catch (e: any) {
821
+ return { enabled: false, reason: `error during detection: ${String(e?.message ?? e)}` }
822
+ }
823
+ return { enabled: false, reason: 'no isMatterEnabled API or server fallback detected' }
824
+ }
825
+
826
+ /**
827
+ * Backwards-compatible boolean wrapper for detectMatter.
828
+ */
829
+ export function detectMatterEnabled(apiObj: API): boolean {
830
+ return detectMatter(apiObj).enabled
831
+ }
832
+
833
+ /**
834
+ * Create platform logging helpers used by both HAP and Matter platforms.
835
+ *
836
+ * getPlatformLogging may be either a synchronous string-returning function or an
837
+ * async function that resolves to the current platform logging setting. The
838
+ * returned helpers mirror the instance methods previously implemented on the
839
+ * HAP platform (infoLog, warnLog, errorLog, debugLog, etc.).
840
+ */
841
+ export function createPlatformLogger(getPlatformLogging: () => string | Promise<string | undefined>, log: Logging) {
842
+ const getPL = async () => {
843
+ try {
844
+ return await getPlatformLogging()
845
+ } catch {
846
+ return undefined
847
+ }
848
+ }
849
+
850
+ const loggingIsDebug = async () => {
851
+ const pl = await getPL()
852
+ return pl === 'debugMode' || pl === 'debug'
853
+ }
854
+
855
+ const enablingPlatformLogging = async () => {
856
+ const pl = await getPL()
857
+ return pl === 'debugMode' || pl === 'debug' || pl === 'standard'
858
+ }
859
+
860
+ return {
861
+ infoLog: async (...args: any[]) => {
862
+ if (await enablingPlatformLogging()) {
863
+ log.info(String(...args))
864
+ }
865
+ },
866
+ successLog: async (...args: any[]) => {
867
+ if (await enablingPlatformLogging()) {
868
+ // Some Logging implementations expose `success` — call if present
869
+ ;(log as any).success?.(String(...args)) ?? log.info(String(...args))
870
+ }
871
+ },
872
+ debugSuccessLog: async (...args: any[]) => {
873
+ if (await enablingPlatformLogging()) {
874
+ if (await loggingIsDebug()) {
875
+ ;(log as any).success?.('[DEBUG]', String(...args)) ?? log.info('[DEBUG]', String(...args))
876
+ }
877
+ }
878
+ },
879
+ warnLog: async (...args: any[]) => {
880
+ if (await enablingPlatformLogging()) {
881
+ log.warn(String(...args))
882
+ }
883
+ },
884
+ debugWarnLog: async (...args: any[]) => {
885
+ if (await enablingPlatformLogging()) {
886
+ if (await loggingIsDebug()) {
887
+ log.warn('[DEBUG]', String(...args))
888
+ }
889
+ }
890
+ },
891
+ errorLog: async (...args: any[]) => {
892
+ if (await enablingPlatformLogging()) {
893
+ log.error(String(...args))
894
+ }
895
+ },
896
+ debugErrorLog: async (...args: any[]) => {
897
+ if (await enablingPlatformLogging()) {
898
+ if (await loggingIsDebug()) {
899
+ log.error('[DEBUG]', String(...args))
900
+ }
901
+ }
902
+ },
903
+ debugLog: async (...args: any[]) => {
904
+ if (await enablingPlatformLogging()) {
905
+ const pl = await getPL()
906
+ if (pl === 'debug') {
907
+ log.info('[DEBUG]', String(...args))
908
+ } else if (pl === 'debugMode') {
909
+ log.debug(String(...args))
910
+ }
911
+ }
912
+ },
913
+ loggingIsDebug,
914
+ enablingPlatformLogging,
915
+ }
916
+ }
917
+
918
+ /**
919
+ * Create a Platform proxy class that selects between two platform constructors
920
+ * (HAP vs Matter) at runtime using `detectMatter`. Returns a class suitable
921
+ * for passing to `api.registerPlatform`.
922
+ */
923
+ export function createPlatformProxy(HAPCtor: any, MatterCtor: any) {
924
+ return class PlatformProxy {
925
+ delegate: any
926
+
927
+ constructor(public readonly log: any, public readonly config: any, public readonly api: API) {
928
+ const matterInfo = detectMatter(this.api)
929
+ const isMatter = matterInfo.enabled
930
+ const reason = matterInfo.reason ? ` Reason: ${matterInfo.reason}` : ''
931
+ this.log.info?.(`Homebridge SwitchBot Plugin initializing in ${isMatter ? 'Matter' : 'HAP'} mode.`)
932
+ this.log.debug?.(`Homebridge SwitchBot Plugin initializing in ${isMatter ? 'Matter' : 'HAP'} mode.${reason}`)
933
+ const PlatformCtor = isMatter ? MatterCtor : HAPCtor
934
+ this.delegate = new PlatformCtor(this.log, this.config, this.api)
935
+ }
936
+
937
+ configureAccessory(accessory: any): void {
938
+ try {
939
+ if (this.delegate && typeof this.delegate.configureAccessory === 'function') {
940
+ return this.delegate.configureAccessory(accessory)
941
+ }
942
+ } catch (e) {
943
+ // swallow — preserve previous behaviour where delegate errors don't bubble here
944
+ }
945
+ }
946
+
947
+ configureMatterAccessory?(accessory: any): void {
948
+ try {
949
+ if (this.delegate && typeof this.delegate.configureMatterAccessory === 'function') {
950
+ return this.delegate.configureMatterAccessory(accessory)
951
+ }
952
+ } catch (e) {
953
+ // swallow — delegate may not implement this or may throw
954
+ }
955
+ }
956
+
957
+ get accessories(): any {
958
+ try {
959
+ return this.delegate?.accessories
960
+ } catch (e) {
961
+ return undefined
962
+ }
963
+ }
964
+
965
+ get matterAccessories(): any {
966
+ try {
967
+ return this.delegate?.matterAccessories
968
+ } catch (e) {
969
+ return undefined
970
+ }
971
+ }
972
+ }
973
+ }
@@ -1,6 +1,7 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
1
  import type { SwitchBotPlatformConfig } from './settings.js'
3
2
 
3
+ import { describe, expect, it } from 'vitest'
4
+
4
5
  // Create a minimal mock of the SwitchBotPlatform to test verifyConfig
5
6
  class MockSwitchBotPlatform {
6
7
  config: SwitchBotPlatformConfig
@@ -102,10 +103,10 @@ describe('verifyConfig fix for reboot loop', () => {
102
103
 
103
104
  // This should NOT throw an error anymore - it should log instead
104
105
  await expect(platform.verifyConfig()).resolves.not.toThrow()
105
-
106
+
106
107
  // Verify that the error was logged instead of thrown
107
108
  expect(platform.errorLogCalls).toContain(
108
- 'The devices config section is missing the *Device ID* in the config. Please check your config.'
109
+ 'The devices config section is missing the *Device ID* in the config. Please check your config.',
109
110
  )
110
111
  })
111
112
 
@@ -132,10 +133,10 @@ describe('verifyConfig fix for reboot loop', () => {
132
133
 
133
134
  // This should NOT throw an error anymore - it should log instead
134
135
  await expect(platform.verifyConfig()).resolves.not.toThrow()
135
-
136
+
136
137
  // Verify that the error was logged instead of thrown
137
138
  expect(platform.errorLogCalls).toContain(
138
- 'The devices config section is missing the *Device Type* in the config. Please check your config.'
139
+ 'The devices config section is missing the *Device Type* in the config. Please check your config.',
139
140
  )
140
141
  })
141
142
 
@@ -187,11 +188,11 @@ describe('verifyConfig fix for reboot loop', () => {
187
188
 
188
189
  // Should not throw or log device config errors with valid config
189
190
  await expect(platform.verifyConfig()).resolves.not.toThrow()
190
-
191
+
191
192
  // Should not have device config errors
192
- expect(platform.errorLogCalls.filter(msg =>
193
- msg.includes('missing the *Device ID*') ||
194
- msg.includes('missing the *Device Type*')
193
+ expect(platform.errorLogCalls.filter(msg =>
194
+ msg.includes('missing the *Device ID*')
195
+ || msg.includes('missing the *Device Type*'),
195
196
  )).toHaveLength(0)
196
197
  })
197
- })
198
+ })