@switchbot/homebridge-switchbot 5.0.0-beta.32 → 5.0.0-beta.34

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.
@@ -55,7 +55,7 @@ import { TV } from './irdevice/tv.js'
55
55
  import { VacuumCleaner } from './irdevice/vacuumcleaner.js'
56
56
  import { WaterHeater } from './irdevice/waterheater.js'
57
57
  import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'
58
- import { ApiRequestTracker, cleanDeviceConfig, createPlatformLogger, formatDeviceIdAsMac, isBlindTiltDevice, isCurtainDevice, safeStringify, sleep } from './utils.js'
58
+ import { ApiRequestTracker, cleanDeviceConfig, createPlatformLogger, formatDeviceIdAsMac, isBlindTiltDevice, isCurtainDevice, isSuccessfulStatusCode, logStatusCode, mergeByDeviceId, safeStringify, sleep } from './utils.js'
59
59
 
60
60
  /**
61
61
  * HomebridgePlatform
@@ -543,8 +543,12 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
543
543
  }
544
544
  }
545
545
 
546
+ /**
547
+ * Check if an API status code indicates success
548
+ * @deprecated Use shared isSuccessfulStatusCode from utils.js instead
549
+ */
546
550
  private isSuccessfulResponse(apiStatusCode: number): boolean {
547
- return (apiStatusCode === 200 || apiStatusCode === 100)
551
+ return isSuccessfulStatusCode(apiStatusCode)
548
552
  }
549
553
 
550
554
  private async handleDevices(deviceLists: any[]) {
@@ -640,12 +644,12 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
640
644
  }
641
645
  }
642
646
 
647
+ /**
648
+ * Merge two arrays by deviceId
649
+ * @deprecated Use shared mergeByDeviceId from utils.js instead
650
+ */
643
651
  private mergeByDeviceId(a1: { deviceId: string }[], a2: any[]) {
644
- const normalizeDeviceId = (deviceId: string) => deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '')
645
- return a1.map((itm) => {
646
- const matchingItem = a2.find(item => normalizeDeviceId(item.deviceId) === normalizeDeviceId(itm.deviceId))
647
- return { ...matchingItem, ...itm }
648
- })
652
+ return mergeByDeviceId(a1, a2, false)
649
653
  }
650
654
 
