@switchbot/homebridge-switchbot 1.12.9-beta.3 → 1.12.9-beta.30

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 (74) hide show
  1. package/.github/workflows/codeql-analysis.yml +3 -3
  2. package/.github/workflows/dependabot.yml +17 -0
  3. package/.github/workflows/nodejs-beta.yml +0 -3
  4. package/.github/workflows/nodejs.yml +0 -3
  5. package/CHANGELOG.md +5 -0
  6. package/config.schema.json +18 -0
  7. package/dist/device/bot.d.ts +3 -0
  8. package/dist/device/bot.d.ts.map +1 -1
  9. package/dist/device/bot.js +19 -1
  10. package/dist/device/bot.js.map +1 -1
  11. package/dist/device/colorbulb.d.ts +3 -0
  12. package/dist/device/colorbulb.d.ts.map +1 -1
  13. package/dist/device/colorbulb.js +19 -1
  14. package/dist/device/colorbulb.js.map +1 -1
  15. package/dist/device/contact.d.ts +3 -0
  16. package/dist/device/contact.d.ts.map +1 -1
  17. package/dist/device/contact.js +19 -1
  18. package/dist/device/contact.js.map +1 -1
  19. package/dist/device/curtain.d.ts +6 -0
  20. package/dist/device/curtain.d.ts.map +1 -1
  21. package/dist/device/curtain.js +58 -1
  22. package/dist/device/curtain.js.map +1 -1
  23. package/dist/device/humidifier.d.ts +3 -0
  24. package/dist/device/humidifier.d.ts.map +1 -1
  25. package/dist/device/humidifier.js +19 -2
  26. package/dist/device/humidifier.js.map +1 -1
  27. package/dist/device/lock.d.ts +4 -0
  28. package/dist/device/lock.d.ts.map +1 -1
  29. package/dist/device/lock.js +60 -6
  30. package/dist/device/lock.js.map +1 -1
  31. package/dist/device/meter.d.ts +9 -0
  32. package/dist/device/meter.d.ts.map +1 -1
  33. package/dist/device/meter.js +83 -6
  34. package/dist/device/meter.js.map +1 -1
  35. package/dist/device/meterplus.d.ts +3 -0
  36. package/dist/device/meterplus.d.ts.map +1 -1
  37. package/dist/device/meterplus.js +19 -1
  38. package/dist/device/meterplus.js.map +1 -1
  39. package/dist/device/motion.d.ts +3 -0
  40. package/dist/device/motion.d.ts.map +1 -1
  41. package/dist/device/motion.js +19 -1
  42. package/dist/device/motion.js.map +1 -1
  43. package/dist/device/plug.d.ts +3 -0
  44. package/dist/device/plug.d.ts.map +1 -1
  45. package/dist/device/plug.js +19 -1
  46. package/dist/device/plug.js.map +1 -1
  47. package/dist/device/striplight.d.ts +3 -0
  48. package/dist/device/striplight.d.ts.map +1 -1
  49. package/dist/device/striplight.js +19 -1
  50. package/dist/device/striplight.js.map +1 -1
  51. package/dist/irdevice/other.js +3 -3
  52. package/dist/irdevice/other.js.map +1 -1
  53. package/dist/platform.d.ts +1 -0
  54. package/dist/platform.d.ts.map +1 -1
  55. package/dist/platform.js +44 -41
  56. package/dist/platform.js.map +1 -1
  57. package/dist/settings.d.ts +6 -0
  58. package/dist/settings.d.ts.map +1 -1
  59. package/dist/settings.js.map +1 -1
  60. package/package.json +16 -14
  61. package/src/device/bot.ts +19 -1
  62. package/src/device/colorbulb.ts +19 -1
  63. package/src/device/contact.ts +19 -1
  64. package/src/device/curtain.ts +61 -1
  65. package/src/device/humidifier.ts +19 -2
  66. package/src/device/lock.ts +61 -6
  67. package/src/device/meter.ts +91 -6
  68. package/src/device/meterplus.ts +19 -1
  69. package/src/device/motion.ts +19 -1
  70. package/src/device/plug.ts +19 -1
  71. package/src/device/striplight.ts +19 -1
  72. package/src/irdevice/other.ts +3 -3
  73. package/src/platform.ts +47 -41
  74. package/src/settings.ts +6 -0
