@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
@@ -1,12 +1,16 @@
1
1
  import https from 'https';
2
2
  import crypto from 'crypto';
3
3
  import { Context } from 'vm';
4
+ import { hostname } from 'os';
5
+ import { MqttClient } from 'mqtt';
6
+ import { connectAsync } from 'async-mqtt';
4
7
  import { interval, Subject } from 'rxjs';
5
8
  import { skipWhile } from 'rxjs/operators';
6
9
  import superStringify from 'super-stringify';
7
10
  import { SwitchBotPlatform } from '../platform';
8
11
  import { Service, PlatformAccessory, Units, CharacteristicValue } from 'homebridge';
9
12
  import { device, devicesConfig, serviceData, ad, switchbot, temperature, deviceStatus, HostDomain, DevicePath } from '../settings';
13
+ import { IncomingMessage } from 'http';
10
14
 
11
15
  /**
12
16
  * Platform Accessory
@@ -34,15 +38,20 @@ export class MeterPlus {
34
38
  // BLE Others
35
39
  connected?: boolean;
36
40
  switchbot!: switchbot;
37
- SwitchToOpenAPI?: boolean;
38
41
  serviceData!: serviceData;
39
42
  address!: ad['address'];
43
+ SwitchToOpenAPI?: boolean;
40
44
  temperature!: temperature['c'];
41
45
  battery!: serviceData['battery'];
42
46
  humidity!: serviceData['humidity'];
43
47
 
48
+ //MQTT stuff
49
+ mqttClient: MqttClient | null = null;
50
+
51
+ // EVE history service handler
52
+ historyService: any;
53
+
44
54
  // Config
45
- OpenAPI?: boolean;
46
55
  scanDuration!: number;
47
56
  deviceLogging!: string;
48
57
  deviceRefreshRate!: number;
@@ -51,23 +60,19 @@ export class MeterPlus {
51
60
  meterUpdateInProgress!: boolean;
52
61
  doMeterUpdate: Subject<void>;
53
62
 
63
+ // Connection
64
+ private readonly BLE = (this.device.connectionType === 'BLE' || this.device.connectionType === 'Both');
65
+ private readonly OpenAPI = (this.device.connectionType === 'OpenAPI' || this.device.connectionType === 'Both');
66
+
54
67
  constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: device & devicesConfig) {
55
68
  // default placeholders
56
69
  this.logs(device);
57
- this.openAPI();
58
70
  this.scan(device);
59
71
  this.refreshRate(device);
60
72
  this.config(device);
61
- if (this.CurrentRelativeHumidity === undefined) {
62
- this.CurrentRelativeHumidity = 0;
63
- } else {
64
- this.CurrentRelativeHumidity = this.accessory.context.CurrentRelativeHumidity;
65
- }
66
- if (this.CurrentTemperature === undefined) {
67
- this.CurrentTemperature = 0;
68
- } else {
69
- this.CurrentTemperature = this.accessory.context.CurrentTemperature;
70
- }
73
+ this.context();
74
+ this.setupHistoryService(device);
75
+ this.setupMqtt(device);
71
76
 
72
77
  // this is subject we use to track when we need to POST changes to the SwitchBot API
73
78
  this.doMeterUpdate = new Subject();
@@ -141,11 +146,11 @@ export class MeterPlus {
141
146
  }
142
147
 
143
148
  // Battery Service
144
- if (!device.ble) {
149
+ if (!this.BLE) {
145
150
  this.debugLog(`${this.device.deviceType}: ${accessory.displayName} Removing Battery Service`);
146
151
  this.batteryService = this.accessory.getService(this.platform.Service.Battery);
147
152
  accessory.removeService(this.batteryService!);
148
- } else if (device.ble && !this.batteryService) {
153
+ } else if (this.BLE && !this.batteryService) {
149
154
  this.debugLog(`${this.device.deviceType}: ${accessory.displayName} Add Battery Service`);
150
155
  (this.batteryService = this.accessory.getService(this.platform.Service.Battery) || this.accessory.addService(this.platform.Service.Battery)),
151
156
  `${accessory.displayName} Battery`;
@@ -172,15 +177,19 @@ export class MeterPlus {
172
177
  * Parse the device status from the SwitchBot api
173
178
  */
