bt-sensors-plugin-sk 1.2.6-beta-3 → 1.2.6-beta-4

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
@@ -75,7 +75,7 @@ function signalQualityPercentQuad(rssi, perfect_rssi=-20, worst_rssi=-85) {
75
75
  class BTSensor extends EventEmitter {
76
76
 
77
77
  static DEFAULTS = require('./plugin_defaults.json');
78
- static DistanceManagerSingleton= new DistanceManager({DISTANCE_FIND_LAST_FEW_SAMPLE_TIME_FRAME_MILLIS:60000}) //should be a singleton
78
+ static DistanceManagerSingleton= new DistanceManager({DISTANCE_FIND_LAST_FEW_SAMPLE_TIME_FRAME_MILLIS:60000})
79
79
 
80
80
  static IsRoaming = false;
81
81
 
@@ -255,6 +255,36 @@ class BTSensor extends EventEmitter {
255
255
 
256
256
  //END static identity functions
257
257
 
258
+ _debugLog=[]
259
+ _errorLog=[]
260
+
261
+ getErrorLog(){
262
+ return this._errorLog
263
+ }
264
+ getDebugLog(){
265
+ return this._debugLog
266
+ }
267
+
268
+ debug(message){
269
+ if (this._app)
270
+ this._app.debug(message)
271
+ else
272
+ console.log(message)
273
+ this._debugLog.push({timestamp:Date.now(), message:message})
274
+ }
275
+
276
+ setError(message){
277
+ if (this._app){
278
+ this._app.debug(message)
279
+ this._app.setPluginError(message)
280
+ }
281
+ else
282
+ console.log(message)
283
+
284
+ this._errorLog.push({timestamp:Date.now(), message:message})
285
+ this.setState("ERROR")
286
+ }
287
+
258
288
  //Instance Initialization functions
259
289
  /**
260
290
  *
@@ -264,6 +294,7 @@ class BTSensor extends EventEmitter {
264
294
  *
265
295
  */
266
296
 
297
+
267
298
  async initSchema(){
268
299
  this._schema = {
269
300
  type: "object",
@@ -337,26 +368,50 @@ class BTSensor extends EventEmitter {
337
368
  try {
338
369
  this.activateGATT()
339
370
  } catch (e) {
340
- this.debug(`GATT services unavailable for ${this.getName()}. Reason: ${e}`)
341
- this._state="ERROR"
371
+ this.setError(`GATT services unavailable for ${this.getName()}. Reason: ${e}`)
342
372
  return
343
373
  }
344
374
  }
345
- this._state="ACTIVE"
375
+ this.setState("ACTIVE")
346
376
  this._propertiesChanged(this.currentProperties)
347
377
 
348
378
  }
349
379
 
350
- activateGATT(){
351
- this.initGATTConnection().then(async ()=>{
380
+ async getGATTServer(){
381
+ if (!this._gattServer)
382
+ this._gattServer = await this.device.gatt()
383
+ return this._gattServer
384
+ }
385
+ async activateGATT(isReconnecting=false){
386
+ try{
387
+ await this.initGATTConnection(isReconnecting)
352
388
  await this.emitGATT()
353
389
  if (this.pollFreq)
354
390
  this.initGATTInterval()
355
391
  else
356
392
  await this.initGATTNotifications()
357
- }).catch((e)=>{
358
- this.app.debug(`Unable to activate GATT connection for ${this.getName()} (${this.getMacAddress()}): ${e}`)
359
- })
393
+ }
394
+ catch(e) {
395
+ this.debug(`Unable to activate GATT connection for ${this.getName()} (${this.getMacAddress()}): ${e}`)
396
+ }
397
+ }
398
+
399
+ async deactivateGATT(){
400
+ if (this.device) {
401
+ this.debug(`(${this.getName()}) disconnecting from GATT server`)
402
+ if (this._gattServer) { //getGATTServer() was called which calls node-ble's Device::gatt() which needs cleaning up after
403
+ (await this._gattServer.services()).forEach(async (uuid) => {
404
+ await this._gattServer.getPrimaryService(uuid).then(async (service) => {
405
+ (await service.characteristics()).forEach(async (uuid) => {
406
+ (await service.getCharacteristic(uuid)).helper.removeListeners();
407
+ });
408
+ service.helper.removeListeners();
409
+ });
410
+ this._gattServer.helper.removeListeners();
411
+ });
412
+ }
413
+ await this.device.disconnect()
414
+ }
360
415
  }
361
416
 
362
417
  /**
@@ -451,8 +506,8 @@ class BTSensor extends EventEmitter {
451
506
  * see: VictronBatteryMonitor for an example
452
507
  */
453
508
 
454
- initGATTConnection(){
455
- throw new Error("::initGATTConnection() should be implemented by the BTSensor subclass")
509
+ async initGATTConnection(isReconnecting=false, retries=5, retryInterval=3){
510
+ await this.connectDevice(isReconnecting,retries, retryInterval);
456
511
  }
457
512
  /**
458
513
  * Subclasses providing GATT support should override this method
@@ -462,11 +517,36 @@ class BTSensor extends EventEmitter {
462
517
  *
463
518
  * see: VictronBatteryMonitor for an example
464
519
  */
465
- initGATTNotifications(){
520
+ async initGATTNotifications(){
466
521
  throw new Error("::initGATTNotifications() should be implemented by the BTSensor subclass")
467
522
  }
468
523
 
469
- deviceConnect(reconnect=false) {
524
+ isConnected(){
525
+ return this?._connected??false
526
+ }
527
+ async connectDevice( isReconnecting, retries=5, retryInterval=3){
528
+ for (let attempts = 0; attempts<retries; attempts++) {
529
+ try
530
+ {
531
+ this.debug( `(${this.getName()}) Trying to connect (attempt # ${attempts +1}) to Bluetooth device.` );
532
+ await this.deviceConnect(isReconnecting);
533
+ return this.device
534
+ }
535
+
536
+ catch (e){
537
+ if (attempts==retries)
538
+ throw new Error (`(${this.getName}) Unable to connect to Bluetooth device after ${attempts} attempts`)
539
+ this.debug(`(${this.getName()}) Error connecting to device. Retrying... `);
540
+ await new Promise((r) => setTimeout(r, retryInterval*1000));
541
+
542
+ }
543
+ }
544
+ }
545
+ setConnected(state){
546
+ this._connected=state
547
+ this.emit("connected", this._connected)
548
+ }
549
+ deviceConnect(isReconnecting=false, autoReconnect=false) {
470
550
 
471
551
 
472
552
  return connectQueue.enqueue( async ()=>{
@@ -474,33 +554,50 @@ class BTSensor extends EventEmitter {
474
554
  await this.device.helper.callMethod('Connect')
475
555
 
476
556
  this.debug(`Connected to ${this.getName()}`)
477
- if (!reconnect) {
478
- this.device.helper.on('PropertiesChanged', (propertiesChanged) => {
479
- if ('Connected' in propertiesChanged) {
480
- const { value } = propertiesChanged.Connected
481
- if (value) {
482
- this.device.emit('connect', { connected: true })
483
- } else {
484
- this.device.emit('disconnect', { connected: false })
485
- }
557
+ if (!isReconnecting) {
558
+ this.device.helper.on(
559
+ "PropertiesChanged",
560
+ (propertiesChanged) => {
561
+ if ("Connected" in propertiesChanged) {
562
+ const { value } = propertiesChanged.Connected;
563
+ if (value) {
564
+ this._connected = true;
565
+ this.device.emit("connect", { connected: true });
566
+ } else {
567
+ this._connected = false;
568
+ this.device.emit("disconnect", { connected: false });
486
569
  }
487
- })
488
- this.device.on("disconnect", ()=>{
489
- if (this.isActive()) {
490
- this.debug(`Device disconnected. Attempting to reconnect to ${this.getName()}`)
491
- try {
492
- this.deviceConnect(true).then(()=>{
493
- this.debug(`Device reconnected -- ${this.getName()}`)
494
- })
495
- }
496
- catch (e) {
497
- this.setPluginError( `Error while reconnecting to ${this.getName()}: ${e.message}`)
498
- this.debug( `Error while reconnecting to ${this.getName()}: ${e.message}`)
499
- this.debug(e)
500
- }
501
- }
502
- })
570
+ }
571
+ }
572
+ );
573
+ if (autoReconnect){
574
+ this.device.on("disconnect", async () => {
575
+ this._connected = false;
576
+ if (this.isActive()) {
577
+ this.debug(
578
+ `Device disconnected. Attempting to reconnect to ${this.getName()}`
579
+ );
580
+ try {
581
+ await this.deactivateGATT();
582
+ await this.activateGATT(true);
583
+ this.debug(`(${this.getName()}) Device reconnected.`);
584
+ } catch (e) {
585
+ this.setError(
586
+ `Error while reconnecting to ${this.getName()}: ${
587
+ e.message
588
+ }`
589
+ );
590
+ this.debug(
591
+ `Error while reconnecting to ${this.getName()}: ${
592
+ e.message
593
+ }`
594
+ );
595
+ this.debug(e);
596
+ }
597
+ }
598
+ });
503
599
  }
600
+ }
504
601
 
505
602
  try {
506
603
 
@@ -517,9 +614,10 @@ class BTSensor extends EventEmitter {
517
614
  You know, the little things.
518
615
  */
519
616
  await this._adapter.helper.callMethod('StopDiscovery')
520
- await this._adapter.helper.callMethod('SetDiscoveryFilter', {
521
- Transport: new Variant('s', this._adapter?._transport??"le")
522
- })
617
+
618
+ //await this._adapter.helper.callMethod('SetDiscoveryFilter', {
619
+ // Transport: new Variant('s', this._adapter?._transport??"le")
620
+ //})
523
621
  await this._adapter.helper.callMethod('StartDiscovery')
524
622
 
525
623
  } catch (e){
@@ -542,22 +640,21 @@ class BTSensor extends EventEmitter {
542
640
 
543
641
  initGATTInterval(){
544
642
  this.device.disconnect().then(()=>{
545
- this.initPropertiesChanged()
546
- this.intervalID = setInterval( () => {
547
- this.initGATTConnection().then(async ()=>{
643
+ this.intervalID = setInterval( async () => {
644
+ try {
645
+ await this.initGATTConnection()
548
646
  await this.emitGATT()
549
- this.device.disconnect()
550
- .then(()=>
551
- this.initPropertiesChanged()
552
- )
553
- .catch((e)=>{
554
- this.debug(`Error disconnecting from ${this.getName()}: ${e.message}`)
555
- })
556
- })
557
- .catch((error)=>{
647
+ }
648
+ catch(error) {
558
649
  this.debug(error)
559
650
  throw new Error(`unable to emit values for device ${this.getName()}:${error}`)
560
- })
651
+ }
652
+ finally{
653
+ this.deactivateGATT().catch(
654
+ (e)=>{
655
+ this.debug(`(${this.getName()}) Error deactivating GATT Connection: ${e.message}`)
656
+ })
657
+ }
561
658
  }
562
659
  , this.pollFreq*1000)
563
660
  })
@@ -721,6 +818,10 @@ class BTSensor extends EventEmitter {
721
818
  getState(){
722
819
  return this._state
723
820
  }
821
+ setState(state){
822
+ this._state = state
823
+ this.emit("state",this._state)
824
+ }
724
825
 
725
826
  getDomain(){
726
827
  return this.constructor.Domain
@@ -824,14 +925,13 @@ class BTSensor extends EventEmitter {
824
925
  this.currentProperties = await this.constructor.getDeviceProps(this.device)
825
926
  if (this.usingGATT()){
826
927
  try {
827
- this.activateGATT()
928
+ await this.activateGATT(true)
828
929
  } catch (e) {
829
- this.debug(`GATT services unavailable for ${this.getName()}. Reason: ${e}`)
830
- this._state="ERROR"
930
+ this.setError(`GATT services unavailable for ${this.getName()}. Reason: ${e}`)
831
931
  return
832
932
  }
833
933
  }
834
- this._state="ACTIVE"
934
+ this.setState("ACTIVE")
835
935
  this._propertiesChanged(this.currentProperties)
836
936
  }
837
937
 
@@ -863,9 +963,12 @@ class BTSensor extends EventEmitter {
863
963
  this.device.helper.removeListeners()
864
964
 
865
965
  if (this.intervalID){
966
+ this.debug(`${this.getName()}::stopListening called clearInterval()`)
866
967
  clearInterval(this.intervalID)
867
968
  }
868
- this._state="ASLEEP"
969
+ if( this.usingGATT() )
970
+ await this.deactivateGATT()
971
+ this.setState("ASLEEP")
869
972
  }
870
973
  //END Sensor listen-to-changes functions
871
974
 
@@ -890,7 +993,7 @@ class BTSensor extends EventEmitter {
890
993
  const path = config.paths[tag]
891
994
  if (!(path===undefined)) {
892
995
  const preparedPath =
893
- this.app.handleMessage(id,
996
+ this._app.handleMessage(id,
894
997
  {
895
998
  updates:
896
999
  [{ meta: [{path: this.preparePath(path), value: { units: pathMeta?.unit }}]}]
@@ -908,7 +1011,7 @@ class BTSensor extends EventEmitter {
908
1011
  let preparedPath = this.preparePath(path)
909
1012
  this.on(tag, (val)=>{
910
1013
  if (pathMeta.notify){
911
- this.app.notify(tag, val, id )
1014
+ this._app.notify(tag, val, id )
912
1015
  } else {
913
1016
  this.updatePath(preparedPath,val, id, source)
914
1017
  }
@@ -917,7 +1020,7 @@ class BTSensor extends EventEmitter {
917
1020
  })
918
1021
  }
919
1022
  updatePath(path, val, id, source){
920
- this.app.handleMessage(id, {updates: [ { $source: source, values: [ { path: path, value: val }] } ] })
1023
+ this._app.handleMessage(id, {updates: [ { $source: source, values: [ { path: path, value: val }] } ] })
921
1024
  }
922
1025
  elapsedTimeSinceLastContact(){
923
1026
  return (Date.now()-this?._lastContact??Date.now())/1000
package/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Bluetooth Sensors for [Signal K](http://www.signalk.org)
2
2
 
3
3
  ## WHAT'S NEW
4
+ # Version 1.2.6-beta-4
5
+
6
+ - Improved reconnect logic for connected devices
7
+ - Error display in config (preliminary effort)
8
+ - JikongBMS support (aka JKBMS)
4
9
 
5
10
  # Version 1.2.6-beta-3
6
11
 
package/classLoader.js CHANGED
@@ -10,8 +10,15 @@ const semver = require('semver')
10
10
 
11
11
  classFiles.forEach( (file) => {
12
12
  const filePath = path.join(dir, file);
13
- const cls = require (filePath)
14
- classMap.set(cls.name, cls);
13
+ try{
14
+ const cls = require (filePath)
15
+ classMap.set(cls.name, cls);
16
+ }
17
+ catch (e) {
18
+ console.log(`Unable to load class (${cls?.name}): ${e.message}`)
19
+ console.log(e)
20
+ }
21
+
15
22
  })
16
23
  return classMap
17
24
  }
@@ -26,10 +26,13 @@ export class FakeGATTCharacteristic extends EventEmitter{
26
26
  this.values=values
27
27
  this.interval = interval
28
28
  }
29
+ writeValueWithoutResponse(buffer){
30
+ //do nothing for now
31
+ }
29
32
  startNotifications(){
30
33
  this.intervalID=setInterval(()=>{
31
34
  if (this.values.length>0){
32
- this.emit("valuechanged",Buffer.from(this.values[this.valueIndex++],"hex") )
35
+ this.emit("valuechanged",Buffer.from((this.values[this.valueIndex++]).replaceAll(" ",""),"hex") )
33
36
  if (this.valueIndex>=this.values.length)
34
37
  this.valueIndex=0
35
38
  }