@@ -4,6 +4,7 @@ import { SwitchBotPlatform } from '../platform';
4
4
  import { debounceTime, skipWhile, tap } from 'rxjs/operators';
5
5
  import { Service, PlatformAccessory, CharacteristicValue, ControllerConstructor, Controller, ControllerServiceMap } from 'homebridge';
6
6
  import { DeviceURL, device, devicesConfig, switchbot, deviceStatusResponse, payload, hs2rgb, rgb2hs, m2hs, deviceStatus } from '../settings';
7
+ import { Context } from 'vm';
7
8
 
8
9
  /**
9
10
  * Platform Accessory
@@ -83,7 +84,9 @@ export class ColorBulb {
83
84
  .getService(this.platform.Service.AccessoryInformation)!
84
85
  .setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
85
86
  .setCharacteristic(this.platform.Characteristic.Model, 'W1401400')
86
- .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
87
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
88
+ .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
89
+ .getCharacteristic(this.platform.Characteristic.FirmwareRevision).updateValue(this.FirmwareRevision(accessory, device));
87
90
 
88
91
  // get the Lightbulb service if it exists, otherwise create a new Lightbulb service
89
92
  // you can create multiple services for each accessory
@@ -671,6 +674,21 @@ export class ColorBulb {
671
674
  }
672
675
  }
673
676
 
677
+ FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
678
+ let FirmwareRevision: string;
679
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
680
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
681
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
682
+ if (accessory.context.FirmwareRevision) {
683
+ FirmwareRevision = accessory.context.FirmwareRevision;
684
+ } else if (device.firmware) {
685
+ FirmwareRevision = device.firmware;
686
+ } else {
687
+ FirmwareRevision = this.platform.version;
688
+ }
689
+ return FirmwareRevision;
690
+ }
691
+
674
692
  async adaptiveLighting(device: device & devicesConfig): Promise<void> {
675
693
  if (device.colorbulb?.adaptiveLightingShift) {
676
694
  this.adaptiveLightingShift = device.colorbulb.adaptiveLightingShift;
@@ -3,6 +3,7 @@ import { skipWhile } from 'rxjs/operators';
3
3
  import { SwitchBotPlatform } from '../platform';
4
4
  import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
5
5
  import { DeviceURL, device, devicesConfig, serviceData, switchbot, deviceStatusResponse, deviceStatus, ad } from '../settings';
6
+ import { Context } from 'vm';
6
7
 
7
8
  /**
8
9
  * Platform Accessory
@@ -71,7 +72,9 @@ export class Contact {
71
72
  .getService(this.platform.Service.AccessoryInformation)!
72
73
  .setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
73
74
  .setCharacteristic(this.platform.Characteristic.Model, 'W1201500')
74
- .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
75
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
76
+ .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
77
+ .getCharacteristic(this.platform.Characteristic.FirmwareRevision).updateValue(this.FirmwareRevision(accessory, device));
75
78
 
76
79
  // get the Contact service if it exists, otherwise create a new Contact service
77
80
  // you can create multiple services for each accessory
@@ -509,6 +512,21 @@ export class Contact {
509
512
  }
510
513
  }
511
514
 
515
+ FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
516
+ let FirmwareRevision: string;
517
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
518
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
519
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
520
+ if (accessory.context.FirmwareRevision) {
521
+ FirmwareRevision = accessory.context.FirmwareRevision;
522
+ } else if (device.firmware) {
523
+ FirmwareRevision = device.firmware;
524
+ } else {
525
+ FirmwareRevision = this.platform.version;
526
+ }
527
+ return FirmwareRevision;
528
+ }
529
+
512
530
  minLux(): number {
513
531
  if (this.device.contact?.set_minLux) {
514
532
  this.set_minLux = this.device.contact!.set_minLux!;
@@ -4,6 +4,9 @@ import { SwitchBotPlatform } from '../platform';
4
4
  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
+ import { Context } from 'vm';
8
+ import { MqttClient } from 'mqtt';
9
+ import { connectAsync } from 'async-mqtt';
7
10
 
8
11
  export class Curtain {
9
12
  // Services
@@ -58,12 +61,16 @@ export class Curtain {
58
61
  curtainUpdateInProgress!: boolean;
59
62
  doCurtainUpdate!: Subject<void>;
60
63
 
64
+ //MQTT stuff
65
+ mqttClient: MqttClient | null = null;
66
+
61
67
  constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: device & devicesConfig) {
62
68
  // default placeholders
63
69
  this.logs(device);
64
70
  this.refreshRate(device);
65
71
  this.scan(device);
66
72
  this.config(device);
73
+ this.setupMqtt(device);
67
74
  this.CurrentPosition = 0;
68
75
  this.TargetPosition = 0;
69
76
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
@@ -81,7 +88,9 @@ export class Curtain {
81
88
  .getService(this.platform.Service.AccessoryInformation)!
82
89
  .setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
83
90
  .setCharacteristic(this.platform.Characteristic.Model, 'W0701600')
84
- .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId);
91
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId)
92
+ .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
93
+ .getCharacteristic(this.platform.Characteristic.FirmwareRevision).updateValue(this.FirmwareRevision(accessory, device));
85
94
 
86
95
  // get the WindowCovering service if it exists, otherwise create a new WindowCovering service
87
96
  // you can create multiple services for each accessory
@@ -198,6 +207,35 @@ export class Curtain {
198
207
  });
199
208
  }
200
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
+
201
239
  /**
202
240
  * Parse the device status from the SwitchBot api
203
241
  */
