@switchbot/homebridge-switchbot 0.1.2-beta.7 → 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.github/ISSUE_TEMPLATE/feature-request.yml +1 -1
  2. package/.github/dependabot.yml +2 -0
  3. package/.github/workflows/codeql-analysis.yml +71 -0
  4. package/CHANGELOG.md +5 -1
  5. package/README.md +2 -1
  6. package/config.schema.json +420 -471
  7. package/dist/devices/bots.d.ts +19 -5
  8. package/dist/devices/bots.d.ts.map +1 -1
  9. package/dist/devices/bots.js +172 -148
  10. package/dist/devices/bots.js.map +1 -1
  11. package/dist/devices/bulb.d.ts +8 -6
  12. package/dist/devices/bulb.d.ts.map +1 -1
  13. package/dist/devices/bulb.js +45 -28
  14. package/dist/devices/bulb.js.map +1 -1
  15. package/dist/devices/contact.d.ts +10 -5
  16. package/dist/devices/contact.d.ts.map +1 -1
  17. package/dist/devices/contact.js +94 -63
  18. package/dist/devices/contact.js.map +1 -1
  19. package/dist/devices/curtains.d.ts +14 -5
  20. package/dist/devices/curtains.d.ts.map +1 -1
  21. package/dist/devices/curtains.js +174 -120
  22. package/dist/devices/curtains.js.map +1 -1
  23. package/dist/devices/humidifiers.d.ts +6 -6
  24. package/dist/devices/humidifiers.d.ts.map +1 -1
  25. package/dist/devices/humidifiers.js +78 -94
  26. package/dist/devices/humidifiers.js.map +1 -1
  27. package/dist/devices/indoorcam.d.ts +43 -0
  28. package/dist/devices/indoorcam.d.ts.map +1 -0
  29. package/dist/devices/indoorcam.js +241 -0
  30. package/dist/devices/indoorcam.js.map +1 -0
  31. package/dist/devices/meters.d.ts +8 -5
  32. package/dist/devices/meters.d.ts.map +1 -1
  33. package/dist/devices/meters.js +126 -92
  34. package/dist/devices/meters.js.map +1 -1
  35. package/dist/devices/motion.d.ts +9 -5
  36. package/dist/devices/motion.d.ts.map +1 -1
  37. package/dist/devices/motion.js +74 -59
  38. package/dist/devices/motion.js.map +1 -1
  39. package/dist/devices/plugs.d.ts +5 -4
  40. package/dist/devices/plugs.d.ts.map +1 -1
  41. package/dist/devices/plugs.js +65 -56
  42. package/dist/devices/plugs.js.map +1 -1
  43. package/dist/irdevices/airconditioners.d.ts +3 -3
  44. package/dist/irdevices/airconditioners.d.ts.map +1 -1
  45. package/dist/irdevices/airconditioners.js +12 -7
  46. package/dist/irdevices/airconditioners.js.map +1 -1
  47. package/dist/irdevices/airpurifiers.d.ts +3 -3
  48. package/dist/irdevices/airpurifiers.d.ts.map +1 -1
  49. package/dist/irdevices/airpurifiers.js +1 -1
  50. package/dist/irdevices/airpurifiers.js.map +1 -1
  51. package/dist/irdevices/cameras.d.ts +3 -3
  52. package/dist/irdevices/cameras.d.ts.map +1 -1
  53. package/dist/irdevices/cameras.js +1 -1
  54. package/dist/irdevices/cameras.js.map +1 -1
  55. package/dist/irdevices/fans.d.ts +3 -3
  56. package/dist/irdevices/fans.d.ts.map +1 -1
  57. package/dist/irdevices/fans.js +11 -12
  58. package/dist/irdevices/fans.js.map +1 -1
  59. package/dist/irdevices/lights.d.ts +3 -3
  60. package/dist/irdevices/lights.d.ts.map +1 -1
  61. package/dist/irdevices/lights.js +1 -1
  62. package/dist/irdevices/lights.js.map +1 -1
  63. package/dist/irdevices/others.d.ts +3 -3
  64. package/dist/irdevices/others.d.ts.map +1 -1
  65. package/dist/irdevices/others.js +8 -8
  66. package/dist/irdevices/others.js.map +1 -1
  67. package/dist/irdevices/tvs.d.ts +3 -3
  68. package/dist/irdevices/tvs.d.ts.map +1 -1
  69. package/dist/irdevices/tvs.js +16 -6
  70. package/dist/irdevices/tvs.js.map +1 -1
  71. package/dist/irdevices/vacuumcleaners.d.ts +3 -3
  72. package/dist/irdevices/vacuumcleaners.d.ts.map +1 -1
  73. package/dist/irdevices/vacuumcleaners.js +2 -3
  74. package/dist/irdevices/vacuumcleaners.js.map +1 -1
  75. package/dist/irdevices/waterheaters.d.ts +3 -3
  76. package/dist/irdevices/waterheaters.d.ts.map +1 -1
  77. package/dist/irdevices/waterheaters.js +2 -2
  78. package/dist/irdevices/waterheaters.js.map +1 -1
  79. package/dist/platform.d.ts +14 -5
  80. package/dist/platform.d.ts.map +1 -1
  81. package/dist/platform.js +420 -418
  82. package/dist/platform.js.map +1 -1
  83. package/dist/settings.d.ts +18 -15
  84. package/dist/settings.d.ts.map +1 -1
  85. package/package.json +65 -65
  86. package/src/devices/bots.ts +188 -165
  87. package/src/devices/bulb.ts +48 -40
  88. package/src/devices/contact.ts +100 -73
  89. package/src/devices/curtains.ts +192 -140
  90. package/src/devices/humidifiers.ts +82 -124
  91. package/src/devices/indoorcam.ts +303 -0
  92. package/src/devices/meters.ts +130 -110
  93. package/src/devices/motion.ts +80 -69
  94. package/src/devices/plugs.ts +67 -65
  95. package/src/irdevices/airconditioners.ts +14 -10
  96. package/src/irdevices/airpurifiers.ts +4 -4
  97. package/src/irdevices/cameras.ts +4 -4
  98. package/src/irdevices/fans.ts +14 -18
  99. package/src/irdevices/lights.ts +4 -4
  100. package/src/irdevices/others.ts +12 -14
  101. package/src/irdevices/tvs.ts +21 -10
  102. package/src/irdevices/vacuumcleaners.ts +5 -9
  103. package/src/irdevices/waterheaters.ts +5 -5
  104. package/src/platform.ts +455 -619
  105. package/src/settings.ts +19 -15
  106. package/.github/workflows/issue-validate.yml +0 -18
