@switchbot/homebridge-switchbot 5.0.0-beta.42 → 5.0.0-beta.44
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 +2 -0
- package/README.md +22 -0
- package/config.schema.json +71 -9
- 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.map +1 -1
- package/dist/devices-hap/device.js +20 -1
- 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/homebridge-ui/public/index.html +13 -1
- package/dist/platform-hap.d.ts +5 -0
- package/dist/platform-hap.d.ts.map +1 -1
- package/dist/platform-hap.js +106 -16
- package/dist/platform-hap.js.map +1 -1
- package/dist/platform-matter.d.ts +5 -0
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +129 -8
- package/dist/platform-matter.js.map +1 -1
- package/dist/settings.d.ts +17 -1
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- 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/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/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +74 -20
- package/dist/utils.js.map +1 -1
- package/docs/assets/highlight.css +14 -0
- package/docs/index.html +12 -1
- package/docs/variables/default.html +1 -1
- package/package.json +2 -2
- 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 +20 -1
- 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/homebridge-ui/public/index.html +13 -1
- package/src/platform-hap.ts +110 -16
- package/src/platform-matter.ts +137 -8
- package/src/settings.ts +17 -1
- package/src/test/hap/device-webhook-context.test.ts +136 -0
- package/src/test/matter/platform-matter.webhook.test.ts +54 -0
- package/src/utils.ts +88 -29
|
@@ -172,6 +172,11 @@ export declare class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
172
172
|
private batchRefreshAllDevices;
|
|
173
173
|
/** Refresh a single device with retry and backoff; returns status object if successful */
|
|
174
174
|
private refreshSingleDeviceWithRetry;
|
|
175
|
+
/**
|
|
176
|
+
* Validate that the user's configuration won't exceed API limits
|
|
177
|
+
* Warn if device count × polling frequency will hit daily limits
|
|
178
|
+
*/
|
|
179
|
+
private validateApiUsageConfig;
|
|
175
180
|
/** Simple concurrency limiter for an array of items */
|
|
176
181
|
private runWithConcurrency;
|
|
177
182
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-matter.d.ts","sourceRoot":"","sources":["../src/platform-matter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,GAAG,EACH,qBAAqB,EACrB,OAAO,EAEP,yBAAyB,EAC1B,MAAM,YAAY,CAAA;AAEnB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAY,MAAM,gBAAgB,CAAA;AAElE,OAAO,KAAK,EAAkC,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAkC5F,qBAAa,uBAAwB,YAAW,qBAAqB;aAsDjD,GAAG,EAAE,OAAO;aACZ,MAAM,EAAE,uBAAuB;aAC/B,GAAG,EAAE,GAAG;IAlD1B,SAAgB,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAY;IAErF,OAAO,CAAC,YAAY,CAAC,CAAkB;IACvC,OAAO,CAAC,YAAY,CAAC,CAAc;IAEnC,OAAO,CAAC,iBAAiB,CAAe;IAExC,OAAO,CAAC,mBAAmB,CAAiB;IAE5C,OAAO,CAAC,kBAAkB,CAA8B;IAExD,OAAO,CAAC,aAAa,CAAyC;IAE9D,OAAO,CAAC,oBAAoB,CAAC,CAAgB;IAE7C,OAAO,CAAC,iBAAiB,CAA8B;IAEvD,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,eAAe,CAAC,CAAQ;IAEhC,OAAO,CAAC,mBAAmB,CAAyB;IAEpD,OAAO,CAAC,eAAe,CAA8C;IAGrE,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,oBAAoB,CAAsB;IAClD,OAAO,CAAC,mBAAmB,CAA8C;IAGzE,OAAO,CAAC,eAAe,CAAC,CAAQ;IAGhC,OAAO,CAAC,UAAU,CAAC,CAAmB;IAGtC,OAAO,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAClC,UAAU,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACrC,eAAe,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC1C,OAAO,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAClC,YAAY,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACvC,QAAQ,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACnC,aAAa,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACxC,QAAQ,EAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACnC,cAAc,EAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACvC,uBAAuB,EAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;gBAG9B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,uBAAuB,EAC/B,GAAG,EAAE,GAAG;
|
|
1
|
+
{"version":3,"file":"platform-matter.d.ts","sourceRoot":"","sources":["../src/platform-matter.ts"],"names":[],"mappings":"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"}
|
package/dist/platform-matter.js
CHANGED
|
@@ -139,6 +139,7 @@ export class SwitchBotMatterPlatform {
|
|
|
139
139
|
dailyLimit: this.config.options?.dailyApiLimit ?? 10000,
|
|
140
140
|
reserveForCommands: this.config.options?.dailyApiReserveForCommands ?? 1000,
|
|
141
141
|
pausePollingAtReserve: this.config.options?.webhookOnlyOnReserve ?? false,
|
|
142
|
+
resetAtLocalMidnight: this.config.options?.dailyApiResetAtLocalMidnight ?? false,
|
|
142
143
|
});
|
|
143
144
|
this.apiTracker.startHourlyLogging();
|
|
144
145
|
}
|
|
@@ -384,6 +385,15 @@ export class SwitchBotMatterPlatform {
|
|
|
384
385
|
// Merge per-device overrides by matching deviceId
|
|
385
386
|
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices);
|
|
386
387
|
const merged = mergeByDeviceId(this.config.options?.devices ?? [], devicesWithTemplates ?? [], allowConfigOnly);
|
|
388
|
+
// Apply global webhook setting if not explicitly set on individual devices
|
|
389
|
+
if (this.config.options?.webhook === true) {
|
|
390
|
+
merged.forEach((device) => {
|
|
391
|
+
if (device.webhook === undefined) {
|
|
392
|
+
device.webhook = true;
|
|
393
|
+
this.debugLog(`Applied global webhook setting to Matter device: ${device.deviceId}`);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
387
397
|
return merged;
|
|
388
398
|
}
|
|
389
399
|
/**
|
|
@@ -412,6 +422,15 @@ export class SwitchBotMatterPlatform {
|
|
|
412
422
|
// Merge per-device overrides by matching deviceId
|
|
413
423
|
const allowConfigOnly = Boolean(this.config.options?.allowConfigOnlyDevices);
|
|
414
424
|
const merged = mergeByDeviceId(this.config.options?.irdevices ?? [], devicesWithTemplates ?? [], allowConfigOnly);
|
|
425
|
+
// Apply global webhook setting if not explicitly set on individual IR devices
|
|
426
|
+
if (this.config.options?.webhook === true) {
|
|
427
|
+
merged.forEach((device) => {
|
|
428
|
+
if (device.webhook === undefined) {
|
|
429
|
+
device.webhook = true;
|
|
430
|
+
this.debugLog(`Applied global webhook setting to Matter IR device: ${device.deviceId}`);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
415
434
|
return merged;
|
|
416
435
|
}
|
|
417
436
|
/**
|
|
@@ -460,8 +479,19 @@ export class SwitchBotMatterPlatform {
|
|
|
460
479
|
// so accessories can decide how verbose they should be.
|
|
461
480
|
deviceLogging: dev?.logging,
|
|
462
481
|
platformLogging: this.platformLogging,
|
|
482
|
+
// Expose effective webhook setting for parity with HAP base device logic
|
|
483
|
+
// Prefer explicit per-device setting; otherwise fall back to global option
|
|
484
|
+
webhook: dev?.webhook !== undefined ? dev.webhook : (this.config.options?.webhook === true ? true : undefined),
|
|
463
485
|
},
|
|
464
486
|
};
|
|
487
|
+
// Log effective webhook for diagnostics
|
|
488
|
+
try {
|
|
489
|
+
const effectiveWebhook = dev?.webhook !== undefined ? dev.webhook : (this.config.options?.webhook === true ? true : undefined);
|
|
490
|
+
this.debugLog(`Effective webhook for Matter device ${displayName} (${dev.deviceId}): ${String(effectiveWebhook)}`);
|
|
491
|
+
}
|
|
492
|
+
catch (e) {
|
|
493
|
+
this.debugLog(`Failed logging effective webhook for Matter device ${displayName} (${dev.deviceId}): ${e?.message ?? e}`);
|
|
494
|
+
}
|
|
465
495
|
// Build platform-side helpers using shared factories so they can be reused/tested
|
|
466
496
|
const sendOpenAPI = makeOpenAPISender(this.retryCommand.bind(this), dev, { maxRetries: this.config.options?.maxRetries ?? 1, delayBetweenRetries: this.config.options?.delayBetweenRetries ?? 1000 });
|
|
467
497
|
const sendBLE = makeBLESender(this.switchBotBLE, dev, { bleRetries: this.config.options?.bleRetries ?? 2, bleRetryDelay: this.config.options?.bleRetryDelay ?? 500 });
|
|
@@ -536,6 +566,7 @@ export class SwitchBotMatterPlatform {
|
|
|
536
566
|
// Locks
|
|
537
567
|
'Smart Lock': DoorLockAccessory,
|
|
538
568
|
'Smart Lock Pro': DoorLockAccessory,
|
|
569
|
+
'Smart Lock Ultra': DoorLockAccessory,
|
|
539
570
|
// Sensors
|
|
540
571
|
'Motion Sensor': OccupancySensorAccessory,
|
|
541
572
|
'Contact Sensor': ContactSensorAccessory,
|
|
@@ -1060,6 +1091,8 @@ export class SwitchBotMatterPlatform {
|
|
|
1060
1091
|
for (const d of irDeviceList) {
|
|
1061
1092
|
this.debugLog(` - ${d.deviceName} (${d.remoteType}) id=${d.deviceId}`);
|
|
1062
1093
|
}
|
|
1094
|
+
// Diagnostic: warn users if their device count + refresh rate may exceed daily limits
|
|
1095
|
+
this.validateApiUsageConfig(deviceList.length, irDeviceList.length);
|
|
1063
1096
|
}
|
|
1064
1097
|
else {
|
|
1065
1098
|
this.warnLog(`SwitchBot getDevices returned status ${statusCode}`);
|
|
@@ -1153,15 +1186,16 @@ export class SwitchBotMatterPlatform {
|
|
|
1153
1186
|
* Retry wrapper for control commands using SwitchBot OpenAPI
|
|
1154
1187
|
*/
|
|
1155
1188
|
async retryCommand(deviceObj, bodyChange, maxRetries = 1, delayBetweenRetries = 1000) {
|
|
1189
|
+
// Check API budget BEFORE attempting any retries
|
|
1190
|
+
if (!this.apiTracker?.trySpend('command')) {
|
|
1191
|
+
return { response: {}, statusCode: 429 };
|
|
1192
|
+
}
|
|
1156
1193
|
let retryCount = 0;
|
|
1157
1194
|
while (retryCount < maxRetries) {
|
|
1158
1195
|
try {
|
|
1159
1196
|
if (!this.switchBotAPI) {
|
|
1160
1197
|
throw new Error('SwitchBot OpenAPI not initialized');
|
|
1161
1198
|
}
|
|
1162
|
-
if (!this.apiTracker?.trySpend('command')) {
|
|
1163
|
-
return { response: {}, statusCode: 429 };
|
|
1164
|
-
}
|
|
1165
1199
|
const { response, statusCode } = await this.switchBotAPI.controlDevice(deviceObj.deviceId, bodyChange.command, bodyChange.parameter, bodyChange.commandType, this.config.credentials?.token, this.config.credentials?.secret);
|
|
1166
1200
|
return { response, statusCode };
|
|
1167
1201
|
}
|
|
@@ -1412,6 +1446,41 @@ export class SwitchBotMatterPlatform {
|
|
|
1412
1446
|
this.debugLog(`Failed to apply runState for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
1413
1447
|
}
|
|
1414
1448
|
}
|
|
1449
|
+
// Robot vacuum: some firmwares expose 'taskType' or 'task' with similar semantics
|
|
1450
|
+
// to runState (e.g., 'cleaning', 'mapping', 'idle'). Map to rvcRunMode accordingly.
|
|
1451
|
+
if (status?.taskType !== undefined || status?.task_type !== undefined || status?.task !== undefined) {
|
|
1452
|
+
try {
|
|
1453
|
+
const raw = status?.taskType ?? status?.task_type ?? status?.task;
|
|
1454
|
+
let mode;
|
|
1455
|
+
if (typeof raw === 'number') {
|
|
1456
|
+
mode = Number(raw);
|
|
1457
|
+
}
|
|
1458
|
+
else if (typeof raw === 'string') {
|
|
1459
|
+
const s = raw.toLowerCase();
|
|
1460
|
+
if (s.includes('clean')) {
|
|
1461
|
+
mode = 1; // Cleaning
|
|
1462
|
+
}
|
|
1463
|
+
else if (s.includes('map')) {
|
|
1464
|
+
mode = 2; // Mapping
|
|
1465
|
+
}
|
|
1466
|
+
else if (s.includes('idle') || s.includes('stop') || s.includes('dock') || s.includes('charge') || s.includes('docked')) {
|
|
1467
|
+
mode = 0; // Idle
|
|
1468
|
+
}
|
|
1469
|
+
else {
|
|
1470
|
+
const n = Number(raw);
|
|
1471
|
+
if (!Number.isNaN(n)) {
|
|
1472
|
+
mode = n;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (mode !== undefined) {
|
|
1477
|
+
await safeUpdate('rvcRunMode', { currentMode: Number(mode) }, 'updateRunMode');
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
catch (e) {
|
|
1481
|
+
this.debugLog(`Failed to apply taskType for ${dev.deviceId}: ${e?.message ?? e}`);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1415
1484
|
// Brightness
|
|
1416
1485
|
if (status?.brightness !== undefined) {
|
|
1417
1486
|
const rawBrightness = Number(status.brightness);
|
|
@@ -1473,7 +1542,8 @@ export class SwitchBotMatterPlatform {
|
|
|
1473
1542
|
}
|
|
1474
1543
|
// Battery/powerSource (support many possible field names)
|
|
1475
1544
|
// Note: Some device types like WindowBlind don't support PowerSource cluster
|
|
1476
|
-
if (status?.battery !== undefined || status?.batt !== undefined || status?.batteryLevel !== undefined || status?.batteryPercentage !== undefined || status?.battery_level !== undefined
|
|
1545
|
+
if (status?.battery !== undefined || status?.batt !== undefined || status?.batteryLevel !== undefined || status?.batteryPercentage !== undefined || status?.battery_level !== undefined
|
|
1546
|
+
|| status?.baseBattery !== undefined || status?.base_battery !== undefined || status?.stationBattery !== undefined || status?.waterBaseBattery !== undefined || status?.dockBattery !== undefined) {
|
|
1477
1547
|
// Skip battery updates for device types that don't support PowerSource cluster
|
|
1478
1548
|
const deviceType = String(status?.deviceType ?? dev?.deviceType ?? '');
|
|
1479
1549
|
const unsupportedTypes = ['Curtain', 'Curtain2', 'Curtain3', 'Curtain 2', 'Blind Tilt'];
|
|
@@ -1482,7 +1552,8 @@ export class SwitchBotMatterPlatform {
|
|
|
1482
1552
|
}
|
|
1483
1553
|
else {
|
|
1484
1554
|
try {
|
|
1485
|
-
const percentage = Number(status?.battery ?? status?.batt ?? status?.batteryPercentage ?? status?.batteryLevel ?? status?.battery_level
|
|
1555
|
+
const percentage = Number(status?.battery ?? status?.batt ?? status?.batteryPercentage ?? status?.batteryLevel ?? status?.battery_level
|
|
1556
|
+
?? status?.baseBattery ?? status?.base_battery ?? status?.stationBattery ?? status?.waterBaseBattery ?? status?.dockBattery);
|
|
1486
1557
|
const batPercentRemaining = Math.max(0, Math.min(200, Math.round(percentage * 2)));
|
|
1487
1558
|
let batChargeLevel = 0;
|
|
1488
1559
|
if (percentage < 20) {
|
|
@@ -2250,12 +2321,14 @@ export class SwitchBotMatterPlatform {
|
|
|
2250
2321
|
/** Refresh a single device with retry and backoff; returns status object if successful */
|
|
2251
2322
|
async refreshSingleDeviceWithRetry(dev, retries = 3, baseDelayMs = 500) {
|
|
2252
2323
|
const deviceId = dev.deviceId;
|
|
2324
|
+
// Check API budget BEFORE attempting any retries - don't waste cycles on blocked requests
|
|
2325
|
+
if (!this.apiTracker?.trySpend('poll')) {
|
|
2326
|
+
// Don't log on every blocked request - the ApiRequestTracker handles periodic warnings
|
|
2327
|
+
return null;
|
|
2328
|
+
}
|
|
2253
2329
|
let attempt = 0;
|
|
2254
2330
|
while (attempt <= retries) {
|
|
2255
2331
|
try {
|
|
2256
|
-
if (!this.apiTracker?.trySpend('poll')) {
|
|
2257
|
-
return null;
|
|
2258
|
-
}
|
|
2259
2332
|
const { response, statusCode } = await this.switchBotAPI.getDeviceStatus(deviceId, this.config.credentials?.token, this.config.credentials?.secret);
|
|
2260
2333
|
const respAny = response;
|
|
2261
2334
|
const body = respAny?.body ?? respAny;
|
|
@@ -2287,6 +2360,54 @@ export class SwitchBotMatterPlatform {
|
|
|
2287
2360
|
catch { }
|
|
2288
2361
|
return null;
|
|
2289
2362
|
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Validate that the user's configuration won't exceed API limits
|
|
2365
|
+
* Warn if device count × polling frequency will hit daily limits
|
|
2366
|
+
*/
|
|
2367
|
+
validateApiUsageConfig(deviceCount, irDeviceCount) {
|
|
2368
|
+
try {
|
|
2369
|
+
const totalDevices = deviceCount + irDeviceCount;
|
|
2370
|
+
if (totalDevices === 0) {
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
// For Matter platform, use matterBatchRefreshRate or fallback to refreshRate
|
|
2374
|
+
const refreshRate = this.getPlatformBatchInterval(); // seconds
|
|
2375
|
+
const dailyLimit = this.config.options?.dailyApiLimit ?? 10000;
|
|
2376
|
+
const reserveForCommands = this.config.options?.dailyApiReserveForCommands ?? 1000;
|
|
2377
|
+
// Calculate polls per day (86400 seconds in a day)
|
|
2378
|
+
const pollsPerDevicePerDay = Math.floor(86400 / refreshRate);
|
|
2379
|
+
const totalPollsPerDay = pollsPerDevicePerDay * totalDevices;
|
|
2380
|
+
// Add discovery calls (typically 1-2 per day)
|
|
2381
|
+
const estimatedDiscoveryCalls = 2;
|
|
2382
|
+
const totalEstimatedCalls = totalPollsPerDay + estimatedDiscoveryCalls;
|
|
2383
|
+
const usableLimit = dailyLimit - reserveForCommands;
|
|
2384
|
+
const percentOfLimit = Math.round((totalEstimatedCalls / usableLimit) * 100);
|
|
2385
|
+
this.debugLog(`[API Usage Diagnostic] ${totalDevices} devices × ${pollsPerDevicePerDay} polls/day = ${totalPollsPerDay} estimated daily polls`);
|
|
2386
|
+
this.debugLog(`[API Usage Diagnostic] With ${reserveForCommands} reserved for commands, usable limit is ${usableLimit}`);
|
|
2387
|
+
if (totalEstimatedCalls > dailyLimit) {
|
|
2388
|
+
this.errorLog(`⚠️ API LIMIT WARNING: Your configuration will exceed the daily API limit!`);
|
|
2389
|
+
this.errorLog(` Devices: ${totalDevices} | Refresh rate: ${refreshRate}s | Estimated daily polls: ${totalEstimatedCalls}`);
|
|
2390
|
+
this.errorLog(` Daily limit: ${dailyLimit} | You will use ${percentOfLimit}% of available budget`);
|
|
2391
|
+
this.errorLog(` SOLUTION: Increase matterBatchRefreshRate to ${Math.ceil((totalDevices * 86400) / usableLimit)} seconds or higher`);
|
|
2392
|
+
this.errorLog(` OR: Enable webhooks and set 'webhookOnlyOnReserve: true' to reduce polling`);
|
|
2393
|
+
}
|
|
2394
|
+
else if (totalEstimatedCalls > usableLimit) {
|
|
2395
|
+
this.warnLog(`⚠️ API USAGE WARNING: Configuration may exceed usable daily API budget`);
|
|
2396
|
+
this.warnLog(` Devices: ${totalDevices} | Refresh rate: ${refreshRate}s | Estimated daily polls: ${totalEstimatedCalls}`);
|
|
2397
|
+
this.warnLog(` Usable limit (after reserve): ${usableLimit} | You will use ${percentOfLimit}% of budget`);
|
|
2398
|
+
this.warnLog(` Polling may pause when approaching limit. Consider increasing matterBatchRefreshRate to ${Math.ceil((totalDevices * 86400) / usableLimit)}s`);
|
|
2399
|
+
}
|
|
2400
|
+
else if (percentOfLimit > 75) {
|
|
2401
|
+
this.infoLog(`[API Usage] Using ${percentOfLimit}% of daily budget (${totalEstimatedCalls}/${usableLimit} calls). Monitor usage if adding more devices.`);
|
|
2402
|
+
}
|
|
2403
|
+
else {
|
|
2404
|
+
this.debugLog(`[API Usage] Configuration looks good: ${percentOfLimit}% of daily budget (${totalEstimatedCalls}/${usableLimit} calls)`);
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
catch (e) {
|
|
2408
|
+
this.debugLog(`Failed to validate API usage config: ${e?.message ?? e}`);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2290
2411
|
/** Simple concurrency limiter for an array of items */
|
|
2291
2412
|
async runWithConcurrency(items, worker, concurrency) {
|
|
2292
2413
|
const queue = items.slice();
|