@switchbot/homebridge-switchbot 2.0.0-beta.4 → 2.0.0-beta.41

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 (119) hide show
  1. package/CHANGELOG.md +12 -2
  2. package/README.md +3 -2
  3. package/config.schema.json +108 -20
  4. package/dist/device/bot.d.ts +34 -28
  5. package/dist/device/bot.d.ts.map +1 -1
  6. package/dist/device/bot.js +657 -648
  7. package/dist/device/bot.js.map +1 -1
  8. package/dist/device/ceilinglight.d.ts +137 -0
  9. package/dist/device/ceilinglight.d.ts.map +1 -0
  10. package/dist/device/ceilinglight.js +1127 -0
  11. package/dist/device/ceilinglight.js.map +1 -0
  12. package/dist/device/colorbulb.d.ts +36 -19
  13. package/dist/device/colorbulb.d.ts.map +1 -1
  14. package/dist/device/colorbulb.js +486 -309
  15. package/dist/device/colorbulb.js.map +1 -1
  16. package/dist/device/contact.d.ts +18 -8
  17. package/dist/device/contact.d.ts.map +1 -1
  18. package/dist/device/contact.js +306 -237
  19. package/dist/device/contact.js.map +1 -1
  20. package/dist/device/curtain.d.ts +23 -16
  21. package/dist/device/curtain.d.ts.map +1 -1
  22. package/dist/device/curtain.js +566 -502
  23. package/dist/device/curtain.js.map +1 -1
  24. package/dist/device/humidifier.d.ts +24 -18
  25. package/dist/device/humidifier.d.ts.map +1 -1
  26. package/dist/device/humidifier.js +453 -369
  27. package/dist/device/humidifier.js.map +1 -1
  28. package/dist/device/lock.d.ts +27 -8
  29. package/dist/device/lock.d.ts.map +1 -1
  30. package/dist/device/lock.js +305 -148
  31. package/dist/device/lock.js.map +1 -1
  32. package/dist/device/meter.d.ts +22 -16
  33. package/dist/device/meter.d.ts.map +1 -1
  34. package/dist/device/meter.js +367 -267
  35. package/dist/device/meter.js.map +1 -1
  36. package/dist/device/meterplus.d.ts +24 -12
  37. package/dist/device/meterplus.d.ts.map +1 -1
  38. package/dist/device/meterplus.js +381 -213
  39. package/dist/device/meterplus.js.map +1 -1
  40. package/dist/device/motion.d.ts +18 -8
  41. package/dist/device/motion.d.ts.map +1 -1
  42. package/dist/device/motion.js +279 -216
  43. package/dist/device/motion.js.map +1 -1
  44. package/dist/device/plug.d.ts +27 -17
  45. package/dist/device/plug.d.ts.map +1 -1
  46. package/dist/device/plug.js +295 -251
  47. package/dist/device/plug.js.map +1 -1
  48. package/dist/device/striplight.d.ts +43 -19
  49. package/dist/device/striplight.d.ts.map +1 -1
  50. package/dist/device/striplight.js +506 -262
  51. package/dist/device/striplight.js.map +1 -1
  52. package/dist/irdevice/airconditioner.d.ts +9 -12
  53. package/dist/irdevice/airconditioner.d.ts.map +1 -1
  54. package/dist/irdevice/airconditioner.js +278 -230
  55. package/dist/irdevice/airconditioner.js.map +1 -1
  56. package/dist/irdevice/airpurifier.d.ts +5 -3
  57. package/dist/irdevice/airpurifier.d.ts.map +1 -1
  58. package/dist/irdevice/airpurifier.js +154 -104
  59. package/dist/irdevice/airpurifier.js.map +1 -1
  60. package/dist/irdevice/camera.d.ts +11 -8
  61. package/dist/irdevice/camera.d.ts.map +1 -1
  62. package/dist/irdevice/camera.js +133 -86
  63. package/dist/irdevice/camera.js.map +1 -1
  64. package/dist/irdevice/fan.d.ts +10 -7
  65. package/dist/irdevice/fan.d.ts.map +1 -1
  66. package/dist/irdevice/fan.js +160 -109
  67. package/dist/irdevice/fan.js.map +1 -1
  68. package/dist/irdevice/light.d.ts +11 -8
  69. package/dist/irdevice/light.d.ts.map +1 -1
  70. package/dist/irdevice/light.js +137 -103
  71. package/dist/irdevice/light.js.map +1 -1
  72. package/dist/irdevice/other.d.ts +11 -8
  73. package/dist/irdevice/other.d.ts.map +1 -1
  74. package/dist/irdevice/other.js +145 -98
  75. package/dist/irdevice/other.js.map +1 -1
  76. package/dist/irdevice/tv.d.ts +12 -9
  77. package/dist/irdevice/tv.d.ts.map +1 -1
  78. package/dist/irdevice/tv.js +143 -93
  79. package/dist/irdevice/tv.js.map +1 -1
  80. package/dist/irdevice/vacuumcleaner.d.ts +9 -10
  81. package/dist/irdevice/vacuumcleaner.d.ts.map +1 -1
  82. package/dist/irdevice/vacuumcleaner.js +132 -89
  83. package/dist/irdevice/vacuumcleaner.js.map +1 -1
  84. package/dist/irdevice/waterheater.d.ts +8 -9
  85. package/dist/irdevice/waterheater.d.ts.map +1 -1
  86. package/dist/irdevice/waterheater.js +132 -89
  87. package/dist/irdevice/waterheater.js.map +1 -1
  88. package/dist/platform.d.ts +15 -12
  89. package/dist/platform.d.ts.map +1 -1
  90. package/dist/platform.js +574 -454
  91. package/dist/platform.js.map +1 -1
  92. package/dist/settings.d.ts +21 -11
  93. package/dist/settings.d.ts.map +1 -1
  94. package/dist/settings.js.map +1 -1
  95. package/homebridge-ui/public/index.html +1 -0
  96. package/package.json +13 -11
  97. package/src/device/bot.ts +656 -659
  98. package/src/device/ceilinglight.ts +1208 -0
  99. package/src/device/colorbulb.ts +490 -344
  100. package/src/device/contact.ts +309 -241
  101. package/src/device/curtain.ts +571 -516
  102. package/src/device/humidifier.ts +456 -403
  103. package/src/device/lock.ts +314 -149
  104. package/src/device/meter.ts +378 -280
  105. package/src/device/meterplus.ts +391 -219
  106. package/src/device/motion.ts +285 -222
  107. package/src/device/plug.ts +299 -260
  108. package/src/device/striplight.ts +522 -289
  109. package/src/irdevice/airconditioner.ts +282 -241
  110. package/src/irdevice/airpurifier.ts +151 -103
  111. package/src/irdevice/camera.ts +131 -85
  112. package/src/irdevice/fan.ts +157 -106
  113. package/src/irdevice/light.ts +135 -103
  114. package/src/irdevice/other.ts +143 -97
  115. package/src/irdevice/tv.ts +142 -93
  116. package/src/irdevice/vacuumcleaner.ts +131 -89
  117. package/src/irdevice/waterheater.ts +130 -89
  118. package/src/platform.ts +567 -476
  119. package/src/settings.ts +22 -11