@@ -2,8 +2,7 @@ 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, deviceStatusResponse } from '../settings';
6
- import { AxiosResponse } from 'axios';
5
+ import { DeviceURL, device, devicesConfig } from '../settings';
7
6
 
8
7
  /**
9
8
  * Platform Accessory
@@ -25,16 +24,17 @@ export class Humidifier {
25
24
  WaterLevel!: CharacteristicValue;
26
25
 
27
26
  // Others
28
- deviceStatus!: deviceStatusResponse;
27
+ deviceStatus!: any;
29
28
 
30
29
  // Updates
31
30
  humidifierUpdateInProgress!: boolean;
32
31
  doHumidifierUpdate;
32
+ set_minStep: number | undefined;
33
33
 
34
34
  constructor(
35
35
  private readonly platform: SwitchBotPlatform,
36
36
  private accessory: PlatformAccessory,
37
- public device: device,
37
+ public device: device & devicesConfig,
38
38
  ) {
39
39
  // default placeholders
40
40
  this.CurrentRelativeHumidity = 0;
@@ -63,7 +63,7 @@ export class Humidifier {
63
63
  // you can create multiple services for each accessory
64
64
  (this.service =
65
65
  accessory.getService(this.platform.Service.HumidifierDehumidifier) ||
66
- accessory.addService(this.platform.Service.HumidifierDehumidifier)), `${device.deviceName} ${device.deviceType}`;
66
+ accessory.addService(this.platform.Service.HumidifierDehumidifier)), `${accessory.displayName} Humidifier`;
67
67
 
68
68
  // To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
69
69
  // when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
@@ -100,25 +100,23 @@ export class Humidifier {
100
100
  validValueRanges: [0, 100],
101
101
  minValue: 0,
102
102
  maxValue: 100,
103
- minStep: this.platform.config.options?.humidifier?.set_minStep || 1,
103
+ minStep: this.minStep(),
104
104
  })
105
105
  .onSet(this.handleRelativeHumidityHumidifierThresholdSet.bind(this));
106
106
 
107
107
  // create a new Temperature Sensor service
108
108
  // Temperature Sensor Service
109
- if (this.platform.config.options?.humidifier?.hide_temperature) {
110
- if (this.platform.config.options.debug) {
111
- this.platform.log.error('Removing service');
112
- }
109
+ if (device.humidifier?.hide_temperature) {
110
+ this.platform.device('Removing Temerature Sensor Service');
113
111
  this.temperatureservice = this.accessory.getService(this.platform.Service.TemperatureSensor);
114
112
  accessory.removeService(this.temperatureservice!);
115
113
  } else if (!this.temperatureservice) {
116
- if (this.platform.config.options?.debug) {
117
- this.platform.log.warn('Adding service');
118
- }
114
+ this.platform.device('Adding Temerature Sensor Service');
119
115
  (this.temperatureservice =
120
116
  this.accessory.getService(this.platform.Service.TemperatureSensor) ||
121
- this.accessory.addService(this.platform.Service.TemperatureSensor)), `${device.deviceName} ${device.deviceType} TemperatureSensor`;
117
+ this.accessory.addService(this.platform.Service.TemperatureSensor)), `${accessory.displayName} Temperature Sensor`;
118
+
119
+ this.temperatureservice.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Temperature Sensor`);
122
120
 
123
121
  this.temperatureservice
124
122
  .getCharacteristic(this.platform.Characteristic.CurrentTemperature)
@@ -129,11 +127,11 @@ export class Humidifier {
129
127
  minStep: 0.1,
130
128
  })
131
129
  .onGet(() => {
132
- return this.CurrentTemperature;
130
+ return Number.isNaN(this.CurrentTemperature);
133
131
  });
134
132
  } else {
135
133
  if (this.platform.config.options?.debug) {
136
- this.platform.log.warn('TemperatureSensor not added.');
134
+ this.platform.device('Temperature Sensor Not Added');
137
135
  }
138
136
  }
139
137
 
@@ -168,6 +166,15 @@ export class Humidifier {
168
166
  });
169
167
  }
170
168
 
169
+ private minStep(): number | undefined {
170
+ if (this.device.humidifier?.set_minStep) {
171
+ this.set_minStep = this.device.humidifier?.set_minStep;
172
+ } else {
173
+ this.set_minStep = 1;
174
+ }
175
+ return this.set_minStep;
176
+ }
177
+
171
178
  /**
172
179
  * Parse the device status from the SwitchBot api
173
180
  */
