bt-sensors-plugin-sk 1.1.0-beta.2.2.0.1 → 1.1.0-beta.2.2.1.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.
package/BTSensor.js CHANGED
@@ -227,7 +227,7 @@ class BTSensor extends EventEmitter {
227
227
  const _propsProxy = await this._getPropsProxy(device)
228
228
  try{
229
229
  const rawProps = await _propsProxy.Get(device.helper.iface,prop)
230
- return rawProps?.value
230
+ return rawProps?.value
231
231
  }
232
232
  catch(e){
233
233
  return null //Property $prop (probably) doesn't exist in $device
@@ -258,7 +258,7 @@ class BTSensor extends EventEmitter {
258
258
  if (!md) return null
259
259
  const keys = Object.keys(md)
260
260
  if (keys && keys.length>0){
261
- return parseInt(keys[0])
261
+ return parseInt(keys[keys.length-1])
262
262
  }
263
263
  return null
264
264
  }
package/README.md CHANGED
@@ -1,8 +1,16 @@
1
1
  # Bluetooth Sensors for [Signal K](http://www.signalk.org)
2
2
 
3
+ ## BETA 2.2.1
4
+
5
+ ### What's New
6
+
7
+ GoveeH50xx sensor support. Selectable bluetooth adapter on config screen (in case you have more than bluetooth adapter on your server).
8
+
9
+
3
10
  ## BETA 2.2.0
4
11
 
5
12
  ### What's New
13
+
6
14
  Support for Aranet4 environment sensor and Renogy Rover/Wanderer Controllers. Untested support for Renogy Battery and Inverter clients. If you have a Renogy Battery or Inverter with bluetooth support, please give it a try and let me know how it goes.
7
15
 
8
16
  ### RENOGY NOTES
@@ -30,7 +38,7 @@ Signalk users with a Linux boat-puter (Windows and MacOS are NOT currently suppo
30
38
 
31
39
  ## REQUIREMENTS
32
40
 
33
- * A Linux Signalk boat-puter with System-D (NOTE: Most Linux installations support System-D)
41
+ * A Linux Signalk boat-puter with bluetooth-DBUS support
34
42
  * A Bluetooth adapter
35
43
  * [Bluez](https://www.bluez.org) installed
36
44
  (Go here for [Snap installation instructions](https://snapcraft.io/bluez))
@@ -57,6 +65,7 @@ If you want to install directly from source (this is mostly of interest to custo
57
65
  git clone https://github.com/naugehyde/bt-sensors-plugin-sk
58
66
  cd bt-sensors-plugin-sk
59
67
  git switch '1.1.0'
68
+ git pull
60
69
  npm i
61
70
  [sudo] npm link
62
71
  cd [signalk_home]
package/index.js CHANGED
@@ -176,8 +176,11 @@ module.exports = function (app) {
176
176
  "2) You will have to wait until the scanner has found your device before seeing your device's config fields and saving the configuration. \n"+
177
177
  "3) To refresh the list of available devices and their configurations, just open and close the config screen by clicking on the arrow symbol in the config's top bar. \n"+
178
178
  "4) If you submit and get errors it may be because the configured devices have not yet all been discovered.",
179
- required:["discoveryTimeout", "discoveryInterval"],
179
+ required:["adapter","discoveryTimeout", "discoveryInterval"],
180
180
  properties: {
181
+ adapter: {title: "Bluetooth adapter",
182
+ type: "string", default: "hci0"},
183
+
181
184
  discoveryTimeout: {title: "Default device discovery timeout (in seconds)",
182
185
  type: "integer", default: 30,
183
186
  minimum: 10,
@@ -422,13 +425,32 @@ module.exports = function (app) {
422
425
  sensorMap.clear()
423
426
  deviceConfigs=options?.peripherals??[]
424
427
 
428
+
429
+
425
430
  if (plugin.stopped) {
426
431
  await sleep(5000) //Make sure plugin.stop() completes first
427
432
  //plugin.start is called asynchronously for some reason
428
433
  //and does not wait for plugin.stop to complete
429
434
  plugin.stopped=false
430
435
  }
431
- adapter = await bluetooth.getAdapter(app.settings?.btAdapter??adapterID)
436
+ var adapterID=options.adapter
437
+
438
+ if (!adapterID || adapterID==="")
439
+ adapterID = "hci0"
440
+
441
+ app.debug(`Connecting to bluetooth adapter ${adapterID}`);
442
+
443
+ const activeAdapters = await bluetooth.activeAdapters()
444
+ plugin.schema.properties.adapter.enum=[]
445
+ plugin.schema.properties.adapter.enumNames=[]
446
+ for (a of activeAdapters){
447
+ plugin.schema.properties.adapter.enum.push(a.adapter)
448
+ plugin.schema.properties.adapter.enumNames.push(`${a.adapter} @ ${ await a.getAddress()} (${await a.getName()})`)
449
+ }
450
+
451
+ plugin.uiSchema.adapter={'ui:disabled': (activeAdapters.length==1)}
452
+
453
+ adapter = await bluetooth.getAdapter(adapterID)
432
454
  await startScanner()
433
455
  if (starts>0){
434
456
  app.debug(`Plugin ${packageInfo.version} restarting...`);
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "bt-sensors-plugin-sk",
3
- "version": "1.1.0-beta.2.2.0.1",
3
+ "version": "1.1.0-beta.2.2.1.1",
4
4
  "description": "Bluetooth Sensors for Signalk -- support for Victron devices, RuuviTag, Xiaomi, ATC and Inkbird, Ultrasonic wind meters, Mopeka tank readers, Renogy Battery and Solar Controllers (new), Aranet4 environment sensors, and Govee GVH51xx temp sensors",
5
5
  "main": "index.js",
6
6
  "dependencies": {
7
7
  "dbus-next": "^0.10.2",
8
- "node-ble": "^1.9.0",
8
+ "node-ble": "^1.12.0",
9
9
  "int24":"^0.0.1"
10
10
  },
11
11
  "scripts": {
@@ -51,6 +51,8 @@ class ATC extends BTSensor{
51
51
  }
52
52
  propertiesChanged(props){
53
53
  super.propertiesChanged(props)
54
+ if (!props.hasOwnProperty("ServiceData")) return
55
+
54
56
  const buff = this.getServiceData("0000181a-0000-1000-8000-00805f9b34fb")
55
57
  if (!buff)
56
58
  throw new Error("Unable to get service data for "+this.getDisplayName())
@@ -34,7 +34,9 @@ class Aranet2 extends AranetSensor{
34
34
 
35
35
  }
36
36
  propertiesChanged(props){
37
- super.propertiesChanged(props)
37
+ super.propertiesChanged(props)
38
+ if (!props.hasOwnProperty("ManufacturerData")) return
39
+
38
40
  const buff = this.getManufacturerData(0x0702)
39
41
  this.emitData("temp", buff)
40
42
  this.emitData("humidity", buff)
@@ -39,6 +39,8 @@ class Aranet4 extends AranetSensor{
39
39
  }
40
40
  propertiesChanged(props){
41
41
  super.propertiesChanged(props)
42
+ if (!props.hasOwnProperty("ManufacturerData")) return
43
+
42
44
  const buff = this.getManufacturerData(0x0702)
43
45
  this.emitData("co2", buff)
44
46
  this.emitData("temp", buff)
@@ -2,10 +2,13 @@ const BTSensor = require("../BTSensor");
2
2
  class BLACKLISTED extends BTSensor{
3
3
  static isSystem = true
4
4
  static async identify(device){
5
- if (await this.getManufacturerID(device)===0x004C) //apple devices use
6
- return this //randomised macs and clog up our list
7
- else
8
- return null
5
+ const md = await this.getDeviceProp(device,'ManufacturerData')
6
+ if (md){
7
+ const keys = Object.keys(md)
8
+ if (keys.length==1 && keys[0]==0x004C)
9
+ return this
10
+ }
11
+ return null
9
12
  }
10
13
  async init(){
11
14
  await super.init()
@@ -13,7 +16,7 @@ class BLACKLISTED extends BTSensor{
13
16
  }
14
17
  reasonForBlacklisting() {
15
18
  switch ( this.getManufacturerID()){
16
- case (0x004C): return "Randomized MAC address"
19
+ case (0x004C): return "Randomized MAC address (Apple)"
17
20
  case (0x02e1): return "Device is using VE.Smart" //NOTE: Victron/VictronSensor class
18
21
  //determines if a device is using VE.Smart
19
22
  //in identify(). If so, identify() returns
@@ -0,0 +1,47 @@
1
+ const BTSensor = require("../BTSensor");
2
+
3
+ class GoveeH50xx extends BTSensor {
4
+
5
+ static async identify(device){
6
+ const regex = /^Govee_H50[0-9]{2}_[a-f,A-F,0-9]{4}$/
7
+ //this.getManufacturer()
8
+ const name = await this.getDeviceProp(device,"Name")
9
+ const uuids = await this.getDeviceProp(device,'UUIDs')
10
+
11
+ if (name && name.match(regex) &&
12
+ uuids && uuids.length > 0 &&
13
+ uuids[0] == '0000ec88-0000-1000-8000-00805f9b34fb')
14
+ return this
15
+ else
16
+ return null
17
+ t
18
+ }
19
+
20
+ async init(){
21
+ await super.init()
22
+ this.initMetadata()
23
+ }
24
+ initMetadata(){
25
+ this.addMetadatum('temp','K', 'temperature',
26
+ (buffer)=>{return 273.15+(buffer.readUInt16LE(1)/100)
27
+ })
28
+ this.addMetadatum('humidity','ratio', 'humidity',
29
+ (buffer)=>{return buffer.readUInt16LE(3)/10000
30
+ })
31
+ this.addMetadatum('battery','ratio', 'battery strength', (buffer)=>{return buffer.readUInt8(5)/100})
32
+ }
33
+
34
+ getManufacturer(){
35
+ return "Govee"
36
+ }
37
+ async propertiesChanged(props){
38
+ super.propertiesChanged(props)
39
+ if (!props.hasOwnProperty("ManufacturerData")) return
40
+
41
+ const buffer = this.getManufacturerData(0xec88)
42
+ if (buffer) {
43
+ this.emitValuesFrom(buffer)
44
+ }
45
+ }
46
+ }
47
+ module.exports=GoveeH50xx
@@ -18,10 +18,10 @@ function decodeTempHumid(tempHumidBytes) {
18
18
  return {t: tempAsFloat, h: humid};
19
19
  }
20
20
  }
21
- class GoveeTH extends BTSensor{
21
+ class GoveeH510x extends BTSensor{
22
22
 
23
23
  static async identify(device){
24
- const regex = /^GVH5[0-9]{3}_[a-f,A-F,0-9]{4}$/
24
+ const regex = /^GVH510[0-9]_[a-f,A-F,0-9]{4}$/
25
25
  const name = await this.getDeviceProp(device,"Name")
26
26
  const uuids = await this.getDeviceProp(device,'UUIDs')
27
27
 
@@ -56,12 +56,12 @@ class GoveeTH extends BTSensor{
56
56
  }
57
57
  async propertiesChanged(props){
58
58
  super.propertiesChanged(props)
59
- if (props.ManufacturerData) {
60
- const buffer = this.getManufacturerData(0x0001)
61
- if (buffer) {
62
- this.emitValuesFrom(buffer)
63
- }
64
- }
59
+ if (!props.hasOwnProperty("ManufacturerData")) return
60
+
61
+ const buffer = this.getManufacturerData(0x0001)
62
+ if (buffer) {
63
+ this.emitValuesFrom(buffer)
64
+ }
65
65
  }
66
66
  }
67
- module.exports=GoveeTH
67
+ module.exports=GoveeH510x
@@ -27,6 +27,7 @@ class Inkbird extends BTSensor{
27
27
  }
28
28
  async propertiesChanged(props){
29
29
  super.propertiesChanged(props)
30
+
30
31
  if (props.ManufacturerData) {
31
32
  const keys = Object.keys(this.currentProperties.ManufacturerData)
32
33
  const key = keys[keys.length-1]
@@ -0,0 +1,81 @@
1
+ const BTSensor = require("../BTSensor");
2
+
3
+ class SwitchBotTH extends BTSensor {
4
+ static async test(){
5
+
6
+ const p = {[this.ID.toString()]: [0xd8, 0x35, 0x34, 0x38, 0x4f, 0x70, 0x07, 0x02, 0x04, 0x96, 0x2c, 0x00]}
7
+ this.getDeviceProp=async ()=>{
8
+ return p
9
+ }
10
+ const c = await this.identify()
11
+ const sb = new c()
12
+ sb.initMetadata()
13
+ sb.currentProperties={}
14
+ sb.on("temp", (t)=>console.log(t))
15
+ sb.on("humidity", (h)=>console.log(h))
16
+ sb.on("battery", (b)=>console.log(b))
17
+
18
+ sb.propertiesChanged( {ManufacturerData: p})
19
+
20
+ sb.propertiesChanged( {ServiceData: {[this.batteryService]:[0x77,0x00,0x64]}})
21
+
22
+
23
+ }
24
+ static ID = 0x0969
25
+ static batteryService = "0000fd3d-0000-1000-8000-00805f9b34fb"
26
+ static async identify(device){
27
+ const md = await this.getDeviceProp(device,'ManufacturerData')
28
+ if (!md) return null
29
+ const keys = Object.keys(md)
30
+ if (keys && keys.length>0){
31
+ const id = keys[keys.length-1]
32
+ if (parseInt(id)==this.ID && md[id].length==12)
33
+ return this
34
+ }
35
+ return null
36
+
37
+ }
38
+
39
+ async init(){
40
+ await super.init()
41
+ this.initMetadata()
42
+ }
43
+ initMetadata(){
44
+ this.addMetadatum('temp','K', 'temperature',
45
+ (buffer)=>{
46
+ return (27315+(((buffer[8] & 0x0F)/10 + (buffer[9] & 0x7F)) * (((buffer[9] & 0x80)>0)?100:-100)))/100
47
+ })
48
+ this.addMetadatum('humidity','ratio', 'humidity',
49
+
50
+ (buffer)=>{return (buffer[10] & 0x7F)/100
51
+ })
52
+
53
+ this.addMetadatum("battery", "ratio", "battery strength",
54
+ (buffer)=>{return buffer[2]/100}
55
+ )
56
+ }
57
+
58
+ getManufacturer(){
59
+ return "Wonder Labs"
60
+ }
61
+ getName() {
62
+ "SwitchBotTH"
63
+ }
64
+ async propertiesChanged(props){
65
+ super.propertiesChanged(props)
66
+ if (props.ManufacturerData) {
67
+ const buffer = this.getManufacturerData(this.constructor.ID)
68
+ if (buffer) {
69
+ this.emitData("temp", buffer)
70
+ this.emitData("humidity", buffer)
71
+ }
72
+ }
73
+ if (props.ServiceData) {
74
+ const buffer = this.getServiceData(this.constructor.batteryService)
75
+ if (buffer){
76
+ this.emitData("battery", buffer)
77
+ }
78
+ }
79
+ }
80
+ }
81
+ module.exports=SwitchBotTH
@@ -19,12 +19,12 @@ class UltrasonicWindMeter extends BTSensor{
19
19
  )
20
20
  this.awsCharacteristic.readValue()
21
21
  .then((buffer)=>
22
- this.emit("aws", buffer)
22
+ this.emitData("aws", buffer)
23
23
 
24
24
  )
25
25
  this.awaCharacteristic.readValue()
26
26
  .then((buffer)=>
27
- this.emit("awa", buffer)
27
+ this.emitData("awa", buffer)
28
28
  )
29
29
 
30
30
  }
@@ -88,7 +88,7 @@ function sleep(x) {
88
88
  propertiesChanged(props){
89
89
  super.propertiesChanged(props)
90
90
  if (this.usingGATT()) return
91
-
91
+ if (!props.hasOwnProperty("ManufacturerData")) return
92
92
  try{
93
93
  const md = this.getManufacturerData(0x2e1)
94
94
  if (md && md.length && md[0]==0x10){
@@ -1,7 +1,7 @@
1
- const { throws } = require("assert");
2
1
  const BTSensor = require("../BTSensor");
3
2
 
4
3
  const crypto = require('crypto');
4
+ const util = require('util');
5
5
  const { isGeneratorFunction } = require("util/types");
6
6
 
7
7
  const DEVICE_TYPES = new Map([
@@ -96,7 +96,7 @@ class XiaomiMiBeacon extends BTSensor{
96
96
 
97
97
  emitValues(buffer){
98
98
  this.emitData("temp", buffer, 0)
99
- this.emitData("humidity", buffer,2)
99
+ this.emit("humidity", buffer.readUInt8(2)/100)
100
100
  this.emitData("voltage",buffer,3);
101
101
  }
102
102
  getManufacturer(){
@@ -164,6 +164,8 @@ class XiaomiMiBeacon extends BTSensor{
164
164
  propertiesChanged(props){
165
165
  super.propertiesChanged(props)
166
166
  if (this.usingGATT()) return
167
+ if (!props.hasOwnProperty("ServiceData")) return
168
+
167
169
  const data = this.getServiceData(this.constructor.SERVICE_MIBEACON)
168
170
  var dec
169
171
  if (!this.encryptionKey){
@@ -180,14 +182,22 @@ class XiaomiMiBeacon extends BTSensor{
180
182
  throw new Error(`${this.getNameAndAddress()} received empty decrypted packet. Check that the bind/encryption key in config is correct.`)
181
183
 
182
184
  switch(dec[0]){
185
+ case 0x0D:
186
+ this.emitData("temp",dec,3)
187
+ this.emitData("humidity",dec,5)
188
+ break
189
+
190
+ case 0x0A:
191
+ this.emitData("batteryStrength",dec,3)
192
+ break
183
193
  case 0x04:
184
- this.emit("temp",(dec.readInt16LE(3)/10)+273.15)
194
+ this.emitData("temp",dec,3)
185
195
  break
186
196
  case 0x06:
187
- this.emit("humidity",(dec.readInt16LE(3)/1000))
197
+ this.emitData("humidity",dec,3)
188
198
  break
189
199
  default:
190
- throw new Error(`${this.getNameAndAddress()} unable to parse decrypted service data (${dec})`)
200
+ throw new Error(`${this.getNameAndAddress()} unable to parse decrypted service data (${util.inspect(dec)})`)
191
201
 
192
202
  }
193
203
  }
@@ -197,8 +207,11 @@ class XiaomiMiBeacon extends BTSensor{
197
207
  const md = this.addMetadatum("encryptionKey", "", "encryptionKey (AKA bindKey) for decryption")
198
208
  md.isParam=true
199
209
  this.addMetadatum('temp','K', 'temperature',
200
- (buff,offset)=>{return ((buff.readInt16LE(offset))/100) + 273.1})
210
+ (buff,offset)=>{return ((buff.readInt16LE(offset))/10) + 273.15})
201
211
  this.addMetadatum('humidity','ratio', 'humidity',
212
+ (buff,offset)=>{return buff.readInt16LE(offset)/1000})
213
+
214
+ this.addMetadatum('batteryStrength', 'ratio', 'sensor battery strength',
202
215
  (buff,offset)=>{return ((buff.readUInt8(offset))/100)})
203
216
  this.addMetadatum('voltage', 'V', 'sensor battery voltage',
204
217
  (buff,offset)=>{return ((buff.readUInt16LE(offset))/1000)})