@switchbot/homebridge-switchbot 1.12.9-beta.28 → 1.12.9-beta.29

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.
@@ -5,6 +5,8 @@ import { debounceTime, skipWhile, take, tap } from 'rxjs/operators';
5
5
  import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
6
6
  import { DeviceURL, device, devicesConfig, serviceData, switchbot, deviceStatusResponse, payload, deviceStatus, ad } from '../settings';
7
7
  import { Context } from 'vm';
8
+ import { MqttClient } from 'mqtt';
9
+ import { connectAsync } from 'async-mqtt';
8
10
 
9
11
  export class Curtain {
10
12
  // Services
@@ -59,12 +61,16 @@ export class Curtain {
59
61
  curtainUpdateInProgress!: boolean;
60
62
  doCurtainUpdate!: Subject<void>;
61
63
 
64
+ //MQTT stuff
65
+ mqttClient: MqttClient | null = null;
66
+
62
67
  constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: device & devicesConfig) {
63
68
  // default placeholders
64
69
  this.logs(device);
65
70
  this.refreshRate(device);
66
71
  this.scan(device);
67
72
  this.config(device);
73
+ this.setupMqtt(device);
68
74
  this.CurrentPosition = 0;
69
75
  this.TargetPosition = 0;
70
76
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
@@ -201,6 +207,35 @@ export class Curtain {
201
207
  });
202
208
  }
203
209
 
210
+ /*
211
+ * Publish MQTT message for topics of
212
+ * 'homebridge-switchbot/curtain/xx:xx:xx:xx:xx:xx'
213
+ */
214
+ mqttPublish(topic: string, message: any) {
215
+ const mac = this.device.deviceId?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':');
216
+ const options = this.device.mqttPubOptions || {};
217
+ this.mqttClient?.publish(`homebridge-switchbot/curtain/${mac}/${topic}`, `${message}`, options);
218
+ this.debugLog(`Meter: ${this.accessory.displayName} MQTT message: ${topic}/${message} options:${JSON.stringify(options)}`);
219
+ }
220
+
221
+ /*
222
+ * Setup MQTT hadler if URL is specifed.
223
+ */
224
+ async setupMqtt(device: device & devicesConfig): Promise<void> {
225
+ if (device.mqttURL) {
226
+ try {
227
+ this.mqttClient = await connectAsync(device.mqttURL, device.mqttOptions || {});
228
+ this.debugLog(`Meter: ${this.accessory.displayName} MQTT connection has been established successfully.`)
229
+ this.mqttClient.on('error', (e: Error) => {
230
+ this.errorLog(`Meter: ${this.accessory.displayName} Failed to publish MQTT messages. ${e}`)
231
+ });
232
+ } catch (e) {
233
+ this.mqttClient = null;
234
+ this.errorLog(`Meter: ${this.accessory.displayName} Failed to establish MQTT connection. ${e}`)
235
+ }
236
+ }
237
+ }
238
+
204
239
  /**
205
240
  * Parse the device status from the SwitchBot api
206
241
  */
@@ -656,18 +691,21 @@ export class Curtain {
656
691
  } else {
657
692
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.CurrentPosition, Number(this.CurrentPosition));
658
693
  this.debugLog(`Curtain: ${this.accessory.displayName} updateCharacteristic CurrentPosition: ${this.CurrentPosition}`);
694
+ this.mqttPublish('CurrentPosition', this.CurrentPosition);
659
695
  }
660
696
  if (this.PositionState === undefined) {
661
697
  this.debugLog(`Curtain: ${this.accessory.displayName} PositionState: ${this.PositionState}`);
662
698
  } else {
663
699
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.PositionState, Number(this.PositionState));
664
700
  this.debugLog(`Curtain: ${this.accessory.displayName} updateCharacteristic PositionState: ${this.PositionState}`);
701
+ this.mqttPublish('PositionState', this.PositionState);
665
702
  }
