bt-sensors-plugin-sk 1.1.1 → 1.2.0-beta.0.0.10
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 +304 -108
- package/Queue.js +48 -0
- package/README.md +36 -22
- package/classLoader.js +38 -0
- package/definitions.json +941 -0
- package/index.js +425 -375
- package/package.json +52 -6
- package/plugin_defaults.json +146 -0
- package/public/893.js +1 -1
- package/public/main.js +1 -100
- package/public/remoteEntry.js +1 -129
- package/sensor_classes/ATC.js +34 -22
- package/sensor_classes/Aranet/AranetSensor.js +4 -0
- package/sensor_classes/Aranet2.js +16 -15
- package/sensor_classes/Aranet4.js +23 -17
- package/sensor_classes/BlackListedDevice.js +4 -0
- package/sensor_classes/DEVELOPMENT.md +1 -6
- package/sensor_classes/GoveeH50xx.js +12 -12
- package/sensor_classes/GoveeH510x.js +7 -8
- package/sensor_classes/IBeacon.js +6 -10
- package/sensor_classes/Inkbird.js +8 -6
- package/sensor_classes/JBDBMS.js +33 -25
- package/sensor_classes/KilovaultHLXPlus.js +33 -16
- package/sensor_classes/LancolVoltageMeter.js +4 -3
- package/sensor_classes/MopekaTankSensor.js +31 -15
- package/sensor_classes/Renogy/RenogySensor.js +15 -6
- package/sensor_classes/RenogyBattery.js +15 -13
- package/sensor_classes/RenogyRoverClient.js +2 -3
- package/sensor_classes/RuuviTag.js +36 -27
- package/sensor_classes/ShellySBHT003C.js +19 -22
- package/sensor_classes/SwitchBotMeterPlus.js +10 -12
- package/sensor_classes/SwitchBotTH.js +13 -16
- package/sensor_classes/UNKNOWN.js +8 -4
- package/sensor_classes/UltrasonicWindMeter.js +13 -5
- package/sensor_classes/Victron/VictronSensor.js +6 -2
- package/sensor_classes/VictronACCharger.js +19 -8
- package/sensor_classes/VictronBatteryMonitor.js +49 -37
- package/sensor_classes/VictronDCDCConverter.js +14 -5
- package/sensor_classes/VictronDCEnergyMeter.js +27 -15
- package/sensor_classes/VictronGXDevice.js +2 -5
- package/sensor_classes/VictronInverter.js +19 -15
- package/sensor_classes/VictronInverterRS.js +19 -8
- package/sensor_classes/VictronLynxSmartBMS.js +15 -13
- package/sensor_classes/VictronOrionXS.js +16 -3
- package/sensor_classes/VictronSmartBatteryProtect.js +5 -6
- package/sensor_classes/VictronSmartLithium.js +18 -18
- package/sensor_classes/VictronSolarCharger.js +31 -18
- package/sensor_classes/VictronVEBus.js +2 -5
- package/sensor_classes/XiaomiMiBeacon.js +31 -21
- package/spec/electrical.json +688 -0
- package/spec/environment.json +401 -0
- package/spec/sensors.json +39 -0
- package/spec/tanks.json +115 -0
- package/src/components/PluginConfigurationPanel.js +393 -0
- package/src/index.js +0 -0
- package/webpack.config.js +71 -0
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.js")
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @author Andrew Gerngross <oh.that.andy@gmail.com>
|
|
@@ -10,7 +11,9 @@ const EventEmitter = require('node:events');
|
|
|
10
11
|
* {@link module:node-ble}
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
const BTCompanies = require('./bt_co.json')
|
|
14
|
+
const BTCompanies = require('./bt_co.json');
|
|
15
|
+
const connectQueue = new AutoQueue()
|
|
16
|
+
|
|
14
17
|
/**
|
|
15
18
|
* @global A map of company names keyed by their Bluetooth ID
|
|
16
19
|
* {@link ./sensor_classes/bt_co.json} file derived from bluetooth-sig source:
|
|
@@ -51,6 +54,38 @@ function signalQualityPercentQuad(rssi, perfect_rssi=-20, worst_rssi=-85) {
|
|
|
51
54
|
}
|
|
52
55
|
return Math.ceil(signal_quality);
|
|
53
56
|
}
|
|
57
|
+
function preparePath(obj, str) {
|
|
58
|
+
const regex = /\{([^}]+)\}/g;
|
|
59
|
+
let match;
|
|
60
|
+
let resultString = "";
|
|
61
|
+
let lastIndex = 0;
|
|
62
|
+
|
|
63
|
+
while ((match = regex.exec(str)) !== null) {
|
|
64
|
+
const fullMatch = match[0];
|
|
65
|
+
const keyToAccess = match[1].trim();
|
|
66
|
+
|
|
67
|
+
// Append the text before the current curly braces
|
|
68
|
+
resultString += str.substring(lastIndex, match.index);
|
|
69
|
+
lastIndex = regex.lastIndex;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
let evalResult = obj[keyToAccess];
|
|
73
|
+
if (typeof evalResult === 'function'){
|
|
74
|
+
evalResult= evalResult.call(obj)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
resultString += evalResult !== undefined ? evalResult : `${keyToAccess}_value_undefined`;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`Error accessing key '${keyToAccess}':`, error);
|
|
80
|
+
resultString += fullMatch; // Keep the original curly braces on error
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Append any remaining text after the last curly braces
|
|
85
|
+
resultString += str.substring(lastIndex);
|
|
86
|
+
|
|
87
|
+
return resultString || str; // Return original string if no replacements were made
|
|
88
|
+
}
|
|
54
89
|
|
|
55
90
|
/**
|
|
56
91
|
* @classdesc Class that all sensor classes should inherit from. Sensor subclasses
|
|
@@ -65,8 +100,9 @@ function signalQualityPercentQuad(rssi, perfect_rssi=-20, worst_rssi=-85) {
|
|
|
65
100
|
*/
|
|
66
101
|
|
|
67
102
|
class BTSensor extends EventEmitter {
|
|
68
|
-
|
|
69
|
-
|
|
103
|
+
|
|
104
|
+
static DEFAULTS = require('./plugin_defaults.json');
|
|
105
|
+
|
|
70
106
|
/**
|
|
71
107
|
*
|
|
72
108
|
* @param {module:node-ble/Device} device
|
|
@@ -77,13 +113,11 @@ class BTSensor extends EventEmitter {
|
|
|
77
113
|
super()
|
|
78
114
|
|
|
79
115
|
this.device=device
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.Metadatum = this.constructor.Metadatum
|
|
86
|
-
this.metadata = new Map()
|
|
116
|
+
|
|
117
|
+
Object.assign(this,config)
|
|
118
|
+
Object.assign(this,gattConfig)
|
|
119
|
+
|
|
120
|
+
this._state = null
|
|
87
121
|
}
|
|
88
122
|
/**
|
|
89
123
|
* @function _test Test sensor parsing
|
|
@@ -133,7 +167,7 @@ class BTSensor extends EventEmitter {
|
|
|
133
167
|
var b = Buffer.from(data.replaceAll(" ",""),"hex")
|
|
134
168
|
const d = new this(null,config)
|
|
135
169
|
d.initMetadata()
|
|
136
|
-
d.
|
|
170
|
+
Object.keys(d.getPaths()).forEach((tag)=>{
|
|
137
171
|
d.on(tag,(v)=>console.log(`${tag}=${v}`))
|
|
138
172
|
})
|
|
139
173
|
if (key) {
|
|
@@ -145,38 +179,7 @@ class BTSensor extends EventEmitter {
|
|
|
145
179
|
d.removeAllListeners()
|
|
146
180
|
|
|
147
181
|
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* @class encapsulates a sensor's metadata
|
|
151
|
-
* @todo refactor and/or just plain rethink constructor parameters
|
|
152
|
-
*/
|
|
153
|
-
class Metadatum{
|
|
154
|
-
|
|
155
|
-
constructor(tag, unit, description, read, gatt=null, type){
|
|
156
|
-
this.tag = tag
|
|
157
|
-
this.unit = unit
|
|
158
|
-
this.description = description
|
|
159
|
-
this.read = read
|
|
160
|
-
this.gatt = gatt
|
|
161
|
-
this.type = type //schema type e.g. 'number'
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
*
|
|
165
|
-
* @returns A JSON object passed by plugin to the plugin's schema
|
|
166
|
-
* dynamically updated at runtime upon discovery and interrogation
|
|
167
|
-
* of the device
|
|
168
|
-
*/
|
|
169
|
-
asJSONSchema(){
|
|
170
|
-
return {
|
|
171
|
-
type:this?.type??'string',
|
|
172
|
-
title: this?.description,
|
|
173
|
-
unit: this?.unit,
|
|
174
|
-
enum: this?.enum,
|
|
175
|
-
default: this?.default
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
182
|
+
|
|
180
183
|
//static utility Functions
|
|
181
184
|
/**
|
|
182
185
|
*
|
|
@@ -274,42 +277,157 @@ class BTSensor extends EventEmitter {
|
|
|
274
277
|
*
|
|
275
278
|
*/
|
|
276
279
|
|
|
277
|
-
|
|
280
|
+
initSchema(){
|
|
281
|
+
this._schema = {
|
|
282
|
+
properties:{
|
|
283
|
+
active: {title: "Active", type: "boolean", default: true },
|
|
284
|
+
discoveryTimeout: {title: "Device discovery timeout (in seconds)",
|
|
285
|
+
type: "integer", default:30,
|
|
286
|
+
minimum: 10,
|
|
287
|
+
maximum: 600 },
|
|
288
|
+
|
|
289
|
+
params:{
|
|
290
|
+
title:`Device parameters`,
|
|
291
|
+
description: this.getDescription(),
|
|
292
|
+
type:"object",
|
|
293
|
+
properties:{}
|
|
294
|
+
},
|
|
295
|
+
paths:{
|
|
296
|
+
title:"Signalk Paths",
|
|
297
|
+
description: `Signalk paths to be updated when ${this.getName()}'s values change`,
|
|
298
|
+
type:"object",
|
|
299
|
+
properties:{}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (this.hasGATT()){
|
|
305
|
+
|
|
306
|
+
this._schema.properties.gattParams={
|
|
307
|
+
title:`GATT Specific device parameters`,
|
|
308
|
+
description: this.getGATTDescription(),
|
|
309
|
+
type:"object",
|
|
310
|
+
properties:{
|
|
311
|
+
useGATT: {title: "Use GATT connection", type: "boolean", default: false },
|
|
312
|
+
pollFreq: { type: "number", title: "Polling frequency in seconds"}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
278
318
|
//create the 'name' parameter
|
|
279
|
-
|
|
280
|
-
|
|
319
|
+
this.addDefaultParam("name")
|
|
320
|
+
.default=this?.currentProperties?.Name
|
|
321
|
+
|
|
322
|
+
//create the 'location' parameter
|
|
323
|
+
|
|
324
|
+
//this.addDefaultParam("location")
|
|
325
|
+
|
|
281
326
|
//create the 'RSSI' parameter
|
|
327
|
+
this.addDefaultPath("RSSI","sensors.RSSI")
|
|
328
|
+
this.getPath("RSSI").read=()=>{return this.getRSSI()}
|
|
329
|
+
this.getPath("RSSI").read.bind(this)
|
|
330
|
+
|
|
331
|
+
}
|
|
332
|
+
async init(){
|
|
282
333
|
this.currentProperties = await this.constructor.getDeviceProps(this.device)
|
|
283
|
-
this.
|
|
284
|
-
|
|
285
|
-
this.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
md.isParam=true
|
|
297
|
-
md.isGATT=true
|
|
334
|
+
this.initSchema()
|
|
335
|
+
|
|
336
|
+
this.initListen()
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
initListen(){
|
|
340
|
+
Promise.resolve(this.listen())
|
|
341
|
+
}
|
|
342
|
+
activate(config, plugin){
|
|
343
|
+
if (config.paths){
|
|
344
|
+
this.createPaths(config,plugin.id)
|
|
345
|
+
this.initPaths(config,plugin.id)
|
|
346
|
+
this.debug(`Paths activated for ${this.getDisplayName()}`);
|
|
298
347
|
}
|
|
348
|
+
if (this.usingGATT()){
|
|
349
|
+
try {
|
|
350
|
+
this.activateGATT()
|
|
351
|
+
} catch (e) {
|
|
352
|
+
this.debug(`GATT services unavailable for ${this.getName()}. Reason: ${e}`)
|
|
353
|
+
this._state="ERROR"
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
this._state="ACTIVE"
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
activateGATT(){
|
|
361
|
+
this.initGATTConnection().then(async ()=>{
|
|
362
|
+
this.emitGATT()
|
|
363
|
+
if (this.pollFreq){
|
|
364
|
+
this.initGATTInterval()
|
|
365
|
+
}
|
|
366
|
+
else
|
|
367
|
+
await this.initGATTNotifications()
|
|
368
|
+
}).catch((e)=>{
|
|
369
|
+
this.app.debug(`Unable to activate GATT connection for ${this.getName()} (${this.getMacAddress()}): ${e}`)
|
|
370
|
+
})
|
|
299
371
|
}
|
|
300
372
|
|
|
301
373
|
/**
|
|
302
374
|
* Add a metadatum instance to the sensor instance
|
|
303
375
|
*
|
|
304
|
-
* @param {String} tag
|
|
376
|
+
* @param {String} tag
|
|
305
377
|
* @param {...any} args
|
|
306
378
|
* @returns {this.Metadatum} the new metadatum instance
|
|
307
379
|
*/
|
|
308
380
|
|
|
309
381
|
addMetadatum(tag, ...args){
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
382
|
+
|
|
383
|
+
const md = {}
|
|
384
|
+
if (args[0]) md.unit = args[0]
|
|
385
|
+
if (args[1]) md.title = args[1]
|
|
386
|
+
if (args[2]) md.read = args[2]
|
|
387
|
+
if (args[3]) md.gatt = args[3]
|
|
388
|
+
if (args[4]) md.type = args[4]
|
|
389
|
+
|
|
390
|
+
return this.addPath(tag,md)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
addParameter(tag, param){
|
|
394
|
+
|
|
395
|
+
if (!param.type)
|
|
396
|
+
param.type="string"
|
|
397
|
+
|
|
398
|
+
this._schema.properties.params.properties[tag]=param
|
|
399
|
+
return this._schema.properties.params.properties[tag]
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
addPath(tag, path){
|
|
403
|
+
if (!path.type)
|
|
404
|
+
path.type="string"
|
|
405
|
+
|
|
406
|
+
if (!path.pattern)
|
|
407
|
+
path.pattern="^(?:[^{}\\s]*\\{[a-zA-Z0-9]+\\}[^{}\\s]*|[^{}\\s]*)$"
|
|
408
|
+
this._schema.properties.paths.properties[tag]=path
|
|
409
|
+
return this._schema.properties.paths.properties[tag]
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
addGATTParameter(tag, param){
|
|
413
|
+
|
|
414
|
+
if (!param.type)
|
|
415
|
+
param.type="string"
|
|
416
|
+
|
|
417
|
+
return this._schema.properties.gattParams.properties[tag]=param
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
addDefaultPath(tag,defaultPath){
|
|
421
|
+
const path = eval(`BTSensor.DEFAULTS.${defaultPath}`)
|
|
422
|
+
return this.addPath(tag,Object.assign({}, path))
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
addDefaultParam(tag){
|
|
426
|
+
return this.addParameter(tag,Object.assign({}, BTSensor.DEFAULTS.params[tag]))
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
getJSONSchema(){
|
|
430
|
+
return this._schema
|
|
313
431
|
}
|
|
314
432
|
|
|
315
433
|
//GATT Initialization functions
|
|
@@ -337,6 +455,39 @@ class BTSensor extends EventEmitter {
|
|
|
337
455
|
throw new Error("::initGATTNotifications() should be implemented by the BTSensor subclass")
|
|
338
456
|
}
|
|
339
457
|
|
|
458
|
+
deviceConnect() {
|
|
459
|
+
|
|
460
|
+
/* CAUTION: HACK AHEAD
|
|
461
|
+
|
|
462
|
+
Bluez for some cockamamie reason (It's 2025 for chrissake.
|
|
463
|
+
BLUETOOTH ISN'T JUST FOR DESKTOPS ANYMORE, BLUEZ DEVS!)
|
|
464
|
+
SUSPENDS scanning while connected to a device.
|
|
465
|
+
|
|
466
|
+
The next line of code gives the scanner a kick in the arse,
|
|
467
|
+
starting it up again so, I dunno, another device might be able
|
|
468
|
+
to connect and sensor classes could maybe get beacon updates.
|
|
469
|
+
|
|
470
|
+
You know, the little things.
|
|
471
|
+
*/
|
|
472
|
+
return connectQueue.enqueue( async ()=>{
|
|
473
|
+
this.debug(`Connecting to ${this.getName()}`)
|
|
474
|
+
await this.device.connect()
|
|
475
|
+
try {
|
|
476
|
+
|
|
477
|
+
await this._adapter.helper.callMethod('StopDiscovery')
|
|
478
|
+
await this._adapter.helper.callMethod('SetDiscoveryFilter', {
|
|
479
|
+
Transport: new Variant('s', this._adapter?._transport??"le")
|
|
480
|
+
})
|
|
481
|
+
await this._adapter.helper.callMethod('StartDiscovery')
|
|
482
|
+
|
|
483
|
+
} catch (e){
|
|
484
|
+
//probably ignorable error. probably.
|
|
485
|
+
console.log(e)
|
|
486
|
+
}
|
|
487
|
+
})
|
|
488
|
+
/* END HACK*/
|
|
489
|
+
}
|
|
490
|
+
|
|
340
491
|
/**
|
|
341
492
|
*
|
|
342
493
|
* Subclasses do NOT need to override this function
|
|
@@ -376,12 +527,12 @@ class BTSensor extends EventEmitter {
|
|
|
376
527
|
*/
|
|
377
528
|
initPropertiesChanged(){
|
|
378
529
|
|
|
379
|
-
this.
|
|
530
|
+
this._propertiesChanged.bind(this)
|
|
380
531
|
this.device.helper._prepare()
|
|
381
532
|
this.device.helper.on("PropertiesChanged",
|
|
382
533
|
((props)=> {
|
|
383
534
|
try{
|
|
384
|
-
this.
|
|
535
|
+
this._propertiesChanged(props)
|
|
385
536
|
}
|
|
386
537
|
catch(error){
|
|
387
538
|
this.debug(`Error occured on ${this.getNameAndAddress()}: ${error?.message??error}`)
|
|
@@ -392,30 +543,23 @@ class BTSensor extends EventEmitter {
|
|
|
392
543
|
//END instance initialization functions
|
|
393
544
|
|
|
394
545
|
//Metadata functions
|
|
395
|
-
getMetadata(){
|
|
396
|
-
if (this.metadata==undefined)
|
|
397
|
-
this.metadata= new Map(this.constructor.getMetadata())
|
|
398
|
-
return this.metadata
|
|
399
|
-
}
|
|
400
546
|
|
|
401
|
-
|
|
402
|
-
return this.
|
|
547
|
+
getPath(tag){
|
|
548
|
+
return this._schema.properties.paths.properties[tag]
|
|
403
549
|
}
|
|
404
550
|
|
|
405
|
-
|
|
406
|
-
return
|
|
407
|
-
[...this.getMetadata().entries()].filter(([key,value]) => !(value?.isParam??false))
|
|
408
|
-
)
|
|
551
|
+
getPaths(){
|
|
552
|
+
return this._schema.properties.paths.properties
|
|
409
553
|
}
|
|
410
|
-
|
|
411
|
-
return
|
|
412
|
-
[...this.getMetadata().entries()].filter(([key,value]) => (value?.isParam??false) && !(value?.isGATT??false))
|
|
413
|
-
)
|
|
554
|
+
getParams(){
|
|
555
|
+
return this._schema.properties.params.properties
|
|
414
556
|
}
|
|
415
|
-
|
|
416
|
-
return
|
|
417
|
-
|
|
418
|
-
|
|
557
|
+
getParameter(param){
|
|
558
|
+
return this.getParams()[param]
|
|
559
|
+
}
|
|
560
|
+
getGATTParams(){
|
|
561
|
+
return this._schema.properties.gattParams.properties
|
|
562
|
+
|
|
419
563
|
}
|
|
420
564
|
//End metadata functions
|
|
421
565
|
|
|
@@ -429,9 +573,13 @@ class BTSensor extends EventEmitter {
|
|
|
429
573
|
return `${this.getName()} from ${this.getManufacturer()}`
|
|
430
574
|
}
|
|
431
575
|
getName(){
|
|
432
|
-
|
|
433
|
-
|
|
576
|
+
const name = this?.name??this.currentProperties.Name
|
|
577
|
+
return name?name:"Unknown"
|
|
434
578
|
|
|
579
|
+
}
|
|
580
|
+
macAndName(){
|
|
581
|
+
return `${this.getName().replaceAll(':', '-').replaceAll(" ","_")}-${this.getMacAddress().replaceAll(':', '-')}`
|
|
582
|
+
}
|
|
435
583
|
getNameAndAddress(){
|
|
436
584
|
return `${this.getName()} at ${this.getMacAddress()}`
|
|
437
585
|
}
|
|
@@ -475,6 +623,8 @@ class BTSensor extends EventEmitter {
|
|
|
475
623
|
else
|
|
476
624
|
return null
|
|
477
625
|
}
|
|
626
|
+
|
|
627
|
+
|
|
478
628
|
//END Device property functions
|
|
479
629
|
|
|
480
630
|
//Sensor RSSI state functions
|
|
@@ -490,6 +640,13 @@ class BTSensor extends EventEmitter {
|
|
|
490
640
|
return signalQualityPercentQuad(rssi)
|
|
491
641
|
}
|
|
492
642
|
|
|
643
|
+
getState(){
|
|
644
|
+
return this._state
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
isActive(){
|
|
648
|
+
return this._state=="ACTIVE"
|
|
649
|
+
}
|
|
493
650
|
getBars(){
|
|
494
651
|
const ss = this.getSignalStrength()
|
|
495
652
|
var bars = ""
|
|
@@ -532,7 +689,8 @@ class BTSensor extends EventEmitter {
|
|
|
532
689
|
* @param {*} props which contains ManufacturerData and ServiceData (where the sensor's data resides)
|
|
533
690
|
* set up by BTSensor::initPropertiesChanged()
|
|
534
691
|
*/
|
|
535
|
-
|
|
692
|
+
_propertiesChanged(props){
|
|
693
|
+
this._lastContact=Date.now()
|
|
536
694
|
|
|
537
695
|
if (props.RSSI) {
|
|
538
696
|
this.currentProperties.RSSI=this.valueIfVariant(props.RSSI)
|
|
@@ -543,8 +701,13 @@ class BTSensor extends EventEmitter {
|
|
|
543
701
|
|
|
544
702
|
if (props.ManufacturerData)
|
|
545
703
|
this.currentProperties.ManufacturerData=this.valueIfVariant(props.ManufacturerData)
|
|
704
|
+
if (this.isActive())
|
|
705
|
+
this.propertiesChanged(props)
|
|
546
706
|
|
|
547
707
|
}
|
|
708
|
+
propertiesChanged(props){
|
|
709
|
+
//implemented by subclass
|
|
710
|
+
}
|
|
548
711
|
|
|
549
712
|
/**
|
|
550
713
|
*
|
|
@@ -555,14 +718,18 @@ class BTSensor extends EventEmitter {
|
|
|
555
718
|
}
|
|
556
719
|
|
|
557
720
|
emitData(tag, buffer, ...args){
|
|
558
|
-
|
|
721
|
+
const md = this.getPath(tag)
|
|
722
|
+
if (md && md.read)
|
|
723
|
+
this.emit(tag, md.read(buffer, ...args))
|
|
724
|
+
|
|
725
|
+
|
|
559
726
|
}
|
|
560
727
|
|
|
561
728
|
emitValuesFrom(buffer){
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
729
|
+
Object.keys(this.getPaths())
|
|
730
|
+
.forEach(
|
|
731
|
+
(tag)=>this.emitData(tag,buffer)
|
|
732
|
+
)
|
|
566
733
|
}
|
|
567
734
|
|
|
568
735
|
/**
|
|
@@ -576,21 +743,10 @@ class BTSensor extends EventEmitter {
|
|
|
576
743
|
listen(){
|
|
577
744
|
try{
|
|
578
745
|
this.initPropertiesChanged()
|
|
579
|
-
this.
|
|
746
|
+
this._propertiesChanged(this.currentProperties)
|
|
580
747
|
} catch(e){
|
|
581
748
|
this.debug(e)
|
|
582
749
|
}
|
|
583
|
-
if (this.usingGATT()){
|
|
584
|
-
this.initGATTConnection().then(async ()=>{
|
|
585
|
-
this.emitGATT()
|
|
586
|
-
if (this.pollFreq){
|
|
587
|
-
this.initGATTInterval()
|
|
588
|
-
}
|
|
589
|
-
else
|
|
590
|
-
await this.initGATTNotifications()
|
|
591
|
-
})
|
|
592
|
-
.catch((e)=>this.debug(`GATT services unavailable for ${this.getName()}. Reason: ${e}`))
|
|
593
|
-
}
|
|
594
750
|
return this
|
|
595
751
|
}
|
|
596
752
|
|
|
@@ -601,12 +757,13 @@ class BTSensor extends EventEmitter {
|
|
|
601
757
|
* Called automatically by Plugin::plugin.stop()
|
|
602
758
|
*/
|
|
603
759
|
|
|
604
|
-
stopListening(){
|
|
760
|
+
async stopListening(){
|
|
605
761
|
this.removeAllListeners()
|
|
606
762
|
this.device.helper.removeListeners()
|
|
607
763
|
if (this.intervalID){
|
|
608
764
|
clearInterval(this.intervalID)
|
|
609
|
-
}
|
|
765
|
+
}
|
|
766
|
+
this._state="ASLEEP"
|
|
610
767
|
}
|
|
611
768
|
//END Sensor listen-to-changes functions
|
|
612
769
|
|
|
@@ -622,6 +779,45 @@ class BTSensor extends EventEmitter {
|
|
|
622
779
|
|
|
623
780
|
}
|
|
624
781
|
//End instance utility functions
|
|
782
|
+
|
|
783
|
+
createPaths(config, id){
|
|
784
|
+
// const source = `${this.getName()} (${id})`
|
|
785
|
+
|
|
786
|
+
Object.keys(this.getPaths()).forEach((tag)=>{
|
|
787
|
+
const pathMeta=this.getPath(tag)
|
|
788
|
+
const path = config.paths[tag]
|
|
789
|
+
if (!(path===undefined))
|
|
790
|
+
this.app.handleMessage(id,
|
|
791
|
+
{
|
|
792
|
+
// $source: source,
|
|
793
|
+
updates:
|
|
794
|
+
[{ meta: [{path: preparePath(this, path), value: { units: pathMeta?.unit }}]}]
|
|
795
|
+
})
|
|
796
|
+
})
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
initPaths(deviceConfig, id){
|
|
800
|
+
const source = this.getName()
|
|
801
|
+
Object.keys(this.getPaths()).forEach((tag)=>{
|
|
802
|
+
const pathMeta=this.getPath(tag)
|
|
803
|
+
const path = deviceConfig.paths[tag];
|
|
804
|
+
if (!(path === undefined)) {
|
|
805
|
+
this.on(tag, (val)=>{
|
|
806
|
+
if (pathMeta.notify){
|
|
807
|
+
this.app.notify(tag, val, id )
|
|
808
|
+
} else {
|
|
809
|
+
this.updatePath(preparePath(this,path),val, id, source)
|
|
810
|
+
}
|
|
811
|
+
})
|
|
812
|
+
}
|
|
813
|
+
})
|
|
814
|
+
}
|
|
815
|
+
updatePath(path, val, id, source){
|
|
816
|
+
this.app.handleMessage(id, {updates: [ { $source: source, values: [ { path: path, value: val }] } ] })
|
|
817
|
+
}
|
|
818
|
+
elapsedTimeSinceLastContact(){
|
|
819
|
+
return (Date.now()-this?._lastContact??Date.now())/1000
|
|
820
|
+
}
|
|
625
821
|
}
|
|
626
822
|
|
|
627
823
|
module.exports = BTSensor
|
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
|