174
179
  async parseStatus(): Promise<void> {
175
- if (this.SwitchToOpenAPI || !this.device.ble) {
180
+ if (this.BLE && !this.SwitchToOpenAPI) {
181
+ await this.BLEparseStatus();
182
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
183
+ this.SwitchToOpenAPI = false;
176
184
  await this.openAPIparseStatus();
177
185
  } else {
178
- await this.BLEparseStatus();
186
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
187
+ + ` ${this.device.connectionType}, parseStatus will not happen.`);
179
188
  }
180
189
  }
181
190
 
182
191
  async BLEparseStatus(): Promise<void> {
183
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE parseStatus`);
192
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLEparseStatus`);
184
193
 
185
194
  // Battery
186
195
  this.BatteryLevel = Number(this.battery);
@@ -206,22 +215,17 @@ export class MeterPlus {
206
215
  }
207
216
 
208
217
  async openAPIparseStatus(): Promise<void> {
209
- if (this.device.ble) {
210
- this.SwitchToOpenAPI = false;
218
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIparseStatus`);
219
+ // Current Relative Humidity
220
+ if (!this.device.meter?.hide_humidity) {
221
+ this.CurrentRelativeHumidity = this.Humidity!;
222
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Humidity: ${this.CurrentRelativeHumidity}%`);
211
223
  }
212
- if (this.platform.config.credentials?.token) {
213
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} OpenAPI parseStatus`);
214
- // Current Relative Humidity
215
- if (!this.device.meter?.hide_humidity) {
216
- this.CurrentRelativeHumidity = this.Humidity!;
217
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Humidity: ${this.CurrentRelativeHumidity}%`);
218
- }
219
224
 
220
- // Current Temperature
221
- if (!this.device.meter?.hide_temperature) {
222
- this.CurrentTemperature = this.Temperature!;
223
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Temperature: ${this.CurrentTemperature}°c`);
224
- }
225
+ // Current Temperature
226
+ if (!this.device.meter?.hide_temperature) {
227
+ this.CurrentTemperature = this.Temperature!;
228
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Temperature: ${this.CurrentTemperature}°c`);
225
229
  }
226
230
  }
227
231
 
