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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.github/workflows/nodejs-beta.yml +1 -1
  2. package/.github/workflows/nodejs.yml +1 -1
  3. package/.github/workflows/{prerelease.js → prerelease-beta.js} +0 -0
  4. package/CHANGELOG.md +1 -1
  5. package/README.md +9 -5
  6. package/config.schema.json +370 -142
  7. package/dist/devices/bots.d.ts +18 -34
  8. package/dist/devices/bots.d.ts.map +1 -1
  9. package/dist/devices/bots.js +166 -171
  10. package/dist/devices/bots.js.map +1 -1
  11. package/dist/devices/{bulb.d.ts → colorbulb.d.ts} +12 -19
  12. package/dist/devices/colorbulb.d.ts.map +1 -0
  13. package/dist/devices/{bulb.js → colorbulb.js} +34 -32
  14. package/dist/devices/colorbulb.js.map +1 -0
  15. package/dist/devices/contact.d.ts +18 -16
  16. package/dist/devices/contact.d.ts.map +1 -1
  17. package/dist/devices/contact.js +101 -44
  18. package/dist/devices/contact.js.map +1 -1
  19. package/dist/devices/curtains.d.ts +17 -51
  20. package/dist/devices/curtains.d.ts.map +1 -1
  21. package/dist/devices/curtains.js +170 -248
  22. package/dist/devices/curtains.js.map +1 -1
  23. package/dist/devices/humidifiers.d.ts +13 -4
  24. package/dist/devices/humidifiers.d.ts.map +1 -1
  25. package/dist/devices/humidifiers.js +81 -8
  26. package/dist/devices/humidifiers.js.map +1 -1
  27. package/dist/devices/indoorcam.d.ts +6 -13
  28. package/dist/devices/indoorcam.d.ts.map +1 -1
  29. package/dist/devices/indoorcam.js.map +1 -1
  30. package/dist/devices/meters.d.ts +12 -15
  31. package/dist/devices/meters.d.ts.map +1 -1
  32. package/dist/devices/meters.js +38 -44
  33. package/dist/devices/meters.js.map +1 -1
  34. package/dist/devices/motion.d.ts +17 -15
  35. package/dist/devices/motion.d.ts.map +1 -1
  36. package/dist/devices/motion.js +87 -40
  37. package/dist/devices/motion.js.map +1 -1
  38. package/dist/devices/plugs.d.ts +6 -13
  39. package/dist/devices/plugs.d.ts.map +1 -1
  40. package/dist/devices/plugs.js.map +1 -1
  41. package/dist/irdevices/airconditioners.d.ts +10 -10
  42. package/dist/irdevices/airconditioners.d.ts.map +1 -1
  43. package/dist/irdevices/airconditioners.js +29 -9
  44. package/dist/irdevices/airconditioners.js.map +1 -1
  45. package/dist/irdevices/airpurifiers.d.ts +11 -11
  46. package/dist/irdevices/airpurifiers.d.ts.map +1 -1
  47. package/dist/irdevices/airpurifiers.js.map +1 -1
  48. package/dist/irdevices/cameras.d.ts +3 -3
  49. package/dist/irdevices/cameras.d.ts.map +1 -1
  50. package/dist/irdevices/cameras.js.map +1 -1
  51. package/dist/irdevices/fans.d.ts +6 -6
  52. package/dist/irdevices/fans.d.ts.map +1 -1
  53. package/dist/irdevices/fans.js +10 -10
  54. package/dist/irdevices/fans.js.map +1 -1
  55. package/dist/irdevices/lights.d.ts +3 -3
  56. package/dist/irdevices/lights.d.ts.map +1 -1
  57. package/dist/irdevices/lights.js.map +1 -1
  58. package/dist/irdevices/others.d.ts +3 -3
  59. package/dist/irdevices/others.d.ts.map +1 -1
  60. package/dist/irdevices/others.js.map +1 -1
  61. package/dist/irdevices/tvs.d.ts +3 -3
  62. package/dist/irdevices/tvs.d.ts.map +1 -1
  63. package/dist/irdevices/tvs.js +7 -5
  64. package/dist/irdevices/tvs.js.map +1 -1
  65. package/dist/irdevices/vacuumcleaners.d.ts +3 -3
  66. package/dist/irdevices/vacuumcleaners.d.ts.map +1 -1
  67. package/dist/irdevices/vacuumcleaners.js.map +1 -1
  68. package/dist/irdevices/waterheaters.d.ts +3 -3
  69. package/dist/irdevices/waterheaters.d.ts.map +1 -1
  70. package/dist/irdevices/waterheaters.js.map +1 -1
  71. package/dist/platform.d.ts +3 -2
  72. package/dist/platform.d.ts.map +1 -1
  73. package/dist/platform.js +136 -46
  74. package/dist/platform.js.map +1 -1
  75. package/dist/settings.d.ts +51 -8
  76. package/dist/settings.d.ts.map +1 -1
  77. package/package.json +65 -65
  78. package/src/devices/bots.ts +188 -193
  79. package/src/devices/{bulb.ts → colorbulb.ts} +46 -51
  80. package/src/devices/contact.ts +124 -68
  81. package/src/devices/curtains.ts +192 -300
  82. package/src/devices/humidifiers.ts +97 -14
  83. package/src/devices/indoorcam.ts +10 -20
  84. package/src/devices/meters.ts +57 -69
  85. package/src/devices/motion.ts +108 -62
  86. package/src/devices/plugs.ts +8 -19
  87. package/src/irdevices/airconditioners.ts +43 -20
  88. package/src/irdevices/airpurifiers.ts +15 -11
  89. package/src/irdevices/cameras.ts +5 -3
  90. package/src/irdevices/fans.ts +22 -16
  91. package/src/irdevices/lights.ts +5 -3
  92. package/src/irdevices/others.ts +5 -3
  93. package/src/irdevices/tvs.ts +13 -7
  94. package/src/irdevices/vacuumcleaners.ts +5 -3
  95. package/src/irdevices/waterheaters.ts +5 -3
  96. package/src/platform.ts +148 -49
  97. package/src/settings.ts +80 -8
  98. package/dist/devices/bulb.d.ts.map +0 -1
  99. package/dist/devices/bulb.js.map +0 -1
