@switchbot/homebridge-switchbot 0.2.0-alpha.2 → 1.0.1-beta.2

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 (99) hide show
  1. package/.github/workflows/nodejs-beta.yml +1 -1
  2. package/.github/workflows/nodejs.yml +1 -1
  3. package/.github/workflows/{prerelease.js → prerelease-beta.js} +0 -0
  4. package/CHANGELOG.md +1 -1
  5. package/README.md +9 -5
  6. package/config.schema.json +370 -142
  7. package/dist/devices/bots.d.ts +18 -34
  8. package/dist/devices/bots.d.ts.map +1 -1
  9. package/dist/devices/bots.js +166 -171
  10. package/dist/devices/bots.js.map +1 -1
  11. package/dist/devices/{bulb.d.ts → colorbulb.d.ts} +12 -19
  12. package/dist/devices/colorbulb.d.ts.map +1 -0
  13. package/dist/devices/{bulb.js → colorbulb.js} +34 -32
  14. package/dist/devices/colorbulb.js.map +1 -0
  15. package/dist/devices/contact.d.ts +18 -16
  16. package/dist/devices/contact.d.ts.map +1 -1
  17. package/dist/devices/contact.js +101 -44
  18. package/dist/devices/contact.js.map +1 -1
  19. package/dist/devices/curtains.d.ts +17 -51
  20. package/dist/devices/curtains.d.ts.map +1 -1
  21. package/dist/devices/curtains.js +170 -248
  22. package/dist/devices/curtains.js.map +1 -1
  23. package/dist/devices/humidifiers.d.ts +13 -4
  24. package/dist/devices/humidifiers.d.ts.map +1 -1
  25. package/dist/devices/humidifiers.js +81 -8
  26. package/dist/devices/humidifiers.js.map +1 -1
  27. package/dist/devices/indoorcam.d.ts +6 -13
  28. package/dist/devices/indoorcam.d.ts.map +1 -1
  29. package/dist/devices/indoorcam.js.map +1 -1
  30. package/dist/devices/meters.d.ts +12 -15
  31. package/dist/devices/meters.d.ts.map +1 -1
  32. package/dist/devices/meters.js +38 -44
  33. package/dist/devices/meters.js.map +1 -1
  34. package/dist/devices/motion.d.ts +17 -15
  35. package/dist/devices/motion.d.ts.map +1 -1
  36. package/dist/devices/motion.js +87 -40
  37. package/dist/devices/motion.js.map +1 -1
  38. package/dist/devices/plugs.d.ts +6 -13
  39. package/dist/devices/plugs.d.ts.map +1 -1
  40. package/dist/devices/plugs.js.map +1 -1
  41. package/dist/irdevices/airconditioners.d.ts +10 -10
  42. package/dist/irdevices/airconditioners.d.ts.map +1 -1
  43. package/dist/irdevices/airconditioners.js +29 -9
  44. package/dist/irdevices/airconditioners.js.map +1 -1
  45. package/dist/irdevices/airpurifiers.d.ts +11 -11
  46. package/dist/irdevices/airpurifiers.d.ts.map +1 -1
  47. package/dist/irdevices/airpurifiers.js.map +1 -1
  48. package/dist/irdevices/cameras.d.ts +3 -3
  49. package/dist/irdevices/cameras.d.ts.map +1 -1
  50. package/dist/irdevices/cameras.js.map +1 -1
  51. package/dist/irdevices/fans.d.ts +6 -6
  52. package/dist/irdevices/fans.d.ts.map +1 -1
  53. package/dist/irdevices/fans.js +10 -10
  54. package/dist/irdevices/fans.js.map +1 -1
  55. package/dist/irdevices/lights.d.ts +3 -3
  56. package/dist/irdevices/lights.d.ts.map +1 -1
  57. package/dist/irdevices/lights.js.map +1 -1
  58. package/dist/irdevices/others.d.ts +3 -3
  59. package/dist/irdevices/others.d.ts.map +1 -1
  60. package/dist/irdevices/others.js.map +1 -1
  61. package/dist/irdevices/tvs.d.ts +3 -3
  62. package/dist/irdevices/tvs.d.ts.map +1 -1
  63. package/dist/irdevices/tvs.js +7 -5
  64. package/dist/irdevices/tvs.js.map +1 -1
  65. package/dist/irdevices/vacuumcleaners.d.ts +3 -3
  66. package/dist/irdevices/vacuumcleaners.d.ts.map +1 -1
  67. package/dist/irdevices/vacuumcleaners.js.map +1 -1
  68. package/dist/irdevices/waterheaters.d.ts +3 -3
  69. package/dist/irdevices/waterheaters.d.ts.map +1 -1
  70. package/dist/irdevices/waterheaters.js.map +1 -1
  71. package/dist/platform.d.ts +3 -2
  72. package/dist/platform.d.ts.map +1 -1
  73. package/dist/platform.js +136 -46
  74. package/dist/platform.js.map +1 -1
  75. package/dist/settings.d.ts +51 -8
  76. package/dist/settings.d.ts.map +1 -1
  77. package/package.json +65 -65
  78. package/src/devices/bots.ts +188 -193
  79. package/src/devices/{bulb.ts → colorbulb.ts} +46 -51
  80. package/src/devices/contact.ts +124 -68
  81. package/src/devices/curtains.ts +192 -300
  82. package/src/devices/humidifiers.ts +97 -14
  83. package/src/devices/indoorcam.ts +10 -20
  84. package/src/devices/meters.ts +57 -69
  85. package/src/devices/motion.ts +108 -62
  86. package/src/devices/plugs.ts +8 -19
  87. package/src/irdevices/airconditioners.ts +43 -20
  88. package/src/irdevices/airpurifiers.ts +15 -11
  89. package/src/irdevices/cameras.ts +5 -3
  90. package/src/irdevices/fans.ts +22 -16
  91. package/src/irdevices/lights.ts +5 -3
  92. package/src/irdevices/others.ts +5 -3
  93. package/src/irdevices/tvs.ts +13 -7
  94. package/src/irdevices/vacuumcleaners.ts +5 -3
  95. package/src/irdevices/waterheaters.ts +5 -3
  96. package/src/platform.ts +148 -49
  97. package/src/settings.ts +80 -8
  98. package/dist/devices/bulb.d.ts.map +0 -1
  99. package/dist/devices/bulb.js.map +0 -1