@@ -229,15 +233,18 @@ export class MeterPlus {
229
233
  * Asks the SwitchBot API for the latest device information
230
234
  */
231
235
  async refreshStatus(): Promise<void> {
232
- if (this.device.ble) {
233
- this.BLErefreshStatus();
236
+ if (this.BLE) {
237
+ await this.BLERefreshStatus();
238
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
239
+ await this.openAPIRefreshStatus();
234
240
  } else {
235
- this.openAPIRefreshStatus();
241
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Connection Type:`
242
+ + ` ${this.device.connectionType}, refreshStatus will not happen.`);
236
243
  }
237
244
  }
238
245
 
239
- async BLErefreshStatus(): Promise<void> {
240
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE RefreshStatus`);
246
+ async BLERefreshStatus(): Promise<void> {
247
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLERefreshStatus`);
241
248
  const switchbot = await this.platform.connectBLE();
242
249
  // Convert to BLE Address
243
250
  this.device.bleMac = this.device
@@ -263,9 +270,12 @@ export class MeterPlus {
263
270
  this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address Found: ${this.address}`);
264
271
  this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Config BLE Address: ${this.device.bleMac}`);
265
272
  }
273
+ if (ad.serviceData.humidity! > 0) {
274
+ // reject unreliable data
275
+ this.humidity = ad.serviceData.humidity;
276
+ }
266
277
  this.serviceData = ad.serviceData;
267
278
  this.temperature = ad.serviceData.temperature!.c;
268
- this.humidity = ad.serviceData.humidity;
269
279
  this.battery = ad.serviceData.battery;
270
280
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${superStringify(ad.serviceData)}`);
271
281
  this.debugLog(
@@ -296,111 +306,66 @@ export class MeterPlus {
296
306
  }
297
307
  })
298
308
  .catch(async (e: any) => {
299
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed refreshStatus with BLE Connection`);
300
- if (this.deviceLogging.includes('debug')) {
301
- this.errorLog(
302
- `${this.device.deviceType}: ${this.accessory.displayName} failed refreshStatus with BLE Connection,`
303
- + ` Error Message: ${superStringify(e.message)}`,
304
- );
305
- }
306
- if (this.platform.config.credentials?.token) {
307
- this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection`);
308
- this.SwitchToOpenAPI = true;
309
- this.openAPIRefreshStatus();
310
- }
311
309
  this.apiError(e);
310
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed BLERefreshStatus with ${this.device.connectionType}`
311
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
312
+ await this.BLEconnection(switchbot);
312
313
  });
313
314
  } else {
314
315
  await this.BLEconnection(switchbot);
315
316
  }
316
317
  }
317
318
 
318
- async getCustomBLEAddress(switchbot: any) {
319
- if (this.device.customBLEaddress && this.deviceLogging.includes('debug')) {
320
- (async () => {
321
- // Start to monitor advertisement packets
322
- await switchbot.startScan({
323
- model: 'i',
324
- });
325
- // Set an event handler
326
- switchbot.onadvertisement = (ad: any) => {
327
- this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} ad: ${superStringify(ad, null, ' ')}`);
328
- };
329
- await switchbot.wait(10000);
330
- // Stop to monitor
331
- switchbot.stopScan();
332
- })();
333
- }
334
- }
335
-
336
- async BLEconnection(switchbot: any): Promise<void> {
337
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`);
338
- if (this.platform.config.credentials?.token) {
339
- this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection`);
340
- this.SwitchToOpenAPI = true;
341
- await this.openAPIRefreshStatus();
342
- }
343
- }
344
-
345
319
  async openAPIRefreshStatus(): Promise<void> {
346
- if (this.platform.config.credentials?.token && this.OpenAPI) {
347
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} OpenAPI RefreshStatus`);
348
- try {
349
- const t = Date.now();
350
- const nonce = 'requestID';
351
- const data = this.platform.config.credentials?.token + t + nonce;
352
- const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
353
- const sign = signTerm.toString('base64');
354
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
355
- const options = {
356
- hostname: HostDomain,
357
- port: 443,
358
- path: `${DevicePath}/${this.device.deviceId}/status`,
359
- method: 'GET',
360
- headers: {
361
- Authorization: this.platform.config.credentials?.token,
362
- sign: sign,
363
- nonce: nonce,
364
- t: t,
365
- 'Content-Type': 'application/json',
366
- },
367
- };
368
- const req = https.request(options, (res) => {
369
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} statusCode: ${res.statusCode}`);
370
- let rawData = '';
371
- res.on('data', (d) => {
372
- rawData += d;
373
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
374
- });
375
- res.on('end', () => {
376
- try {
377
- this.deviceStatus = JSON.parse(rawData);
378
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus: ${superStringify(this.deviceStatus)}`);
379
- this.Humidity = this.deviceStatus.body.humidity!;
380
- this.Temperature = this.deviceStatus.body.temperature!;
381
- this.parseStatus();
382
- this.updateHomeKitCharacteristics();
383
- } catch (e: any) {
384
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
385
- }
386
- });
320
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus`);
321
+ try {
322
+ const t = Date.now();
323
+ const nonce = 'requestID';
324
+ const data = this.platform.config.credentials?.token + t + nonce;
325
+ const signTerm = crypto.createHmac('sha256', this.platform.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest();
326
+ const sign = signTerm.toString('base64');
327
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
328
+ const options = {
329
+ hostname: HostDomain,
330
+ port: 443,
331
+ path: `${DevicePath}/${this.device.deviceId}/status`,
332
+ method: 'GET',
333
+ headers: {
334
+ Authorization: this.platform.config.credentials?.token,
335
+ sign: sign,
336
+ nonce: nonce,
337
+ t: t,
338
+ 'Content-Type': 'application/json',
339
+ },
340
+ };
341
+ const req = https.request(options, (res) => {
342
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus statusCode: ${res.statusCode}`);
343
+ let rawData = '';
344
+ res.on('data', (d) => {
345
+ rawData += d;
346
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} d: ${d}`);
387
347
  });
388
- req.on('error', (e: any) => {
389
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
348
+ res.on('end', () => {
349
+ try {
350
+ this.deviceStatus = JSON.parse(rawData);
351
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIRefreshStatus: ${superStringify(this.deviceStatus)}`);
352
+ this.Humidity = this.deviceStatus.body.humidity!;
353
+ this.Temperature = this.deviceStatus.body.temperature!;
354
+ this.parseStatus();
355
+ this.updateHomeKitCharacteristics();
356
+ } catch (e: any) {
357
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
358
+ }
390
359
  });
391
- req.end();
392
- } catch (e: any) {
393
- this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed refreshStatus with OpenAPI Connection`);
394
- if (this.deviceLogging.includes('debug')) {
395
- this.errorLog(
396
- `${this.device.deviceType}: ${this.accessory.displayName} failed refreshStatus with OpenAPI Connection,` +
397
- ` Error Message: ${superStringify(e.message)}`,
398
- );
399
- }
400
- this.apiError(e);
401
- }
402
- } else {
403
- this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} OpenAPI is disabled, commands will not be sent.`);
360
+ });
361
+ req.on('error', (e: any) => {
362
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} error message: ${e.message}`);
363
+ });
364
+ req.end();
365
+ } catch (e: any) {
366
+ this.apiError(e);
367
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} failed openAPIRefreshStatus with ${this.device.connectionType}`
368
+ + ` Connection, Error Message: ${superStringify(e.message)}`);
404
369
  }
405
370
  }
406
371
 
@@ -408,51 +373,130 @@ export class MeterPlus {
408
373
  * Updates the status for each of the HomeKit Characteristics
409
374
  */
410
375
  async updateHomeKitCharacteristics(): Promise<void> {
376
+ const mqttmessage: string[] = [];
377
+ const entry = { time: Math.round(new Date().valueOf() / 1000) };
411
378
  if (!this.device.meter?.hide_humidity) {
412
379
  if (this.CurrentRelativeHumidity === undefined) {
413
380
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`);
414
381
  } else {
382
+ this.accessory.context.CurrentRelativeHumidity = this.CurrentRelativeHumidity;
415
383
  this.humidityservice?.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity);
