bt-sensors-plugin-sk 1.2.0-beta.0.0.1.test → 1.2.0-beta.0.0.3

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
@@ -1,6 +1,7 @@
1
1
  const { Variant } = require('dbus-next');
2
2
  const { log } = require('node:console');
3
3
  const EventEmitter = require('node:events');
4
+ const AutoQueue = require("./Queue")
4
5
 
5
6
  /**
6
7
  * @author Andrew Gerngross <oh.that.andy@gmail.com>
@@ -11,6 +12,7 @@ const EventEmitter = require('node:events');
11
12
  */
12
13
 
13
14
  const BTCompanies = require('./bt_co.json');
15
+ const connectQueue = new AutoQueue()
14
16
 
15
17
  /**
16
18
  * @global A map of company names keyed by their Bluetooth ID
@@ -449,6 +451,39 @@ class BTSensor extends EventEmitter {
449
451
  throw new Error("::initGATTNotifications() should be implemented by the BTSensor subclass")
450
452
  }
451
453
 
454
+ deviceConnect() {
455
+
456
+ /* CAUTION: HACK AHEAD
457
+
458
+ Bluez for some cockamamie reason (It's 2025 for chrissake.
459
+ BLUETOOTH ISN'T JUST FOR DESKTOPS ANYMORE, BLUEZ DEVS!)
460
+ SUSPENDS scanning while connected to a device.
461
+
462
+ The next line of code gives the scanner a kick in the arse,
463
+ starting it up again so, I dunno, another device might be able
464
+ to connect and sensor classes could maybe get beacon updates.
465
+
466
+ You know, the little things.
467
+ */
468
+ return connectQueue.enqueue( async ()=>{
469
+ this.debug(`Connecting to ${this.getName()}`)
470
+ await this.device.connect()
471
+ try {
472
+
473
+ await this._adapter.helper.callMethod('StopDiscovery')
474
+ await this._adapter.helper.callMethod('SetDiscoveryFilter', {
475
+ Transport: new Variant('s', this._adapter?._transport??"le")
476
+ })
477
+ await this._adapter.helper.callMethod('StartDiscovery')
478
+
479
+ } catch (e){
480
+ //probably ignorable error. probably.
481
+ console.log(e)
482
+ }
483
+ })
484
+ /* END HACK*/
485
+ }
486
+
452
487
  /**
453
488
  *
454
489
  * Subclasses do NOT need to override this function
@@ -739,12 +774,15 @@ class BTSensor extends EventEmitter {
739
774
  //End instance utility functions
740
775
 
741
776
  createPaths(config, id){
777
+ // const source = `${this.getName()} (${id})`
778
+
742
779
  Object.keys(this.getPaths()).forEach((tag)=>{
743
780
  const pathMeta=this.getPath(tag)
744
781
  const path = config.paths[tag]
745
782
  if (!(path===undefined))
746
783
  this.app.handleMessage(id,
747
784
  {
785
+ // $source: source,
748
786
  updates:
749
787
  [{ meta: [{path: preparePath(this, path), value: { units: pathMeta?.unit }}]}]
750
788
  })
@@ -752,6 +790,7 @@ class BTSensor extends EventEmitter {
752
790
  }
753
791
 
754
792
  initPaths(deviceConfig, id){
793
+ const source = this.getName()
755
794
  Object.keys(this.getPaths()).forEach((tag)=>{
756
795
  const pathMeta=this.getPath(tag)
757
796
  const path = deviceConfig.paths[tag];
@@ -760,14 +799,14 @@ class BTSensor extends EventEmitter {
760
799
  if (pathMeta.notify){
761
800
  this.app.notify(tag, val, id )
762
801
  } else {
763
- this.updatePath(preparePath(this,path),val,id)
802
+ this.updatePath(preparePath(this,path),val, id, source)
764
803
  }
765
804
  })
766
805
  }
767
806
  })
768
807
  }
769
- updatePath(path, val,id){
770
- this.app.handleMessage(id, {updates: [ { values: [ {path: path, value: val }] } ] })
808
+ updatePath(path, val, id, source){
809
+ this.app.handleMessage(id, {updates: [ { $source: source, values: [ { path: path, value: val }] } ] })
771
810
  }
772
811
  elapsedTimeSinceLastContact(){
773
812
  return (Date.now()-this?._lastContact??Date.now())/1000
package/Queue.js ADDED
@@ -0,0 +1,48 @@
1
+ /*
2
+ see: https://stackoverflow.com/questions/53540348/js-async-await-tasks-queue
3
+ */
4
+ class Queue {
5
+ constructor() { this._items = []; }
6
+ enqueue(item) { this._items.push(item); }
7
+ dequeue() { return this._items.shift(); }
8
+ get size() { return this._items.length; }
9
+ }
10
+
11
+ class AutoQueue extends Queue {
12
+ constructor() {
13
+ super();
14
+ this._pendingPromise = false;
15
+ }
16
+
17
+ enqueue(action) {
18
+ return new Promise((resolve, reject) => {
19
+ super.enqueue({ action, resolve, reject });
20
+ this.dequeue();
21
+ });
22
+ }
23
+
24
+ async dequeue() {
25
+ if (this._pendingPromise) return false;
26
+
27
+ let item = super.dequeue();
28
+
29
+ if (!item) return false;
30
+
31
+ try {
32
+ this._pendingPromise = true;
33
+
34
+ let payload = await item.action(this);
35
+
36
+ this._pendingPromise = false;
37
+ item.resolve(payload);
38
+ } catch (e) {
39
+ this._pendingPromise = false;
40
+ item.reject(e);
41
+ } finally {
42
+ this.dequeue();
43
+ }
44
+
45
+ return true;
46
+ }
47
+ }
48
+ module.exports = AutoQueue
package/index.js CHANGED
@@ -47,6 +47,12 @@ class MissingSensor {
47
47
  hasGATT(){
48
48
  return this.config.gattParams
49
49
  }
50
+ initGATTConnection(){
51
+
52
+ }
53
+ getGATTDescription(){
54
+ return ""
55
+ }
50
56
  getMetadata(){
51
57
  return this.metadata
52
58
  }
@@ -196,6 +202,12 @@ module.exports = function (app) {
196
202
  app.debug(req.body)
197
203
  const i = deviceConfigs.findIndex((p)=>p.mac_address==req.body.mac_address)
198
204
  if (i<0){
205
+ if (!options.peripherals){
206
+ if (!options.hasOwnProperty("peripherals"))
207
+ options.peripherals=[]
208
+
209
+ options.peripherals=[]
210
+ }
199
211
  options.peripherals.push(req.body)
200
212
  } else {
201
213
  options.peripherals[i] = req.body
@@ -330,13 +342,15 @@ module.exports = function (app) {
330
342
  //filter options which can cause issues with Device::Connect()
331
343
  //turning off Discovery
332
344
  //try {await adapter.startDiscovery()}
345
+ const _transport = transport?plugin.schema.properties.transport.default:transport
333
346
  try{
334
- if (transport) {
335
- app.debug(`Setting Bluetooth transport option to ${transport}`)
347
+ if (_transport) {
348
+ app.debug(`Setting Bluetooth transport option to ${_transport}`)
336
349
  await adapter.helper.callMethod('SetDiscoveryFilter', {
337
- Transport: new Variant('s', transport)
350
+ Transport: new Variant('s', _transport)
338
351
  })
339
352
  }
353
+ adapter._transport=_transport
340
354
  await adapter.helper.callMethod('StartDiscovery')
341
355
  }
342
356
  catch (error){
@@ -370,7 +384,7 @@ module.exports = function (app) {
370
384
  var s
371
385
  const startNumber=starts
372
386
  //app.debug(`Waiting on ${deviceNameAndAddress(config)}`)
373
- adapter.waitDevice(config.mac_address,config.discoveryTimeout*1000)
387
+ adapter.waitDevice(config.mac_address,(config?.discoveryTimeout??30)*1000)
374
388
  .then(async (device)=> {
375
389
  if (startNumber != starts ) {
376
390
  debugger
@@ -382,7 +396,6 @@ module.exports = function (app) {
382
396
  if (s instanceof BLACKLISTED)
383
397
  reject ( `Device is blacklisted (${s.reasonForBlacklisting()}).`)
384
398
  else{
385
-
386
399
  addSensorToList(s)
387
400
  s.on("RSSI",(()=>{
388
401
  updateSensor(s)
@@ -413,6 +426,8 @@ module.exports = function (app) {
413
426
  const sensor = new c(device,config?.params, config?.gattParams)
414
427
  sensor.debug=app.debug
415
428
  sensor.app=app
429
+ sensor._adapter=adapter //HACK!
430
+
416
431
  await sensor.init()
417
432
  //app.debug(`instantiated ${await BTSensor.getDeviceProp(device,"Address")}`)
418
433
 
@@ -427,15 +442,15 @@ module.exports = function (app) {
427
442
  //if we're here ain't got no class for the device
428
443
  var sensor
429
444
  if (config.params?.sensorClass){
430
- const c = classMap.get(config.params.sensorClass)
431
- c.debug=app.debug
432
- sensor = new c(device,config?.params, config?.gattParams)
433
- sensor.debug=app.debug
434
- sensor.app=app
445
+ var c = classMap.get(config.params.sensorClass)
435
446
  } else{
436
- sensor = new (classMap.get('UNKNOWN'))(device)
437
- sensor.app=app
447
+ c = classMap.get('UNKNOWN')
438
448
  }
449
+ c.debug=app.debug
450
+ sensor = new c(device,config?.params, config?.gattParams)
451
+ sensor.debug=app.debug
452
+ sensor.app=app
453
+
439
454
  await sensor.init()
440
455
  return sensor
441
456
  }
@@ -626,11 +641,14 @@ module.exports = function (app) {
626
641
  if (sensor.elapsedTimeSinceLastContact()> dt)
627
642
  channel.broadcast(getSensorInfo(sensor), "sensorchanged")
628
643
  })
629
- }, minTimeout*1000)
644
+ }, (minTimeout==Infinity)?(options?.discoveryTimeout??plugin.schema.properties.discoveryTimeout.default):minTimeout)
630
645
 
646
+ if (!options.hasOwnProperty("discoveryInterval" )) //no config -- first run
647
+ options.discoveryInterval = plugin.schema.properties.discoveryInterval.default
648
+
631
649
  if (options.discoveryInterval && !discoveryIntervalID)
632
- findDeviceLoop(options.discoveryTimeout, options.discoveryInterval)
633
-
650
+ findDeviceLoop(options?.discoveryTimeout??plugin.schema.properties.discoveryTimeout.default,
651
+ options.discoveryInterval)
634
652
  }
635
653
  plugin.stop = async function () {
636
654
  app.debug("Stopping plugin")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bt-sensors-plugin-sk",
3
- "version": "1.2.0-beta.0.0.1.test",
3
+ "version": "1.2.0-beta.0.0.3",
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, Aranet4 environment sensors, SwitchBot temp and humidity sensors, KilovaultHLXPlus smart batteries, and Govee GVH51xx temp sensors",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -65,6 +65,7 @@
65
65
  "signalk-category-hardware",
66
66
  "signalk-plugin-configurator"
67
67
  ],
68
+ "signalk-plugin-enabled-by-default": true,
68
69
  "author": "Andrew Gerngross",
69
70
  "license": "ISC",
70
71
  "bugs": {
@@ -23,7 +23,8 @@ class ATC extends BTSensor{
23
23
  {
24
24
  title:'data parsing strategy',
25
25
  type: 'string',
26
- enum:["ATC-LE","ATC-BE"]
26
+ enum:["ATC-LE","ATC-BE"],
27
+ default: ["ATC-LE"]
27
28
  }
28
29
  )
29
30
  if (!this.parser){
@@ -49,7 +49,7 @@ class JBDBMS extends BTSensor {
49
49
  this.addMetadatum('FET', '', 'FET Control',
50
50
  (buffer)=>{return buffer.readUInt8(24)} )
51
51
 
52
- await this.device.connect()
52
+ await this.deviceConnect()
53
53
  await this.initCharacteristics()
54
54
  const cellsAndTemps = await this.getNumberOfCellsAndTemps()
55
55
  this.numberOfCells=cellsAndTemps.cells
@@ -74,14 +74,14 @@ class JBDBMS extends BTSensor {
74
74
  return true
75
75
  }
76
76
  initCharacteristics(){
77
- return new Promise((resolve,reject )=>{ this.device.connect().then(async ()=>{
77
+ return new Promise( async (resolve,reject )=>{
78
78
  const gattServer = await this.device.gatt()
79
79
  const txRxService= await gattServer.getPrimaryService(this.constructor.TX_RX_SERVICE)
80
80
  this.rxChar = await txRxService.getCharacteristic(this.constructor.NOTIFY_CHAR_UUID)
81
81
  this.txChar = await txRxService.getCharacteristic(this.constructor.WRITE_CHAR_UUID)
82
82
  await this.rxChar.startNotifications()
83
83
  resolve(this)
84
- }) .catch((e)=>{ reject(e.message) }) })
84
+ .catch((e)=>{ reject(e.message) }) })
85
85
  }
86
86
 
87
87
  async initGATTNotifications(){
@@ -92,7 +92,7 @@ class JBDBMS extends BTSensor {
92
92
 
93
93
  emitGATT(){
94
94
  this.getAndEmitBatteryInfo()
95
- setTimeout(()=>{this.getAndEmitCellVoltages()}, 5000)
95
+ setTimeout(()=>{this.getAndEmitCellVoltages()}, 10000)
96
96
  }
97
97
 
98
98
  async getNumberOfCellsAndTemps(){
@@ -175,7 +175,7 @@ class KilovaultHLXPlus extends BTSensor{
175
175
  }
176
176
 
177
177
  initGATTConnection(){
178
- return new Promise((resolve,reject )=>{ this.device.connect().then(async ()=>{
178
+ return new Promise((resolve,reject )=>{ this.deviceConnect().then(async ()=>{
179
179
  if (!this.gattServer) {
180
180
  this.gattServer = await this.device.gatt()
181
181
  this.battService = await this.gattServer.getPrimaryService("0000ffe0-0000-1000-8000-00805f9b34fb")
@@ -57,7 +57,7 @@ class RenogySensor extends BTSensor{
57
57
  )
58
58
 
59
59
 
60
- await this.device.connect()
60
+ await this.deviceConnect()
61
61
  const rw = await this.constructor.getReadWriteCharacteristics(this.device)
62
62
 
63
63
  this.readChar = rw.read
@@ -40,7 +40,7 @@ class UltrasonicWindMeter extends BTSensor{
40
40
  }
41
41
 
42
42
  initGATTConnection(){
43
- return new Promise((resolve,reject )=>{ this.device.connect().then(async ()=>{
43
+ return new Promise((resolve,reject )=>{ this.deviceConnect().then(async ()=>{
44
44
  if (!this.gattServer) {
45
45
  this.gattServer = await this.device.gatt()
46
46
  this.battService = await this.gattServer.getPrimaryService("0000180f-0000-1000-8000-00805f9b34fb")
@@ -125,7 +125,7 @@ class VictronBatteryMonitor extends VictronSensor{
125
125
  return new Promise((resolve,reject )=>{
126
126
  if (!this.valueIfVariant(this.currentProperties.Paired))
127
127
  reject(`${this.getName()} must be paired with the Signalk server to use GATT protocol`)
128
- this.device.connect().then(async ()=>{
128
+ this.deviceConnect().then(async ()=>{
129
129
  this.debug(`${this.getName()} connected.`)
130
130
  if (!this.gattServer) {
131
131
  this.gattServer = await this.device.gatt()
@@ -23,7 +23,7 @@ class VictronOrionXS extends VictronSensor{
23
23
  this.addMetadatum('inputCurrent','A','input current',
24
24
  (buff)=>{return this.NaNif(buff.readUInt16LE(8),0xFFFF)/10})
25
25
  this.addMetadatum('deviceOffReason','', 'device off reason',
26
- (buff)=>{return VC.OffReasons(buff.readUInt32(10))})
26
+ (buff)=>{return VC.OffReasons(buff.readUInt32BE(10))})
27
27
  }
28
28
 
29
29
  }
@@ -110,7 +110,7 @@ class XiaomiMiBeacon extends BTSensor{
110
110
  this.gattWarningDelivered=true
111
111
  }
112
112
  return new Promise((resolve,reject )=>{
113
- this.device.connect().then(async ()=>{
113
+ this.deviceConnect().then(async ()=>{
114
114
  if (!this.gattServer) {
115
115
  this.gattServer = await this.device.gatt()
116
116
  this.gattService = await this.gattServer.getPrimaryService("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6")