@switchbot/homebridge-switchbot 3.5.0-beta.5 → 3.5.0-beta.51

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