@@ -175,9 +182,7 @@ export class Humidifier {
175
182
  // Current Relative Humidity
176
183
  this.CurrentRelativeHumidity = this.deviceStatus.body.humidity!;
177
184
  this.platform.debug(`Humidifier ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`);
178
- if (this.platform.config.options?.debug) {
179
- this.platform.log.warn(JSON.stringify(this.deviceStatus.body));
180
- }
185
+ this.platform.device(JSON.stringify(this.deviceStatus.body));
181
186
  // Water Level
182
187
  if (this.deviceStatus.body.lackWater) {
183
188
  this.WaterLevel = 0;
@@ -220,8 +225,8 @@ export class Humidifier {
220
225
  this.platform.debug(`Humidifier ${this.accessory.displayName} RelativeHumidityHumidifierThreshold: ${this.RelativeHumidityHumidifierThreshold}`);
221
226
  this.platform.debug(`Humidifier ${this.accessory.displayName} CurrentHumidifierDehumidifierState: ${this.CurrentHumidifierDehumidifierState}`);
222
227
  // Current Temperature
223
- if (!this.platform.config.options?.humidifier?.hide_temperature) {
224
- this.CurrentTemperature = this.deviceStatus.body.temperature!;
228
+ if (!this.device.humidifier?.hide_temperature) {
229
+ this.CurrentTemperature = this.deviceStatus.body.temperature;
225
230
  this.platform.debug(`Humidifier ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`);
226
231
  }
227
232
  }
@@ -231,18 +236,17 @@ export class Humidifier {
231
236
  */
232
237
  async refreshStatus() {
233
238
  try {
234
- const deviceStatus: deviceStatusResponse = (
235
- await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)
236
- ).data;
237
- if (deviceStatus.message === 'success') {
238
- this.deviceStatus = deviceStatus;
239
+ this.deviceStatus = (await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)).data;
240
+ if (this.deviceStatus.message === 'success') {
239
241
  this.platform.debug(`Humidifier ${this.accessory.displayName} refreshStatus: ${JSON.stringify(this.deviceStatus)}`);
240
242
  this.parseStatus();
241
243
  this.updateHomeKitCharacteristics();
244
+ } else {
245
+ this.platform.debug(this.deviceStatus);
242
246
  }
243
247
  } catch (e: any) {
244
- this.platform.log.error(`Humidifier - Failed to update status of ${this.device.deviceName}: ${JSON.stringify(e.message)}`);
245
- this.platform.debug(`Humidifier ${this.accessory.displayName} ${JSON.stringify(e)}`);
248
+ this.platform.log.error(`Humidifier ${this.accessory.displayName}, Failed to update status. Error Message: ${JSON.stringify(e.message)}`);
249
+ this.platform.debug(`Humidifier ${this.accessory.displayName}, Error: ${JSON.stringify(e)}`);
246
250
  this.apiError(e);
247
251
  }
248
252
  }
@@ -273,11 +277,11 @@ export class Humidifier {
273
277
  'commandType:',
274
278
  payload.commandType,
275
279
  );
276
- this.platform.debug(`Humidifier ${this.accessory.displayName} pushChanges - ${JSON.stringify(payload)}`);
280
+ this.platform.debug(`Humidifier ${this.accessory.displayName} pushChanges: ${JSON.stringify(payload)}`);
277
281
 
278
282
  // Make the API request
279
- const push = await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId}/commands`, payload);
280
- this.platform.debug(`Humidifier ${this.accessory.displayName} Changes pushed - ${push.data}`);
283
+ const push: any = (await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId}/commands`, payload));
284
+ this.platform.debug(`Humidifier ${this.accessory.displayName} Changes pushed: ${JSON.stringify(push.data)}`);
281
285
  this.statusCode(push);
