node-switchbot 4.0.0-beta.1 → 4.0.0-beta.3

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.
Files changed (170) hide show
  1. package/BLE.md +63 -0
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +6 -0
  4. package/dist/devices/base.d.ts +59 -5
  5. package/dist/devices/base.d.ts.map +1 -1
  6. package/dist/devices/base.js +280 -28
  7. package/dist/devices/base.js.map +1 -1
  8. package/dist/devices/wo-hand.d.ts +35 -0
  9. package/dist/devices/wo-hand.d.ts.map +1 -1
  10. package/dist/devices/wo-hand.js +87 -0
  11. package/dist/devices/wo-hand.js.map +1 -1
  12. package/dist/settings.d.ts +6 -0
  13. package/dist/settings.d.ts.map +1 -1
  14. package/dist/settings.js +6 -0
  15. package/dist/settings.js.map +1 -1
  16. package/dist/switchbot.d.ts.map +1 -1
  17. package/dist/switchbot.js +14 -0
  18. package/dist/switchbot.js.map +1 -1
  19. package/dist/types/index.d.ts +12 -0
  20. package/dist/types/index.d.ts.map +1 -1
  21. package/dist/types/index.js.map +1 -1
  22. package/dist/utils/bot-ble.d.ts +36 -0
  23. package/dist/utils/bot-ble.d.ts.map +1 -0
  24. package/dist/utils/bot-ble.js +107 -0
  25. package/dist/utils/bot-ble.js.map +1 -0
  26. package/dist/utils/circuit-breaker.d.ts +98 -0
  27. package/dist/utils/circuit-breaker.d.ts.map +1 -0
  28. package/dist/utils/circuit-breaker.js +187 -0
  29. package/dist/utils/circuit-breaker.js.map +1 -0
  30. package/dist/utils/connection-tracker.d.ts +66 -0
  31. package/dist/utils/connection-tracker.d.ts.map +1 -0
  32. package/dist/utils/connection-tracker.js +184 -0
  33. package/dist/utils/connection-tracker.js.map +1 -0
  34. package/dist/utils/fallback-handler.d.ts +68 -0
  35. package/dist/utils/fallback-handler.d.ts.map +1 -0
  36. package/dist/utils/fallback-handler.js +131 -0
  37. package/dist/utils/fallback-handler.js.map +1 -0
  38. package/dist/utils/index.d.ts +5 -0
  39. package/dist/utils/index.d.ts.map +1 -1
  40. package/dist/utils/index.js +7 -0
  41. package/dist/utils/index.js.map +1 -1
  42. package/dist/utils/retry.d.ts +55 -0
  43. package/dist/utils/retry.d.ts.map +1 -0
  44. package/dist/utils/retry.js +95 -0
  45. package/dist/utils/retry.js.map +1 -0
  46. package/docs/assets/hierarchy.js +1 -1
  47. package/docs/assets/search.js +1 -1
  48. package/docs/classes/APIError.html +2 -2
  49. package/docs/classes/APINotAvailableError.html +2 -2
  50. package/docs/classes/BLEConnection.html +9 -9
  51. package/docs/classes/BLENotAvailableError.html +2 -2
  52. package/docs/classes/BLEScanner.html +8 -8
  53. package/docs/classes/CommandFailedError.html +2 -2
  54. package/docs/classes/ConnectionTimeoutError.html +2 -2
  55. package/docs/classes/DeviceManager.html +13 -13
  56. package/docs/classes/DeviceNotFoundError.html +2 -2
  57. package/docs/classes/DiscoveryError.html +2 -2
  58. package/docs/classes/OpenAPIClient.html +24 -24
  59. package/docs/classes/SwitchBot.html +11 -11
  60. package/docs/classes/SwitchBotDevice.html +33 -15
  61. package/docs/classes/SwitchBotError.html +2 -2
  62. package/docs/classes/ValidationError.html +2 -2
  63. package/docs/classes/WoAirPurifier.html +36 -18
  64. package/docs/classes/WoAirPurifierTable.html +36 -18
  65. package/docs/classes/WoBlindTilt.html +38 -20
  66. package/docs/classes/WoBulb.html +37 -19
  67. package/docs/classes/WoCeilingLight.html +37 -19
  68. package/docs/classes/WoContact.html +32 -14
  69. package/docs/classes/WoCurtain.html +36 -18
  70. package/docs/classes/WoHand.html +45 -19
  71. package/docs/classes/WoHub2.html +32 -14
  72. package/docs/classes/WoHub3.html +32 -14
  73. package/docs/classes/WoHumi.html +36 -18
  74. package/docs/classes/WoHumi2.html +36 -18
  75. package/docs/classes/WoIOSensorTH.html +32 -14
  76. package/docs/classes/WoKeypad.html +32 -14
  77. package/docs/classes/WoLeak.html +32 -14
  78. package/docs/classes/WoPlugMiniJP.html +35 -17
  79. package/docs/classes/WoPlugMiniUS.html +35 -17
  80. package/docs/classes/WoPresence.html +32 -14
  81. package/docs/classes/WoRelaySwitch1.html +35 -17
  82. package/docs/classes/WoRelaySwitch1PM.html +35 -17
  83. package/docs/classes/WoRemote.html +32 -14
  84. package/docs/classes/WoSensorTH.html +32 -14
  85. package/docs/classes/WoSensorTHPlus.html +32 -14
  86. package/docs/classes/WoSensorTHPro.html +32 -14
  87. package/docs/classes/WoSensorTHProCO2.html +32 -14
  88. package/docs/classes/WoSmartLock.html +34 -16
  89. package/docs/classes/WoSmartLockPro.html +35 -17
  90. package/docs/classes/WoStrip.html +37 -19
  91. package/docs/enums/LogLevel.html +2 -2
  92. package/docs/enums/SwitchBotBLEModel.html +2 -2
  93. package/docs/enums/SwitchBotBLEModelName.html +2 -2
  94. package/docs/functions/updateBaseURL.html +1 -1
  95. package/docs/hierarchy.html +1 -1
  96. package/docs/index.html +1 -1
  97. package/docs/interfaces/APICommandRequest.html +2 -2
  98. package/docs/interfaces/APICommandResponse.html +2 -2
  99. package/docs/interfaces/APIDevice.html +2 -2
  100. package/docs/interfaces/APIDeviceStatus.html +2 -2
  101. package/docs/interfaces/APIErrorResponse.html +2 -2
  102. package/docs/interfaces/APIResponse.html +2 -2
  103. package/docs/interfaces/AirPurifierCommands.html +2 -2
  104. package/docs/interfaces/AirPurifierServiceData.html +5 -5
  105. package/docs/interfaces/AirPurifierStatus.html +7 -7
  106. package/docs/interfaces/BLEAdvertisement.html +2 -2
  107. package/docs/interfaces/BLEScanOptions.html +5 -5
  108. package/docs/interfaces/BLEServiceData.html +5 -5
  109. package/docs/interfaces/BlindTiltCommands.html +2 -2
  110. package/docs/interfaces/BlindTiltServiceData.html +5 -5
  111. package/docs/interfaces/BlindTiltStatus.html +6 -6
  112. package/docs/interfaces/BotCommands.html +2 -2
  113. package/docs/interfaces/BotServiceData.html +5 -5
  114. package/docs/interfaces/BotStatus.html +6 -6
  115. package/docs/interfaces/BulbCommands.html +2 -2
  116. package/docs/interfaces/BulbServiceData.html +5 -5
  117. package/docs/interfaces/BulbStatus.html +6 -6
  118. package/docs/interfaces/CeilingLightCommands.html +2 -2
  119. package/docs/interfaces/CeilingLightServiceData.html +5 -5
  120. package/docs/interfaces/CeilingLightStatus.html +6 -6
  121. package/docs/interfaces/CommandResult.html +6 -6
  122. package/docs/interfaces/ContactServiceData.html +5 -5
  123. package/docs/interfaces/ContactStatus.html +6 -6
  124. package/docs/interfaces/CurtainCommands.html +2 -2
  125. package/docs/interfaces/CurtainServiceData.html +5 -5
  126. package/docs/interfaces/CurtainStatus.html +6 -6
  127. package/docs/interfaces/DeviceInfo.html +13 -13
  128. package/docs/interfaces/DeviceListResponse.html +2 -2
  129. package/docs/interfaces/DeviceStatus.html +6 -6
  130. package/docs/interfaces/DiscoveryOptions.html +7 -7
  131. package/docs/interfaces/HubServiceData.html +5 -5
  132. package/docs/interfaces/HubStatus.html +6 -6
  133. package/docs/interfaces/HumidifierCommands.html +2 -2
  134. package/docs/interfaces/HumidifierServiceData.html +6 -6
  135. package/docs/interfaces/HumidifierStatus.html +6 -6
  136. package/docs/interfaces/KeypadStatus.html +6 -6
  137. package/docs/interfaces/LeakServiceData.html +5 -5
  138. package/docs/interfaces/LeakStatus.html +6 -6
  139. package/docs/interfaces/LockCommands.html +2 -2
  140. package/docs/interfaces/LockServiceData.html +6 -6
  141. package/docs/interfaces/LockStatus.html +6 -6
  142. package/docs/interfaces/MeterServiceData.html +5 -5
  143. package/docs/interfaces/MeterStatus.html +6 -6
  144. package/docs/interfaces/MotionServiceData.html +5 -5
  145. package/docs/interfaces/MotionStatus.html +6 -6
  146. package/docs/interfaces/PlugCommands.html +2 -2
  147. package/docs/interfaces/PlugServiceData.html +5 -5
  148. package/docs/interfaces/PlugStatus.html +6 -6
  149. package/docs/interfaces/PresenceServiceData.html +5 -5
  150. package/docs/interfaces/PresenceStatus.html +6 -6
  151. package/docs/interfaces/RelaySwitchCommands.html +2 -2
  152. package/docs/interfaces/RelaySwitchServiceData.html +5 -5
  153. package/docs/interfaces/RelaySwitchStatus.html +6 -6
  154. package/docs/interfaces/RemoteStatus.html +6 -6
  155. package/docs/interfaces/SceneListResponse.html +2 -2
  156. package/docs/interfaces/StripCommands.html +2 -2
  157. package/docs/interfaces/StripServiceData.html +5 -5
  158. package/docs/interfaces/StripStatus.html +6 -6
  159. package/docs/interfaces/SwitchBotConfig.html +21 -9
  160. package/docs/interfaces/WebhookConfig.html +2 -2
  161. package/docs/interfaces/WebhookDetails.html +2 -2
  162. package/docs/interfaces/WebhookQueryResponse.html +2 -2
  163. package/docs/interfaces/WebhookSetupResponse.html +2 -2
  164. package/docs/media/BLE.md +63 -0
  165. package/docs/types/ConnectionType.html +1 -1
  166. package/docs/types/PhysicalDeviceType.html +1 -1
  167. package/docs/types/VirtualDeviceType.html +1 -1
  168. package/docs/variables/urls.html +1 -1
  169. package/package.json +2 -2
  170. package/tsconfig.build.json +17 -0
