@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.
@@ -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
- } else {
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
  }
@@ -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, do not register example/demo accessories.
1869
- this.infoLog('No discovered SwitchBot devices found.')
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
  */