651
655
  private async handleErrorResponse(apiStatusCode: number, retryCount: number, maxRetries: number, delayBetweenRetries: number) {
@@ -2772,41 +2776,13 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
2772
2776
  *
2773
2777
  * @param statusCode - The status code returned by the device.
2774
2778
  * @returns A promise that resolves when the logging is complete.
2779
+ * @deprecated Use shared logStatusCode from utils.js instead
2775
2780
  */
2776
2781
  async statusCode(statusCode: number): Promise<void> {
2777
- const messages: { [key: number]: string } = {
2778
- 151: `Command not supported by this device type, statusCode: ${statusCode}, Submit Feature Request Here:
2779
- https://tinyurl.com/SwitchBotFeatureRequest`,
2780
- 152: `Device not found, statusCode: ${statusCode}`,
2781
- 160: `Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`,
2782
- 161: `Device is offline, statusCode: ${statusCode}`,
2783
- 171: `is offline, statusCode: ${statusCode}`,
2784
- 190: `Requests reached the daily limit, statusCode: ${statusCode}`,
2785
- 100: `Command successfully sent, statusCode: ${statusCode}`,
2786
- 200: `Request successful, statusCode: ${statusCode}`,
2787
- 400: `Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request payload,
2788
- statusCode: ${statusCode}`,
2789
- 401: `Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`,
2790
- 403: `Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found,
2791
- statusCode: ${statusCode}`,
2792
- 404: `Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`,
2793
- 406: `Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server,
2794
- statusCode: ${statusCode}`,
2795
- 415: `Unsupported Media Type, The client has defined a contentType header that is not supported by the server, statusCode: ${statusCode}`,
2796
- 422: `Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for APIs for which
2797
- certain limits have been exceeded, statusCode: ${statusCode}`,
2798
- 429: `Too Many Requests, The client has exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`,
2799
- 500: `Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare,
2800
- statusCode: ${statusCode}`,
2801
- }
2802
-
2803
- const message = messages[statusCode] ?? `Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`
2804
-
2805
- if ([100, 200].includes(statusCode)) {
2806
- this.debugLog(message)
2807
- } else {
2808
- this.errorLog(message)
2809
- }
2782
+ await logStatusCode(statusCode, {
2783
+ debugLog: this.debugLog.bind(this),
2784
+ errorLog: this.errorLog.bind(this),
2785
+ })
2810
2786
  }
2811
2787
 
2812
2788
  async retryRequest(device: (device & devicesConfig) | (irdevice & irDevicesConfig), deviceMaxRetries: number, deviceDelayBetweenRetries: number): Promise<{ response: any, statusCode: deviceStatusRequest['statusCode'] }> {
@@ -42,7 +42,7 @@ import {
42
42
  WindowBlindAccessory,
43
43
  } from './devices-matter/index.js'
44
44
  import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'
45
- import { ApiRequestTracker, cleanDeviceConfig, createPlatformLogger, formatDeviceIdAsMac, hs2rgb, makeBLESender, makeOpenAPISender, rgb2hs, sleep } from './utils.js'
45
+ import { ApiRequestTracker, cleanDeviceConfig, createPlatformLogger, formatDeviceIdAsMac, hs2rgb, isSuccessfulStatusCode, makeBLESender, makeOpenAPISender, mergeByDeviceId, normalizeDeviceId, rgb2hs, sleep } from './utils.js'
46
46
 
47
47
  export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
48
48
  // Track restored HAP cached accessories (required for DynamicPlatformPlugin)
@@ -365,9 +365,10 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
365
365
 
366
366
  /**
367
367
  * Normalize a deviceId for matching (uppercase alphanumerics only)
368
+ * @deprecated Use shared normalizeDeviceId from utils.js instead
368
369
  */
369
370
  private normalizeDeviceId(deviceId: string) {
370
- return (deviceId ?? '').toUpperCase().replace(/[^A-Z0-9]+/g, '')
371
+ return normalizeDeviceId(deviceId)
371
372
  }
372
373
 
373
374
  /** Determine the platform-level batch interval in seconds */
@@ -420,21 +421,11 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
420
421
  /**
421
422
  * Merge two arrays by deviceId. For each item in a1 (user-provided devices list),
422
423
  * find matching item in a2 (discovered devices) and merge them with user overrides last.
424
+ * @deprecated Use shared mergeByDeviceId from utils.js instead
423
425
  */
424
426
  private mergeByDeviceId(a1: { deviceId: string }[], a2: any[]) {
425
427
  const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices)
426
- const result: any[] = []
427
- for (const itm of (a1 || [])) {
428
- const matchingItem = (a2 || []).find(item => this.normalizeDeviceId(item.deviceId) === this.normalizeDeviceId(itm.deviceId))
429
- if (matchingItem) {
430
- result.push(Object.assign({}, matchingItem, itm))
431
- } else if (allowConfigOnly) {
432
- // include config-only device as-is when explicitly allowed
433
- result.push(Object.assign({}, itm))
434
- }
435
- // otherwise skip config-only entries
436
- }
437
- return result
428
+ return mergeByDeviceId(a1, a2, allowConfigOnly)
438
429
  }
439
430
 
440
431
  /**
@@ -993,7 +984,7 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
993
984
  } catch (e) {
994
985
  this.debugLog(`OpenAPI getDeviceStatus for ${dev.deviceId} returned statusCode=${statusCode} (body not stringifiable)`)
995
986
  }
996
- if (!(statusCode === 100 || statusCode === 200)) {
987
+ if (!isSuccessfulStatusCode(statusCode)) {
997
988
  return
998
989
  }
999
990
  const status = body?.status ?? body
@@ -1035,7 +1026,7 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
1035
1026
  } catch (e) {
1036
1027
  this.debugLog(`Initial OpenAPI refresh for ${dev.deviceId} returned statusCode=${statusCode} (body not stringifiable)`)
1037
1028
  }
1038
- if (statusCode === 100 || statusCode === 200) {
1029
+ if (isSuccessfulStatusCode(statusCode)) {
1039
1030
  const status = body?.status ?? body
1040
1031
  await this.applyStatusToAccessory(uuid, dev, status)
1041
1032
  this.infoLog(`Initial OpenAPI refresh succeeded for ${dev.deviceId}`)
@@ -1091,7 +1082,7 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
1091
1082
  this.apiTracker?.track()
1092
1083
  const { response, statusCode } = await this.switchBotAPI.getDevices()
1093
1084
  this.debugLog(`SwitchBot getDevices response status: ${statusCode}`)
1094
- if (statusCode === 100 || statusCode === 200) {
1085
+ if (isSuccessfulStatusCode(statusCode)) {
1095
1086
  const deviceList = Array.isArray(response?.body?.deviceList) ? response.body.deviceList : []
1096
1087
  this.discoveredDevices = deviceList
1097
1088
  this.infoLog(`Discovered ${deviceList.length} SwitchBot device(s) from OpenAPI`)
@@ -2300,7 +2291,7 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
2300
2291
  const { response, statusCode } = await this.switchBotAPI!.getDeviceStatus(deviceId, this.config.credentials?.token, this.config.credentials?.secret)
2301
2292
  const respAny: any = response
2302
2293
  const body = respAny?.body ?? respAny
2303
- if (statusCode === 100 || statusCode === 200) {
2294
+ if (isSuccessfulStatusCode(statusCode)) {
2304
2295
  const status = body?.status ?? body
2305
2296
  this.deviceStatusCache.set(this.normalizeDeviceId(deviceId), { status, timestamp: Date.now() })
2306
2297
  this.debugLog(`OpenAPI refresh succeeded for ${deviceId} (attempt ${attempt + 1})`)
package/src/utils.ts CHANGED
@@ -1132,3 +1132,158 @@ export class ApiRequestTracker {
1132
1132
  return this.date
1133
1133
  }
1134
1134
  }
1135
+
1136
+ /**
1137
+ * Normalize a deviceId for matching (uppercase alphanumerics only)
1138
+ */
1139
+ export function normalizeDeviceId(deviceId: string): string {
1140
+ return (deviceId ?? '').toUpperCase().replace(/[^A-Z0-9]+/g, '')
1141
+ }
1142
+
1143
+ /**
1144
+ * Merge two arrays by deviceId. For each item in a1 (user-provided devices list),
1145
+ * find matching item in a2 (discovered devices) and merge them with user overrides last.
1146
+ */
1147
+ export function mergeByDeviceId(a1: { deviceId: string }[], a2: any[], allowConfigOnly = false): any[] {
1148
+ const result: any[] = []
1149
+ for (const itm of (a1 || [])) {
1150
+ const matchingItem = (a2 || []).find(item => normalizeDeviceId(item.deviceId) === normalizeDeviceId(itm.deviceId))
1151
+ if (matchingItem) {
1152
+ result.push({ ...matchingItem, ...itm })
1153
+ } else if (allowConfigOnly) {
1154
+ result.push(itm)
1155
+ }
1156
+ }
1157
+ return result
1158
+ }
1159
+
1160
+ /**
1161
+ * Check if an API status code indicates success
1162
+ */
1163
+ export function isSuccessfulStatusCode(statusCode: number): boolean {
1164
+ return statusCode === 200 || statusCode === 100
1165
+ }
1166
+
1167
+ /**
1168
+ * Log status code messages with appropriate log level
1169
+ */
1170
+ export async function logStatusCode(statusCode: number, log: {
1171
+ debugLog: (...args: any[]) => void | Promise<void>
1172
+ errorLog: (...args: any[]) => void | Promise<void>
1173
+ }): Promise<void> {
1174
+ const messages: { [key: number]: string } = {
1175
+ 151: `Command not supported by this device type, statusCode: ${statusCode}, Submit Feature Request Here:
1176
+ https://tinyurl.com/SwitchBotFeatureRequest`,
1177
+ 152: `Device not found, statusCode: ${statusCode}`,
1178
+ 160: `Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`,
1179
+ 161: `Device is offline, statusCode: ${statusCode}`,
1180
+ 171: `is offline, statusCode: ${statusCode}`,
1181
+ 190: `Requests reached the daily limit, statusCode: ${statusCode}`,
1182
+ 100: `Command successfully sent, statusCode: ${statusCode}`,
1183
+ 200: `Request successful, statusCode: ${statusCode}`,
1184
+ 400: `Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request payload,
1185
+ statusCode: ${statusCode}`,
1186
+ 401: `Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`,
1187
+ 403: `Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found,
1188
+ statusCode: ${statusCode}`,
1189
+ 404: `Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`,
1190
+ 406: `Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server,
1191
+ statusCode: ${statusCode}`,
1192
+ 415: `Unsupported Media Type, The client has defined a contentType header that is not supported by the server, statusCode: ${statusCode}`,
1193
+ 422: `Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for APIs for which
1194
+ certain limits have been exceeded, statusCode: ${statusCode}`,
1195
+ 429: `Too Many Requests, The client has exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`,
1196
+ 500: `Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare,
1197
+ statusCode: ${statusCode}`,
1198
+ }
1199
+
1200
+ const message = messages[statusCode] ?? `Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`
1201
+
1202
+ if ([100, 200].includes(statusCode)) {
1203
+ await log.debugLog(message)
1204
+ } else {
1205
+ await log.errorLog(message)
1206
+ }
1207
+ }
1208
+
1209
+ /**
1210
+ * Shared device logging helpers
1211
+ */
1212
+
1213
+ /**
1214
+ * Check if device logging is in debug mode
1215
+ */
1216
+ export function deviceLoggingIsDebug(deviceLogging?: string): boolean {
1217
+ return deviceLogging === 'debugMode' || deviceLogging === 'debug'
1218
+ }
1219
+
1220
+ /**
1221
+ * Check if device logging is enabled
1222
+ */
1223
+ export function deviceLoggingEnabled(deviceLogging?: string, platformLogging?: string): boolean {
1224
+ // If deviceLogging isn't provided, fall back to platform-wide flag
1225
+ if (deviceLogging === undefined || deviceLogging === '') {
1226
+ return platformLogging === 'debugMode' || platformLogging === 'debug' || platformLogging === 'standard'
1227
+ }
1228
+ return deviceLogging === 'debugMode' || deviceLogging === 'debug' || deviceLogging === 'standard'
1229
+ }
1230
+
1231
+ /**
1232
+ * Device status code handler with comprehensive messages
1233
+ */
1234
+ export interface DeviceStatusCodeLogger {
1235
+ debugLog: (...args: any[]) => void | Promise<void>
1236
+ debugErrorLog?: (...args: any[]) => void | Promise<void>
1237
+ errorLog: (...args: any[]) => void | Promise<void>
1238
+ infoLog?: (...args: any[]) => void | Promise<void>
1239
+ }
1240
+
1241
+ export async function logDeviceStatusCode(
1242
+ statusCode: number,
1243
+ log: DeviceStatusCodeLogger,
1244
+ deviceId?: string,
1245
+ hubDeviceId?: string,
1246
+ ): Promise<void> {
1247
+ let adjustedStatusCode = statusCode
1248
+
1249
+ // Handle special case where device is its own hub
1250
+ if (statusCode === 171 && hubDeviceId && deviceId && (hubDeviceId === deviceId || hubDeviceId === '000000000000')) {
1251
+ if (log.debugErrorLog) {
1252
+ log.debugErrorLog(`statusCode 171 changed to 161: hubDeviceId ${hubDeviceId} matches deviceId ${deviceId}, device is its own hub.`)
1253
+ }
1254
+ adjustedStatusCode = 161
1255
+ }
1256
+
1257
+ const statusMessages: { [key: number]: string } = {
1258
+ 151: 'Command not supported by this device type',
1259
+ 152: 'Device not found',
1260
+ 160: 'Command is not supported',
1261
+ 161: 'Device is offline',
1262
+ 171: hubDeviceId ? `Hub Device is offline. Hub: ${hubDeviceId}` : 'Hub Device is offline',
1263
+ 190: 'Device internal error due to device states not synchronized with server, or command format is invalid',
1264
+ 100: 'Command successfully sent',
1265
+ 200: 'Request successful',
1266
+ 400: 'Bad Request, an invalid payload request',
1267
+ 401: 'Unauthorized, Authorization for the API is required, but the request has not been authenticated',
1268
+ 403: 'Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found',
1269
+ 404: 'Not Found, Specifies the requested path does not exist',
1270
+ 406: 'Not Acceptable, a MIME type has been requested via the Accept header for a value not supported by the server',
1271
+ 415: 'Unsupported Media Type, a contentType header has been defined that is not supported by the server',
1272
+ 422: 'Unprocessable Entity: The server cannot process the request, often due to exceeded API limits.',
1273
+ 429: 'Too Many Requests, exceeded the number of requests allowed for a given time window',
1274
+ 500: 'Internal Server Error, An unexpected error occurred. These errors should be rare',
1275
+ }
1276
+
1277
+ const logMessage = statusMessages[adjustedStatusCode] || `Unknown statusCode: ${adjustedStatusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`
1278
+ const fullMessage = `${logMessage}, statusCode: ${adjustedStatusCode}`
1279
+
1280
+ if ([100, 200].includes(adjustedStatusCode)) {
1281
+ await log.debugLog(fullMessage)
1282
+ } else if (statusMessages[adjustedStatusCode]) {
1283
+ await log.errorLog(fullMessage)
1284
+ } else if (log.infoLog) {
1285
+ await log.infoLog(fullMessage)
1286
+ } else {
1287
+ await log.errorLog(fullMessage)
1288
+ }
1289
+ }