@switchbot/homebridge-switchbot 5.0.0-beta.6 → 5.0.0-beta.60
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/CHANGELOG.md +15 -0
- package/README.md +45 -3
- package/config.schema.json +866 -13754
- package/dist/devices-hap/airpurifier.d.ts.map +1 -1
- package/dist/devices-hap/airpurifier.js +12 -6
- package/dist/devices-hap/airpurifier.js.map +1 -1
- package/dist/devices-hap/blindtilt.js +3 -3
- package/dist/devices-hap/bot.d.ts.map +1 -1
- package/dist/devices-hap/bot.js +16 -5
- package/dist/devices-hap/bot.js.map +1 -1
- package/dist/devices-hap/ceilinglight.d.ts.map +1 -1
- package/dist/devices-hap/ceilinglight.js +13 -7
- package/dist/devices-hap/ceilinglight.js.map +1 -1
- package/dist/devices-hap/colorbulb.d.ts.map +1 -1
- package/dist/devices-hap/colorbulb.js +49 -9
- package/dist/devices-hap/colorbulb.js.map +1 -1
- package/dist/devices-hap/contact.js +3 -3
- package/dist/devices-hap/curtain.js +2 -2
- package/dist/devices-hap/curtain.js.map +1 -1
- package/dist/devices-hap/device.d.ts +18 -8
- package/dist/devices-hap/device.d.ts.map +1 -1
- package/dist/devices-hap/device.js +141 -69
- package/dist/devices-hap/device.js.map +1 -1
- package/dist/devices-hap/fan.d.ts.map +1 -1
- package/dist/devices-hap/fan.js +12 -6
- package/dist/devices-hap/fan.js.map +1 -1
- package/dist/devices-hap/hub.d.ts.map +1 -1
- package/dist/devices-hap/hub.js +6 -5
- package/dist/devices-hap/hub.js.map +1 -1
- package/dist/devices-hap/humidifier.d.ts +5 -0
- package/dist/devices-hap/humidifier.d.ts.map +1 -1
- package/dist/devices-hap/humidifier.js +92 -4
- package/dist/devices-hap/humidifier.js.map +1 -1
- package/dist/devices-hap/iosensor.d.ts.map +1 -1
- package/dist/devices-hap/iosensor.js +36 -21
- package/dist/devices-hap/iosensor.js.map +1 -1
- package/dist/devices-hap/lightstrip.d.ts.map +1 -1
- package/dist/devices-hap/lightstrip.js +38 -8
- package/dist/devices-hap/lightstrip.js.map +1 -1
- package/dist/devices-hap/lock.d.ts.map +1 -1
- package/dist/devices-hap/lock.js +14 -6
- package/dist/devices-hap/lock.js.map +1 -1
- package/dist/devices-hap/meter.d.ts.map +1 -1
- package/dist/devices-hap/meter.js +6 -5
- package/dist/devices-hap/meter.js.map +1 -1
- package/dist/devices-hap/meterplus.d.ts.map +1 -1
- package/dist/devices-hap/meterplus.js +6 -5
- package/dist/devices-hap/meterplus.js.map +1 -1
- package/dist/devices-hap/meterpro.d.ts.map +1 -1
- package/dist/devices-hap/meterpro.js +7 -6
- package/dist/devices-hap/meterpro.js.map +1 -1
- package/dist/devices-hap/motion.js +3 -3
- package/dist/devices-hap/plug.d.ts.map +1 -1
- package/dist/devices-hap/plug.js +11 -6
- package/dist/devices-hap/plug.js.map +1 -1
- package/dist/devices-hap/relayswitch.js +3 -3
- package/dist/devices-hap/robotvacuumcleaner.d.ts.map +1 -1
- package/dist/devices-hap/robotvacuumcleaner.js +13 -6
- package/dist/devices-hap/robotvacuumcleaner.js.map +1 -1
- package/dist/devices-hap/waterdetector.js +3 -3
- package/dist/devices-matter/BaseMatterAccessory.d.ts +27 -0
- package/dist/devices-matter/BaseMatterAccessory.d.ts.map +1 -1
- package/dist/devices-matter/BaseMatterAccessory.js +169 -5
- package/dist/devices-matter/BaseMatterAccessory.js.map +1 -1
- package/dist/devices-matter/ColorLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/ColorLightAccessory.js +12 -12
- package/dist/devices-matter/ColorLightAccessory.js.map +1 -1
- package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/ColorTemperatureLightAccessory.js +5 -7
- package/dist/devices-matter/ColorTemperatureLightAccessory.js.map +1 -1
- package/dist/devices-matter/DimmableLightAccessory.js +9 -9
- package/dist/devices-matter/DimmableLightAccessory.js.map +1 -1
- package/dist/devices-matter/ExtendedColorLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/ExtendedColorLightAccessory.js +14 -15
- package/dist/devices-matter/ExtendedColorLightAccessory.js.map +1 -1
- package/dist/devices-matter/OnOffLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/OnOffLightAccessory.js +8 -16
- package/dist/devices-matter/OnOffLightAccessory.js.map +1 -1
- package/dist/devices-matter/OnOffOutletAccessory.d.ts +2 -0
- package/dist/devices-matter/OnOffOutletAccessory.d.ts.map +1 -1
- package/dist/devices-matter/OnOffOutletAccessory.js +10 -7
- package/dist/devices-matter/OnOffOutletAccessory.js.map +1 -1
- package/dist/devices-matter/OnOffSwitchAccessory.js +2 -2
- package/dist/devices-matter/OnOffSwitchAccessory.js.map +1 -1
- package/dist/devices-matter/RoboticVacuumAccessory.d.ts +29 -43
- package/dist/devices-matter/RoboticVacuumAccessory.d.ts.map +1 -1
- package/dist/devices-matter/RoboticVacuumAccessory.js +287 -262
- package/dist/devices-matter/RoboticVacuumAccessory.js.map +1 -1
- package/dist/homebridge-ui/public/index.html +200 -18
- package/dist/homebridge-ui/server.js +82 -9
- package/dist/homebridge-ui/server.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -7
- package/dist/index.js.map +1 -1
- package/dist/irdevice/irdevice.d.ts +11 -10
- package/dist/irdevice/irdevice.d.ts.map +1 -1
- package/dist/irdevice/irdevice.js +76 -35
- package/dist/irdevice/irdevice.js.map +1 -1
- package/dist/platform-hap.d.ts +26 -15
- package/dist/platform-hap.d.ts.map +1 -1
- package/dist/platform-hap.js +333 -153
- package/dist/platform-hap.js.map +1 -1
- package/dist/platform-matter.d.ts +93 -6
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +1822 -224
- package/dist/platform-matter.js.map +1 -1
- package/dist/settings.d.ts +58 -7
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- package/dist/test/apiRequestTracker.test.d.ts +2 -0
- package/dist/test/apiRequestTracker.test.d.ts.map +1 -0
- package/dist/test/apiRequestTracker.test.js +392 -0
- package/dist/test/apiRequestTracker.test.js.map +1 -0
- package/dist/test/hap/device-webhook-context.test.d.ts +2 -0
- package/dist/test/hap/device-webhook-context.test.d.ts.map +1 -0
- package/dist/test/hap/device-webhook-context.test.js +128 -0
- package/dist/test/hap/device-webhook-context.test.js.map +1 -0
- package/dist/test/hap/platform-hap.logging.test.d.ts +2 -0
- package/dist/test/hap/platform-hap.logging.test.d.ts.map +1 -0
- package/dist/test/hap/platform-hap.logging.test.js +33 -0
- package/dist/test/hap/platform-hap.logging.test.js.map +1 -0
- package/dist/test/hap/platform-hap.test.d.ts +2 -0
- package/dist/test/hap/platform-hap.test.d.ts.map +1 -0
- package/dist/test/hap/platform-hap.test.js +62 -0
- package/dist/test/hap/platform-hap.test.js.map +1 -0
- package/dist/test/helpers/platform-fixtures.d.ts +9 -0
- package/dist/test/helpers/platform-fixtures.d.ts.map +1 -0
- package/dist/test/helpers/platform-fixtures.js +30 -0
- package/dist/test/helpers/platform-fixtures.js.map +1 -0
- package/dist/test/homebridge-ui/server.test.d.ts +2 -0
- package/dist/test/homebridge-ui/server.test.d.ts.map +1 -0
- package/dist/test/homebridge-ui/server.test.js +445 -0
- package/dist/test/homebridge-ui/server.test.js.map +1 -0
- package/dist/{index.test.d.ts.map → test/index.test.d.ts.map} +1 -1
- package/dist/test/index.test.js +19 -0
- package/dist/test/index.test.js.map +1 -0
- package/dist/test/matter/devices-matter/baseMatterAccessory.test.d.ts +2 -0
- package/dist/test/matter/devices-matter/baseMatterAccessory.test.d.ts.map +1 -0
- package/dist/test/matter/devices-matter/baseMatterAccessory.test.js +71 -0
- package/dist/test/matter/devices-matter/baseMatterAccessory.test.js.map +1 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.d.ts +2 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.d.ts.map +1 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.js +366 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.js.map +1 -0
- package/dist/test/matter/platform-matter.additional.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.additional.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.additional.test.js +35 -0
- package/dist/test/matter/platform-matter.additional.test.js.map +1 -0
- package/dist/test/matter/platform-matter.bleparse.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.bleparse.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.bleparse.test.js +43 -0
- package/dist/test/matter/platform-matter.bleparse.test.js.map +1 -0
- package/dist/test/matter/platform-matter.cleanup.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.cleanup.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.cleanup.test.js +70 -0
- package/dist/test/matter/platform-matter.cleanup.test.js.map +1 -0
- package/dist/test/matter/platform-matter.keepstale.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.keepstale.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.keepstale.test.js +27 -0
- package/dist/test/matter/platform-matter.keepstale.test.js.map +1 -0
- package/dist/test/matter/platform-matter.logging.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.logging.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.logging.test.js +29 -0
- package/dist/test/matter/platform-matter.logging.test.js.map +1 -0
- package/dist/test/matter/platform-matter.mapping.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.mapping.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.mapping.test.js +43 -0
- package/dist/test/matter/platform-matter.mapping.test.js.map +1 -0
- package/dist/test/matter/platform-matter.openapi-mapping.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.openapi-mapping.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.openapi-mapping.test.js +84 -0
- package/dist/test/matter/platform-matter.openapi-mapping.test.js.map +1 -0
- package/dist/test/matter/platform-matter.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.test.js +117 -0
- package/dist/test/matter/platform-matter.test.js.map +1 -0
- package/dist/test/matter/platform-matter.unregister.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.unregister.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.unregister.test.js +30 -0
- package/dist/test/matter/platform-matter.unregister.test.js.map +1 -0
- package/dist/test/matter/platform-matter.webhook.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.webhook.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.webhook.test.js +46 -0
- package/dist/test/matter/platform-matter.webhook.test.js.map +1 -0
- package/dist/test/utils.test.d.ts +2 -0
- package/dist/test/utils.test.d.ts.map +1 -0
- package/dist/test/utils.test.js +95 -0
- package/dist/test/utils.test.js.map +1 -0
- package/dist/test/verifyconfig.test.d.ts.map +1 -0
- package/dist/{verifyconfig.test.js → test/verifyconfig.test.js} +2 -2
- package/dist/test/verifyconfig.test.js.map +1 -0
- package/dist/utils.d.ts +204 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +713 -33
- package/dist/utils.js.map +1 -1
- package/docs/assets/highlight.css +14 -0
- package/docs/assets/main.js +2 -2
- package/docs/index.html +31 -2
- package/docs/variables/default.html +1 -1
- package/package.json +15 -15
- package/src/devices-hap/airpurifier.ts +11 -6
- package/src/devices-hap/blindtilt.ts +3 -3
- package/src/devices-hap/bot.ts +15 -5
- package/src/devices-hap/ceilinglight.ts +12 -7
- package/src/devices-hap/colorbulb.ts +46 -10
- package/src/devices-hap/contact.ts +3 -3
- package/src/devices-hap/curtain.ts +2 -2
- package/src/devices-hap/device.ts +149 -70
- package/src/devices-hap/fan.ts +11 -6
- package/src/devices-hap/hub.ts +6 -5
- package/src/devices-hap/humidifier.ts +97 -4
- package/src/devices-hap/iosensor.ts +36 -21
- package/src/devices-hap/lightstrip.ts +35 -8
- package/src/devices-hap/lock.ts +13 -6
- package/src/devices-hap/meter.ts +6 -5
- package/src/devices-hap/meterplus.ts +6 -5
- package/src/devices-hap/meterpro.ts +7 -6
- package/src/devices-hap/motion.ts +3 -3
- package/src/devices-hap/plug.ts +10 -6
- package/src/devices-hap/relayswitch.ts +3 -3
- package/src/devices-hap/robotvacuumcleaner.ts +12 -6
- package/src/devices-hap/waterdetector.ts +3 -3
- package/src/devices-matter/BaseMatterAccessory.ts +176 -5
- package/src/devices-matter/ColorLightAccessory.ts +12 -12
- package/src/devices-matter/ColorTemperatureLightAccessory.ts +5 -7
- package/src/devices-matter/DimmableLightAccessory.ts +9 -9
- package/src/devices-matter/ExtendedColorLightAccessory.ts +14 -15
- package/src/devices-matter/OnOffLightAccessory.ts +8 -16
- package/src/devices-matter/OnOffOutletAccessory.ts +12 -7
- package/src/devices-matter/OnOffSwitchAccessory.ts +2 -2
- package/src/devices-matter/RoboticVacuumAccessory.ts +340 -313
- package/src/homebridge-ui/public/index.html +200 -18
- package/src/homebridge-ui/server.ts +85 -9
- package/src/index.ts +4 -7
- package/src/irdevice/irdevice.ts +74 -35
- package/src/platform-hap.ts +365 -169
- package/src/platform-matter.ts +1872 -229
- package/src/settings.ts +62 -3
- package/src/test/apiRequestTracker.test.ts +417 -0
- package/src/test/hap/device-webhook-context.test.ts +136 -0
- package/src/test/hap/platform-hap.logging.test.ts +36 -0
- package/src/test/hap/platform-hap.test.ts +70 -0
- package/src/test/helpers/platform-fixtures.ts +33 -0
- package/src/test/homebridge-ui/server.test.ts +486 -0
- package/src/test/index.test.ts +24 -0
- package/src/test/matter/devices-matter/baseMatterAccessory.test.ts +88 -0
- package/src/test/matter/devices-matter/roboticVacuumAccessory.test.ts +453 -0
- package/src/test/matter/platform-matter.additional.test.ts +44 -0
- package/src/test/matter/platform-matter.bleparse.test.ts +47 -0
- package/src/test/matter/platform-matter.cleanup.test.ts +86 -0
- package/src/test/matter/platform-matter.keepstale.test.ts +37 -0
- package/src/test/matter/platform-matter.logging.test.ts +33 -0
- package/src/test/matter/platform-matter.mapping.test.ts +57 -0
- package/src/test/matter/platform-matter.openapi-mapping.test.ts +109 -0
- package/src/test/matter/platform-matter.test.ts +144 -0
- package/src/test/matter/platform-matter.unregister.test.ts +39 -0
- package/src/test/matter/platform-matter.webhook.test.ts +54 -0
- package/src/test/utils.test.ts +96 -0
- package/src/{verifyconfig.test.ts → test/verifyconfig.test.ts} +12 -11
- package/src/utils.ts +777 -36
- package/dist/index.test.js +0 -14
- package/dist/index.test.js.map +0 -1
- package/dist/verifyconfig.test.d.ts.map +0 -1
- package/dist/verifyconfig.test.js.map +0 -1
- package/src/index.test.ts +0 -19
- /package/dist/{index.test.d.ts → test/index.test.d.ts} +0 -0
- /package/dist/{verifyconfig.test.d.ts → test/verifyconfig.test.d.ts} +0 -0
package/src/platform-hap.ts
CHANGED
|
@@ -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 {
|
|
58
|
+
import { ApiRequestTracker, applyDeviceTypeTemplates, createPlatformLogger, formatDeviceIdAsMac, isBlindTiltDevice, isCurtainDevice, isSuccessfulStatusCode, logStatusCode, mergeByDeviceId, safeStringify, sleep } from './utils.js'
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* HomebridgePlatform
|
|
@@ -68,6 +68,18 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
68
68
|
public readonly api: API
|
|
69
69
|
public readonly log: Logging
|
|
70
70
|
|
|
71
|
+
// Logging helper functions (attached from utils.createPlatformLogger in constructor)
|
|
72
|
+
infoLog!: (...log: any[]) => Promise<void>
|
|
73
|
+
successLog!: (...log: any[]) => Promise<void>
|
|
74
|
+
debugSuccessLog!: (...log: any[]) => Promise<void>
|
|
75
|
+
warnLog!: (...log: any[]) => Promise<void>
|
|
76
|
+
debugWarnLog!: (...log: any[]) => Promise<void>
|
|
77
|
+
errorLog!: (...log: any[]) => Promise<void>
|
|
78
|
+
debugErrorLog!: (...log: any[]) => Promise<void>
|
|
79
|
+
debugLog!: (...log: any[]) => Promise<void>
|
|
80
|
+
loggingIsDebug!: () => Promise<boolean>
|
|
81
|
+
enablingPlatformLogging!: () => Promise<boolean>
|
|
82
|
+
|
|
71
83
|
// Configuration properties
|
|
72
84
|
platformConfig!: SwitchBotPlatformConfig
|
|
73
85
|
platformLogging!: options['logging']
|
|
@@ -88,6 +100,9 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
88
100
|
switchBotAPI!: SwitchBotOpenAPI
|
|
89
101
|
switchBotBLE!: SwitchBotBLE
|
|
90
102
|
|
|
103
|
+
// API request tracking
|
|
104
|
+
private apiTracker?: ApiRequestTracker
|
|
105
|
+
|
|
91
106
|
// External APIs
|
|
92
107
|
public readonly eve: any
|
|
93
108
|
public readonly fakegatoAPI: any
|
|
@@ -104,9 +119,22 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
104
119
|
this.api = api
|
|
105
120
|
this.log = log
|
|
106
121
|
|
|
122
|
+
// Attach shared platform logging helpers (moved to utils for reuse)
|
|
123
|
+
const _pl = createPlatformLogger(async () => this.platformLogging, this.log)
|
|
124
|
+
this.infoLog = _pl.infoLog
|
|
125
|
+
this.successLog = _pl.successLog
|
|
126
|
+
this.debugSuccessLog = _pl.debugSuccessLog
|
|
127
|
+
this.warnLog = _pl.warnLog
|
|
128
|
+
this.debugWarnLog = _pl.debugWarnLog
|
|
129
|
+
this.errorLog = _pl.errorLog
|
|
130
|
+
this.debugErrorLog = _pl.debugErrorLog
|
|
131
|
+
this.debugLog = _pl.debugLog
|
|
132
|
+
this.loggingIsDebug = _pl.loggingIsDebug
|
|
133
|
+
this.enablingPlatformLogging = _pl.enablingPlatformLogging
|
|
134
|
+
|
|
107
135
|
// only load if configured
|
|
108
136
|
if (!config) {
|
|
109
|
-
this.
|
|
137
|
+
this.errorLog('No configuration found for the plugin, please check your config.')
|
|
110
138
|
return
|
|
111
139
|
}
|
|
112
140
|
|
|
@@ -119,21 +147,23 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
119
147
|
devices: config.devices as { deviceId: string }[],
|
|
120
148
|
}
|
|
121
149
|
|
|
122
|
-
//
|
|
150
|
+
// Determine platform logging preference (match HAP behaviour as closely as
|
|
151
|
+
// possible using config values. We default to 'standard' when unspecified.)
|
|
152
|
+
this.platformLogging = (this.config.options?.logging === 'debug' || this.config.options?.logging === 'standard' || this.config.options?.logging === 'none')
|
|
153
|
+
? this.config.options.logging
|
|
154
|
+
: 'standard'
|
|
155
|
+
|
|
156
|
+
// Unconditional diagnostic using the raw Homebridge `log` so it always
|
|
157
|
+
// appears regardless of the platform logging helpers' gating logic.
|
|
123
158
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
;(this.config as any).options.deviceConfig = cleaned
|
|
128
|
-
} else {
|
|
129
|
-
// remove the empty deviceConfig so downstream checks treat it as absent
|
|
130
|
-
delete (this.config as any).options.deviceConfig
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
} catch (e) {
|
|
134
|
-
this.debugErrorLog(`Failed to clean deviceConfig: ${e}`)
|
|
159
|
+
this.log.debug?.(`[SwitchBot HAP] effective platformLogging=${String(this.platformLogging)}`)
|
|
160
|
+
} catch (e: any) {
|
|
161
|
+
// swallow any logging errors — diagnostics are best-effort
|
|
135
162
|
}
|
|
136
163
|
|
|
164
|
+
// Note: deviceConfig and irdeviceConfig have been removed from the platform.
|
|
165
|
+
// All device-specific configuration should be done via options.devices and options.irdevices arrays.
|
|
166
|
+
|
|
137
167
|
// Plugin Configuration
|
|
138
168
|
this.getPlatformLogSettings()
|
|
139
169
|
this.getPlatformRateSettings()
|
|
@@ -203,19 +233,39 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
203
233
|
// to start discovery of new accessories.
|
|
204
234
|
this.api.on('didFinishLaunching', async () => {
|
|
205
235
|
this.debugLog('Executed didFinishLaunching callback')
|
|
236
|
+
|
|
237
|
+
// Initialize API request tracking
|
|
238
|
+
if (this.switchBotAPI) {
|
|
239
|
+
try {
|
|
240
|
+
const dailyApiLimit = this.config.options?.dailyApiLimit ?? 10000
|
|
241
|
+
const dailyApiReserveForCommands = this.config.options?.dailyApiReserveForCommands ?? 1000
|
|
242
|
+
const webhookOnlyOnReserve = this.config.options?.webhookOnlyOnReserve ?? false
|
|
243
|
+
this.apiTracker = new ApiRequestTracker(this.api, this.log, 'SwitchBot HAP', {
|
|
244
|
+
dailyLimit: dailyApiLimit,
|
|
245
|
+
reserveForCommands: dailyApiReserveForCommands,
|
|
246
|
+
pausePollingAtReserve: webhookOnlyOnReserve,
|
|
247
|
+
resetAtLocalMidnight: this.config.options?.dailyApiResetAtLocalMidnight ?? false,
|
|
248
|
+
})
|
|
249
|
+
this.apiTracker.startHourlyLogging()
|
|
250
|
+
this.debugLog('API request tracking initialized for OpenAPI usage')
|
|
251
|
+
} catch (e: any) {
|
|
252
|
+
this.errorLog(`Failed to initialize API request tracking: ${e.message ?? e}`)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
206
256
|
// run the method to discover / register your devices as accessories
|
|
207
257
|
try {
|
|
208
258
|
// Does the user have a version of Homebridge that is compatible with matter?
|
|
209
259
|
if (!this.api.isMatterAvailable?.()) {
|
|
210
|
-
this.
|
|
260
|
+
this.debugLog(`Matter is not available in this version of Homebridge. Please update Homebridge to use this plugin, ${this.api.isMatterAvailable?.() ? '' : ' (Matter is not available in this version of Homebridge)'}`)
|
|
211
261
|
}
|
|
212
262
|
if (!this.api.isMatterEnabled?.()) {
|
|
213
|
-
this.
|
|
263
|
+
this.debugLog(`Matter is not enabled in Homebridge. Please enable Matter in the Homebridge settings to use this plugin, ${this.api.isMatterEnabled?.() ? '' : ' (Matter is not enabled in Homebridge)'}`)
|
|
214
264
|
}
|
|
215
265
|
if (!this.api.isMatterAvailable?.() && !this.api.isMatterEnabled?.()) {
|
|
216
266
|
await this.discoverDevices()
|
|
217
267
|
} else {
|
|
218
|
-
this.
|
|
268
|
+
this.infoLog('Matter is enabled in Homebridge. SwitchBot Matter devices will be handled by the Matter platform.')
|
|
219
269
|
}
|
|
220
270
|
} catch (e: any) {
|
|
221
271
|
this.errorLog(`Failed to Discover, Error Message: ${e.message ?? e}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug')
|
|
@@ -444,6 +494,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
444
494
|
let retryCount = 0
|
|
445
495
|
const maxRetries = this.platformMaxRetries ?? 5
|
|
446
496
|
const delayBetweenRetries = this.platformDelayBetweenRetries || 5000
|
|
497
|
+
let rateLimitExceeded = false
|
|
447
498
|
|
|
448
499
|
this.debugWarnLog(`Retry Count: ${retryCount}`)
|
|
449
500
|
this.debugWarnLog(`Max Retries: ${this.platformMaxRetries}`)
|
|
@@ -451,13 +502,29 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
451
502
|
|
|
452
503
|
while (retryCount < maxRetries) {
|
|
453
504
|
try {
|
|
505
|
+
if (!this.apiTracker?.trySpend('discovery')) {
|
|
506
|
+
rateLimitExceeded = true
|
|
507
|
+
this.warnLog('OpenAPI daily budget reached (discovery blocked). Falling back to manual device configuration.')
|
|
508
|
+
break
|
|
509
|
+
}
|
|
454
510
|
const { response, statusCode } = await this.switchBotAPI.getDevices()
|
|
455
511
|
this.debugLog(`response: ${JSON.stringify(response)}`)
|
|
456
512
|
if (this.isSuccessfulResponse(statusCode)) {
|
|
457
|
-
|
|
458
|
-
|
|
513
|
+
const deviceList = Array.isArray(response.body.deviceList) ? response.body.deviceList : []
|
|
514
|
+
const irDeviceList = Array.isArray(response.body.infraredRemoteList) ? response.body.infraredRemoteList : []
|
|
515
|
+
await this.handleDevices(deviceList)
|
|
516
|
+
await this.handleIRDevices(irDeviceList)
|
|
517
|
+
// Diagnostic: warn users if their device count + refresh rate may exceed daily limits
|
|
518
|
+
this.validateApiUsageConfig(deviceList.length, irDeviceList.length)
|
|
459
519
|
break
|
|
460
520
|
} else {
|
|
521
|
+
// Check if rate limit exceeded (429)
|
|
522
|
+
if (statusCode === 429) {
|
|
523
|
+
rateLimitExceeded = true
|
|
524
|
+
this.warnLog('OpenAPI rate limit (429) exceeded. Falling back to manual device configuration.')
|
|
525
|
+
this.warnLog('Webhook functionality will still work if devices are configured manually.')
|
|
526
|
+
break
|
|
527
|
+
}
|
|
461
528
|
await this.handleErrorResponse(statusCode, retryCount, maxRetries, delayBetweenRetries)
|
|
462
529
|
retryCount++
|
|
463
530
|
}
|
|
@@ -467,9 +534,117 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
467
534
|
this.debugErrorLog(`Failed to Discover Devices, Error: ${e.message ?? e}`)
|
|
468
535
|
}
|
|
469
536
|
}
|
|
537
|
+
|
|
538
|
+
// If rate limit exceeded or retries exhausted, try to load from manual config or cached accessories
|
|
539
|
+
if (rateLimitExceeded || retryCount >= maxRetries) {
|
|
540
|
+
const hasCachedAccessories = this.accessories.length > 0
|
|
541
|
+
const hasManualConfig = this.config.options?.devices || this.config.options?.irdevices
|
|
542
|
+
|
|
543
|
+
if (hasManualConfig || hasCachedAccessories) {
|
|
544
|
+
if (hasCachedAccessories) {
|
|
545
|
+
this.warnLog(`Found ${this.accessories.length} cached accessories from previous sessions.`)
|
|
546
|
+
this.warnLog('Reinstantiating device classes to enable webhook handlers...')
|
|
547
|
+
await this.restoreCachedAccessories()
|
|
548
|
+
}
|
|
549
|
+
if (hasManualConfig) {
|
|
550
|
+
this.warnLog('Attempting to load devices from manual configuration...')
|
|
551
|
+
await this.handleManualConfig()
|
|
552
|
+
}
|
|
553
|
+
if (hasCachedAccessories) {
|
|
554
|
+
this.infoLog('Cached accessories restored. Webhook functionality is active.')
|
|
555
|
+
this.infoLog('Device discovery will resume when API rate limit resets.')
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
this.errorLog('OpenAPI unavailable and no cached accessories or manual device configuration found.')
|
|
559
|
+
this.errorLog('Please configure devices manually in the plugin settings to use webhook functionality.')
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Restore cached accessories by reinstantiating their device classes
|
|
566
|
+
* This ensures webhook handlers and other functionality are properly set up
|
|
567
|
+
*/
|
|
568
|
+
private async restoreCachedAccessories() {
|
|
569
|
+
this.debugLog('Restoring cached accessories and setting up webhook handlers...')
|
|
570
|
+
|
|
571
|
+
for (const accessory of this.accessories) {
|
|
572
|
+
try {
|
|
573
|
+
const device = accessory.context.device
|
|
574
|
+
const deviceType = accessory.context.deviceType || device?.deviceType
|
|
575
|
+
const deviceId = accessory.context.deviceId || device?.deviceId
|
|
576
|
+
|
|
577
|
+
if (!device || !deviceType || !deviceId) {
|
|
578
|
+
this.debugWarnLog(`Skipping cached accessory ${accessory.displayName} - missing context data`)
|
|
579
|
+
continue
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
this.debugLog(`Reinstantiating ${deviceType} for cached accessory: ${accessory.displayName}`)
|
|
583
|
+
|
|
584
|
+
// Reinstantiate the device class based on deviceType
|
|
585
|
+
const deviceTypeHandlers: { [key: string]: new (platform: any, accessory: PlatformAccessory, device: any) => any } = {
|
|
586
|
+
'Humidifier': Humidifier,
|
|
587
|
+
'Humidifier2': Humidifier,
|
|
588
|
+
'Hub 2': Hub,
|
|
589
|
+
'Hub 3': Hub,
|
|
590
|
+
'Bot': Bot,
|
|
591
|
+
'Relay Switch 1': RelaySwitch,
|
|
592
|
+
'Relay Switch 1PM': RelaySwitch,
|
|
593
|
+
'Meter': Meter,
|
|
594
|
+
'MeterPlus': MeterPlus,
|
|
595
|
+
'Meter Plus (JP)': MeterPlus,
|
|
596
|
+
'MeterPro': MeterPro,
|
|
597
|
+
'MeterPro(CO2)': MeterPro,
|
|
598
|
+
'WoIOSensor': IOSensor,
|
|
599
|
+
'Water Detector': WaterDetector,
|
|
600
|
+
'Motion Sensor': Motion,
|
|
601
|
+
'Contact Sensor': Contact,
|
|
602
|
+
'Curtain': Curtain,
|
|
603
|
+
'Curtain3': Curtain,
|
|
604
|
+
'WoRollerShade': Curtain,
|
|
605
|
+
'Roller Shade': Curtain,
|
|
606
|
+
'Blind Tilt': BlindTilt,
|
|
607
|
+
'Plug': Plug,
|
|
608
|
+
'Plug Mini (US)': Plug,
|
|
609
|
+
'Plug Mini (JP)': Plug,
|
|
610
|
+
'Smart Lock': Lock,
|
|
611
|
+
'Smart Lock Pro': Lock,
|
|
612
|
+
'Smart Lock Ultra': Lock,
|
|
613
|
+
'Color Bulb': ColorBulb,
|
|
614
|
+
'K10+': RobotVacuumCleaner,
|
|
615
|
+
'K10+ Pro': RobotVacuumCleaner,
|
|
616
|
+
'WoSweeper': RobotVacuumCleaner,
|
|
617
|
+
'WoSweeperMini': RobotVacuumCleaner,
|
|
618
|
+
'Robot Vacuum Cleaner S1': RobotVacuumCleaner,
|
|
619
|
+
'Robot Vacuum Cleaner S1 Plus': RobotVacuumCleaner,
|
|
620
|
+
'Robot Vacuum Cleaner S10': RobotVacuumCleaner,
|
|
621
|
+
'Ceiling Light': CeilingLight,
|
|
622
|
+
'Ceiling Light Pro': CeilingLight,
|
|
623
|
+
'Strip Light': StripLight,
|
|
624
|
+
'Battery Circulator Fan': Fan,
|
|
625
|
+
'Air Purifier PM2.5': AirPurifier,
|
|
626
|
+
'Air Purifier Table PM2.5': AirPurifier,
|
|
627
|
+
'Air Purifier VOC': AirPurifier,
|
|
628
|
+
'Air Purifier Table VOC': AirPurifier,
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const DeviceClass = deviceTypeHandlers[deviceType]
|
|
632
|
+
if (DeviceClass) {
|
|
633
|
+
new DeviceClass(this, accessory, device)
|
|
634
|
+
this.debugSuccessLog(`Successfully restored ${deviceType}: ${accessory.displayName}`)
|
|
635
|
+
} else {
|
|
636
|
+
this.debugLog(`No handler for device type: ${deviceType}`)
|
|
637
|
+
}
|
|
638
|
+
} catch (e: any) {
|
|
639
|
+
this.errorLog(`Failed to restore cached accessory ${accessory.displayName}, Error: ${e.message ?? e}`)
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
this.infoLog(`Restored ${this.accessories.length} cached accessories with webhook support`)
|
|
470
644
|
}
|
|
471
645
|
|
|
472
646
|
private async handleManualConfig() {
|
|
647
|
+
// Handle regular devices
|
|
473
648
|
if (this.config.options?.devices) {
|
|
474
649
|
this.debugLog(`SwitchBot Device Manual Config Set: ${JSON.stringify(this.config.options?.devices)}`)
|
|
475
650
|
const devices = this.config.options.devices.map((v: any) => v)
|
|
@@ -486,17 +661,41 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
486
661
|
this.errorLog(`failed to format device ID as MAC, Error: ${error}`)
|
|
487
662
|
}
|
|
488
663
|
}
|
|
489
|
-
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Handle IR devices
|
|
667
|
+
if (this.config.options?.irdevices) {
|
|
668
|
+
this.debugLog(`SwitchBot IR Device Manual Config Set: ${JSON.stringify(this.config.options?.irdevices)}`)
|
|
669
|
+
const irdevices = this.config.options.irdevices.map((v: any) => v)
|
|
670
|
+
for (const irdevice of irdevices) {
|
|
671
|
+
irdevice.remoteType = irdevice.configRemoteType !== undefined ? irdevice.configRemoteType : 'Unknown'
|
|
672
|
+
irdevice.deviceName = irdevice.configDeviceName !== undefined ? irdevice.configDeviceName : 'Unknown'
|
|
673
|
+
try {
|
|
674
|
+
this.debugLog(`IR deviceId: ${irdevice.deviceId}`)
|
|
675
|
+
if (irdevice.remoteType) {
|
|
676
|
+
await this.createIRDevice(irdevice)
|
|
677
|
+
}
|
|
678
|
+
} catch (error) {
|
|
679
|
+
this.errorLog(`failed to create IR device, Error: ${error}`)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (!this.config.options?.devices && !this.config.options?.irdevices) {
|
|
490
685
|
this.errorLog('Neither SwitchBot Token or Device Config are set.')
|
|
491
686
|
}
|
|
492
687
|
}
|
|
493
688
|
|
|
689
|
+
/**
|
|
690
|
+
* Check if an API status code indicates success
|
|
691
|
+
* @deprecated Use shared isSuccessfulStatusCode from utils.js instead
|
|
692
|
+
*/
|
|
494
693
|
private isSuccessfulResponse(apiStatusCode: number): boolean {
|
|
495
|
-
return (apiStatusCode
|
|
694
|
+
return isSuccessfulStatusCode(apiStatusCode)
|
|
496
695
|
}
|
|
497
696
|
|
|
498
697
|
private async handleDevices(deviceLists: any[]) {
|
|
499
|
-
if (!this.config.options?.devices
|
|
698
|
+
if (!this.config.options?.devices) {
|
|
500
699
|
this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`)
|
|
501
700
|
if (deviceLists.length === 0) {
|
|
502
701
|
this.debugLog('SwitchBot API Has No Devices With Cloud Services Enabled')
|
|
@@ -510,92 +709,118 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
510
709
|
}
|
|
511
710
|
}
|
|
512
711
|
}
|
|
513
|
-
} else
|
|
712
|
+
} else {
|
|
514
713
|
this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`)
|
|
515
714
|
|
|
516
|
-
//
|
|
517
|
-
const
|
|
715
|
+
// Check and assign configDeviceType to deviceType if deviceType is not present
|
|
716
|
+
const devicesWithTypeAssigned = deviceLists.map((device) => {
|
|
518
717
|
if (!device.deviceType) {
|
|
519
718
|
device.deviceType = device.configDeviceType !== undefined ? device.configDeviceType : 'Unknown'
|
|
520
719
|
this.warnLog(`API is displaying no deviceType: ${device.deviceType}, So using configDeviceType: ${device.configDeviceType}`)
|
|
521
720
|
}
|
|
522
|
-
|
|
523
|
-
// Retrieve deviceTypeConfig for each device and merge it
|
|
524
|
-
const deviceTypeConfig = this.config.options?.deviceConfig?.[device.deviceType] || {}
|
|
525
|
-
return Object.assign({}, device, deviceTypeConfig)
|
|
721
|
+
return device
|
|
526
722
|
})
|
|
527
723
|
|
|
528
|
-
//
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
724
|
+
// Apply device-type templates from config entries with applyToAllDevicesOfType=true
|
|
725
|
+
const devicesWithTemplates = applyDeviceTypeTemplates(
|
|
726
|
+
devicesWithTypeAssigned,
|
|
727
|
+
this.config.options.devices,
|
|
728
|
+
'deviceType',
|
|
729
|
+
msg => this.debugLog(msg),
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices)
|
|
733
|
+
const devices = mergeByDeviceId(this.config.options.devices ?? [], devicesWithTemplates ?? [], allowConfigOnly)
|
|
734
|
+
|
|
735
|
+
// Apply global webhook option to devices that don't have their own webhook setting
|
|
736
|
+
if (this.config.options?.webhook === true) {
|
|
737
|
+
for (const device of devices) {
|
|
738
|
+
if (device.webhook === undefined) {
|
|
739
|
+
device.webhook = true
|
|
740
|
+
this.debugLog(`Applying global webhook option to device: ${device.deviceName ?? device.deviceId}`)
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
532
744
|
|
|
533
745
|
this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`)
|
|
534
746
|
|
|
535
747
|
for (const device of devices) {
|
|
536
|
-
const deviceIdConfig = this.config.options?.devices?.[device.deviceId] || {}
|
|
537
|
-
const deviceWithConfig = Object.assign({}, device, deviceIdConfig)
|
|
538
|
-
|
|
539
748
|
if (device.configDeviceName) {
|
|
540
749
|
device.deviceName = device.configDeviceName
|
|
541
750
|
}
|
|
542
|
-
//
|
|
543
|
-
|
|
751
|
+
// Log effective webhook setting for diagnostics
|
|
752
|
+
try {
|
|
753
|
+
const effectiveWebhook = device.webhook !== undefined ? device.webhook : (this.config.options?.webhook === true ? true : undefined)
|
|
754
|
+
this.debugLog(`Effective webhook for device ${device.deviceName ?? device.deviceId}: ${String(effectiveWebhook)}`)
|
|
755
|
+
} catch (e: any) {
|
|
756
|
+
this.debugLog(`Failed logging effective webhook for ${device.deviceName ?? device.deviceId}: ${e?.message ?? e}`)
|
|
757
|
+
}
|
|
758
|
+
await this.createDevice(device)
|
|
544
759
|
}
|
|
545
760
|
}
|
|
546
761
|
}
|
|
547
762
|
|
|
548
763
|
private async handleIRDevices(irDeviceLists: any[]) {
|
|
549
|
-
if (!this.config.options?.irdevices
|
|
764
|
+
if (!this.config.options?.irdevices) {
|
|
550
765
|
this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`)
|
|
551
766
|
for (const device of irDeviceLists) {
|
|
552
767
|
if (device.remoteType) {
|
|
553
768
|
await this.createIRDevice(device)
|
|
554
769
|
}
|
|
555
770
|
}
|
|
556
|
-
} else
|
|
771
|
+
} else {
|
|
557
772
|
this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`)
|
|
558
773
|
|
|
559
|
-
//
|
|
560
|
-
const
|
|
774
|
+
// Check and assign configRemoteType to remoteType if remoteType is not present
|
|
775
|
+
const devicesWithTypeAssigned = irDeviceLists.map((device) => {
|
|
561
776
|
if (!device.remoteType && device.configRemoteType) {
|
|
562
777
|
device.remoteType = device.configRemoteType
|
|
563
778
|
this.warnLog(`API is displaying no remoteType: ${device.remoteType}, So using configRemoteType: ${device.configRemoteType}`)
|
|
564
779
|
} else if (!device.remoteType && !device.configDeviceName) {
|
|
565
780
|
this.errorLog('No remoteType or configRemoteType for device. No device will be created.')
|
|
566
|
-
return null
|
|
781
|
+
return null
|
|
567
782
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
783
|
+
return device
|
|
784
|
+
}).filter(device => device !== null) // Filter out skipped devices
|
|
785
|
+
|
|
786
|
+
// Apply remote-type templates from config entries with applyToAllDevicesOfType=true
|
|
787
|
+
const devicesWithTemplates = applyDeviceTypeTemplates(
|
|
788
|
+
devicesWithTypeAssigned,
|
|
789
|
+
this.config.options.irdevices,
|
|
790
|
+
'remoteType',
|
|
791
|
+
msg => this.debugLog(msg),
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices)
|
|
795
|
+
const devices = mergeByDeviceId(this.config.options.irdevices ?? [], devicesWithTemplates ?? [], allowConfigOnly)
|
|
796
|
+
|
|
797
|
+
// Apply global webhook option to IR devices that don't have their own webhook setting
|
|
798
|
+
if (this.config.options?.webhook === true) {
|
|
799
|
+
for (const device of devices) {
|
|
800
|
+
if (device.webhook === undefined) {
|
|
801
|
+
device.webhook = true
|
|
802
|
+
this.debugLog(`Applying global webhook option to IR device: ${device.deviceName ?? device.deviceId}`)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
577
806
|
|
|
578
807
|
this.debugLog(`IR Devices: ${JSON.stringify(devices)}`)
|
|
579
808
|
for (const device of devices) {
|
|
580
|
-
const irdeviceIdConfig = this.config.options?.irdevices?.[device.deviceId] || {}
|
|
581
|
-
const irdeviceWithConfig = Object.assign({}, device, irdeviceIdConfig)
|
|
582
|
-
|
|
583
809
|
if (device.configDeviceName) {
|
|
584
810
|
device.deviceName = device.configDeviceName
|
|
585
811
|
}
|
|
586
|
-
|
|
812
|
+
// Log effective webhook setting for diagnostics (IR devices)
|
|
813
|
+
try {
|
|
814
|
+
const effectiveWebhook = device.webhook !== undefined ? device.webhook : (this.config.options?.webhook === true ? true : undefined)
|
|
815
|
+
this.debugLog(`Effective webhook for IR device ${device.deviceName ?? device.deviceId}: ${String(effectiveWebhook)}`)
|
|
816
|
+
} catch (e: any) {
|
|
817
|
+
this.debugLog(`Failed logging effective webhook for IR ${device.deviceName ?? device.deviceId}: ${e?.message ?? e}`)
|
|
818
|
+
}
|
|
819
|
+
await this.createIRDevice(device)
|
|
587
820
|
}
|
|
588
821
|
}
|
|
589
822
|
}
|
|
590
823
|
|
|
591
|
-
private mergeByDeviceId(a1: { deviceId: string }[], a2: any[]) {
|
|
592
|
-
const normalizeDeviceId = (deviceId: string) => deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '')
|
|
593
|
-
return a1.map((itm) => {
|
|
594
|
-
const matchingItem = a2.find(item => normalizeDeviceId(item.deviceId) === normalizeDeviceId(itm.deviceId))
|
|
595
|
-
return { ...matchingItem, ...itm }
|
|
596
|
-
})
|
|
597
|
-
}
|
|
598
|
-
|
|
599
824
|
private async handleErrorResponse(apiStatusCode: number, retryCount: number, maxRetries: number, delayBetweenRetries: number) {
|
|
600
825
|
await this.statusCode(apiStatusCode)
|
|
601
826
|
if (apiStatusCode === 500) {
|
|
@@ -632,6 +857,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
632
857
|
'Plug Mini (JP)': this.createPlug.bind(this),
|
|
633
858
|
'Smart Lock': this.createLock.bind(this),
|
|
634
859
|
'Smart Lock Pro': this.createLock.bind(this),
|
|
860
|
+
'Smart Lock Ultra': this.createLock.bind(this),
|
|
635
861
|
'Color Bulb': this.createColorBulb.bind(this),
|
|
636
862
|
'K10+': this.createRobotVacuumCleaner.bind(this),
|
|
637
863
|
'K10+ Pro': this.createRobotVacuumCleaner.bind(this),
|
|
@@ -1627,7 +1853,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
1627
1853
|
existingAccessory.context.device = device
|
|
1628
1854
|
existingAccessory.context.deviceId = device.deviceId
|
|
1629
1855
|
existingAccessory.context.deviceType = device.deviceType
|
|
1630
|
-
existingAccessory.context.model = device.deviceType === 'Smart Lock Pro' ? SwitchBotModel.LockPro : SwitchBotModel.Lock
|
|
1856
|
+
existingAccessory.context.model = (device.deviceType === 'Smart Lock Pro' || device.deviceType === 'Smart Lock Ultra') ? SwitchBotModel.LockPro : SwitchBotModel.Lock
|
|
1631
1857
|
existingAccessory.displayName = device.configDeviceName
|
|
1632
1858
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
1633
1859
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName)
|
|
@@ -1653,7 +1879,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
1653
1879
|
accessory.context.device = device
|
|
1654
1880
|
accessory.context.deviceId = device.deviceId
|
|
1655
1881
|
accessory.context.deviceType = device.deviceType
|
|
1656
|
-
accessory.context.model = device.deviceType === 'Smart Lock Pro' ? SwitchBotModel.LockPro : SwitchBotModel.Lock
|
|
1882
|
+
accessory.context.model = (device.deviceType === 'Smart Lock Pro' || device.deviceType === 'Smart Lock Ultra') ? SwitchBotModel.LockPro : SwitchBotModel.Lock
|
|
1657
1883
|
accessory.displayName = device.configDeviceName
|
|
1658
1884
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
1659
1885
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName)
|
|
@@ -2720,44 +2946,31 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
2720
2946
|
*
|
|
2721
2947
|
* @param statusCode - The status code returned by the device.
|
|
2722
2948
|
* @returns A promise that resolves when the logging is complete.
|
|
2949
|
+
* @deprecated Use shared logStatusCode from utils.js instead
|
|
2723
2950
|
*/
|
|
2724
2951
|
async statusCode(statusCode: number): Promise<void> {
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
160: `Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`,
|
|
2730
|
-
161: `Device is offline, statusCode: ${statusCode}`,
|
|
2731
|
-
171: `is offline, statusCode: ${statusCode}`,
|
|
2732
|
-
190: `Requests reached the daily limit, statusCode: ${statusCode}`,
|
|
2733
|
-
100: `Command successfully sent, statusCode: ${statusCode}`,
|
|
2734
|
-
200: `Request successful, statusCode: ${statusCode}`,
|
|
2735
|
-
400: `Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request payload,
|
|
2736
|
-
statusCode: ${statusCode}`,
|
|
2737
|
-
401: `Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`,
|
|
2738
|
-
403: `Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found,
|
|
2739
|
-
statusCode: ${statusCode}`,
|
|
2740
|
-
404: `Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`,
|
|
2741
|
-
406: `Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server,
|
|
2742
|
-
statusCode: ${statusCode}`,
|
|
2743
|
-
415: `Unsupported Media Type, The client has defined a contentType header that is not supported by the server, statusCode: ${statusCode}`,
|
|
2744
|
-
422: `Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for APIs for which
|
|
2745
|
-
certain limits have been exceeded, statusCode: ${statusCode}`,
|
|
2746
|
-
429: `Too Many Requests, The client has exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`,
|
|
2747
|
-
500: `Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare,
|
|
2748
|
-
statusCode: ${statusCode}`,
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
const message = messages[statusCode] ?? `Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`
|
|
2752
|
-
|
|
2753
|
-
if ([100, 200].includes(statusCode)) {
|
|
2754
|
-
this.debugLog(message)
|
|
2755
|
-
} else {
|
|
2756
|
-
this.errorLog(message)
|
|
2757
|
-
}
|
|
2952
|
+
await logStatusCode(statusCode, {
|
|
2953
|
+
debugLog: this.debugLog.bind(this),
|
|
2954
|
+
errorLog: this.errorLog.bind(this),
|
|
2955
|
+
})
|
|
2758
2956
|
}
|
|
2759
2957
|
|
|
2760
2958
|
async retryRequest(device: (device & devicesConfig) | (irdevice & irDevicesConfig), deviceMaxRetries: number, deviceDelayBetweenRetries: number): Promise<{ response: any, statusCode: deviceStatusRequest['statusCode'] }> {
|
|
2959
|
+
// Check API budget BEFORE attempting any retries - don't waste cycles on blocked requests
|
|
2960
|
+
if (!this.apiTracker?.trySpend('poll')) {
|
|
2961
|
+
// Don't log on every blocked request - the ApiRequestTracker handles periodic warnings
|
|
2962
|
+
return {
|
|
2963
|
+
response: {
|
|
2964
|
+
deviceId: '',
|
|
2965
|
+
deviceType: '',
|
|
2966
|
+
hubDeviceId: '',
|
|
2967
|
+
version: 0,
|
|
2968
|
+
deviceName: '',
|
|
2969
|
+
},
|
|
2970
|
+
statusCode: 429,
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2761
2974
|
let retryCount = 0
|
|
2762
2975
|
const maxRetries = deviceMaxRetries
|
|
2763
2976
|
const delayBetweenRetries = deviceDelayBetweenRetries
|
|
@@ -2788,6 +3001,9 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
2788
3001
|
const delayBetweenRetries = deviceDelayBetweenRetries ?? 1000
|
|
2789
3002
|
while (retryCount < maxRetries) {
|
|
2790
3003
|
try {
|
|
3004
|
+
if (!this.apiTracker?.trySpend('command')) {
|
|
3005
|
+
return { response: {}, statusCode: 429 }
|
|
3006
|
+
}
|
|
2791
3007
|
const { response, statusCode } = await this.switchBotAPI.controlDevice(
|
|
2792
3008
|
device.deviceId,
|
|
2793
3009
|
bodyChange.command,
|
|
@@ -2886,6 +3102,56 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
2886
3102
|
this.version = version
|
|
2887
3103
|
}
|
|
2888
3104
|
|
|
3105
|
+
/**
|
|
3106
|
+
* Validate that the user's configuration won't exceed API limits
|
|
3107
|
+
* Warn if device count × polling frequency will hit daily limits
|
|
3108
|
+
*/
|
|
3109
|
+
private validateApiUsageConfig(deviceCount: number, irDeviceCount: number): void {
|
|
3110
|
+
try {
|
|
3111
|
+
const totalDevices = deviceCount + irDeviceCount
|
|
3112
|
+
if (totalDevices === 0) {
|
|
3113
|
+
return
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
const refreshRate = this.platformRefreshRate ?? 300 // seconds
|
|
3117
|
+
const dailyLimit = this.config.options?.dailyApiLimit ?? 10000
|
|
3118
|
+
const reserveForCommands = this.config.options?.dailyApiReserveForCommands ?? 1000
|
|
3119
|
+
|
|
3120
|
+
// Calculate polls per day (86400 seconds in a day)
|
|
3121
|
+
const pollsPerDevicePerDay = Math.floor(86400 / refreshRate)
|
|
3122
|
+
const totalPollsPerDay = pollsPerDevicePerDay * totalDevices
|
|
3123
|
+
|
|
3124
|
+
// Add discovery calls (typically 1-2 per day)
|
|
3125
|
+
const estimatedDiscoveryCalls = 2
|
|
3126
|
+
const totalEstimatedCalls = totalPollsPerDay + estimatedDiscoveryCalls
|
|
3127
|
+
|
|
3128
|
+
const usableLimit = dailyLimit - reserveForCommands
|
|
3129
|
+
const percentOfLimit = Math.round((totalEstimatedCalls / usableLimit) * 100)
|
|
3130
|
+
|
|
3131
|
+
this.debugLog(`[API Usage Diagnostic] ${totalDevices} devices × ${pollsPerDevicePerDay} polls/day = ${totalPollsPerDay} estimated daily polls`)
|
|
3132
|
+
this.debugLog(`[API Usage Diagnostic] With ${reserveForCommands} reserved for commands, usable limit is ${usableLimit}`)
|
|
3133
|
+
|
|
3134
|
+
if (totalEstimatedCalls > dailyLimit) {
|
|
3135
|
+
this.errorLog(`⚠️ API LIMIT WARNING: Your configuration will exceed the daily API limit!`)
|
|
3136
|
+
this.errorLog(` Devices: ${totalDevices} | Refresh rate: ${refreshRate}s | Estimated daily polls: ${totalEstimatedCalls}`)
|
|
3137
|
+
this.errorLog(` Daily limit: ${dailyLimit} | You will use ${percentOfLimit}% of available budget`)
|
|
3138
|
+
this.errorLog(` SOLUTION: Increase refreshRate to ${Math.ceil((totalDevices * 86400) / usableLimit)} seconds or higher`)
|
|
3139
|
+
this.errorLog(` OR: Enable webhooks and set 'webhookOnlyOnReserve: true' to reduce polling`)
|
|
3140
|
+
} else if (totalEstimatedCalls > usableLimit) {
|
|
3141
|
+
this.warnLog(`⚠️ API USAGE WARNING: Configuration may exceed usable daily API budget`)
|
|
3142
|
+
this.warnLog(` Devices: ${totalDevices} | Refresh rate: ${refreshRate}s | Estimated daily polls: ${totalEstimatedCalls}`)
|
|
3143
|
+
this.warnLog(` Usable limit (after reserve): ${usableLimit} | You will use ${percentOfLimit}% of budget`)
|
|
3144
|
+
this.warnLog(` Polling may pause when approaching limit. Consider increasing refreshRate to ${Math.ceil((totalDevices * 86400) / usableLimit)}s`)
|
|
3145
|
+
} else if (percentOfLimit > 75) {
|
|
3146
|
+
this.infoLog(`[API Usage] Using ${percentOfLimit}% of daily budget (${totalEstimatedCalls}/${usableLimit} calls). Monitor usage if adding more devices.`)
|
|
3147
|
+
} else {
|
|
3148
|
+
this.debugLog(`[API Usage] Configuration looks good: ${percentOfLimit}% of daily budget (${totalEstimatedCalls}/${usableLimit} calls)`)
|
|
3149
|
+
}
|
|
3150
|
+
} catch (e: any) {
|
|
3151
|
+
this.debugErrorLog(`Failed to validate API usage config: ${e.message ?? e}`)
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
|
|
2889
3155
|
/**
|
|
2890
3156
|
* Validate and clean a string value for a Name Characteristic.
|
|
2891
3157
|
* @param displayName - The display name of the accessory.
|
|
@@ -2924,74 +3190,4 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
2924
3190
|
return value
|
|
2925
3191
|
}
|
|
2926
3192
|
}
|
|
2927
|
-
|
|
2928
|
-
/**
|
|
2929
|
-
* If device level logging is turned on, log to log.warn
|
|
2930
|
-
* Otherwise send debug logs to log.debug
|
|
2931
|
-
*/
|
|
2932
|
-
async infoLog(...log: any[]): Promise<void> {
|
|
2933
|
-
if (await this.enablingPlatformLogging()) {
|
|
2934
|
-
this.log.info(String(...log))
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
|
|
2938
|
-
async successLog(...log: any[]): Promise<void> {
|
|
2939
|
-
if (await this.enablingPlatformLogging()) {
|
|
2940
|
-
this.log.success(String(...log))
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
|
-
async debugSuccessLog(...log: any[]): Promise<void> {
|
|
2945
|
-
if (await this.enablingPlatformLogging()) {
|
|
2946
|
-
if (await this.loggingIsDebug()) {
|
|
2947
|
-
this.log.success('[DEBUG]', String(...log))
|
|
2948
|
-
}
|
|
2949
|
-
}
|
|
2950
|
-
}
|
|
2951
|
-
|
|
2952
|
-
async warnLog(...log: any[]): Promise<void> {
|
|
2953
|
-
if (await this.enablingPlatformLogging()) {
|
|
2954
|
-
this.log.warn(String(...log))
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
|
|
2958
|
-
async debugWarnLog(...log: any[]): Promise<void> {
|
|
2959
|
-
if (await this.enablingPlatformLogging()) {
|
|
2960
|
-
if (await this.loggingIsDebug()) {
|
|
2961
|
-
this.log.warn('[DEBUG]', String(...log))
|
|
2962
|
-
}
|
|
2963
|
-
}
|
|
2964
|
-
}
|
|
2965
|
-
|
|
2966
|
-
async errorLog(...log: any[]): Promise<void> {
|
|
2967
|
-
if (await this.enablingPlatformLogging()) {
|
|
2968
|
-
this.log.error(String(...log))
|
|
2969
|
-
}
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
async debugErrorLog(...log: any[]): Promise<void> {
|
|
2973
|
-
if (await this.enablingPlatformLogging()) {
|
|
2974
|
-
if (await this.loggingIsDebug()) {
|
|
2975
|
-
this.log.error('[DEBUG]', String(...log))
|
|
2976
|
-
}
|
|
2977
|
-
}
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
async debugLog(...log: any[]): Promise<void> {
|
|
2981
|
-
if (await this.enablingPlatformLogging()) {
|
|
2982
|
-
if (this.platformLogging === 'debug') {
|
|
2983
|
-
this.log.info('[DEBUG]', String(...log))
|
|
2984
|
-
} else if (this.platformLogging === 'debugMode') {
|
|
2985
|
-
this.log.debug(String(...log))
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
|
|
2990
|
-
async loggingIsDebug(): Promise<boolean> {
|
|
2991
|
-
return this.platformLogging === 'debugMode' || this.platformLogging === 'debug'
|
|
2992
|
-
}
|
|
2993
|
-
|
|
2994
|
-
async enablingPlatformLogging(): Promise<boolean> {
|
|
2995
|
-
return this.platformLogging === 'debugMode' || this.platformLogging === 'debug' || this.platformLogging === 'standard'
|
|
2996
|
-
}
|
|
2997
3193
|
}
|