@switchbot/homebridge-switchbot 2.0.0-beta.19 → 2.0.0-beta.20

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 (112) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/README.md +1 -2
  3. package/config.schema.json +51 -32
  4. package/dist/device/bot.d.ts +32 -29
  5. package/dist/device/bot.d.ts.map +1 -1
  6. package/dist/device/bot.js +549 -575
  7. package/dist/device/bot.js.map +1 -1
  8. package/dist/device/colorbulb.d.ts +33 -15
  9. package/dist/device/colorbulb.d.ts.map +1 -1
  10. package/dist/device/colorbulb.js +426 -341
  11. package/dist/device/colorbulb.js.map +1 -1
  12. package/dist/device/contact.d.ts +16 -9
  13. package/dist/device/contact.d.ts.map +1 -1
  14. package/dist/device/contact.js +246 -208
  15. package/dist/device/contact.js.map +1 -1
  16. package/dist/device/curtain.d.ts +21 -18
  17. package/dist/device/curtain.d.ts.map +1 -1
  18. package/dist/device/curtain.js +449 -449
  19. package/dist/device/curtain.js.map +1 -1
  20. package/dist/device/humidifier.d.ts +22 -20
  21. package/dist/device/humidifier.d.ts.map +1 -1
  22. package/dist/device/humidifier.js +346 -324
  23. package/dist/device/humidifier.js.map +1 -1
  24. package/dist/device/lock.d.ts +22 -10
  25. package/dist/device/lock.d.ts.map +1 -1
  26. package/dist/device/lock.js +257 -178
  27. package/dist/device/lock.js.map +1 -1
  28. package/dist/device/meter.d.ts +21 -14
  29. package/dist/device/meter.d.ts.map +1 -1
  30. package/dist/device/meter.js +287 -239
  31. package/dist/device/meter.js.map +1 -1
  32. package/dist/device/meterplus.d.ts +23 -10
  33. package/dist/device/meterplus.d.ts.map +1 -1
  34. package/dist/device/meterplus.js +308 -190
  35. package/dist/device/meterplus.js.map +1 -1
  36. package/dist/device/motion.d.ts +17 -10
  37. package/dist/device/motion.d.ts.map +1 -1
  38. package/dist/device/motion.js +225 -193
  39. package/dist/device/motion.js.map +1 -1
  40. package/dist/device/plug.d.ts +24 -17
  41. package/dist/device/plug.d.ts.map +1 -1
  42. package/dist/device/plug.js +270 -267
  43. package/dist/device/plug.js.map +1 -1
  44. package/dist/device/striplight.d.ts +36 -14
  45. package/dist/device/striplight.d.ts.map +1 -1
  46. package/dist/device/striplight.js +447 -299
  47. package/dist/device/striplight.js.map +1 -1
  48. package/dist/irdevice/airconditioner.d.ts +7 -14
  49. package/dist/irdevice/airconditioner.d.ts.map +1 -1
  50. package/dist/irdevice/airconditioner.js +177 -169
  51. package/dist/irdevice/airconditioner.js.map +1 -1
  52. package/dist/irdevice/airpurifier.d.ts +3 -5
  53. package/dist/irdevice/airpurifier.d.ts.map +1 -1
  54. package/dist/irdevice/airpurifier.js +72 -57
  55. package/dist/irdevice/airpurifier.js.map +1 -1
  56. package/dist/irdevice/camera.d.ts +3 -4
  57. package/dist/irdevice/camera.d.ts.map +1 -1
  58. package/dist/irdevice/camera.js +47 -33
  59. package/dist/irdevice/camera.js.map +1 -1
  60. package/dist/irdevice/fan.d.ts +3 -4
  61. package/dist/irdevice/fan.d.ts.map +1 -1
  62. package/dist/irdevice/fan.js +66 -48
  63. package/dist/irdevice/fan.js.map +1 -1
  64. package/dist/irdevice/light.d.ts +3 -4
  65. package/dist/irdevice/light.d.ts.map +1 -1
  66. package/dist/irdevice/light.js +48 -47
  67. package/dist/irdevice/light.js.map +1 -1
  68. package/dist/irdevice/other.d.ts +3 -4
  69. package/dist/irdevice/other.d.ts.map +1 -1
  70. package/dist/irdevice/other.js +47 -33
  71. package/dist/irdevice/other.js.map +1 -1
  72. package/dist/irdevice/tv.d.ts +10 -11
  73. package/dist/irdevice/tv.d.ts.map +1 -1
  74. package/dist/irdevice/tv.js +64 -48
  75. package/dist/irdevice/tv.js.map +1 -1
  76. package/dist/irdevice/vacuumcleaner.d.ts +7 -12
  77. package/dist/irdevice/vacuumcleaner.d.ts.map +1 -1
  78. package/dist/irdevice/vacuumcleaner.js +52 -41
  79. package/dist/irdevice/vacuumcleaner.js.map +1 -1
  80. package/dist/irdevice/waterheater.d.ts +6 -11
  81. package/dist/irdevice/waterheater.d.ts.map +1 -1
  82. package/dist/irdevice/waterheater.js +52 -42
  83. package/dist/irdevice/waterheater.js.map +1 -1
  84. package/dist/platform.d.ts +0 -4
  85. package/dist/platform.d.ts.map +1 -1
  86. package/dist/platform.js +44 -66
  87. package/dist/platform.js.map +1 -1
  88. package/dist/settings.d.ts +3 -3
  89. package/dist/settings.d.ts.map +1 -1
  90. package/package.json +7 -6
  91. package/src/device/bot.ts +551 -589
  92. package/src/device/colorbulb.ts +441 -375
  93. package/src/device/contact.ts +249 -213
  94. package/src/device/curtain.ts +452 -461
  95. package/src/device/humidifier.ts +358 -365
  96. package/src/device/lock.ts +263 -179
  97. package/src/device/meter.ts +302 -253
  98. package/src/device/meterplus.ts +319 -194
  99. package/src/device/motion.ts +230 -199
  100. package/src/device/plug.ts +273 -274
  101. package/src/device/striplight.ts +460 -316
  102. package/src/irdevice/airconditioner.ts +181 -183
  103. package/src/irdevice/airpurifier.ts +70 -60
  104. package/src/irdevice/camera.ts +46 -36
  105. package/src/irdevice/fan.ts +64 -49
  106. package/src/irdevice/light.ts +47 -51
  107. package/src/irdevice/other.ts +46 -36
  108. package/src/irdevice/tv.ts +64 -51
  109. package/src/irdevice/vacuumcleaner.ts +52 -45
  110. package/src/irdevice/waterheater.ts +50 -45
  111. package/src/platform.ts +44 -68
  112. package/src/settings.ts +3 -3