416
384
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName}`
417
385
  + ` updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`);
386
+ mqttmessage.push(`"humidity": ${this.CurrentRelativeHumidity}`);
387
+ entry['humidity'] = this.CurrentRelativeHumidity;
418
388
  }
419
389
  }
420
390
  if (!this.device.meter?.hide_temperature) {
421
391
  if (this.CurrentTemperature === undefined) {
422
392
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentTemperature: ${this.CurrentTemperature}`);
423
393
  } else {
394
+ mqttmessage.push(`"temperature": ${this.CurrentTemperature}`);
395
+ entry['temp'] = this.CurrentTemperature;
396
+ this.accessory.context.CurrentTemperature = this.CurrentTemperature;
424
397
  this.temperatureservice?.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.CurrentTemperature);
425
398
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`);
426
399
  }
427
400
  }
428
- if (this.device.ble) {
401
+ if (this.BLE) {
429
402
  if (this.BatteryLevel === undefined) {
430
403
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}`);
431
404
  } else {
405
+ mqttmessage.push(`"battery": ${this.BatteryLevel}`);
406
+ this.accessory.context.BatteryLevel = this.BatteryLevel;
432
407
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.BatteryLevel);
433
408
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`);
434
409
  }
435
410
  if (this.StatusLowBattery === undefined) {
436
411
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`);
437
412
  } else {
413
+ mqttmessage.push(`"lowBattery": ${this.StatusLowBattery}`);
414
+ this.accessory.context.StatusLowBattery = this.StatusLowBattery;
438
415
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery);
439
416
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`);
440
417
  }
441
418
  }
419
+ this.mqttPublish(`{${mqttmessage.join(',')}}`);
420
+ if (this.CurrentRelativeHumidity > 0) {
421
+ // reject unreliable data
422
+ this.historyService?.addEntry(entry);
423
+ }
442
424
  }
443
425
 
444
- async apiError(e: any): Promise<void> {
445
- if (!this.device.meter?.hide_humidity) {
446
- this.humidityservice?.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, e);
426
+ /*
427
+ * Publish MQTT message for topics of
428
+ * 'homebridge-switchbot/meter/xx:xx:xx:xx:xx:xx'
429
+ */
430
+ mqttPublish(message: any) {
431
+ const mac = this.device.deviceId
432
+ ?.toLowerCase()
433
+ .match(/[\s\S]{1,2}/g)
434
+ ?.join(':');
435
+ const options = this.device.mqttPubOptions || {};
436
+ this.mqttClient?.publish(`homebridge-switchbot/meter/${mac}`, `${message}`, options);
437
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} MQTT message: ${message} options:${superStringify(options)}`);
438
+ }
439
+
440
+ /*
441
+ * Setup MQTT hadler if URL is specifed.
442
+ */
443
+ async setupMqtt(device: device & devicesConfig): Promise<void> {
444
+ if (device.mqttURL) {
445
+ try {
446
+ this.mqttClient = await connectAsync(device.mqttURL, device.mqttOptions || {});
447
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} MQTT connection has been established successfully.`);
448
+ this.mqttClient.on('error', (e: Error) => {
449
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Failed to publish MQTT messages. ${e}`);
450
+ });
451
+ } catch (e) {
452
+ this.mqttClient = null;
453
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Failed to establish MQTT connection. ${e}`);
454
+ }
447
455
  }
448
- if (!this.device.meter?.hide_temperature) {
449
- this.temperatureservice?.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, e);
456
+ }
457
+
458
+ /*
459
+ * Setup EVE history graph feature if enabled.
460
+ */
461
+ async setupHistoryService(device: device & devicesConfig): Promise<void> {
462
+ const mac = this.device
463
+ .deviceId!.match(/.{1,2}/g)!
464
+ .join(':')
465
+ .toLowerCase();
466
+ this.historyService = device.history
467
+ ? new this.platform.fakegatoAPI('room', this.accessory, {
468
+ log: this.platform.log,
469
+ storage: 'fs',
470
+ filename: `${hostname().split('.')[0]}_${mac}_persist.json`,
471
+ })
472
+ : null;
473
+ }
474
+
475
+ async getCustomBLEAddress(switchbot: any) {
476
+ if (this.device.customBLEaddress && this.deviceLogging.includes('debug')) {
477
+ (async () => {
478
+ // Start to monitor advertisement packets
479
+ await switchbot.startScan({
480
+ model: 'i',
481
+ });
482
+ // Set an event handler
483
+ switchbot.onadvertisement = (ad: any) => {
484
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} ad: ${superStringify(ad, null, ' ')}`);
485
+ };
486
+ await switchbot.wait(10000);
487
+ // Stop to monitor
488
+ switchbot.stopScan();
489
+ })();
450
490
  }
