@switchbot/homebridge-switchbot 5.0.0-beta.13 → 5.0.0-beta.15

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/src/utils.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  *
3
3
  * util.ts: @switchbot/homebridge-switchbot platform class.
4
4
  */
5
- import type { API } from 'homebridge'
5
+ import type { API, Logging } from 'homebridge'
6
6
  import type { blindTilt, curtain, curtain3, device } from 'node-switchbot'
7
7
 
8
8
  import type { devicesConfig } from './settings.js'
@@ -718,6 +718,78 @@ export function cleanDeviceConfig(deviceConfig?: Record<string, Record<string, a
718
718
  return undefined
719
719
  }
720
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
+
721
793
  /**
722
794
  * Detect whether Matter is enabled/available on the provided Homebridge API object.
723
795
  * This encapsulates the multi-fallback detection used across the project.
@@ -758,6 +830,91 @@ export function detectMatterEnabled(apiObj: API): boolean {
758
830
  return detectMatter(apiObj).enabled
759
831
  }
760
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
+
761
918
  /**
762
919
  * Create a Platform proxy class that selects between two platform constructors
763
920
  * (HAP vs Matter) at runtime using `detectMatter`. Returns a class suitable
@@ -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
+ })