@switchbot/homebridge-switchbot 5.0.0-beta.5 → 5.0.0-beta.50
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 +11 -2
- package/dist/devices-matter/RoboticVacuumAccessory.d.ts.map +1 -1
- package/dist/devices-matter/RoboticVacuumAccessory.js +26 -17
- package/dist/devices-matter/RoboticVacuumAccessory.js.map +1 -1
- package/dist/homebridge-ui/public/index.html +64 -2
- package/dist/homebridge-ui/server.js +77 -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 +330 -153
- package/dist/platform-hap.js.map +1 -1
- package/dist/platform-matter.d.ts +98 -6
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +1884 -254
- 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/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 +27 -17
- package/src/homebridge-ui/public/index.html +64 -2
- package/src/homebridge-ui/server.ts +80 -9
- package/src/index.ts +4 -7
- package/src/irdevice/irdevice.ts +74 -35
- package/src/platform-hap.ts +362 -169
- package/src/platform-matter.ts +1937 -257
- 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/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/dist/platform-hap.js
CHANGED
|
@@ -36,7 +36,7 @@ import { TV } from './irdevice/tv.js';
|
|
|
36
36
|
import { VacuumCleaner } from './irdevice/vacuumcleaner.js';
|
|
37
37
|
import { WaterHeater } from './irdevice/waterheater.js';
|
|
38
38
|
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
39
|
-
import {
|
|
39
|
+
import { ApiRequestTracker, applyDeviceTypeTemplates, createPlatformLogger, formatDeviceIdAsMac, isBlindTiltDevice, isCurtainDevice, isSuccessfulStatusCode, logStatusCode, mergeByDeviceId, safeStringify, sleep } from './utils.js';
|
|
40
40
|
/**
|
|
41
41
|
* HomebridgePlatform
|
|
42
42
|
* This class is the main constructor for your plugin, this is where you should
|
|
@@ -47,6 +47,17 @@ export class SwitchBotHAPPlatform {
|
|
|
47
47
|
accessories = [];
|
|
48
48
|
api;
|
|
49
49
|
log;
|
|
50
|
+
// Logging helper functions (attached from utils.createPlatformLogger in constructor)
|
|
51
|
+
infoLog;
|
|
52
|
+
successLog;
|
|
53
|
+
debugSuccessLog;
|
|
54
|
+
warnLog;
|
|
55
|
+
debugWarnLog;
|
|
56
|
+
errorLog;
|
|
57
|
+
debugErrorLog;
|
|
58
|
+
debugLog;
|
|
59
|
+
loggingIsDebug;
|
|
60
|
+
enablingPlatformLogging;
|
|
50
61
|
// Configuration properties
|
|
51
62
|
platformConfig;
|
|
52
63
|
platformLogging;
|
|
@@ -64,6 +75,8 @@ export class SwitchBotHAPPlatform {
|
|
|
64
75
|
// SwitchBot APIs
|
|
65
76
|
switchBotAPI;
|
|
66
77
|
switchBotBLE;
|
|
78
|
+
// API request tracking
|
|
79
|
+
apiTracker;
|
|
67
80
|
// External APIs
|
|
68
81
|
eve;
|
|
69
82
|
fakegatoAPI;
|
|
@@ -73,9 +86,21 @@ export class SwitchBotHAPPlatform {
|
|
|
73
86
|
constructor(log, config, api) {
|
|
74
87
|
this.api = api;
|
|
75
88
|
this.log = log;
|
|
89
|
+
// Attach shared platform logging helpers (moved to utils for reuse)
|
|
90
|
+
const _pl = createPlatformLogger(async () => this.platformLogging, this.log);
|
|
91
|
+
this.infoLog = _pl.infoLog;
|
|
92
|
+
this.successLog = _pl.successLog;
|
|
93
|
+
this.debugSuccessLog = _pl.debugSuccessLog;
|
|
94
|
+
this.warnLog = _pl.warnLog;
|
|
95
|
+
this.debugWarnLog = _pl.debugWarnLog;
|
|
96
|
+
this.errorLog = _pl.errorLog;
|
|
97
|
+
this.debugErrorLog = _pl.debugErrorLog;
|
|
98
|
+
this.debugLog = _pl.debugLog;
|
|
99
|
+
this.loggingIsDebug = _pl.loggingIsDebug;
|
|
100
|
+
this.enablingPlatformLogging = _pl.enablingPlatformLogging;
|
|
76
101
|
// only load if configured
|
|
77
102
|
if (!config) {
|
|
78
|
-
this.
|
|
103
|
+
this.errorLog('No configuration found for the plugin, please check your config.');
|
|
79
104
|
return;
|
|
80
105
|
}
|
|
81
106
|
// Plugin options into our config variables.
|
|
@@ -86,23 +111,21 @@ export class SwitchBotHAPPlatform {
|
|
|
86
111
|
options: config.options,
|
|
87
112
|
devices: config.devices,
|
|
88
113
|
};
|
|
89
|
-
//
|
|
114
|
+
// Determine platform logging preference (match HAP behaviour as closely as
|
|
115
|
+
// possible using config values. We default to 'standard' when unspecified.)
|
|
116
|
+
this.platformLogging = (this.config.options?.logging === 'debug' || this.config.options?.logging === 'standard' || this.config.options?.logging === 'none')
|
|
117
|
+
? this.config.options.logging
|
|
118
|
+
: 'standard';
|
|
119
|
+
// Unconditional diagnostic using the raw Homebridge `log` so it always
|
|
120
|
+
// appears regardless of the platform logging helpers' gating logic.
|
|
90
121
|
try {
|
|
91
|
-
|
|
92
|
-
const cleaned = cleanDeviceConfig(this.config.options.deviceConfig);
|
|
93
|
-
if (cleaned) {
|
|
94
|
-
;
|
|
95
|
-
this.config.options.deviceConfig = cleaned;
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
// remove the empty deviceConfig so downstream checks treat it as absent
|
|
99
|
-
delete this.config.options.deviceConfig;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
122
|
+
this.log.debug?.(`[SwitchBot HAP] effective platformLogging=${String(this.platformLogging)}`);
|
|
102
123
|
}
|
|
103
124
|
catch (e) {
|
|
104
|
-
|
|
125
|
+
// swallow any logging errors — diagnostics are best-effort
|
|
105
126
|
}
|
|
127
|
+
// Note: deviceConfig and irdeviceConfig have been removed from the platform.
|
|
128
|
+
// All device-specific configuration should be done via options.devices and options.irdevices arrays.
|
|
106
129
|
// Plugin Configuration
|
|
107
130
|
this.getPlatformLogSettings();
|
|
108
131
|
this.getPlatformRateSettings();
|
|
@@ -171,20 +194,36 @@ export class SwitchBotHAPPlatform {
|
|
|
171
194
|
// to start discovery of new accessories.
|
|
172
195
|
this.api.on('didFinishLaunching', async () => {
|
|
173
196
|
this.debugLog('Executed didFinishLaunching callback');
|
|
197
|
+
// Initialize API request tracking
|
|
198
|
+
try {
|
|
199
|
+
const dailyApiLimit = this.config.options?.dailyApiLimit ?? 10000;
|
|
200
|
+
const dailyApiReserveForCommands = this.config.options?.dailyApiReserveForCommands ?? 1000;
|
|
201
|
+
const webhookOnlyOnReserve = this.config.options?.webhookOnlyOnReserve ?? false;
|
|
202
|
+
this.apiTracker = new ApiRequestTracker(this.api, this.log, 'SwitchBot HAP', {
|
|
203
|
+
dailyLimit: dailyApiLimit,
|
|
204
|
+
reserveForCommands: dailyApiReserveForCommands,
|
|
205
|
+
pausePollingAtReserve: webhookOnlyOnReserve,
|
|
206
|
+
resetAtLocalMidnight: this.config.options?.dailyApiResetAtLocalMidnight ?? false,
|
|
207
|
+
});
|
|
208
|
+
this.apiTracker.startHourlyLogging();
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
this.errorLog(`Failed to initialize API request tracking: ${e.message ?? e}`);
|
|
212
|
+
}
|
|
174
213
|
// run the method to discover / register your devices as accessories
|
|
175
214
|
try {
|
|
176
215
|
// Does the user have a version of Homebridge that is compatible with matter?
|
|
177
216
|
if (!this.api.isMatterAvailable?.()) {
|
|
178
|
-
this.
|
|
217
|
+
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)'}`);
|
|
179
218
|
}
|
|
180
219
|
if (!this.api.isMatterEnabled?.()) {
|
|
181
|
-
this.
|
|
220
|
+
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)'}`);
|
|
182
221
|
}
|
|
183
222
|
if (!this.api.isMatterAvailable?.() && !this.api.isMatterEnabled?.()) {
|
|
184
223
|
await this.discoverDevices();
|
|
185
224
|
}
|
|
186
225
|
else {
|
|
187
|
-
this.
|
|
226
|
+
this.infoLog('Matter is enabled in Homebridge. SwitchBot Matter devices will be handled by the Matter platform.');
|
|
188
227
|
}
|
|
189
228
|
}
|
|
190
229
|
catch (e) {
|
|
@@ -413,19 +452,36 @@ export class SwitchBotHAPPlatform {
|
|
|
413
452
|
let retryCount = 0;
|
|
414
453
|
const maxRetries = this.platformMaxRetries ?? 5;
|
|
415
454
|
const delayBetweenRetries = this.platformDelayBetweenRetries || 5000;
|
|
455
|
+
let rateLimitExceeded = false;
|
|
416
456
|
this.debugWarnLog(`Retry Count: ${retryCount}`);
|
|
417
457
|
this.debugWarnLog(`Max Retries: ${this.platformMaxRetries}`);
|
|
418
458
|
this.debugWarnLog(`Delay Between Retries: ${this.platformDelayBetweenRetries}`);
|
|
419
459
|
while (retryCount < maxRetries) {
|
|
420
460
|
try {
|
|
461
|
+
if (!this.apiTracker?.trySpend('discovery')) {
|
|
462
|
+
rateLimitExceeded = true;
|
|
463
|
+
this.warnLog('OpenAPI daily budget reached (discovery blocked). Falling back to manual device configuration.');
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
421
466
|
const { response, statusCode } = await this.switchBotAPI.getDevices();
|
|
422
467
|
this.debugLog(`response: ${JSON.stringify(response)}`);
|
|
423
468
|
if (this.isSuccessfulResponse(statusCode)) {
|
|
424
|
-
|
|
425
|
-
|
|
469
|
+
const deviceList = Array.isArray(response.body.deviceList) ? response.body.deviceList : [];
|
|
470
|
+
const irDeviceList = Array.isArray(response.body.infraredRemoteList) ? response.body.infraredRemoteList : [];
|
|
471
|
+
await this.handleDevices(deviceList);
|
|
472
|
+
await this.handleIRDevices(irDeviceList);
|
|
473
|
+
// Diagnostic: warn users if their device count + refresh rate may exceed daily limits
|
|
474
|
+
this.validateApiUsageConfig(deviceList.length, irDeviceList.length);
|
|
426
475
|
break;
|
|
427
476
|
}
|
|
428
477
|
else {
|
|
478
|
+
// Check if rate limit exceeded (429)
|
|
479
|
+
if (statusCode === 429) {
|
|
480
|
+
rateLimitExceeded = true;
|
|
481
|
+
this.warnLog('OpenAPI rate limit (429) exceeded. Falling back to manual device configuration.');
|
|
482
|
+
this.warnLog('Webhook functionality will still work if devices are configured manually.');
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
429
485
|
await this.handleErrorResponse(statusCode, retryCount, maxRetries, delayBetweenRetries);
|
|
430
486
|
retryCount++;
|
|
431
487
|
}
|
|
@@ -436,8 +492,110 @@ export class SwitchBotHAPPlatform {
|
|
|
436
492
|
this.debugErrorLog(`Failed to Discover Devices, Error: ${e.message ?? e}`);
|
|
437
493
|
}
|
|
438
494
|
}
|
|
495
|
+
// If rate limit exceeded or retries exhausted, try to load from manual config or cached accessories
|
|
496
|
+
if (rateLimitExceeded || retryCount >= maxRetries) {
|
|
497
|
+
const hasCachedAccessories = this.accessories.length > 0;
|
|
498
|
+
const hasManualConfig = this.config.options?.devices || this.config.options?.irdevices;
|
|
499
|
+
if (hasManualConfig || hasCachedAccessories) {
|
|
500
|
+
if (hasCachedAccessories) {
|
|
501
|
+
this.warnLog(`Found ${this.accessories.length} cached accessories from previous sessions.`);
|
|
502
|
+
this.warnLog('Reinstantiating device classes to enable webhook handlers...');
|
|
503
|
+
await this.restoreCachedAccessories();
|
|
504
|
+
}
|
|
505
|
+
if (hasManualConfig) {
|
|
506
|
+
this.warnLog('Attempting to load devices from manual configuration...');
|
|
507
|
+
await this.handleManualConfig();
|
|
508
|
+
}
|
|
509
|
+
if (hasCachedAccessories) {
|
|
510
|
+
this.infoLog('Cached accessories restored. Webhook functionality is active.');
|
|
511
|
+
this.infoLog('Device discovery will resume when API rate limit resets.');
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
this.errorLog('OpenAPI unavailable and no cached accessories or manual device configuration found.');
|
|
516
|
+
this.errorLog('Please configure devices manually in the plugin settings to use webhook functionality.');
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Restore cached accessories by reinstantiating their device classes
|
|
522
|
+
* This ensures webhook handlers and other functionality are properly set up
|
|
523
|
+
*/
|
|
524
|
+
async restoreCachedAccessories() {
|
|
525
|
+
this.debugLog('Restoring cached accessories and setting up webhook handlers...');
|
|
526
|
+
for (const accessory of this.accessories) {
|
|
527
|
+
try {
|
|
528
|
+
const device = accessory.context.device;
|
|
529
|
+
const deviceType = accessory.context.deviceType || device?.deviceType;
|
|
530
|
+
const deviceId = accessory.context.deviceId || device?.deviceId;
|
|
531
|
+
if (!device || !deviceType || !deviceId) {
|
|
532
|
+
this.debugWarnLog(`Skipping cached accessory ${accessory.displayName} - missing context data`);
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
this.debugLog(`Reinstantiating ${deviceType} for cached accessory: ${accessory.displayName}`);
|
|
536
|
+
// Reinstantiate the device class based on deviceType
|
|
537
|
+
const deviceTypeHandlers = {
|
|
538
|
+
'Humidifier': Humidifier,
|
|
539
|
+
'Humidifier2': Humidifier,
|
|
540
|
+
'Hub 2': Hub,
|
|
541
|
+
'Hub 3': Hub,
|
|
542
|
+
'Bot': Bot,
|
|
543
|
+
'Relay Switch 1': RelaySwitch,
|
|
544
|
+
'Relay Switch 1PM': RelaySwitch,
|
|
545
|
+
'Meter': Meter,
|
|
546
|
+
'MeterPlus': MeterPlus,
|
|
547
|
+
'Meter Plus (JP)': MeterPlus,
|
|
548
|
+
'MeterPro': MeterPro,
|
|
549
|
+
'MeterPro(CO2)': MeterPro,
|
|
550
|
+
'WoIOSensor': IOSensor,
|
|
551
|
+
'Water Detector': WaterDetector,
|
|
552
|
+
'Motion Sensor': Motion,
|
|
553
|
+
'Contact Sensor': Contact,
|
|
554
|
+
'Curtain': Curtain,
|
|
555
|
+
'Curtain3': Curtain,
|
|
556
|
+
'WoRollerShade': Curtain,
|
|
557
|
+
'Roller Shade': Curtain,
|
|
558
|
+
'Blind Tilt': BlindTilt,
|
|
559
|
+
'Plug': Plug,
|
|
560
|
+
'Plug Mini (US)': Plug,
|
|
561
|
+
'Plug Mini (JP)': Plug,
|
|
562
|
+
'Smart Lock': Lock,
|
|
563
|
+
'Smart Lock Pro': Lock,
|
|
564
|
+
'Smart Lock Ultra': Lock,
|
|
565
|
+
'Color Bulb': ColorBulb,
|
|
566
|
+
'K10+': RobotVacuumCleaner,
|
|
567
|
+
'K10+ Pro': RobotVacuumCleaner,
|
|
568
|
+
'WoSweeper': RobotVacuumCleaner,
|
|
569
|
+
'WoSweeperMini': RobotVacuumCleaner,
|
|
570
|
+
'Robot Vacuum Cleaner S1': RobotVacuumCleaner,
|
|
571
|
+
'Robot Vacuum Cleaner S1 Plus': RobotVacuumCleaner,
|
|
572
|
+
'Robot Vacuum Cleaner S10': RobotVacuumCleaner,
|
|
573
|
+
'Ceiling Light': CeilingLight,
|
|
574
|
+
'Ceiling Light Pro': CeilingLight,
|
|
575
|
+
'Strip Light': StripLight,
|
|
576
|
+
'Battery Circulator Fan': Fan,
|
|
577
|
+
'Air Purifier PM2.5': AirPurifier,
|
|
578
|
+
'Air Purifier Table PM2.5': AirPurifier,
|
|
579
|
+
'Air Purifier VOC': AirPurifier,
|
|
580
|
+
'Air Purifier Table VOC': AirPurifier,
|
|
581
|
+
};
|
|
582
|
+
const DeviceClass = deviceTypeHandlers[deviceType];
|
|
583
|
+
if (DeviceClass) {
|
|
584
|
+
new DeviceClass(this, accessory, device);
|
|
585
|
+
this.debugSuccessLog(`Successfully restored ${deviceType}: ${accessory.displayName}`);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
this.debugLog(`No handler for device type: ${deviceType}`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
catch (e) {
|
|
592
|
+
this.errorLog(`Failed to restore cached accessory ${accessory.displayName}, Error: ${e.message ?? e}`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
this.infoLog(`Restored ${this.accessories.length} cached accessories with webhook support`);
|
|
439
596
|
}
|
|
440
597
|
async handleManualConfig() {
|
|
598
|
+
// Handle regular devices
|
|
441
599
|
if (this.config.options?.devices) {
|
|
442
600
|
this.debugLog(`SwitchBot Device Manual Config Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
443
601
|
const devices = this.config.options.devices.map((v) => v);
|
|
@@ -456,15 +614,37 @@ export class SwitchBotHAPPlatform {
|
|
|
456
614
|
}
|
|
457
615
|
}
|
|
458
616
|
}
|
|
459
|
-
|
|
617
|
+
// Handle IR devices
|
|
618
|
+
if (this.config.options?.irdevices) {
|
|
619
|
+
this.debugLog(`SwitchBot IR Device Manual Config Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
620
|
+
const irdevices = this.config.options.irdevices.map((v) => v);
|
|
621
|
+
for (const irdevice of irdevices) {
|
|
622
|
+
irdevice.remoteType = irdevice.configRemoteType !== undefined ? irdevice.configRemoteType : 'Unknown';
|
|
623
|
+
irdevice.deviceName = irdevice.configDeviceName !== undefined ? irdevice.configDeviceName : 'Unknown';
|
|
624
|
+
try {
|
|
625
|
+
this.debugLog(`IR deviceId: ${irdevice.deviceId}`);
|
|
626
|
+
if (irdevice.remoteType) {
|
|
627
|
+
await this.createIRDevice(irdevice);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
catch (error) {
|
|
631
|
+
this.errorLog(`failed to create IR device, Error: ${error}`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (!this.config.options?.devices && !this.config.options?.irdevices) {
|
|
460
636
|
this.errorLog('Neither SwitchBot Token or Device Config are set.');
|
|
461
637
|
}
|
|
462
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* Check if an API status code indicates success
|
|
641
|
+
* @deprecated Use shared isSuccessfulStatusCode from utils.js instead
|
|
642
|
+
*/
|
|
463
643
|
isSuccessfulResponse(apiStatusCode) {
|
|
464
|
-
return (apiStatusCode
|
|
644
|
+
return isSuccessfulStatusCode(apiStatusCode);
|
|
465
645
|
}
|
|
466
646
|
async handleDevices(deviceLists) {
|
|
467
|
-
if (!this.config.options?.devices
|
|
647
|
+
if (!this.config.options?.devices) {
|
|
468
648
|
this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
469
649
|
if (deviceLists.length === 0) {
|
|
470
650
|
this.debugLog('SwitchBot API Has No Devices With Cloud Services Enabled');
|
|
@@ -480,35 +660,48 @@ export class SwitchBotHAPPlatform {
|
|
|
480
660
|
}
|
|
481
661
|
}
|
|
482
662
|
}
|
|
483
|
-
else
|
|
663
|
+
else {
|
|
484
664
|
this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
485
|
-
//
|
|
486
|
-
const
|
|
665
|
+
// Check and assign configDeviceType to deviceType if deviceType is not present
|
|
666
|
+
const devicesWithTypeAssigned = deviceLists.map((device) => {
|
|
487
667
|
if (!device.deviceType) {
|
|
488
668
|
device.deviceType = device.configDeviceType !== undefined ? device.configDeviceType : 'Unknown';
|
|
489
669
|
this.warnLog(`API is displaying no deviceType: ${device.deviceType}, So using configDeviceType: ${device.configDeviceType}`);
|
|
490
670
|
}
|
|
491
|
-
|
|
492
|
-
const deviceTypeConfig = this.config.options?.deviceConfig?.[device.deviceType] || {};
|
|
493
|
-
return Object.assign({}, device, deviceTypeConfig);
|
|
671
|
+
return device;
|
|
494
672
|
});
|
|
495
|
-
//
|
|
496
|
-
const
|
|
497
|
-
const
|
|
673
|
+
// Apply device-type templates from config entries with applyToAllDevicesOfType=true
|
|
674
|
+
const devicesWithTemplates = applyDeviceTypeTemplates(devicesWithTypeAssigned, this.config.options.devices, 'deviceType', msg => this.debugLog(msg));
|
|
675
|
+
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices);
|
|
676
|
+
const devices = mergeByDeviceId(this.config.options.devices ?? [], devicesWithTemplates ?? [], allowConfigOnly);
|
|
677
|
+
// Apply global webhook option to devices that don't have their own webhook setting
|
|
678
|
+
if (this.config.options?.webhook === true) {
|
|
679
|
+
for (const device of devices) {
|
|
680
|
+
if (device.webhook === undefined) {
|
|
681
|
+
device.webhook = true;
|
|
682
|
+
this.debugLog(`Applying global webhook option to device: ${device.deviceName ?? device.deviceId}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
498
686
|
this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`);
|
|
499
687
|
for (const device of devices) {
|
|
500
|
-
const deviceIdConfig = this.config.options?.devices?.[device.deviceId] || {};
|
|
501
|
-
const deviceWithConfig = Object.assign({}, device, deviceIdConfig);
|
|
502
688
|
if (device.configDeviceName) {
|
|
503
689
|
device.deviceName = device.configDeviceName;
|
|
504
690
|
}
|
|
505
|
-
//
|
|
506
|
-
|
|
691
|
+
// Log effective webhook setting for diagnostics
|
|
692
|
+
try {
|
|
693
|
+
const effectiveWebhook = device.webhook !== undefined ? device.webhook : (this.config.options?.webhook === true ? true : undefined);
|
|
694
|
+
this.debugLog(`Effective webhook for device ${device.deviceName ?? device.deviceId}: ${String(effectiveWebhook)}`);
|
|
695
|
+
}
|
|
696
|
+
catch (e) {
|
|
697
|
+
this.debugLog(`Failed logging effective webhook for ${device.deviceName ?? device.deviceId}: ${e?.message ?? e}`);
|
|
698
|
+
}
|
|
699
|
+
await this.createDevice(device);
|
|
507
700
|
}
|
|
508
701
|
}
|
|
509
702
|
}
|
|
510
703
|
async handleIRDevices(irDeviceLists) {
|
|
511
|
-
if (!this.config.options?.irdevices
|
|
704
|
+
if (!this.config.options?.irdevices) {
|
|
512
705
|
this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
513
706
|
for (const device of irDeviceLists) {
|
|
514
707
|
if (device.remoteType) {
|
|
@@ -516,43 +709,50 @@ export class SwitchBotHAPPlatform {
|
|
|
516
709
|
}
|
|
517
710
|
}
|
|
518
711
|
}
|
|
519
|
-
else
|
|
712
|
+
else {
|
|
520
713
|
this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
521
|
-
//
|
|
522
|
-
const
|
|
714
|
+
// Check and assign configRemoteType to remoteType if remoteType is not present
|
|
715
|
+
const devicesWithTypeAssigned = irDeviceLists.map((device) => {
|
|
523
716
|
if (!device.remoteType && device.configRemoteType) {
|
|
524
717
|
device.remoteType = device.configRemoteType;
|
|
525
718
|
this.warnLog(`API is displaying no remoteType: ${device.remoteType}, So using configRemoteType: ${device.configRemoteType}`);
|
|
526
719
|
}
|
|
527
720
|
else if (!device.remoteType && !device.configDeviceName) {
|
|
528
721
|
this.errorLog('No remoteType or configRemoteType for device. No device will be created.');
|
|
529
|
-
return null;
|
|
722
|
+
return null;
|
|
530
723
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
|
|
724
|
+
return device;
|
|
725
|
+
}).filter(device => device !== null); // Filter out skipped devices
|
|
726
|
+
// Apply remote-type templates from config entries with applyToAllDevicesOfType=true
|
|
727
|
+
const devicesWithTemplates = applyDeviceTypeTemplates(devicesWithTypeAssigned, this.config.options.irdevices, 'remoteType', msg => this.debugLog(msg));
|
|
728
|
+
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices);
|
|
729
|
+
const devices = mergeByDeviceId(this.config.options.irdevices ?? [], devicesWithTemplates ?? [], allowConfigOnly);
|
|
730
|
+
// Apply global webhook option to IR devices that don't have their own webhook setting
|
|
731
|
+
if (this.config.options?.webhook === true) {
|
|
732
|
+
for (const device of devices) {
|
|
733
|
+
if (device.webhook === undefined) {
|
|
734
|
+
device.webhook = true;
|
|
735
|
+
this.debugLog(`Applying global webhook option to IR device: ${device.deviceName ?? device.deviceId}`);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
538
739
|
this.debugLog(`IR Devices: ${JSON.stringify(devices)}`);
|
|
539
740
|
for (const device of devices) {
|
|
540
|
-
const irdeviceIdConfig = this.config.options?.irdevices?.[device.deviceId] || {};
|
|
541
|
-
const irdeviceWithConfig = Object.assign({}, device, irdeviceIdConfig);
|
|
542
741
|
if (device.configDeviceName) {
|
|
543
742
|
device.deviceName = device.configDeviceName;
|
|
544
743
|
}
|
|
545
|
-
|
|
744
|
+
// Log effective webhook setting for diagnostics (IR devices)
|
|
745
|
+
try {
|
|
746
|
+
const effectiveWebhook = device.webhook !== undefined ? device.webhook : (this.config.options?.webhook === true ? true : undefined);
|
|
747
|
+
this.debugLog(`Effective webhook for IR device ${device.deviceName ?? device.deviceId}: ${String(effectiveWebhook)}`);
|
|
748
|
+
}
|
|
749
|
+
catch (e) {
|
|
750
|
+
this.debugLog(`Failed logging effective webhook for IR ${device.deviceName ?? device.deviceId}: ${e?.message ?? e}`);
|
|
751
|
+
}
|
|
752
|
+
await this.createIRDevice(device);
|
|
546
753
|
}
|
|
547
754
|
}
|
|
548
755
|
}
|
|
549
|
-
mergeByDeviceId(a1, a2) {
|
|
550
|
-
const normalizeDeviceId = (deviceId) => deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '');
|
|
551
|
-
return a1.map((itm) => {
|
|
552
|
-
const matchingItem = a2.find(item => normalizeDeviceId(item.deviceId) === normalizeDeviceId(itm.deviceId));
|
|
553
|
-
return { ...matchingItem, ...itm };
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
756
|
async handleErrorResponse(apiStatusCode, retryCount, maxRetries, delayBetweenRetries) {
|
|
557
757
|
await this.statusCode(apiStatusCode);
|
|
558
758
|
if (apiStatusCode === 500) {
|
|
@@ -588,6 +788,7 @@ export class SwitchBotHAPPlatform {
|
|
|
588
788
|
'Plug Mini (JP)': this.createPlug.bind(this),
|
|
589
789
|
'Smart Lock': this.createLock.bind(this),
|
|
590
790
|
'Smart Lock Pro': this.createLock.bind(this),
|
|
791
|
+
'Smart Lock Ultra': this.createLock.bind(this),
|
|
591
792
|
'Color Bulb': this.createColorBulb.bind(this),
|
|
592
793
|
'K10+': this.createRobotVacuumCleaner.bind(this),
|
|
593
794
|
'K10+ Pro': this.createRobotVacuumCleaner.bind(this),
|
|
@@ -1550,7 +1751,7 @@ export class SwitchBotHAPPlatform {
|
|
|
1550
1751
|
existingAccessory.context.device = device;
|
|
1551
1752
|
existingAccessory.context.deviceId = device.deviceId;
|
|
1552
1753
|
existingAccessory.context.deviceType = device.deviceType;
|
|
1553
|
-
existingAccessory.context.model = device.deviceType === 'Smart Lock Pro' ? SwitchBotModel.LockPro : SwitchBotModel.Lock;
|
|
1754
|
+
existingAccessory.context.model = (device.deviceType === 'Smart Lock Pro' || device.deviceType === 'Smart Lock Ultra') ? SwitchBotModel.LockPro : SwitchBotModel.Lock;
|
|
1554
1755
|
existingAccessory.displayName = device.configDeviceName
|
|
1555
1756
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
1556
1757
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
@@ -1577,7 +1778,7 @@ export class SwitchBotHAPPlatform {
|
|
|
1577
1778
|
accessory.context.device = device;
|
|
1578
1779
|
accessory.context.deviceId = device.deviceId;
|
|
1579
1780
|
accessory.context.deviceType = device.deviceType;
|
|
1580
|
-
accessory.context.model = device.deviceType === 'Smart Lock Pro' ? SwitchBotModel.LockPro : SwitchBotModel.Lock;
|
|
1781
|
+
accessory.context.model = (device.deviceType === 'Smart Lock Pro' || device.deviceType === 'Smart Lock Ultra') ? SwitchBotModel.LockPro : SwitchBotModel.Lock;
|
|
1581
1782
|
accessory.displayName = device.configDeviceName
|
|
1582
1783
|
? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
|
|
1583
1784
|
: await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName);
|
|
@@ -2608,42 +2809,29 @@ export class SwitchBotHAPPlatform {
|
|
|
2608
2809
|
*
|
|
2609
2810
|
* @param statusCode - The status code returned by the device.
|
|
2610
2811
|
* @returns A promise that resolves when the logging is complete.
|
|
2812
|
+
* @deprecated Use shared logStatusCode from utils.js instead
|
|
2611
2813
|
*/
|
|
2612
2814
|
async statusCode(statusCode) {
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
160: `Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`,
|
|
2618
|
-
161: `Device is offline, statusCode: ${statusCode}`,
|
|
2619
|
-
171: `is offline, statusCode: ${statusCode}`,
|
|
2620
|
-
190: `Requests reached the daily limit, statusCode: ${statusCode}`,
|
|
2621
|
-
100: `Command successfully sent, statusCode: ${statusCode}`,
|
|
2622
|
-
200: `Request successful, statusCode: ${statusCode}`,
|
|
2623
|
-
400: `Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request payload,
|
|
2624
|
-
statusCode: ${statusCode}`,
|
|
2625
|
-
401: `Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`,
|
|
2626
|
-
403: `Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found,
|
|
2627
|
-
statusCode: ${statusCode}`,
|
|
2628
|
-
404: `Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`,
|
|
2629
|
-
406: `Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server,
|
|
2630
|
-
statusCode: ${statusCode}`,
|
|
2631
|
-
415: `Unsupported Media Type, The client has defined a contentType header that is not supported by the server, statusCode: ${statusCode}`,
|
|
2632
|
-
422: `Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for APIs for which
|
|
2633
|
-
certain limits have been exceeded, statusCode: ${statusCode}`,
|
|
2634
|
-
429: `Too Many Requests, The client has exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`,
|
|
2635
|
-
500: `Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare,
|
|
2636
|
-
statusCode: ${statusCode}`,
|
|
2637
|
-
};
|
|
2638
|
-
const message = messages[statusCode] ?? `Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`;
|
|
2639
|
-
if ([100, 200].includes(statusCode)) {
|
|
2640
|
-
this.debugLog(message);
|
|
2641
|
-
}
|
|
2642
|
-
else {
|
|
2643
|
-
this.errorLog(message);
|
|
2644
|
-
}
|
|
2815
|
+
await logStatusCode(statusCode, {
|
|
2816
|
+
debugLog: this.debugLog.bind(this),
|
|
2817
|
+
errorLog: this.errorLog.bind(this),
|
|
2818
|
+
});
|
|
2645
2819
|
}
|
|
2646
2820
|
async retryRequest(device, deviceMaxRetries, deviceDelayBetweenRetries) {
|
|
2821
|
+
// Check API budget BEFORE attempting any retries - don't waste cycles on blocked requests
|
|
2822
|
+
if (!this.apiTracker?.trySpend('poll')) {
|
|
2823
|
+
// Don't log on every blocked request - the ApiRequestTracker handles periodic warnings
|
|
2824
|
+
return {
|
|
2825
|
+
response: {
|
|
2826
|
+
deviceId: '',
|
|
2827
|
+
deviceType: '',
|
|
2828
|
+
hubDeviceId: '',
|
|
2829
|
+
version: 0,
|
|
2830
|
+
deviceName: '',
|
|
2831
|
+
},
|
|
2832
|
+
statusCode: 429,
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2647
2835
|
let retryCount = 0;
|
|
2648
2836
|
const maxRetries = deviceMaxRetries;
|
|
2649
2837
|
const delayBetweenRetries = deviceDelayBetweenRetries;
|
|
@@ -2674,6 +2862,9 @@ export class SwitchBotHAPPlatform {
|
|
|
2674
2862
|
const delayBetweenRetries = deviceDelayBetweenRetries ?? 1000;
|
|
2675
2863
|
while (retryCount < maxRetries) {
|
|
2676
2864
|
try {
|
|
2865
|
+
if (!this.apiTracker?.trySpend('command')) {
|
|
2866
|
+
return { response: {}, statusCode: 429 };
|
|
2867
|
+
}
|
|
2677
2868
|
const { response, statusCode } = await this.switchBotAPI.controlDevice(device.deviceId, bodyChange.command, bodyChange.parameter, bodyChange.commandType, this.config.credentials?.token, this.config.credentials?.secret);
|
|
2678
2869
|
this.debugLog(`response: ${JSON.stringify(response)}`);
|
|
2679
2870
|
return { response, statusCode };
|
|
@@ -2761,6 +2952,53 @@ export class SwitchBotHAPPlatform {
|
|
|
2761
2952
|
this.debugLog(`Plugin Version: ${version}`);
|
|
2762
2953
|
this.version = version;
|
|
2763
2954
|
}
|
|
2955
|
+
/**
|
|
2956
|
+
* Validate that the user's configuration won't exceed API limits
|
|
2957
|
+
* Warn if device count × polling frequency will hit daily limits
|
|
2958
|
+
*/
|
|
2959
|
+
validateApiUsageConfig(deviceCount, irDeviceCount) {
|
|
2960
|
+
try {
|
|
2961
|
+
const totalDevices = deviceCount + irDeviceCount;
|
|
2962
|
+
if (totalDevices === 0) {
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
const refreshRate = this.platformRefreshRate ?? 300; // seconds
|
|
2966
|
+
const dailyLimit = this.config.options?.dailyApiLimit ?? 10000;
|
|
2967
|
+
const reserveForCommands = this.config.options?.dailyApiReserveForCommands ?? 1000;
|
|
2968
|
+
// Calculate polls per day (86400 seconds in a day)
|
|
2969
|
+
const pollsPerDevicePerDay = Math.floor(86400 / refreshRate);
|
|
2970
|
+
const totalPollsPerDay = pollsPerDevicePerDay * totalDevices;
|
|
2971
|
+
// Add discovery calls (typically 1-2 per day)
|
|
2972
|
+
const estimatedDiscoveryCalls = 2;
|
|
2973
|
+
const totalEstimatedCalls = totalPollsPerDay + estimatedDiscoveryCalls;
|
|
2974
|
+
const usableLimit = dailyLimit - reserveForCommands;
|
|
2975
|
+
const percentOfLimit = Math.round((totalEstimatedCalls / usableLimit) * 100);
|
|
2976
|
+
this.debugLog(`[API Usage Diagnostic] ${totalDevices} devices × ${pollsPerDevicePerDay} polls/day = ${totalPollsPerDay} estimated daily polls`);
|
|
2977
|
+
this.debugLog(`[API Usage Diagnostic] With ${reserveForCommands} reserved for commands, usable limit is ${usableLimit}`);
|
|
2978
|
+
if (totalEstimatedCalls > dailyLimit) {
|
|
2979
|
+
this.errorLog(`⚠️ API LIMIT WARNING: Your configuration will exceed the daily API limit!`);
|
|
2980
|
+
this.errorLog(` Devices: ${totalDevices} | Refresh rate: ${refreshRate}s | Estimated daily polls: ${totalEstimatedCalls}`);
|
|
2981
|
+
this.errorLog(` Daily limit: ${dailyLimit} | You will use ${percentOfLimit}% of available budget`);
|
|
2982
|
+
this.errorLog(` SOLUTION: Increase refreshRate to ${Math.ceil((totalDevices * 86400) / usableLimit)} seconds or higher`);
|
|
2983
|
+
this.errorLog(` OR: Enable webhooks and set 'webhookOnlyOnReserve: true' to reduce polling`);
|
|
2984
|
+
}
|
|
2985
|
+
else if (totalEstimatedCalls > usableLimit) {
|
|
2986
|
+
this.warnLog(`⚠️ API USAGE WARNING: Configuration may exceed usable daily API budget`);
|
|
2987
|
+
this.warnLog(` Devices: ${totalDevices} | Refresh rate: ${refreshRate}s | Estimated daily polls: ${totalEstimatedCalls}`);
|
|
2988
|
+
this.warnLog(` Usable limit (after reserve): ${usableLimit} | You will use ${percentOfLimit}% of budget`);
|
|
2989
|
+
this.warnLog(` Polling may pause when approaching limit. Consider increasing refreshRate to ${Math.ceil((totalDevices * 86400) / usableLimit)}s`);
|
|
2990
|
+
}
|
|
2991
|
+
else if (percentOfLimit > 75) {
|
|
2992
|
+
this.infoLog(`[API Usage] Using ${percentOfLimit}% of daily budget (${totalEstimatedCalls}/${usableLimit} calls). Monitor usage if adding more devices.`);
|
|
2993
|
+
}
|
|
2994
|
+
else {
|
|
2995
|
+
this.debugLog(`[API Usage] Configuration looks good: ${percentOfLimit}% of daily budget (${totalEstimatedCalls}/${usableLimit} calls)`);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
catch (e) {
|
|
2999
|
+
this.debugErrorLog(`Failed to validate API usage config: ${e.message ?? e}`);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
2764
3002
|
/**
|
|
2765
3003
|
* Validate and clean a string value for a Name Characteristic.
|
|
2766
3004
|
* @param displayName - The display name of the accessory.
|
|
@@ -2796,66 +3034,5 @@ export class SwitchBotHAPPlatform {
|
|
|
2796
3034
|
return value;
|
|
2797
3035
|
}
|
|
2798
3036
|
}
|
|
2799
|
-
/**
|
|
2800
|
-
* If device level logging is turned on, log to log.warn
|
|
2801
|
-
* Otherwise send debug logs to log.debug
|
|
2802
|
-
*/
|
|
2803
|
-
async infoLog(...log) {
|
|
2804
|
-
if (await this.enablingPlatformLogging()) {
|
|
2805
|
-
this.log.info(String(...log));
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
2808
|
-
async successLog(...log) {
|
|
2809
|
-
if (await this.enablingPlatformLogging()) {
|
|
2810
|
-
this.log.success(String(...log));
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
async debugSuccessLog(...log) {
|
|
2814
|
-
if (await this.enablingPlatformLogging()) {
|
|
2815
|
-
if (await this.loggingIsDebug()) {
|
|
2816
|
-
this.log.success('[DEBUG]', String(...log));
|
|
2817
|
-
}
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
async warnLog(...log) {
|
|
2821
|
-
if (await this.enablingPlatformLogging()) {
|
|
2822
|
-
this.log.warn(String(...log));
|
|
2823
|
-
}
|
|
2824
|
-
}
|
|
2825
|
-
async debugWarnLog(...log) {
|
|
2826
|
-
if (await this.enablingPlatformLogging()) {
|
|
2827
|
-
if (await this.loggingIsDebug()) {
|
|
2828
|
-
this.log.warn('[DEBUG]', String(...log));
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
}
|
|
2832
|
-
async errorLog(...log) {
|
|
2833
|
-
if (await this.enablingPlatformLogging()) {
|
|
2834
|
-
this.log.error(String(...log));
|
|
2835
|
-
}
|
|
2836
|
-
}
|
|
2837
|
-
async debugErrorLog(...log) {
|
|
2838
|
-
if (await this.enablingPlatformLogging()) {
|
|
2839
|
-
if (await this.loggingIsDebug()) {
|
|
2840
|
-
this.log.error('[DEBUG]', String(...log));
|
|
2841
|
-
}
|
|
2842
|
-
}
|
|
2843
|
-
}
|
|
2844
|
-
async debugLog(...log) {
|
|
2845
|
-
if (await this.enablingPlatformLogging()) {
|
|
2846
|
-
if (this.platformLogging === 'debug') {
|
|
2847
|
-
this.log.info('[DEBUG]', String(...log));
|
|
2848
|
-
}
|
|
2849
|
-
else if (this.platformLogging === 'debugMode') {
|
|
2850
|
-
this.log.debug(String(...log));
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
}
|
|
2854
|
-
async loggingIsDebug() {
|
|
2855
|
-
return this.platformLogging === 'debugMode' || this.platformLogging === 'debug';
|
|
2856
|
-
}
|
|
2857
|
-
async enablingPlatformLogging() {
|
|
2858
|
-
return this.platformLogging === 'debugMode' || this.platformLogging === 'debug' || this.platformLogging === 'standard';
|
|
2859
|
-
}
|
|
2860
3037
|
}
|
|
2861
3038
|
//# sourceMappingURL=platform-hap.js.map
|