282
286
  } else if (
283
287
  this.TargetHumidifierDehumidifierState ===
@@ -317,12 +321,12 @@ export class Humidifier {
317
321
  'commandType:',
318
322
  payload.commandType,
319
323
  );
320
- this.platform.debug(`Humidifier ${this.accessory.displayName} pushAutoChanges - ${JSON.stringify(payload)}`);
324
+ this.platform.debug(`Humidifier ${this.accessory.displayName} pushAutoChanges: ${JSON.stringify(payload)}`);
321
325
 
322
326
  // Make the API request
323
- const pushAuto = await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId}/commands`, payload);
324
- this.platform.debug(`Humidifier ${this.accessory.displayName} Changes pushed - ${pushAuto.data}`);
325
- this.statusCodeAuto(pushAuto);
327
+ const push: any = (await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId}/commands`, payload));
328
+ this.platform.debug(`Humidifier ${this.accessory.displayName} Changes pushed: ${JSON.stringify(push.data)}`);
329
+ this.statusCode(push);
326
330
  }
327
331
  } catch (e: any) {
328
332
  this.platform.log.error(JSON.stringify(e.message));
@@ -354,12 +358,12 @@ export class Humidifier {
354
358
  'commandType:',
355
359
  payload.commandType,
356
360
  );
357
- this.platform.debug(`Humidifier ${this.accessory.displayName} pushActiveChanges - ${JSON.stringify(payload)}`);
361
+ this.platform.debug(`Humidifier ${this.accessory.displayName} pushActiveChanges: ${JSON.stringify(payload)}`);
358
362
 
359
363
  // Make the API request
360
- const pushActive = await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId}/commands`, payload);
361
- this.platform.debug(`Humidifier ${this.accessory.displayName} Changes pushed - ${pushActive.data}`);
362
- this.statusCodeActive(pushActive);
364
+ const push: any = (await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId}/commands`, payload));
365
+ this.platform.debug(`Humidifier ${this.accessory.displayName} Changes pushed: ${JSON.stringify(push.data)}`);
366
+ this.statusCode(push);
363
367
  }