@@ -36,10 +36,10 @@ export class Curtain {
36
36
  // BLE Others
37
37
  connected?: boolean;
38
38
  switchbot!: switchbot;
39
- SwitchToOpenAPI?: boolean;
40
39
  serviceData!: serviceData;
41
40
  spaceBetweenLevels!: number;
42
41
  address!: ad['address'];
42
+ SwitchToOpenAPI?: boolean;
43
43
  calibration: serviceData['calibration'];
44
44
  battery: serviceData['battery'];
45
45
  position: serviceData['position'];
@@ -49,8 +49,10 @@ export class Curtain {
49
49
  setNewTarget!: boolean;
50
50
  setNewTargetTimer!: NodeJS.Timeout;
51
51
 
52
+ //MQTT stuff
53
+ mqttClient: MqttClient | null = null;
54
+
52
55
  // Config
53
- OpenAPI?: boolean;
54
56
  set_minStep!: number;
55
57
  updateRate!: number;
56
58
  set_minLux!: number;
@@ -65,20 +67,18 @@ export class Curtain {
65
67
  curtainUpdateInProgress!: boolean;
66
68
  doCurtainUpdate!: Subject<void>;
67
69
 
68
- //MQTT stuff
69
- mqttClient: MqttClient | null = null;
70
+ // Connection
71
+ private readonly BLE = (this.device.connectionType === 'BLE' || this.device.connectionType === 'Both');
72
+ private readonly OpenAPI = (this.device.connectionType === 'OpenAPI' || this.device.connectionType === 'Both');
70
73
 
71
74
  constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: device & devicesConfig) {
72
75
  // default placeholders
73
76
  this.logs(device);
74
77
  this.refreshRate(device);
75
- this.openAPI();
76
78
  this.scan(device);
77
79
  this.config(device);
78
80
  this.setupMqtt(device);
79
- this.CurrentPosition = 0;
80
- this.TargetPosition = 0;
81
- this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
81
+ this.context();
82
82
 
83
83
  // this is subject we use to track when we need to POST changes to the SwitchBot API
84
84
  this.doCurtainUpdate = new Subject();
@@ -155,11 +155,11 @@ export class Curtain {
155
155
  }
156
156
 
157
157
  // Battery Service
158
- if (!device.ble) {
158
+ if (!this.BLE) {
159
159
  this.debugLog(`${this.device.deviceType}: ${accessory.displayName} Removing Battery Service`);
160
160
  this.batteryService = this.accessory.getService(this.platform.Service.Battery);
161
161
  accessory.removeService(this.batteryService!);
162
- } else if (device.ble && !this.batteryService) {
162
+ } else if (this.BLE && !this.batteryService) {
163
163
  this.debugLog(`${this.device.deviceType}: ${accessory.displayName} Add Battery Service`);
164
164
  (this.batteryService = this.accessory.getService(this.platform.Service.Battery) || this.accessory.addService(this.platform.Service.Battery)),
165
165
  `${accessory.displayName} Battery`;
@@ -203,62 +203,31 @@ export class Curtain {
203
203
  try {
204
204
  await this.pushChanges();
205
205
  } catch (e: any) {
206
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges`);
207
- if (this.deviceLogging.includes('debug')) {
208
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges,`
209
- + ` Error Message: ${superStringify(e.message)}`);
210
- }
211
206
  this.apiError(e);
207
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges with ${this.device.connectionType} Connection,`
208
+ + ` Error Message: ${superStringify(e.message)}`);
212
209
  }
213
210
  this.curtainUpdateInProgress = false;
214
211
  });
215
212
  }
216
213
 
217
- /*
218
- * Publish MQTT message for topics of
219
- * 'homebridge-switchbot/curtain/xx:xx:xx:xx:xx:xx'
220
- */
221
- mqttPublish(topic: string, message: any) {
222
- const mac = this.device.deviceId
223
- ?.toLowerCase()
224
- .match(/[\s\S]{1,2}/g)
225
- ?.join(':');
226
- const options = this.device.mqttPubOptions || {};
227
- this.mqttClient?.publish(`homebridge-switchbot/curtain/${mac}/${topic}`, `${message}`, options);
228
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} MQTT message: ${topic}/${message} options:${superStringify(options)}`);
229
- }
230
-
231
- /*
232
- * Setup MQTT hadler if URL is specifed.
233
- */
234
- async setupMqtt(device: device & devicesConfig): Promise<void> {
235
- if (device.mqttURL) {
236
- try {
237
- this.mqttClient = await connectAsync(device.mqttURL, device.mqttOptions || {});
238
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} MQTT connection has been established successfully.`);
239
- this.mqttClient.on('error', (e: Error) => {
240
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Failed to publish MQTT messages. ${e}`);
241
- });
242
- } catch (e) {
243
- this.mqttClient = null;
244
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Failed to establish MQTT connection. ${e}`);
245
- }
246
- }
247
- }
248
-
249
214
  /**
250
215
  * Parse the device status from the SwitchBot api
251
216
  */
252
217
  async parseStatus(): Promise<void> {
253
- if (this.SwitchToOpenAPI || !this.device.ble) {
218
+ if (this.BLE && !this.SwitchToOpenAPI) {
219
+ await this.BLEparseStatus();
220
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
221
+ this.SwitchToOpenAPI = false;
254
222
  await this.openAPIparseStatus();
255
223
  } else {
256
- await this.BLEparseStatus();
224
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
225
+ + ` ${this.device.connectionType}, parseStatus will not happen.`);
257
226
  }
258
227
  }
259
228
 
260
229
  async BLEparseStatus(): Promise<void> {
261
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE parseStatus`);
230
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEparseStatus`);
262
231
  // CurrentPosition
263
232
  this.CurrentPosition = 100 - Number(this.position);
264
233
  await this.setMinMax();
@@ -364,75 +333,73 @@ export class Curtain {
364
333
  }
365
334
 
366
335
  async openAPIparseStatus(): Promise<void> {
367
- if (this.device.ble) {
368
- this.SwitchToOpenAPI = false;
336
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIparseStatus`);
337
+ // CurrentPosition
338
+ this.CurrentPosition = 100 - Number(this.slidePosition);
339
+ await this.setMinMax();
340
+ this.debugLog(`Curtain ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition}`);
341
+ if (this.setNewTarget) {
342
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Checking Status ...`);
369
343
  }
370
- if (this.platform.config.credentials?.token) {
371
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} OpenAPI parseStatus`);
372
- // CurrentPosition
373
- this.CurrentPosition = 100 - Number(this.slidePosition);
374
- await this.setMinMax();
375
- this.debugLog(`Curtain ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition}`);
376
- if (this.setNewTarget) {
377
- this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Checking Status ...`);
378
- }
379
344
 
380
- if (this.setNewTarget && this.moving) {
381
- await this.setMinMax();
382
- if (this.TargetPosition > this.CurrentPosition) {
383
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Closing, CurrentPosition: ${this.CurrentPosition} `);
384
- this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
385
- this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
386
- this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} INCREASING PositionState: ${this.PositionState}`);
387
- } else if (this.TargetPosition < this.CurrentPosition) {
388
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Opening, CurrentPosition: ${this.CurrentPosition} `);
389
- this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
390
- this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
391
- this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} DECREASING PositionState: ${this.PositionState}`);
392
- } else {
393
- this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} Standby, CurrentPosition: ${this.CurrentPosition}`);
394
- this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
395
- this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
396
- this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} STOPPED PositionState: ${this.PositionState}`);
397
- }
345
+ if (this.setNewTarget && this.moving) {
346
+ await this.setMinMax();
347
+ if (this.TargetPosition > this.CurrentPosition) {
348
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Closing, CurrentPosition: ${this.CurrentPosition} `);
349
+ this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
350
+ this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
351
+ this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} INCREASING PositionState: ${this.PositionState}`);
352
+ } else if (this.TargetPosition < this.CurrentPosition) {
353
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Opening, CurrentPosition: ${this.CurrentPosition} `);
354
+ this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
355
+ this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
356
+ this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} DECREASING PositionState: ${this.PositionState}`);
398
357
  } else {
399
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Standby, CurrentPosition: ${this.CurrentPosition}`);
400
- this.TargetPosition = this.CurrentPosition;
358
+ this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} Standby, CurrentPosition: ${this.CurrentPosition}`);
401
359
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
402
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Stopped`);
360
+ this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
361
+ this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} STOPPED PositionState: ${this.PositionState}`);
403
362
  }
404
- this.debugLog(
405
- `${this.device.deviceType}: ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition},` +
363
+ } else {
364
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Standby, CurrentPosition: ${this.CurrentPosition}`);
365
+ this.TargetPosition = this.CurrentPosition;
366
+ this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
367
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Stopped`);
368
+ }
369
+ this.debugLog(
370
+ `${this.device.deviceType}: ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition},` +
406
371
  ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},`,