@@ -2,7 +2,8 @@ import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
2
2
  import { SwitchBotPlatform } from '../platform';
3
3
  import { interval, Subject } from 'rxjs';
4
4
  import { debounceTime, skipWhile, tap } from 'rxjs/operators';
5
- import { DeviceURL, device, devicesConfig } from '../settings';
5
+ import { DeviceURL, device, devicesConfig, serviceData, ad, deviceStatusResponse } from '../settings';
6
+ import { AxiosResponse } from 'axios';
6
7
 
7
8
  /**
8
9
  * Platform Accessory
@@ -23,19 +24,31 @@ export class Humidifier {
23
24
  Active!: CharacteristicValue;
24
25
  WaterLevel!: CharacteristicValue;
25
26
 
26
- // Others
27
- deviceStatus!: any;
27
+ // OpenAPI
28
+ deviceStatus!: deviceStatusResponse;
29
+
30
+ // BLE Others
31
+ serviceData!: serviceData;
32
+ onState!: serviceData['onState'];
33
+ autoMode!: serviceData['autoMode'];
34
+ percentage!: serviceData['percentage'];
35
+
36
+ // Config
37
+ set_minStep?: number;
28
38
 
29
39
  // Updates
30
40
  humidifierUpdateInProgress!: boolean;
31
- doHumidifierUpdate;
32
- set_minStep: number | undefined;
41
+ doHumidifierUpdate!: Subject<void>;
33
42
 
34
43
  constructor(
35
44
  private readonly platform: SwitchBotPlatform,
36
45
  private accessory: PlatformAccessory,
37
46
  public device: device & devicesConfig,
38
47
  ) {
48
+ // Humidifiers Config
49
+ this.platform.device(`[Humidifiers Config] ble: ${device.ble}, hide_temperature: ${device.humidifier?.hide_temperature},`
50
+ + ` set_minStep: ${device.humidifier?.set_minStep}`);
51
+
39
52
  // default placeholders
40
53
  this.CurrentRelativeHumidity = 0;
41
54
  this.TargetHumidifierDehumidifierState = this.platform.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER;
@@ -180,7 +193,11 @@ export class Humidifier {
180
193
  */
