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

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 +350 -126
  7. package/dist/devices/bots.d.ts +17 -33
  8. package/dist/devices/bots.d.ts.map +1 -1
  9. package/dist/devices/bots.js +103 -152
  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} +32 -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 +168 -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 +78 -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 +35 -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 +50 -8
  76. package/dist/settings.d.ts.map +1 -1
  77. package/package.json +65 -65
  78. package/src/devices/bots.ts +126 -176
  79. package/src/devices/{bulb.ts → colorbulb.ts} +43 -51
  80. package/src/devices/contact.ts +124 -68
  81. package/src/devices/curtains.ts +189 -300
  82. package/src/devices/humidifiers.ts +93 -14
  83. package/src/devices/indoorcam.ts +10 -20
  84. package/src/devices/meters.ts +53 -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 +79 -8
  98. package/dist/devices/bulb.d.ts.map +0 -1
  99. package/dist/devices/bulb.js.map +0 -1
@@ -1,58 +1,45 @@
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,
@@ -60,11 +47,9 @@ export class Curtain {
60
47
  public device: device & devicesConfig,
61
48
  ) {
62
49
  // default placeholders
63
- this.setMinMax();
64
50
  this.CurrentPosition = 0;
65
51
  this.TargetPosition = 0;
66
52
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
67
- this.ScanDuration = this.platform.config.options!.refreshRate!;
68
53
 
69
54
  // BLE Connection
70
55
  if (device.ble) {
@@ -98,6 +83,8 @@ export class Curtain {
98
83
  accessory.getService(this.platform.Service.WindowCovering) ||
99
84
  accessory.addService(this.platform.Service.WindowCovering)), `${device.deviceName} ${device.deviceType}`;
100
85
 
86
+ this.platform.device(`[Curtain Config] disable_group: ${device.curtain?.disable_group}, refreshRate: ${device.curtain?.refreshRate},`
87
+ + ` set_max: ${device.curtain?.set_max}, set_min: ${device.curtain?.set_min}, set_minStep: ${device.curtain?.set_minStep}`);
101
88
  // To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
102
89
  // when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
103
90
  // accessory.getService('NAME') ?? accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE');
@@ -121,7 +108,7 @@ export class Curtain {
121
108
  validValueRanges: [0, 100],
122
109
  })
123
110
  .onGet(() => {
124
- return Number.isNaN(this.CurrentPosition);
111
+ return this.CurrentPosition;
125
112
  });
126
113
 
127
114
  this.service
@@ -137,15 +124,6 @@ export class Curtain {
137
124
  accessory.getService(this.platform.Service.LightSensor) ||
138
125
  accessory.addService(this.platform.Service.LightSensor)), `${device.deviceName} Light Sensor`;
139
126
  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
127
 
150
128
  // Update Homekit
151
129
  this.updateHomeKitCharacteristics();
@@ -224,10 +202,80 @@ export class Curtain {
224
202
 
225
203
  private async BLEparseStatus() {
226
204
  this.platform.device('Curtains BLE Device parseStatus');
205
+ this.CurrentPosition = 100 - Number(this.position);
206
+ this.setMinMax();
207
+ this.platform.debug(`Curtain ${this.accessory.displayName} CurrentPosition - Device is Currently: ${this.CurrentPosition}`);
208
+ if (this.setNewTarget) {
209
+ this.platform.log.info(`Checking ${this.accessory.displayName} Status ...`);
210
+ }
211
+
212
+ if (this.setNewTarget) {
213
+ if (this.TargetPosition > this.CurrentPosition) {
214
+ this.platform.debug(`Curtain ${this.accessory.displayName} - Current position: ${this.CurrentPosition} closing`);
215
+ this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
216
+ } else if (this.TargetPosition < this.CurrentPosition) {
217
+ this.platform.debug(`Curtain ${this.accessory.displayName} - Current position: ${this.CurrentPosition} opening`);
218
+ this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
219
+ } else {
220
+ this.platform.debug(`Curtain ${this.CurrentPosition} - standby`);
221
+ this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
222
+ }
223
+ } else {
224
+ this.platform.debug(`Curtain ${this.accessory.displayName} - Current position: ${this.CurrentPosition} standby`);
225
+ if (!this.setNewTarget) {
226
+ /*If Curtain calibration distance is short, there will be an error between the current percentage and the target percentage.*/
227
+ this.TargetPosition = this.CurrentPosition;
228
+ this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
229
+ }
230
+ }
231
+ // Brightness
232
+ switch (this.lightLevel) {
233
+ case 1:
234
+ this.CurrentAmbientLightLevel = 0.0001;
235
+ break;
236
+ case 2:
237
+ this.CurrentAmbientLightLevel = 100;
238
+ break;
239
+ case 3:
240
+ this.CurrentAmbientLightLevel = 200;
241
+ break;
242
+ case 4:
243
+ this.CurrentAmbientLightLevel = 4000;
244
+ break;
245
+ case 5:
246
+ this.CurrentAmbientLightLevel = 12000;
247
+ break;
248
+ case 6:
249
+ this.CurrentAmbientLightLevel = 28000;
250
+ break;
251
+ case 7:
252
+ this.CurrentAmbientLightLevel = 56000;
253
+ break;
254
+ case 8:
255
+ this.CurrentAmbientLightLevel = 75000;
256
+ break;
257
+ case 9:
258
+ this.CurrentAmbientLightLevel = 90000;
259
+ break;
260
+ case 10:
261
+ default:
262
+ this.CurrentAmbientLightLevel = 100000;
263
+ }
264
+ // Battery
265
+ this.BatteryLevel = Number(this.battery);
266
+ if (this.BatteryLevel < 10) {
267
+ this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW;
268
+ } else {
269
+ this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
270
+ }
271
+ this.platform.debug(
272
+ `Curtain ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition},`
273
+ + ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},`
274
+ + ` CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
227
275
  }
228
276
 
229
277
  private async openAPIparseStatus() {
230
- this.setMinMax();
278
+ this.platform.device('Curtains OpenAPI Device parseStatus');
231
279
  this.CurrentPosition = 100 - this.deviceStatus.body.slidePosition!;
232
280
  this.setMinMax();
233
281
  this.platform.debug(`Curtain ${this.accessory.displayName} CurrentPosition - Device is Currently: ${this.CurrentPosition}`);
@@ -260,80 +308,76 @@ export class Curtain {
260
308
  case 'dim':
261
309
  this.CurrentAmbientLightLevel = 0.0001;
262
310
  break;
311
+ case 'bright':
263
312
  default:
264
313
  this.CurrentAmbientLightLevel = 100000;
265
314
  }
266
315
  this.platform.debug(
267
- `Curtain ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition}
268
- , TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState}
269
- , CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
316
+ `Curtain ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition},`
317
+ + ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},`
318
+ + ` CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
270
319
  }
271
320
 
272
321
  async refreshStatus() {
273
322
  if (this.device.ble) {
274
323
  this.platform.device('BLE');
275
- await this.BLErefreshStatus();
324
+ await this.BLERefreshStatus();
276
325
  } else {
277
326
  this.platform.device('OpenAPI');
278
327
  await this.openAPIRefreshStatus();
279
328
  }
280
329
  }
281
330
 
282
- private async BLErefreshStatus() {
283
- this.platform.debug('Curtains BLE Device RefreshStatus');
331
+ private connectBLE() {
284
332
  // eslint-disable-next-line @typescript-eslint/no-var-requires
285
333
  const Switchbot = require('node-switchbot');
286
334
  const switchbot = new Switchbot();
287
335
  const colon = this.device.deviceId!.match(/.{1,2}/g);
288
336
  const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
289
337
  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);
338
+ this.platform.device(this.device.bleMac!);
339
+ return switchbot;
340
+ }
341
+
342
+ private async BLERefreshStatus() {
343
+ this.platform.debug('Curtains BLE Device RefreshStatus');
344
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
345
+ const switchbot = this.connectBLE();
346
+ // Start to monitor advertisement packets
347
+ switchbot.startScan({
348
+ model: 'c',
349
+ id: this.device.bleMac,
350
+ }).then(() => {
351
+ // Set an event hander
352
+ switchbot.onadvertisement = (ad: any) => {
353
+ this.serviceData = ad.serviceData;
354
+ this.platform.device(`${this.device.bleMac}: ${JSON.stringify(ad.serviceData)}`);
355
+ this.calibration = ad.serviceData.calibration;
356
+ this.battery = ad.serviceData.battery;
357
+ this.position = ad.serviceData.position;
358
+ this.lightLevel = ad.serviceData.lightLevel;
359
+ this.platform.device(`${this.accessory.displayName}, Calibration: ${ad.serviceData.calibration}, Position: ${ad.serviceData.position},`
360
+ + ` Light Level: ${ad.serviceData.lightLevel}, Battery: ${ad.serviceData.battery}`);
361
+ };
362
+ // Wait 10 seconds
363
+ return switchbot.wait(10000);
364
+ }).then(() => {
365
+ // Stop to monitor
366
+ switchbot.stopScan();
367
+ this.parseStatus();
368
+ this.updateHomeKitCharacteristics();
369
+ }).catch(async (e: any) => {
370
+ this.platform.log.error(`BLE Connection Failed: ${e.message}`);
371
+ this.platform.log.warn('Using OpenAPI Connection');
372
+ await this.openAPIRefreshStatus();
373
+ });
330
374
  }
331
375
 
332
376
  private async openAPIRefreshStatus() {
377
+ this.platform.debug('Curtains OpenAPI Device RefreshStatus');
333
378
  try {
334
379
  this.deviceStatus = (await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)).data;
335
380
  this.platform.debug(`Curtain ${this.accessory.displayName} refreshStatus: ${JSON.stringify(this.deviceStatus)}`);
336
- this.setMinMax();
337
381
  this.parseStatus();
338
382
  this.updateHomeKitCharacteristics();
339
383
  } catch (e: any) {
@@ -355,6 +399,15 @@ export class Curtain {
355
399
 
356
400
  private async BLEpushChanges() {
357
401
  this.platform.device('Curtains BLE Device pushChanges');
402
+ const switchbot = this.connectBLE();
403
+ switchbot.discover({ model: 'c', quick: true, id: this.device.bleMac }).then((device_list) => {
404
+ this.platform.log.info(`${this.accessory.displayName}, Target Position: ${this.TargetPosition}`);
405
+ return device_list[0].runToPos(100 - Number(this.TargetPosition));
406
+ }).then(() => {
407
+ this.platform.device('Done.');
408
+ }).catch((e: any) => {
409
+ this.platform.log.error(`BLE pushChanges Error Message: ${e.message}`);
410
+ });
358
411
  }
359
412
 
360
413
  private async OpenAPIpushChanges() {
@@ -380,7 +433,6 @@ export class Curtain {
380
433
  this.platform.debug(`Curtain ${this.accessory.displayName} pushchanges: ${JSON.stringify(payload)}`);
381
434
 
382
435
  // Make the API request
383
- this.platform.log.error(this.device.deviceId!);
384
436
  const push: any = (await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId!}/commands`, payload));
385
437
  this.platform.debug(`Curtain ${this.accessory.displayName} Changes pushed: ${JSON.stringify(push.data)}`);
386
438
  this.statusCode(push);
@@ -390,13 +442,12 @@ export class Curtain {
390
442
  updateHomeKitCharacteristics() {
391
443
  this.platform.debug(
392
444
  `Curtain ${this.accessory.displayName} updateHomeKitCharacteristics - ${JSON.stringify({
393
- CurrentPosition: Number.isNaN(this.CurrentPosition),
445
+ CurrentPosition: this.CurrentPosition,
394
446
  PositionState: this.PositionState,
395
- TargetPosition: Number.isNaN(this.TargetPosition),
447
+ TargetPosition: this.TargetPosition,
396
448
  CurrentAmbientLightLevel: this.CurrentAmbientLightLevel,
397
449
  })}`,
398
450
  );
399
- this.setMinMax();
400
451
  if (this.CurrentPosition === undefined) {
401
452
  this.platform.debug(`Curtain ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition}`);
402
453
  } else {
@@ -421,6 +472,20 @@ export class Curtain {
421
472
  this.lightSensorService.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, this.CurrentAmbientLightLevel);
422
473
  this.platform.device(`Curtain ${this.accessory.displayName} updateCharacteristic CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
423
474
  }
475
+ if (this.device.ble) {
476
+ if (this.BatteryLevel === undefined) {
477
+ this.platform.debug(`Curtain ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}`);
478
+ } else {
479
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.BatteryLevel);
480
+ this.platform.device(`Curtain ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`);
481
+ }
482
+ if (this.StatusLowBattery === undefined) {
483
+ this.platform.debug(`Curtain ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`);
484
+ } else {
485
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery);
486
+ this.platform.device(`Curtain ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`);
487
+ }
488
+ }
424
489
  }
425
490
 
426
491
  public apiError(e: any) {
@@ -428,9 +493,13 @@ export class Curtain {
428
493
  this.service.updateCharacteristic(this.platform.Characteristic.PositionState, e);
429
494
  this.service.updateCharacteristic(this.platform.Characteristic.TargetPosition, e);
430
495
  this.lightSensorService.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, e);
496
+ if (this.device.ble) {
497
+ this.batteryService!.updateCharacteristic(this.platform.Characteristic.BatteryLevel, e);
498
+ this.batteryService!.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e);
499
+ }
431
500
  }
432
501
 
433
- private statusCode(push: { data: { statusCode: any; }; }) {
502
+ private statusCode(push: AxiosResponse<{ statusCode: number; }>) {
434
503
  switch (push.data.statusCode) {
435
504
  case 151:
436
505
  this.platform.log.error('Command not supported by this device type.');
@@ -462,215 +531,35 @@ export class Curtain {
462
531
  * Handle requests to set the value of the "Target Position" characteristic
463
532
  */
464
533
  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
- }
534
+ this.platform.debug(`Curtain ${this.accessory.displayName} - Set TargetPosition: ${value}`);
476
535
 
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}`);
536
+ this.TargetPosition = value;
508
537
 
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;
538
+ this.setMinMax();
539
+ if (value > this.CurrentPosition) {
540
+ this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
541
+ this.setNewTarget = true;
542
+ } else if (value < this.CurrentPosition) {
543
+ this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
544
+ this.setNewTarget = true;
551
545
  } else {
552
- covertToDevicePosition = 100 - n;
546
+ this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
547
+ this.setNewTarget = false;
553
548
  }
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
- }
549
+ this.service.setCharacteristic(this.platform.Characteristic.PositionState, this.PositionState);
590
550
 
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`.
551
+ /**
552
+ * If Curtain movement time is short, the moving flag from backend is always false.
553
+ * The minimum time depends on the network control latency.
595
554
  */
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);
555
+ clearTimeout(this.setNewTargetTimer);
556
+ if (this.setNewTarget) {
557
+ this.setNewTargetTimer = setTimeout(() => {
558
+ this.platform.debug(`Curtain ${this.accessory.displayName} - setNewTarget ${this.setNewTarget} timeout`);
559
+ this.setNewTarget = false;
560
+ }, 10000);
669
561
  }
670
-
671
- this.AutoDisableFastScanTimeoutId = setTimeout(() => {
672
- this.stopFastScan();
673
- }, this.FastScanDuration);
562
+ this.doCurtainUpdate.next();
674
563
  }
675
564
 
676
565
  public setMinMax() {