407
- );
372
+ );
408
373
 
409
- if (!this.device.curtain?.hide_lightsensor) {
410
- this.set_minLux = this.minLux();
411
- this.set_maxLux = this.maxLux();
412
- // Brightness
413
- switch (this.brightness) {
414
- case 'dim':
415
- this.CurrentAmbientLightLevel = this.set_minLux;
416
- break;
417
- case 'bright':
418
- default:
419
- this.CurrentAmbientLightLevel = this.set_maxLux;
420
- }
421
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
374
+ if (!this.device.curtain?.hide_lightsensor) {
375
+ this.set_minLux = this.minLux();
376
+ this.set_maxLux = this.maxLux();
377
+ // Brightness
378
+ switch (this.brightness) {
379
+ case 'dim':
380
+ this.CurrentAmbientLightLevel = this.set_minLux;
381
+ break;
382
+ case 'bright':
383
+ default:
384
+ this.CurrentAmbientLightLevel = this.set_maxLux;
422
385
  }
386
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
423
387
  }
424
388
  }
425
389
 
426
390
  async refreshStatus(): Promise<void> {
427
- if (this.device.ble) {
391
+ if (this.BLE) {
428
392
  await this.BLERefreshStatus();
429
- } else {
393
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
430
394
  await this.openAPIRefreshStatus();
395
+ } else {
396
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
397
+ + ` ${this.device.connectionType}, refreshStatus will not happen.`);
431
398
  }
432
399
  }
433
400
 
434
401
  async BLERefreshStatus(): Promise<void> {
435
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE refreshStatus`);
402
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLERefreshStatus`);
436
403
  const switchbot = await this.platform.connectBLE();
437
404
  // Convert to BLE Address
438
405
  this.device.bleMac = this.device
@@ -487,141 +454,93 @@ export class Curtain {
487
454
  this.parseStatus();
488
455
  this.updateHomeKitCharacteristics();
489
456
  } else {
490
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} wasn't able to establish BLE Connection`);
491
- if (this.platform.config.credentials?.token) {
492
- this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection`);
493
- this.SwitchToOpenAPI = true;
494
- await this.openAPIRefreshStatus();
495
- }
457
+ await this.BLEconnection(switchbot);
496
458
  }
497
459
  })
498
460
  .catch(async (e: any) => {
499
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed refreshStatus with BLE Connection`);
500
- if (this.deviceLogging.includes('debug')) {
501
- this.errorLog(
502
- `${this.device.deviceType}: ${this.accessory.displayName} failed refreshStatus with BLE Connection,`
503
- + ` Error Message: ${superStringify(e.message)}`,
504
- );
505
- }
506
- if (this.platform.config.credentials?.token) {
507
- this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection`);
508
- this.SwitchToOpenAPI = true;
509
- await this.openAPIRefreshStatus();
510
- }
511
461
  this.apiError(e);
462
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed BLERefreshStatus with ${this.device.connectionType}`
463
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
464
+ await this.BLEconnection(switchbot);
512
465
  });
513
466
  } else {
514
467
  await this.BLEconnection(switchbot);
515
468
  }
516
469
  }
517
470
 