451
- if (this.device.ble) {
452
- this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, e);
453
- this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e);
491
+ }
492
+
493
+ async BLEconnection(switchbot: any): Promise<void> {
494
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`);
495
+ if (this.platform.config.credentials?.token && this.device.connectionType !== 'Both') {
496
+ this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI Connection`);
497
+ this.SwitchToOpenAPI = true;
498
+ await this.openAPIRefreshStatus();
454
499
  }
455
- //throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
456
500
  }
457
501
 
458
502
  model(device: device & devicesConfig): CharacteristicValue {
@@ -465,28 +509,126 @@ export class MeterPlus {
465
509
  return model;
466
510
  }
467
511
 
468
- async config(device: device & devicesConfig): Promise<void> {
469
- let config = {};
470
- if (device.meter) {
471
- config = device.meter;
512
+ async scan(device: device & devicesConfig): Promise<void> {
513
+ if (device.scanDuration) {
514
+ this.scanDuration = this.accessory.context.scanDuration = device.scanDuration;
515
+ if (this.BLE) {
516
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config scanDuration: ${this.scanDuration}`);
517
+ }
518
+ } else {
519
+ this.scanDuration = this.accessory.context.scanDuration = 1;
520
+ if (this.BLE) {
521
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default scanDuration: ${this.scanDuration}`);
522
+ }
472
523
  }
473
- if (device.ble !== undefined) {
474
- config['ble'] = device.ble;
524
+ }
525
+
526
+ async statusCode({ res }: { res: IncomingMessage }): Promise<void> {
527
+ switch (res.statusCode) {
528
+ case 151:
529
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command not supported by this device type.`);
530
+ break;
531
+ case 152:
532
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device not found.`);
533
+ break;
534
+ case 160:
535
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Command is not supported.`);
536
+ break;
537
+ case 161:
538
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} Device is offline.`);
539
+ this.offlineOff();
540
+ break;
541
+ case 171:
542
+ this.errorLog(`${this.device.deviceType}: ${this.accessory.displayName} is offline. Hub: ${this.device.hubDeviceId}`);
543
+ this.offlineOff();
544
+ break;
545
+ case 190:
546
+ this.errorLog(
547
+ `${this.device.deviceType}: ${this.accessory.displayName} Device internal error due to device states not synchronized with server,` +
548
+ ` Or command: ${superStringify(res)} format is invalid`,
549
+ );
550
+ break;
551
+ case 100:
552
+ if (this.platform.debugMode) {
553
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Command successfully sent.`);
554
+ }
555
+ break;
556
+ default:
557
+ if (this.platform.debugMode) {
558
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Unknown statusCode.`);
559
+ }
475
560
  }
476
- if (device.openAPI !== undefined) {
477
- config['openAPI'] = device.openAPI;
561
+ }
562
+
563
+ async offlineOff(): Promise<void> {
564
+ if (this.device.offline) {
565
+ if (!this.device.meter?.hide_humidity) {
566
+ this.humidityservice?.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity,
567
+ this.accessory.context.CurrentRelativeHumidity);
568
+ }
569
+ if (!this.device.meter?.hide_temperature) {
570
+ this.temperatureservice?.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.accessory.context.CurrentTemperature);
571
+ }
572
+ if (this.BLE) {
573
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.accessory.context.BatteryLevel);
574
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.accessory.context.StatusLowBattery);
575
+ }
576
+ await this.updateHomeKitCharacteristics();
478
577
  }
