@switchbot/homebridge-switchbot 5.0.0-beta.26 → 5.0.0-beta.28

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.
@@ -37,6 +37,43 @@ class PluginUiServer extends HomebridgePluginUiServer {
37
37
  return []
38
38
  }
39
39
  })
40
+ // Provide Matter cached accessories if Homebridge stores them separately.
41
+ this.onRequest('getCachedMatterAccessories', () => {
42
+ try {
43
+ const plugin = 'homebridge-switchbot'
44
+ const devicesToReturn: any[] = []
45
+
46
+ const accFile = `${this.homebridgeStoragePath}/accessories/cachedAccessories`
47
+ const matterFile = `${this.homebridgeStoragePath}/accessories/cachedMatterAccessories`
48
+
49
+ const readAndCollect = (filePath: string) => {
50
+ if (!fs.existsSync(filePath)) {
51
+ return
52
+ }
53
+ try {
54
+ const parsed: any[] = JSON.parse(fs.readFileSync(filePath, 'utf8'))
55
+ parsed.forEach((entry: any) => {
56
+ // Entry shape varies between Homebridge versions; try common locations
57
+ const pluginName = entry.plugin || entry?.accessory?.plugin || entry?.accessory?.pluginName
58
+ const acc = entry.accessory ?? entry
59
+ if (pluginName === plugin) {
60
+ devicesToReturn.push(acc as never)
61
+ }
62
+ })
63
+ } catch {
64
+ // ignore parse errors for a single file
65
+ }
66
+ }
67
+
68
+ // Read both canonical files (some Homebridge versions use one or the other)
69
+ readAndCollect(accFile)
70
+ readAndCollect(matterFile)
71
+
72
+ return devicesToReturn
73
+ } catch {
74
+ return []
75
+ }
76
+ })
40
77
  this.ready()
41
78
  }
42
79
  }
@@ -144,6 +144,20 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
144
144
  devices: config.devices as { deviceId: string }[],
145
145
  }
146
146
 
147
+ // Determine platform logging preference (match HAP behaviour as closely as
148
+ // possible using config values. We default to 'standard' when unspecified.)
149
+ this.platformLogging = (this.config.options?.logging === 'debug' || this.config.options?.logging === 'standard' || this.config.options?.logging === 'none')
150
+ ? this.config.options.logging
151
+ : 'standard'
152
+
153
+ // Unconditional diagnostic using the raw Homebridge `log` so it always
154
+ // appears regardless of the platform logging helpers' gating logic.
155
+ try {
156
+ this.log.debug?.(`[SwitchBot HAP] effective platformLogging=${String(this.platformLogging)}`)
157
+ } catch (e: any) {
158
+ // swallow any logging errors — diagnostics are best-effort
159
+ }
160
+
147
161
  // Normalize deviceConfig to remove UI-inserted defaults (lots of false/empty values)
148
162
  try {
149
163
  if ((this.config as any).options) {
@@ -65,7 +65,8 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
65
65
  private webhookEventListener: Server | null = null
66
66
  private webhookEventHandler: { [x: string]: (context: any) => void } = {}
67
67
  // Platform logging toggle (can be controlled via UI or config)
68
- private platformLogging?: boolean
68
+ // Use same shape as HAP platform: string values like 'debug', 'debugMode', 'standard', or 'none'
69
+ private platformLogging?: string
69
70
 
70
71
  // Platform-provided logging helpers (attached in constructor)
71
72
  infoLog!: (...args: any[]) => void
@@ -84,6 +85,20 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
84
85
  public readonly config: SwitchBotPlatformConfig,
85
86
  public readonly api: API,
86
87
  ) {
88
+ // Determine platform logging preference (match HAP behaviour as closely as
89
+ // possible using config values. We default to 'standard' when unspecified.)
90
+ this.platformLogging = (this.config.options?.logging === 'debug' || this.config.options?.logging === 'standard' || this.config.options?.logging === 'none')
91
+ ? this.config.options.logging
92
+ : 'standard'
93
+
94
+ // Unconditional diagnostic using the raw Homebridge `log` so it always
95
+ // appears regardless of the platform logging helpers' gating logic.
96
+ try {
97
+ this.log.debug?.(`[SwitchBot Matter] effective platformLogging=${String(this.platformLogging)}`)
98
+ } catch (e: any) {
99
+ // swallow any logging errors — diagnostics are best-effort
100
+ }
101
+
87
102
  // Attach platform-wide logging helpers from utils so Matter and device
88
103
  // classes can use consistent logging methods (infoLog/debugLog/etc.)
89
104
  const _pl = createPlatformLogger(async () => (this as any).platformLogging, this.log)
@@ -727,7 +742,15 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
727
742
  } else if (percentage < 40) {
728
743
  batChargeLevel = 1
729
744
  }
730
- await this.api.matter.updateAccessoryState(uuidLocal, 'powerSource', { batPercentRemaining, batChargeLevel })
745
+ try {
746
+ await this.api.matter.updateAccessoryState(uuidLocal, 'powerSource', { batPercentRemaining, batChargeLevel })
747
+ } catch (updateError: any) {
748
+ // Silently skip if powerSource cluster doesn't exist on this device
749
+ const msg = String(updateError?.message ?? updateError)
750
+ if (!msg.includes('does not exist') && !msg.includes('not found')) {
751
+ throw updateError
752
+ }
753
+ }
731
754
  } catch (e: any) {
732
755
  this.debugLog(`Failed to update battery state for ${dev.deviceId}: ${e?.message ?? e}`)
733
756
  }