181
194
  parseStatus() {
182
195
  // Current Relative Humidity
183
- this.CurrentRelativeHumidity = this.deviceStatus.body.humidity!;
196
+ if (this.device.ble) {
197
+ this.CurrentRelativeHumidity = this.percentage!;
198
+ } else {
199
+ this.CurrentRelativeHumidity = this.deviceStatus.body.humidity!;
200
+ }
184
201
  this.platform.debug(`Humidifier ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`);
185
202
  this.platform.device(JSON.stringify(this.deviceStatus.body));
186
203
  // Water Level
@@ -191,12 +208,16 @@ export class Humidifier {
191
208
  }
192
209
  this.platform.debug(`Humidifier ${this.accessory.displayName} WaterLevel: ${this.WaterLevel}`);
193
210
  // Active
194
- switch (this.deviceStatus.body.power) {
195
- case 'on':
196
- this.Active = this.platform.Characteristic.Active.ACTIVE;
197
- break;
198
- default:
199
- this.Active = this.platform.Characteristic.Active.INACTIVE;
211
+ if (this.device.ble) {
212
+ this.Active = this.State();
213
+ } else {
214
+ switch (this.deviceStatus.body.power) {
215
+ case 'on':
216
+ this.Active = this.platform.Characteristic.Active.ACTIVE;
217
+ break;
218
+ default:
219
+ this.Active = this.platform.Characteristic.Active.INACTIVE;
220
+ }
200
221
  }
201
222
  this.platform.debug(`Humidifier ${this.accessory.displayName} Active: ${this.Active}`);
202
223
  // Target Humidifier Dehumidifier State
@@ -226,15 +247,77 @@ export class Humidifier {
226
247
  this.platform.debug(`Humidifier ${this.accessory.displayName} CurrentHumidifierDehumidifierState: ${this.CurrentHumidifierDehumidifierState}`);
227
248
  // Current Temperature
228
249
  if (!this.device.humidifier?.hide_temperature) {
229
- this.CurrentTemperature = this.deviceStatus.body.temperature;
250
+ this.CurrentTemperature = Number(this.deviceStatus.body.temperature);
230
251
  this.platform.debug(`Humidifier ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`);
231
252
  }
232
253
  }
233
254
 
255
+ private State(): CharacteristicValue {
256
+ if (this.onState) {
257
+ return this.platform.Characteristic.Active.ACTIVE;
258
+ } else {
259
+ return this.platform.Characteristic.Active.INACTIVE;
260
+ }
261
+ }
262
+
234
263
  /**
235
264
  * Asks the SwitchBot API for the latest device information
236
265
  */
237
266
  async refreshStatus() {
267
+ if (this.device.ble) {
268
+ this.platform.device('BLE');
269
+ await this.BLERefreshStatus();
270
+ } else {
271
+ this.platform.device('OpenAPI');
272
+ await this.openAPIRefreshStatus();
273
+ }
274
+ }
275
+
276
+ private connectBLE() {
277
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
278
+ const Switchbot = require('node-switchbot');
279
+ const switchbot = new Switchbot();
280
+ const colon = this.device.deviceId!.match(/.{1,2}/g);
281
+ const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
282
+ this.device.bleMac = bleMac.toLowerCase();
283
+ this.platform.device(this.device.bleMac!);
284
+ return switchbot;
285
+ }
286
+
287
+ private async BLERefreshStatus() {
288
+ this.platform.device('Bot BLE Device refreshStatus');
289
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
290
+ const switchbot = this.connectBLE();
291
+ // Start to monitor advertisement packets
292
+ switchbot.startScan({
293
+ model: 'e',
294
+ id: this.device.bleMac,
295
+ }).then(() => {
296
+ // Set an event hander
297
+ switchbot.onadvertisement = (ad: ad) => {
298
+ this.serviceData = ad.serviceData;
299
+ this.autoMode = ad.serviceData.autoMode;
300
+ this.onState = ad.serviceData.onState;
301
+ this.percentage = ad.serviceData.percentage;
302
+ this.platform.device(`${this.device.bleMac}: ${JSON.stringify(ad.serviceData)}`);
303
+ this.platform.device(`${this.accessory.displayName}, Model: ${ad.serviceData.model}, Model Name: ${ad.serviceData.modelName},`
304
+ + `autoMode: ${ad.serviceData.autoMode}, onState: ${ad.serviceData.onState}, percentage: ${ad.serviceData.percentage}`);
305
+ };
306
+ // Wait 10 seconds
307
+ return switchbot.wait(10000);
308
+ }).then(() => {
309
+ // Stop to monitor
310
+ switchbot.stopScan();
311
+ this.parseStatus();
312
+ this.updateHomeKitCharacteristics();
313
+ }).catch(async (e: any) => {
314
+ this.platform.log.error(`BLE Connection Failed: ${e.message}`);
315
+ this.platform.log.warn('Using OpenAPI Connection');
316
+ await this.openAPIRefreshStatus();
317
+ });
318
+ }
319
+
320
+ private async openAPIRefreshStatus() {
238
321
  try {
239
322
  this.deviceStatus = (await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)).data;
240
323
  if (this.deviceStatus.message === 'success') {
@@ -436,7 +519,7 @@ export class Humidifier {
436
519
  }
437
520
  }
438
521
 
439
- private statusCode(push: { data: { statusCode: any; }; }) {
522
+ private statusCode(push: AxiosResponse<{ statusCode: number; }>) {
440
523
  switch (push.data.statusCode) {
441
524
  case 151:
442
525
  this.platform.log.error('Command not supported by this device type.');
@@ -1,8 +1,9 @@
1
- import { Service, PlatformAccessory, CharacteristicValue, MacAddress } from 'homebridge';
1
+ import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
2
2
  import { SwitchBotPlatform } from '../platform';
3
3
  import { interval, Subject } from 'rxjs';
4
4
  import { debounceTime, skipWhile, tap } from 'rxjs/operators';
5
- import { DeviceURL, device, devicesConfig } from '../settings';
5
+ import { DeviceURL, device, devicesConfig, switchbot, deviceStatusResponse } from '../settings';
6
+ import { AxiosResponse } from 'axios';
6
7
 
7
8
  export class IndoorCam {
8
9
  // Services
@@ -12,26 +13,15 @@ export class IndoorCam {
12
13
  On!: CharacteristicValue;
13
14
  OutletInUse!: CharacteristicValue;
14
15
 
15
- // Others
16
- deviceStatus!: any;
17
- switchbot!: {
18
- discover: (
19
- arg0:
20
- {
21
- duration: any;
22
- model: string;
23
- quick: boolean;
24
- id: MacAddress;
25
- }
26
- ) => Promise<any>;
27
- wait: (
28
- arg0: number
29
- ) => any;
30
- };
16
+ // OpenAPI Others
17
+ deviceStatus!: deviceStatusResponse;
18
+
19
+ // BLE Others
20
+ switchbot!: switchbot;
31
21
 
32
22
  // Updates
33
23
  cameraUpdateInProgress!: boolean;
34
- doCameraUpdate;
24
+ doCameraUpdate!: Subject<void>;
35
25
 
36
26
  constructor(
37
27
  private readonly platform: SwitchBotPlatform,
@@ -259,7 +249,7 @@ export class IndoorCam {
259
249
  }
260
250
 
261
251
 
262
- private statusCode(push: { data: { statusCode: any; }; }) {
252
+ private statusCode(push: AxiosResponse<{ statusCode: number;}>) {
263
253
  switch (push.data.statusCode) {
264
254
  case 151:
265
255
  this.platform.log.error('Command not supported by this device type.');
@@ -1,8 +1,8 @@
1
- import { Service, PlatformAccessory, Units, CharacteristicValue, MacAddress } from 'homebridge';
1
+ import { Service, PlatformAccessory, Units, CharacteristicValue } from 'homebridge';
2
2
  import { SwitchBotPlatform } from '../platform';
3
3
  import { interval, Subject } from 'rxjs';
4
4
  import { skipWhile } from 'rxjs/operators';
5
- import { DeviceURL, device, devicesConfig } from '../settings';
5
+ import { DeviceURL, device, devicesConfig, serviceData, ad, switchbot, deviceStatusResponse } from '../settings';
6
6
 
7
7
  /**
8
8
  * Platform Accessory
@@ -24,34 +24,30 @@ export class Meter {
24
24
  Active!: CharacteristicValue;
25
25
  WaterLevel!: CharacteristicValue;
26
26
 
27
- // Others
28
- deviceStatus!: any;
29
- BLEtemperature!: number;
30
- BLEHumidity!: number;
31
- switchbot!: {
32
- discover: (
33
- arg0:
34
- {
35
- duration: any;
36
- model: string;
37
- quick: boolean;
38
- id: MacAddress;
39
- }
40
- ) => Promise<any>;
41
- wait: (
42
- arg0: number
43
- ) => any;
44
- };
27
+ // OpenAPI Others
28
+ deviceStatus!: deviceStatusResponse;
29
+
30
+ // BLE Others
31
+ switchbot!: switchbot;
32
+ serviceData!: serviceData;
33
+ battery!: serviceData['battery'];
34
+ humidity!: serviceData['humidity'];
35
+ fahrenheit!: serviceData['fahrenheit'];
36
+ temperature!: serviceData['temperature'];
45
37
 
46
38
  // Updates
47
39
  meterUpdateInProgress!: boolean;
48
- doMeterUpdate;
40
+ doMeterUpdate: Subject<void>;
49
41
 
50
42
  constructor(
51
43
  private readonly platform: SwitchBotPlatform,
52
44
  private accessory: PlatformAccessory,
53
45
  public device: device & devicesConfig,
54
46
  ) {
47
+ // Meter Config
48
+ this.platform.device(`[Meter Config] ble: ${device.ble}, unit: ${device.meter?.unit},`
49
+ + ` hide_temperature: ${device.meter?.hide_temperature}, hide_humidity: ${device.meter?.hide_humidity}`);
50
+
55
51
  // default placeholders
56
52
  this.BatteryLevel = 0;
57
53
  this.ChargingState = 2;
@@ -211,7 +207,7 @@ export class Meter {
211
207
  // Current Relative Humidity
212
208
  if (!this.device.meter?.hide_humidity) {
213
209
  if (this.device.ble) {
214
- this.CurrentRelativeHumidity = this.BLEHumidity;
210
+ this.CurrentRelativeHumidity = Number(this.humidity);
215
211
  } else {
216
212
  this.CurrentRelativeHumidity = this.deviceStatus.body.humidity!;
217
213
  }
@@ -221,14 +217,14 @@ export class Meter {
221
217
  // Current Temperature
222
218
  if (!this.device.meter?.hide_temperature) {
223
219
  if (this.device.ble) {
224
- this.CurrentTemperature = this.BLEtemperature;
220
+ this.CurrentTemperature = Number(this.temperature);
225
221
  } else {
226
222
  if (this.device.meter?.unit === 1) {
227
223
  this.CurrentTemperature = this.toFahrenheit(this.deviceStatus.body.temperature!);
228
224
  } else if (this.device.meter?.unit === 0) {
229
225
  this.CurrentTemperature = this.toCelsius(this.deviceStatus.body.temperature!);
230
226
  } else {
231
- this.CurrentTemperature = this.deviceStatus.body.temperature;
227
+ this.CurrentTemperature = Number(this.deviceStatus.body.temperature);
232
228
  }
233
229
  }
234
230
  this.platform.debug(`Meter ${this.accessory.displayName} - Temperature: ${this.CurrentTemperature}°c`);
@@ -248,8 +244,7 @@ export class Meter {
248
244
  }
249
245
  }
250
246
 
251
- private async BLErefreshStatus() {
252
- this.platform.debug('Meter BLE Device RefreshStatus');
247
+ private connectBLE() {
253
248
  // eslint-disable-next-line @typescript-eslint/no-var-requires
254
249
  const Switchbot = require('node-switchbot');
255
250
  const switchbot = new Switchbot();
@@ -257,49 +252,42 @@ export class Meter {
257
252
  const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
258
253
  this.device.bleMac = bleMac.toLowerCase();
259
254
  this.platform.device(this.device.bleMac!);
260
- switchbot.onadvertisement = (ad: any) => {
261
- this.platform.debug(JSON.stringify(ad, null, ' '));
262
- this.platform.device('ad:', JSON.stringify(ad));
263
- this.platform.device(`Temperature: ${ad.serviceData.temperature.c}`);
264
- this.platform.device(`Humidity: ${ad.serviceData.humidity}`);
265
- this.BLEtemperature = ad.serviceData.temperature.c;
266
- this.BLEHumidity = ad.serviceData.humidity;
267
- };
268
- this.parseStatus();
269
- this.updateHomeKitCharacteristics();
270
- switchbot
271
- .startScan({
272
- id: this.device.bleMac,
273
- })
274
- .then(() => {
275
- return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
276
- })
277
- .then(() => {
278
- switchbot.stopScan();
279
- })
280
- .catch(async (error: any) => {
281
- this.platform.log.error(error);
282
- await this.openAPIRefreshStatus();
283
- });
284
- setInterval(() => {
285
- this.platform.log.info('Start scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
286
- switchbot
287
- .startScan({
288
- mode: 'T',
289
- id: bleMac,
290
- })
291
- .then(() => {
292
- return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
293
- })
294
- .then(() => {
295
- switchbot.stopScan();
296
- this.platform.log.info('Stop scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
297
- })
298
- .catch(async (error: any) => {
299
- this.platform.log.error(error);
300
- await this.openAPIRefreshStatus();
301
- });
302
- }, this.platform.config.options!.refreshRate! * 60000);
255
+ return switchbot;
256
+ }
257
+
258
+ private async BLErefreshStatus() {
259
+ this.platform.debug('Meter BLE Device RefreshStatus');
260
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
261
+ const switchbot = this.connectBLE();
262
+ // Start to monitor advertisement packets
263
+ switchbot.startScan({
264
+ model: 'e',
265
+ id: this.device.bleMac,
266
+ }).then(() => {
267
+ // Set an event hander
268
+ switchbot.onadvertisement = (ad: ad) => {
269
+ this.serviceData = ad.serviceData;
270
+ this.temperature = ad.serviceData.temperature;
271
+ this.fahrenheit = ad.serviceData.fahrenheit;
272
+ this.humidity = ad.serviceData.humidity;
273
+ this.battery = ad.serviceData.battery;
274
+ this.platform.device(`${this.device.bleMac}: ${JSON.stringify(ad.serviceData)}`);
275
+ this.platform.device(`${this.accessory.displayName}, Model: ${ad.serviceData.model}, Model Name: ${ad.serviceData.modelName},`
276
+ + `Temperature: ${ad.serviceData.temperature}, Fahrenheit: ${ad.serviceData.fahrenheit}, Humidity: ${ad.serviceData.humidity}`
277
+ + `Battery: ${ad.serviceData.battery}`);
278
+ };
279
+ // Wait 10 seconds
280
+ return switchbot.wait(10000);
281
+ }).then(() => {
282
+ // Stop to monitor
283
+ switchbot.stopScan();
284
+ this.parseStatus();
285
+ this.updateHomeKitCharacteristics();
286
+ }).catch(async (e: any) => {
287
+ this.platform.log.error(`BLE Connection Failed: ${e.message}`);
288
+ this.platform.log.warn('Using OpenAPI Connection');
289
+ await this.openAPIRefreshStatus();
290
+ });
303
291
  }
304
292
 
305
293
  private async openAPIRefreshStatus() {
@@ -1,8 +1,8 @@
1
- import { Service, PlatformAccessory, CharacteristicValue, MacAddress } from 'homebridge';
1
+ import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
2
2
  import { SwitchBotPlatform } from '../platform';
3
3
  import { interval, Subject } from 'rxjs';
4
4
  import { skipWhile } from 'rxjs/operators';
5
- import { DeviceURL, device, devicesConfig } from '../settings';
5
+ import { DeviceURL, device, devicesConfig, serviceData, switchbot, deviceStatusResponse } from '../settings';
6
6
 
7
7
  /**
8
8
  * Platform Accessory
@@ -12,31 +12,28 @@ import { DeviceURL, device, devicesConfig } from '../settings';
12
12
  export class Motion {
13
13
  // Services
14
14
  private service: Service;
15
+ private lightSensorService?: Service;
16
+ private batteryService?: Service;
15
17
 
16
18
  // Characteristic Values
17
19
  MotionDetected!: CharacteristicValue;
20
+ BatteryLevel?: CharacteristicValue;
21
+ StatusLowBattery?: CharacteristicValue;
22
+ CurrentAmbientLightLevel?: CharacteristicValue;
18
23
 
19
- // Others
20
- deviceStatus!: any;
21
- BLEmotion!: boolean;
22
- switchbot!: {
23
- discover: (
24
- arg0:
25
- {
26
- duration: any;
27
- model: string;
28
- quick: boolean;
29
- id: MacAddress;
30
- }
31
- ) => Promise<any>;
32
- wait: (
33
- arg0: number
34
- ) => any;
35
- };
24
+ // OpenAPI Others
25
+ deviceStatus!: deviceStatusResponse;
26
+
27
+ // BLE Others
28
+ switchbot!: switchbot;
29
+ serviceData!: serviceData;
30
+ battery!: serviceData['battery'];
31
+ movement!: serviceData['movement'];
32
+ lightLevel!: serviceData['lightLevel'];
36
33
 
37
34
  // Updates
38
35
  motionUbpdateInProgress!: boolean;
39
- doMotionUpdate;
36
+ doMotionUpdate!: Subject<void>;
40
37
 
41
38
  constructor(
42
39
  private readonly platform: SwitchBotPlatform,
@@ -77,6 +74,21 @@ export class Motion {
77
74
  accessory.getService(this.platform.Service.MotionSensor) ||
78
75
  accessory.addService(this.platform.Service.MotionSensor)), `${accessory.displayName} Motion Sensor`;
79
76
 
77
+ if (device.ble) {
78
+ (this.lightSensorService =
79
+ accessory.getService(this.platform.Service.LightSensor) ||
80
+ accessory.addService(this.platform.Service.LightSensor)), `${accessory.displayName} Light Sensor`;
81
+
82
+ this.lightSensorService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Light Sensor`);
83
+
84
+
85
+ (this.batteryService =
86
+ accessory.getService(this.platform.Service.Battery) ||
87
+ accessory.addService(this.platform.Service.Battery)), `${accessory.displayName} Battery`;
88
+
89
+ this.batteryService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Battery`);
90
+ }
91
+
80
92
  // To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
81
93
  // when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
82
94
  // accessory.getService('NAME') ?? accessory.addService(this.platform.Service.Motion, 'NAME', 'USER_DEFINED_SUBTYPE');
@@ -116,8 +128,24 @@ export class Motion {
116
128
  }
117
129
 
118
130
  private async BLEparseStatus() {
119
- this.MotionDetected = Boolean(this.BLEmotion);
131
+ this.MotionDetected = Boolean(this.movement);
120
132
  this.platform.debug(`${this.accessory.displayName}, MotionDetected: ${this.MotionDetected}`);
133
+ // Light Level
134
+ switch (this.lightLevel) {
135
+ case 'dark':
136
+ case 0:
137
+ this.CurrentAmbientLightLevel = 0.0001;
138
+ break;
139
+ default:
140
+ this.CurrentAmbientLightLevel = 100000;
141
+ }
142
+ // Battery
143
+ this.BatteryLevel = Number(this.battery);
144
+ if (this.BatteryLevel < 10) {
145
+ this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW;
146
+ } else {
147
+ this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
148
+ }
121
149
  }
122
150
 
123
151
  private async openAPIparseStatus() {
@@ -131,15 +159,14 @@ export class Motion {
131
159
  async refreshStatus() {
132
160
  if (this.device.ble) {
133
161
  this.platform.device('BLE');
134
- await this.BLErefreshStatus();
162
+ await this.BLERefreshStatus();
135
163
  } else {
136
164
  this.platform.device('OpenAPI');
137
165
  await this.openAPIRefreshStatus();
138
166
  }
139
167
  }
140
168
 
141
- private async BLErefreshStatus() {
142
- this.platform.debug('Motion BLE Device RefreshStatus');
169
+ private connectBLE() {
143
170
  // eslint-disable-next-line @typescript-eslint/no-var-requires
144
171
  const Switchbot = require('node-switchbot');
145
172
  const switchbot = new Switchbot();
@@ -147,46 +174,40 @@ export class Motion {
147
174
  const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
148
175
  this.device.bleMac = bleMac.toLowerCase();
149
176
  this.platform.device(this.device.bleMac!);
150
- switchbot.onadvertisement = (ad: any) => {
151
- this.platform.debug(JSON.stringify(ad, null, ' '));
152
- this.platform.device('ad:', JSON.stringify(ad));
153
- this.BLEmotion = ad.serviceData.motion;
154
- };
155
- switchbot
156
- .startScan({
157
- id: this.device.bleMac,
158
- })
159
- .then(() => {
160
- return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
161
- })
162
- .then(() => {
163
- switchbot.stopScan();
164
- })
165
- .catch(async (error: any) => {
166
- this.platform.log.error(error);
167
- await this.openAPIRefreshStatus();
168
- });
169
- setInterval(() => {
170
- this.platform.log.info('Start scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
171
- switchbot
172
- .startScan({
173
- mode: 'S',
174
- id: bleMac,
175
- })
176
- .then(() => {
177
- return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
178
- })
179
- .then(() => {
180
- switchbot.stopScan();
181
- this.platform.log.info('Stop scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
182
- })
183
- .catch(async (error: any) => {
184
- this.platform.log.error(error);
185
- await this.openAPIRefreshStatus();
186
- });
177
+ return switchbot;
178
+ }
179
+
180
+ private async BLERefreshStatus() {
181
+ this.platform.debug('Motion BLE Device RefreshStatus');
182
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
183
+ const switchbot = this.connectBLE();
184
+ // Start to monitor advertisement packets
185
+ switchbot.startScan({
186
+ model: 's',
187
+ id: this.device.bleMac,
188
+ }).then(() => {
189
+ // Set an event hander
190
+ switchbot.onadvertisement = (ad: any) => {
191
+ this.serviceData = ad.serviceData;
192
+ this.platform.device(`${this.device.bleMac}: ${JSON.stringify(ad.serviceData)}`);
193
+ this.movement = ad.serviceData.movement;
194
+ this.battery = ad.serviceData.battery;
195
+ this.lightLevel = ad.serviceData.lightLevel;
196
+ this.platform.device(`${this.accessory.displayName}, Movement: ${ad.serviceData.movement}, Light Level: ${ad.serviceData.lightLevel},`
197
+ + ` Battery: ${ad.serviceData.battery}`);
198
+ };
199
+ // Wait 10 seconds
200
+ return switchbot.wait(10000);
201
+ }).then(() => {
202
+ // Stop to monitor
203
+ switchbot.stopScan();
187
204
  this.parseStatus();
188
205
  this.updateHomeKitCharacteristics();
189
- }, this.platform.config.options!.refreshRate! * 60000);
206
+ }).catch(async (e: any) => {
207
+ this.platform.log.error(`BLE Connection Failed: ${e.message}`);
208
+ this.platform.log.warn('Using OpenAPI Connection');
209
+ await this.openAPIRefreshStatus();
210
+ });
190
211
  }
191
212
 
192
213
  private async openAPIRefreshStatus() {
@@ -212,9 +233,34 @@ export class Motion {
212
233
  this.service.updateCharacteristic(this.platform.Characteristic.MotionDetected, this.MotionDetected);
213
234
  this.platform.device(`Motion ${this.accessory.displayName} updateCharacteristic MotionDetected: ${this.MotionDetected}`);
214
235
  }
236
+ if (this.device.ble) {
237
+ if (this.BatteryLevel === undefined) {
238
+ this.platform.debug(`Motion ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}`);
239
+ } else {
240
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.BatteryLevel);
241
+ this.platform.device(`Motion ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`);
242
+ }
243
+ if (this.StatusLowBattery === undefined) {
244
+ this.platform.debug(`Motion ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`);
245
+ } else {
246
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery);
247
+ this.platform.device(`Motion ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`);
248
+ }
249
+ if (this.CurrentAmbientLightLevel === undefined) {
250
+ this.platform.debug(`Motion ${this.accessory.displayName} CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
251
+ } else {
252
+ this.lightSensorService?.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, this.CurrentAmbientLightLevel);
253
+ this.platform.device(`Motion ${this.accessory.displayName} updateCharacteristic CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
254
+ }
255
+ }
215
256
  }
216
257
 
217
258
  public apiError(e: any) {
218
259
  this.service.updateCharacteristic(this.platform.Characteristic.MotionDetected, e);
260
+ if (this.device.ble) {
261
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, e);
262
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e);
263
+ this.lightSensorService?.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, e);
264
+ }
219
265
  }
220
266
  }