@switchbot/homebridge-switchbot 3.5.0-beta.6 → 3.5.0-beta.60

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