479
- if (device.logging !== undefined) {
480
- config['logging'] = device.logging;
578
+ }
579
+
580
+ async apiError(e: any): Promise<void> {
581
+ if (!this.device.meter?.hide_humidity) {
582
+ this.humidityservice?.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, e);
481
583
  }
482
- if (device.refreshRate !== undefined) {
483
- config['refreshRate'] = device.refreshRate;
584
+ if (!this.device.meter?.hide_temperature) {
585
+ this.temperatureservice?.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, e);
484
586
  }
485
- if (device.scanDuration !== undefined) {
486
- config['scanDuration'] = device.scanDuration;
587
+ if (this.BLE) {
588
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, e);
589
+ this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e);
487
590
  }
488
- if (Object.entries(config).length !== 0) {
489
- this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Config: ${superStringify(config)}`);
591
+ }
592
+
593
+ FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
594
+ let FirmwareRevision: string;
595
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName}`
596
+ + ` accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
597
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
598
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
599
+ if (accessory.context.FirmwareRevision) {
600
+ FirmwareRevision = accessory.context.FirmwareRevision;
601
+ } else if (device.firmware) {
602
+ FirmwareRevision = device.firmware;
603
+ } else {
604
+ FirmwareRevision = this.platform.version;
605
+ }
606
+ return FirmwareRevision;
607
+ }
608
+
609
+ private context() {
610
+ if (this.CurrentRelativeHumidity === undefined) {
611
+ this.CurrentRelativeHumidity = 0;
612
+ } else {
613
+ this.CurrentRelativeHumidity = this.accessory.context.CurrentRelativeHumidity;
614
+ }
615
+ if (this.CurrentTemperature === undefined) {
616
+ this.CurrentTemperature = 0;
617
+ } else {
618
+ this.CurrentTemperature = this.accessory.context.CurrentTemperature;
619
+ }
620
+ if (this.BLE) {
621
+ if (this.BatteryLevel === undefined) {
622
+ this.BatteryLevel = 100;
623
+ } else {
624
+ this.BatteryLevel = this.accessory.context.BatteryLevel;
625
+ }
626
+ if (this.StatusLowBattery === undefined) {
627
+ this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
628
+ this.accessory.context.StatusLowBattery = this.StatusLowBattery;
629
+ } else {
630
+ this.StatusLowBattery = this.accessory.context.StatusLowBattery;
631
+ }
490
632
  }
491
633
  }
492
634
 
@@ -500,26 +642,25 @@ export class MeterPlus {
500
642
  }
501
643
  }
502
644
 
503
- async openAPI() {
504
- if (!this.device.openAPI) {
505
- this.OpenAPI = true;
506
- } else {
507
- this.OpenAPI = this.device.openAPI;
645
+ async config(device: device & devicesConfig): Promise<void> {
646
+ let config = {};
647
+ if (device.meter) {
648
+ config = device.meter;
508
649
  }
509
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using OpenAPI: ${this.OpenAPI}`);
510
- }
511
-
512
- async scan(device: device & devicesConfig): Promise<void> {
513
- if (device.scanDuration) {
514
- this.scanDuration = this.accessory.context.scanDuration = device.scanDuration;
515
- if (device.ble) {
516
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config scanDuration: ${this.scanDuration}`);
517
- }
518
- } else {
519
- this.scanDuration = this.accessory.context.scanDuration = 1;
520
- if (this.device.ble) {
521
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default scanDuration: ${this.scanDuration}`);
522
- }
650
+ if (device.connectionType !== undefined) {
651
+ config['connectionType'] = device.connectionType;
652
+ }
653
+ if (device.logging !== undefined) {
654
+ config['logging'] = device.logging;
655
+ }
656
+ if (device.refreshRate !== undefined) {
657
+ config['refreshRate'] = device.refreshRate;
658
+ }
659
+ if (device.scanDuration !== undefined) {
660
+ config['scanDuration'] = device.scanDuration;
661
+ }
662
+ if (Object.entries(config).length !== 0) {
663
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Config: ${superStringify(config)}`);
523
664
  }
524
665
  }
525
666
 
@@ -539,22 +680,6 @@ export class MeterPlus {
539
680
  }
540
681
  }
541
682
 
542
- FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
543
- let FirmwareRevision: string;
544
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName}`
545
- + ` accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
546
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
547
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
548
- if (accessory.context.FirmwareRevision) {
549
- FirmwareRevision = accessory.context.FirmwareRevision;
550
- } else if (device.firmware) {
551
- FirmwareRevision = device.firmware;
552
- } else {
553
- FirmwareRevision = this.platform.version;
554
- }
555
- return FirmwareRevision;
556
- }
557
-
558
683
  /**
559
684
  * Logging for Device
560
685
  */