518
- async getCustomBLEAddress(switchbot: any) {
519
- if (this.device.customBLEaddress && this.deviceLogging.includes('debug')) {
520
- (async () => {
521
- // Start to monitor advertisement packets
522
- await switchbot.startScan({
523
- model: 'c',
524
- });
525
- // Set an event handler
526
- switchbot.onadvertisement = (ad: any) => {
527
- this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} ad: ${superStringify(ad, null, ' ')}`);
528
- };
529
- await switchbot.wait(10000);
530
- // Stop to monitor
531
- switchbot.stopScan();
532
- })();
533
- }
534
- }
535
-
536
- async BLEconnection(switchbot: any): Promise<void> {
537
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`);
538
- if (this.platform.config.credentials?.token) {
539
- this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection`);
540
- this.SwitchToOpenAPI = true;
541
- await this.openAPIRefreshStatus();
542
- }
543
- }
544
-
545
471
  async openAPIRefreshStatus(): Promise<void> {
546
- if (this.platform.config.credentials?.token && this.OpenAPI) {
547
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} OpenAPI refreshStatus`);
548
- try {
549
- const t = Date.now();
550
- const nonce = 'requestID';
551
- const data = this.platform.config.credentials?.token + t + nonce;
552
- const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
553
- const sign = signTerm.toString('base64');
554
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
555
- const options = {
556
- hostname: HostDomain,
557
- port: 443,
558
- path: `${DevicePath}/${this.device.deviceId}/status`,
559
- method: 'GET',
560
- headers: {
561
- Authorization: this.platform.config.credentials?.token,
562
- sign: sign,
563
- nonce: nonce,
564
- t: t,
565
- 'Content-Type': 'application/json',
566
- },
567
- };
568
- const req = https.request(options, (res) => {
569
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${res.statusCode}`);
570
- let rawData = '';
571
- res.on('data', (d) => {
572
- rawData += d;
573
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
574
- });
575
-
576
- res.on('end', () => {
577
- try {
578
- this.deviceStatus = JSON.parse(rawData);
579
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} refreshStatus: ${superStringify(this.deviceStatus)}`);
580
- this.slidePosition = this.deviceStatus.body.slidePosition;
581
- this.moving = this.deviceStatus.body.moving;
582
- this.brightness = this.deviceStatus.body.brightness;
583
- this.parseStatus();
584
- this.updateHomeKitCharacteristics();
585
- } catch (e: any) {
586
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
587
- }
588
- });
472
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus`);
473
+ try {
474
+ const t = Date.now();
475
+ const nonce = 'requestID';
476
+ const data = this.platform.config.credentials?.token + t + nonce;
477
+ const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
478
+ const sign = signTerm.toString('base64');
479
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
480
+ const options = {
481
+ hostname: HostDomain,
482
+ port: 443,
483
+ path: `${DevicePath}/${this.device.deviceId}/status`,
484
+ method: 'GET',
485
+ headers: {
486
+ Authorization: this.platform.config.credentials?.token,
487
+ sign: sign,
488
+ nonce: nonce,
489
+ t: t,
490
+ 'Content-Type': 'application/json',
491
+ },
492
+ };
493
+ const req = https.request(options, (res) => {
494
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${res.statusCode}`);
495
+ let rawData = '';
496
+ res.on('data', (d) => {
497
+ rawData += d;
498
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
589
499
  });
590
- req.on('error', (e: any) => {
591
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
500
+ res.on('end', () => {
501
+ try {
502
+ this.deviceStatus = JSON.parse(rawData);
503
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} refreshStatus: ${superStringify(this.deviceStatus)}`);
504
+ this.slidePosition = this.deviceStatus.body.slidePosition;
505
+ this.moving = this.deviceStatus.body.moving;
506
+ this.brightness = this.deviceStatus.body.brightness;
507
+ this.parseStatus();
508
+ this.updateHomeKitCharacteristics();
509
+ } catch (e: any) {
510
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
511
+ }
592
512
  });
593
- req.end();
594
- } catch (e: any) {
595
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed refreshStatus with OpenAPI Connection`);
596
- if (this.deviceLogging.includes('debug')) {
597
- this.errorLog(
598
- `${this.device.deviceType}: ${this.accessory.displayName} failed refreshStatus with OpenAPI Connection,`
599
- + ` Error Message: ${superStringify(e.message)}`,
600
- );
601
- }
602
- this.apiError(e);
603
- }
604
- } else {
605
- this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} OpenAPI is disabled, commands will not be sent.`);
513
+ });
514
+ req.on('error', (e: any) => {
515
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
516
+ });
517
+ req.end();
518
+ } catch (e: any) {
519
+ this.apiError(e);
520
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed openAPIRefreshStatus with ${this.device.connectionType}`
521
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
606
522
  }
607
523
  }
608
524
 
609
525
  async pushChanges(): Promise<void> {
610
- if (this.device.ble) {
611
- await this.BLEpushChanges();
612
- } else {
526
+ if (this.BLE) {
527
+ await this.BLERefreshStatus();
528
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
613
529
  await this.openAPIpushChanges();
530
+ } else {
531
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
532
+ + ` ${this.device.connectionType}, pushChanges will not happen.`);
614
533
  }
615
534
  interval(this.updateRate * 1000)
616
- .pipe(take(2))
535
+ .pipe(take(1))
617
536
  .subscribe(async () => {
618
537
  await this.refreshStatus();
619
538
  });
620
539
  }
621
540
 
622
541
  async BLEpushChanges(): Promise<void> {
542
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEpushChanges`);
623
543
  if (this.TargetPosition !== this.CurrentPosition) {
624
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE pushChanges`);
625
544
  const switchbot = await this.platform.connectBLE();
626
545
  // Convert to BLE Address
627
546
  this.device.bleMac = this.device