@@ -896,6 +919,7 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
896
919
  // Immediate one-shot to populate initial state
897
920
  ;(async () => {
898
921
  try {
922
+ this.infoLog(`Performing initial OpenAPI refresh for ${dev.deviceId}`)
899
923
  const { response, statusCode } = await this.switchBotAPI!.getDeviceStatus(dev.deviceId, this.config.credentials?.token, this.config.credentials?.secret)
900
924
  const respAny: any = response
901
925
  const body = respAny?.body ?? respAny
@@ -908,14 +932,18 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
908
932
  if (statusCode === 100 || statusCode === 200) {
909
933
  const status = body?.status ?? body
910
934
  await this.applyStatusToAccessory(uuid, dev, status)
935
+ this.infoLog(`Initial OpenAPI refresh succeeded for ${dev.deviceId}`)
936
+ } else {
937
+ this.warnLog(`Initial OpenAPI refresh returned unexpected statusCode=${statusCode} for ${dev.deviceId}`)
911
938
  }
912
939
  } catch (e: any) {
913
- this.debugLog(`Initial OpenAPI refresh failed for ${dev.deviceId}: ${e?.message ?? e}`)
940
+ this.errorLog(`Initial OpenAPI refresh failed for ${dev.deviceId}: ${e?.message ?? e}`)
914
941
  }
915
942
  })()
916
943
 
917
944
  const timer = setInterval(async () => {
918
945
  try {
946
+ this.debugLog(`Performing periodic OpenAPI refresh for ${dev.deviceId}`)
919
947
  const { response, statusCode } = await this.switchBotAPI!.getDeviceStatus(dev.deviceId, this.config.credentials?.token, this.config.credentials?.secret)
920
948
  const respAny: any = response
921
949
  const body = respAny?.body ?? respAny
@@ -928,9 +956,11 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
928
956
  if (statusCode === 100 || statusCode === 200) {
929
957
  const status = body?.status ?? body
930
958
  await this.applyStatusToAccessory(uuid, dev, status)
959
+ } else {
960
+ this.debugLog(`Periodic OpenAPI refresh returned unexpected statusCode=${statusCode} for ${dev.deviceId}`)
931
961
  }
932
962
  } catch (e: any) {
933
- this.debugLog(`Periodic OpenAPI refresh failed for ${dev.deviceId}: ${e?.message ?? e}`)
963
+ this.errorLog(`Periodic OpenAPI refresh failed for ${dev.deviceId}: ${e?.message ?? e}`)
934
964
  }
935
965
  }, Number(refreshRateSec) * 1000)
936
966
 
@@ -1013,9 +1043,11 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
1013
1043
  const url = this.config.options?.webhookURL
1014
1044
  try {
1015
1045
  this.switchBotAPI?.setupWebhook(url)
1046
+ this.infoLog(`Webhook configured for URL: ${url}`)
1016
1047
  // Listen for webhook events
1017
1048
  this.switchBotAPI?.on('webhookEvent', (body: any) => {
1018
1049
  try {
1050
+ this.infoLog(`Received webhook event for device: ${body.context.deviceMac}`)
1019
1051
  if (this.config.options?.mqttURL) {
1020
1052
  const mac = body.context.deviceMac?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':')
1021
1053
  const options = this.config.options?.mqttPubOptions || {}
@@ -1236,7 +1268,18 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
1236
1268
  // some accessories expose updateState that accepts cluster and attributes
1237
1269
  await instance.updateState(cluster, attributes)
1238
1270
  } else {
1239
- await this.api.matter.updateAccessoryState(uuidLocal, cluster, attributes)
1271
+ try {
1272
+ await this.api.matter.updateAccessoryState(uuidLocal, cluster, attributes)
1273
+ } catch (updateError: any) {
1274
+ // Silently ignore "does not exist" errors for clusters that aren't
1275
+ // supported by this device type (e.g., powerSource on WindowBlind).
1276
+ const msg = String(updateError?.message ?? updateError)
1277
+ if (msg.includes('does not exist') || msg.includes('not found')) {
1278
+ this.debugLog(`Cluster ${cluster} not available on ${dev.deviceId}, skipping update`)
1279
+ return
1280
+ }
1281
+ throw updateError
1282
+ }
1240
1283
  }
1241
1284
  } catch (e: any) {
1242
1285
  this.debugLog(`safeUpdate failed for ${dev.deviceId} cluster=${cluster}: ${e?.message ?? e}`)