@switchbot/homebridge-switchbot 3.5.0-beta.7 → 3.5.0-beta.70

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 (177) hide show
  1. package/.vscode/settings.json +4 -1
  2. package/CHANGELOG.md +4 -1
  3. package/config.schema.json +120 -17
  4. package/dist/device/blindtilt.d.ts +23 -96
  5. package/dist/device/blindtilt.d.ts.map +1 -1
  6. package/dist/device/blindtilt.js +425 -778
  7. package/dist/device/blindtilt.js.map +1 -1
  8. package/dist/device/bot.d.ts +26 -72
  9. package/dist/device/bot.d.ts.map +1 -1
  10. package/dist/device/bot.js +803 -837
  11. package/dist/device/bot.js.map +1 -1
  12. package/dist/device/ceilinglight.d.ts +14 -68
  13. package/dist/device/ceilinglight.d.ts.map +1 -1
  14. package/dist/device/ceilinglight.js +241 -606
  15. package/dist/device/ceilinglight.js.map +1 -1
  16. package/dist/device/colorbulb.d.ts +14 -75
  17. package/dist/device/colorbulb.d.ts.map +1 -1
  18. package/dist/device/colorbulb.js +283 -658
  19. package/dist/device/colorbulb.js.map +1 -1
  20. package/dist/device/contact.d.ts +15 -66
  21. package/dist/device/contact.d.ts.map +1 -1
  22. package/dist/device/contact.js +254 -545
  23. package/dist/device/contact.js.map +1 -1
  24. package/dist/device/curtain.d.ts +19 -88
  25. package/dist/device/curtain.d.ts.map +1 -1
  26. package/dist/device/curtain.js +412 -806
  27. package/dist/device/curtain.js.map +1 -1
  28. package/dist/device/device.d.ts +59 -0
  29. package/dist/device/device.d.ts.map +1 -0
  30. package/dist/device/device.js +711 -0
  31. package/dist/device/device.js.map +1 -0
  32. package/dist/device/fan.d.ts +51 -0
  33. package/dist/device/fan.d.ts.map +1 -0
  34. package/dist/device/fan.js +540 -0
  35. package/dist/device/fan.js.map +1 -0
  36. package/dist/device/hub.d.ts +16 -60
  37. package/dist/device/hub.d.ts.map +1 -1
  38. package/dist/device/hub.js +261 -616
  39. package/dist/device/hub.js.map +1 -1
  40. package/dist/device/humidifier.d.ts +11 -66
  41. package/dist/device/humidifier.d.ts.map +1 -1
  42. package/dist/device/humidifier.js +240 -594
  43. package/dist/device/humidifier.js.map +1 -1
  44. package/dist/device/iosensor.d.ts +11 -64
  45. package/dist/device/iosensor.d.ts.map +1 -1
  46. package/dist/device/iosensor.js +196 -600
  47. package/dist/device/iosensor.js.map +1 -1
  48. package/dist/device/lightstrip.d.ts +11 -69
  49. package/dist/device/lightstrip.d.ts.map +1 -1
  50. package/dist/device/lightstrip.js +281 -586
  51. package/dist/device/lightstrip.js.map +1 -1
  52. package/dist/device/lock.d.ts +16 -63
  53. package/dist/device/lock.d.ts.map +1 -1
  54. package/dist/device/lock.js +252 -560
  55. package/dist/device/lock.js.map +1 -1
  56. package/dist/device/meter.d.ts +14 -64
  57. package/dist/device/meter.d.ts.map +1 -1
  58. package/dist/device/meter.js +191 -593
  59. package/dist/device/meter.js.map +1 -1
  60. package/dist/device/meterplus.d.ts +14 -65
  61. package/dist/device/meterplus.d.ts.map +1 -1
  62. package/dist/device/meterplus.js +180 -593
  63. package/dist/device/meterplus.js.map +1 -1
  64. package/dist/device/motion.d.ts +13 -69
  65. package/dist/device/motion.d.ts.map +1 -1
  66. package/dist/device/motion.js +188 -532
  67. package/dist/device/motion.js.map +1 -1
  68. package/dist/device/plug.d.ts +9 -57
  69. package/dist/device/plug.d.ts.map +1 -1
  70. package/dist/device/plug.js +96 -458
  71. package/dist/device/plug.js.map +1 -1
  72. package/dist/device/robotvacuumcleaner.d.ts +12 -63
  73. package/dist/device/robotvacuumcleaner.d.ts.map +1 -1
  74. package/dist/device/robotvacuumcleaner.js +202 -521
  75. package/dist/device/robotvacuumcleaner.js.map +1 -1
  76. package/dist/device/waterdetector.d.ts +10 -63
  77. package/dist/device/waterdetector.d.ts.map +1 -1
  78. package/dist/device/waterdetector.js +146 -475
  79. package/dist/device/waterdetector.js.map +1 -1
  80. package/dist/homebridge-ui/public/index.html +22 -16
  81. package/dist/homebridge-ui/server.js +1 -1
  82. package/dist/homebridge-ui/server.js.map +1 -1
  83. package/dist/index.d.ts +1 -1
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +2 -2
  86. package/dist/index.js.map +1 -1
  87. package/dist/irdevice/airconditioner.d.ts +10 -50
  88. package/dist/irdevice/airconditioner.d.ts.map +1 -1
  89. package/dist/irdevice/airconditioner.js +324 -452
  90. package/dist/irdevice/airconditioner.js.map +1 -1
  91. package/dist/irdevice/airpurifier.d.ts +8 -39
  92. package/dist/irdevice/airpurifier.d.ts.map +1 -1
  93. package/dist/irdevice/airpurifier.js +93 -307
  94. package/dist/irdevice/airpurifier.js.map +1 -1
  95. package/dist/irdevice/camera.d.ts +7 -36
  96. package/dist/irdevice/camera.d.ts.map +1 -1
  97. package/dist/irdevice/camera.js +39 -281
  98. package/dist/irdevice/camera.js.map +1 -1
  99. package/dist/irdevice/fan.d.ts +10 -43
  100. package/dist/irdevice/fan.d.ts.map +1 -1
  101. package/dist/irdevice/fan.js +113 -341
  102. package/dist/irdevice/fan.js.map +1 -1
  103. package/dist/irdevice/irdevice.d.ts +40 -0
  104. package/dist/irdevice/irdevice.d.ts.map +1 -0
  105. package/dist/irdevice/irdevice.js +330 -0
  106. package/dist/irdevice/irdevice.js.map +1 -0
  107. package/dist/irdevice/light.d.ts +12 -45
  108. package/dist/irdevice/light.d.ts.map +1 -1
  109. package/dist/irdevice/light.js +118 -362
  110. package/dist/irdevice/light.js.map +1 -1
  111. package/dist/irdevice/other.d.ts +19 -48
  112. package/dist/irdevice/other.d.ts.map +1 -1
  113. package/dist/irdevice/other.js +438 -570
  114. package/dist/irdevice/other.js.map +1 -1
  115. package/dist/irdevice/tv.d.ts +8 -40
  116. package/dist/irdevice/tv.d.ts.map +1 -1
  117. package/dist/irdevice/tv.js +87 -335
  118. package/dist/irdevice/tv.js.map +1 -1
  119. package/dist/irdevice/vacuumcleaner.d.ts +7 -36
  120. package/dist/irdevice/vacuumcleaner.d.ts.map +1 -1
  121. package/dist/irdevice/vacuumcleaner.js +45 -285
  122. package/dist/irdevice/vacuumcleaner.js.map +1 -1
  123. package/dist/irdevice/waterheater.d.ts +7 -36
  124. package/dist/irdevice/waterheater.d.ts.map +1 -1
  125. package/dist/irdevice/waterheater.js +47 -291
  126. package/dist/irdevice/waterheater.js.map +1 -1
  127. package/dist/platform.d.ts +19 -6
  128. package/dist/platform.d.ts.map +1 -1
  129. package/dist/platform.js +310 -172
  130. package/dist/platform.js.map +1 -1
  131. package/dist/settings.d.ts +62 -19
  132. package/dist/settings.d.ts.map +1 -1
  133. package/dist/settings.js +16 -552
  134. package/dist/settings.js.map +1 -1
  135. package/dist/utils.d.ts +77 -0
  136. package/dist/utils.d.ts.map +1 -1
  137. package/dist/utils.js +638 -1
  138. package/dist/utils.js.map +1 -1
  139. package/eslint.config.js +96 -0
  140. package/package.json +19 -15
  141. package/src/custom.d.ts +2 -1
  142. package/src/device/blindtilt.ts +462 -817
  143. package/src/device/bot.ts +862 -931
  144. package/src/device/ceilinglight.ts +259 -633
  145. package/src/device/colorbulb.ts +300 -688
  146. package/src/device/contact.ts +288 -571
  147. package/src/device/curtain.ts +441 -838
  148. package/src/device/device.ts +734 -0
  149. package/src/device/fan.ts +596 -0
  150. package/src/device/hub.ts +299 -645
  151. package/src/device/humidifier.ts +274 -647
  152. package/src/device/iosensor.ts +233 -626
  153. package/src/device/lightstrip.ts +309 -620
  154. package/src/device/lock.ts +292 -601
  155. package/src/device/meter.ts +228 -619
  156. package/src/device/meterplus.ts +216 -619
  157. package/src/device/motion.ts +218 -559
  158. package/src/device/plug.ts +111 -490
  159. package/src/device/robotvacuumcleaner.ts +228 -550
  160. package/src/device/waterdetector.ts +169 -501
  161. package/src/homebridge-ui/public/index.html +22 -16
  162. package/src/homebridge-ui/server.ts +1 -1
  163. package/src/index.ts +4 -3
  164. package/src/irdevice/airconditioner.ts +350 -477
  165. package/src/irdevice/airpurifier.ts +120 -330
  166. package/src/irdevice/camera.ts +53 -301
  167. package/src/irdevice/fan.ts +130 -360
  168. package/src/irdevice/irdevice.ts +346 -0
  169. package/src/irdevice/light.ts +157 -412
  170. package/src/irdevice/other.ts +507 -648
  171. package/src/irdevice/tv.ts +117 -367
  172. package/src/irdevice/vacuumcleaner.ts +59 -311
  173. package/src/irdevice/waterheater.ts +61 -320
  174. package/src/platform.ts +355 -219
  175. package/src/settings.ts +116 -611
  176. package/src/utils.ts +642 -1
  177. package/src/device/waterdetector.json +0 -11