@@ -629,23 +548,7 @@ export class Curtain {
629
548
  .join(':')
630
549
  .toLowerCase();
631
550
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
632
- if (this.TargetPosition > 50) {
633
- if (this.device.curtain?.setOpenMode === '1') {
634
- this.setPositionMode = 1;
635
- this.Mode = 'Silent Mode';
636
- } else {
637
- this.setPositionMode = 0;
638
- this.Mode = 'Performance Mode';
639
- }
640
- } else {
641
- if (this.device.curtain?.setCloseMode === '1') {
642
- this.setPositionMode = 1;
643
- this.Mode = 'Silent Mode';
644
- } else {
645
- this.setPositionMode = 0;
646
- this.Mode = 'Performance Mode';
647
- }
648
- }
551
+ this.SilentPerformance();
649
552
  const adjustedMode = this.setPositionMode || null;
650
553
  if (adjustedMode === null) {
651
554
  this.Mode = 'Default Mode';
@@ -654,7 +557,7 @@ export class Curtain {
654
557
  if (switchbot !== false) {
655
558
  switchbot
656
559
  .discover({ model: 'c', quick: true, id: this.device.bleMac })
657
- .then((device_list) => {
560
+ .then((device_list: any) => {
658
561
  this.infoLog(`${this.accessory.displayName} Target Position: ${this.TargetPosition}`);
659
562
  return device_list[0].runToPos(100 - Number(this.TargetPosition), adjustedMode);
660
563
  })
@@ -662,18 +565,13 @@ export class Curtain {
662
565
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Done.`);
663
566
  })
664
567
  .catch(async (e: any) => {
665
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges with BLE Connection`);
666
- if (this.deviceLogging.includes('debug')) {
667
- this.errorLog(
668
- `${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges with BLE Connection,`
669
- + ` Error Message: ${superStringify(e.message)}`,
670
- );
671
- }
672
- if (this.platform.config.credentials?.token) {
568
+ this.apiError(e);
569
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed BLEpushChanges with ${this.device.connectionType}`
570
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
571
+ if (this.platform.config.credentials?.token && this.device.connectionType !== 'Both') {
673
572
  this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection`);
674
573
  await this.openAPIpushChanges();
675
574
  }
676
- this.apiError(e);
677
575
  });
678
576
  } else {
679
577
  this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} wasn't able to establish BLE Connection`);
@@ -683,95 +581,126 @@ export class Curtain {
683
581
  }
684
582
  }
685
583
  } else {
686
- this.debugLog(
687
- `${this.device.deviceType}: ${this.accessory.displayName} No BLE Changes, CurrentPosition & TargetPosition Are the Same.` +
584
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No BLEpushChanges, CurrentPosition & TargetPosition Are the Same.` +
688
585
  ` CurrentPosition: ${this.CurrentPosition}, TargetPosition ${this.TargetPosition}`,
689
586
  );
690
587
  }
691
588
  }
692
589
 
693
590
  async openAPIpushChanges(): Promise<void> {
694
- if (this.platform.config.credentials?.token && this.OpenAPI) {
695
- try {
696
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} OpenAPI pushChanges`);
697
- if (this.TargetPosition !== this.CurrentPosition) {
698
- // Make Push On request to the API
699
- const t = Date.now();
700
- const nonce = 'requestID';
701
- const data = this.platform.config.credentials?.token + t + nonce;
702
- const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret)
703
- .update(Buffer.from(data, 'utf-8'))
704
- .digest();
705
- const sign = signTerm.toString('base64');
706
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
707
- this.debugLog(`Pushing ${this.TargetPosition}`);
708
- const adjustedTargetPosition = 100 - Number(this.TargetPosition);
709
- if (this.TargetPosition > 50) {
710
- this.setPositionMode = this.device.curtain?.setOpenMode;
711
- } else {
712
- this.setPositionMode = this.device.curtain?.setCloseMode;
713
- }
714
- if (this.setPositionMode === '1') {
715
- this.Mode = 'Silent Mode';
716
- } else if (this.setPositionMode === '0') {
717
- this.Mode = 'Performance Mode';
718
- } else {
719
- this.Mode = 'Default Mode';
720
- }
721
- this.debugLog(`${this.accessory.displayName} Mode: ${this.Mode}`);
722
- const adjustedMode = this.setPositionMode || 'ff';
723
- const body = superStringify({
724
- 'command': 'setPosition',
725
- 'parameter': `0,${adjustedMode},${adjustedTargetPosition}`,
726
- 'commandType': 'command',
727
- });
728
- this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API. body: ${body},`);
729
- const options = {
730
- hostname: HostDomain,
731
- port: 443,
732
- path: `${DevicePath}/${this.device.deviceId}/commands`,
733
- method: 'POST',
734
- headers: {
735
- 'Authorization': this.platform.config.credentials?.token,
736
- 'sign': sign,
737
- 'nonce': nonce,
738
- 't': t,
739
- 'Content-Type': 'application/json',
740
- 'Content-Length': body.length,
741
- },
742
- };
743
- const req = https.request(options, res => {
744
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${res.statusCode}`);
745
- this.statusCode({ res });
746
- res.on('data', d => {
747
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
748
- });
749
- });
750
- req.on('error', (e: any) => {
751
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
752
- });
753
- req.write(body);
754
- req.end();
755
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIpushChanges: ${superStringify(req)}`);
591
+ try {
592
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} OpenAPI pushChanges`);
593
+ if (this.TargetPosition !== this.CurrentPosition) {
594
+ // Make Push On request to the API
595
+ const t = Date.now();
596
+ const nonce = 'requestID';
597
+ const data = this.platform.config.credentials?.token + t + nonce;
598
+ const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret)
599
+ .update(Buffer.from(data, 'utf-8'))
600
+ .digest();
601
+ const sign = signTerm.toString('base64');
602
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
603
+ this.debugLog(`Pushing ${this.TargetPosition}`);
604
+ const adjustedTargetPosition = 100 - Number(this.TargetPosition);
605
+ if (this.TargetPosition > 50) {
606
+ this.setPositionMode = this.device.curtain?.setOpenMode;
756
607
  } else {
757
- this.debugLog(
758
- `${this.device.deviceType}: ${this.accessory.displayName} No OpenAPI Changes, CurrentPosition & TargetPosition Are the Same.` +
759
- ` CurrentPosition: ${this.CurrentPosition}, TargetPosition ${this.TargetPosition}`,
760
- );
608
+ this.setPositionMode = this.device.curtain?.setCloseMode;
761
609
  }