@@ -1,70 +1,60 @@
1
1
  /* eslint-disable @typescript-eslint/no-var-requires */
2
- import { Service, PlatformAccessory, CharacteristicValue, MacAddress } from 'homebridge';
2
+ import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
3
3
  import { SwitchBotPlatform } from '../platform';
4
4
  import { interval, Subject } from 'rxjs';
5
5
  import { debounceTime, skipWhile, tap } from 'rxjs/operators';
6
- import { DeviceURL, device, devicesConfig } from '../settings';
6
+ import { DeviceURL, device, devicesConfig, serviceData, switchbot, deviceStatusResponse } from '../settings';
7
+ import { AxiosResponse } from 'axios';
7
8
 
8
9
  export class Curtain {
9
10
  // Services
10
11
  private service: Service;
11
12
  private lightSensorService: Service;
13
+ private batteryService?: Service;
12
14
 
13
15
  // Characteristic Values
14
16
  CurrentPosition!: CharacteristicValue;
15
17
  PositionState!: CharacteristicValue;
16
18
  TargetPosition!: CharacteristicValue;
17
19
  CurrentAmbientLightLevel!: CharacteristicValue;
20
+ BatteryLevel?: CharacteristicValue;
21
+ StatusLowBattery?: CharacteristicValue;
18
22
 
19
- // Others
20
- deviceStatus!: any;
21
- moveTimer!: NodeJS.Timeout;
22
- ScanIntervalId!: NodeJS.Timeout;
23
- setNewTargetTimer!: NodeJS.Timeout;
24
- AutoDisableFastScanTimeoutId!: NodeJS.Timeout;
23
+ // OpenAPI Others
24
+ deviceStatus!: deviceStatusResponse;
25
25
  setNewTarget!: boolean;
26
- moveTime!: number;
27
- ScanDuration: any;
28
- ReverseDir: any;
29
- FastScanEnabled!: boolean;
30
- FastScanInterval!: number;
31
- SlowScanInterval!: number;
32
- OpenCloseThreshold: any;
33
- PreviousPosition: any;
34
- Position: any;
35
- FastScanDuration: number | undefined;
26
+ setNewTargetTimer!: NodeJS.Timeout;
27
+
28
+ // BLE Others
29
+ switchbot!: switchbot;
30
+ serviceData!: serviceData;
31
+ calibration: serviceData['calibration'];
32
+ battery: serviceData['battery'];
33
+ position: serviceData['position'];
34
+ lightLevel: serviceData['lightLevel'];
35
+
36
+ // Config
36
37
  set_minStep!: number;
37
38
  refreshRate!: number;
38
- switchbot!: {
39
- discover: (
40
- arg0:
41
- {
42
- duration: any;
43
- model: string;
44
- quick: boolean;
45
- id: MacAddress;
46
- }
47
- ) => Promise<any>;
48
- wait: (
49
- arg0: number
50
- ) => any;
51
- };
52
39
 
53
40
  // Updates
54
41
  curtainUpdateInProgress!: boolean;
55
- doCurtainUpdate;
42
+ doCurtainUpdate!: Subject<void>;
56
43
 
57
44
  constructor(
58
45
  private readonly platform: SwitchBotPlatform,
59
46
  private accessory: PlatformAccessory,
60
47
  public device: device & devicesConfig,
61
48
  ) {
49
+ // Curtain Config
50
+ this.platform.device(`[Curtain Config] ble: ${device.ble}, disable_group: ${device.curtain?.disable_group},`
51
+ + ` refreshRate: ${device.curtain?.refreshRate}, set_max: ${device.curtain?.set_max}, set_min: ${device.curtain?.set_min},`
52
+ + ` set_minStep: ${device.curtain?.set_minStep}`);
53
+
62
54
  // default placeholders
63
- this.setMinMax();
64
55
  this.CurrentPosition = 0;
65
56
  this.TargetPosition = 0;
66
57
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
67
- this.ScanDuration = this.platform.config.options!.refreshRate!;
68
58
 
69
59
  // BLE Connection
70
60
  if (device.ble) {
@@ -121,7 +111,7 @@ export class Curtain {
121
111
  validValueRanges: [0, 100],
122
112
  })
123
113
  .onGet(() => {
124
- return Number.isNaN(this.CurrentPosition);
114
+ return this.CurrentPosition;
125
115
  });
126
116
 
127
117
  this.service
@@ -137,15 +127,6 @@ export class Curtain {
137
127
  accessory.getService(this.platform.Service.LightSensor) ||
138
128
  accessory.addService(this.platform.Service.LightSensor)), `${device.deviceName} Light Sensor`;
139
129
  this.lightSensorService.setCharacteristic(this.platform.Characteristic.Name, `${device.deviceName} Light Sensor`);
140
- // handle on / off events using the On characteristic
141
- this.lightSensorService.getCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel).onGet(() => {
142
- if (this.CurrentAmbientLightLevel === 'bright') {
143
- return 100000;
144
- } else {
145
- return 0.0001;
146
- }
147
- });
148
-
149
130
 
150
131
  // Update Homekit
151
132
  this.updateHomeKitCharacteristics();
@@ -224,10 +205,80 @@ export class Curtain {
224
205
 
225
206
  private async BLEparseStatus() {
226
207
  this.platform.device('Curtains BLE Device parseStatus');
208
+ this.CurrentPosition = 100 - Number(this.position);
209
+ this.setMinMax();
210
+ this.platform.debug(`Curtain ${this.accessory.displayName} CurrentPosition - Device is Currently: ${this.CurrentPosition}`);
211
+ if (this.setNewTarget) {
212
+ this.platform.log.info(`Checking ${this.accessory.displayName} Status ...`);
213
+ }
214
+
215
+ if (this.setNewTarget) {
216
+ if (this.TargetPosition > this.CurrentPosition) {
217
+ this.platform.debug(`Curtain ${this.accessory.displayName} - Current position: ${this.CurrentPosition} closing`);
218
+ this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
219
+ } else if (this.TargetPosition < this.CurrentPosition) {
220
+ this.platform.debug(`Curtain ${this.accessory.displayName} - Current position: ${this.CurrentPosition} opening`);
221
+ this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
222
+ } else {
223
+ this.platform.debug(`Curtain ${this.CurrentPosition} - standby`);
224
+ this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
225
+ }
226
+ } else {
227
+ this.platform.debug(`Curtain ${this.accessory.displayName} - Current position: ${this.CurrentPosition} standby`);
228
+ if (!this.setNewTarget) {
229
+ /*If Curtain calibration distance is short, there will be an error between the current percentage and the target percentage.*/
230
+ this.TargetPosition = this.CurrentPosition;
231
+ this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
232
+ }
233
+ }
234
+ // Brightness
235
+ switch (this.lightLevel) {
236
+ case 1:
237
+ this.CurrentAmbientLightLevel = 0.0001;
238
+ break;
239
+ case 2:
240
+ this.CurrentAmbientLightLevel = 100;
241
+ break;
242
+ case 3:
243
+ this.CurrentAmbientLightLevel = 200;
244
+ break;
245
+ case 4:
246
+ this.CurrentAmbientLightLevel = 4000;
247
+ break;
248
+ case 5:
249
+ this.CurrentAmbientLightLevel = 12000;
250
+ break;
251
+ case 6:
252
+ this.CurrentAmbientLightLevel = 28000;
253
+ break;
254
+ case 7:
255
+ this.CurrentAmbientLightLevel = 56000;
256
+ break;
257
+ case 8:
258
+ this.CurrentAmbientLightLevel = 75000;
259
+ break;
260
+ case 9:
261
+ this.CurrentAmbientLightLevel = 90000;
262
+ break;
263
+ case 10:
264
+ default:
265
+ this.CurrentAmbientLightLevel = 100000;
266
+ }
267
+ // Battery
268
+ this.BatteryLevel = Number(this.battery);
269
+ if (this.BatteryLevel < 10) {
270
+ this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW;
271
+ } else {
272
+ this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
273
+ }
274
+ this.platform.debug(
275
+ `Curtain ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition},`
276
+ + ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},`
277
+ + ` CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
227
278
  }
228
279
 
229
280
  private async openAPIparseStatus() {
230
- this.setMinMax();
281
+ this.platform.device('Curtains OpenAPI Device parseStatus');
231
282
  this.CurrentPosition = 100 - this.deviceStatus.body.slidePosition!;
232
283
  this.setMinMax();
233
284
  this.platform.debug(`Curtain ${this.accessory.displayName} CurrentPosition - Device is Currently: ${this.CurrentPosition}`);
@@ -260,80 +311,76 @@ export class Curtain {
260
311
  case 'dim':
261
312
  this.CurrentAmbientLightLevel = 0.0001;
262
313
  break;
314
+ case 'bright':
263
315
  default:
264
316
  this.CurrentAmbientLightLevel = 100000;
265
317
  }
266
318
  this.platform.debug(
267
- `Curtain ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition}
268
- , TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState}
269
- , CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
319
+ `Curtain ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition},`
320
+ + ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},`
321
+ + ` CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
270
322
  }
271
323
 
272
324
  async refreshStatus() {
273
325
  if (this.device.ble) {
274
326
  this.platform.device('BLE');
275
- await this.BLErefreshStatus();
327
+ await this.BLERefreshStatus();
276
328
  } else {
277
329
  this.platform.device('OpenAPI');
278
330
  await this.openAPIRefreshStatus();
279
331
  }
280
332
  }
281
333
 
282
- private async BLErefreshStatus() {
283
- this.platform.debug('Curtains BLE Device RefreshStatus');
334
+ private connectBLE() {
284
335
  // eslint-disable-next-line @typescript-eslint/no-var-requires
285
336
  const Switchbot = require('node-switchbot');
286
337
  const switchbot = new Switchbot();
287
338
  const colon = this.device.deviceId!.match(/.{1,2}/g);
288
339
  const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
289
340
  this.device.bleMac = bleMac.toLowerCase();
290
- this.platform.debug(this.device.bleMac!);
291
- switchbot.onadvertisement = (ad: any) => {
292
- this.platform.debug(JSON.stringify(ad, null, ' '));
293
- this.platform.device(`ad: ${JSON.stringify(ad)}`);
294
- };
295
- this.parseStatus();
296
- this.updateHomeKitCharacteristics();
297
- switchbot
298
- .startScan({
299
- id: this.device.bleMac,
300
- })
301
- .then(() => {
302
- return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
303
- })
304
- .then(() => {
305
- switchbot.stopScan();
306
- })
307
- .catch(async (error: any) => {
308
- this.platform.log.error(error);
309
- await this.openAPIRefreshStatus();
310
- });
311
- setInterval(() => {
312
- this.platform.log.info('Start scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
313
- switchbot
314
- .startScan({
315
- mode: 'T',
316
- id: bleMac,
317
- })
318
- .then(() => {
319
- return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
320
- })
321
- .then(() => {
322
- switchbot.stopScan();
323
- this.platform.log.info('Stop scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
324
- })
325
- .catch(async (error: any) => {
326
- this.platform.log.error(error);
327
- await this.openAPIRefreshStatus();
328
- });
329
- }, this.platform.config.options!.refreshRate! * 60000);
341
+ this.platform.device(this.device.bleMac!);
342
+ return switchbot;
343
+ }
344
+
345
+ private async BLERefreshStatus() {
346
+ this.platform.debug('Curtains BLE Device RefreshStatus');
347
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
348
+ const switchbot = this.connectBLE();
349
+ // Start to monitor advertisement packets
350
+ switchbot.startScan({
351
+ model: 'c',
352
+ id: this.device.bleMac,
353
+ }).then(() => {
354
+ // Set an event hander
355
+ switchbot.onadvertisement = (ad: any) => {
356
+ this.serviceData = ad.serviceData;
357
+ this.platform.device(`${this.device.bleMac}: ${JSON.stringify(ad.serviceData)}`);
358
+ this.calibration = ad.serviceData.calibration;
359
+ this.battery = ad.serviceData.battery;
360
+ this.position = ad.serviceData.position;
361
+ this.lightLevel = ad.serviceData.lightLevel;
362
+ this.platform.device(`${this.accessory.displayName}, Calibration: ${ad.serviceData.calibration}, Position: ${ad.serviceData.position},`
363
+ + ` Light Level: ${ad.serviceData.lightLevel}, Battery: ${ad.serviceData.battery}`);
364
+ };
365
+ // Wait 10 seconds
366
+ return switchbot.wait(10000);
367
+ }).then(() => {
368
+ // Stop to monitor
369
+ switchbot.stopScan();
370
+ this.parseStatus();
371
+ this.updateHomeKitCharacteristics();
372
+ }).catch(async (e: any) => {
373
+ this.platform.log.error(`BLE Connection Failed: ${e.message}`);
374
+ this.platform.log.warn('Using OpenAPI Connection');
375
+ await this.openAPIRefreshStatus();
376
+ });
330
377
  }
331
378
 
332
379
  private async openAPIRefreshStatus() {
380
+ this.platform.debug('Curtains OpenAPI Device RefreshStatus');
333
381
  try {
334
382
  this.deviceStatus = (await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)).data;
335
383
  this.platform.debug(`Curtain ${this.accessory.displayName} refreshStatus: ${JSON.stringify(this.deviceStatus)}`);
336
- this.setMinMax();
337
384
  this.parseStatus();
338
385
  this.updateHomeKitCharacteristics();
339
386
  } catch (e: any) {
@@ -355,6 +402,15 @@ export class Curtain {
355
402
 
356
403
  private async BLEpushChanges() {
357
404
  this.platform.device('Curtains BLE Device pushChanges');
405
+ const switchbot = this.connectBLE();
406
+ switchbot.discover({ model: 'c', quick: true, id: this.device.bleMac }).then((device_list) => {
407
+ this.platform.log.info(`${this.accessory.displayName}, Target Position: ${this.TargetPosition}`);
408
+ return device_list[0].runToPos(100 - Number(this.TargetPosition));
409
+ }).then(() => {
410
+ this.platform.device('Done.');
411
+ }).catch((e: any) => {
412
+ this.platform.log.error(`BLE pushChanges Error Message: ${e.message}`);
413
+ });
358
414
  }
359
415
 
360
416
  private async OpenAPIpushChanges() {
@@ -380,7 +436,6 @@ export class Curtain {
380
436
  this.platform.debug(`Curtain ${this.accessory.displayName} pushchanges: ${JSON.stringify(payload)}`);
381
437
 
382
438
  // Make the API request
383
- this.platform.log.error(this.device.deviceId!);
384
439
  const push: any = (await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId!}/commands`, payload));
385
440
  this.platform.debug(`Curtain ${this.accessory.displayName} Changes pushed: ${JSON.stringify(push.data)}`);
386
441
  this.statusCode(push);
@@ -390,13 +445,12 @@ export class Curtain {
390
445
  updateHomeKitCharacteristics() {
391
446
  this.platform.debug(
392
447
  `Curtain ${this.accessory.displayName} updateHomeKitCharacteristics - ${JSON.stringify({
393
- CurrentPosition: Number.isNaN(this.CurrentPosition),
448
+ CurrentPosition: this.CurrentPosition,
394
449
  PositionState: this.PositionState,
395
- TargetPosition: Number.isNaN(this.TargetPosition),
450
+ TargetPosition: this.TargetPosition,
396
451
  CurrentAmbientLightLevel: this.CurrentAmbientLightLevel,
397
452
  })}`,
398
453
  );
399
- this.setMinMax();
400
454
  if (this.CurrentPosition === undefined) {
401
455
  this.platform.debug(`Curtain ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition}`);
402
456
  } else {
@@ -421,6 +475,20 @@ export class Curtain {
421
475
  this.lightSensorService.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, this.CurrentAmbientLightLevel);
422
476
  this.platform.device(`Curtain ${this.accessory.displayName} updateCharacteristic CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
423
477
  }
478
+ if (this.device.ble) {
479
+ if (this.BatteryLevel === undefined) {
480
+ this.platform.debug(`Curtain ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}`);
481
+ } else {
482
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.BatteryLevel);
483
+ this.platform.device(`Curtain ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`);
484
+ }
485
+ if (this.StatusLowBattery === undefined) {
486
+ this.platform.debug(`Curtain ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`);
487
+ } else {
488
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery);
489
+ this.platform.device(`Curtain ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`);
490
+ }
491
+ }
424
492
  }
425
493
 
426
494
  public apiError(e: any) {
@@ -428,9 +496,13 @@ export class Curtain {
428
496
  this.service.updateCharacteristic(this.platform.Characteristic.PositionState, e);
429
497
  this.service.updateCharacteristic(this.platform.Characteristic.TargetPosition, e);
430
498
  this.lightSensorService.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, e);
499
+ if (this.device.ble) {
500
+ this.batteryService!.updateCharacteristic(this.platform.Characteristic.BatteryLevel, e);
501
+ this.batteryService!.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e);
502
+ }
431
503
  }
432
504
 
433
- private statusCode(push: { data: { statusCode: any; }; }) {
505
+ private statusCode(push: AxiosResponse<{ statusCode: number; }>) {
434
506
  switch (push.data.statusCode) {
435
507
  case 151:
436
508
  this.platform.log.error('Command not supported by this device type.');
@@ -462,215 +534,35 @@ export class Curtain {
462
534
  * Handle requests to set the value of the "Target Position" characteristic
463
535
  */
464
536
  TargetPositionSet(value: CharacteristicValue) {
465
- if (this.device.ble) {
466
- this.TargetPosition = value as number;
467
- this.platform.log.info('Target position of Curtain setting: ' + this.TargetPosition + '%');
468
- clearTimeout(this.moveTimer);
469
- if (this.TargetPosition > this.CurrentPosition) {
470
- this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
471
- } else if (this.TargetPosition < this.CurrentPosition) {
472
- this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
473
- } else {
474
- this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
475
- }
537
+ this.platform.debug(`Curtain ${this.accessory.displayName} - Set TargetPosition: ${value}`);
476
538
 
477
- if (this.PositionState === this.platform.Characteristic.PositionState.STOPPED) {
478
- this.service?.getCharacteristic(this.platform.Characteristic.TargetPosition).updateValue(this.TargetPosition);
479
- this.service?.getCharacteristic(this.platform.Characteristic.CurrentPosition).updateValue(this.CurrentPosition);
480
- this.service?.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
481
- } else {
482
- this.runToPosition(this.convertFromHomeKitPosition(this.TargetPosition))
483
- .then(() => {
484
- this.platform.log.info('Done.');
485
- this.platform.log.info('Target position of Curtain has been set to: ' + this.TargetPosition + '%');
486
- this.moveTimer = setTimeout(() => {
487
- // log.info("setTimeout", this.positionState.toString(), this.currentPosition.toString(), this.targetPosition.toString());
488
- this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
489
- // this.curtainService?.getCharacteristic(hap.Characteristic.TargetPosition).updateValue(this.targetPosition);
490
- this.service?.getCharacteristic(this.platform.Characteristic.CurrentPosition).updateValue(this.CurrentPosition);
491
- this.service?.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
492
- }, this.moveTime);
493
- })
494
- .catch((error: any) => {
495
- this.platform.log.error(error);
496
- this.moveTimer = setTimeout(() => {
497
- this.TargetPosition = this.CurrentPosition;
498
- this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
499
- this.service?.getCharacteristic(this.platform.Characteristic.TargetPosition).updateValue(this.TargetPosition);
500
- // this.curtainService?.getCharacteristic(hap.Characteristic.CurrentPosition).updateValue(this.currentPosition);
501
- this.service?.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
502
- }, 1000);
503
- this.platform.log.info('Target position of Curtain failed to be set to: ' + this.TargetPosition + '%');
504
- });
505
- }
506
- } else {
507
- this.platform.debug(`Curtain ${this.accessory.displayName} - Set TargetPosition: ${value}`);
539
+ this.TargetPosition = value;
508
540
 
509
- this.TargetPosition = value;
510
-
511
- if (value > this.CurrentPosition) {
512
- this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
513
- this.setNewTarget = true;
514
- this.setMinMax();
515
- } else if (value < this.CurrentPosition) {
516
- this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
517
- this.setNewTarget = true;
518
- this.setMinMax();
519
- } else {
520
- this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
521
- this.setNewTarget = false;
522
- this.setMinMax();
523
- }
524
- this.service.setCharacteristic(this.platform.Characteristic.PositionState, this.PositionState);
525
-
526
- /**
527
- * If Curtain movement time is short, the moving flag from backend is always false.
528
- * The minimum time depends on the network control latency.
529
- */
530
- clearTimeout(this.setNewTargetTimer);
531
- if (this.setNewTarget) {
532
- this.setNewTargetTimer = setTimeout(() => {
533
- this.platform.debug(`Curtain ${this.accessory.displayName} - setNewTarget ${this.setNewTarget} timeout`);
534
- this.setNewTarget = false;
535
- }, 10000);
536
- }
537
- this.doCurtainUpdate.next();
538
- }
539
- }
540
-
541
- /**
542
- * Convert to/from device/HomeKit's position, since:
543
- *
544
- * - opened is 0% in HomeKit and 100% in Curtain device.
545
- * - closed is 100% in HomeKit, 0% in Curtain device.
546
- */
547
- convertFromHomeKitPosition(n: number): number {
548
- let covertToDevicePosition: number;
549
- if (this.ReverseDir) {
550
- covertToDevicePosition = n;
541
+ this.setMinMax();
542
+ if (value > this.CurrentPosition) {
543
+ this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
544
+ this.setNewTarget = true;
545
+ } else if (value < this.CurrentPosition) {
546
+ this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
547
+ this.setNewTarget = true;
551
548
  } else {
552
- covertToDevicePosition = 100 - n;
549
+ this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
550
+ this.setNewTarget = false;
553
551
  }
554
- return covertToDevicePosition;
555
- }
556
-
557
- /**
558
- * Ask the device to start moving to the given position (which must be a device value and not an HomeKit value).
559
- * Returns a Promise that's resolved as soon as the command was sent to the device.
560
- */
561
- public async runToPosition(pos: number): Promise<void> {
562
- const SwitchBot = require('node-switchbot');
563
- const switchbot = new SwitchBot();
564
-
565
- return switchbot
566
- .discover({ duration: this.ScanDuration, model: 'c', quick: false })
567
- .then((device_list: any) => {
568
- let targetDevice: any = null;
569
-
570
- for (const device of device_list) {
571
- this.platform.log.info(device.modelName, device.address);
572
- if (device.address === this.device.bleMac!.toLowerCase()) {
573
- targetDevice = device;
574
- break;
575
- }
576
- }
577
-
578
- if (!targetDevice) {
579
- return new Promise((resolve, reject) => {
580
- reject(new Error('Curtain \'' + this.device.deviceName + '\' (' + this.device.bleMac!.toLowerCase() + '): device not found.'));
581
- });
582
- }
583
-
584
- this.startFastScan();
585
-
586
- this.platform.log.info(`Curtain ${this.device.deviceName} (${this.device.bleMac!.toLowerCase()}) is moving to ${pos}...`);
587
- return targetDevice.runToPos(pos);
588
- });
589
- }
552
+ this.service.setCharacteristic(this.platform.Characteristic.PositionState, this.PositionState);
590
553
 
591
- /**
592
- * Start a faster scan loop (using fastScanInterval). Used in calls to `runToPosition` to
593
- * report quicker on device position change. Will disable on its own as soon if the curtain's
594
- * device position does not change for more than `fastScanDuration`.
554
+ /**
555
+ * If Curtain movement time is short, the moving flag from backend is always false.
556
+ * The minimum time depends on the network control latency.
595
557
  */
596
- private startFastScan() {
597
- if (this.FastScanEnabled) {
598
- return;
599
- }
600
- this.FastScanEnabled = true;
601
- this.startScanLoop();
602
- }
603
-
604
- private stopFastScan() {
605
- if (!this.FastScanEnabled) {
606
- return;
607
- }
608
- this.FastScanEnabled = false;
609
- this.startScanLoop();
610
- }
611
-
612
- private get scanInterval(): number {
613
- return this.FastScanEnabled ? this.FastScanInterval : this.SlowScanInterval;
614
- }
615
-
616
- private startScanLoop() {
617
- if (this.ScanIntervalId !== null) {
618
- clearInterval(this.ScanIntervalId);
619
- }
620
-
621
- this.platform.log.info(`Curtain ${this.device.deviceName}: starting scan loop with interval ${this.scanInterval}`);
622
-
623
- this.ScanIntervalId = setInterval(() => {
624
- this.scan().catch((err) => {
625
- this.platform.log.error(`error while scanning for Curtain ${this.device.deviceName}: ${err}`);
626
- });
627
- }, this.scanInterval);
628
- }
629
-
630
- private scan(): Promise<void> {
631
- return new Promise((resolve, reject) => {
632
- const SwitchBot = require('node-switchbot');
633
- const switchbot = new SwitchBot();
634
- switchbot.onadvertisement = (ad: any) => {
635
- this.applyPosition(ad);
636
- };
637
- switchbot.startScan({ id: this.device.bleMac!.toLowerCase() })
638
- .then(() => {
639
- return switchbot.wait(this.ScanDuration);
640
- })
641
- .then(() => {
642
- resolve();
643
- switchbot.stopScan();
644
- })
645
- .catch((err: any) => {
646
- reject(err);
647
- });
648
- });
649
- }
650
-
651
- private applyPosition(ad: any) {
652
- let pos = ad.serviceData.position;
653
-
654
- if (pos + this.OpenCloseThreshold >= 100) {
655
- pos = 100;
656
- } else if (pos - this.OpenCloseThreshold <= 0) {
657
- pos = 0;
658
- }
659
-
660
- if (pos === this.PreviousPosition) {
661
- return;
662
- }
663
-
664
- this.PreviousPosition = this.Position;
665
- this.Position = pos;
666
-
667
- if (this.AutoDisableFastScanTimeoutId !== null) {
668
- clearTimeout(this.AutoDisableFastScanTimeoutId);
558
+ clearTimeout(this.setNewTargetTimer);
559
+ if (this.setNewTarget) {
560
+ this.setNewTargetTimer = setTimeout(() => {
561
+ this.platform.debug(`Curtain ${this.accessory.displayName} - setNewTarget ${this.setNewTarget} timeout`);
562
+ this.setNewTarget = false;
563
+ }, 10000);
669
564
  }
670
-
671
- this.AutoDisableFastScanTimeoutId = setTimeout(() => {
672
- this.stopFastScan();
673
- }, this.FastScanDuration);
565
+ this.doCurtainUpdate.next();
674
566
  }
675
567
 
676
568
  public setMinMax() {