bt-sensors-plugin-sk 1.1.0-beta.2.1.1 → 1.1.0-beta.2.1.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 +391 -147
- package/README.md +2 -2
- package/index.js +14 -10
- package/package.json +1 -1
- package/sensor_classes/ATC.js +43 -11
- package/sensor_classes/BlackListedDevice.js +6 -0
- package/sensor_classes/Inkbird.js +1 -1
- package/sensor_classes/RuuviTag.js +3 -3
- package/sensor_classes/Victron/VictronConstants.js +5 -0
- package/sensor_classes/Victron/VictronSensor.js +15 -20
- package/sensor_classes/VictronACCharger.js +8 -5
- package/sensor_classes/VictronBatteryMonitor.js +51 -19
- package/sensor_classes/VictronGXDevice.js +1 -1
- package/sensor_classes/VictronSmartLithium.js +15 -12
- package/sensor_classes/VictronSolarCharger.js +18 -17
- package/sensor_classes/XiaomiMiBeacon.js +5 -5
package/BTSensor.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
const { Variant } = require('dbus-next');
|
|
2
2
|
const { log } = require('node:console');
|
|
3
3
|
const EventEmitter = require('node:events');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @author Andrew Gerngross <oh.that.andy@gmail.com>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* {@link module:node-ble}
|
|
11
|
+
*/
|
|
12
|
+
|
|
4
13
|
const BTCompanies = require('./bt_co.json')
|
|
5
14
|
/**
|
|
6
|
-
* @
|
|
7
|
-
*
|
|
8
|
-
* @
|
|
9
|
-
* @see EventEmitter, node-ble/Device
|
|
15
|
+
* @global A map of company names keyed by their Bluetooth ID
|
|
16
|
+
* {@link ./sensor_classes/bt_co.json} file derived from bluetooth-sig source:
|
|
17
|
+
* {@link https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/company_identifiers/company_identifiers.yaml}
|
|
10
18
|
*/
|
|
11
19
|
|
|
12
20
|
const BTCompanyMap=new Map()
|
|
@@ -16,7 +24,14 @@ BTCompanies.company_identifiers.forEach( (v) =>{
|
|
|
16
24
|
})
|
|
17
25
|
|
|
18
26
|
/**
|
|
19
|
-
*
|
|
27
|
+
* @function signalQualityPercentQuad
|
|
28
|
+
*
|
|
29
|
+
* Utility to convert RSSI (Bluetooth radio strength signal)
|
|
30
|
+
* decibel values to a linear percentage
|
|
31
|
+
*
|
|
32
|
+
* See {@link https://www.intuitibits.com/2016/03/23/dbm-to-percent-conversion/ }
|
|
33
|
+
*
|
|
34
|
+
*
|
|
20
35
|
*/
|
|
21
36
|
|
|
22
37
|
function signalQualityPercentQuad(rssi, perfect_rssi=-20, worst_rssi=-85) {
|
|
@@ -37,8 +52,27 @@ function signalQualityPercentQuad(rssi, perfect_rssi=-20, worst_rssi=-85) {
|
|
|
37
52
|
return Math.ceil(signal_quality);
|
|
38
53
|
}
|
|
39
54
|
|
|
55
|
+
/**
|
|
56
|
+
* @classdesc Class that all sensor classes should inherit from. Sensor subclasses
|
|
57
|
+
* monitor a BT peripheral and emit changes in the sensors's value like "temp" or "humidity"
|
|
58
|
+
* @class BTSensor
|
|
59
|
+
* @abstract
|
|
60
|
+
* @extends EventEmitter
|
|
61
|
+
*
|
|
62
|
+
* @requires module:node-ble/Device
|
|
63
|
+
* @requires module:node-ble/BusHelper
|
|
64
|
+
*
|
|
65
|
+
*/
|
|
66
|
+
|
|
40
67
|
class BTSensor extends EventEmitter {
|
|
41
68
|
static metadata=new Map()
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
*
|
|
72
|
+
* @param {module:node-ble/Device} device
|
|
73
|
+
* @param {*} config
|
|
74
|
+
* @param {*} gattConfig
|
|
75
|
+
*/
|
|
42
76
|
constructor(device, config={}, gattConfig={}) {
|
|
43
77
|
super()
|
|
44
78
|
|
|
@@ -49,11 +83,56 @@ class BTSensor extends EventEmitter {
|
|
|
49
83
|
this.pollFreq = gattConfig?.pollFreq
|
|
50
84
|
|
|
51
85
|
this.Metadatum = this.constructor.Metadatum
|
|
52
|
-
this.metadata = new Map(
|
|
53
|
-
}
|
|
54
|
-
|
|
86
|
+
this.metadata = new Map()
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* @function _test Test sensor parsing
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
*
|
|
93
|
+
* 1) from a command line execute:
|
|
94
|
+
* bluetoothctl scan on
|
|
95
|
+
*
|
|
96
|
+
* 2) from the command line execute:
|
|
97
|
+
* bluetoothctl devices
|
|
98
|
+
* until you see your device
|
|
99
|
+
*
|
|
100
|
+
* 3) from the command line execute:
|
|
101
|
+
* bluetoothctl info [your device's mac address]
|
|
102
|
+
*
|
|
103
|
+
* 4) copy the sensor's Manufacturer Data or Service Data (whichever advertises your sensor's values)
|
|
104
|
+
*
|
|
105
|
+
* 5) format the string of data so it's just the hex value separated by a space
|
|
106
|
+
*
|
|
107
|
+
* 6) from the command line execute:
|
|
108
|
+
* node
|
|
109
|
+
*
|
|
110
|
+
* 7) from the node prompt execute:
|
|
111
|
+
* const MySensorClass = require('./path_to_my_sensor_class/MySensorClass')
|
|
112
|
+
*
|
|
113
|
+
* 8) from the node prompt execute:
|
|
114
|
+
* const myData = <string of data you captured and formatted>
|
|
115
|
+
* const optionalDecryptionKey = <the encryption key for your sensor>
|
|
116
|
+
* MySensorClass._test(myData,optionalDecryptionKey)
|
|
117
|
+
*
|
|
118
|
+
* You should see your data parsed and formated on the console
|
|
119
|
+
* If you don't, it's possible your sensor is doing its parsing in the
|
|
120
|
+
* BTSensor::propertiesChanged function or the encryption key is invalid.
|
|
121
|
+
*
|
|
122
|
+
* Errors will occur if the data string is incomplete
|
|
123
|
+
*
|
|
124
|
+
* Unusual values are likely to appear if the data string is encrypted
|
|
125
|
+
* and you didn't provide a decryption key
|
|
126
|
+
*
|
|
127
|
+
* @static
|
|
128
|
+
* @param {string} data string formatted as "AE FF 45..." which is more or less how Bluetoothctl presents it
|
|
129
|
+
* @param {string|null} key encryption key (optional)
|
|
130
|
+
*
|
|
131
|
+
*/
|
|
132
|
+
static _test(data, key, config={}){
|
|
55
133
|
var b = Buffer.from(data.replaceAll(" ",""),"hex")
|
|
56
|
-
const d = new this()
|
|
134
|
+
const d = new this(null,config)
|
|
135
|
+
d.initMetadata()
|
|
57
136
|
d.getPathMetadata().forEach((datum,tag)=>{
|
|
58
137
|
d.on(tag,(v)=>console.log(`${tag}=${v}`))
|
|
59
138
|
})
|
|
@@ -67,12 +146,13 @@ class BTSensor extends EventEmitter {
|
|
|
67
146
|
|
|
68
147
|
}
|
|
69
148
|
static Metadatum =
|
|
149
|
+
/**
|
|
150
|
+
* @class encapsulates a sensor's metadata
|
|
151
|
+
* @todo refactor and/or just plain rethink constructor parameters
|
|
152
|
+
*/
|
|
70
153
|
class Metadatum{
|
|
71
154
|
|
|
72
|
-
constructor(tag, unit, description,
|
|
73
|
-
read=()=>{
|
|
74
|
-
return null
|
|
75
|
-
}, gatt=null, type){
|
|
155
|
+
constructor(tag, unit, description, read=()=>{return null}, gatt=null, type){
|
|
76
156
|
this.tag = tag
|
|
77
157
|
this.unit = unit
|
|
78
158
|
this.description = description
|
|
@@ -80,47 +160,99 @@ class BTSensor extends EventEmitter {
|
|
|
80
160
|
this.gatt = gatt
|
|
81
161
|
this.type = type //schema type e.g. 'number'
|
|
82
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
|
+
*/
|
|
83
169
|
asJSONSchema(){
|
|
84
170
|
return {
|
|
85
171
|
type:this?.type??'string',
|
|
86
172
|
title: this?.description,
|
|
87
173
|
unit: this?.unit,
|
|
174
|
+
enum: this?.enum,
|
|
88
175
|
default: this?.default
|
|
89
176
|
}
|
|
90
177
|
}
|
|
91
178
|
}
|
|
92
179
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
180
|
+
//static utility Functions
|
|
181
|
+
/**
|
|
182
|
+
*
|
|
183
|
+
* @param {*} v1
|
|
184
|
+
* @param {*} v2
|
|
185
|
+
* @returns NaN if v1 equals v2 otherwise, v1
|
|
186
|
+
*/
|
|
187
|
+
static NaNif(v1,v2) { return (v1==v2)?NaN:v1 }
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
*
|
|
192
|
+
* Following three functions are direct to DBUS functions to get
|
|
193
|
+
* around node-ble's not always reliable and not always persistent
|
|
194
|
+
* property get methods
|
|
195
|
+
*
|
|
196
|
+
* Subclasses do not need to call these functions
|
|
197
|
+
* except in static ::identify(device) functions
|
|
198
|
+
*
|
|
199
|
+
* ::_getPropsProxy(device) need never be called by any subclass
|
|
200
|
+
*
|
|
201
|
+
* Instances do not to call these functions at all.
|
|
202
|
+
* Instance device property gets are handled by the BTSensor class implementation
|
|
203
|
+
*
|
|
204
|
+
* @todo duplicate and derive a better {@link module:node-ble}
|
|
205
|
+
*
|
|
206
|
+
*/
|
|
207
|
+
static async _getPropsProxy(device){
|
|
208
|
+
|
|
209
|
+
if (!device._propsProxy) {
|
|
210
|
+
const objectProxy = await device.helper.dbus.getProxyObject(device.helper.service, device.helper.object)
|
|
211
|
+
device._propsProxy = await objectProxy.getInterface('org.freedesktop.DBus.Properties')
|
|
101
212
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
213
|
+
return device._propsProxy
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
static async getDeviceProps(device, propNames=[]){
|
|
217
|
+
const _propsProxy = await this._getPropsProxy(device)
|
|
218
|
+
const rawProps = await _propsProxy.GetAll(device.helper.iface)
|
|
219
|
+
const props = {}
|
|
220
|
+
for (const propKey in rawProps) {
|
|
221
|
+
if (propNames.length==0 || propNames.indexOf(propKey)>=0)
|
|
222
|
+
props[propKey] = rawProps[propKey].value
|
|
111
223
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
224
|
+
return props
|
|
225
|
+
}
|
|
226
|
+
static async getDeviceProp(device, prop){
|
|
227
|
+
const _propsProxy = await this._getPropsProxy(device)
|
|
228
|
+
try{
|
|
229
|
+
const rawProps = await _propsProxy.Get(device.helper.iface,prop)
|
|
230
|
+
return rawProps?.value
|
|
231
|
+
}
|
|
232
|
+
catch(e){
|
|
233
|
+
return null //Property $prop (probably) doesn't exist in $device
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//End static utitity functions
|
|
237
|
+
|
|
122
238
|
|
|
123
239
|
|
|
240
|
+
//static identity functions
|
|
241
|
+
|
|
242
|
+
static identify(device){
|
|
243
|
+
throw new Error("BTSensor is an abstract class. ::identify must be implemented by the subclass")
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* getManufacturerID is used to help ID the manufacturer of a device
|
|
248
|
+
*
|
|
249
|
+
* NOTE: Not all devices advertise their ManufacturerID in their ManufacturerData key
|
|
250
|
+
*
|
|
251
|
+
* @param {Device} device
|
|
252
|
+
* @returns the numeric ID of a device's manufacturer iff the device
|
|
253
|
+
* advertises ManufacturerData otherwise, null
|
|
254
|
+
*
|
|
255
|
+
*/
|
|
124
256
|
static async getManufacturerID(device){
|
|
125
257
|
const md = await this.getDeviceProp(device,'ManufacturerData')
|
|
126
258
|
if (!md) return null
|
|
@@ -130,16 +262,29 @@ class BTSensor extends EventEmitter {
|
|
|
130
262
|
}
|
|
131
263
|
return null
|
|
132
264
|
}
|
|
133
|
-
|
|
134
|
-
|
|
265
|
+
|
|
266
|
+
//END static identity functions
|
|
267
|
+
|
|
268
|
+
//Instance Initialization functions
|
|
269
|
+
/**
|
|
270
|
+
*
|
|
271
|
+
* init() initializes the sensor adding metadata as appropriate
|
|
272
|
+
* as well as initializing any other instance-specific values
|
|
273
|
+
* subclasses must call await super.init()
|
|
274
|
+
*
|
|
275
|
+
*/
|
|
276
|
+
|
|
135
277
|
async init(){
|
|
278
|
+
//create the 'name' parameter
|
|
136
279
|
var md = this.addMetadatum("name", "string","Name of sensor" )
|
|
137
280
|
md.isParam=true
|
|
281
|
+
//create the 'RSSI' parameter
|
|
138
282
|
this.currentProperties = await this.constructor.getDeviceProps(this.device)
|
|
139
283
|
this.addMetadatum("RSSI","db","Signal strength in db")
|
|
140
284
|
this.getMetadatum("RSSI").default=`sensors.${this.getMacAddress().replaceAll(':', '')}.rssi`
|
|
141
285
|
this.getMetadatum("RSSI").read=()=>{return this.getRSSI()}
|
|
142
286
|
this.getMetadatum("RSSI").read.bind(this)
|
|
287
|
+
//create GATT params (iff sensor is GATT-ish)
|
|
143
288
|
if (this.hasGATT()) {
|
|
144
289
|
md = this.addMetadatum("useGATT", "boolean", "Use GATT connection")
|
|
145
290
|
md.type="boolean"
|
|
@@ -152,8 +297,14 @@ class BTSensor extends EventEmitter {
|
|
|
152
297
|
md.isGATT=true
|
|
153
298
|
}
|
|
154
299
|
}
|
|
155
|
-
|
|
156
|
-
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Add a metadatum instance to the sensor instance
|
|
303
|
+
*
|
|
304
|
+
* @param {String} tag
|
|
305
|
+
* @param {...any} args
|
|
306
|
+
* @returns {this.Metadatum} the new metadatum instance
|
|
307
|
+
*/
|
|
157
308
|
|
|
158
309
|
addMetadatum(tag, ...args){
|
|
159
310
|
var metadatum = new this.Metadatum(tag, ...args)
|
|
@@ -161,11 +312,87 @@ class BTSensor extends EventEmitter {
|
|
|
161
312
|
return metadatum
|
|
162
313
|
}
|
|
163
314
|
|
|
315
|
+
//GATT Initialization functions
|
|
316
|
+
/**
|
|
317
|
+
* Subclasses providing GATT support should override this method
|
|
318
|
+
*
|
|
319
|
+
* initGATTFunction is where subclasses are expected to connect to their devices and
|
|
320
|
+
* make any GATTServer and GATTCharacteristic connections necessary
|
|
321
|
+
*
|
|
322
|
+
* see: VictronBatteryMonitor for an example
|
|
323
|
+
*/
|
|
324
|
+
|
|
325
|
+
initGATTConnection(){
|
|
326
|
+
throw new Error("::initGATTConnection() should be implemented by the BTSensor subclass")
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Subclasses providing GATT support should override this method
|
|
330
|
+
*
|
|
331
|
+
* initGATTNotifications() is where subclasses are expected setup listeners to their
|
|
332
|
+
* GATTCharacteristics and emit values when received
|
|
333
|
+
*
|
|
334
|
+
* see: VictronBatteryMonitor for an example
|
|
335
|
+
*/
|
|
336
|
+
initGATTNotifications(){
|
|
337
|
+
throw new Error("::initGATTNotifications() should be implemented by the BTSensor subclass")
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
*
|
|
342
|
+
* Subclasses do NOT need to override this function
|
|
343
|
+
* This function is only called when the property pollFreq is set to > 0
|
|
344
|
+
* The function calls #emitGATT() at the specified interval then disconnects
|
|
345
|
+
* from the device.
|
|
346
|
+
*/
|
|
347
|
+
|
|
348
|
+
initGATTInterval(){
|
|
349
|
+
this.device.disconnect().then(()=>{
|
|
350
|
+
this.initPropertiesChanged()
|
|
351
|
+
this.intervalID = setInterval( () => {
|
|
352
|
+
this.initGATTConnection().then(()=>{
|
|
353
|
+
this.emitGATT()
|
|
354
|
+
this.device.disconnect()
|
|
355
|
+
.then(()=>
|
|
356
|
+
this.initPropertiesChanged()
|
|
357
|
+
)
|
|
358
|
+
.catch((e)=>{
|
|
359
|
+
this.debug(`Error disconnecting from ${this.getName()}: ${e.message}`)
|
|
360
|
+
})
|
|
361
|
+
})
|
|
362
|
+
.catch((error)=>{
|
|
363
|
+
this.debug(error)
|
|
364
|
+
throw new Error(`unable to emit values for device ${this.getName()}:${error}`)
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
, this.pollFreq*1000)
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* ::propertiesChanged() is a callback function invoked when the device's properties change
|
|
373
|
+
*
|
|
374
|
+
* NOTE: The function mucks about with node-ble internal functions to help make sure the
|
|
375
|
+
* DBUS connection stays alive, doesn't tax resources and doesn't spit out spurious errors.
|
|
376
|
+
*/
|
|
377
|
+
initPropertiesChanged(){
|
|
378
|
+
|
|
379
|
+
this.propertiesChanged.bind(this)
|
|
380
|
+
this.device.helper._prepare()
|
|
381
|
+
this.device.helper.on("PropertiesChanged",
|
|
382
|
+
((props)=> {
|
|
383
|
+
this.propertiesChanged(props)
|
|
384
|
+
}))
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
//END instance initialization functions
|
|
388
|
+
|
|
389
|
+
//Metadata functions
|
|
164
390
|
getMetadata(){
|
|
165
391
|
if (this.metadata==undefined)
|
|
166
392
|
this.metadata= new Map(this.constructor.getMetadata())
|
|
167
393
|
return this.metadata
|
|
168
394
|
}
|
|
395
|
+
|
|
169
396
|
getMetadatum(tag){
|
|
170
397
|
return this.getMetadata().get(tag)
|
|
171
398
|
}
|
|
@@ -185,33 +412,14 @@ class BTSensor extends EventEmitter {
|
|
|
185
412
|
[...this.getMetadata().entries()].filter(([key,value]) => (value?.isParam??false) && (value?.isGATT??false))
|
|
186
413
|
)
|
|
187
414
|
}
|
|
188
|
-
|
|
189
|
-
return ""
|
|
190
|
-
}
|
|
191
|
-
getSignalStrength(){
|
|
192
|
-
const rssi = this.getRSSI()
|
|
193
|
-
if (!rssi)
|
|
194
|
-
return NaN
|
|
195
|
-
return signalQualityPercentQuad(rssi)
|
|
196
|
-
}
|
|
415
|
+
//End metadata functions
|
|
197
416
|
|
|
198
|
-
|
|
199
|
-
const ss = this.getSignalStrength()
|
|
200
|
-
var bars = ""
|
|
201
|
-
|
|
202
|
-
if (ss>0)
|
|
203
|
-
bars+= '\u{2582} ' //;"▂ "
|
|
204
|
-
if (ss>=30)
|
|
205
|
-
bars+= "\u{2584} "
|
|
206
|
-
if (ss>=60)
|
|
207
|
-
bars+= "\u{2586} "
|
|
208
|
-
if (ss > 80)
|
|
209
|
-
bars+= "\u{2588}"
|
|
210
|
-
return bars
|
|
417
|
+
//Sensor description functions
|
|
211
418
|
|
|
419
|
+
getMacAddress(){
|
|
420
|
+
return this.currentProperties.Address
|
|
212
421
|
}
|
|
213
422
|
|
|
214
|
-
|
|
215
423
|
getDescription(){
|
|
216
424
|
return `${this.getName()} from ${this.getManufacturer()}`
|
|
217
425
|
}
|
|
@@ -222,50 +430,14 @@ class BTSensor extends EventEmitter {
|
|
|
222
430
|
getNameAndAddress(){
|
|
223
431
|
return `${this.getName()} at ${this.getMacAddress()}`
|
|
224
432
|
}
|
|
225
|
-
|
|
433
|
+
|
|
434
|
+
getDisplayName(){
|
|
226
435
|
return `${ this.getName()} (${ this.getMacAddress()} RSSI: ${this.getRSSI()} db / ${this.getSignalStrength().toFixed()}%) ${ this.getBars()}`
|
|
227
436
|
}
|
|
437
|
+
//END sensor description functions
|
|
228
438
|
|
|
229
|
-
getMacAddress(){
|
|
230
|
-
return this.currentProperties.Address
|
|
231
|
-
}
|
|
232
|
-
getRSSI(){
|
|
233
|
-
return this.currentProperties?.RSSI??NaN
|
|
234
|
-
}
|
|
235
|
-
hasGATT(){
|
|
236
|
-
return false
|
|
237
|
-
}
|
|
238
|
-
usingGATT(){
|
|
239
|
-
return this.useGATT
|
|
240
|
-
}
|
|
241
|
-
valueIfVariant(obj){
|
|
242
|
-
if (obj.constructor && obj.constructor.name=='Variant')
|
|
243
|
-
return obj.value
|
|
244
|
-
else
|
|
245
|
-
return obj
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
emitData(tag, buffer, ...args){
|
|
249
|
-
this.emit(tag, this.getMetadatum(tag).read(buffer, ...args))
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* callback function on device properties changing
|
|
254
|
-
*/
|
|
255
|
-
propertiesChanged(props){
|
|
256
|
-
|
|
257
|
-
if (props.RSSI) {
|
|
258
|
-
this.currentProperties.RSSI=this.valueIfVariant(props.RSSI)
|
|
259
|
-
this.emit("RSSI", this.currentProperties.RSSI)
|
|
260
|
-
}
|
|
261
|
-
if (props.ServiceData)
|
|
262
|
-
this.currentProperties.ServiceData=this.valueIfVariant(props.ServiceData)
|
|
263
|
-
|
|
264
|
-
if (props.ManufacturerData)
|
|
265
|
-
this.currentProperties.ManufacturerData=this.valueIfVariant(props.ManufacturerData)
|
|
266
|
-
|
|
267
|
-
}
|
|
268
439
|
|
|
440
|
+
//Device property functions
|
|
269
441
|
getServiceData(key){
|
|
270
442
|
if (this.currentProperties.ServiceData)
|
|
271
443
|
return this.valueIfVariant (this.currentProperties.ServiceData[key])
|
|
@@ -298,47 +470,109 @@ class BTSensor extends EventEmitter {
|
|
|
298
470
|
else
|
|
299
471
|
return null
|
|
300
472
|
}
|
|
473
|
+
//END Device property functions
|
|
474
|
+
|
|
475
|
+
//Sensor RSSI state functions
|
|
301
476
|
|
|
302
|
-
|
|
303
|
-
this.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
477
|
+
getRSSI(){
|
|
478
|
+
return this.currentProperties?.RSSI??NaN
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
getSignalStrength(){
|
|
482
|
+
const rssi = this.getRSSI()
|
|
483
|
+
if (!rssi)
|
|
484
|
+
return NaN
|
|
485
|
+
return signalQualityPercentQuad(rssi)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
getBars(){
|
|
489
|
+
const ss = this.getSignalStrength()
|
|
490
|
+
var bars = ""
|
|
491
|
+
|
|
492
|
+
if (ss>0)
|
|
493
|
+
bars+= '\u{2582} ' //;"▂ "
|
|
494
|
+
if (ss>=30)
|
|
495
|
+
bars+= "\u{2584} "
|
|
496
|
+
if (ss>=60)
|
|
497
|
+
bars+= "\u{2586} "
|
|
498
|
+
if (ss > 80)
|
|
499
|
+
bars+= "\u{2588}"
|
|
500
|
+
return bars
|
|
501
|
+
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
//End Sensor RSSI state functions
|
|
505
|
+
|
|
506
|
+
//Sensor GATT state functions
|
|
507
|
+
|
|
508
|
+
hasGATT(){
|
|
509
|
+
return false
|
|
510
|
+
}
|
|
511
|
+
usingGATT(){
|
|
512
|
+
return this.useGATT
|
|
513
|
+
}
|
|
514
|
+
getGATTDescription() {
|
|
515
|
+
return ""
|
|
516
|
+
}
|
|
517
|
+
//END Sensor GATT state functions
|
|
518
|
+
|
|
519
|
+
//Sensor listen-to-changes functions
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* callback function on a device's properties changing
|
|
523
|
+
* BTSensor subclasses should override this function but call super.propertiesChanged(props) first
|
|
524
|
+
* This function stores the latest broadcast RSSI ServiceData and ManufacturerData in the
|
|
525
|
+
* currentproperties instance variable eliminating the need to make async calls to DBUS for same.
|
|
526
|
+
*
|
|
527
|
+
* @param {*} props which contains ManufacturerData and ServiceData (where the sensor's data resides)
|
|
528
|
+
* set up by BTSensor::initPropertiesChanged()
|
|
529
|
+
*/
|
|
530
|
+
propertiesChanged(props){
|
|
531
|
+
|
|
532
|
+
if (props.RSSI) {
|
|
533
|
+
this.currentProperties.RSSI=this.valueIfVariant(props.RSSI)
|
|
534
|
+
this.emit("RSSI", this.currentProperties.RSSI) //tell any RSSI listeners of the new value
|
|
535
|
+
}
|
|
536
|
+
if (props.ServiceData)
|
|
537
|
+
this.currentProperties.ServiceData=this.valueIfVariant(props.ServiceData)
|
|
538
|
+
|
|
539
|
+
if (props.ManufacturerData)
|
|
540
|
+
this.currentProperties.ManufacturerData=this.valueIfVariant(props.ManufacturerData)
|
|
541
|
+
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
*
|
|
546
|
+
*/
|
|
547
|
+
|
|
548
|
+
emitGATT(){
|
|
549
|
+
throw new Error("Subclass must implement ::emitGATT function")
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
emitData(tag, buffer, ...args){
|
|
553
|
+
this.emit(tag, this.getMetadatum(tag).read(buffer, ...args))
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
emitValuesFrom(buffer){
|
|
557
|
+
this.getMetadata().forEach((datum, tag)=>{
|
|
558
|
+
if (!(datum.isParam||datum.notify) && datum.read)
|
|
559
|
+
this.emit(tag, datum.read(buffer))
|
|
322
560
|
})
|
|
323
561
|
}
|
|
324
|
-
initPropertiesChanged(){
|
|
325
562
|
|
|
326
|
-
this.propertiesChanged.bind(this)
|
|
327
|
-
this.device.helper._prepare()
|
|
328
|
-
this.device.helper.on("PropertiesChanged",
|
|
329
|
-
((props)=> {
|
|
330
|
-
this.propertiesChanged(props)
|
|
331
|
-
}))
|
|
332
|
-
}
|
|
333
563
|
/**
|
|
334
|
-
*
|
|
335
|
-
*
|
|
564
|
+
* Listen to sensor.
|
|
565
|
+
* ::listen() sets up listeners for property changes thru ::propertiesChanged(props)
|
|
566
|
+
* If GATT connections are available and active, function inits the GATT connection and
|
|
567
|
+
* optional GATT connection interval
|
|
336
568
|
*/
|
|
337
|
-
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
listen(){
|
|
338
572
|
this.initPropertiesChanged()
|
|
339
573
|
this.propertiesChanged(this.currentProperties)
|
|
340
574
|
if (this.usingGATT()){
|
|
341
|
-
this.
|
|
575
|
+
this.initGATTConnection().then(async ()=>{
|
|
342
576
|
this.emitGATT()
|
|
343
577
|
if (this.pollFreq){
|
|
344
578
|
this.initGATTInterval()
|
|
@@ -350,25 +584,35 @@ class BTSensor extends EventEmitter {
|
|
|
350
584
|
}
|
|
351
585
|
return this
|
|
352
586
|
}
|
|
587
|
+
|
|
353
588
|
/**
|
|
354
|
-
*
|
|
355
|
-
* Implemented by subclass if additional behavior necessary (like disconnect from device's GattServer etc.)
|
|
589
|
+
* Stop Listening to sensor.
|
|
590
|
+
* Implemented by subclass if additional behavior necessary (like disconnect() from device's GattServer etc.)
|
|
591
|
+
*
|
|
592
|
+
* Called automatically by Plugin::plugin.stop()
|
|
356
593
|
*/
|
|
357
594
|
|
|
358
|
-
|
|
595
|
+
stopListening(){
|
|
359
596
|
this.removeAllListeners()
|
|
360
597
|
this.device.helper.removeListeners()
|
|
361
598
|
if (this.intervalID){
|
|
362
599
|
clearInterval(this.intervalID)
|
|
363
600
|
}
|
|
364
601
|
}
|
|
602
|
+
//END Sensor listen-to-changes functions
|
|
603
|
+
|
|
604
|
+
//Instance utility functions
|
|
365
605
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
606
|
+
NaNif(v1,v2) { return this.constructor.NaNif(v1,v2) }
|
|
607
|
+
|
|
608
|
+
valueIfVariant(obj){
|
|
609
|
+
if (obj.constructor && obj.constructor.name=='Variant')
|
|
610
|
+
return obj.value
|
|
611
|
+
else
|
|
612
|
+
return obj
|
|
613
|
+
|
|
371
614
|
}
|
|
615
|
+
//End instance utility functions
|
|
372
616
|
}
|
|
373
617
|
|
|
374
618
|
module.exports = BTSensor
|