762
- } catch (e: any) {
763
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges with OpenAPI Connection`);
764
- if (this.deviceLogging.includes('debug')) {
765
- this.errorLog(
766
- `${this.device.deviceType}: ${this.accessory.displayName} failed pushChanges with OpenAPI Connection,`
767
- + ` Error Message: ${superStringify(e.message)}`,
768
- );
610
+ if (this.setPositionMode === '1') {
611
+ this.Mode = 'Silent Mode';
612
+ } else if (this.setPositionMode === '0') {
613
+ this.Mode = 'Performance Mode';
614
+ } else {
615
+ this.Mode = 'Default Mode';
769
616
  }
770
- this.apiError(e);
617
+ this.debugLog(`${this.accessory.displayName} Mode: ${this.Mode}`);
618
+ const adjustedMode = this.setPositionMode || 'ff';
619
+ const body = superStringify({
620
+ 'command': 'setPosition',
621
+ 'parameter': `0,${adjustedMode},${adjustedTargetPosition}`,
622
+ 'commandType': 'command',
623
+ });
624
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API. body: ${body},`);
625
+ const options = {
626
+ hostname: HostDomain,
627
+ port: 443,
628
+ path: `${DevicePath}/${this.device.deviceId}/commands`,
629
+ method: 'POST',
630
+ headers: {
631
+ 'Authorization': this.platform.config.credentials?.token,
632
+ 'sign': sign,
633
+ 'nonce': nonce,
634
+ 't': t,
635
+ 'Content-Type': 'application/json',
636
+ 'Content-Length': body.length,
637
+ },
638
+ };
639
+ const req = https.request(options, res => {
640
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIpushChanges statusCode: ${res.statusCode}`);
641
+ this.statusCode({ res });
642
+ res.on('data', d => {
643
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
644
+ });
645
+ });
646
+ req.on('error', (e: any) => {
647
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
648
+ });
649
+ req.write(body);
650
+ req.end();
651
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIpushChanges: ${superStringify(req)}`);
652
+ } else {
653
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No OpenAPI Changes, CurrentPosition & TargetPosition Are the Same.`
654
+ +` CurrentPosition: ${this.CurrentPosition}, TargetPosition ${this.TargetPosition}`,
655
+ );
771
656
  }
657
+ } catch (e: any) {
658
+ this.apiError(e);
659
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed pushBrightnessChanges with ${this.device.connectionType}`
660
+ + ` Connection, Error Message: ${superStringify(e.message)}`,
661
+ );
662
+ }
663
+ }
664
+
665
+ /**
666
+ * Handle requests to set the value of the "Target Position" characteristic
667
+ */
668
+ async TargetPositionSet(value: CharacteristicValue): Promise<void> {
669
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set TargetPosition: ${value}`);
670
+
671
+ this.TargetPosition = value;
672
+ this.mqttPublish('TargetPosition', this.TargetPosition);
673
+
674
+ await this.setMinMax();
675
+ if (value > this.CurrentPosition) {
676
+ this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
677
+ this.setNewTarget = true;
678
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${value}, CurrentPosition: ${this.CurrentPosition}`);
679
+ } else if (value < this.CurrentPosition) {
680
+ this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
681
+ this.setNewTarget = true;
682
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${value}, CurrentPosition: ${this.CurrentPosition}`);
772
683
  } else {
773
- this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} OpenAPI is disabled, commands will not be sent.`);
684
+ this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
685
+ this.setNewTarget = false;
686
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${value}, CurrentPosition: ${this.CurrentPosition}`);
687
+ }
688
+ this.windowCoveringService.setCharacteristic(this.platform.Characteristic.PositionState, this.PositionState);
689
+ this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
690
+
691
+ /**
692
+ * If Curtain movement time is short, the moving flag from backend is always false.
693
+ * The minimum time depends on the network control latency.
694
+ */
695
+ clearTimeout(this.setNewTargetTimer);
696
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateRate: ${this.updateRate}`);
697
+ if (this.setNewTarget) {
698
+ this.setNewTargetTimer = setTimeout(() => {
699
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} setNewTarget ${this.setNewTarget} timeout`);
700
+ this.setNewTarget = false;
701
+ }, this.updateRate * 1000);
774
702
  }
703
+ this.doCurtainUpdate.next();
775
704
  }
776
705
 
777
706
  async updateHomeKitCharacteristics(): Promise<void> {
@@ -779,6 +708,7 @@ export class Curtain {
779
708
  if (this.CurrentPosition === undefined || Number.isNaN(this.CurrentPosition)) {
780
709
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition}`);
781
710
  } else {
711
+ this.accessory.context.CurrentPosition = this.CurrentPosition;
782
712
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.CurrentPosition, Number(this.CurrentPosition));
783
713
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic CurrentPosition: ${this.CurrentPosition}`);
784
714
  this.mqttPublish('CurrentPosition', this.CurrentPosition);
@@ -786,6 +716,7 @@ export class Curtain {
786
716
  if (this.PositionState === undefined) {
787
717
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} PositionState: ${this.PositionState}`);
788
718
  } else {
719
+ this.accessory.context.PositionState = this.PositionState;
789
720
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.PositionState, Number(this.PositionState));
790
721
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic PositionState: ${this.PositionState}`);
791
722
  this.mqttPublish('PositionState', this.PositionState);
@@ -793,6 +724,7 @@ export class Curtain {
793
724
  if (this.TargetPosition === undefined || Number.isNaN(this.TargetPosition)) {
794
725
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} TargetPosition: ${this.TargetPosition}`);
795
726
  } else {
727
+ this.accessory.context.TargetPosition = this.TargetPosition;
796
728
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.TargetPosition, Number(this.TargetPosition));
797
729
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic TargetPosition: ${this.TargetPosition}`);
798
730
  this.mqttPublish('TargetPosition', this.TargetPosition);
@@ -801,16 +733,18 @@ export class Curtain {
801
733
  if (this.CurrentAmbientLightLevel === undefined || Number.isNaN(this.CurrentAmbientLightLevel)) {
802
734
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
803
735
  } else {
736
+ this.accessory.context.CurrentAmbientLightLevel = this.CurrentAmbientLightLevel;
804
737
  this.lightSensorService?.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, this.CurrentAmbientLightLevel);
805
738
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName}`
806
739
  + ` updateCharacteristic CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
807
740
  this.mqttPublish('CurrentAmbientLightLevel', this.CurrentAmbientLightLevel);
808
741
  }
809
742
  }
810
- if (this.device.ble) {
743
+ if (this.BLE) {
811
744
  if (this.BatteryLevel === undefined) {
812
745
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}`);
813
746
  } else {
747
+ this.accessory.context.BatteryLevel = this.BatteryLevel;
814
748
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.BatteryLevel);
815
749
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`);
816
750
  this.mqttPublish('BatteryLevel', this.BatteryLevel);
@@ -818,6 +752,7 @@ export class Curtain {
818
752
  if (this.StatusLowBattery === undefined) {
819
753
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`);
820
754
  } else {
755
+ this.accessory.context.StatusLowBattery = this.StatusLowBattery;
821
756
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery);
822
757
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`);
823
758
  this.mqttPublish('StatusLowBattery', this.StatusLowBattery);
@@ -825,90 +760,83 @@ export class Curtain {
825
760
  }
826
761
  }
827
762
 
828
- async apiError(e: any): Promise<void> {
829
- this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.CurrentPosition, e);
830
- this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.PositionState, e);
831
- this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.TargetPosition, e);
832
- if (!this.device.curtain?.hide_lightsensor) {
833
- this.lightSensorService?.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, e);
834
- }
835
- if (this.device.ble) {
836
- this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, e);
837
- this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e);
838
- }
839
- //throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
763
+ /*
764
+ * Publish MQTT message for topics of
765
+ * 'homebridge-switchbot/curtain/xx:xx:xx:xx:xx:xx'
766
+ */
767
+ mqttPublish(topic: string, message: any) {
768
+ const mac = this.device.deviceId
769
+ ?.toLowerCase()
770
+ .match(/[\s\S]{1,2}/g)
771
+ ?.join(':');
772
+ const options = this.device.mqttPubOptions || {};
773
+ this.mqttClient?.publish(`homebridge-switchbot/curtain/${mac}/${topic}`, `${message}`, options);
774
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} MQTT message: ${topic}/${message} options:${superStringify(options)}`);
840
775
  }
841
776
 
842
- async statusCode({ res }: { res: IncomingMessage }): Promise<void> {
843
- switch (res.statusCode) {
844
- case 151:
845
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command not supported by this device type.`);
846
- break;
847
- case 152:
848
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device not found.`);
849
- break;
850
- case 160:
851
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command is not supported.`);
852
- break;
853
- case 161:
854
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device is offline.`);
855
- break;
856
- case 171:
857
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Hub Device is offline. Hub: ${this.device.hubDeviceId}`);
858
- break;
859
- case 190:
860
- this.errorLog(
861
- `${this.device.deviceType}: ${this.accessory.displayName} Device internal error due to device states not synchronized with server,` +
862
- ` Or command: ${superStringify(res)} format is invalid`,
863
- );
864
- break;
865
- case 100:
866
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Command successfully sent.`);
867
- break;
868
- default:
869
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Unknown statusCode.`);
777
+ /*
778
+ * Setup MQTT hadler if URL is specifed.
779
+ */
780
+ async setupMqtt(device: device & devicesConfig): Promise<void> {
781
+ if (device.mqttURL) {
782
+ try {
783
+ this.mqttClient = await connectAsync(device.mqttURL, device.mqttOptions || {});
784
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} MQTT connection has been established successfully.`);
785
+ this.mqttClient.on('error', (e: Error) => {
786
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Failed to publish MQTT messages. ${e}`);
787
+ });
788
+ } catch (e) {
789
+ this.mqttClient = null;
790
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Failed to establish MQTT connection. ${e}`);
791
+ }
870
792
  }
