@switchbot/homebridge-switchbot 5.0.0-beta.4 → 5.0.0-beta.40
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 +53 -8
- 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/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/{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 +55 -8
- 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/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/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/utils.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* util.ts: @switchbot/homebridge-switchbot platform class.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
1
7
|
export var BlindTiltMappingMode;
|
|
2
8
|
(function (BlindTiltMappingMode) {
|
|
3
9
|
BlindTiltMappingMode["OnlyUp"] = "only_up";
|
|
@@ -648,46 +654,666 @@ export function m2hs(m) {
|
|
|
648
654
|
return [Math.round(toReturn[1]), Math.round(toReturn[0])];
|
|
649
655
|
}
|
|
650
656
|
/**
|
|
651
|
-
*
|
|
652
|
-
*
|
|
657
|
+
* Factory that returns a function to send OpenAPI commands using a retry wrapper.
|
|
658
|
+
*
|
|
659
|
+
* @param retryCommandFunc - bound function that calls platform.retryCommand(device, bodyChange, maxRetries, delay)
|
|
660
|
+
* @param deviceObj - the device object to operate on
|
|
661
|
+
* @param opts - optional overrides for maxRetries and delayBetweenRetries
|
|
662
|
+
* @param opts.maxRetries - override for maxRetries
|
|
663
|
+
* @param opts.delayBetweenRetries - override for delayBetweenRetries
|
|
664
|
+
*/
|
|
665
|
+
export function makeOpenAPISender(retryCommandFunc, deviceObj, opts) {
|
|
666
|
+
return async (command, parameter = 'default') => {
|
|
667
|
+
const bodyChange = { command, parameter, commandType: 'command' };
|
|
668
|
+
return retryCommandFunc(deviceObj, bodyChange, opts?.maxRetries, opts?.delayBetweenRetries);
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Factory that returns a function to perform BLE actions using a SwitchBotBLE client.
|
|
673
|
+
* Handles discovery retries and method invocation on the discovered device instance.
|
|
674
|
+
*
|
|
675
|
+
* @param switchBotBLE - instance of SwitchBotBLE (may be undefined)
|
|
676
|
+
* @param deviceObj - the device object (used to obtain bleModel/deviceId)
|
|
677
|
+
* @param opts - optional retry settings
|
|
678
|
+
* @param opts.bleRetries - number of BLE discovery retries
|
|
679
|
+
* @param opts.bleRetryDelay - delay between BLE retries in ms
|
|
653
680
|
*/
|
|
654
|
-
export function
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
const cleaned = {};
|
|
659
|
-
for (const [deviceName, cfg] of Object.entries(deviceConfig)) {
|
|
660
|
-
if (!cfg || typeof cfg !== 'object') {
|
|
661
|
-
continue;
|
|
681
|
+
export function makeBLESender(switchBotBLE, deviceObj, opts) {
|
|
682
|
+
return async (methodName, ...args) => {
|
|
683
|
+
if (!switchBotBLE) {
|
|
684
|
+
throw new Error('Platform BLE not available');
|
|
662
685
|
}
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
686
|
+
const id = formatDeviceIdAsMac(deviceObj.deviceId);
|
|
687
|
+
const maxRetries = opts?.bleRetries ?? 2;
|
|
688
|
+
const retryDelay = opts?.bleRetryDelay ?? 500;
|
|
689
|
+
let attempt = 0;
|
|
690
|
+
while (attempt < maxRetries) {
|
|
691
|
+
try {
|
|
692
|
+
const list = await switchBotBLE.discover({ model: deviceObj.bleModel, id });
|
|
693
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
694
|
+
throw new Error('BLE device not found');
|
|
695
|
+
}
|
|
696
|
+
const deviceInst = list[0];
|
|
697
|
+
if (typeof deviceInst[methodName] !== 'function') {
|
|
698
|
+
throw new TypeError(`BLE method ${methodName} not available on device`);
|
|
699
|
+
}
|
|
700
|
+
return await deviceInst[methodName](...args);
|
|
666
701
|
}
|
|
667
|
-
|
|
668
|
-
|
|
702
|
+
catch (e) {
|
|
703
|
+
attempt++;
|
|
704
|
+
if (attempt >= maxRetries) {
|
|
705
|
+
throw e;
|
|
706
|
+
}
|
|
707
|
+
await sleep(retryDelay);
|
|
669
708
|
}
|
|
670
|
-
|
|
671
|
-
|
|
709
|
+
}
|
|
710
|
+
throw new Error('BLE operation failed');
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Decide effective connection type for a device given platform options.
|
|
715
|
+
* Mirrors the logic previously in platform-matter.
|
|
716
|
+
*/
|
|
717
|
+
export function chooseConnectionType(platformOptions, deviceObj) {
|
|
718
|
+
if (deviceObj?.connectionType) {
|
|
719
|
+
return deviceObj.connectionType === 'BLE' ? 'BLE' : 'OpenAPI';
|
|
720
|
+
}
|
|
721
|
+
if (platformOptions?.BLE && (deviceObj?.bleModel || (typeof deviceObj?.deviceId === 'string' && deviceObj.deviceId.length > 0))) {
|
|
722
|
+
return 'BLE';
|
|
723
|
+
}
|
|
724
|
+
return 'OpenAPI';
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Detect whether Matter is enabled/available on the provided Homebridge API object.
|
|
728
|
+
* This encapsulates the multi-fallback detection used across the project.
|
|
729
|
+
*/
|
|
730
|
+
/**
|
|
731
|
+
* Detect whether Matter is enabled on the provided Homebridge API object.
|
|
732
|
+
* Returns an object with an `enabled` boolean and an optional `reason` string
|
|
733
|
+
* describing which check matched (useful for diagnostics).
|
|
734
|
+
*/
|
|
735
|
+
export function detectMatter(apiObj) {
|
|
736
|
+
try {
|
|
737
|
+
const maybe = apiObj.isMatterEnabled;
|
|
738
|
+
if (typeof maybe === 'function') {
|
|
739
|
+
return { enabled: Boolean(maybe.call(apiObj)), reason: 'api.isMatterEnabled() returned truthy' };
|
|
740
|
+
}
|
|
741
|
+
if (typeof maybe !== 'undefined') {
|
|
742
|
+
return { enabled: Boolean(maybe), reason: 'api.isMatterEnabled property present' };
|
|
743
|
+
}
|
|
744
|
+
const server = apiObj.server ?? apiObj.homebridgeServer ?? apiObj.homebridge_server;
|
|
745
|
+
const serverMaybe = server?.isMatterEnabled;
|
|
746
|
+
if (typeof serverMaybe === 'function') {
|
|
747
|
+
return { enabled: Boolean(serverMaybe.call(server)), reason: 'server.isMatterEnabled() returned truthy' };
|
|
748
|
+
}
|
|
749
|
+
if (typeof server?.isMatterEnabled !== 'undefined') {
|
|
750
|
+
return { enabled: Boolean(server.isMatterEnabled), reason: 'server.isMatterEnabled property present' };
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
catch (e) {
|
|
754
|
+
return { enabled: false, reason: `error during detection: ${String(e?.message ?? e)}` };
|
|
755
|
+
}
|
|
756
|
+
return { enabled: false, reason: 'no isMatterEnabled API or server fallback detected' };
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Backwards-compatible boolean wrapper for detectMatter.
|
|
760
|
+
*/
|
|
761
|
+
export function detectMatterEnabled(apiObj) {
|
|
762
|
+
return detectMatter(apiObj).enabled;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Create platform logging helpers used by both HAP and Matter platforms.
|
|
766
|
+
*
|
|
767
|
+
* getPlatformLogging may be either a synchronous string-returning function or an
|
|
768
|
+
* async function that resolves to the current platform logging setting. The
|
|
769
|
+
* returned helpers mirror the instance methods previously implemented on the
|
|
770
|
+
* HAP platform (infoLog, warnLog, errorLog, debugLog, etc.).
|
|
771
|
+
*/
|
|
772
|
+
export function createPlatformLogger(getPlatformLogging, log) {
|
|
773
|
+
const getPL = async () => {
|
|
774
|
+
try {
|
|
775
|
+
return await getPlatformLogging();
|
|
776
|
+
}
|
|
777
|
+
catch {
|
|
778
|
+
return undefined;
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
const loggingIsDebug = async () => {
|
|
782
|
+
const pl = await getPL();
|
|
783
|
+
return pl === 'debugMode' || pl === 'debug';
|
|
784
|
+
};
|
|
785
|
+
const enablingPlatformLogging = async () => {
|
|
786
|
+
const pl = await getPL();
|
|
787
|
+
return pl === 'debugMode' || pl === 'debug' || pl === 'standard';
|
|
788
|
+
};
|
|
789
|
+
const formatArgs = (args) => {
|
|
790
|
+
return args
|
|
791
|
+
.map((a) => {
|
|
792
|
+
if (typeof a === 'string') {
|
|
793
|
+
return a;
|
|
672
794
|
}
|
|
673
|
-
|
|
674
|
-
return
|
|
795
|
+
try {
|
|
796
|
+
return JSON.stringify(a);
|
|
675
797
|
}
|
|
676
|
-
|
|
677
|
-
return
|
|
798
|
+
catch {
|
|
799
|
+
return String(a);
|
|
678
800
|
}
|
|
679
|
-
|
|
680
|
-
|
|
801
|
+
})
|
|
802
|
+
.join(' ');
|
|
803
|
+
};
|
|
804
|
+
return {
|
|
805
|
+
// Format arbitrary arguments into a single string to ensure values are not dropped
|
|
806
|
+
// when loggers only accept a single message parameter.
|
|
807
|
+
// Prefer readable JSON for objects, fall back to String() on errors.
|
|
808
|
+
// Example: infoLog('Loaded', accessory.displayName) => "Loaded My Light"
|
|
809
|
+
infoLog: async (...args) => {
|
|
810
|
+
if (await enablingPlatformLogging()) {
|
|
811
|
+
const msg = formatArgs(args);
|
|
812
|
+
log.info(msg);
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
successLog: async (...args) => {
|
|
816
|
+
if (await enablingPlatformLogging()) {
|
|
817
|
+
const msg = formatArgs(args);
|
|
818
|
+
log.success?.(msg) ?? log.info(msg);
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
debugSuccessLog: async (...args) => {
|
|
822
|
+
if (await enablingPlatformLogging()) {
|
|
823
|
+
if (await loggingIsDebug()) {
|
|
824
|
+
const msg = formatArgs(args);
|
|
825
|
+
log.success?.(`[DEBUG] ${msg}`) ?? log.info(`[DEBUG] ${msg}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
warnLog: async (...args) => {
|
|
830
|
+
if (await enablingPlatformLogging()) {
|
|
831
|
+
const msg = formatArgs(args);
|
|
832
|
+
log.warn(msg);
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
debugWarnLog: async (...args) => {
|
|
836
|
+
if (await enablingPlatformLogging()) {
|
|
837
|
+
if (await loggingIsDebug()) {
|
|
838
|
+
const msg = formatArgs(args);
|
|
839
|
+
log.warn(`[DEBUG] ${msg}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
errorLog: async (...args) => {
|
|
844
|
+
if (await enablingPlatformLogging()) {
|
|
845
|
+
const msg = formatArgs(args);
|
|
846
|
+
log.error(msg);
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
debugErrorLog: async (...args) => {
|
|
850
|
+
if (await enablingPlatformLogging()) {
|
|
851
|
+
if (await loggingIsDebug()) {
|
|
852
|
+
const msg = formatArgs(args);
|
|
853
|
+
log.error(`[DEBUG] ${msg}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
debugLog: async (...args) => {
|
|
858
|
+
if (await enablingPlatformLogging()) {
|
|
859
|
+
const pl = await getPL();
|
|
860
|
+
if (pl === 'debug') {
|
|
861
|
+
const msg = formatArgs(args);
|
|
862
|
+
log.info(`[DEBUG] ${msg}`);
|
|
863
|
+
}
|
|
864
|
+
else if (pl === 'debugMode') {
|
|
865
|
+
const msg = formatArgs(args);
|
|
866
|
+
log.debug(msg);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
},
|
|
870
|
+
loggingIsDebug,
|
|
871
|
+
enablingPlatformLogging,
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Create a Platform proxy class that selects between two platform constructors
|
|
876
|
+
* (HAP vs Matter) at runtime using `detectMatter`. Returns a class suitable
|
|
877
|
+
* for passing to `api.registerPlatform`.
|
|
878
|
+
*/
|
|
879
|
+
export function createPlatformProxy(HAPCtor, MatterCtor) {
|
|
880
|
+
return class PlatformProxy {
|
|
881
|
+
log;
|
|
882
|
+
config;
|
|
883
|
+
api;
|
|
884
|
+
delegate;
|
|
885
|
+
constructor(log, config, api) {
|
|
886
|
+
this.log = log;
|
|
887
|
+
this.config = config;
|
|
888
|
+
this.api = api;
|
|
889
|
+
const matterInfo = detectMatter(this.api);
|
|
890
|
+
const isMatter = matterInfo.enabled;
|
|
891
|
+
const reason = matterInfo.reason ? ` Reason: ${matterInfo.reason}` : '';
|
|
892
|
+
this.log.info?.(`Homebridge SwitchBot Plugin initializing in ${isMatter ? 'Matter' : 'HAP'} mode.`);
|
|
893
|
+
this.log.debug?.(`Homebridge SwitchBot Plugin initializing in ${isMatter ? 'Matter' : 'HAP'} mode.${reason}`);
|
|
894
|
+
const PlatformCtor = isMatter ? MatterCtor : HAPCtor;
|
|
895
|
+
this.delegate = new PlatformCtor(this.log, this.config, this.api);
|
|
896
|
+
}
|
|
897
|
+
configureAccessory(accessory) {
|
|
898
|
+
try {
|
|
899
|
+
if (this.delegate && typeof this.delegate.configureAccessory === 'function') {
|
|
900
|
+
return this.delegate.configureAccessory(accessory);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
catch (e) {
|
|
904
|
+
// swallow — preserve previous behaviour where delegate errors don't bubble here
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
configureMatterAccessory(accessory) {
|
|
908
|
+
try {
|
|
909
|
+
if (this.delegate && typeof this.delegate.configureMatterAccessory === 'function') {
|
|
910
|
+
return this.delegate.configureMatterAccessory(accessory);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
catch (e) {
|
|
914
|
+
// swallow — delegate may not implement this or may throw
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
get accessories() {
|
|
918
|
+
try {
|
|
919
|
+
return this.delegate?.accessories;
|
|
920
|
+
}
|
|
921
|
+
catch (e) {
|
|
922
|
+
return undefined;
|
|
681
923
|
}
|
|
682
|
-
return true;
|
|
683
|
-
});
|
|
684
|
-
if (hasMeaningful) {
|
|
685
|
-
cleaned[deviceName] = cfg;
|
|
686
924
|
}
|
|
925
|
+
get matterAccessories() {
|
|
926
|
+
try {
|
|
927
|
+
return this.delegate?.matterAccessories;
|
|
928
|
+
}
|
|
929
|
+
catch (e) {
|
|
930
|
+
return undefined;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* API Request Tracker - Persistent tracking of SwitchBot API calls
|
|
937
|
+
* Tracks requests per day with automatic midnight rollover
|
|
938
|
+
*/
|
|
939
|
+
export class ApiRequestTracker {
|
|
940
|
+
count = 0;
|
|
941
|
+
date = '';
|
|
942
|
+
statsFile = '';
|
|
943
|
+
hourlyTimer;
|
|
944
|
+
midnightTimer;
|
|
945
|
+
log;
|
|
946
|
+
// Daily limits
|
|
947
|
+
dailyLimit;
|
|
948
|
+
reserveForCommands;
|
|
949
|
+
lastWarn = {};
|
|
950
|
+
pausePollingAtReserve = false;
|
|
951
|
+
constructor(api, log, pluginName = 'SwitchBot', limits) {
|
|
952
|
+
this.log = log;
|
|
953
|
+
this.statsFile = join(api.user.storagePath(), `${pluginName.toLowerCase()}-api-stats.json`);
|
|
954
|
+
this.dailyLimit = Math.max(0, Number(limits?.dailyLimit ?? 10000));
|
|
955
|
+
this.reserveForCommands = Math.max(0, Number(limits?.reserveForCommands ?? 1000));
|
|
956
|
+
this.pausePollingAtReserve = Boolean(limits?.pausePollingAtReserve ?? false);
|
|
957
|
+
this.load();
|
|
687
958
|
}
|
|
688
|
-
|
|
689
|
-
|
|
959
|
+
/**
|
|
960
|
+
* Load API request statistics from persistent storage
|
|
961
|
+
*/
|
|
962
|
+
load() {
|
|
963
|
+
try {
|
|
964
|
+
const today = new Date().toISOString().split('T')[0];
|
|
965
|
+
if (existsSync(this.statsFile)) {
|
|
966
|
+
const data = JSON.parse(readFileSync(this.statsFile, 'utf8'));
|
|
967
|
+
// If it's a new day, reset the counter
|
|
968
|
+
if (data.date === today) {
|
|
969
|
+
this.count = data.count || 0;
|
|
970
|
+
this.date = data.date;
|
|
971
|
+
this.log.warn?.(`[API Stats] Loaded: ${this.count} requests today (${today})`);
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
974
|
+
this.log.error?.(`[API Stats] New day detected. Previous: ${data.count || 0} requests on ${data.date}`);
|
|
975
|
+
this.count = 0;
|
|
976
|
+
this.date = today;
|
|
977
|
+
this.save();
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
this.log.debug?.('[API Stats] No existing stats file, starting fresh');
|
|
982
|
+
this.count = 0;
|
|
983
|
+
this.date = today;
|
|
984
|
+
this.save();
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
catch (e) {
|
|
988
|
+
this.log.error?.(`[API Stats] Failed to load stats: ${e?.message ?? e}`);
|
|
989
|
+
this.count = 0;
|
|
990
|
+
this.date = new Date().toISOString().split('T')[0];
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Save API request statistics to persistent storage
|
|
995
|
+
*/
|
|
996
|
+
save() {
|
|
997
|
+
try {
|
|
998
|
+
const data = {
|
|
999
|
+
date: this.date,
|
|
1000
|
+
count: this.count,
|
|
1001
|
+
lastUpdated: new Date().toISOString(),
|
|
1002
|
+
};
|
|
1003
|
+
writeFileSync(this.statsFile, JSON.stringify(data, null, 2), 'utf8');
|
|
1004
|
+
}
|
|
1005
|
+
catch (e) {
|
|
1006
|
+
this.log.debug?.(`[API Stats] Failed to save stats: ${e?.message ?? e}`);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Increment API request counter and save
|
|
1011
|
+
*/
|
|
1012
|
+
track() {
|
|
1013
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1014
|
+
// Reset counter if it's a new day
|
|
1015
|
+
if (this.date !== today) {
|
|
1016
|
+
this.log.debug?.(`[API Stats] Day rollover: ${this.count} requests on ${this.date}`);
|
|
1017
|
+
this.count = 0;
|
|
1018
|
+
this.date = today;
|
|
1019
|
+
}
|
|
1020
|
+
this.count++;
|
|
1021
|
+
this.save();
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Attempt to spend from the daily budget for a request of a given kind.
|
|
1025
|
+
* Kinds: 'command' (user actions), 'poll' (status refresh), 'discovery'.
|
|
1026
|
+
* Returns true if allowed (and increments the counter), false if blocked.
|
|
1027
|
+
*/
|
|
1028
|
+
trySpend(kind, n = 1) {
|
|
1029
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1030
|
+
if (this.date !== today) {
|
|
1031
|
+
// Day rollover
|
|
1032
|
+
this.log.debug?.(`[API Stats] Day rollover: ${this.count} requests on ${this.date}`);
|
|
1033
|
+
this.count = 0;
|
|
1034
|
+
this.date = today;
|
|
1035
|
+
this.save();
|
|
1036
|
+
}
|
|
1037
|
+
const softCap = Math.max(0, this.dailyLimit - this.reserveForCommands);
|
|
1038
|
+
const projected = this.count + n;
|
|
1039
|
+
const now = Date.now();
|
|
1040
|
+
const overHardCap = projected > this.dailyLimit;
|
|
1041
|
+
const overSoftCap = projected > softCap;
|
|
1042
|
+
const shouldRateLimit = (kind === 'command')
|
|
1043
|
+
? overHardCap
|
|
1044
|
+
: (this.pausePollingAtReserve ? overSoftCap : overHardCap);
|
|
1045
|
+
if (shouldRateLimit) {
|
|
1046
|
+
const warnKey = kind === 'command' ? 'hardcap' : 'softcap';
|
|
1047
|
+
const last = this.lastWarn[warnKey] ?? 0;
|
|
1048
|
+
if (now - last > 10 * 60 * 1000) { // warn at most every 10 minutes
|
|
1049
|
+
if (kind === 'command') {
|
|
1050
|
+
this.log.error?.(`[API Stats] Daily limit (${this.dailyLimit}) reached. Blocking command requests until reset.`);
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
const remainingForCommands = Math.max(0, this.dailyLimit - this.count);
|
|
1054
|
+
this.log.warn?.(`[API Stats] Near daily limit. Pausing ${kind} requests to reserve ~${this.reserveForCommands} calls for commands. Remaining today: ${remainingForCommands}`);
|
|
1055
|
+
}
|
|
1056
|
+
this.lastWarn[warnKey] = now;
|
|
1057
|
+
}
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
this.count += n;
|
|
1061
|
+
this.save();
|
|
1062
|
+
return true;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Start hourly logging of API request count
|
|
1066
|
+
*/
|
|
1067
|
+
startHourlyLogging() {
|
|
1068
|
+
// Log immediately on startup
|
|
1069
|
+
this.log.info?.(`[API Stats] Today (${this.date}): ${this.count} API requests`);
|
|
1070
|
+
// Then log every hour
|
|
1071
|
+
this.hourlyTimer = setInterval(() => {
|
|
1072
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1073
|
+
if (this.date !== today) {
|
|
1074
|
+
// Day rollover
|
|
1075
|
+
this.log.info?.(`[API Stats] Day rollover - Previous day (${this.date}): ${this.count} API requests`);
|
|
1076
|
+
this.count = 0;
|
|
1077
|
+
this.date = today;
|
|
1078
|
+
this.save();
|
|
1079
|
+
this.log.info?.('[API Stats] Polling resumed after daily reset');
|
|
1080
|
+
}
|
|
1081
|
+
this.log.info?.(`[API Stats] Today (${this.date}): ${this.count} API requests`);
|
|
1082
|
+
}, 60 * 60 * 1000); // Every hour
|
|
1083
|
+
// Schedule an exact UTC midnight rollover log/reset
|
|
1084
|
+
this.scheduleMidnightRollover();
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Stop hourly logging
|
|
1088
|
+
*/
|
|
1089
|
+
stopHourlyLogging() {
|
|
1090
|
+
if (this.hourlyTimer) {
|
|
1091
|
+
clearInterval(this.hourlyTimer);
|
|
1092
|
+
this.hourlyTimer = undefined;
|
|
1093
|
+
}
|
|
1094
|
+
if (this.midnightTimer) {
|
|
1095
|
+
clearTimeout(this.midnightTimer);
|
|
1096
|
+
this.midnightTimer = undefined;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Get current count
|
|
1101
|
+
*/
|
|
1102
|
+
getCount() {
|
|
1103
|
+
return this.count;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Get current date
|
|
1107
|
+
*/
|
|
1108
|
+
getDate() {
|
|
1109
|
+
return this.date;
|
|
1110
|
+
}
|
|
1111
|
+
/** Schedule a precise log/reset at the next UTC midnight */
|
|
1112
|
+
scheduleMidnightRollover() {
|
|
1113
|
+
try {
|
|
1114
|
+
// Clear any previous timer
|
|
1115
|
+
if (this.midnightTimer) {
|
|
1116
|
+
clearTimeout(this.midnightTimer);
|
|
1117
|
+
this.midnightTimer = undefined;
|
|
1118
|
+
}
|
|
1119
|
+
const now = new Date();
|
|
1120
|
+
const nextUtcMidnightMs = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1, 0, 0, 0, 0);
|
|
1121
|
+
const delay = Math.max(1000, nextUtcMidnightMs - now.getTime());
|
|
1122
|
+
this.midnightTimer = setTimeout(() => {
|
|
1123
|
+
try {
|
|
1124
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1125
|
+
if (this.date !== today) {
|
|
1126
|
+
this.log.info?.(`[API Stats] Day rollover - Previous day (${this.date}): ${this.count} API requests`);
|
|
1127
|
+
this.count = 0;
|
|
1128
|
+
this.date = today;
|
|
1129
|
+
this.save();
|
|
1130
|
+
}
|
|
1131
|
+
// Emit the precise resume line and a fresh today counter line
|
|
1132
|
+
this.log.info?.('[API Stats] Polling resumed after daily reset');
|
|
1133
|
+
this.log.info?.(`[API Stats] Today (${this.date}): ${this.count} API requests`);
|
|
1134
|
+
}
|
|
1135
|
+
catch { }
|
|
1136
|
+
// Reschedule for the next midnight
|
|
1137
|
+
this.scheduleMidnightRollover();
|
|
1138
|
+
}, delay);
|
|
1139
|
+
}
|
|
1140
|
+
catch { }
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Normalize a deviceId for matching (uppercase alphanumerics only)
|
|
1145
|
+
*/
|
|
1146
|
+
export function normalizeDeviceId(deviceId) {
|
|
1147
|
+
return (deviceId ?? '').toUpperCase().replace(/[^A-Z0-9]+/g, '');
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Merge two arrays by deviceId. For each item in a1 (user-provided devices list),
|
|
1151
|
+
* find matching item in a2 (discovered devices) and merge them with user overrides last.
|
|
1152
|
+
*/
|
|
1153
|
+
export function mergeByDeviceId(a1, a2, allowConfigOnly = false) {
|
|
1154
|
+
const result = [];
|
|
1155
|
+
for (const itm of (a1 || [])) {
|
|
1156
|
+
const matchingItem = (a2 || []).find(item => normalizeDeviceId(item.deviceId) === normalizeDeviceId(itm.deviceId));
|
|
1157
|
+
if (matchingItem) {
|
|
1158
|
+
result.push({ ...matchingItem, ...itm });
|
|
1159
|
+
}
|
|
1160
|
+
else if (allowConfigOnly) {
|
|
1161
|
+
result.push(itm);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return result;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Apply device-type or remote-type templates to an array of devices.
|
|
1168
|
+
* Templates are config entries with applyToAllDevicesOfType=true.
|
|
1169
|
+
*
|
|
1170
|
+
* @param devices - Array of devices to apply templates to
|
|
1171
|
+
* @param configDevices - User config array that may contain template entries
|
|
1172
|
+
* @param typeKey - Property name to match device types ('deviceType' for devices, 'remoteType' for IR devices)
|
|
1173
|
+
* @param debugLog - Optional debug logging function
|
|
1174
|
+
* @returns Array of devices with templates applied
|
|
1175
|
+
*/
|
|
1176
|
+
export function applyDeviceTypeTemplates(devices, configDevices, typeKey, debugLog) {
|
|
1177
|
+
// Build a map of device-type templates from config devices with applyToAllDevicesOfType=true
|
|
1178
|
+
const typeTemplates = new Map();
|
|
1179
|
+
for (const configDevice of configDevices || []) {
|
|
1180
|
+
if (configDevice.applyToAllDevicesOfType) {
|
|
1181
|
+
// Get the type value from multiple possible sources
|
|
1182
|
+
const deviceType = configDevice[typeKey] || configDevice.configDeviceType || configDevice.configRemoteType;
|
|
1183
|
+
if (!deviceType) {
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
// Store all config properties except deviceId and applyToAllDevicesOfType flag
|
|
1187
|
+
const template = { ...configDevice };
|
|
1188
|
+
delete template.deviceId;
|
|
1189
|
+
delete template.applyToAllDevicesOfType;
|
|
1190
|
+
typeTemplates.set(deviceType, template);
|
|
1191
|
+
if (debugLog) {
|
|
1192
|
+
debugLog(`Device type template found for '${deviceType}': ${JSON.stringify(template)}`);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
// If no templates found, return original array
|
|
1197
|
+
if (typeTemplates.size === 0) {
|
|
1198
|
+
return devices;
|
|
1199
|
+
}
|
|
1200
|
+
// Apply templates to devices
|
|
1201
|
+
return devices.map((device) => {
|
|
1202
|
+
const deviceType = device[typeKey] || device.configDeviceType || device.configRemoteType;
|
|
1203
|
+
const template = typeTemplates.get(deviceType);
|
|
1204
|
+
if (template) {
|
|
1205
|
+
if (debugLog) {
|
|
1206
|
+
debugLog(`Applying device type template to ${device.deviceId} (${deviceType})`);
|
|
1207
|
+
}
|
|
1208
|
+
// Template settings go first, then device data (device data takes precedence)
|
|
1209
|
+
return Object.assign({}, template, device);
|
|
1210
|
+
}
|
|
1211
|
+
return device;
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Check if an API status code indicates success
|
|
1216
|
+
*/
|
|
1217
|
+
export function isSuccessfulStatusCode(statusCode) {
|
|
1218
|
+
return statusCode === 200 || statusCode === 100;
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Log status code messages with appropriate log level
|
|
1222
|
+
*/
|
|
1223
|
+
export async function logStatusCode(statusCode, log) {
|
|
1224
|
+
const messages = {
|
|
1225
|
+
151: `Command not supported by this device type, statusCode: ${statusCode}, Submit Feature Request Here:
|
|
1226
|
+
https://tinyurl.com/SwitchBotFeatureRequest`,
|
|
1227
|
+
152: `Device not found, statusCode: ${statusCode}`,
|
|
1228
|
+
160: `Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`,
|
|
1229
|
+
161: `Device is offline, statusCode: ${statusCode}`,
|
|
1230
|
+
171: `is offline, statusCode: ${statusCode}`,
|
|
1231
|
+
190: `Requests reached the daily limit, statusCode: ${statusCode}`,
|
|
1232
|
+
100: `Command successfully sent, statusCode: ${statusCode}`,
|
|
1233
|
+
200: `Request successful, statusCode: ${statusCode}`,
|
|
1234
|
+
400: `Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request payload,
|
|
1235
|
+
statusCode: ${statusCode}`,
|
|
1236
|
+
401: `Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`,
|
|
1237
|
+
403: `Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found,
|
|
1238
|
+
statusCode: ${statusCode}`,
|
|
1239
|
+
404: `Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`,
|
|
1240
|
+
406: `Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server,
|
|
1241
|
+
statusCode: ${statusCode}`,
|
|
1242
|
+
415: `Unsupported Media Type, The client has defined a contentType header that is not supported by the server, statusCode: ${statusCode}`,
|
|
1243
|
+
422: `Unprocessable Entity, The client has made a valid request, but the server cannot process it. This is often used for APIs for which
|
|
1244
|
+
certain limits have been exceeded, statusCode: ${statusCode}`,
|
|
1245
|
+
429: `Too Many Requests, The client has exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`,
|
|
1246
|
+
500: `Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare,
|
|
1247
|
+
statusCode: ${statusCode}`,
|
|
1248
|
+
};
|
|
1249
|
+
const message = messages[statusCode] ?? `Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`;
|
|
1250
|
+
if ([100, 200].includes(statusCode)) {
|
|
1251
|
+
await log.debugLog(message);
|
|
1252
|
+
}
|
|
1253
|
+
else {
|
|
1254
|
+
await log.errorLog(message);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Shared device logging helpers
|
|
1259
|
+
*/
|
|
1260
|
+
/**
|
|
1261
|
+
* Check if device logging is in debug mode
|
|
1262
|
+
*/
|
|
1263
|
+
export function deviceLoggingIsDebug(deviceLogging) {
|
|
1264
|
+
return deviceLogging === 'debugMode' || deviceLogging === 'debug';
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Check if device logging is enabled
|
|
1268
|
+
*/
|
|
1269
|
+
export function deviceLoggingEnabled(deviceLogging, platformLogging) {
|
|
1270
|
+
// If deviceLogging isn't provided, fall back to platform-wide flag
|
|
1271
|
+
if (deviceLogging === undefined || deviceLogging === '') {
|
|
1272
|
+
return platformLogging === 'debugMode' || platformLogging === 'debug' || platformLogging === 'standard';
|
|
1273
|
+
}
|
|
1274
|
+
return deviceLogging === 'debugMode' || deviceLogging === 'debug' || deviceLogging === 'standard';
|
|
1275
|
+
}
|
|
1276
|
+
export async function logDeviceStatusCode(statusCode, log, deviceId, hubDeviceId) {
|
|
1277
|
+
let adjustedStatusCode = statusCode;
|
|
1278
|
+
// Handle special case where device is its own hub
|
|
1279
|
+
if (statusCode === 171 && hubDeviceId && deviceId && (hubDeviceId === deviceId || hubDeviceId === '000000000000')) {
|
|
1280
|
+
if (log.debugErrorLog) {
|
|
1281
|
+
log.debugErrorLog(`statusCode 171 changed to 161: hubDeviceId ${hubDeviceId} matches deviceId ${deviceId}, device is its own hub.`);
|
|
1282
|
+
}
|
|
1283
|
+
adjustedStatusCode = 161;
|
|
1284
|
+
}
|
|
1285
|
+
const statusMessages = {
|
|
1286
|
+
151: 'Command not supported by this device type',
|
|
1287
|
+
152: 'Device not found',
|
|
1288
|
+
160: 'Command is not supported',
|
|
1289
|
+
161: 'Device is offline',
|
|
1290
|
+
171: hubDeviceId ? `Hub Device is offline. Hub: ${hubDeviceId}` : 'Hub Device is offline',
|
|
1291
|
+
190: 'Device internal error due to device states not synchronized with server, or command format is invalid',
|
|
1292
|
+
100: 'Command successfully sent',
|
|
1293
|
+
200: 'Request successful',
|
|
1294
|
+
400: 'Bad Request, an invalid payload request',
|
|
1295
|
+
401: 'Unauthorized, Authorization for the API is required, but the request has not been authenticated',
|
|
1296
|
+
403: 'Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found',
|
|
1297
|
+
404: 'Not Found, Specifies the requested path does not exist',
|
|
1298
|
+
406: 'Not Acceptable, a MIME type has been requested via the Accept header for a value not supported by the server',
|
|
1299
|
+
415: 'Unsupported Media Type, a contentType header has been defined that is not supported by the server',
|
|
1300
|
+
422: 'Unprocessable Entity: The server cannot process the request, often due to exceeded API limits.',
|
|
1301
|
+
429: 'Too Many Requests, exceeded the number of requests allowed for a given time window',
|
|
1302
|
+
500: 'Internal Server Error, An unexpected error occurred. These errors should be rare',
|
|
1303
|
+
};
|
|
1304
|
+
const logMessage = statusMessages[adjustedStatusCode] || `Unknown statusCode: ${adjustedStatusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`;
|
|
1305
|
+
const fullMessage = `${logMessage}, statusCode: ${adjustedStatusCode}`;
|
|
1306
|
+
if ([100, 200].includes(adjustedStatusCode)) {
|
|
1307
|
+
await log.debugLog(fullMessage);
|
|
1308
|
+
}
|
|
1309
|
+
else if (statusMessages[adjustedStatusCode]) {
|
|
1310
|
+
await log.errorLog(fullMessage);
|
|
1311
|
+
}
|
|
1312
|
+
else if (log.infoLog) {
|
|
1313
|
+
await log.infoLog(fullMessage);
|
|
1314
|
+
}
|
|
1315
|
+
else {
|
|
1316
|
+
await log.errorLog(fullMessage);
|
|
690
1317
|
}
|
|
691
|
-
return undefined;
|
|
692
1318
|
}
|
|
693
1319
|
//# sourceMappingURL=utils.js.map
|