666
703
  if (this.TargetPosition === undefined || Number.isNaN(this.TargetPosition)) {
667
704
  this.debugLog(`Curtain: ${this.accessory.displayName} TargetPosition: ${this.TargetPosition}`);
668
705
  } else {
669
706
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.TargetPosition, Number(this.TargetPosition));
670
707
  this.debugLog(`Curtain: ${this.accessory.displayName} updateCharacteristic TargetPosition: ${this.TargetPosition}`);
708
+ this.mqttPublish('TargetPosition', this.TargetPosition);
671
709
  }
672
710
  if (!this.device.curtain?.hide_lightsensor) {
673
711
  if (this.CurrentAmbientLightLevel === undefined || Number.isNaN(this.CurrentAmbientLightLevel)) {
@@ -675,6 +713,7 @@ export class Curtain {
675
713
  } else {
676
714
  this.lightSensorService?.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, this.CurrentAmbientLightLevel);
677
715
  this.debugLog(`Curtain: ${this.accessory.displayName}` + ` updateCharacteristic CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
716
+ this.mqttPublish('CurrentAmbientLightLevel', this.CurrentAmbientLightLevel);
678
717
  }
679
718
  }
680
719
  if (this.device.ble) {
@@ -683,12 +722,14 @@ export class Curtain {
683
722
  } else {
684
723
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.BatteryLevel);
685
724
  this.debugLog(`Curtain: ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`);
725
+ this.mqttPublish('BatteryLevel', this.BatteryLevel);
686
726
  }
687
727
  if (this.StatusLowBattery === undefined) {
688
728
  this.debugLog(`Curtain: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`);
689
729
  } else {
690
730
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery);
691
731
  this.debugLog(`Curtain: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`);
732
+ this.mqttPublish('StatusLowBattery', this.StatusLowBattery);
692
733
  }
693
734
  }
694
735
  }
@@ -745,6 +786,7 @@ export class Curtain {
745
786
  this.debugLog(`Curtain: ${this.accessory.displayName} TargetPosition: ${value}`);
746
787
 
747
788
  this.TargetPosition = value;
789
+ this.mqttPublish('TargetPosition', this.TargetPosition);
748
790
 
749
791
  await this.setMinMax();
750
792
  if (value > this.CurrentPosition) {
@@ -4,6 +4,9 @@ import { SwitchBotPlatform } from '../platform';
4
4
  import { Service, PlatformAccessory, Units, CharacteristicValue } from 'homebridge';
5
5
  import { DeviceURL, device, devicesConfig, serviceData, ad, switchbot, deviceStatusResponse, temperature, deviceStatus } from '../settings';
6
6
  import { Context } from 'vm';
7
+ import { MqttClient } from 'mqtt';
8
+ import { connectAsync } from 'async-mqtt';
9
+ import { hostname } from "os";
7
10
 
8
11
  /**
9
12
  * Platform Accessory
@@ -51,12 +54,20 @@ export class Meter {
51
54
  meterUpdateInProgress!: boolean;
52
55
  doMeterUpdate: Subject<void>;
53
56
 
57
+ //MQTT stuff
58
+ mqttClient: MqttClient | null = null;
59
+
60
+ // EVE history service handler
61
+ historyService: any;
62
+
54
63
  constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: device & devicesConfig) {
55
64
  // default placeholders
56
65
  this.logs(device);
57
66
  this.scan(device);
67
+ this.setupHistoryService(device);
58
68
  this.refreshRate(device);
59
69
  this.config(device);
70
+ this.setupMqtt(device);
60
71
  if (this.CurrentRelativeHumidity === undefined) {
61
72
  this.CurrentRelativeHumidity = 0;
62
73
  } else {
@@ -166,6 +177,48 @@ export class Meter {
166
177
  });
167
178
  }
168
179
 
180
+ /*
181
+ * Publish MQTT message for topics of
182
+ * 'homebridge-switchbot/meter/xx:xx:xx:xx:xx:xx'
183
+ */
184
+ mqttPublish(message: any) {
185
+ const mac = this.device.deviceId?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':');
186
+ const options = this.device.mqttPubOptions || {};
187
+ this.mqttClient?.publish(`homebridge-switchbot/meter/${mac}`, `${message}`, options);
188
+ this.debugLog(`Meter: ${this.accessory.displayName} MQTT message: ${message} options:${JSON.stringify(options)}`);
189
+ }
190
+
191
+ /*
192
+ * Setup MQTT hadler if URL is specifed.
193
+ */
194
+ async setupMqtt(device: device & devicesConfig): Promise<void> {
195
+ if (device.mqttURL) {
196
+ try {
197
+ this.mqttClient = await connectAsync(device.mqttURL, device.mqttOptions || {});
198
+ this.debugLog(`Meter: ${this.accessory.displayName} MQTT connection has been established successfully.`)
199
+ this.mqttClient.on('error', (e: Error) => {
200
+ this.errorLog(`Meter: ${this.accessory.displayName} Failed to publish MQTT messages. ${e}`)
201
+ });
202
+ } catch (e) {
203
+ this.mqttClient = null;
204
+ this.errorLog(`Meter: ${this.accessory.displayName} Failed to establish MQTT connection. ${e}`)
205
+ }
206
+ }
207
+ }
208
+
209
+ /*
210
+ * Setup EVE history graph feature if enabled.
211
+ */
212
+ async setupHistoryService(device: device & devicesConfig): Promise<void> {
213
+ const mac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase();
214
+ this.historyService = device.history ?
215
+ new this.platform.fakegatoAPI('room', this.accessory,
216
+ {log: this.platform.log, storage: 'fs',
217
+ filename: `${hostname().split(".")[0]}_${mac}_persist.json`
218
+ }) :
219
+ null;
220
+ }
221
+
169
222
  /**
170
223
  * Parse the device status from the SwitchBot api
171
224
  */
@@ -187,7 +240,7 @@ export class Meter {
187
240
  } else {
188
241
  this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
189
242
  }
190
- this.debugLog(`${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}, StatusLowBattery: ${this.StatusLowBattery}`);
243
+ this.debugLog(`Meter: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}, StatusLowBattery: ${this.StatusLowBattery}`);
191
244
 
192
245
  // Humidity
193
246
  if (!this.device.meter?.hide_humidity) {
@@ -261,10 +314,12 @@ export class Meter {
261
314
  this.infoLog(`Meter: ${this.accessory.displayName} BLE Address Found: ${this.address}`);
262
315
  this.infoLog(`Meter: ${this.accessory.displayName} Config BLE Address: ${this.device.bleMac}`);
263
316
  }
264
- this.serviceData = ad.serviceData;
265
- this.temperature = ad.serviceData.temperature!.c;
266
- this.humidity = ad.serviceData.humidity;
267
- this.battery = ad.serviceData.battery;
317
+ if (ad.serviceData.humidity! > 0) { // reject unreliable data
318
+ this.serviceData = ad.serviceData;
319
+ this.temperature = ad.serviceData.temperature!.c;
320
+ this.humidity = ad.serviceData.humidity;
321
+ this.battery = ad.serviceData.battery;
322
+ }
268
323
  this.debugLog(`Meter: ${this.accessory.displayName} serviceData: ${JSON.stringify(ad.serviceData)}`);
269
324
  this.debugLog(
270
325
  `Meter: ${this.accessory.displayName} model: ${ad.serviceData.model}, modelName: ${ad.serviceData.modelName}, ` +
@@ -365,12 +420,16 @@ export class Meter {
365
420
  * Updates the status for each of the HomeKit Characteristics
366
421
  */
367
422
  async updateHomeKitCharacteristics(): Promise<void> {
423
+ let mqttmessage: string[] = [];
424
+ let entry = {time: Math.round(new Date().valueOf()/1000)};
368
425
  if (!this.device.meter?.hide_humidity) {
369
426
  if (this.CurrentRelativeHumidity === undefined) {
370
427
  this.debugLog(`Meter: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`);
371
428
  } else {
372
429
  this.humidityservice?.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity);
373
430
  this.debugLog(`Meter: ${this.accessory.displayName} updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`);
431
+ mqttmessage.push(`"humidity": ${this.CurrentRelativeHumidity}`);
432
+ entry["humidity"] = this.CurrentRelativeHumidity;
374
433
  }
375
434
  }
376
435
  if (!this.device.meter?.hide_temperature) {
@@ -379,6 +438,8 @@ export class Meter {
379
438
  } else {
380
439
  this.temperatureservice?.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.CurrentTemperature);
381
440
  this.debugLog(`Meter: ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`);
441
+ mqttmessage.push(`"temperature": ${this.CurrentTemperature}`);
442
+ entry["temp"] = this.CurrentTemperature;
382
443
  }
383
444
  }
384
445
  if (this.device.ble) {
@@ -387,14 +448,20 @@ export class Meter {
387
448
  } else {
388
449
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.BatteryLevel);
389
450
  this.debugLog(`Meter: ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`);
451
+ mqttmessage.push(`"battery": ${this.BatteryLevel}`);
390
452
  }
391
453
  if (this.StatusLowBattery === undefined) {
392
454
  this.debugLog(`Meter: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`);
393
455
  } else {
394
456
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery);
395
457
  this.debugLog(`Meter: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`);
458
+ mqttmessage.push(`"lowBattery": ${this.StatusLowBattery}`);
396
459
  }
397
460
  }
461
+ this.mqttPublish(`{${mqttmessage.join(',')}}`)
462
+ if (this.CurrentRelativeHumidity > 0) { // reject unreliable data
463
+ this.historyService?.addEntry(entry);
464
+ }
398
465
  }
399
466
 
400
467
  async apiError(e: any): Promise<void> {
package/src/platform.ts CHANGED
@@ -21,6 +21,7 @@ import { AirConditioner } from './irdevice/airconditioner';
21
21
  import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
22
22
  import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, Service, Characteristic } from 'homebridge';
23
23
  import { PLATFORM_NAME, PLUGIN_NAME, DeviceURL, irdevice, device, SwitchBotPlatformConfig, deviceResponses, devicesConfig } from './settings';
24
+ import fakegato from 'fakegato-history';
24
25
 
25
26
  /**
26
27
  * HomebridgePlatform
@@ -44,6 +45,8 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
44
45
  debugMode!: boolean;
45
46
  platformLogging?: string;
46
47
 
48
+ public readonly fakegatoAPI: any;
49
+
47
50
  constructor(public readonly log: Logger, public readonly config: SwitchBotPlatformConfig, public readonly api: API) {
48
51
  this.logs();
49
52
  this.debugLog('Finished initializing platform:', this.config.name);
@@ -76,6 +79,9 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
76
79
  return request;
77
80
  });
78
81
 
82
+ // import fakegato-history module
83
+ this.fakegatoAPI = fakegato(api);
84
+
79
85
  // When this event is fired it means Homebridge has restored all cached accessories from disk.
80
86
  // Dynamic Platform plugins should only register new accessories after this event was fired,
81
87
  // in order to ensure they weren't added to homebridge already. This event can also be used
package/src/settings.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { MacAddress, PlatformConfig } from 'homebridge';
2
+ import { IClientOptions } from 'async-mqtt';
2
3
  /**
3
4
  * This is the name of the platform that users will use to register the plugin in the Homebridge config.json
4
5
  */
@@ -58,6 +59,10 @@ export interface devicesConfig extends device {
58
59
  scanDuration?: number;
59
60
  hide_device?: boolean;
60
61
  offline?: boolean;
62
+ mqttURL?: string;
63
+ mqttOptions?: IClientOptions;
64
+ mqttPubOptions?: IClientOptions;
65
+ history?: boolean;
61
66
  }
62
67
 
63
68
  export type meter = {