@switchbot/homebridge-switchbot 5.0.0-beta.4 → 5.0.0-beta.41
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 +13 -0
- package/README.md +23 -3
- package/config.schema.json +722 -13684
- 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 +121 -68
- package/dist/devices-hap/device.js.map +1 -1
- 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/homebridge-ui/public/index.html +48 -1
- package/dist/homebridge-ui/server.js +66 -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 +21 -15
- package/dist/platform-hap.d.ts.map +1 -1
- package/dist/platform-hap.js +246 -147
- package/dist/platform-hap.js.map +1 -1
- package/dist/platform-matter.d.ts +88 -6
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +1726 -243
- package/dist/platform-matter.js.map +1 -1
- package/dist/settings.d.ts +41 -6
- 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/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/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 +196 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +656 -30
- package/dist/utils.js.map +1 -1
- package/docs/assets/main.js +2 -2
- package/docs/index.html +20 -2
- package/docs/variables/default.html +1 -1
- package/package.json +14 -14
- package/src/devices-hap/device.ts +129 -69
- 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/homebridge-ui/public/index.html +48 -1
- package/src/homebridge-ui/server.ts +69 -9
- package/src/index.ts +4 -7
- package/src/irdevice/irdevice.ts +74 -35
- package/src/platform-hap.ts +270 -160
- package/src/platform-matter.ts +1768 -240
- package/src/settings.ts +45 -2
- package/src/test/apiRequestTracker.test.ts +417 -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/utils.test.ts +96 -0
- package/src/{verifyconfig.test.ts → test/verifyconfig.test.ts} +12 -11
- package/src/utils.ts +714 -32
- 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,9 +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
|
+
});
|
|
207
|
+
this.apiTracker.startHourlyLogging();
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
this.errorLog(`Failed to initialize API request tracking: ${e.message ?? e}`);
|
|
211
|
+
}
|
|
174
212
|
// run the method to discover / register your devices as accessories
|
|
175
213
|
try {
|
|
176
|
-
|
|
214
|
+
// Does the user have a version of Homebridge that is compatible with matter?
|
|
215
|
+
if (!this.api.isMatterAvailable?.()) {
|
|
216
|
+
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)'}`);
|
|
217
|
+
}
|
|
218
|
+
if (!this.api.isMatterEnabled?.()) {
|
|
219
|
+
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)'}`);
|
|
220
|
+
}
|
|
221
|
+
if (!this.api.isMatterAvailable?.() && !this.api.isMatterEnabled?.()) {
|
|
222
|
+
await this.discoverDevices();
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this.infoLog('Matter is enabled in Homebridge. SwitchBot Matter devices will be handled by the Matter platform.');
|
|
226
|
+
}
|
|
177
227
|
}
|
|
178
228
|
catch (e) {
|
|
179
229
|
this.errorLog(`Failed to Discover, Error Message: ${e.message ?? e}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug');
|
|
@@ -401,11 +451,17 @@ export class SwitchBotHAPPlatform {
|
|
|
401
451
|
let retryCount = 0;
|
|
402
452
|
const maxRetries = this.platformMaxRetries ?? 5;
|
|
403
453
|
const delayBetweenRetries = this.platformDelayBetweenRetries || 5000;
|
|
454
|
+
let rateLimitExceeded = false;
|
|
404
455
|
this.debugWarnLog(`Retry Count: ${retryCount}`);
|
|
405
456
|
this.debugWarnLog(`Max Retries: ${this.platformMaxRetries}`);
|
|
406
457
|
this.debugWarnLog(`Delay Between Retries: ${this.platformDelayBetweenRetries}`);
|
|
407
458
|
while (retryCount < maxRetries) {
|
|
408
459
|
try {
|
|
460
|
+
if (!this.apiTracker?.trySpend('discovery')) {
|
|
461
|
+
rateLimitExceeded = true;
|
|
462
|
+
this.warnLog('OpenAPI daily budget reached (discovery blocked). Falling back to manual device configuration.');
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
409
465
|
const { response, statusCode } = await this.switchBotAPI.getDevices();
|
|
410
466
|
this.debugLog(`response: ${JSON.stringify(response)}`);
|
|
411
467
|
if (this.isSuccessfulResponse(statusCode)) {
|
|
@@ -414,6 +470,13 @@ export class SwitchBotHAPPlatform {
|
|
|
414
470
|
break;
|
|
415
471
|
}
|
|
416
472
|
else {
|
|
473
|
+
// Check if rate limit exceeded (429)
|
|
474
|
+
if (statusCode === 429) {
|
|
475
|
+
rateLimitExceeded = true;
|
|
476
|
+
this.warnLog('OpenAPI rate limit (429) exceeded. Falling back to manual device configuration.');
|
|
477
|
+
this.warnLog('Webhook functionality will still work if devices are configured manually.');
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
417
480
|
await this.handleErrorResponse(statusCode, retryCount, maxRetries, delayBetweenRetries);
|
|
418
481
|
retryCount++;
|
|
419
482
|
}
|
|
@@ -424,8 +487,109 @@ export class SwitchBotHAPPlatform {
|
|
|
424
487
|
this.debugErrorLog(`Failed to Discover Devices, Error: ${e.message ?? e}`);
|
|
425
488
|
}
|
|
426
489
|
}
|
|
490
|
+
// If rate limit exceeded or retries exhausted, try to load from manual config or cached accessories
|
|
491
|
+
if (rateLimitExceeded || retryCount >= maxRetries) {
|
|
492
|
+
const hasCachedAccessories = this.accessories.length > 0;
|
|
493
|
+
const hasManualConfig = this.config.options?.devices || this.config.options?.irdevices;
|
|
494
|
+
if (hasManualConfig || hasCachedAccessories) {
|
|
495
|
+
if (hasCachedAccessories) {
|
|
496
|
+
this.warnLog(`Found ${this.accessories.length} cached accessories from previous sessions.`);
|
|
497
|
+
this.warnLog('Reinstantiating device classes to enable webhook handlers...');
|
|
498
|
+
await this.restoreCachedAccessories();
|
|
499
|
+
}
|
|
500
|
+
if (hasManualConfig) {
|
|
501
|
+
this.warnLog('Attempting to load devices from manual configuration...');
|
|
502
|
+
await this.handleManualConfig();
|
|
503
|
+
}
|
|
504
|
+
if (hasCachedAccessories) {
|
|
505
|
+
this.infoLog('Cached accessories restored. Webhook functionality is active.');
|
|
506
|
+
this.infoLog('Device discovery will resume when API rate limit resets.');
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
this.errorLog('OpenAPI unavailable and no cached accessories or manual device configuration found.');
|
|
511
|
+
this.errorLog('Please configure devices manually in the plugin settings to use webhook functionality.');
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Restore cached accessories by reinstantiating their device classes
|
|
517
|
+
* This ensures webhook handlers and other functionality are properly set up
|
|
518
|
+
*/
|
|
519
|
+
async restoreCachedAccessories() {
|
|
520
|
+
this.debugLog('Restoring cached accessories and setting up webhook handlers...');
|
|
521
|
+
for (const accessory of this.accessories) {
|
|
522
|
+
try {
|
|
523
|
+
const device = accessory.context.device;
|
|
524
|
+
const deviceType = accessory.context.deviceType || device?.deviceType;
|
|
525
|
+
const deviceId = accessory.context.deviceId || device?.deviceId;
|
|
526
|
+
if (!device || !deviceType || !deviceId) {
|
|
527
|
+
this.debugWarnLog(`Skipping cached accessory ${accessory.displayName} - missing context data`);
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
this.debugLog(`Reinstantiating ${deviceType} for cached accessory: ${accessory.displayName}`);
|
|
531
|
+
// Reinstantiate the device class based on deviceType
|
|
532
|
+
const deviceTypeHandlers = {
|
|
533
|
+
'Humidifier': Humidifier,
|
|
534
|
+
'Humidifier2': Humidifier,
|
|
535
|
+
'Hub 2': Hub,
|
|
536
|
+
'Hub 3': Hub,
|
|
537
|
+
'Bot': Bot,
|
|
538
|
+
'Relay Switch 1': RelaySwitch,
|
|
539
|
+
'Relay Switch 1PM': RelaySwitch,
|
|
540
|
+
'Meter': Meter,
|
|
541
|
+
'MeterPlus': MeterPlus,
|
|
542
|
+
'Meter Plus (JP)': MeterPlus,
|
|
543
|
+
'MeterPro': MeterPro,
|
|
544
|
+
'MeterPro(CO2)': MeterPro,
|
|
545
|
+
'WoIOSensor': IOSensor,
|
|
546
|
+
'Water Detector': WaterDetector,
|
|
547
|
+
'Motion Sensor': Motion,
|
|
548
|
+
'Contact Sensor': Contact,
|
|
549
|
+
'Curtain': Curtain,
|
|
550
|
+
'Curtain3': Curtain,
|
|
551
|
+
'WoRollerShade': Curtain,
|
|
552
|
+
'Roller Shade': Curtain,
|
|
553
|
+
'Blind Tilt': BlindTilt,
|
|
554
|
+
'Plug': Plug,
|
|
555
|
+
'Plug Mini (US)': Plug,
|
|
556
|
+
'Plug Mini (JP)': Plug,
|
|
557
|
+
'Smart Lock': Lock,
|
|
558
|
+
'Smart Lock Pro': Lock,
|
|
559
|
+
'Color Bulb': ColorBulb,
|
|
560
|
+
'K10+': RobotVacuumCleaner,
|
|
561
|
+
'K10+ Pro': RobotVacuumCleaner,
|
|
562
|
+
'WoSweeper': RobotVacuumCleaner,
|
|
563
|
+
'WoSweeperMini': RobotVacuumCleaner,
|
|
564
|
+
'Robot Vacuum Cleaner S1': RobotVacuumCleaner,
|
|
565
|
+
'Robot Vacuum Cleaner S1 Plus': RobotVacuumCleaner,
|
|
566
|
+
'Robot Vacuum Cleaner S10': RobotVacuumCleaner,
|
|
567
|
+
'Ceiling Light': CeilingLight,
|
|
568
|
+
'Ceiling Light Pro': CeilingLight,
|
|
569
|
+
'Strip Light': StripLight,
|
|
570
|
+
'Battery Circulator Fan': Fan,
|
|
571
|
+
'Air Purifier PM2.5': AirPurifier,
|
|
572
|
+
'Air Purifier Table PM2.5': AirPurifier,
|
|
573
|
+
'Air Purifier VOC': AirPurifier,
|
|
574
|
+
'Air Purifier Table VOC': AirPurifier,
|
|
575
|
+
};
|
|
576
|
+
const DeviceClass = deviceTypeHandlers[deviceType];
|
|
577
|
+
if (DeviceClass) {
|
|
578
|
+
new DeviceClass(this, accessory, device);
|
|
579
|
+
this.debugSuccessLog(`Successfully restored ${deviceType}: ${accessory.displayName}`);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
this.debugLog(`No handler for device type: ${deviceType}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
catch (e) {
|
|
586
|
+
this.errorLog(`Failed to restore cached accessory ${accessory.displayName}, Error: ${e.message ?? e}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
this.infoLog(`Restored ${this.accessories.length} cached accessories with webhook support`);
|
|
427
590
|
}
|
|
428
591
|
async handleManualConfig() {
|
|
592
|
+
// Handle regular devices
|
|
429
593
|
if (this.config.options?.devices) {
|
|
430
594
|
this.debugLog(`SwitchBot Device Manual Config Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
431
595
|
const devices = this.config.options.devices.map((v) => v);
|
|
@@ -444,15 +608,37 @@ export class SwitchBotHAPPlatform {
|
|
|
444
608
|
}
|
|
445
609
|
}
|
|
446
610
|
}
|
|
447
|
-
|
|
611
|
+
// Handle IR devices
|
|
612
|
+
if (this.config.options?.irdevices) {
|
|
613
|
+
this.debugLog(`SwitchBot IR Device Manual Config Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
614
|
+
const irdevices = this.config.options.irdevices.map((v) => v);
|
|
615
|
+
for (const irdevice of irdevices) {
|
|
616
|
+
irdevice.remoteType = irdevice.configRemoteType !== undefined ? irdevice.configRemoteType : 'Unknown';
|
|
617
|
+
irdevice.deviceName = irdevice.configDeviceName !== undefined ? irdevice.configDeviceName : 'Unknown';
|
|
618
|
+
try {
|
|
619
|
+
this.debugLog(`IR deviceId: ${irdevice.deviceId}`);
|
|
620
|
+
if (irdevice.remoteType) {
|
|
621
|
+
await this.createIRDevice(irdevice);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
this.errorLog(`failed to create IR device, Error: ${error}`);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (!this.config.options?.devices && !this.config.options?.irdevices) {
|
|
448
630
|
this.errorLog('Neither SwitchBot Token or Device Config are set.');
|
|
449
631
|
}
|
|
450
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* Check if an API status code indicates success
|
|
635
|
+
* @deprecated Use shared isSuccessfulStatusCode from utils.js instead
|
|
636
|
+
*/
|
|
451
637
|
isSuccessfulResponse(apiStatusCode) {
|
|
452
|
-
return (apiStatusCode
|
|
638
|
+
return isSuccessfulStatusCode(apiStatusCode);
|
|
453
639
|
}
|
|
454
640
|
async handleDevices(deviceLists) {
|
|
455
|
-
if (!this.config.options?.devices
|
|
641
|
+
if (!this.config.options?.devices) {
|
|
456
642
|
this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
457
643
|
if (deviceLists.length === 0) {
|
|
458
644
|
this.debugLog('SwitchBot API Has No Devices With Cloud Services Enabled');
|
|
@@ -468,35 +654,31 @@ export class SwitchBotHAPPlatform {
|
|
|
468
654
|
}
|
|
469
655
|
}
|
|
470
656
|
}
|
|
471
|
-
else
|
|
657
|
+
else {
|
|
472
658
|
this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`);
|
|
473
|
-
//
|
|
474
|
-
const
|
|
659
|
+
// Check and assign configDeviceType to deviceType if deviceType is not present
|
|
660
|
+
const devicesWithTypeAssigned = deviceLists.map((device) => {
|
|
475
661
|
if (!device.deviceType) {
|
|
476
662
|
device.deviceType = device.configDeviceType !== undefined ? device.configDeviceType : 'Unknown';
|
|
477
663
|
this.warnLog(`API is displaying no deviceType: ${device.deviceType}, So using configDeviceType: ${device.configDeviceType}`);
|
|
478
664
|
}
|
|
479
|
-
|
|
480
|
-
const deviceTypeConfig = this.config.options?.deviceConfig?.[device.deviceType] || {};
|
|
481
|
-
return Object.assign({}, device, deviceTypeConfig);
|
|
665
|
+
return device;
|
|
482
666
|
});
|
|
483
|
-
//
|
|
484
|
-
const
|
|
485
|
-
const
|
|
667
|
+
// Apply device-type templates from config entries with applyToAllDevicesOfType=true
|
|
668
|
+
const devicesWithTemplates = applyDeviceTypeTemplates(devicesWithTypeAssigned, this.config.options.devices, 'deviceType', msg => this.debugLog(msg));
|
|
669
|
+
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices);
|
|
670
|
+
const devices = mergeByDeviceId(this.config.options.devices ?? [], devicesWithTemplates ?? [], allowConfigOnly);
|
|
486
671
|
this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`);
|
|
487
672
|
for (const device of devices) {
|
|
488
|
-
const deviceIdConfig = this.config.options?.devices?.[device.deviceId] || {};
|
|
489
|
-
const deviceWithConfig = Object.assign({}, device, deviceIdConfig);
|
|
490
673
|
if (device.configDeviceName) {
|
|
491
674
|
device.deviceName = device.configDeviceName;
|
|
492
675
|
}
|
|
493
|
-
|
|
494
|
-
await this.createDevice(deviceWithConfig);
|
|
676
|
+
await this.createDevice(device);
|
|
495
677
|
}
|
|
496
678
|
}
|
|
497
679
|
}
|
|
498
680
|
async handleIRDevices(irDeviceLists) {
|
|
499
|
-
if (!this.config.options?.irdevices
|
|
681
|
+
if (!this.config.options?.irdevices) {
|
|
500
682
|
this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
501
683
|
for (const device of irDeviceLists) {
|
|
502
684
|
if (device.remoteType) {
|
|
@@ -504,43 +686,33 @@ export class SwitchBotHAPPlatform {
|
|
|
504
686
|
}
|
|
505
687
|
}
|
|
506
688
|
}
|
|
507
|
-
else
|
|
689
|
+
else {
|
|
508
690
|
this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`);
|
|
509
|
-
//
|
|
510
|
-
const
|
|
691
|
+
// Check and assign configRemoteType to remoteType if remoteType is not present
|
|
692
|
+
const devicesWithTypeAssigned = irDeviceLists.map((device) => {
|
|
511
693
|
if (!device.remoteType && device.configRemoteType) {
|
|
512
694
|
device.remoteType = device.configRemoteType;
|
|
513
695
|
this.warnLog(`API is displaying no remoteType: ${device.remoteType}, So using configRemoteType: ${device.configRemoteType}`);
|
|
514
696
|
}
|
|
515
697
|
else if (!device.remoteType && !device.configDeviceName) {
|
|
516
698
|
this.errorLog('No remoteType or configRemoteType for device. No device will be created.');
|
|
517
|
-
return null;
|
|
699
|
+
return null;
|
|
518
700
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
const devices = this.mergeByDeviceId(this.config.options.irdevices ?? [], devicesWithRemoteTypeConfig ?? []);
|
|
701
|
+
return device;
|
|
702
|
+
}).filter(device => device !== null); // Filter out skipped devices
|
|
703
|
+
// Apply remote-type templates from config entries with applyToAllDevicesOfType=true
|
|
704
|
+
const devicesWithTemplates = applyDeviceTypeTemplates(devicesWithTypeAssigned, this.config.options.irdevices, 'remoteType', msg => this.debugLog(msg));
|
|
705
|
+
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices);
|
|
706
|
+
const devices = mergeByDeviceId(this.config.options.irdevices ?? [], devicesWithTemplates ?? [], allowConfigOnly);
|
|
526
707
|
this.debugLog(`IR Devices: ${JSON.stringify(devices)}`);
|
|
527
708
|
for (const device of devices) {
|
|
528
|
-
const irdeviceIdConfig = this.config.options?.irdevices?.[device.deviceId] || {};
|
|
529
|
-
const irdeviceWithConfig = Object.assign({}, device, irdeviceIdConfig);
|
|
530
709
|
if (device.configDeviceName) {
|
|
531
710
|
device.deviceName = device.configDeviceName;
|
|
532
711
|
}
|
|
533
|
-
await this.createIRDevice(
|
|
712
|
+
await this.createIRDevice(device);
|
|
534
713
|
}
|
|
535
714
|
}
|
|
536
715
|
}
|
|
537
|
-
mergeByDeviceId(a1, a2) {
|
|
538
|
-
const normalizeDeviceId = (deviceId) => deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '');
|
|
539
|
-
return a1.map((itm) => {
|
|
540
|
-
const matchingItem = a2.find(item => normalizeDeviceId(item.deviceId) === normalizeDeviceId(itm.deviceId));
|
|
541
|
-
return { ...matchingItem, ...itm };
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
716
|
async handleErrorResponse(apiStatusCode, retryCount, maxRetries, delayBetweenRetries) {
|
|
545
717
|
await this.statusCode(apiStatusCode);
|
|
546
718
|
if (apiStatusCode === 500) {
|
|
@@ -2596,40 +2768,13 @@ export class SwitchBotHAPPlatform {
|
|
|
2596
2768
|
*
|
|
2597
2769
|
* @param statusCode - The status code returned by the device.
|
|
2598
2770
|
* @returns A promise that resolves when the logging is complete.
|
|
2771
|
+
* @deprecated Use shared logStatusCode from utils.js instead
|
|
2599
2772
|
*/
|
|
2600
2773
|
async statusCode(statusCode) {
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
160: `Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`,
|
|
2606
|
-
161: `Device is offline, statusCode: ${statusCode}`,
|
|
2607
|
-
171: `is offline, statusCode: ${statusCode}`,
|
|
2608
|
-
190: `Requests reached the daily limit, statusCode: ${statusCode}`,
|
|
2609
|
-
100: `Command successfully sent, statusCode: ${statusCode}`,
|
|
2610
|
-
200: `Request successful, statusCode: ${statusCode}`,
|
|
2611
|
-
400: `Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request payload,
|
|
2612
|
-
statusCode: ${statusCode}`,
|
|
2613
|
-
401: `Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`,
|
|
2614
|
-
403: `Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found,
|
|
2615
|
-
statusCode: ${statusCode}`,
|
|
2616
|
-
404: `Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`,
|
|
2617
|
-
406: `Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server,
|
|
2618
|
-
statusCode: ${statusCode}`,
|
|
2619
|
-
415: `Unsupported Media Type, The client has defined a contentType header that is not supported by the server, statusCode: ${statusCode}`,
|
|
2620
|
-
422: `Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for APIs for which
|
|
2621
|
-
certain limits have been exceeded, statusCode: ${statusCode}`,
|
|
2622
|
-
429: `Too Many Requests, The client has exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`,
|
|
2623
|
-
500: `Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare,
|
|
2624
|
-
statusCode: ${statusCode}`,
|
|
2625
|
-
};
|
|
2626
|
-
const message = messages[statusCode] ?? `Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`;
|
|
2627
|
-
if ([100, 200].includes(statusCode)) {
|
|
2628
|
-
this.debugLog(message);
|
|
2629
|
-
}
|
|
2630
|
-
else {
|
|
2631
|
-
this.errorLog(message);
|
|
2632
|
-
}
|
|
2774
|
+
await logStatusCode(statusCode, {
|
|
2775
|
+
debugLog: this.debugLog.bind(this),
|
|
2776
|
+
errorLog: this.errorLog.bind(this),
|
|
2777
|
+
});
|
|
2633
2778
|
}
|
|
2634
2779
|
async retryRequest(device, deviceMaxRetries, deviceDelayBetweenRetries) {
|
|
2635
2780
|
let retryCount = 0;
|
|
@@ -2637,6 +2782,18 @@ export class SwitchBotHAPPlatform {
|
|
|
2637
2782
|
const delayBetweenRetries = deviceDelayBetweenRetries;
|
|
2638
2783
|
while (retryCount < maxRetries) {
|
|
2639
2784
|
try {
|
|
2785
|
+
if (!this.apiTracker?.trySpend('poll')) {
|
|
2786
|
+
return {
|
|
2787
|
+
response: {
|
|
2788
|
+
deviceId: '',
|
|
2789
|
+
deviceType: '',
|
|
2790
|
+
hubDeviceId: '',
|
|
2791
|
+
version: 0,
|
|
2792
|
+
deviceName: '',
|
|
2793
|
+
},
|
|
2794
|
+
statusCode: 429,
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2640
2797
|
const { response, statusCode } = await this.switchBotAPI.getDeviceStatus(device.deviceId, this.config.credentials?.token, this.config.credentials?.secret);
|
|
2641
2798
|
this.debugLog(`response: ${JSON.stringify(response)}`);
|
|
2642
2799
|
return { response, statusCode };
|
|
@@ -2662,6 +2819,9 @@ export class SwitchBotHAPPlatform {
|
|
|
2662
2819
|
const delayBetweenRetries = deviceDelayBetweenRetries ?? 1000;
|
|
2663
2820
|
while (retryCount < maxRetries) {
|
|
2664
2821
|
try {
|
|
2822
|
+
if (!this.apiTracker?.trySpend('command')) {
|
|
2823
|
+
return { response: {}, statusCode: 429 };
|
|
2824
|
+
}
|
|
2665
2825
|
const { response, statusCode } = await this.switchBotAPI.controlDevice(device.deviceId, bodyChange.command, bodyChange.parameter, bodyChange.commandType, this.config.credentials?.token, this.config.credentials?.secret);
|
|
2666
2826
|
this.debugLog(`response: ${JSON.stringify(response)}`);
|
|
2667
2827
|
return { response, statusCode };
|
|
@@ -2784,66 +2944,5 @@ export class SwitchBotHAPPlatform {
|
|
|
2784
2944
|
return value;
|
|
2785
2945
|
}
|
|
2786
2946
|
}
|
|
2787
|
-
/**
|
|
2788
|
-
* If device level logging is turned on, log to log.warn
|
|
2789
|
-
* Otherwise send debug logs to log.debug
|
|
2790
|
-
*/
|
|
2791
|
-
async infoLog(...log) {
|
|
2792
|
-
if (await this.enablingPlatformLogging()) {
|
|
2793
|
-
this.log.info(String(...log));
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
async successLog(...log) {
|
|
2797
|
-
if (await this.enablingPlatformLogging()) {
|
|
2798
|
-
this.log.success(String(...log));
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
async debugSuccessLog(...log) {
|
|
2802
|
-
if (await this.enablingPlatformLogging()) {
|
|
2803
|
-
if (await this.loggingIsDebug()) {
|
|
2804
|
-
this.log.success('[DEBUG]', String(...log));
|
|
2805
|
-
}
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
2808
|
-
async warnLog(...log) {
|
|
2809
|
-
if (await this.enablingPlatformLogging()) {
|
|
2810
|
-
this.log.warn(String(...log));
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
async debugWarnLog(...log) {
|
|
2814
|
-
if (await this.enablingPlatformLogging()) {
|
|
2815
|
-
if (await this.loggingIsDebug()) {
|
|
2816
|
-
this.log.warn('[DEBUG]', String(...log));
|
|
2817
|
-
}
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
async errorLog(...log) {
|
|
2821
|
-
if (await this.enablingPlatformLogging()) {
|
|
2822
|
-
this.log.error(String(...log));
|
|
2823
|
-
}
|
|
2824
|
-
}
|
|
2825
|
-
async debugErrorLog(...log) {
|
|
2826
|
-
if (await this.enablingPlatformLogging()) {
|
|
2827
|
-
if (await this.loggingIsDebug()) {
|
|
2828
|
-
this.log.error('[DEBUG]', String(...log));
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
}
|
|
2832
|
-
async debugLog(...log) {
|
|
2833
|
-
if (await this.enablingPlatformLogging()) {
|
|
2834
|
-
if (this.platformLogging === 'debug') {
|
|
2835
|
-
this.log.info('[DEBUG]', String(...log));
|
|
2836
|
-
}
|
|
2837
|
-
else if (this.platformLogging === 'debugMode') {
|
|
2838
|
-
this.log.debug(String(...log));
|
|
2839
|
-
}
|
|
2840
|
-
}
|
|
2841
|
-
}
|
|
2842
|
-
async loggingIsDebug() {
|
|
2843
|
-
return this.platformLogging === 'debugMode' || this.platformLogging === 'debug';
|
|
2844
|
-
}
|
|
2845
|
-
async enablingPlatformLogging() {
|
|
2846
|
-
return this.platformLogging === 'debugMode' || this.platformLogging === 'debug' || this.platformLogging === 'standard';
|
|
2847
|
-
}
|
|
2848
2947
|
}
|
|
2849
2948
|
//# sourceMappingURL=platform-hap.js.map
|