@@ -0,0 +1,596 @@
1
+ /* Copyright(C) 2021-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved.
2
+ *
3
+ * plug.ts: @switchbot/homebridge-switchbot.
4
+ */
5
+ import { request } from 'undici';
6
+ import { deviceBase } from './device.js';
7
+ import { Devices } from '../settings.js';
8
+ import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs';
9
+
10
+ import type { SwitchBotPlatform } from '../platform.js';
11
+ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge';
12
+ import type { device, devicesConfig, serviceData, deviceStatus} from '../settings.js';
13
+
14
+ export class Fan extends deviceBase {
15
+ // Services
16
+ private Fan: {
17
+ Name: CharacteristicValue;
18
+ Service: Service;
19
+ Active: CharacteristicValue;
20
+ SwingMode: CharacteristicValue;
21
+ RotationSpeed: CharacteristicValue;
22
+ };
23
+
24
+ private Battery: {
25
+ Name: CharacteristicValue;
26
+ Service: Service;
27
+ BatteryLevel: CharacteristicValue;
28
+ StatusLowBattery: CharacteristicValue;
29
+ ChargingState: CharacteristicValue;
30
+ };
31
+
32
+ // Updates
33
+ plugUpdateInProgress!: boolean;
34
+ doPlugUpdate!: Subject<void>;
35
+
36
+ constructor(
37
+ readonly platform: SwitchBotPlatform,
38
+ accessory: PlatformAccessory,
39
+ device: device & devicesConfig,
40
+ ) {
41
+ super(platform, accessory, device);
42
+ // this is subject we use to track when we need to POST changes to the SwitchBot API
43
+ this.doPlugUpdate = new Subject();
44
+ this.plugUpdateInProgress = false;
45
+
46
+ // Initialize Fan Service
47
+ accessory.context.Fan = accessory.context.Fan ?? {};
48
+ this.Fan = {
49
+ Name: accessory.context.Fan.Name ?? accessory.displayName,
50
+ Service: accessory.getService(this.hap.Service.Fanv2) ?? accessory.addService(this.hap.Service.Fanv2) as Service,
51
+ Active: accessory.context.Active ?? this.hap.Characteristic.Active.INACTIVE,
52
+ SwingMode: accessory.context.SwingMode ?? this.hap.Characteristic.SwingMode.SWING_DISABLED,
53
+ RotationSpeed: accessory.context.RotationSpeed ?? 0,
54
+ };
55
+ accessory.context.Fan = this.Fan as object;
56
+
57
+ // Initialize Fan Service
58
+ this.Fan.Service
59
+ .setCharacteristic(this.hap.Characteristic.Name, this.Fan.Name)
60
+ .getCharacteristic(this.hap.Characteristic.Active)
61
+ .onGet(() => {
62
+ return this.Fan.Active;
63
+ })
64
+ .onSet(this.ActiveSet.bind(this));
65
+
66
+ // Initialize Fan RotationSpeed Characteristic
67
+ this.Fan.Service
68
+ .getCharacteristic(this.hap.Characteristic.RotationSpeed)
69
+ .onGet(() => {
70
+ return this.Fan.RotationSpeed;
71
+ })
72
+ .onSet(this.RotationSpeedSet.bind(this));
73
+
74
+ // Initialize Fan SwingMode Characteristic
75
+ this.Fan.Service.getCharacteristic(this.hap.Characteristic.SwingMode)
76
+ .onGet(() => {
77
+ return this.Fan.SwingMode;
78
+ })
79
+ .onSet(this.SwingModeSet.bind(this));
80
+
81
+ // Initialize Battery Service
82
+ accessory.context.Battery = accessory.context.Battery ?? {};
83
+ this.Battery = {
84
+ Name: accessory.context.Battery.Name ?? accessory.displayName,
85
+ Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service,
86
+ BatteryLevel: accessory.context.BatteryLevel ?? 100,
87
+ StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL,
88
+ ChargingState: accessory.context.ChargingState ?? this.hap.Characteristic.ChargingState.NOT_CHARGING,
89
+ };
90
+ accessory.context.Battery = this.Battery as object;
91
+
92
+ // Initialize Battery Service
93
+ this.Battery.Service
94
+ .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name)
95
+ .getCharacteristic(this.hap.Characteristic.BatteryLevel)
96
+ .onGet(() => {
97
+ return this.Battery.BatteryLevel;
98
+ });
99
+
100
+ // Initialize Battery ChargingState Characteristic
101
+ this.Battery.Service
102
+ .getCharacteristic(this.hap.Characteristic.ChargingState)
103
+ .onGet(() => {
104
+ return this.Battery.ChargingState;
105
+ });
106
+
107
+ // Initialize Battery StatusLowBattery Characteristic
108
+ this.Battery.Service
109
+ .getCharacteristic(this.hap.Characteristic.StatusLowBattery)
110
+ .onGet(() => {
111
+ return this.Battery.StatusLowBattery;
112
+ });
113
+
114
+ // Retrieve initial values and updateHomekit
115
+ this.refreshStatus();
116
+
117
+ // Update Homekit
118
+ this.updateHomeKitCharacteristics();
119
+
120
+ // Start an update interval
121
+ interval(this.deviceRefreshRate * 1000)
122
+ .pipe(skipWhile(() => this.plugUpdateInProgress))
123
+ .subscribe(async () => {
124
+ await this.refreshStatus();
125
+ });
126
+
127
+ //regisiter webhook event handler
128
+ if (device.webhook) {
129
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} is listening webhook.`);
130
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context) => {
131
+ try {
132
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} received Webhook: ${JSON.stringify(context)}`);
133
+ const { version, battery, powerState, oscillation, chargingStatus, fanSpeed } = context;
134
+ const { Active, SwingMode, RotationSpeed } = this.Fan;
135
+ const { BatteryLevel, ChargingState } = this.Battery;
136
+ const { FirmwareRevision } = this.accessory.context;
137
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} ` +
138
+ '(version, battery, powerState, oscillation, chargingStatus, fanSpeed) = '
139
+ + `Webhook:(${version}, ${battery}, ${powerState}, ${oscillation}, ${chargingStatus}, ${fanSpeed}), `
140
+ + `current:(${FirmwareRevision}, ${BatteryLevel}, ${Active}, ${SwingMode}, ${ChargingState}, ${RotationSpeed})`);
141
+
142
+ // Active
143
+ this.Fan.Active = powerState === 'ON' ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE;
144
+
145
+ // SwingMode
146
+ this.Fan.SwingMode = oscillation === 'on' ?
147
+ this.hap.Characteristic.SwingMode.SWING_ENABLED : this.hap.Characteristic.SwingMode.SWING_DISABLED;
148
+
149
+ // RotationSpeed
150
+ this.Fan.RotationSpeed = fanSpeed;
151
+
152
+ // ChargingState
153
+ this.Battery.ChargingState = chargingStatus === 'charging' ?
154
+ this.hap.Characteristic.ChargingState.CHARGING : this.hap.Characteristic.ChargingState.NOT_CHARGING;
155
+
156
+ // BatteryLevel
157
+ this.Battery.BatteryLevel = battery;
158
+
159
+ // Firmware Version
160
+ if (version) {
161
+ this.accessory.context.version = version;
162
+ this.accessory
163
+ .getService(this.hap.Service.AccessoryInformation)!
164
+ .setCharacteristic(this.hap.Characteristic.FirmwareRevision, this.accessory.context.version)
165
+ .getCharacteristic(this.hap.Characteristic.FirmwareRevision)
166
+ .updateValue(this.accessory.context.version);
167
+ }
168
+ this.updateHomeKitCharacteristics();
169
+ } catch (e: any) {
170
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} `
171
+ + `failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`);
172
+ }
173
+ };
174
+ }
175
+
176
+ // Watch for Plug change events
177
+ // We put in a debounce of 100ms so we don't make duplicate calls
178
+ this.doPlugUpdate
179
+ .pipe(
180
+ tap(() => {
181
+ this.plugUpdateInProgress = true;
182
+ }),
183
+ debounceTime(this.platform.config.options!.pushRate! * 1000),
184
+ )
185
+ .subscribe(async () => {
186
+ try {
187
+ await this.pushChanges();
188
+ } catch (e: any) {
189
+ this.apiError(e);
190
+ this.errorLog(
191
+ `${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges with ${this.device.connectionType} Connection,` +
192
+ ` Error Message: ${JSON.stringify(e.message)}`,
193
+ );
194
+ }
195
+ this.plugUpdateInProgress = false;
196
+ });
197
+ }
198
+
199
+ async BLEparseStatus(serviceData: serviceData): Promise<void> {
200
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEparseStatus`);
201
+
202
+ // State
203
+ switch (serviceData.state) {
204
+ case 'on':
205
+ this.Fan.Active = true;
206
+ break;
207
+ default:
208
+ this.Fan.Active = false;
209
+ }
210
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.Fan.Active}`);
211
+ }
212
+
213
+ async openAPIparseStatus(deviceStatus: deviceStatus) {
214
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIparseStatus`);
215
+
216
+ // Active
217
+ this.Fan.Active = deviceStatus.body.power === 'on' ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE;
218
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Active: ${this.Fan.Active}`);
219
+
220
+ // SwingMode
221
+ this.Fan.SwingMode = deviceStatus.body.oscillation === 'on' ?
222
+ this.hap.Characteristic.SwingMode.SWING_ENABLED : this.hap.Characteristic.SwingMode.SWING_DISABLED;
223
+
224
+ // RotationSpeed
225
+ this.Fan.RotationSpeed = deviceStatus.body.fanSpeed;
226
+
227
+ // ChargingState
228
+ this.Battery.ChargingState = deviceStatus.body.chargingStatus === 'charging' ?
229
+ this.hap.Characteristic.ChargingState.CHARGING : this.hap.Characteristic.ChargingState.NOT_CHARGING;
230
+
231
+ // BatteryLevel
232
+ this.Battery.BatteryLevel = Number(deviceStatus.body.battery);
233
+ if (this.Battery.BatteryLevel < 10) {
234
+ this.Battery.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW;
235
+ } else {
236
+ this.Battery.StatusLowBattery = this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
237
+ }
238
+ if (Number.isNaN(this.Battery.BatteryLevel)) {
239
+ this.Battery.BatteryLevel = 100;
240
+ }
241
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BatteryLevel: ${this.Battery.BatteryLevel},`
242
+ + ` StatusLowBattery: ${this.Battery.StatusLowBattery}`);
243
+
244
+ // Firmware Version
245
+ const version = deviceStatus.body.version?.toString();
246
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Firmware Version: ${version?.replace(/^V|-.*$/g, '')}`);
247
+ if (deviceStatus.body.version) {
248
+ const deviceVersion = version?.replace(/^V|-.*$/g, '') ?? '0.0.0';
249
+ this.accessory
250
+ .getService(this.hap.Service.AccessoryInformation)!
251
+ .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion)
252
+ .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion)
253
+ .getCharacteristic(this.hap.Characteristic.FirmwareRevision)
254
+ .updateValue(deviceVersion);
255
+ this.accessory.context.deviceVersion = deviceVersion;
256
+ this.debugSuccessLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceVersion: ${this.accessory.context.deviceVersion}`);
257
+ }
258
+ }
259
+
260
+
261
+
262
+ /**
263
+ * Asks the SwitchBot API for the latest device information
264
+ */
265
+ async refreshStatus(): Promise<void> {
266
+ if (!this.device.enableCloudService && this.OpenAPI) {
267
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} refreshStatus enableCloudService: ${this.device.enableCloudService}`);
268
+ } else if (this.BLE) {
269
+ await this.BLERefreshStatus();
270
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
271
+ await this.openAPIRefreshStatus();
272
+ } else {
273
+ await this.offlineOff();
274
+ this.debugWarnLog(
275
+ `${this.device.deviceType}: ${this.accessory.displayName} Connection Type:` +
276
+ ` ${this.device.connectionType}, refreshStatus will not happen.`,
277
+ );
278
+ }
279
+ }
280
+
281
+ async BLERefreshStatus(): Promise<void> {
282
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLERefreshStatus`);
283
+ const switchbot = await this.platform.connectBLE();
284
+ // Convert to BLE Address
285
+ this.device.bleMac = this.device
286
+ .deviceId!.match(/.{1,2}/g)!
287
+ .join(':')
288
+ .toLowerCase();
289
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
290
+ this.getCustomBLEAddress(switchbot);
291
+ // Start to monitor advertisement packets
292
+ (async () => {
293
+ // Start to monitor advertisement packets
294
+ await switchbot.startScan({ model: this.device.bleModel, id: this.device.bleMac });
295
+ // Set an event handler
296
+ switchbot.onadvertisement = (ad: any) => {
297
+ if (this.device.bleMac === ad.address && ad.model === this.device.bleModel) {
298
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} ${JSON.stringify(ad, null, ' ')}`);
299
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} address: ${ad.address}, model: ${ad.model}`);
300
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${JSON.stringify(ad.serviceData)}`);
301
+ } else {
302
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${JSON.stringify(ad.serviceData)}`);
303
+ }
304
+ };
305
+ // Wait 10 seconds
306
+ await switchbot.wait(this.scanDuration * 1000);
307
+ // Stop to monitor
308
+ await switchbot.stopScan();
309
+ // Update HomeKit
310
+ await this.BLEparseStatus(switchbot.onadvertisement.serviceData);
311
+ await this.updateHomeKitCharacteristics();
312
+ })();
313
+ if (switchbot === undefined) {
314
+ await this.BLERefreshConnection(switchbot);
315
+ }
316
+ }
317
+
318
+ async openAPIRefreshStatus(): Promise<void> {
319
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus`);
320
+ try {
321
+ const { body, statusCode } = await this.platform.retryRequest(this.deviceMaxRetries, this.deviceDelayBetweenRetries,
322
+ `${Devices}/${this.device.deviceId}/status`, { headers: this.platform.generateHeaders() });
323
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${statusCode}`);
324
+ const deviceStatus: any = await body.json();
325
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceStatus: ${JSON.stringify(deviceStatus)}`);
326
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceStatus statusCode: ${deviceStatus.statusCode}`);
327
+ if ((statusCode === 200 || statusCode === 100) && (deviceStatus.statusCode === 200 || deviceStatus.statusCode === 100)) {
328
+ this.debugSuccessLog(`${this.device.deviceType}: ${this.accessory.displayName} `
329
+ + `statusCode: ${statusCode} & deviceStatus StatusCode: ${deviceStatus.statusCode}`);
330
+ this.openAPIparseStatus(deviceStatus);
331
+ this.updateHomeKitCharacteristics();
332
+ } else {
333
+ this.statusCode(statusCode);
334
+ this.statusCode(deviceStatus.statusCode);
335
+ }
336
+ } catch (e: any) {
337
+ this.apiError(e);
338
+ this.errorLog(
339
+ `${this.device.deviceType}: ${this.accessory.displayName} failed openAPIRefreshStatus with ${this.device.connectionType}` +
340
+ ` Connection, Error Message: ${JSON.stringify(e.message)}`,
341
+ );
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Pushes the requested changes to the SwitchBot API
347
+ * commandType command parameter Description
348
+ * "command" "turnOff" "default" = set to OFF state
349
+ * "command" "turnOn" "default" = set to ON state
350
+ * "command" "setNightLightMode" "off, 1, or 2" = off, turn off nightlight, (1, bright) (2, dim)
351
+ * "command" "setWindMode" "direct, natural, sleep, or baby" = Set fan mode
352
+ * "command" "setWindSpeed" "{1-100} e.g. 10" = Set fan speed 1~100
353
+ */
354
+
355
+ async pushChanges(): Promise<void> {
356
+ if (!this.device.enableCloudService && this.OpenAPI) {
357
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} pushChanges enableCloudService: ${this.device.enableCloudService}`);
358
+ } else if (this.BLE) {
359
+ await this.BLEpushChanges();
360
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
361
+ await this.openAPIpushChanges();
362
+ } else {
363
+ await this.offlineOff();
364
+ this.debugWarnLog(
365
+ `${this.device.deviceType}: ${this.accessory.displayName} Connection Type:` + ` ${this.device.connectionType}, pushChanges will not happen.`,
366
+ );
367
+ }
368
+ // Refresh the status from the API
369
+ interval(15000)
370
+ .pipe(skipWhile(() => this.plugUpdateInProgress))
371
+ .pipe(take(1))
372
+ .subscribe(async () => {
373
+ await this.refreshStatus();
374
+ });
375
+ }
376
+
377
+ async BLEpushChanges(): Promise<void> {
378
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEpushChanges`);
379
+ if (this.Fan.Active !== this.accessory.context.Active) {
380
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEpushChanges`
381
+ + ` On: ${this.Fan.Active} OnCached: ${this.accessory.context.Active}`);
382
+ const switchbot = await this.platform.connectBLE();
383
+ // Convert to BLE Address
384
+ this.device.bleMac = this.device
385
+ .deviceId!.match(/.{1,2}/g)!
386
+ .join(':')
387
+ .toLowerCase();
388
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
389
+ switchbot
390
+ .discover({
391
+ model: this.device.bleModel,
392
+ id: this.device.bleMac,
393
+ })
394
+ .then(async (device_list: any) => {
395
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.Fan.Active}`);
396
+ return await this.retryBLE({
397
+ max: await this.maxRetryBLE(),
398
+ fn: async () => {
399
+ if (this.Fan.Active) {
400
+ return await device_list[0].turnOn({ id: this.device.bleMac });
401
+ } else {
402
+ return await device_list[0].turnOff({ id: this.device.bleMac });
403
+ }
404
+ },
405
+ });
406
+ })
407
+ .then(() => {
408
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Done.`);
409
+ this.successLog(`${this.device.deviceType}: ${this.accessory.displayName} `
410
+ + `Active: ${this.Fan.Active} sent over BLE, sent successfully`);
411
+ this.Fan.Active = false;
412
+ })
413
+ .catch(async (e: any) => {
414
+ this.apiError(e);
415
+ this.errorLog(
416
+ `${this.device.deviceType}: ${this.accessory.displayName} failed BLEpushChanges with ${this.device.connectionType}` +
417
+ ` Connection, Error Message: ${JSON.stringify(e.message)}`,
418
+ );
419
+ await this.BLEPushConnection();
420
+ });
421
+ } else {
422
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No BLEpushChanges,`
423
+ + ` Active: ${this.Fan.Active}, ActiveCached: ${this.accessory.context.Active}`);
424
+ }
425
+ }
426
+
427
+ async openAPIpushChanges() {
428
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIpushChanges`);
429
+ if (this.Fan.Active !== this.accessory.context.Active) {
430
+ let command = '';
431
+ if (this.Fan.Active) {
432
+ command = 'turnOn';
433
+ } else {
434
+ command = 'turnOff';
435
+ }
436
+ const bodyChange = JSON.stringify({
437
+ command: `${command}`,
438
+ parameter: 'default',
439
+ commandType: 'command',
440
+ });
441
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API, body: ${bodyChange},`);
442
+ try {
443
+ const { body, statusCode } = await request(`${Devices}/${this.device.deviceId}/commands`, {
444
+ body: bodyChange,
445
+ method: 'POST',
446
+ headers: this.platform.generateHeaders(),
447
+ });
448
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${statusCode}`);
449
+ const deviceStatus: any = await body.json();
450
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceStatus: ${JSON.stringify(deviceStatus)}`);
451
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceStatus body: ${JSON.stringify(deviceStatus.body)}`);
452
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} deviceStatus statusCode: ${deviceStatus.statusCode}`);
453
+ if ((statusCode === 200 || statusCode === 100) && (deviceStatus.statusCode === 200 || deviceStatus.statusCode === 100)) {
454
+ this.debugErrorLog(`${this.device.deviceType}: ${this.accessory.displayName} `
455
+ + `statusCode: ${statusCode} & deviceStatus StatusCode: ${deviceStatus.statusCode}`);
456
+ this.successLog(`${this.device.deviceType}: ${this.accessory.displayName} `
457
+ + `request to SwitchBot API, body: ${JSON.stringify(JSON.parse(bodyChange))} sent successfully`);
458
+ } else {
459
+ this.statusCode(statusCode);
460
+ this.statusCode(deviceStatus.statusCode);
461
+ }
462
+ } catch (e: any) {
463
+ this.apiError(e);
464
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed openAPIpushChanges with ${this.device.connectionType}`
465
+ + ` Connection, Error Message: ${JSON.stringify(e.message)}`);
466
+ }
467
+ } else {
468
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No openAPIpushChanges.`
469
+ + `On: ${this.Fan.Active}, ActiveCached: ${this.accessory.context.Active}`);
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Handle requests to set the value of the "On" characteristic
475
+ */
476
+ async ActiveSet(value: CharacteristicValue): Promise<void> {
477
+ if (this.Fan.Active === this.accessory.context.Active) {
478
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set Active: ${value}`);
479
+ } else {
480
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set Active: ${value}`);
481
+ }
482
+
483
+ this.Fan.Active = value;
484
+ this.doPlugUpdate.next();
485
+ }
486
+
487
+ /**
488
+ * Handle requests to set the value of the "On" characteristic
489
+ */
490
+ async RotationSpeedSet(value: CharacteristicValue): Promise<void> {
491
+ if (this.Fan.RotationSpeed === this.accessory.context.RotationSpeed) {
492
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set RotationSpeed: ${value}`);
493
+ } else {
494
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set RotationSpeed: ${value}`);
495
+ }
496
+
497
+ this.Fan.RotationSpeed = value;
498
+ this.doPlugUpdate.next();
499
+ }
500
+
501
+ /**
502
+ * Handle requests to set the value of the "On" characteristic
503
+ */
504
+ async SwingModeSet(value: CharacteristicValue): Promise<void> {
505
+ if (this.Fan.SwingMode === this.accessory.context.SwingMode) {
506
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set SwingMode: ${value}`);
507
+ } else {
508
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set SwingMode: ${value}`);
509
+ }
510
+
511
+ this.Fan.SwingMode = value;
512
+ this.doPlugUpdate.next();
513
+ }
514
+
515
+ async updateHomeKitCharacteristics(): Promise<void> {
516
+ // Active
517
+ if (this.Fan.Active === undefined) {
518
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Active: ${this.Fan.Active}`);
519
+ } else {
520
+ this.accessory.context.Active = this.Fan.Active;
521
+ this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.Fan.Active);
522
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic Active: ${this.Fan.Active}`);
523
+ }
524
+ // RotationSpeed
525
+ if (this.Fan.RotationSpeed === undefined) {
526
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} RotationSpeed: ${this.Fan.RotationSpeed}`);
527
+ } else {
528
+ this.accessory.context.RotationSpeed = this.Fan.RotationSpeed;
529
+ this.Fan.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, this.Fan.RotationSpeed);
530
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic RotationSpeed: ${this.Fan.RotationSpeed}`);
531
+ }
532
+ // SwingMode
533
+ if (this.Fan.SwingMode === undefined) {
534
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} SwingMode: ${this.Fan.SwingMode}`);
535
+ } else {
536
+ this.accessory.context.SwingMode = this.Fan.SwingMode;
537
+ this.Fan.Service.updateCharacteristic(this.hap.Characteristic.SwingMode, this.Fan.SwingMode);
538
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic SwingMode: ${this.Fan.SwingMode}`);
539
+ }
540
+ // BateryLevel
541
+ if (this.Battery.BatteryLevel === undefined) {
542
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BatteryLevel: ${this.Battery.BatteryLevel}`);
543
+ } else {
544
+ this.accessory.context.BatteryLevel = this.Battery.BatteryLevel;
545
+ this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel);
546
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.Battery.BatteryLevel}`);
547
+ }
548
+ // ChargingState
549
+ if (this.Battery.ChargingState === undefined) {
550
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} ChargingState: ${this.Battery.ChargingState}`);
551
+ } else {
552
+ this.accessory.context.ChargingState = this.Battery.ChargingState;
553
+ this.Battery.Service.updateCharacteristic(this.hap.Characteristic.ChargingState, this.Battery.ChargingState);
554
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic ChargingState: ${this.Battery.ChargingState}`);
555
+ }
556
+ // StatusLowBattery
557
+ if (this.Battery.StatusLowBattery === undefined) {
558
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} StatusLowBattery: ${this.Battery.StatusLowBattery}`);
559
+ } else {
560
+ this.accessory.context.StatusLowBattery = this.Battery.StatusLowBattery;
561
+ this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery);
562
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic`
563
+ + ` StatusLowBattery: ${this.Battery.StatusLowBattery}`);
564
+ }
565
+ }
566
+
567
+ async BLEPushConnection() {
568
+ if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') {
569
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection to Push Changes`);
570
+ await this.openAPIpushChanges();
571
+ }
572
+ }
573
+
574
+ async BLERefreshConnection(switchbot: any): Promise<void> {
575
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} wasn't able to establish BLE Connection, node-switchbot:`
576
+ + ` ${JSON.stringify(switchbot)}`);
577
+ if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') {
578
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection to Refresh Status`);
579
+ await this.openAPIRefreshStatus();
580
+ }
581
+ }
582
+
583
+ async offlineOff(): Promise<void> {
584
+ if (this.device.offline) {
585
+ this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE);
586
+ this.Fan.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, 0);
587
+ this.Fan.Service.updateCharacteristic(this.hap.Characteristic.SwingMode, this.hap.Characteristic.SwingMode.SWING_DISABLED);
588
+ }
589
+ }
590
+
591
+ async apiError(e: any): Promise<void> {
592
+ this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, e);
593
+ this.Fan.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, e);
594
+ this.Fan.Service.updateCharacteristic(this.hap.Characteristic.SwingMode, e);
595
+ }
596
+ }