871
793
  }
872
794
 
873
- /**
874
- * Handle requests to set the value of the "Target Position" characteristic
875
- */
876
- async TargetPositionSet(value: CharacteristicValue): Promise<void> {
877
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} TargetPosition: ${value}`);
878
-
879
- this.TargetPosition = value;
880
- this.mqttPublish('TargetPosition', this.TargetPosition);
795
+ async getCustomBLEAddress(switchbot: any) {
796
+ if (this.device.customBLEaddress && this.deviceLogging.includes('debug')) {
797
+ (async () => {
798
+ // Start to monitor advertisement packets
799
+ await switchbot.startScan({
800
+ model: 'c',
801
+ });
802
+ // Set an event handler
803
+ switchbot.onadvertisement = (ad: any) => {
804
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} ad: ${superStringify(ad, null, ' ')}`);
805
+ };
806
+ await switchbot.wait(10000);
807
+ // Stop to monitor
808
+ switchbot.stopScan();
809
+ })();
810
+ }
811
+ }
881
812
 
882
- await this.setMinMax();
883
- if (value > this.CurrentPosition) {
884
- this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
885
- this.setNewTarget = true;
886
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${value}, CurrentPosition: ${this.CurrentPosition}`);
887
- } else if (value < this.CurrentPosition) {
888
- this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
889
- this.setNewTarget = true;
890
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${value}, CurrentPosition: ${this.CurrentPosition}`);
891
- } else {
892
- this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
893
- this.setNewTarget = false;
894
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${value}, CurrentPosition: ${this.CurrentPosition}`);
813
+ async BLEconnection(switchbot: any): Promise<void> {
814
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`);
815
+ if (this.platform.config.credentials?.token && this.device.connectionType !== 'Both') {
816
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection`);
817
+ this.SwitchToOpenAPI = true;
818
+ await this.openAPIRefreshStatus();
895
819
  }
896
- this.windowCoveringService.setCharacteristic(this.platform.Characteristic.PositionState, this.PositionState);
897
- this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
820
+ }
898
821
 
899
- /**
900
- * If Curtain movement time is short, the moving flag from backend is always false.
901
- * The minimum time depends on the network control latency.
902
- */
903
- clearTimeout(this.setNewTargetTimer);
904
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateRate: ${this.updateRate}`);
905
- if (this.setNewTarget) {
906
- this.setNewTargetTimer = setTimeout(() => {
907
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} setNewTarget ${this.setNewTarget} timeout`);
908
- this.setNewTarget = false;
909
- }, this.updateRate * 1000);
822
+ async SilentPerformance() {
823
+ if (this.TargetPosition > 50) {
824
+ if (this.device.curtain?.setOpenMode === '1') {
825
+ this.setPositionMode = 1;
826
+ this.Mode = 'Silent Mode';
827
+ } else {
828
+ this.setPositionMode = 0;
829
+ this.Mode = 'Performance Mode';
830
+ }
831
+ } else {
832
+ if (this.device.curtain?.setCloseMode === '1') {
833
+ this.setPositionMode = 1;
834
+ this.Mode = 'Silent Mode';
835
+ } else {
836
+ this.setPositionMode = 0;
837
+ this.Mode = 'Performance Mode';
838
+ }
910
839
  }
911
- this.doCurtainUpdate.next();
912
840
  }
913
841
 
914
842
  async setMinMax(): Promise<void> {
@@ -924,57 +852,31 @@ export class Curtain {
924
852
  }
925
853
  }
926
854
 
927
- async config(device: device & devicesConfig): Promise<void> {
928
- let config = {};
929
- if (device.curtain) {
930
- config = device.curtain;
931
- }
932
- if (device.ble) {
933
- config['ble'] = device.ble;
934
- }
935
- if (device.openAPI !== undefined) {
936
- config['openAPI'] = device.openAPI;
937
- }
938
- if (device.logging !== undefined) {
939
- config['logging'] = device.logging;
940
- }
941
- if (device.refreshRate !== undefined) {
942
- config['refreshRate'] = device.refreshRate;
943
- }
944
- if (device.scanDuration !== undefined) {
945
- config['scanDuration'] = device.scanDuration;
946
- }
947
- if (Object.entries(config).length !== 0) {
948
- this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Config: ${superStringify(config)}`);
855
+ minStep(device: device & devicesConfig): number {
856
+ if (device.curtain?.set_minStep) {
857
+ this.set_minStep = device.curtain?.set_minStep;
858
+ } else {
859
+ this.set_minStep = 1;
949
860
  }
861
+ return this.set_minStep;
950
862
  }
951
863
 
952
- async refreshRate(device: device & devicesConfig): Promise<void> {
953
- // refreshRate
954
- if (device.refreshRate) {
955
- this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate;
956
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`);
957
- } else if (this.platform.config.options!.refreshRate) {
958
- this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate;
959
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`);
960
- }
961
- // updateRate
962
- if (device?.curtain?.updateRate) {
963
- this.updateRate = device?.curtain?.updateRate;
964
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config Curtain refreshRate: ${this.deviceRefreshRate}`);
864
+ minLux(): number {
865
+ if (this.device.curtain?.set_minLux) {
866
+ this.set_minLux = this.device.curtain?.set_minLux;
965
867
  } else {
966
- this.updateRate = 7;
967
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default Curtain Refresh Rate.`);
868
+ this.set_minLux = 1;
968
869
  }
870
+ return this.set_minLux;
969
871
  }
970
872
 
