@switchbot/homebridge-switchbot 5.0.0-beta.38 → 5.0.0-beta.39
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/dist/platform-hap.d.ts +5 -0
- package/dist/platform-hap.d.ts.map +1 -1
- package/dist/platform-hap.js +128 -1
- 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 +87 -2
- package/dist/platform-matter.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/platform-hap.ts +136 -1
- package/src/platform-matter.ts +90 -2
package/src/platform-hap.ts
CHANGED
|
@@ -483,6 +483,7 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
483
483
|
let retryCount = 0
|
|
484
484
|
const maxRetries = this.platformMaxRetries ?? 5
|
|
485
485
|
const delayBetweenRetries = this.platformDelayBetweenRetries || 5000
|
|
486
|
+
let rateLimitExceeded = false
|
|
486
487
|
|
|
487
488
|
this.debugWarnLog(`Retry Count: ${retryCount}`)
|
|
488
489
|
this.debugWarnLog(`Max Retries: ${this.platformMaxRetries}`)
|
|
@@ -498,6 +499,13 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
498
499
|
await this.handleIRDevices(Array.isArray(response.body.infraredRemoteList) ? response.body.infraredRemoteList : [])
|
|
499
500
|
break
|
|
500
501
|
} else {
|
|
502
|
+
// Check if rate limit exceeded (429)
|
|
503
|
+
if (statusCode === 429) {
|
|
504
|
+
rateLimitExceeded = true
|
|
505
|
+
this.warnLog('OpenAPI rate limit (429) exceeded. Falling back to manual device configuration.')
|
|
506
|
+
this.warnLog('Webhook functionality will still work if devices are configured manually.')
|
|
507
|
+
break
|
|
508
|
+
}
|
|
501
509
|
await this.handleErrorResponse(statusCode, retryCount, maxRetries, delayBetweenRetries)
|
|
502
510
|
retryCount++
|
|
503
511
|
}
|
|
@@ -507,9 +515,116 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
507
515
|
this.debugErrorLog(`Failed to Discover Devices, Error: ${e.message ?? e}`)
|
|
508
516
|
}
|
|
509
517
|
}
|
|
518
|
+
|
|
519
|
+
// If rate limit exceeded or retries exhausted, try to load from manual config or cached accessories
|
|
520
|
+
if (rateLimitExceeded || retryCount >= maxRetries) {
|
|
521
|
+
const hasCachedAccessories = this.accessories.length > 0
|
|
522
|
+
const hasManualConfig = this.config.options?.devices || this.config.options?.irdevices
|
|
523
|
+
|
|
524
|
+
if (hasManualConfig || hasCachedAccessories) {
|
|
525
|
+
if (hasCachedAccessories) {
|
|
526
|
+
this.warnLog(`Found ${this.accessories.length} cached accessories from previous sessions.`)
|
|
527
|
+
this.warnLog('Reinstantiating device classes to enable webhook handlers...')
|
|
528
|
+
await this.restoreCachedAccessories()
|
|
529
|
+
}
|
|
530
|
+
if (hasManualConfig) {
|
|
531
|
+
this.warnLog('Attempting to load devices from manual configuration...')
|
|
532
|
+
await this.handleManualConfig()
|
|
533
|
+
}
|
|
534
|
+
if (hasCachedAccessories) {
|
|
535
|
+
this.infoLog('Cached accessories restored. Webhook functionality is active.')
|
|
536
|
+
this.infoLog('Device discovery will resume when API rate limit resets.')
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
this.errorLog('OpenAPI unavailable and no cached accessories or manual device configuration found.')
|
|
540
|
+
this.errorLog('Please configure devices manually in the plugin settings to use webhook functionality.')
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Restore cached accessories by reinstantiating their device classes
|
|
547
|
+
* This ensures webhook handlers and other functionality are properly set up
|
|
548
|
+
*/
|
|
549
|
+
private async restoreCachedAccessories() {
|
|
550
|
+
this.debugLog('Restoring cached accessories and setting up webhook handlers...')
|
|
551
|
+
|
|
552
|
+
for (const accessory of this.accessories) {
|
|
553
|
+
try {
|
|
554
|
+
const device = accessory.context.device
|
|
555
|
+
const deviceType = accessory.context.deviceType || device?.deviceType
|
|
556
|
+
const deviceId = accessory.context.deviceId || device?.deviceId
|
|
557
|
+
|
|
558
|
+
if (!device || !deviceType || !deviceId) {
|
|
559
|
+
this.debugWarnLog(`Skipping cached accessory ${accessory.displayName} - missing context data`)
|
|
560
|
+
continue
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
this.debugLog(`Reinstantiating ${deviceType} for cached accessory: ${accessory.displayName}`)
|
|
564
|
+
|
|
565
|
+
// Reinstantiate the device class based on deviceType
|
|
566
|
+
const deviceTypeHandlers: { [key: string]: new (platform: any, accessory: PlatformAccessory, device: any) => any } = {
|
|
567
|
+
'Humidifier': Humidifier,
|
|
568
|
+
'Humidifier2': Humidifier,
|
|
569
|
+
'Hub 2': Hub,
|
|
570
|
+
'Hub 3': Hub,
|
|
571
|
+
'Bot': Bot,
|
|
572
|
+
'Relay Switch 1': RelaySwitch,
|
|
573
|
+
'Relay Switch 1PM': RelaySwitch,
|
|
574
|
+
'Meter': Meter,
|
|
575
|
+
'MeterPlus': MeterPlus,
|
|
576
|
+
'Meter Plus (JP)': MeterPlus,
|
|
577
|
+
'MeterPro': MeterPro,
|
|
578
|
+
'MeterPro(CO2)': MeterPro,
|
|
579
|
+
'WoIOSensor': IOSensor,
|
|
580
|
+
'Water Detector': WaterDetector,
|
|
581
|
+
'Motion Sensor': Motion,
|
|
582
|
+
'Contact Sensor': Contact,
|
|
583
|
+
'Curtain': Curtain,
|
|
584
|
+
'Curtain3': Curtain,
|
|
585
|
+
'WoRollerShade': Curtain,
|
|
586
|
+
'Roller Shade': Curtain,
|
|
587
|
+
'Blind Tilt': BlindTilt,
|
|
588
|
+
'Plug': Plug,
|
|
589
|
+
'Plug Mini (US)': Plug,
|
|
590
|
+
'Plug Mini (JP)': Plug,
|
|
591
|
+
'Smart Lock': Lock,
|
|
592
|
+
'Smart Lock Pro': Lock,
|
|
593
|
+
'Color Bulb': ColorBulb,
|
|
594
|
+
'K10+': RobotVacuumCleaner,
|
|
595
|
+
'K10+ Pro': RobotVacuumCleaner,
|
|
596
|
+
'WoSweeper': RobotVacuumCleaner,
|
|
597
|
+
'WoSweeperMini': RobotVacuumCleaner,
|
|
598
|
+
'Robot Vacuum Cleaner S1': RobotVacuumCleaner,
|
|
599
|
+
'Robot Vacuum Cleaner S1 Plus': RobotVacuumCleaner,
|
|
600
|
+
'Robot Vacuum Cleaner S10': RobotVacuumCleaner,
|
|
601
|
+
'Ceiling Light': CeilingLight,
|
|
602
|
+
'Ceiling Light Pro': CeilingLight,
|
|
603
|
+
'Strip Light': StripLight,
|
|
604
|
+
'Battery Circulator Fan': Fan,
|
|
605
|
+
'Air Purifier PM2.5': AirPurifier,
|
|
606
|
+
'Air Purifier Table PM2.5': AirPurifier,
|
|
607
|
+
'Air Purifier VOC': AirPurifier,
|
|
608
|
+
'Air Purifier Table VOC': AirPurifier,
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const DeviceClass = deviceTypeHandlers[deviceType]
|
|
612
|
+
if (DeviceClass) {
|
|
613
|
+
new DeviceClass(this, accessory, device)
|
|
614
|
+
this.debugSuccessLog(`Successfully restored ${deviceType}: ${accessory.displayName}`)
|
|
615
|
+
} else {
|
|
616
|
+
this.debugLog(`No handler for device type: ${deviceType}`)
|
|
617
|
+
}
|
|
618
|
+
} catch (e: any) {
|
|
619
|
+
this.errorLog(`Failed to restore cached accessory ${accessory.displayName}, Error: ${e.message ?? e}`)
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
this.infoLog(`Restored ${this.accessories.length} cached accessories with webhook support`)
|
|
510
624
|
}
|
|
511
625
|
|
|
512
626
|
private async handleManualConfig() {
|
|
627
|
+
// Handle regular devices
|
|
513
628
|
if (this.config.options?.devices) {
|
|
514
629
|
this.debugLog(`SwitchBot Device Manual Config Set: ${JSON.stringify(this.config.options?.devices)}`)
|
|
515
630
|
const devices = this.config.options.devices.map((v: any) => v)
|
|
@@ -526,7 +641,27 @@ export class SwitchBotHAPPlatform implements DynamicPlatformPlugin {
|
|
|
526
641
|
this.errorLog(`failed to format device ID as MAC, Error: ${error}`)
|
|
527
642
|
}
|
|
528
643
|
}
|
|
529
|
-
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Handle IR devices
|
|
647
|
+
if (this.config.options?.irdevices) {
|
|
648
|
+
this.debugLog(`SwitchBot IR Device Manual Config Set: ${JSON.stringify(this.config.options?.irdevices)}`)
|
|
649
|
+
const irdevices = this.config.options.irdevices.map((v: any) => v)
|
|
650
|
+
for (const irdevice of irdevices) {
|
|
651
|
+
irdevice.remoteType = irdevice.configRemoteType !== undefined ? irdevice.configRemoteType : 'Unknown'
|
|
652
|
+
irdevice.deviceName = irdevice.configDeviceName !== undefined ? irdevice.configDeviceName : 'Unknown'
|
|
653
|
+
try {
|
|
654
|
+
this.debugLog(`IR deviceId: ${irdevice.deviceId}`)
|
|
655
|
+
if (irdevice.remoteType) {
|
|
656
|
+
await this.createIRDevice(irdevice)
|
|
657
|
+
}
|
|
658
|
+
} catch (error) {
|
|
659
|
+
this.errorLog(`failed to create IR device, Error: ${error}`)
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (!this.config.options?.devices && !this.config.options?.irdevices) {
|
|
530
665
|
this.errorLog('Neither SwitchBot Token or Device Config are set.')
|
|
531
666
|
}
|
|
532
667
|
}
|
package/src/platform-matter.ts
CHANGED
|
@@ -1076,6 +1076,25 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1076
1076
|
this.debugLog(`Failed to schedule refresh for ${dev.deviceId}: ${e?.message ?? e}`)
|
|
1077
1077
|
}
|
|
1078
1078
|
|
|
1079
|
+
// Register webhook handler for this device
|
|
1080
|
+
try {
|
|
1081
|
+
if (dev.webhook && dev.deviceId) {
|
|
1082
|
+
this.debugLog(`Registering webhook handler for Matter device: ${dev.deviceId}`)
|
|
1083
|
+
this.webhookEventHandler[dev.deviceId] = async (context: any) => {
|
|
1084
|
+
try {
|
|
1085
|
+
this.debugLog(`Received webhook for Matter device ${dev.deviceId}: ${JSON.stringify(context)}`)
|
|
1086
|
+
// Apply webhook status update to the accessory
|
|
1087
|
+
await this.applyStatusToAccessory(uuid, dev, context)
|
|
1088
|
+
} catch (e: any) {
|
|
1089
|
+
this.errorLog(`Failed to handle webhook for ${dev.deviceId}: ${e?.message ?? e}`)
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
this.debugSuccessLog(`Webhook handler registered for ${dev.deviceId}`)
|
|
1093
|
+
}
|
|
1094
|
+
} catch (e: any) {
|
|
1095
|
+
this.debugLog(`Failed to register webhook handler for ${dev.deviceId}: ${e?.message ?? e}`)
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1079
1098
|
return instance.toAccessory()
|
|
1080
1099
|
}
|
|
1081
1100
|
|
|
@@ -1108,6 +1127,12 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1108
1127
|
}
|
|
1109
1128
|
} else {
|
|
1110
1129
|
this.warnLog(`SwitchBot getDevices returned status ${statusCode}`)
|
|
1130
|
+
// If rate limit exceeded (429), log specific message
|
|
1131
|
+
if (statusCode === 429) {
|
|
1132
|
+
this.warnLog('OpenAPI rate limit (429) exceeded during discovery.')
|
|
1133
|
+
this.warnLog('Webhook functionality will still work for manually configured devices.')
|
|
1134
|
+
this.warnLog('Device state updates will be limited until rate limit resets.')
|
|
1135
|
+
}
|
|
1111
1136
|
}
|
|
1112
1137
|
} catch (e: any) {
|
|
1113
1138
|
this.errorLog('Failed to discover SwitchBot devices:', e?.message ?? e)
|
|
@@ -1865,14 +1890,77 @@ export class SwitchBotMatterPlatform implements DynamicPlatformPlugin {
|
|
|
1865
1890
|
return
|
|
1866
1891
|
}
|
|
1867
1892
|
|
|
1868
|
-
// If no discovered devices are available,
|
|
1869
|
-
this.
|
|
1893
|
+
// If no discovered devices are available, check for cached Matter accessories
|
|
1894
|
+
const hasCachedAccessories = this.matterAccessories.size > 0
|
|
1895
|
+
if (hasCachedAccessories) {
|
|
1896
|
+
this.infoLog(`No devices discovered via OpenAPI, but found ${this.matterAccessories.size} cached Matter accessories.`)
|
|
1897
|
+
this.infoLog('Cached accessories will continue to function with webhook updates.')
|
|
1898
|
+
this.infoLog('Restoring webhook handlers for cached Matter accessories...')
|
|
1899
|
+
await this.restoreCachedMatterAccessoryWebhooks()
|
|
1900
|
+
this.infoLog('Device discovery will resume when API becomes available.')
|
|
1901
|
+
} else {
|
|
1902
|
+
this.infoLog('No discovered SwitchBot devices found.')
|
|
1903
|
+
}
|
|
1870
1904
|
|
|
1871
1905
|
this.debugLog('═'.repeat(80))
|
|
1872
1906
|
this.debugLog('Finished registering Matter accessories')
|
|
1873
1907
|
this.debugLog('═'.repeat(80))
|
|
1874
1908
|
}
|
|
1875
1909
|
|
|
1910
|
+
/**
|
|
1911
|
+
* Restore webhook handlers for cached Matter accessories
|
|
1912
|
+
* This ensures webhook functionality continues to work even when device discovery fails
|
|
1913
|
+
*/
|
|
1914
|
+
private async restoreCachedMatterAccessoryWebhooks() {
|
|
1915
|
+
this.debugLog('Restoring webhook handlers for cached Matter accessories...')
|
|
1916
|
+
|
|
1917
|
+
let restoredCount = 0
|
|
1918
|
+
for (const [uuid, accessory] of this.matterAccessories.entries()) {
|
|
1919
|
+
try {
|
|
1920
|
+
const context = (accessory as any)?.context
|
|
1921
|
+
const deviceId = context?.deviceId
|
|
1922
|
+
const webhook = context?.webhook
|
|
1923
|
+
|
|
1924
|
+
if (!deviceId) {
|
|
1925
|
+
this.debugLog(`Skipping cached accessory ${accessory.displayName} - no deviceId in context`)
|
|
1926
|
+
continue
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// Only register webhook if the device had webhook enabled
|
|
1930
|
+
if (webhook) {
|
|
1931
|
+
this.debugLog(`Restoring webhook handler for Matter device: ${deviceId}`)
|
|
1932
|
+
|
|
1933
|
+
// Create a minimal device object from cached context for webhook handling
|
|
1934
|
+
const dev: any = {
|
|
1935
|
+
deviceId,
|
|
1936
|
+
deviceName: context?.name || accessory.displayName,
|
|
1937
|
+
deviceType: context?.deviceType,
|
|
1938
|
+
webhook: true,
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
this.webhookEventHandler[deviceId] = async (webhookContext: any) => {
|
|
1942
|
+
try {
|
|
1943
|
+
this.debugLog(`Received webhook for cached Matter device ${deviceId}: ${JSON.stringify(webhookContext)}`)
|
|
1944
|
+
// Apply webhook status update to the accessory
|
|
1945
|
+
await this.applyStatusToAccessory(uuid, dev, webhookContext)
|
|
1946
|
+
} catch (e: any) {
|
|
1947
|
+
this.errorLog(`Failed to handle webhook for cached device ${deviceId}: ${e?.message ?? e}`)
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
restoredCount++
|
|
1952
|
+
this.debugSuccessLog(`Webhook handler restored for ${deviceId}`)
|
|
1953
|
+
} else {
|
|
1954
|
+
this.debugLog(`Device ${deviceId} does not have webhook enabled, skipping`)
|
|
1955
|
+
}
|
|
1956
|
+
} catch (e: any) {
|
|
1957
|
+
this.errorLog(`Failed to restore webhook handler for cached accessory ${accessory.displayName}: ${e?.message ?? e}`)
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
this.infoLog(`Restored webhook handlers for ${restoredCount} cached Matter accessories`)
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1876
1964
|
/**
|
|
1877
1965
|
* Remove accessories that are disabled in config
|
|
1878
1966
|
*/
|