@@ -0,0 +1,1208 @@
1
+ import https from 'https';
2
+ import crypto from 'crypto';
3
+ import { Context } from 'vm';
4
+ import { IncomingMessage } from 'http';
5
+ import superStringify from 'super-stringify';
6
+ import { interval, Subject } from 'rxjs';
7
+ import { SwitchBotPlatform } from '../platform';
8
+ import { debounceTime, skipWhile, take, tap } from 'rxjs/operators';
9
+ import { Service, PlatformAccessory, CharacteristicValue, ControllerConstructor, Controller, ControllerServiceMap } from 'homebridge';
10
+ import { device, devicesConfig, deviceStatus, switchbot, hs2rgb, rgb2hs, m2hs, serviceData, ad, HostDomain, DevicePath } from '../settings';
11
+
12
+ /**
13
+ * Platform Accessory
14
+ * An instance of this class is created for each accessory your platform registers
15
+ * Each accessory may expose multiple services of different service types.
16
+ */
17
+ export class CeilingLight {
18
+ // Services
19
+ lightBulbService!: Service;
20
+
21
+ // Characteristic Values
22
+ On!: CharacteristicValue;
23
+ Hue!: CharacteristicValue;
24
+ Saturation!: CharacteristicValue;
25
+ Brightness!: CharacteristicValue;
26
+ ColorTemperature?: CharacteristicValue;
27
+
28
+ // OpenAPI Others
29
+ power: deviceStatus['power'];
30
+ color: deviceStatus['color'];
31
+ brightness: deviceStatus['brightness'];
32
+ colorTemperature?: deviceStatus['colorTemperature'];
33
+ deviceStatus!: any;
34
+
35
+ // BLE Others
36
+ connected?: boolean;
37
+ switchbot!: switchbot;
38
+ address!: ad['address'];
39
+ serviceData!: serviceData;
40
+ state: serviceData['state'];
41
+ delay: serviceData['delay'];
42
+ wifiRssi: serviceData['wifiRssi'];
43
+
44
+ // Config
45
+ set_minStep?: number;
46
+ scanDuration!: number;
47
+ deviceLogging!: string;
48
+ deviceRefreshRate!: number;
49
+ adaptiveLightingShift?: number;
50
+
51
+ // Adaptive Lighting
52
+ AdaptiveLightingController?: ControllerConstructor | Controller<ControllerServiceMap>;
53
+ minKelvin!: number;
54
+ maxKelvin!: number;
55
+
56
+ // Others
57
+ cacheKelvin!: number;
58
+
59
+ // Updates
60
+ ceilingLightUpdateInProgress!: boolean;
61
+ doCeilingLightUpdate!: Subject<void>;
62
+
63
+ // Connection
64
+ private readonly BLE = (this.device.connectionType === 'BLE' || this.device.connectionType === 'BLE/OpenAPI');
65
+ private readonly OpenAPI = (this.device.connectionType === 'OpenAPI' || this.device.connectionType === 'BLE/OpenAPI');
66
+
67
+ constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: device & devicesConfig) {
68
+ // default placeholders
69
+ this.logs(device);
70
+ this.scan(device);
71
+ this.refreshRate(device);
72
+ this.adaptiveLighting(device);
73
+ this.config(device);
74
+ this.context();
75
+ // this is subject we use to track when we need to POST changes to the SwitchBot API
76
+ this.doCeilingLightUpdate = new Subject();
77
+ this.ceilingLightUpdateInProgress = false;
78
+
79
+ // Retrieve initial values and updateHomekit
80
+ this.refreshStatus();
81
+
82
+ // set accessory information
83
+ accessory
84
+ .getService(this.platform.Service.AccessoryInformation)!
85
+ .setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
86
+ .setCharacteristic(this.platform.Characteristic.Model, this.model(device))
87
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
88
+ .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
89
+ .getCharacteristic(this.platform.Characteristic.FirmwareRevision)
90
+ .updateValue(this.FirmwareRevision(accessory, device));
91
+
92
+ // get the Lightbulb service if it exists, otherwise create a new Lightbulb service
93
+ // you can create multiple services for each accessory
94
+ (this.lightBulbService = accessory.getService(this.platform.Service.Lightbulb) || accessory.addService(this.platform.Service.Lightbulb)),
95
+ `${accessory.displayName} ${device.deviceType}`;
96
+
97
+ if (this.adaptiveLightingShift === -1 && this.accessory.context.adaptiveLighting) {
98
+ this.accessory.removeService(this.lightBulbService);
99
+ this.lightBulbService = this.accessory.addService(this.platform.Service.Lightbulb);
100
+ this.accessory.context.adaptiveLighting = false;
101
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} adaptiveLighting: ${this.accessory.context.adaptiveLighting}`);
102
+ }
103
+
104
+ // To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
105
+ // when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
106
+ // accessory.getService('NAME') ?? accessory.addService(this.platform.Service.Outlet, 'NAME', 'USER_DEFINED_SUBTYPE');
107
+
108
+ // set the service name, this is what is displayed as the default name on the Home app
109
+ // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method.
110
+ this.lightBulbService.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName);
111
+
112
+ // handle on / off events using the On characteristic
113
+ this.lightBulbService.getCharacteristic(this.platform.Characteristic.On).onSet(this.OnSet.bind(this));
114
+
115
+ // handle Brightness events using the Brightness characteristic
116
+ this.lightBulbService
117
+ .getCharacteristic(this.platform.Characteristic.Brightness)
118
+ .setProps({
119
+ minStep: this.minStep(device),
120
+ minValue: 0,
121
+ maxValue: 100,
122
+ validValueRanges: [0, 100],
123
+ })
124
+ .onGet(() => {
125
+ return this.Brightness;
126
+ })
127
+ .onSet(this.BrightnessSet.bind(this));
128
+
129
+ // handle ColorTemperature events using the ColorTemperature characteristic
130
+ this.lightBulbService
131
+ .getCharacteristic(this.platform.Characteristic.ColorTemperature)
132
+ .setProps({
133
+ minValue: 140,
134
+ maxValue: 500,
135
+ validValueRanges: [140, 500],
136
+ })
137
+ .onGet(() => {
138
+ return this.ColorTemperature!;
139
+ })
140
+ .onSet(this.ColorTemperatureSet.bind(this));
141
+
142
+ // handle Hue events using the Hue characteristic
143
+ this.lightBulbService
144
+ .getCharacteristic(this.platform.Characteristic.Hue)
145
+ .setProps({
146
+ minValue: 0,
147
+ maxValue: 360,
148
+ validValueRanges: [0, 360],
149
+ })
150
+ .onGet(() => {
151
+ return this.Hue;
152
+ })
153
+ .onSet(this.HueSet.bind(this));
154
+
155
+ // handle Hue events using the Hue characteristic
156
+ this.lightBulbService
157
+ .getCharacteristic(this.platform.Characteristic.Saturation)
158
+ .setProps({
159
+ minValue: 0,
160
+ maxValue: 100,
161
+ validValueRanges: [0, 100],
162
+ })
163
+ .onGet(() => {
164
+ return this.Saturation;
165
+ })
166
+ .onSet(this.SaturationSet.bind(this));
167
+
168
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} adaptiveLightingShift: ${this.adaptiveLightingShift}`);
169
+ if (this.adaptiveLightingShift !== -1) {
170
+ this.AdaptiveLightingController = new platform.api.hap.AdaptiveLightingController(this.lightBulbService, {
171
+ customTemperatureAdjustment: this.adaptiveLightingShift,
172
+ });
173
+ this.accessory.configureController(this.AdaptiveLightingController);
174
+ this.accessory.context.adaptiveLighting = true;
175
+ this.debugLog(
176
+ `${this.device.deviceType}: ${this.accessory.displayName} adaptiveLighting: ${this.accessory.context.adaptiveLighting},` +
177
+ ` adaptiveLightingShift: ${this.adaptiveLightingShift}`,
178
+ );
179
+ }
180
+
181
+ // Update Homekit
182
+ this.updateHomeKitCharacteristics();
183
+
184
+ // Start an update interval
185
+ interval(this.deviceRefreshRate * 1000)
186
+ .pipe(skipWhile(() => this.ceilingLightUpdateInProgress))
187
+ .subscribe(async () => {
188
+ await this.refreshStatus();
189
+ });
190
+
191
+ // Watch for Bulb change events
192
+ // We put in a debounce of 100ms so we don't make duplicate calls
193
+ this.doCeilingLightUpdate
194
+ .pipe(
195
+ tap(() => {
196
+ this.ceilingLightUpdateInProgress = true;
197
+ }),
198
+ debounceTime(this.platform.config.options!.pushRate! * 1000),
199
+ )
200
+ .subscribe(async () => {
201
+ try {
202
+ await this.pushChanges();
203
+ } catch (e: any) {
204
+ this.apiError(e);
205
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges with ${this.device.connectionType} Connection,`
206
+ + ` Error Message: ${superStringify(e.message)}`);
207
+ }
208
+ this.ceilingLightUpdateInProgress = false;
209
+ // Refresh the status from the API
210
+ interval(15000)
211
+ .pipe(skipWhile(() => this.ceilingLightUpdateInProgress))
212
+ .pipe(take(1))
213
+ .subscribe(async () => {
214
+ await this.refreshStatus();
215
+ });
216
+ });
217
+ }
218
+
219
+ private model(device): CharacteristicValue {
220
+ let model: string;
221
+ if (device.deviceType === 'Ceiling Light') {
222
+ model = 'W2612230' || 'W2612240';
223
+ } else if (device.deviceType === 'Ceiling Light Pro') {
224
+ model = 'W2612210' || 'W2612220';
225
+ } else {
226
+ model = 'unknown';
227
+ }
228
+ return model;
229
+ }
230
+
231
+ /**
232
+ * Parse the device status from the SwitchBot api
233
+ */
234
+ async parseStatus(): Promise<void> {
235
+ /*if (this.BLE) {
236
+ await this.BLEparseStatus();
237
+ } else*/ if (this.OpenAPI && this.platform.config.credentials?.token) {
238
+ await this.openAPIparseStatus();
239
+ } else {
240
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
241
+ + ` ${this.device.connectionType}, parseStatus will not happen.`);
242
+ }
243
+ }
244
+
245
+ async BLEparseStatus(): Promise<void> {
246
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEparseStatus`);
247
+ // State
248
+ switch (this.state) {
249
+ case 'on':
250
+ this.On = true;
251
+ break;
252
+ default:
253
+ this.On = false;
254
+ }
255
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`);
256
+ }
257
+
258
+ async openAPIparseStatus() {
259
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIparseStatus`);
260
+ switch (this.power) {
261
+ case 'on':
262
+ this.On = true;
263
+ break;
264
+ default:
265
+ this.On = false;
266
+ }
267
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`);
268
+
269
+ // Brightness
270
+ this.Brightness = Number(this.brightness);
271
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Brightness: ${this.Brightness}`);
272
+
273
+ // Color, Hue & Brightness
274
+ if (this.color) {
275
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} color: ${superStringify(this.color)}`);
276
+ const [red, green, blue] = this.color!.split(':');
277
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} red: ${superStringify(red)}`);
278
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} green: ${superStringify(green)}`);
279
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} blue: ${superStringify(blue)}`);
280
+
281
+ const [hue, saturation] = rgb2hs(Number(red), Number(green), Number(blue));
282
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName}`
283
+ + ` hs: ${superStringify(rgb2hs(Number(red), Number(green), Number(blue)))}`);
284
+
285
+ // Hue
286
+ this.Hue = hue;
287
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Hue: ${this.Hue}`);
288
+
289
+ // Saturation
290
+ this.Saturation = saturation;
291
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Saturation: ${this.Saturation}`);
292
+ }
293
+
294
+ // ColorTemperature
295
+ if (!Number.isNaN(this.colorTemperature)) {
296
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} OpenAPI ColorTemperature: ${this.colorTemperature}`);
297
+ const mired = Math.round(1000000 / this.colorTemperature!);
298
+
299
+ this.ColorTemperature = Number(mired);
300
+
301
+ this.ColorTemperature = Math.max(Math.min(this.ColorTemperature, 500), 140);
302
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} ColorTemperature: ${this.ColorTemperature}`);
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Asks the SwitchBot API for the latest device information
308
+ */
309
+ async refreshStatus(): Promise<void> {
310
+ /*if (this.BLE) {
311
+ await this.BLERefreshStatus();
312
+ } else*/ if (this.OpenAPI && this.platform.config.credentials?.token) {
313
+ await this.openAPIRefreshStatus();
314
+ } else {
315
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
316
+ + ` ${this.device.connectionType}, refreshStatus will not happen.`);
317
+ }
318
+ }
319
+
320
+ async BLERefreshStatus(): Promise<void> {
321
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLERefreshStatus`);
322
+ const switchbot = await this.platform.connectBLE();
323
+ // Convert to BLE Address
324
+ this.device.bleMac = this.device
325
+ .deviceId!.match(/.{1,2}/g)!
326
+ .join(':')
327
+ .toLowerCase();
328
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
329
+ this.getCustomBLEAddress(switchbot);
330
+ // Start to monitor advertisement packets
331
+ if (switchbot !== false) {
332
+ switchbot
333
+ .startScan({
334
+ model: 'u',
335
+ id: this.device.bleMac,
336
+ })
337
+ .then(() => {
338
+ // Set an event hander
339
+ switchbot.onadvertisement = (ad: any) => {
340
+ this.address = ad.address;
341
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Config BLE Address: ${this.device.bleMac},`
342
+ + ` BLE Address Found: ${this.address}`);
343
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${superStringify(ad.serviceData)}`);
344
+ this.serviceData = ad.serviceData;
345
+ //this.state = ad.serviceData.state;
346
+ //this.delay = ad.serviceData.delay;
347
+ //this.timer = ad.serviceData.timer;
348
+ //this.syncUtcTime = ad.serviceData.syncUtcTime;
349
+ //this.wifiRssi = ad.serviceData.wifiRssi;
350
+ //this.overload = ad.serviceData.overload;
351
+ //this.currentPower = ad.serviceData.currentPower;
352
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${superStringify(ad.serviceData)}`);
353
+ /*this.debugLog(
354
+ `${this.device.deviceType}: ${this.accessory.displayName} state: ${ad.serviceData.state}, ` +
355
+ `delay: ${ad.serviceData.delay}, timer: ${ad.serviceData.timer}, syncUtcTime: ${ad.serviceData.syncUtcTime} ` +
356
+ `wifiRssi: ${ad.serviceData.wifiRssi}, overload: ${ad.serviceData.overload}, currentPower: ${ad.serviceData.currentPower}`,
357
+ );*/
358
+
359
+ if (this.serviceData) {
360
+ this.connected = true;
361
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} connected: ${this.connected}`);
362
+ } else {
363
+ this.connected = false;
364
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} connected: ${this.connected}`);
365
+ }
366
+ };
367
+ // Wait 2 seconds
368
+ return switchbot.wait(this.scanDuration * 1000);
369
+ })
370
+ .then(async () => {
371
+ // Stop to monitor
372
+ switchbot.stopScan();
373
+ if (this.connected) {
374
+ this.BLEparseStatus();
375
+ this.updateHomeKitCharacteristics();
376
+ } else {
377
+ await this.BLERefreshConnection(switchbot);
378
+ }
379
+ })
380
+ .catch(async (e: any) => {
381
+ this.apiError(e);
382
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed BLERefreshStatus with ${this.device.connectionType}`
383
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
384
+ await this.BLERefreshConnection(switchbot);
385
+ });
386
+ } else {
387
+ await this.BLERefreshConnection(switchbot);
388
+ }
389
+ }
390
+
391
+ async openAPIRefreshStatus() {
392
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus`);
393
+ try {
394
+ const t = Date.now();
395
+ const nonce = 'requestID';
396
+ const data = this.platform.config.credentials?.token + t + nonce;
397
+ const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
398
+ const sign = signTerm.toString('base64');
399
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
400
+ const options = {
401
+ hostname: HostDomain,
402
+ port: 443,
403
+ path: `${DevicePath}/${this.device.deviceId}/status`,
404
+ method: 'GET',
405
+ headers: {
406
+ Authorization: this.platform.config.credentials?.token,
407
+ sign: sign,
408
+ nonce: nonce,
409
+ t: t,
410
+ 'Content-Type': 'application/json',
411
+ },
412
+ };
413
+ const req = https.request(options, (res) => {
414
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus statusCode: ${res.statusCode}`);
415
+ this.statusCode({ res });
416
+ let rawData = '';
417
+ res.on('data', (d) => {
418
+ rawData += d;
419
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
420
+ });
421
+ res.on('end', () => {
422
+ try {
423
+ this.deviceStatus = JSON.parse(rawData);
424
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus: ${superStringify(this.deviceStatus)}`);
425
+ this.power = this.deviceStatus.body.power;
426
+ this.color = this.deviceStatus.body.color;
427
+ this.brightness = this.deviceStatus.body.brightness;
428
+ this.colorTemperature = this.deviceStatus.body.colorTemperature;
429
+ this.openAPIparseStatus();
430
+ this.updateHomeKitCharacteristics();
431
+ } catch (e: any) {
432
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
433
+ }
434
+ });
435
+ });
436
+ req.on('error', (e: any) => {
437
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
438
+ });
439
+ req.end();
440
+ } catch (e: any) {
441
+ this.apiError(e);
442
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed openAPIRefreshStatus with ${this.device.connectionType}`
443
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Pushes the requested changes to the SwitchBot API
449
+ * deviceType commandType Command command parameter Description
450
+ * Color Bulb - "command" "turnOff" "default" = set to OFF state
451
+ * Color Bulb - "command" "turnOn" "default" = set to ON state
452
+ * Color Bulb - "command" "toggle" "default" = toggle state
453
+ * Color Bulb - "command" "setBrightness" "{1-100}" = set brightness
454
+ * Color Bulb - "command" "setColor" "{0-255}:{0-255}:{0-255}" = set RGB color value
455
+ * Color Bulb - "command" "setColorTemperature" "{2700-6500}" = set color temperature
456
+ *
457
+ */
458
+ async pushChanges(): Promise<void> {
459
+ /*if (this.BLE) {
460
+ await this.BLEpushChanges();
461
+ } else*/ if (this.OpenAPI && this.platform.config.credentials?.token) {
462
+ await this.openAPIpushChanges();
463
+ } else {
464
+ this.debugWarnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
465
+ + ` ${this.device.connectionType}, pushChanges will not happen.`);
466
+ }
467
+ interval(5000)
468
+ .pipe(take(1))
469
+ .subscribe(async () => {
470
+ await this.refreshStatus();
471
+ });
472
+ }
473
+
474
+ async BLEpushChanges(): Promise<void> {
475
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEpushChanges`);
476
+ if (this.On !== this.accessory.context.On) {
477
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEpushChanges On: ${this.On} OnCached: ${this.accessory.context.On}`);
478
+ const switchbot = await this.platform.connectBLE();
479
+ // Convert to BLE Address
480
+ this.device.bleMac = this.device
481
+ .deviceId!.match(/.{1,2}/g)!
482
+ .join(':')
483
+ .toLowerCase();
484
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
485
+ switchbot
486
+ .discover({
487
+ model: 'u',
488
+ id: this.device.bleMac,
489
+ })
490
+ .then((device_list: any) => {
491
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`);
492
+ return this.turnOnOff(device_list);
493
+ })
494
+ .then(() => {
495
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Done.`);
496
+ this.On = false;
497
+ })
498
+ .catch(async (e: any) => {
499
+ this.apiError(e);
500
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed BLEpushChanges with ${this.device.connectionType}`
501
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
502
+ await this.BLEPushConnection();
503
+ });
504
+ } else {
505
+ this.debugLog(
506
+ `${this.device.deviceType}: ${this.accessory.displayName} No BLEpushChanges.` + `On: ${this.On}, `
507
+ +`OnCached: ${this.accessory.context.On}`,
508
+ );
509
+ }
510
+ }
511
+
512
+ async openAPIpushChanges(): Promise<void> {
513
+ try {
514
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIpushChanges`);
515
+ if (this.On !== this.accessory.context.On) {
516
+ // Make Push On request to the API
517
+ const t = Date.now();
518
+ const nonce = 'requestID';
519
+ const data = this.platform.config.credentials?.token + t + nonce;
520
+ const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret)
521
+ .update(Buffer.from(data, 'utf-8'))
522
+ .digest();
523
+ const sign = signTerm.toString('base64');
524
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
525
+
526
+ let command = '';
527
+ if (this.On) {
528
+ command = 'turnOn';
529
+ } else {
530
+ command = 'turnOff';
531
+ }
532
+ const body = superStringify({
533
+ 'command': `${command}`,
534
+ 'parameter': 'default',
535
+ 'commandType': 'command',
536
+ });
537
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API. body: ${body},`);
538
+ const options = {
539
+ hostname: HostDomain,
540
+ port: 443,
541
+ path: `${DevicePath}/${this.device.deviceId}/commands`,
542
+ method: 'POST',
543
+ headers: {
544
+ 'Authorization': this.platform.config.credentials?.token,
545
+ 'sign': sign,
546
+ 'nonce': nonce,
547
+ 't': t,
548
+ 'Content-Type': 'application/json',
549
+ 'Content-Length': body.length,
550
+ },
551
+ };
552
+ const req = https.request(options, res => {
553
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIpushChanges statusCode: ${res.statusCode}`);
554
+ this.statusCode({ res });
555
+ res.on('data', d => {
556
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
557
+ });
558
+ });
559
+ req.on('error', (e: any) => {
560
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
561
+ });
562
+ req.write(body);
563
+ req.end();
564
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} pushChanges: ${superStringify(req)}`);
565
+ } else {
566
+ this.debugLog(
567
+ `${this.device.deviceType}: ${this.accessory.displayName} No openAPIpushChanges.` + `On: ${this.On}, `
568
+ +`OnCached: ${this.accessory.context.On}`,
569
+ );
570
+ }
571
+ // Push Brightness Update
572
+ if (this.On) {
573
+ await this.pushBrightnessChanges();
574
+ }
575
+ // Push ColorTemperature Update
576
+ if (this.On) {
577
+ await this.pushColorTemperatureChanges();
578
+ }
579
+ // Push Hue & Saturation Update
580
+ if (this.On) {
581
+ await this.pushHueSaturationChanges();
582
+ }
583
+ } catch (e: any) {
584
+ this.apiError(e);
585
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed openAPIpushChanges with ${this.device.connectionType}`
586
+ + ` Connection, Error Message: ${superStringify(e.message)}`,
587
+ );
588
+ }
589
+ }
590
+
591
+ async pushHueSaturationChanges(): Promise<void> {
592
+ try {
593
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} pushHueSaturationChanges`);
594
+ if (this.Hue !== this.accessory.context.Hue || this.Saturation !== this.accessory.context.Saturation) {
595
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Hue: ${superStringify(this.Hue)}`);
596
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Saturation: ${superStringify(this.Saturation)}`);
597
+
598
+ const [red, green, blue] = hs2rgb(Number(this.Hue), Number(this.Saturation));
599
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} rgb: ${superStringify([red, green, blue])}`);
600
+ // Make Push Hue/Saturation request to the API
601
+ const t = Date.now();
602
+ const nonce = 'requestID';
603
+ const data = this.platform.config.credentials?.token + t + nonce;
604
+ const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret)
605
+ .update(Buffer.from(data, 'utf-8'))
606
+ .digest();
607
+ const sign = signTerm.toString('base64');
608
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
609
+ const body = superStringify({
610
+ 'command': 'setColor',
611
+ 'parameter': `${red}:${green}:${blue}`,
612
+ 'commandType': 'command',
613
+ });
614
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API. body: ${body},`);
615
+ const options = {
616
+ hostname: HostDomain,
617
+ port: 443,
618
+ path: `${DevicePath}/${this.device.deviceId}/commands`,
619
+ method: 'POST',
620
+ headers: {
621
+ 'Authorization': this.platform.config.credentials?.token,
622
+ 'sign': sign,
623
+ 'nonce': nonce,
624
+ 't': t,
625
+ 'Content-Type': 'application/json',
626
+ 'Content-Length': body.length,
627
+ },
628
+ };
629
+ const req = https.request(options, res => {
630
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} pushHueSaturationChanges statusCode: ${res.statusCode}`);
631
+ res.on('data', d => {
632
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
633
+ });
634
+ });
635
+ req.on('error', (e: any) => {
636
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
637
+ });
638
+ req.write(body);
639
+ req.end();
640
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} pushHueSaturationChanges: ${superStringify(req)}`);
641
+ } else {
642
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No pushHueSaturationChanges. Hue: ${this.Hue}, `
643
+ + `HueCached: ${this.accessory.context.Hue}, Saturation: ${this.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`);
644
+ }
645
+ } catch (e: any) {
646
+ this.apiError(e);
647
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushHueSaturationChanges with ${this.device.connectionType}`
648
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
649
+ }
650
+ }
651
+
652
+ async pushColorTemperatureChanges(): Promise<void> {
653
+ try {
654
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} pushColorTemperatureChanges`);
655
+ if (this.ColorTemperature !== this.accessory.context.ColorTemperature) {
656
+ const kelvin = Math.round(1000000 / Number(this.ColorTemperature));
657
+ this.cacheKelvin = kelvin;
658
+
659
+ // Make Push On request to the API
660
+ const t = Date.now();
661
+ const nonce = 'requestID';
662
+ const data = this.platform.config.credentials?.token + t + nonce;
663
+ const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret)
664
+ .update(Buffer.from(data, 'utf-8'))
665
+ .digest();
666
+ const sign = signTerm.toString('base64');
667
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
668
+ const body = superStringify({
669
+ 'command': 'setColorTemperature',
670
+ 'parameter': `${kelvin}`,
671
+ 'commandType': 'command',
672
+ });
673
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API. body: ${body},`);
674
+ const options = {
675
+ hostname: HostDomain,
676
+ port: 443,
677
+ path: `${DevicePath}/${this.device.deviceId}/commands`,
678
+ method: 'POST',
679
+ headers: {
680
+ 'Authorization': this.platform.config.credentials?.token,
681
+ 'sign': sign,
682
+ 'nonce': nonce,
683
+ 't': t,
684
+ 'Content-Type': 'application/json',
685
+ 'Content-Length': body.length,
686
+ },
687
+ };
688
+ const req = https.request(options, res => {
689
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${res.statusCode}`);
690
+ res.on('data', d => {
691
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
692
+ });
693
+ });
694
+ req.on('error', (e: any) => {
695
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
696
+ });
697
+ req.write(body);
698
+ req.end();
699
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} pushColorTemperatureChanges: ${superStringify(req)}`);
700
+ this.accessory.context.ColorTemperature = this.ColorTemperature;
701
+ } else {
702
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No pushColorTemperatureChanges.` +
703
+ `ColorTemperature: ${this.ColorTemperature}, ColorTemperatureCached: ${this.accessory.context.ColorTemperature}`);
704
+ }
705
+ } catch (e: any) {
706
+ this.apiError(e);
707
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushColorTemperatureChanges with ${this.device.connectionType}`
708
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
709
+ }
710
+ }
711
+
712
+ async pushBrightnessChanges(): Promise<void> {
713
+ try {
714
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} pushBrightnessChanges`);
715
+ if (this.Brightness !== this.accessory.context.Brightness) {
716
+ // Make Push On request to the API
717
+ const t = Date.now();
718
+ const nonce = 'requestID';
719
+ const data = this.platform.config.credentials?.token + t + nonce;
720
+ const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret)
721
+ .update(Buffer.from(data, 'utf-8'))
722
+ .digest();
723
+ const sign = signTerm.toString('base64');
724
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
725
+ const body = superStringify({
726
+ 'command': 'setBrightness',
727
+ 'parameter': `${this.Brightness}`,
728
+ 'commandType': 'command',
729
+ });
730
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API. body: ${body},`);
731
+ const options = {
732
+ hostname: HostDomain,
733
+ port: 443,
734
+ path: `${DevicePath}/${this.device.deviceId}/commands`,
735
+ method: 'POST',
736
+ headers: {
737
+ 'Authorization': this.platform.config.credentials?.token,
738
+ 'sign': sign,
739
+ 'nonce': nonce,
740
+ 't': t,
741
+ 'Content-Type': 'application/json',
742
+ 'Content-Length': body.length,
743
+ },
744
+ };
745
+ const req = https.request(options, res => {
746
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} pushBrightnessChanges statusCode: ${res.statusCode}`);
747
+ res.on('data', d => {
748
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
749
+ });
750
+ });
751
+ req.on('error', (e: any) => {
752
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
753
+ });
754
+ req.write(body);
755
+ req.end();
756
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} pushBrightnessChanges: ${superStringify(req)}`);
757
+ } else {
758
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No pushBrightnessChanges.` + `Brightness: ${this.Brightness}, `
759
+ +`BrightnessCached: ${this.accessory.context.Brightness}`);
760
+ }
761
+ } catch (e: any) {
762
+ this.apiError(e);
763
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushBrightnessChanges with ${this.device.connectionType}`
764
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
765
+ }
766
+ }
767
+
768
+ /**
769
+ * Handle requests to set the value of the "On" characteristic
770
+ */
771
+ async OnSet(value: CharacteristicValue): Promise<void> {
772
+ if (this.On === this.accessory.context.On) {
773
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set On: ${value}`);
774
+ } else {
775
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set On: ${value}`);
776
+ }
777
+
778
+ this.On = value;
779
+ this.doCeilingLightUpdate.next();
780
+ }
781
+
782
+ /**
783
+ * Handle requests to set the value of the "Brightness" characteristic
784
+ */
785
+ async BrightnessSet(value: CharacteristicValue): Promise<void> {
786
+ if (this.Brightness === this.accessory.context.Brightness) {
787
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set Brightness: ${value}`);
788
+ } else if (this.On) {
789
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set Brightness: ${value}`);
790
+ } else {
791
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Set Brightness: ${value}`);
792
+ }
793
+
794
+ this.Brightness = value;
795
+ this.doCeilingLightUpdate.next();
796
+ }
797
+
798
+ /**
799
+ * Handle requests to set the value of the "ColorTemperature" characteristic
800
+ */
801
+ async ColorTemperatureSet(value: CharacteristicValue): Promise<void> {
802
+ if (this.ColorTemperature === this.accessory.context.ColorTemperature) {
803
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set ColorTemperature: ${value}`);
804
+ } else if (this.On) {
805
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set ColorTemperature: ${value}`);
806
+ } else {
807
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Set ColorTemperature: ${value}`);
808
+ }
809
+
810
+ // Convert mired to kelvin to nearest 100 (SwitchBot seems to need this)
811
+ const kelvin = Math.round(1000000 / Number(value) / 100) * 100;
812
+
813
+ // Check and increase/decrease kelvin to range of device
814
+ const k = Math.min(Math.max(kelvin, this.minKelvin), this.maxKelvin);
815
+
816
+ if (!this.accessory.context.On || this.cacheKelvin === k) {
817
+ return;
818
+ }
819
+
820
+ // Updating the hue/sat to the corresponding values mimics native adaptive lighting
821
+ const hs = m2hs(value);
822
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.Hue, hs[0]);
823
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.Saturation, hs[1]);
824
+
825
+ this.ColorTemperature = value;
826
+ this.doCeilingLightUpdate.next();
827
+ }
828
+
829
+ /**
830
+ * Handle requests to set the value of the "Hue" characteristic
831
+ */
832
+ async HueSet(value: CharacteristicValue): Promise<void> {
833
+ if (this.Hue === this.accessory.context.Hue) {
834
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set Hue: ${value}`);
835
+ } else if (this.On) {
836
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set Hue: ${value}`);
837
+ } else {
838
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Set Hue: ${value}`);
839
+ }
840
+
841
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.ColorTemperature, 140);
842
+
843
+ this.Hue = value;
844
+ this.doCeilingLightUpdate.next();
845
+ }
846
+
847
+ /**
848
+ * Handle requests to set the value of the "Saturation" characteristic
849
+ */
850
+ async SaturationSet(value: CharacteristicValue): Promise<void> {
851
+ if (this.Saturation === this.accessory.context.Saturation) {
852
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set Saturation: ${value}`);
853
+ } else if (this.On) {
854
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set Saturation: ${value}`);
855
+ } else {
856
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Set Saturation: ${value}`);
857
+ }
858
+
859
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.ColorTemperature, 140);
860
+
861
+ this.Saturation = value;
862
+ this.doCeilingLightUpdate.next();
863
+ }
864
+
865
+ async updateHomeKitCharacteristics(): Promise<void> {
866
+ if (this.On === undefined) {
867
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} On: ${this.On}`);
868
+ } else {
869
+ this.accessory.context.On = this.On;
870
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.On, this.On);
871
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic On: ${this.On}`);
872
+ }
873
+ if (this.Brightness === undefined) {
874
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Brightness: ${this.Brightness}`);
875
+ } else {
876
+ this.accessory.context.Brightness = this.Brightness;
877
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.Brightness, this.Brightness);
878
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic Brightness: ${this.Brightness}`);
879
+ }
880
+ if (this.ColorTemperature === undefined) {
881
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} ColorTemperature: ${this.ColorTemperature}`);
882
+ } else {
883
+ this.accessory.context.ColorTemperature = this.ColorTemperature;
884
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.ColorTemperature, this.ColorTemperature);
885
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic ColorTemperature: ${this.ColorTemperature}`);
886
+ }
887
+ if (this.Hue === undefined) {
888
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Hue: ${this.Hue}`);
889
+ } else {
890
+ this.accessory.context.Hue = this.Hue;
891
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.Hue, this.Hue);
892
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic Hue: ${this.Hue}`);
893
+ }
894
+ if (this.Saturation === undefined) {
895
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Saturation: ${this.Saturation}`);
896
+ } else {
897
+ this.accessory.context.Saturation = this.Saturation;
898
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.Saturation, this.Saturation);
899
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic Saturation: ${this.Saturation}`);
900
+ }
901
+ }
902
+
903
+ async adaptiveLighting(device: device & devicesConfig): Promise<void> {
904
+ if (device.ceilinglight?.adaptiveLightingShift) {
905
+ this.adaptiveLightingShift = device.ceilinglight.adaptiveLightingShift;
906
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} adaptiveLightingShift: ${this.adaptiveLightingShift}`);
907
+ } else {
908
+ this.adaptiveLightingShift = 0;
909
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} adaptiveLightingShift: ${this.adaptiveLightingShift}`);
910
+ }
911
+ }
912
+
913
+ async getCustomBLEAddress(switchbot: any) {
914
+ if (this.device.customBLEaddress && this.deviceLogging.includes('debug')) {
915
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} customBLEaddress: ${this.device.customBLEaddress}`);
916
+ (async () => {
917
+ // Start to monitor advertisement packets
918
+ await switchbot.startScan({
919
+ model: 'u',
920
+ });
921
+ // Set an event handler
922
+ switchbot.onadvertisement = (ad: any) => {
923
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} ad: ${superStringify(ad, null, ' ')}`);
924
+ };
925
+ await switchbot.wait(10000);
926
+ // Stop to monitor
927
+ switchbot.stopScan();
928
+ })();
929
+ }
930
+ }
931
+
932
+ async BLEPushConnection() {
933
+ if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') {
934
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection to Push Changes`);
935
+ await this.openAPIpushChanges();
936
+ }
937
+ }
938
+
939
+ async BLERefreshConnection(switchbot: any): Promise<void> {
940
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`);
941
+ if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') {
942
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection to Refresh Status`);
943
+ await this.openAPIRefreshStatus();
944
+ }
945
+ }
946
+
947
+ async turnOnOff(device_list: any): Promise<any> {
948
+ return await this.retry({
949
+ max: await this.maxRetry(),
950
+ fn: () => {
951
+ if (this.On) {
952
+ return device_list[0].turnOn({ id: this.device.bleMac });
953
+ } else {
954
+ return device_list[0].turnOff({ id: this.device.bleMac });
955
+ }
956
+ },
957
+ });
958
+ }
959
+
960
+ async retry({ max, fn }: { max: number; fn: { (): any; (): Promise<any> } }): Promise<null> {
961
+ return fn().catch(async (err: any) => {
962
+ if (max === 0) {
963
+ throw err;
964
+ }
965
+ this.infoLog(err);
966
+ this.infoLog('Retrying');
967
+ await this.switchbot.wait(1000);
968
+ return this.retry({ max: max - 1, fn });
969
+ });
970
+ }
971
+
972
+ async maxRetry(): Promise<number> {
973
+ let maxRetry: number;
974
+ if (this.device.bot?.maxRetry) {
975
+ maxRetry = this.device.bot?.maxRetry;
976
+ } else {
977
+ maxRetry = 5;
978
+ }
979
+ return maxRetry;
980
+ }
981
+
982
+ minStep(device: device & devicesConfig): number {
983
+ if (device.ceilinglight?.set_minStep) {
984
+ this.set_minStep = device.ceilinglight?.set_minStep;
985
+ } else {
986
+ this.set_minStep = 1;
987
+ }
988
+ return this.set_minStep;
989
+ }
990
+
991
+ async scan(device: device & devicesConfig): Promise<void> {
992
+ if (device.scanDuration) {
993
+ this.scanDuration = this.accessory.context.scanDuration = device.scanDuration;
994
+ if (this.BLE) {
995
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config scanDuration: ${this.scanDuration}`);
996
+ }
997
+ } else {
998
+ this.scanDuration = this.accessory.context.scanDuration = 1;
999
+ if (this.BLE) {
1000
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default scanDuration: ${this.scanDuration}`);
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ async statusCode({ res }: { res: IncomingMessage }): Promise<void> {
1006
+ switch (res.statusCode) {
1007
+ case 151:
1008
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command not supported by this device type.`);
1009
+ break;
1010
+ case 152:
1011
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device not found.`);
1012
+ break;
1013
+ case 160:
1014
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command is not supported.`);
1015
+ break;
1016
+ case 161:
1017
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device is offline.`);
1018
+ this.offlineOff();
1019
+ break;
1020
+ case 171:
1021
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} is offline. Hub: ${this.device.hubDeviceId}`);
1022
+ this.offlineOff();
1023
+ break;
1024
+ case 190:
1025
+ this.errorLog(
1026
+ `${this.device.deviceType}: ${this.accessory.displayName} Device internal error due to device states not synchronized with server,` +
1027
+ ` Or command: ${superStringify(res)} format is invalid`,
1028
+ );
1029
+ break;
1030
+ case 100:
1031
+ if (this.platform.debugMode) {
1032
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Command successfully sent.`);
1033
+ }
1034
+ break;
1035
+ default:
1036
+ if (this.platform.debugMode) {
1037
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Unknown statusCode.`);
1038
+ }
1039
+ }
1040
+ }
1041
+
1042
+ async offlineOff(): Promise<void> {
1043
+ if (this.device.offline) {
1044
+ this.On = false;
1045
+ this.lightBulbService.getCharacteristic(this.platform.Characteristic.On).updateValue(this.On);
1046
+ await this.updateHomeKitCharacteristics();
1047
+ }
1048
+ }
1049
+
1050
+ apiError(e: any): void {
1051
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.On, e);
1052
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.Hue, e);
1053
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.Brightness, e);
1054
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.Saturation, e);
1055
+ this.lightBulbService.updateCharacteristic(this.platform.Characteristic.ColorTemperature, e);
1056
+ }
1057
+
1058
+ FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
1059
+ let FirmwareRevision: string;
1060
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName}`
1061
+ + ` accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
1062
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
1063
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
1064
+ if (accessory.context.FirmwareRevision) {
1065
+ FirmwareRevision = accessory.context.FirmwareRevision;
1066
+ } else if (device.firmware) {
1067
+ FirmwareRevision = device.firmware;
1068
+ } else {
1069
+ FirmwareRevision = this.platform.version;
1070
+ }
1071
+ return FirmwareRevision;
1072
+ }
1073
+
1074
+ private context() {
1075
+ if (this.On === undefined) {
1076
+ this.On = false;
1077
+ } else {
1078
+ this.On = this.accessory.context.On;
1079
+ }
1080
+ if (this.Hue === undefined) {
1081
+ this.Hue = 0;
1082
+ } else {
1083
+ this.Hue = this.accessory.context.Hue;
1084
+ }
1085
+ if (this.Brightness === undefined) {
1086
+ this.Brightness = 0;
1087
+ } else {
1088
+ this.Brightness = this.accessory.context.Brightness;
1089
+ }
1090
+ if (this.Brightness === undefined) {
1091
+ this.Saturation = 0;
1092
+ } else {
1093
+ this.Saturation = this.accessory.context.Saturation;
1094
+ }
1095
+ if (this.ColorTemperature === undefined) {
1096
+ this.ColorTemperature = 140;
1097
+ } else {
1098
+ this.ColorTemperature = this.accessory.context.ColorTemperature;
1099
+ }
1100
+ this.minKelvin = 2000;
1101
+ this.maxKelvin = 9000;
1102
+ }
1103
+
1104
+ async refreshRate(device: device & devicesConfig): Promise<void> {
1105
+ if (device.refreshRate) {
1106
+ this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate;
1107
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`);
1108
+ } else if (this.platform.config.options!.refreshRate) {
1109
+ this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate;
1110
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`);
1111
+ }
1112
+ }
1113
+
1114
+ async config(device: device & devicesConfig): Promise<void> {
1115
+ let config = {};
1116
+ if (device.ceilinglight) {
1117
+ config = device.ceilinglight;
1118
+ }
1119
+ if (device.connectionType !== undefined) {
1120
+ config['connectionType'] = device.connectionType;
1121
+ }
1122
+ if (device.external !== undefined) {
1123
+ config['external'] = device.external;
1124
+ }
1125
+ if (device.logging !== undefined) {
1126
+ config['logging'] = device.logging;
1127
+ }
1128
+ if (device.refreshRate !== undefined) {
1129
+ config['refreshRate'] = device.refreshRate;
1130
+ }
1131
+ if (device.scanDuration !== undefined) {
1132
+ config['scanDuration'] = device.scanDuration;
1133
+ }
1134
+ if (device.offline !== undefined) {
1135
+ config['offline'] = device.offline;
1136
+ }
1137
+ if (Object.entries(config).length !== 0) {
1138
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Config: ${superStringify(config)}`);
1139
+ }
1140
+ }
1141
+
1142
+ async logs(device: device & devicesConfig): Promise<void> {
1143
+ if (this.platform.debugMode) {
1144
+ this.deviceLogging = this.accessory.context.logging = 'debugMode';
1145
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`);
1146
+ } else if (device.logging) {
1147
+ this.deviceLogging = this.accessory.context.logging = device.logging;
1148
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`);
1149
+ } else if (this.platform.config.options?.logging) {
1150
+ this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging;
1151
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`);
1152
+ } else {
1153
+ this.deviceLogging = this.accessory.context.logging = 'standard';
1154
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`);
1155
+ }
1156
+ }
1157
+
1158
+ /**
1159
+ * Logging for Device
1160
+ */
1161
+ infoLog(...log: any[]): void {
1162
+ if (this.enablingDeviceLogging()) {
1163
+ this.platform.log.info(String(...log));
1164
+ }
1165
+ }
1166
+
1167
+ warnLog(...log: any[]): void {
1168
+ if (this.enablingDeviceLogging()) {
1169
+ this.platform.log.warn(String(...log));
1170
+ }
1171
+ }
1172
+
1173
+ debugWarnLog(...log: any[]): void {
1174
+ if (this.enablingDeviceLogging()) {
1175
+ if (this.deviceLogging?.includes('debug')) {
1176
+ this.platform.log.warn('[DEBUG]', String(...log));
1177
+ }
1178
+ }
1179
+ }
1180
+
1181
+ errorLog(...log: any[]): void {
1182
+ if (this.enablingDeviceLogging()) {
1183
+ this.platform.log.error(String(...log));
1184
+ }
1185
+ }
1186
+
1187
+ debugErrorLog(...log: any[]): void {
1188
+ if (this.enablingDeviceLogging()) {
1189
+ if (this.deviceLogging?.includes('debug')) {
1190
+ this.platform.log.error('[DEBUG]', String(...log));
1191
+ }
1192
+ }
1193
+ }
1194
+
1195
+ debugLog(...log: any[]): void {
1196
+ if (this.enablingDeviceLogging()) {
1197
+ if (this.deviceLogging === 'debug') {
1198
+ this.platform.log.info('[DEBUG]', String(...log));
1199
+ } else {
1200
+ this.platform.log.debug(String(...log));
1201
+ }
1202
+ }
1203
+ }
1204
+
1205
+ enablingDeviceLogging(): boolean {
1206
+ return this.deviceLogging.includes('debug') || this.deviceLogging === 'standard';
1207
+ }
1208
+ }