package/BLE.md CHANGED
@@ -587,6 +587,69 @@ switchbot
587
587
  });
588
588
  ```
589
589
 
590
+ #### Password Protection
591
+
592
+ The Bot (WoHand) supports password protection via BLE to prevent unauthorized control. When a password is set, all BLE commands (press, turnOn, turnOff, up, down) will be automatically encrypted.
593
+
594
+ **Password Requirements:**
595
+ - Exactly 4 characters
596
+ - Alphanumeric only (letters and numbers)
597
+ - Case-sensitive
598
+
599
+ **Setting a Password:**
600
+
601
+ ```typescript
602
+ // Using device options during construction
603
+ const bot = new WoHand({
604
+ id: 'c1:2e:45:3e:20:08',
605
+ password: 'A1b2' // Your 4-character password
606
+ });
607
+
608
+ // Or set/change password on existing device
609
+ await bot.setPassword('A1b2');
610
+ ```
611
+
612
+ **Clearing a Password:**
613
+
614
+ ```typescript
615
+ // Remove password protection
616
+ await bot.clearPassword();
617
+ ```
618
+
619
+ **Checking Password Status:**
620
+
621
+ ```typescript
622
+ // Check if device has password configured
623
+ if (bot.hasPassword()) {
624
+ console.log('Device is password protected');
625
+ }
626
+ ```
627
+
628
+ **Example with Password:**
629
+
630
+ ```typescript
631
+ switchbot
632
+ .discover({ model: "H", quick: true })
633
+ .then((device_list) => {
634
+ const bot = device_list[0];
635
+ // Set password for protected Bot
636
+ bot.setPassword('MyP4');
637
+ return bot.press();
638
+ })
639
+ .then(() => {
640
+ console.log("Password-protected Bot pressed successfully");
641
+ })
642
+ .catch((error) => {
643
+ console.error(error);
644
+ });
645
+ ```
646
+
647
+ **Notes:**
648
+ - Password encryption uses CRC32 checksums for secure command transmission
649
+ - All control methods (press, turnOn, turnOff, up, down) automatically use encrypted commands when password is set
650
+ - Password is stored in memory only and must be set each time the application starts
651
+ - If the wrong password is configured, BLE commands will fail silently (Bot will not respond)
652
+
590
653
  ### `WoCurtain` Object
591
654
 
592
655
  The `WoCurtain` object represents a Curtain, which is created through the discovery process triggered by the [`switchBotBLE.discover()`](#discover-method) method.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. This project uses [Semantic Versioning](https://semver.org/)
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ### Features
8
+
9
+ * **Bot Password Protection**: Added BLE password support for Bot (WoHand) devices
10
+ * Password encryption using CRC32 checksums
11
+ * Password validation (4 alphanumeric characters, case-sensitive)
12
+ * Methods: `setPassword()`, `clearPassword()`, `hasPassword()`
13
+ * Automatic encrypted command execution when password is set
14
+ * Example: `examples/bot-password.js`
15
+ * Based on [homebridge-switchbot PR #1337](https://github.com/OpenWonderLabs/homebridge-switchbot/pull/1337)
16
+
17
+ * **Advanced Resilience Features**: Enterprise-grade reliability enhancements
18
+ * Retry logic with exponential backoff and jitter (RetryExecutor)
19
+ * Circuit breaker pattern to prevent cascading failures (CLOSED/OPEN/HALF_OPEN states)
20
+ * Connection intelligence tracking per-device success/failure rates
21
+ * Custom fallback handler system with built-in logging, metrics, and alerting handlers
22
+ * Intelligent connection selection based on historical performance
23
+
24
+ ### Documentation
25
+
26
+ * Added password protection section to [BLE.md](BLE.md)
27
+ * Updated [README.md](README.md) with password examples
28
+ * Created comprehensive password protection example
29
+ * Updated examples index with bot-password.js
30
+
31
+ ### Tests
32
+
33
+ * Added 14 comprehensive password protection tests
34
+ * All 54 tests passing (up from 40 in v4.0.0)
35
+ * Validation of CRC32 encryption, command building, and response parsing
36
+
37
+ ---
38
+
5
39
  ## [4.0.0](https://github.com/OpenWonderLabs/node-switchbot/compare/v3.6.6...v4.0.0) (2026-03-03)
6
40
 
7
41
  ### ⚠ BREAKING CHANGES
package/README.md CHANGED
@@ -66,6 +66,12 @@ await bot.turnOn()
66
66
  await bot.turnOff()
67
67
  await bot.press()
68
68
 
69
+ // Bot with Password Protection (BLE only)
70
+ const protectedBot = new WoHand({ id: 'YOUR_BOT_ID', password: 'A1b2' })
71
+ await protectedBot.setPassword('A1b2') // Set 4-char alphanumeric password
72
+ await protectedBot.press() // Commands are automatically encrypted
73
+ await protectedBot.clearPassword() // Remove password protection
74
+
69
75
  // Curtain - Position control
70
76
  await curtain.open()
71
77
  await curtain.close()
@@ -1,12 +1,13 @@
1
1
  import type { OpenAPIClient } from '../api.js';
2
2
  import type { BLEConnection } from '../ble.js';
3
3
  import type { CommandResult, ConnectionType, DeviceInfo, DeviceStatus } from '../types/index.js';
4
+ import type { CircuitBreakerConfig, FallbackHandler, FallbackHandlerOptions, RetryConfig } from '../utils/index.js';
4
5
  import { Buffer } from 'node:buffer';
5
6
  import { EventEmitter } from 'node:events';
6
- import { Logger } from '../utils/index.js';
7
+ import { CircuitBreaker, ConnectionTracker, FallbackHandlerManager, Logger, RetryExecutor } from '../utils/index.js';
7
8
  /**
8
9
  * Base class for all SwitchBot devices
9
- * Provides hybrid BLE/API functionality with automatic fallback
10
+ * Provides hybrid BLE/API functionality with automatic fallback, circuit breaker, and connection intelligence
10
11
  */
11
12
  export declare abstract class SwitchBotDevice extends EventEmitter {
12
13
  protected info: DeviceInfo;
@@ -15,11 +16,24 @@ export declare abstract class SwitchBotDevice extends EventEmitter {
15
16
  protected apiClient?: OpenAPIClient;
16
17
  protected enableFallback: boolean;
17
18
  protected preferredConnection: ConnectionType;
19
+ protected connectionTracker: ConnectionTracker;
20
+ protected circuitBreakerBLE: CircuitBreaker;
21
+ protected circuitBreakerAPI: CircuitBreaker;
22
+ protected fallbackHandlerManager: FallbackHandlerManager;
23
+ protected retryExecutor: RetryExecutor;
24
+ protected enableConnectionIntelligence: boolean;
25
+ protected enableCircuitBreaker: boolean;
26
+ protected enableRetry: boolean;
18
27
  constructor(info: DeviceInfo, options?: {
19
28
  bleConnection?: BLEConnection;
20
29
  apiClient?: OpenAPIClient;
21
30
  enableFallback?: boolean;
22
31
  preferredConnection?: ConnectionType;
32
+ enableConnectionIntelligence?: boolean;
33
+ enableCircuitBreaker?: boolean;
34
+ enableRetry?: boolean;
35
+ retryConfig?: RetryConfig;
36
+ circuitBreakerConfig?: CircuitBreakerConfig;
23
37
  logLevel?: number;
24
38
  });
25
39
  /**
@@ -59,15 +73,19 @@ export declare abstract class SwitchBotDevice extends EventEmitter {
59
73
  */
60
74
  abstract getStatus(): Promise<DeviceStatus>;
61
75
  /**
62
- * Send a command via BLE
76
+ * Send a command via BLE with circuit breaker and retry logic
63
77
  */
64
78
  protected sendBLECommand(command: readonly number[] | number[] | Buffer): Promise<CommandResult>;
65
79
  /**
66
- * Send a command via OpenAPI
80
+ * Send a command via OpenAPI with circuit breaker and retry logic
67
81
  */
68
82
  protected sendAPICommand(command: string, parameter?: any): Promise<CommandResult>;
69
83
  /**
70
- * Send a command with automatic BLE/API fallback
84
+ * Get best connection type based on intelligence tracking
85
+ */
86
+ private getBestConnection;
87
+ /**
88
+ * Send a command with automatic BLE/API fallback, circuit breaker, and retry logic
71
89
  */
72
90
  protected sendCommand(bleCommand: readonly number[] | number[] | Buffer, apiCommand: string, apiParameter?: any): Promise<CommandResult>;
73
91
  /**
@@ -90,6 +108,42 @@ export declare abstract class SwitchBotDevice extends EventEmitter {
90
108
  * Enable or disable fallback
91
109
  */
92
110
  setFallbackEnabled(enabled: boolean): void;
111
+ /**
112
+ * Enable or disable connection intelligence
113
+ */
114
+ setConnectionIntelligenceEnabled(enabled: boolean): void;
115
+ /**
116
+ * Enable or disable circuit breaker
117
+ */
118
+ setCircuitBreakerEnabled(enabled: boolean): void;
119
+ /**
120
+ * Enable or disable retry logic
121
+ */
122
+ setRetryEnabled(enabled: boolean): void;
123
+ /**
124
+ * Get connection tracker for this device
125
+ */
126
+ getConnectionTracker(): ConnectionTracker;
127
+ /**
128
+ * Get circuit breaker for BLE
129
+ */
130
+ getCircuitBreakerBLE(): CircuitBreaker;
131
+ /**
132
+ * Get circuit breaker for API
133
+ */
134
+ getCircuitBreakerAPI(): CircuitBreaker;
135
+ /**
136
+ * Register a custom fallback handler
137
+ */
138
+ registerFallbackHandler(handler: FallbackHandler, options?: FallbackHandlerOptions): string;
139
+ /**
140
+ * Unregister a fallback handler
141
+ */
142
+ unregisterFallbackHandler(id: string): boolean;
143
+ /**
144
+ * Get fallback handler manager
145
+ */
146
+ getFallbackHandlerManager(): FallbackHandlerManager;
93
147
  }
94
148
  /**
95
149
  * Device Manager for managing multiple devices
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/devices/base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/devices/base.ts"],"names":[],"mappings":"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"}
@@ -5,10 +5,10 @@
5
5
  import { Buffer } from 'node:buffer';
6
6
  import { EventEmitter } from 'node:events';
7
7
  import { APINotAvailableError, BLENotAvailableError } from '../errors.js';
8
- import { Logger } from '../utils/index.js';
8
+ import { CircuitBreaker, CircuitBreakerState, ConnectionTracker, FallbackHandlerManager, Logger, RetryExecutor, } from '../utils/index.js';
9
9
  /**
10
10
  * Base class for all SwitchBot devices
11
- * Provides hybrid BLE/API functionality with automatic fallback
11
+ * Provides hybrid BLE/API functionality with automatic fallback, circuit breaker, and connection intelligence
12
12
  */
13
13
  export class SwitchBotDevice extends EventEmitter {
14
14
  info;
@@ -17,6 +17,15 @@ export class SwitchBotDevice extends EventEmitter {
17
17
  apiClient;
18
18
  enableFallback;
19
19
  preferredConnection;
20
+ // Advanced features
21
+ connectionTracker;
22
+ circuitBreakerBLE;
23
+ circuitBreakerAPI;
24
+ fallbackHandlerManager;
25
+ retryExecutor;
26
+ enableConnectionIntelligence;
27
+ enableCircuitBreaker;
28
+ enableRetry;
20
29
  constructor(info, options = {}) {
21
30
  super();
22
31
  this.info = info;
@@ -25,6 +34,19 @@ export class SwitchBotDevice extends EventEmitter {
25
34
  this.apiClient = options.apiClient;
26
35
  this.enableFallback = options.enableFallback ?? true;
27
36
  this.preferredConnection = options.preferredConnection ?? 'ble';
37
+ // Initialize advanced features
38
+ this.enableConnectionIntelligence = options.enableConnectionIntelligence ?? true;
39
+ this.enableCircuitBreaker = options.enableCircuitBreaker ?? true;
40
+ this.enableRetry = options.enableRetry ?? true;
41
+ // Create connection tracker for this device
42
+ this.connectionTracker = new ConnectionTracker(info.id, options.logLevel);
43
+ // Create circuit breakers for each connection type
44
+ this.circuitBreakerBLE = new CircuitBreaker(`${info.deviceType}:${info.id}:BLE`, options.circuitBreakerConfig, options.logLevel);
45
+ this.circuitBreakerAPI = new CircuitBreaker(`${info.deviceType}:${info.id}:API`, options.circuitBreakerConfig, options.logLevel);
46
+ // Create retry executor
47
+ this.retryExecutor = new RetryExecutor(options.retryConfig, options.logLevel);
48
+ // Create fallback handler manager
49
+ this.fallbackHandlerManager = new FallbackHandlerManager(options.logLevel);
28
50
  }
29
51
  /**
30
52
  * Get device information
@@ -75,7 +97,7 @@ export class SwitchBotDevice extends EventEmitter {
75
97
  return this.info.connectionTypes.includes('api') && !!this.apiClient && this.info.cloudServiceEnabled !== false;
76
98
  }
77
99
  /**
78
- * Send a command via BLE
100
+ * Send a command via BLE with circuit breaker and retry logic
79
101
  */
80
102
  async sendBLECommand(command) {
81
103
  if (!this.hasBLE()) {
@@ -85,18 +107,57 @@ export class SwitchBotDevice extends EventEmitter {
85
107
  error: 'BLE not available for this device',
86
108
  };
87
109
  }
88
- try {
110
+ // Check circuit breaker
111
+ if (this.enableCircuitBreaker && !this.circuitBreakerBLE.canExecute()) {
112
+ const state = this.circuitBreakerBLE.getState();
113
+ if (state === CircuitBreakerState.OPEN) {
114
+ return {
115
+ success: false,
116
+ connectionType: 'ble',
117
+ error: 'BLE circuit breaker is OPEN (too many failures)',
118
+ };
119
+ }
120
+ }
121
+ const executeCommand = async () => {
89
122
  this.logger.debug('Sending BLE command', command);
90
123
  const buffer = Buffer.isBuffer(command) ? command : Buffer.from(command);
124
+ const startTime = Date.now();
91
125
  await this.bleConnection.write(this.info.mac, buffer);
126
+ const latencyMs = Date.now() - startTime;
127
+ // Record success
128
+ if (this.enableConnectionIntelligence) {
129
+ this.connectionTracker.recordSuccess('ble', latencyMs);
130
+ }
131
+ if (this.enableCircuitBreaker) {
132
+ this.circuitBreakerBLE.recordSuccess();
133
+ }
92
134
  this.info.activeConnection = 'ble';
93
135
  this.emit('command', { type: 'ble', success: true });
94
136
  return {
95
137
  success: true,
96
138
  connectionType: 'ble',
97
139
  };
140
+ };
141
+ try {
142
+ if (this.enableRetry) {
143
+ this.circuitBreakerBLE.markHalfOpenAttempt();
144
+ return await this.retryExecutor.executeOrThrow(executeCommand, `BLE command for ${this.info.id}`);
145
+ }
146
+ else {
147
+ if (this.enableCircuitBreaker) {
148
+ this.circuitBreakerBLE.markHalfOpenAttempt();
149
+ }
150
+ return await executeCommand();
151
+ }
98
152
  }
99
153
  catch (error) {
154
+ // Record failure
155
+ if (this.enableConnectionIntelligence) {
156
+ this.connectionTracker.recordFailure('ble');
157
+ }
158
+ if (this.enableCircuitBreaker) {
159
+ this.circuitBreakerBLE.recordFailure();
160
+ }
100
161
  this.logger.error('BLE command failed', error);
101
162
  this.emit('error', { type: 'ble', error });
102
163
  return {
@@ -107,7 +168,7 @@ export class SwitchBotDevice extends EventEmitter {
107
168
  }
108
169
  }
109
170
  /**
110
- * Send a command via OpenAPI
171
+ * Send a command via OpenAPI with circuit breaker and retry logic
111
172
  */
112
173
  async sendAPICommand(command, parameter) {
113
174
  if (!this.hasAPI()) {
@@ -117,9 +178,29 @@ export class SwitchBotDevice extends EventEmitter {
117
178
  error: 'API not available for this device',
118
179
  };
119
180
  }
120
- try {
181
+ // Check circuit breaker
182
+ if (this.enableCircuitBreaker && !this.circuitBreakerAPI.canExecute()) {
183
+ const state = this.circuitBreakerAPI.getState();
184
+ if (state === CircuitBreakerState.OPEN) {
185
+ return {
186
+ success: false,
187
+ connectionType: 'api',
188
+ error: 'API circuit breaker is OPEN (too many failures)',
189
+ };
190
+ }
191
+ }
192
+ const executeCommand = async () => {
121
193
  this.logger.debug('Sending API command', { command, parameter });
194
+ const startTime = Date.now();
122
195
  const response = await this.apiClient.sendCommand(this.info.id, command, parameter);
196
+ const latencyMs = Date.now() - startTime;
197
+ // Record success
198
+ if (this.enableConnectionIntelligence) {
199
+ this.connectionTracker.recordSuccess('api', latencyMs);
200
+ }
201
+ if (this.enableCircuitBreaker) {
202
+ this.circuitBreakerAPI.recordSuccess();
203
+ }
123
204
  this.info.activeConnection = 'api';
124
205
  this.emit('command', { type: 'api', success: true });
125
206
  return {
@@ -127,8 +208,27 @@ export class SwitchBotDevice extends EventEmitter {
127
208
  connectionType: 'api',
128
209
  data: response,
129
210
  };
211
+ };
212
+ try {
213
+ if (this.enableRetry) {
214
+ this.circuitBreakerAPI.markHalfOpenAttempt();
215
+ return await this.retryExecutor.executeOrThrow(executeCommand, `API command for ${this.info.id}`);
216
+ }
217
+ else {
218
+ if (this.enableCircuitBreaker) {
219
+ this.circuitBreakerAPI.markHalfOpenAttempt();
220
+ }
221
+ return await executeCommand();
222
+ }
130
223
  }
131
224
  catch (error) {
225
+ // Record failure
226
+ if (this.enableConnectionIntelligence) {
227
+ this.connectionTracker.recordFailure('api');
228
+ }
229
+ if (this.enableCircuitBreaker) {
230
+ this.circuitBreakerAPI.recordFailure();
231
+ }
132
232
  this.logger.error('API command failed', error);
133
233
  this.emit('error', { type: 'api', error });
134
234
  return {
@@ -139,37 +239,104 @@ export class SwitchBotDevice extends EventEmitter {
139
239
  }
140
240
  }
141
241
  /**
142
- * Send a command with automatic BLE/API fallback
242
+ * Get best connection type based on intelligence tracking
243
+ */
244
+ getBestConnection() {
245
+ if (!this.enableConnectionIntelligence) {
246
+ return this.preferredConnection;
247
+ }
248
+ const availableTypes = [];
249
+ if (this.hasBLE()) {
250
+ availableTypes.push('ble');
251
+ }
252
+ if (this.hasAPI()) {
253
+ availableTypes.push('api');
254
+ }
255
+ if (availableTypes.length === 0) {
256
+ return this.preferredConnection;
257
+ }
258
+ // Get best connection based on statistics
259
+ const best = this.connectionTracker.getBestConnection(availableTypes);
260
+ if (best) {
261
+ return best;
262
+ }
263
+ return availableTypes.length > 0 ? availableTypes[0] : this.preferredConnection;
264
+ }
265
+ /**
266
+ * Send a command with automatic BLE/API fallback, circuit breaker, and retry logic
143
267
  */
144
268
  async sendCommand(bleCommand, apiCommand, apiParameter) {
145
- // Try preferred connection first
146
- if (this.preferredConnection === 'ble') {
147
- const bleResult = await this.sendBLECommand(bleCommand);
148
- if (bleResult.success) {
149
- return bleResult;
269
+ // Determine connection strategy
270
+ let primaryConnection = this.preferredConnection;
271
+ let secondaryConnection;
272
+ if (this.enableConnectionIntelligence) {
273
+ primaryConnection = this.getBestConnection();
274
+ if (this.preferredConnection === 'ble' && primaryConnection === 'api' && this.hasBLE()) {
275
+ secondaryConnection = 'ble';
150
276
  }
151
- // Fallback to API if enabled
152
- if (this.enableFallback && this.hasAPI()) {
153
- this.logger.warn('BLE failed, falling back to API');
154
- const apiResult = await this.sendAPICommand(apiCommand, apiParameter);
155
- return { ...apiResult, usedFallback: true };
277
+ else if (this.preferredConnection === 'api' && primaryConnection === 'ble' && this.hasAPI()) {
278
+ secondaryConnection = 'api';
156
279
  }
157
- return bleResult;
158
280
  }
159
281
  else {
160
- // Prefer API
161
- const apiResult = await this.sendAPICommand(apiCommand, apiParameter);
162
- if (apiResult.success) {
163
- return apiResult;
282
+ // Fallback based on preferred connection
283
+ if (this.preferredConnection === 'ble' && this.hasBLE()) {
284
+ primaryConnection = 'ble';
285
+ if (this.enableFallback && this.hasAPI()) {
286
+ secondaryConnection = 'api';
287
+ }
288
+ }
289
+ else if (this.preferredConnection === 'api' && this.hasAPI()) {
290
+ primaryConnection = 'api';
291
+ if (this.enableFallback && this.hasBLE()) {
292
+ secondaryConnection = 'ble';
293
+ }
294
+ }
295
+ else if (this.hasBLE()) {
296
+ primaryConnection = 'ble';
297
+ if (this.enableFallback && this.hasAPI()) {
298
+ secondaryConnection = 'api';
299
+ }
300
+ }
301
+ else {
302
+ primaryConnection = 'api';
303
+ secondaryConnection = undefined;
304
+ }
305
+ }
306
+ // Try primary connection
307
+ let result;
308
+ let fallbackUsed = false;
309
+ if (primaryConnection === 'ble') {
310
+ result = await this.sendBLECommand(bleCommand);
311
+ }
312
+ else {
313
+ result = await this.sendAPICommand(apiCommand, apiParameter);
314
+ }
315
+ // Try fallback if primary failed and fallback is enabled
316
+ if (!result.success && this.enableFallback && secondaryConnection) {
317
+ fallbackUsed = true;
318
+ this.logger.warn(`${primaryConnection} failed, attempting fallback to ${secondaryConnection}`);
319
+ // Emit fallback event
320
+ const fallbackEvent = {
321
+ deviceId: this.info.id,
322
+ primaryConnection,
323
+ fallbackConnection: secondaryConnection,
324
+ reason: result.error || 'Connection failed',
325
+ timestamp: new Date(),
326
+ totalTimeMs: 0,
327
+ };
328
+ await this.fallbackHandlerManager.emit(fallbackEvent);
329
+ if (secondaryConnection === 'ble') {
330
+ result = await this.sendBLECommand(bleCommand);
164
331
  }
165
- // Fallback to BLE if enabled
166
- if (this.enableFallback && this.hasBLE()) {
167
- this.logger.warn('API failed, falling back to BLE');
168
- const bleResult = await this.sendBLECommand(bleCommand);
169
- return { ...bleResult, usedFallback: true };
332
+ else {
333
+ result = await this.sendAPICommand(apiCommand, apiParameter);
170
334
  }
171
- return apiResult;
172
335
  }
336
+ if (fallbackUsed) {
337
+ result.usedFallback = true;
338
+ }
339
+ return result;
173
340
  }
174
341
  /**
175
342
  * Get device status via BLE
@@ -180,11 +347,25 @@ export class SwitchBotDevice extends EventEmitter {
180
347
  }
181
348
  try {
182
349
  this.logger.debug('Reading BLE status');
350
+ const startTime = Date.now();
183
351
  const data = await this.bleConnection.read(this.info.mac);
352
+ const latencyMs = Date.now() - startTime;
353
+ if (this.enableConnectionIntelligence) {
354
+ this.connectionTracker.recordSuccess('ble', latencyMs);
355
+ }
356
+ if (this.enableCircuitBreaker) {
357
+ this.circuitBreakerBLE.recordSuccess();
358
+ }
184
359
  this.info.activeConnection = 'ble';
185
360
  return data;
186
361
  }
187
362
  catch (error) {
363
+ if (this.enableConnectionIntelligence) {
364
+ this.connectionTracker.recordFailure('ble');
365
+ }
366
+ if (this.enableCircuitBreaker) {
367
+ this.circuitBreakerBLE.recordFailure();
368
+ }
188
369
  this.logger.error('Failed to read BLE status', error);
189
370
  throw error;
190
371
  }
@@ -198,11 +379,25 @@ export class SwitchBotDevice extends EventEmitter {
198
379
  }
199
380
  try {
200
381
  this.logger.debug('Reading API status');
382
+ const startTime = Date.now();
201
383
  const data = await this.apiClient.getStatus(this.info.id);
384
+ const latencyMs = Date.now() - startTime;
385
+ if (this.enableConnectionIntelligence) {
386
+ this.connectionTracker.recordSuccess('api', latencyMs);
387
+ }
388
+ if (this.enableCircuitBreaker) {
389
+ this.circuitBreakerAPI.recordSuccess();
390
+ }
202
391
  this.info.activeConnection = 'api';
203
392
  return data;
204
393
  }
205
394
  catch (error) {
395
+ if (this.enableConnectionIntelligence) {
396
+ this.connectionTracker.recordFailure('api');
397
+ }
398
+ if (this.enableCircuitBreaker) {
399
+ this.circuitBreakerAPI.recordFailure();
400
+ }
206
401
  this.logger.error('Failed to read API status', error);
207
402
  throw error;
208
403
  }
@@ -228,6 +423,63 @@ export class SwitchBotDevice extends EventEmitter {
228
423
  this.enableFallback = enabled;
229
424
  this.logger.info(`Fallback ${enabled ? 'enabled' : 'disabled'}`);
230
425
  }
426
+ /**
427
+ * Enable or disable connection intelligence
428
+ */
429
+ setConnectionIntelligenceEnabled(enabled) {
430
+ this.enableConnectionIntelligence = enabled;
431
+ this.logger.info(`Connection intelligence ${enabled ? 'enabled' : 'disabled'}`);
432
+ }
433
+ /**
434
+ * Enable or disable circuit breaker
435
+ */
436
+ setCircuitBreakerEnabled(enabled) {
437
+ this.enableCircuitBreaker = enabled;
438
+ this.logger.info(`Circuit breaker ${enabled ? 'enabled' : 'disabled'}`);
439
+ }
440
+ /**
441
+ * Enable or disable retry logic
442
+ */
443
+ setRetryEnabled(enabled) {
444
+ this.enableRetry = enabled;
445
+ this.logger.info(`Retry logic ${enabled ? 'enabled' : 'disabled'}`);
446
+ }
447
+ /**
448
+ * Get connection tracker for this device
449
+ */
450
+ getConnectionTracker() {
451
+ return this.connectionTracker;
452
+ }
453
+ /**
454
+ * Get circuit breaker for BLE
455
+ */
456
+ getCircuitBreakerBLE() {
457
+ return this.circuitBreakerBLE;
458
+ }
459
+ /**
460
+ * Get circuit breaker for API
461
+ */
462
+ getCircuitBreakerAPI() {
463
+ return this.circuitBreakerAPI;
464
+ }
465
+ /**
466
+ * Register a custom fallback handler
467
+ */
468
+ registerFallbackHandler(handler, options) {
469
+ return this.fallbackHandlerManager.register(handler, options);
470
+ }
471
+ /**
472
+ * Unregister a fallback handler
473
+ */
474
+ unregisterFallbackHandler(id) {
475
+ return this.fallbackHandlerManager.unregister(id);
476
+ }
477
+ /**
478
+ * Get fallback handler manager
479
+ */
480
+ getFallbackHandlerManager() {
481
+ return this.fallbackHandlerManager;
482
+ }
231
483
  }
232
484
  /**
233
485
  * Device Manager for managing multiple devices