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 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
- * @classdesc Abstract class that all sensor classes should inherit from. Sensor subclasses monitor a
7
- * BT peripheral and emit changes in the sensors's value like "temp" or "humidity"
8
- * @class BTSensor
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
- * https://www.intuitibits.com/2016/03/23/dbm-to-percent-conversion/
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(this.constructor.metadata)
53
- }
54
- static _test(data, key){
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
- static async getPropsProxy(device){
95
-
96
- if (!device._propsProxy) {
97
- const objectProxy = await device.helper.dbus.getProxyObject(device.helper.service, device.helper.object)
98
- device._propsProxy = await objectProxy.getInterface('org.freedesktop.DBus.Properties')
99
- }
100
- return device._propsProxy
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
- static async getDeviceProps(device, propNames=[]){
103
- const _propsProxy = await this.getPropsProxy(device)
104
- const rawProps = await _propsProxy.GetAll(device.helper.iface)
105
- const props = {}
106
- for (const propKey in rawProps) {
107
- if (propNames.length==0 || propNames.indexOf(propKey)>=0)
108
- props[propKey] = rawProps[propKey].value
109
- }
110
- return props
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
- static async getDeviceProp(device, prop){
113
- const _propsProxy = await this.getPropsProxy(device)
114
- try{
115
- const rawProps = await _propsProxy.Get(device.helper.iface,prop)
116
- return rawProps?.value
117
- }
118
- catch(e){
119
- return null //Property $prop (probably) doesn't exist in $device
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
- static NaNif(v1,v2) { return (v1==v2)?NaN:v1 }
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
- NaNif(v1,v2) { return this.constructor.NaNif(v1,v2) }
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
- getGATTDescription() {
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
- getBars(){
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
- getDisplayName(){
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
- initGATTInterval(){
303
- this.device.disconnect().then(()=>{
304
- this.initPropertiesChanged()
305
- this.intervalID = setInterval( () => {
306
- this.initGATT().then(()=>{
307
- this.emitGATT()
308
- this.device.disconnect()
309
- .then(()=>
310
- this.initPropertiesChanged()
311
- )
312
- .catch((e)=>{
313
- this.debug(`Error disconnecting from ${this.getName()}: ${e.message}`)
314
- })
315
- })
316
- .catch((error)=>{
317
- this.debug(error)
318
- throw new Error(`unable to emit values for device ${this.getName()}:${error}`)
319
- })
320
- }
321
- , this.pollFreq*1000)
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
- * Connect to sensor.
335
- * This is where the logic for connecting to sensor, listening for changes in values and emitting those values go
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
- connect(){
569
+
570
+
571
+ listen(){
338
572
  this.initPropertiesChanged()
339
573
  this.propertiesChanged(this.currentProperties)
340
574
  if (this.usingGATT()){
341
- this.initGATT().then(async ()=>{
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
- * Discconnect from sensor.
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
- disconnect(){
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
- emitValuesFrom(buffer){
367
- this.getMetadata().forEach((datum, tag)=>{
368
- if (!(datum.isParam||datum.notify) && datum.read)
369
- this.emit(tag, datum.read(buffer))
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