@@ -653,18 +691,21 @@ export class Curtain {
653
691
  } else {
654
692
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.CurrentPosition, Number(this.CurrentPosition));
655
693
  this.debugLog(`Curtain: ${this.accessory.displayName} updateCharacteristic CurrentPosition: ${this.CurrentPosition}`);
694
+ this.mqttPublish('CurrentPosition', this.CurrentPosition);
656
695
  }
657
696
  if (this.PositionState === undefined) {
658
697
  this.debugLog(`Curtain: ${this.accessory.displayName} PositionState: ${this.PositionState}`);
659
698
  } else {
660
699
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.PositionState, Number(this.PositionState));
661
700
  this.debugLog(`Curtain: ${this.accessory.displayName} updateCharacteristic PositionState: ${this.PositionState}`);
701
+ this.mqttPublish('PositionState', this.PositionState);
662
702
  }
663
703
  if (this.TargetPosition === undefined || Number.isNaN(this.TargetPosition)) {
664
704
  this.debugLog(`Curtain: ${this.accessory.displayName} TargetPosition: ${this.TargetPosition}`);
665
705
  } else {
666
706
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.TargetPosition, Number(this.TargetPosition));
667
707
  this.debugLog(`Curtain: ${this.accessory.displayName} updateCharacteristic TargetPosition: ${this.TargetPosition}`);
708
+ this.mqttPublish('TargetPosition', this.TargetPosition);
668
709
  }