971
- async openAPI() {
972
- if (!this.device.openAPI) {
973
- this.OpenAPI = true;
873
+ maxLux(): number {
874
+ if (this.device.curtain?.set_maxLux) {
875
+ this.set_maxLux = this.device.curtain?.set_maxLux;
974
876
  } else {
975
- this.OpenAPI = this.device.openAPI;
877
+ this.set_maxLux = 6001;
976
878
  }
977
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI: ${this.OpenAPI}`);
879
+ return this.set_maxLux;
978
880
  }
979
881
 
980
882
  async scan(device: device & devicesConfig): Promise<void> {
@@ -986,7 +888,7 @@ export class Curtain {
986
888
  } else {
987
889
  this.scanDuration = this.accessory.context.scanDuration = device.scanDuration;
988
890
  }
989
- if (device.ble) {
891
+ if (this.BLE) {
990
892
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config scanDuration: ${this.scanDuration}`);
991
893
  }
992
894
  } else {
@@ -997,26 +899,65 @@ export class Curtain {
997
899
  } else {
998
900
  this.scanDuration = this.accessory.context.scanDuration = 1;
999
901
  }
1000
- if (this.device.ble) {
902
+ if (this.BLE) {
1001
903
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default scanDuration: ${this.scanDuration}`);
1002
904
  }
1003
905
  }
1004
906
  }
1005
907
 
1006
- async logs(device: device & devicesConfig): Promise<void> {
1007
- if (this.platform.debugMode) {
1008
- this.deviceLogging = this.accessory.context.logging = 'debugMode';
1009
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`);
1010
- } else if (device.logging) {
1011
- this.deviceLogging = this.accessory.context.logging = device.logging;
1012
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`);
1013
- } else if (this.platform.config.options?.logging) {
1014
- this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging;
1015
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`);
1016
- } else {
1017
- this.deviceLogging = this.accessory.context.logging = 'standard';
1018
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`);
908
+ async statusCode({ res }: { res: IncomingMessage }): Promise<void> {
909
+ switch (res.statusCode) {
910
+ case 151:
911
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command not supported by this device type.`);
912
+ break;
913
+ case 152:
914
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device not found.`);
915
+ break;
916
+ case 160:
917
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command is not supported.`);
918
+ break;
919
+ case 161:
920
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device is offline.`);
921
+ this.offlineOff();
922
+ break;
923
+ case 171:
924
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Hub Device is offline. Hub: ${this.device.hubDeviceId}`);
925
+ this.offlineOff();
926
+ break;
927
+ case 190:
928
+ this.errorLog(
929
+ `${this.device.deviceType}: ${this.accessory.displayName} Device internal error due to device states not synchronized with server,` +
930
+ ` Or command: ${superStringify(res)} format is invalid`,
931
+ );
932
+ break;
933
+ case 100:
934
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Command successfully sent.`);
935
+ break;
936
+ default:
937
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Unknown statusCode.`);
938
+ }
939
+ }
940
+
941
+ async offlineOff(): Promise<void> {
942
+ if (this.device.offline) {
943
+ this.CurrentPosition = 100;
944
+ this.windowCoveringService.getCharacteristic(this.platform.Characteristic.On).updateValue(this.CurrentPosition);
945
+ await this.updateHomeKitCharacteristics();
946
+ }
947
+ }
948
+
949
+ async apiError(e: any): Promise<void> {
950
+ this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.CurrentPosition, e);
951
+ this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.PositionState, e);
952
+ this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.TargetPosition, e);
953
+ if (!this.device.curtain?.hide_lightsensor) {
954
+ this.lightSensorService?.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, e);
955
+ }
956
+ if (this.BLE) {
957
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, e);
958
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e);
1019
959
  }
960
+ //throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
1020
961
  }
1021
962
 
1022
963
  FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
@@ -1035,31 +976,81 @@ export class Curtain {
1035
976
  return FirmwareRevision;
1036
977
  }
1037
978
 
1038
- minStep(device: device & devicesConfig): number {
1039
- if (device.curtain?.set_minStep) {
1040
- this.set_minStep = device.curtain?.set_minStep;
979
+ async context() {
980
+ if (this.CurrentPosition === undefined) {
981
+ this.CurrentPosition = 0;
1041
982
  } else {
1042
- this.set_minStep = 1;
983
+ this.CurrentPosition = this.accessory.context.CurrentPosition;
984
+ }
985
+
986
+ if (this.TargetPosition === undefined) {
987
+ this.TargetPosition = 0;
988
+ } else {
989
+ this.TargetPosition = this.accessory.context.TargetPosition;
990
+ }
991
+
992
+ if (this.PositionState === undefined) {
993
+ this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
994
+ } else {
995
+ this.PositionState = this.accessory.context.PositionState;
1043
996
  }
1044
- return this.set_minStep;
1045
997
  }
1046
998
 
1047
- minLux(): number {
1048
- if (this.device.curtain?.set_minLux) {
1049
- this.set_minLux = this.device.curtain?.set_minLux;
999
+ async refreshRate(device: device & devicesConfig): Promise<void> {
1000
+ // refreshRate
1001
+ if (device.refreshRate) {
1002
+ this.deviceRefreshRate = this.accessory.context.refreshRate = device.refreshRate;
1003
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config refreshRate: ${this.deviceRefreshRate}`);
1004
+ } else if (this.platform.config.options!.refreshRate) {
1005
+ this.deviceRefreshRate = this.accessory.context.refreshRate = this.platform.config.options!.refreshRate;
1006
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`);
1007
+ }
1008
+ // updateRate
1009
+ if (device?.curtain?.updateRate) {
1010
+ this.updateRate = device?.curtain?.updateRate;
1011
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config Curtain refreshRate: ${this.deviceRefreshRate}`);
1050
1012
  } else {
1051
- this.set_minLux = 1;
1013
+ this.updateRate = 7;
1014
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default Curtain Refresh Rate.`);
1052
1015
  }
1053
- return this.set_minLux;
1054
1016
  }
1055
1017
 
1056
- maxLux(): number {
1057
- if (this.device.curtain?.set_maxLux) {
1058
- this.set_maxLux = this.device.curtain?.set_maxLux;
1018
+ async config(device: device & devicesConfig): Promise<void> {
1019
+ let config = {};
1020
+ if (device.curtain) {
1021
+ config = device.curtain;
1022
+ }
1023
+ if (device.connectionType !== undefined) {
1024
+ config['connectionType'] = device.connectionType;
1025
+ }
1026
+ if (device.logging !== undefined) {
1027
+ config['logging'] = device.logging;
1028
+ }
1029
+ if (device.refreshRate !== undefined) {
1030
+ config['refreshRate'] = device.refreshRate;
1031
+ }
1032
+ if (device.scanDuration !== undefined) {
1033
+ config['scanDuration'] = device.scanDuration;
1034
+ }
1035
+ if (Object.entries(config).length !== 0) {
1036
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Config: ${superStringify(config)}`);
1037
+ }
1038
+ }
1039
+
1040
+ async logs(device: device & devicesConfig): Promise<void> {
1041
+ if (this.platform.debugMode) {
1042
+ this.deviceLogging = this.accessory.context.logging = 'debugMode';
1043
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Debug Mode Logging: ${this.deviceLogging}`);
1044
+ } else if (device.logging) {
1045
+ this.deviceLogging = this.accessory.context.logging = device.logging;
1046
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config Logging: ${this.deviceLogging}`);
1047
+ } else if (this.platform.config.options?.logging) {
1048
+ this.deviceLogging = this.accessory.context.logging = this.platform.config.options?.logging;
1049
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config Logging: ${this.deviceLogging}`);
1059
1050
  } else {
1060
- this.set_maxLux = 6001;
1051
+ this.deviceLogging = this.accessory.context.logging = 'standard';
1052
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Logging Not Set, Using: ${this.deviceLogging}`);
1061
1053
  }
1062
- return this.set_maxLux;
1063
1054
  }
1064
1055
 
1065
1056
  /**