364
368
  } catch (e: any) {
365
369
  this.platform.log.error(JSON.stringify(e.message));
@@ -372,41 +376,51 @@ export class Humidifier {
372
376
  * Updates the status for each of the HomeKit Characteristics
373
377
  */
374
378
  updateHomeKitCharacteristics() {
375
- if (this.CurrentRelativeHumidity !== undefined) {
376
- this.service.updateCharacteristic(
377
- this.platform.Characteristic.CurrentRelativeHumidity,
378
- this.CurrentRelativeHumidity,
379
- );
379
+ if (this.CurrentRelativeHumidity === undefined) {
380
+ this.platform.debug(`Humidifier ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`);
381
+ } else {
382
+ this.service.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity);
383
+ this.platform.device(`Humidifier ${this.accessory.displayName} updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`);
380
384
  }
381
- if (this.WaterLevel !== undefined) {
385
+ if (this.WaterLevel === undefined) {
386
+ this.platform.debug(`Humidifier ${this.accessory.displayName} WaterLevel: ${this.WaterLevel}`);
387
+ } else {
382
388
  this.service.updateCharacteristic(this.platform.Characteristic.WaterLevel, this.WaterLevel);
389
+ this.platform.device(`Humidifier ${this.accessory.displayName} updateCharacteristic WaterLevel: ${this.WaterLevel}`);
383
390
  }
384
- if (this.CurrentHumidifierDehumidifierState !== undefined) {
385
- this.service.updateCharacteristic(
386
- this.platform.Characteristic.CurrentHumidifierDehumidifierState,
387
- this.CurrentHumidifierDehumidifierState,
388
- );
391
+ if (this.CurrentHumidifierDehumidifierState === undefined) {
392
+ this.platform.debug(`Humidifier ${this.accessory.displayName} CurrentHumidifierDehumidifierState: ${this.CurrentHumidifierDehumidifierState}`);
393
+ } else {
394
+ this.service.updateCharacteristic(this.platform.Characteristic.CurrentHumidifierDehumidifierState, this.CurrentHumidifierDehumidifierState);
395
+ this.platform.device(`Humidifier ${this.accessory.displayName}`
396
+ + ` updateCharacteristic CurrentHumidifierDehumidifierState: ${this.CurrentHumidifierDehumidifierState}`);
389
397
  }
390
- if (this.TargetHumidifierDehumidifierState !== undefined) {
391
- this.service.updateCharacteristic(
392
- this.platform.Characteristic.TargetHumidifierDehumidifierState,
393
- this.TargetHumidifierDehumidifierState,
394
- );
398
+ if (this.TargetHumidifierDehumidifierState === undefined) {
399
+ this.platform.debug(`Humidifier ${this.accessory.displayName} TargetHumidifierDehumidifierState: ${this.TargetHumidifierDehumidifierState}`);
400
+ } else {
401
+ this.service.updateCharacteristic(this.platform.Characteristic.TargetHumidifierDehumidifierState, this.TargetHumidifierDehumidifierState);
402
+ this.platform.device(`Humidifier ${this.accessory.displayName}`
403
+ + ` updateCharacteristic TargetHumidifierDehumidifierState: ${this.TargetHumidifierDehumidifierState}`);
395
404
  }
396
- if (this.Active !== undefined) {
405
+ if (this.Active === undefined) {
406
+ this.platform.debug(`Humidifier ${this.accessory.displayName} Active: ${this.Active}`);
407
+ } else {
397
408
  this.service.updateCharacteristic(this.platform.Characteristic.Active, this.Active);
409
+ this.platform.device(`Humidifier ${this.accessory.displayName} updateCharacteristic Active: ${this.Active}`);
398
410
  }
399
- if (this.RelativeHumidityHumidifierThreshold !== undefined) {
400
- this.service.updateCharacteristic(
401
- this.platform.Characteristic.RelativeHumidityHumidifierThreshold,
402
- this.RelativeHumidityHumidifierThreshold,
403
- );
411
+ if (this.RelativeHumidityHumidifierThreshold === undefined) {
412
+ this.platform.debug(`Humidifier ${this.accessory.displayName}`
413
+ + ` RelativeHumidityHumidifierThreshold: ${this.RelativeHumidityHumidifierThreshold}`);
414
+ } else {
415
+ this.service.updateCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold, this.RelativeHumidityHumidifierThreshold);
416
+ this.platform.device(`Humidifier ${this.accessory.displayName}`
417
+ + ` updateCharacteristic RelativeHumidityHumidifierThreshold: ${this.RelativeHumidityHumidifierThreshold}`);
404
418
  }
405
- if (!this.platform.config.options?.humidifier?.hide_temperature && this.CurrentTemperature !== undefined) {
406
- this.temperatureservice!.updateCharacteristic(
407
- this.platform.Characteristic.CurrentTemperature,
408
- this.CurrentTemperature,
409
- );
419
+ if (!this.device.humidifier?.hide_temperature || this.CurrentTemperature === undefined) {
420
+ this.platform.debug(`Humidifier ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`);
421
+ } else {
422
+ this.temperatureservice!.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.CurrentTemperature);
423
+ this.platform.device(`Humidifier ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`);
410
424
  }
411
425
  }
412
426
 
@@ -417,12 +431,12 @@ export class Humidifier {
417
431
  this.service.updateCharacteristic(this.platform.Characteristic.TargetHumidifierDehumidifierState, e);
418
432
  this.service.updateCharacteristic(this.platform.Characteristic.Active, e);
419
433
  this.service.updateCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold, e);
420
- if (!this.platform.config.options?.humidifier?.hide_temperature) {
434
+ if (!this.device.humidifier?.hide_temperature) {
421
435
  this.temperatureservice!.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, e);
422
436
  }
423
437
  }
424
438
 
425
- private statusCode(push: AxiosResponse<any>) {
439
+ private statusCode(push: { data: { statusCode: any; }; }) {
426
440
  switch (push.data.statusCode) {
427
441
  case 151:
428
442
  this.platform.log.error('Command not supported by this device type.');
@@ -450,62 +464,6 @@ export class Humidifier {
450
464
  }
451
465
  }
452
466
 
453
- private statusCodeAuto(pushAuto: AxiosResponse<any>) {
454
- switch (pushAuto.data.statusCode) {
455
- case 151:
456
- this.platform.log.error('Command not supported by this device type.');
457
- break;
458
- case 152:
459
- this.platform.log.error('Device not found.');
460
- break;
461
- case 160:
462
- this.platform.log.error('Command is not supported.');
463
- break;
464
- case 161:
465
- this.platform.log.error('Device is offline.');
466
- break;
467
- case 171:
468
- this.platform.log.error('Hub Device is offline.');
469
- break;
470
- case 190:
471
- this.platform.log.error('Device internal error due to device states not synchronized with server. Or command fomrat is invalid.');
472
- break;
473
- case 100:
474
- this.platform.debug('Command successfully sent.');
475
- break;
476
- default:
477
- this.platform.debug('Unknown statusCode.');
478
- }
479
- }
480
-
481
- private statusCodeActive(pushActive: AxiosResponse<any>) {
482
- switch (pushActive.data.statusCode) {
483
- case 151:
484
- this.platform.log.error('Command not supported by this device type.');
485
- break;
486
- case 152:
487
- this.platform.log.error('Device not found.');
488
- break;
489
- case 160:
490
- this.platform.log.error('Command is not supported.');
491
- break;
492
- case 161:
493
- this.platform.log.error('Device is offline.');
494
- break;
495
- case 171:
496
- this.platform.log.error('Hub Device is offline.');
497
- break;
498
- case 190:
499
- this.platform.log.error('Device internal error due to device states not synchronized with server. Or command fomrat is invalid.');
500
- break;
501
- case 100:
502
- this.platform.debug('Command successfully sent.');
503
- break;
504
- default:
505
- this.platform.debug('Unknown statusCode.');
506
- }
507
- }
508
-
509
467
  /**
510
468
  * Handle requests to set the "Target Humidifier Dehumidifier State" characteristic
511
469
  */
@@ -0,0 +1,303 @@
1
+ import { Service, PlatformAccessory, CharacteristicValue, MacAddress } from 'homebridge';
2
+ import { SwitchBotPlatform } from '../platform';
3
+ import { interval, Subject } from 'rxjs';
4
+ import { debounceTime, skipWhile, tap } from 'rxjs/operators';
5
+ import { DeviceURL, device, devicesConfig } from '../settings';
6
+
7
+ export class IndoorCam {
8
+ // Services
9
+ private service: Service;
10
+
11
+ // Characteristic Values
12
+ On!: CharacteristicValue;
13
+ OutletInUse!: CharacteristicValue;
14
+
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
+ };
31
+
32
+ // Updates
33
+ cameraUpdateInProgress!: boolean;
34
+ doCameraUpdate;
35
+
36
+ constructor(
37
+ private readonly platform: SwitchBotPlatform,
38
+ private accessory: PlatformAccessory,
39
+ public device: device & devicesConfig,
40
+ ) {
41
+ // default placeholders
42
+ this.On = false;
43
+ this.OutletInUse = true;
44
+
45
+ // BLE Connections
46
+ if (device.ble) {
47
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
48
+ const SwitchBot = require('node-switchbot');
49
+ this.switchbot = new SwitchBot();
50
+ const colon = device.deviceId!.match(/.{1,2}/g);
51
+ const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
52
+ this.device.bleMac = bleMac.toLowerCase();
53
+ this.platform.device(this.device.bleMac.toLowerCase());
54
+ }
55
+
56
+ // this is subject we use to track when we need to POST changes to the SwitchBot API
57
+ this.doCameraUpdate = new Subject();
58
+ this.cameraUpdateInProgress = false;
59
+
60
+ // Retrieve initial values and updateHomekit
61
+ this.refreshStatus();
62
+
63
+ // set accessory information
64
+ accessory
65
+ .getService(this.platform.Service.AccessoryInformation)!
66
+ .setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
67
+ .setCharacteristic(this.platform.Characteristic.Model, 'SWITCHBOT-CAMERA-')
68
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
69
+
70
+ // get the WindowCovering service if it exists, otherwise create a new WindowCovering service
71
+ // you can create multiple services for each accessory
72
+ (this.service =
73
+ accessory.getService(this.platform.Service.Outlet) ||
74
+ accessory.addService(this.platform.Service.Outlet)), `${accessory.displayName} Indoor Camera`;
75
+
76
+ // To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
77
+ // when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
78
+ // accessory.getService('NAME') ?? accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE');
79
+
80
+ // set the service name, this is what is displayed as the default name on the Home app
81
+ // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method.
82
+ this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName);
83
+
84
+ // each service must implement at-minimum the "required characteristics" for the given service type
85
+ // see https://developers.homebridge.io/#/service/WindowCovering
86
+
87
+ // create handlers for required characteristics
88
+ this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.OnSet.bind(this));
89
+
90
+ this.service.setCharacteristic(this.platform.Characteristic.OutletInUse, this.OutletInUse || true);
91
+
92
+ // Update Homekit
93
+ this.updateHomeKitCharacteristics();
94
+
95
+ // Start an update interval
96
+ interval(this.platform.config.options!.refreshRate! * 1000)
97
+ .pipe(skipWhile(() => this.cameraUpdateInProgress))
98
+ .subscribe(() => {
99
+ this.refreshStatus();
100
+ });
101
+
102
+ // Watch for Camera change events
103
+ // We put in a debounce of 100ms so we don't make duplicate calls
104
+ this.doCameraUpdate
105
+ .pipe(
106
+ tap(() => {
107
+ this.cameraUpdateInProgress = true;
108
+ }),
109
+ debounceTime(this.platform.config.options!.pushRate! * 1000),
110
+ )
111
+ .subscribe(async () => {
112
+ try {
113
+ await this.pushChanges();
114
+ } catch (e: any) {
115
+ this.platform.log.error(JSON.stringify(e.message));
116
+ this.platform.debug(`Camera ${accessory.displayName} - ${JSON.stringify(e)}`);
117
+ this.apiError(e);
118
+ }
119
+ this.cameraUpdateInProgress = false;
120
+ });
121
+ }
122
+
123
+ parseStatus() {
124
+ switch (this.deviceStatus.body.power) {
125
+ case 'on':
126
+ this.On = true;
127
+ break;
128
+ default:
129
+ this.On = false;
130
+ }
131
+ this.platform.debug(`Camera ${this.accessory.displayName} On: ${this.On}`);
132
+ }
133
+
134
+ async refreshStatus() {
135
+ if (this.device.ble) {
136
+ this.platform.device('BLE');
137
+ await this.BLErefreshStatus();
138
+ } else {
139
+ this.platform.device('OpenAPI');
140
+ await this.openAPIRefreshStatus();
141
+ }
142
+ }
143
+
144
+ private async BLErefreshStatus() {
145
+ this.platform.debug('IndoorCam BLE Device RefreshStatus');
146
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
147
+ const Switchbot = require('node-switchbot');
148
+ const switchbot = new Switchbot();
149
+ const colon = this.device.deviceId!.match(/.{1,2}/g);
150
+ const bleMac = colon!.join(':'); //returns 1A:23:B4:56:78:9A;
151
+ this.device.bleMac = bleMac.toLowerCase();
152
+ this.platform.device(this.device.bleMac!);
153
+ switchbot.onadvertisement = (ad: any) => {
154
+ this.platform.debug(JSON.stringify(ad, null, ' '));
155
+ this.platform.device(`ad: ${JSON.stringify(ad)}`);
156
+ };
157
+ switchbot
158
+ .startScan({
159
+ id: this.device.bleMac,
160
+ })
161
+ .then(() => {
162
+ return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
163
+ })
164
+ .then(() => {
165
+ switchbot.stopScan();
166
+ })
167
+ .catch(async (error: any) => {
168
+ this.platform.log.error(error);
169
+ await this.openAPIRefreshStatus();
170
+ });
171
+ setInterval(() => {
172
+ this.platform.log.info('Start scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
173
+ switchbot
174
+ .startScan({
175
+ mode: 'T',
176
+ id: bleMac,
177
+ })
178
+ .then(() => {
179
+ return switchbot.wait(this.platform.config.options!.refreshRate! * 1000);
180
+ })
181
+ .then(() => {
182
+ switchbot.stopScan();
183
+ this.platform.log.info('Stop scan ' + this.device.deviceName + '(' + this.device.bleMac + ')');
184
+ })
185
+ .catch(async (error: any) => {
186
+ this.platform.log.error(error);
187
+ await this.openAPIRefreshStatus();
188
+ });
189
+ }, this.platform.config.options!.refreshRate! * 60000);
190
+ }
191
+
192
+ private async openAPIRefreshStatus() {
193
+ try {
194
+ this.deviceStatus = (await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)).data;
195
+ this.platform.device(`Camera ${this.accessory.displayName} refreshStatus: ${JSON.stringify(this.deviceStatus)}`);
196
+ this.parseStatus();
197
+ this.updateHomeKitCharacteristics();
198
+ } catch (e: any) {
199
+ this.platform.log.error(`IndoorCam ${this.accessory.displayName} failed to refresh status, Error Message: ${JSON.stringify(e.message)}`);
200
+ this.platform.debug(`IndoorCam ${this.accessory.displayName}, Error: ${JSON.stringify(e)}`);
201
+ this.apiError(e);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Pushes the requested changes to the SwitchBot API
207
+ * deviceType commandType Command command parameter Description
208
+ * Camera - "command" "turnOff" "default" = set to OFF state
209
+ * Camera - "command" "turnOn" "default" = set to ON state
210
+ */
211
+ async pushChanges() {
212
+ const payload = {
213
+ commandType: 'command',
214
+ parameter: 'default',
215
+ } as any;
216
+
217
+ if (this.On) {
218
+ payload.command = 'turnOn';
219
+ } else {
220
+ payload.command = 'turnOff';
221
+ }
222
+
223
+ this.platform.log.info(
224
+ 'Sending request for',
225
+ this.accessory.displayName,
226
+ 'to SwitchBot API. command:',
227
+ payload.command,
228
+ 'parameter:',
229
+ payload.parameter,
230
+ 'commandType:',
231
+ payload.commandType,
232
+ );
233
+ this.platform.debug(`Camera ${this.accessory.displayName} pushchanges: ${JSON.stringify(payload)}`);
234
+
235
+ // Make the API request
236
+ const push: any = await this.platform.axios.post(`${DeviceURL}/${this.device.deviceId}/commands`, payload);
237
+ this.platform.debug(`Camera ${this.accessory.displayName} Changes pushed: ${JSON.stringify(push.data)}`);
238
+ this.statusCode(push);
239
+ }
240
+
241
+ updateHomeKitCharacteristics() {
242
+ if (this.On === undefined) {
243
+ this.platform.debug(`Camera ${this.accessory.displayName} On: ${this.On}`);
244
+ } else {
245
+ this.service.updateCharacteristic(this.platform.Characteristic.On, this.On);
246
+ this.platform.device(`Camera ${this.accessory.displayName} updateCharacteristic On: ${this.On}`);
247
+ }
248
+ if (this.OutletInUse === undefined) {
249
+ this.platform.debug(`Camera ${this.accessory.displayName} OutletInUse: ${this.OutletInUse}`);
250
+ } else {
251
+ this.service.updateCharacteristic(this.platform.Characteristic.OutletInUse, this.OutletInUse);
252
+ this.platform.device(`Camera ${this.accessory.displayName} updateCharacteristic OutletInUse: ${this.OutletInUse}`);
253
+ }
254
+ }
255
+
256
+ public apiError(e: any) {
257
+ this.service.updateCharacteristic(this.platform.Characteristic.On, e);
258
+ this.service.updateCharacteristic(this.platform.Characteristic.OutletInUse, e);
259
+ }
260
+
261
+
262
+ private statusCode(push: { data: { statusCode: any; }; }) {
263
+ switch (push.data.statusCode) {
264
+ case 151:
265
+ this.platform.log.error('Command not supported by this device type.');
266
+ break;
267
+ case 152:
268
+ this.platform.log.error('Device not found.');
269
+ break;
270
+ case 160:
271
+ this.platform.log.error('Command is not supported.');
272
+ break;
273
+ case 161:
274
+ this.platform.log.error('Device is offline.');
275
+ break;
276
+ case 171:
277
+ this.platform.log.error('Hub Device is offline.');
278
+ break;
279
+ case 190:
280
+ this.platform.log.error('Device internal error due to device states not synchronized with server. Or command fomrat is invalid.');
281
+ break;
282
+ case 100:
283
+ if (this.platform.config.options?.debug) {
284
+ this.platform.log.info('Command successfully sent.');
285
+ }
286
+ break;
287
+ default:
288
+ this.platform.debug('Unknown statusCode.');
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Handle requests to set the value of the "On" characteristic
294
+ */
295
+ OnSet(value: CharacteristicValue) {
296
+ this.platform.debug(`Camera ${this.accessory.displayName} - Set On: ${value}`);
297
+
298
+ this.On = value;
299
+ this.doCameraUpdate.next();
300
+ }
301
+
302
+
303
+ }