669
710
  if (!this.device.curtain?.hide_lightsensor) {
670
711
  if (this.CurrentAmbientLightLevel === undefined || Number.isNaN(this.CurrentAmbientLightLevel)) {
@@ -672,6 +713,7 @@ export class Curtain {
672
713
  } else {
673
714
  this.lightSensorService?.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, this.CurrentAmbientLightLevel);
674
715
  this.debugLog(`Curtain: ${this.accessory.displayName}` + ` updateCharacteristic CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
716
+ this.mqttPublish('CurrentAmbientLightLevel', this.CurrentAmbientLightLevel);
675
717
  }
676
718
  }
677
719
  if (this.device.ble) {
@@ -680,12 +722,14 @@ export class Curtain {
680
722
  } else {
681
723
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.BatteryLevel);
682
724
  this.debugLog(`Curtain: ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`);
725
+ this.mqttPublish('BatteryLevel', this.BatteryLevel);
683
726
  }
684
727
  if (this.StatusLowBattery === undefined) {
685
728
  this.debugLog(`Curtain: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`);
686
729
  } else {
687
730
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery);
688
731
  this.debugLog(`Curtain: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`);
732
+ this.mqttPublish('StatusLowBattery', this.StatusLowBattery);
689
733
  }
690
734
  }
691
735
  }
@@ -742,6 +786,7 @@ export class Curtain {
742
786
  this.debugLog(`Curtain: ${this.accessory.displayName} TargetPosition: ${value}`);
743
787
 
744
788
  this.TargetPosition = value;
789
+ this.mqttPublish('TargetPosition', this.TargetPosition);
745
790
 
746
791
  await this.setMinMax();
747
792
  if (value > this.CurrentPosition) {
@@ -869,6 +914,21 @@ export class Curtain {
869
914
  }
870
915
  }
871
916
 
917
+ FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
918
+ let FirmwareRevision: string;
919
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
920
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
921
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
922
+ if (accessory.context.FirmwareRevision) {
923
+ FirmwareRevision = accessory.context.FirmwareRevision;
924
+ } else if (device.firmware) {
925
+ FirmwareRevision = device.firmware;
926
+ } else {
927
+ FirmwareRevision = this.platform.version;
928
+ }
929
+ return FirmwareRevision;
930
+ }
931
+
872
932
  minStep(device: device & devicesConfig): number {
873
933
  if (device.curtain?.set_minStep) {
874
934
  this.set_minStep = device.curtain?.set_minStep;
@@ -4,6 +4,7 @@ import { SwitchBotPlatform } from '../platform';
4
4
  import { debounceTime, skipWhile, take, tap } from 'rxjs/operators';
5
5
  import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
6
6
  import { DeviceURL, device, devicesConfig, serviceData, ad, deviceStatusResponse, payload, deviceStatus } from '../settings';
7
+ import { Context } from 'vm';
7
8
 
8
9
  /**
9
10
  * Platform Accessory
@@ -77,7 +78,9 @@ export class Humidifier {
77
78
  .getService(this.platform.Service.AccessoryInformation)!
78
79
  .setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
79
80
  .setCharacteristic(this.platform.Characteristic.Model, 'W0801800')
80
- .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
81
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
82
+ .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
83
+ .getCharacteristic(this.platform.Characteristic.FirmwareRevision).updateValue(this.FirmwareRevision(accessory, device));
81
84
 
82
85
  // get the service if it exists, otherwise create a new service
83
86
  // you can create multiple services for each accessory
@@ -124,7 +127,6 @@ export class Humidifier {
124
127
  })
125
128
  .onSet(this.handleRelativeHumidityHumidifierThresholdSet.bind(this));
126
129
 
127
- // create a new Temperature Sensor service
128
130
  // Temperature Sensor Service
129
131
  if (device.humidifier?.hide_temperature || device.ble) {
130
132
  this.debugLog(`Humidifier: ${accessory.displayName} Removing Temperature Sensor Service`);
@@ -812,6 +814,21 @@ export class Humidifier {
812
814
  }
813
815
  }
814
816
 
817
+ FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
818
+ let FirmwareRevision: string;
819
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
820
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
821
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
822
+ if (accessory.context.FirmwareRevision) {
823
+ FirmwareRevision = accessory.context.FirmwareRevision;
824
+ } else if (device.firmware) {
825
+ FirmwareRevision = device.firmware;
826
+ } else {
827
+ FirmwareRevision = this.platform.version;
828
+ }
829
+ return FirmwareRevision;
830
+ }
831
+
815
832
  minStep(): number {
816
833
  if (this.device.humidifier?.set_minStep) {
817
834
  this.set_minStep = this.device.humidifier?.set_minStep;
@@ -4,6 +4,7 @@ import { SwitchBotPlatform } from '../platform';
4
4
  import { debounceTime, skipWhile, take, tap } from 'rxjs/operators';
5
5
  import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
6
6
  import { DeviceURL, device, devicesConfig, deviceStatusResponse, payload, deviceStatus } from '../settings';
7
+ import { Context } from 'vm';
7
8
 
8
9
  export class Lock {
9
10
  // Services
@@ -11,6 +12,7 @@ export class Lock {
11
12
  contactSensorService?: Service;
12
13
 
13
14
  // Characteristic Values
15
+ ContactSensorState!: CharacteristicValue;
14
16
  LockCurrentState!: CharacteristicValue;
15
17
  LockTargetState!: CharacteristicValue;
16
18
  LockTargetStateCached!: CharacteristicValue;
@@ -53,7 +55,9 @@ export class Lock {
53
55
  .getService(this.platform.Service.AccessoryInformation)!
54
56
  .setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
55
57
  .setCharacteristic(this.platform.Characteristic.Model, 'W1601700')
56
- .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
58
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
59
+ .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
60
+ .getCharacteristic(this.platform.Characteristic.FirmwareRevision).updateValue(this.FirmwareRevision(accessory, device));
57
61
 
58
62
  // get the LockMechanism service if it exists, otherwise create a new LockMechanism service
59
63
  // you can create multiple services for each accessory
@@ -74,6 +78,22 @@ export class Lock {
74
78
  // create handlers for required characteristics
75
79
  this.lockService.getCharacteristic(this.platform.Characteristic.LockCurrentState).onSet(this.LockTargetStateSet.bind(this));
76
80
 
81
+ // Contact Sensor Service
82
+ if (device.lock?.hide_contactsensor) {
83
+ this.debugLog(`Lock: ${accessory.displayName} Removing Contact Sensor Service`);
84
+ this.contactSensorService = this.accessory.getService(this.platform.Service.ContactSensor);
85
+ accessory.removeService(this.contactSensorService!);
86
+ } else if (!this.contactSensorService) {
87
+ this.debugLog(`Lock: ${accessory.displayName} Add Contact Sensor Service`);
88
+ (this.contactSensorService =
89
+ this.accessory.getService(this.platform.Service.ContactSensor) || this.accessory.addService(this.platform.Service.ContactSensor)),
90
+ `${accessory.displayName} Contact Sensor`;
91
+
92
+ this.contactSensorService.setCharacteristic(this.platform.Characteristic.Name, `${accessory.displayName} Contact Sensor`);
93
+ } else {
94
+ this.debugLog(`Lock: ${accessory.displayName} Contact Sensor Service Not Added`);
95
+ }
96
+
77
97
  // Update Homekit
78
98
  this.updateHomeKitCharacteristics();
79
99
 
@@ -107,13 +127,24 @@ export class Lock {
107
127
  });
108
128
  }
109
129
 
130
+ //{"deviceId":"DBDC23B53139","deviceType":"Smart Lock","hubDeviceId":"E68B14109DA2","lockState":"locked","doorState":"opened","calibrate":true}
131
+
110
132
  async parseStatus(): Promise<void> {
111
133
  switch (this.lockState) {
112
- case 'on':
134
+ case 'locked':
135
+ this.LockCurrentState = this.platform.Characteristic.LockCurrentState.SECURED;
136
+ this.LockTargetState = this.platform.Characteristic.LockTargetState.SECURED;
137
+ break;
138
+ default:
113
139
  this.LockCurrentState = this.platform.Characteristic.LockCurrentState.UNSECURED;
140
+ this.LockTargetState = this.platform.Characteristic.LockTargetState.UNSECURED;
141
+ }
142
+ switch (this.doorState) {
143
+ case 'opened':
144
+ this.ContactSensorState = this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
114
145
  break;
115
146
  default:
116
- this.LockCurrentState = this.platform.Characteristic.LockCurrentState.SECURED;
147
+ this.ContactSensorState = this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED;
117
148
  }
118
149
  this.debugLog(`Lock: ${this.accessory.displayName} On: ${this.LockTargetState}`);
119
150
  }
@@ -123,9 +154,7 @@ export class Lock {
123
154
  this.deviceStatus = (await this.platform.axios.get(`${DeviceURL}/${this.device.deviceId}/status`)).data;
124
155
  this.debugLog(`Lock: ${this.accessory.displayName} refreshStatus: ${JSON.stringify(this.deviceStatus)}`);
125
156
  this.lockState = this.deviceStatus.body.lockState;
126
- this.warnLog(`Lock: ${this.accessory.displayName} lockState: ${JSON.stringify(this.lockState)} (COPY THIS LOG)`);
127
157
  this.doorState = this.deviceStatus.body.doorState;
128
- this.debugLog(`Lock: ${this.accessory.displayName} doorState: ${JSON.stringify(this.doorState)} (COPY THIS LOG)`);
129
158
  this.parseStatus();
130
159
  this.updateHomeKitCharacteristics();
131
160
  } catch (e: any) {
@@ -178,6 +207,14 @@ export class Lock {
178
207
  }
179
208
 
180
209
  async updateHomeKitCharacteristics(): Promise<void> {
210
+ if (!this.device.lock?.hide_contactsensor) {
211
+ if (this.ContactSensorState === undefined) {
212
+ this.debugLog(`Lock: ${this.accessory.displayName} ContactSensorState: ${this.ContactSensorState}`);
213
+ } else {
214
+ this.contactSensorService?.updateCharacteristic(this.platform.Characteristic.ContactSensorState, this.ContactSensorState);
215
+ this.debugLog(`Lock: ${this.accessory.displayName} updateCharacteristic ContactSensorState: ${this.ContactSensorState}`);
216
+ }
217
+ }
181
218
  if (this.LockTargetState === undefined) {
182
219
  this.debugLog(`Lock: ${this.accessory.displayName} LockTargetState: ${this.LockTargetState}`);
183
220
  } else {
@@ -188,11 +225,14 @@ export class Lock {
188
225
  this.debugLog(`Lock: ${this.accessory.displayName} LockCurrentState: ${this.LockCurrentState}`);
189
226
  } else {
190
227
  this.lockService.updateCharacteristic(this.platform.Characteristic.LockCurrentState, this.LockCurrentState);
191
- this.debugLog(`Lock: ${this.accessory.displayName} updateCharacteristic LockTargetState: ${this.LockCurrentState}`);
228
+ this.debugLog(`Lock: ${this.accessory.displayName} updateCharacteristic LockCurrentState: ${this.LockCurrentState}`);
192
229
  }
193
230
  }
194
231
 
195
232
  async apiError(e: any): Promise<void> {
233
+ if (!this.device.lock?.hide_contactsensor) {
234
+ this.contactSensorService?.updateCharacteristic(this.platform.Characteristic.ContactSensorState, e);
235
+ }
196
236
  this.lockService.updateCharacteristic(this.platform.Characteristic.LockTargetState, e);
197
237
  this.lockService.updateCharacteristic(this.platform.Characteristic.LockCurrentState, e);
198
238
  //throw new this.platform.api.hap.HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
@@ -301,6 +341,21 @@ export class Lock {
301
341
  }
302
342
  }
303
343
 
344
+ FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
345
+ let FirmwareRevision: string;
346
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
347
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
348
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
349
+ if (accessory.context.FirmwareRevision) {
350
+ FirmwareRevision = accessory.context.FirmwareRevision;
351
+ } else if (device.firmware) {
352
+ FirmwareRevision = device.firmware;
353
+ } else {
354
+ FirmwareRevision = this.platform.version;
355
+ }
356
+ return FirmwareRevision;
357
+ }
358
+
304
359
  /**
305
360
  * Logging for Device
306
361
  */
@@ -3,6 +3,10 @@ import { skipWhile } from 'rxjs/operators';
3
3
  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
+ import { Context } from 'vm';
7
+ import { MqttClient } from 'mqtt';
8
+ import { connectAsync } from 'async-mqtt';
9
+ import { hostname } from "os";
6
10
 
7
11
  /**
8
12
  * Platform Accessory
@@ -50,12 +54,20 @@ export class Meter {
50
54
  meterUpdateInProgress!: boolean;
51
55
  doMeterUpdate: Subject<void>;
52
56
 
57
+ //MQTT stuff
58
+ mqttClient: MqttClient | null = null;
59
+
60
+ // EVE history service handler
61
+ historyService: any;
62
+
53
63
  constructor(private readonly platform: SwitchBotPlatform, private accessory: PlatformAccessory, public device: device & devicesConfig) {
54
64
  // default placeholders
55
65
  this.logs(device);
56
66
  this.scan(device);
67
+ this.setupHistoryService(device);
57
68
  this.refreshRate(device);
58
69
  this.config(device);
70
+ this.setupMqtt(device);
59
71
  if (this.CurrentRelativeHumidity === undefined) {
60
72
  this.CurrentRelativeHumidity = 0;
61
73
  } else {
@@ -79,7 +91,9 @@ export class Meter {
79
91
  .getService(this.platform.Service.AccessoryInformation)!
80
92
  .setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
81
93
  .setCharacteristic(this.platform.Characteristic.Model, 'METERTH-S1')
82
- .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
94
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
95
+ .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
96
+ .getCharacteristic(this.platform.Characteristic.FirmwareRevision).updateValue(this.FirmwareRevision(accessory, device));
83
97
 
84
98
  // Temperature Sensor Service
85
99
  if (device.meter?.hide_temperature) {
@@ -163,6 +177,48 @@ export class Meter {
163
177
  });
164
178
  }
165
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
+
166
222
  /**
167
223
  * Parse the device status from the SwitchBot api
168
224
  */
@@ -184,7 +240,7 @@ export class Meter {
184
240
  } else {
185
241
  this.StatusLowBattery = this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
186
242
  }
187
- this.debugLog(`${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}, StatusLowBattery: ${this.StatusLowBattery}`);
243
+ this.debugLog(`Meter: ${this.accessory.displayName} BatteryLevel: ${this.BatteryLevel}, StatusLowBattery: ${this.StatusLowBattery}`);
188
244
 
189
245
  // Humidity
190
246
  if (!this.device.meter?.hide_humidity) {
@@ -258,10 +314,12 @@ export class Meter {
258
314
  this.infoLog(`Meter: ${this.accessory.displayName} BLE Address Found: ${this.address}`);
259
315
  this.infoLog(`Meter: ${this.accessory.displayName} Config BLE Address: ${this.device.bleMac}`);
260
316
  }
261
- this.serviceData = ad.serviceData;
262
- this.temperature = ad.serviceData.temperature!.c;
263
- this.humidity = ad.serviceData.humidity;
264
- 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
+ }
265
323
  this.debugLog(`Meter: ${this.accessory.displayName} serviceData: ${JSON.stringify(ad.serviceData)}`);
266
324
  this.debugLog(
267
325
  `Meter: ${this.accessory.displayName} model: ${ad.serviceData.model}, modelName: ${ad.serviceData.modelName}, ` +
@@ -362,12 +420,16 @@ export class Meter {
362
420
  * Updates the status for each of the HomeKit Characteristics
363
421
  */
364
422
  async updateHomeKitCharacteristics(): Promise<void> {
423
+ let mqttmessage: string[] = [];
424
+ let entry = {time: Math.round(new Date().valueOf()/1000)};
365
425
  if (!this.device.meter?.hide_humidity) {
366
426
  if (this.CurrentRelativeHumidity === undefined) {
367
427
  this.debugLog(`Meter: ${this.accessory.displayName} CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`);
368
428
  } else {
369
429
  this.humidityservice?.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.CurrentRelativeHumidity);
370
430
  this.debugLog(`Meter: ${this.accessory.displayName} updateCharacteristic CurrentRelativeHumidity: ${this.CurrentRelativeHumidity}`);
431
+ mqttmessage.push(`"humidity": ${this.CurrentRelativeHumidity}`);
432
+ entry["humidity"] = this.CurrentRelativeHumidity;
371
433
  }
372
434
  }
373
435
  if (!this.device.meter?.hide_temperature) {
@@ -376,6 +438,8 @@ export class Meter {
376
438
  } else {
377
439
  this.temperatureservice?.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.CurrentTemperature);
378
440
  this.debugLog(`Meter: ${this.accessory.displayName} updateCharacteristic CurrentTemperature: ${this.CurrentTemperature}`);
441
+ mqttmessage.push(`"temperature": ${this.CurrentTemperature}`);
442
+ entry["temp"] = this.CurrentTemperature;
379
443
  }
380
444
  }
381
445
  if (this.device.ble) {
@@ -384,14 +448,20 @@ export class Meter {
384
448
  } else {
385
449
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.BatteryLevel);
386
450
  this.debugLog(`Meter: ${this.accessory.displayName} updateCharacteristic BatteryLevel: ${this.BatteryLevel}`);
451
+ mqttmessage.push(`"battery": ${this.BatteryLevel}`);
387
452
  }
388
453
  if (this.StatusLowBattery === undefined) {
389
454
  this.debugLog(`Meter: ${this.accessory.displayName} StatusLowBattery: ${this.StatusLowBattery}`);
390
455
  } else {
391
456
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, this.StatusLowBattery);
392
457
  this.debugLog(`Meter: ${this.accessory.displayName} updateCharacteristic StatusLowBattery: ${this.StatusLowBattery}`);
458
+ mqttmessage.push(`"lowBattery": ${this.StatusLowBattery}`);
393
459
  }
394
460
  }
461
+ this.mqttPublish(`{${mqttmessage.join(',')}}`)
462
+ if (this.CurrentRelativeHumidity > 0) { // reject unreliable data
463
+ this.historyService?.addEntry(entry);
464
+ }
395
465
  }
396
466
 
397
467
  async apiError(e: any): Promise<void> {
@@ -470,6 +540,21 @@ export class Meter {
470
540
  }
471
541
  }
472
542
 
543
+ FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
544
+ let FirmwareRevision: string;
545
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
546
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
547
+ this.debugLog(`Color Bulb: ${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
+
473
558
  /**
474
559
  * Logging for Device
475
560
  */
@@ -3,6 +3,7 @@ import { skipWhile } from 'rxjs/operators';
3
3
  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
+ import { Context } from 'vm';
6
7
 
7
8
  /**
8
9
  * Platform Accessory
@@ -79,7 +80,9 @@ export class MeterPlus {
79
80
  .getService(this.platform.Service.AccessoryInformation)!
80
81
  .setCharacteristic(this.platform.Characteristic.Manufacturer, 'SwitchBot')
81
82
  .setCharacteristic(this.platform.Characteristic.Model, this.model(device))
82
- .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!);
83
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.deviceId!)
84
+ .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.FirmwareRevision(accessory, device))
85
+ .getCharacteristic(this.platform.Characteristic.FirmwareRevision).updateValue(this.FirmwareRevision(accessory, device));
83
86
 
84
87
  // Temperature Sensor Service
85
88
  if (device.meter?.hide_temperature) {
@@ -481,6 +484,21 @@ export class MeterPlus {
481
484
  }
482
485
  }
483
486
 
487
+ FirmwareRevision(accessory: PlatformAccessory<Context>, device: device & devicesConfig): CharacteristicValue {
488
+ let FirmwareRevision: string;
489
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
490
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
491
+ this.debugLog(`Color Bulb: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
492
+ if (accessory.context.FirmwareRevision) {
493
+ FirmwareRevision = accessory.context.FirmwareRevision;
494
+ } else if (device.firmware) {
495
+ FirmwareRevision = device.firmware;
496
+ } else {
497
+ FirmwareRevision = this.platform.version;
498
+ }
499
+ return FirmwareRevision;
500
+ }
501
+
484
502
  /**
485
503
  * Logging for Device
486
504
  */