bt-sensors-plugin-sk 1.1.0-beta.2.2.2 → 1.1.0-beta.2.2.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/README.md +10 -0
- package/index.js +100 -25
- package/package.json +2 -2
- package/sensor_classes/BlackListedDevice.js +25 -23
- package/sensor_classes/IBeacon.js +42 -0
- package/sensor_classes/JBDBMS.js +170 -0
- package/sensor_classes/RenogyBattery.js +4 -4
- package/sensor_classes/RenogyInverter.js +6 -6
- package/sensor_classes/XiaomiMiBeacon.js +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Bluetooth Sensors for [Signal K](http://www.signalk.org)
|
|
2
2
|
|
|
3
|
+
## BETA 2.2.4
|
|
4
|
+
### What's New
|
|
5
|
+
Fix to 2.2.3 scan and connect issues. Added Transport option to Config page. Default is Bluetooth LE (was Auto in 2.2.3).
|
|
6
|
+
|
|
7
|
+
## BETA 2.2.3
|
|
8
|
+
### What's New
|
|
9
|
+
Support for [JBD/Jiabaida/Xiaoxiang Battery management systems](https://jiabaida-bms.com/), IBeacon and clone devices courtesy of [Arjen R](https://github.com/ArjenR).
|
|
10
|
+
|
|
11
|
+
### What's New for Developers
|
|
12
|
+
Runtime loading of external device modules. See (https://github.com/naugehyde/bt-sensors-plugin-sk/discussions/26)
|
|
3
13
|
## BETA 2.2.2
|
|
4
14
|
### What's New
|
|
5
15
|
Support for Lancol Battery Meters, Kilovault HLX+ smart batteries courtesy of [sdlee1963](https://github.com/sdlee1963) and baseline support for BTHome devices as well as support for the ShellySBHT003C enviromental sensor courtesy of [Sebastian Haas](https://github.com/sebastianhaas)
|
package/index.js
CHANGED
|
@@ -4,13 +4,14 @@ const path = require('path')
|
|
|
4
4
|
const packageInfo = require("./package.json")
|
|
5
5
|
|
|
6
6
|
const {createBluetooth} = require('node-ble')
|
|
7
|
+
const { Variant } = require('dbus-next')
|
|
7
8
|
const {bluetooth, destroy} = createBluetooth()
|
|
8
9
|
|
|
9
10
|
const BTSensor = require('./BTSensor.js')
|
|
10
11
|
const BLACKLISTED = require('./sensor_classes/BlackListedDevice.js')
|
|
11
12
|
|
|
12
13
|
class MissingSensor {
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
|
|
15
16
|
constructor(config){
|
|
16
17
|
this.Metadatum = BTSensor.Metadatum
|
|
@@ -53,8 +54,10 @@ class MissingSensor {
|
|
|
53
54
|
stopListening(){}
|
|
54
55
|
listen(){}
|
|
55
56
|
}
|
|
56
|
-
module.exports =
|
|
57
|
-
|
|
57
|
+
module.exports = function (app) {
|
|
58
|
+
var adapterID = 'hci0'
|
|
59
|
+
|
|
60
|
+
|
|
58
61
|
var deviceConfigs
|
|
59
62
|
var starts=0
|
|
60
63
|
var classMap
|
|
@@ -66,6 +69,8 @@ module.exports = function (app) {
|
|
|
66
69
|
plugin.name = 'BT Sensors plugin';
|
|
67
70
|
plugin.description = 'Plugin to communicate with and update paths to BLE Sensors in Signalk';
|
|
68
71
|
|
|
72
|
+
|
|
73
|
+
|
|
69
74
|
//Try and load utilities-sk NOTE: should be installed from App Store--
|
|
70
75
|
//But there's a fail safe because I'm a reasonable man.
|
|
71
76
|
|
|
@@ -162,10 +167,19 @@ module.exports = function (app) {
|
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
function loadClassMap() {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
import(app.config.appPath+"/lib/modules.js").then( (modulesjs)=>{
|
|
171
|
+
const _classMap = utilities_sk.loadClasses(path.join(__dirname, 'sensor_classes'))
|
|
172
|
+
classMap = new Map([..._classMap].filter(([k, v]) => !k.startsWith("_") ))
|
|
173
|
+
const { default:defaultExport} = modulesjs
|
|
174
|
+
const modules = defaultExport.modulesWithKeyword(app.config, "signalk-bt-sensor-class")
|
|
175
|
+
modules.forEach((module)=>{
|
|
176
|
+
module.metadata.classFiles.forEach((classFile)=>{
|
|
177
|
+
const cls = require(module.location+module.module+"/"+classFile);
|
|
178
|
+
classMap.set(cls.name, cls);
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
classMap.get('UNKNOWN').classMap=new Map([...classMap].sort().filter(([k, v]) => !v.isSystem )) // share the classMap with Unknown for configuration purposes
|
|
182
|
+
})
|
|
169
183
|
}
|
|
170
184
|
|
|
171
185
|
app.debug(`Loading plugin ${packageInfo.version}`)
|
|
@@ -180,12 +194,15 @@ module.exports = function (app) {
|
|
|
180
194
|
properties: {
|
|
181
195
|
adapter: {title: "Bluetooth adapter",
|
|
182
196
|
type: "string", default: "hci0"},
|
|
197
|
+
transport: {title: "Transport ",
|
|
198
|
+
type: "string", enum: ["auto","le","bredr"], default: "le", enumNames:["Auto", "LE-Bluetooth Low Energy", "BR/EDR Bluetooth basic rate/enhanced data rate"]},
|
|
183
199
|
|
|
184
200
|
discoveryTimeout: {title: "Default device discovery timeout (in seconds)",
|
|
185
201
|
type: "integer", default: 30,
|
|
186
202
|
minimum: 10,
|
|
187
203
|
maximum: 3600
|
|
188
204
|
},
|
|
205
|
+
|
|
189
206
|
discoveryInterval: {title: "Scan for new devices interval (in seconds-- 0 for no new device scanning)",
|
|
190
207
|
type: "integer",
|
|
191
208
|
default: 10,
|
|
@@ -335,23 +352,41 @@ module.exports = function (app) {
|
|
|
335
352
|
})})
|
|
336
353
|
}
|
|
337
354
|
|
|
338
|
-
async function startScanner() {
|
|
355
|
+
async function startScanner(transport) {
|
|
339
356
|
|
|
340
357
|
app.debug("Starting scan...");
|
|
341
|
-
|
|
342
|
-
|
|
358
|
+
//Use adapter.helper directly to get around Adapter::startDiscovery()
|
|
359
|
+
//filter options which can cause issues with Device::Connect()
|
|
360
|
+
//turning off Discovery
|
|
361
|
+
//try {await adapter.startDiscovery()}
|
|
362
|
+
try{
|
|
363
|
+
if (transport) {
|
|
364
|
+
app.debug(`Setting Bluetooth transport option to ${transport}`)
|
|
365
|
+
await adapter.helper.callMethod('SetDiscoveryFilter', {
|
|
366
|
+
Transport: new Variant('s', transport)
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
await adapter.helper.callMethod('StartDiscovery')
|
|
370
|
+
}
|
|
371
|
+
catch (error){
|
|
372
|
+
app.debug(error)
|
|
373
|
+
}
|
|
374
|
+
|
|
343
375
|
}
|
|
344
376
|
|
|
345
377
|
const sensorMap=new Map()
|
|
346
|
-
var adapter
|
|
347
378
|
|
|
348
379
|
plugin.started=false
|
|
349
380
|
|
|
350
381
|
loadClassMap()
|
|
351
382
|
var discoveryIntervalID
|
|
352
|
-
|
|
383
|
+
var adapter
|
|
384
|
+
var adapterPower
|
|
353
385
|
plugin.start = async function (options, restartPlugin) {
|
|
354
|
-
|
|
386
|
+
plugin.started=true
|
|
387
|
+
|
|
388
|
+
var adapterID=options.adapter
|
|
389
|
+
|
|
355
390
|
function getDeviceConfig(mac){
|
|
356
391
|
return deviceConfigs.find((p)=>p.mac_address==mac)
|
|
357
392
|
}
|
|
@@ -381,7 +416,8 @@ module.exports = function (app) {
|
|
|
381
416
|
const msg =`Sensor at ${deviceConfig.mac_address} unavailable. Reason: ${error}`
|
|
382
417
|
app.debug(msg)
|
|
383
418
|
app.debug(error)
|
|
384
|
-
|
|
419
|
+
if (deviceConfig.active)
|
|
420
|
+
app.setPluginError(msg)
|
|
385
421
|
deviceConfig.sensor=new MissingSensor(deviceConfig)
|
|
386
422
|
addSensorToList(deviceConfig.sensor) //add sensor to list with known options
|
|
387
423
|
|
|
@@ -420,25 +456,63 @@ module.exports = function (app) {
|
|
|
420
456
|
discoveryIntervalID = setInterval( findDevices, discoveryInterval*1000, discoveryTimeout)
|
|
421
457
|
}
|
|
422
458
|
|
|
423
|
-
|
|
459
|
+
|
|
460
|
+
if (!adapterID || adapterID=="")
|
|
461
|
+
adapterID = "hci0"
|
|
462
|
+
//Check if Adapter has changed since last start()
|
|
463
|
+
if (adapter) {
|
|
464
|
+
const n = await adapter.getName()
|
|
465
|
+
if (n!=adapterID) {
|
|
466
|
+
adapter.helper.removeAllListeners()
|
|
467
|
+
adapter=null
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
//Connect to adapter
|
|
471
|
+
|
|
472
|
+
if (!adapter){
|
|
473
|
+
app.debug(`Connecting to bluetooth adapter ${adapterID}`);
|
|
474
|
+
|
|
475
|
+
adapter = await bluetooth.getAdapter(adapterID)
|
|
476
|
+
|
|
477
|
+
//Set up DBUS listener to monitor Powered status of current adapter
|
|
478
|
+
|
|
479
|
+
await adapter.helper._prepare()
|
|
480
|
+
adapter.helper._propsProxy.on('PropertiesChanged', async (iface,changedProps,invalidated) => {
|
|
481
|
+
if (Object.hasOwn(changedProps,"Powered")){
|
|
482
|
+
if (changedProps.Powered.value==false) {
|
|
483
|
+
if (plugin.started){ //only call stop() if plugin is started
|
|
484
|
+
app.setPluginStatus(`Bluetooth Adapter ${adapterID} turned off. Plugin disabled.`)
|
|
485
|
+
await plugin.stop()
|
|
486
|
+
adapterPower=false
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
if (!adapterPower) { //only call start() once
|
|
490
|
+
adapterPower=true
|
|
491
|
+
await plugin.start(options,restartPlugin)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
if (!await adapter.isPowered()) {
|
|
497
|
+
app.debug(`Bluetooth Adapter ${adapterID} not powered on.`)
|
|
498
|
+
app.setPluginError(`Bluetooth Adapter ${adapterID} not powered on.`)
|
|
499
|
+
adapterPower=false
|
|
500
|
+
await plugin.stop()
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
adapterPower=true
|
|
505
|
+
|
|
424
506
|
plugin.uiSchema.peripherals['ui:disabled']=false
|
|
425
507
|
sensorMap.clear()
|
|
426
508
|
deviceConfigs=options?.peripherals??[]
|
|
427
509
|
|
|
428
|
-
|
|
429
|
-
|
|
430
510
|
if (plugin.stopped) {
|
|
431
511
|
await sleep(5000) //Make sure plugin.stop() completes first
|
|
432
512
|
//plugin.start is called asynchronously for some reason
|
|
433
513
|
//and does not wait for plugin.stop to complete
|
|
434
514
|
plugin.stopped=false
|
|
435
515
|
}
|
|
436
|
-
var adapterID=options.adapter
|
|
437
|
-
|
|
438
|
-
if (!adapterID || adapterID==="")
|
|
439
|
-
adapterID = "hci0"
|
|
440
|
-
|
|
441
|
-
app.debug(`Connecting to bluetooth adapter ${adapterID}`);
|
|
442
516
|
|
|
443
517
|
const activeAdapters = await bluetooth.activeAdapters()
|
|
444
518
|
plugin.schema.properties.adapter.enum=[]
|
|
@@ -450,8 +524,8 @@ module.exports = function (app) {
|
|
|
450
524
|
|
|
451
525
|
plugin.uiSchema.adapter={'ui:disabled': (activeAdapters.length==1)}
|
|
452
526
|
|
|
453
|
-
|
|
454
|
-
await startScanner()
|
|
527
|
+
|
|
528
|
+
await startScanner(options.transport)
|
|
455
529
|
if (starts>0){
|
|
456
530
|
app.debug(`Plugin ${packageInfo.version} restarting...`);
|
|
457
531
|
if (plugin.schema.properties.peripherals.items.dependencies)
|
|
@@ -479,6 +553,7 @@ module.exports = function (app) {
|
|
|
479
553
|
plugin.stop = async function () {
|
|
480
554
|
app.debug("Stopping plugin")
|
|
481
555
|
plugin.stopped=true
|
|
556
|
+
plugin.started=false
|
|
482
557
|
plugin.uiSchema.peripherals['ui:disabled']=true
|
|
483
558
|
if ((sensorMap)){
|
|
484
559
|
plugin.schema.properties.peripherals.items.properties.mac_address.enum=[]
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bt-sensors-plugin-sk",
|
|
3
|
-
"version": "1.1.0-beta.2.2.
|
|
3
|
+
"version": "1.1.0-beta.2.2.4",
|
|
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": {
|
|
7
7
|
"dbus-next": "^0.10.2",
|
|
8
|
-
"node-ble": "^1.
|
|
8
|
+
"node-ble": "^1.13.0",
|
|
9
9
|
"int24":"^0.0.1",
|
|
10
10
|
"kaitai-struct": "^0.10.0"
|
|
11
11
|
},
|
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
const BTSensor = require("../BTSensor");
|
|
2
|
-
class BLACKLISTED extends BTSensor{
|
|
3
|
-
static isSystem = true
|
|
4
|
-
static async identify(device){
|
|
5
|
-
const md = await this.getDeviceProp(device,
|
|
6
|
-
if (md){
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
class BLACKLISTED extends BTSensor {
|
|
3
|
+
static isSystem = true;
|
|
4
|
+
static async identify(device) {
|
|
5
|
+
const md = await this.getDeviceProp(device, "ManufacturerData");
|
|
6
|
+
if (md && Object.hasOwn(md, 0x004c)){
|
|
7
|
+
if (md[0x004c].value.slice(0,2).join() != [0x02, 0x15].join()){ // iBeacons are exempt
|
|
8
|
+
return this;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
10
11
|
}
|
|
11
|
-
return null
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
|
|
14
|
+
async init() {
|
|
15
|
+
await super.init();
|
|
16
|
+
this.currentProperties.Name = `Unsupported device from ${this.getManufacturer()}`;
|
|
16
17
|
}
|
|
17
18
|
reasonForBlacklisting() {
|
|
18
|
-
switch (
|
|
19
|
-
case
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
19
|
+
switch (this.getManufacturerID()) {
|
|
20
|
+
case 0x004c:
|
|
21
|
+
return "Randomized MAC address (Apple)";
|
|
22
|
+
case 0x02e1:
|
|
23
|
+
return "Device is using VE.Smart"; //NOTE: Victron/VictronSensor class
|
|
24
|
+
//determines if a device is using VE.Smart
|
|
25
|
+
//in identify(). If so, identify() returns
|
|
26
|
+
//BlackListedDevice
|
|
28
27
|
|
|
28
|
+
default:
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
|
-
module.exports=BLACKLISTED
|
|
33
|
+
module.exports = BLACKLISTED;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const BTSensor = require("../BTSensor");
|
|
2
|
+
|
|
3
|
+
class IBeacon extends BTSensor {
|
|
4
|
+
static isSystem = true;
|
|
5
|
+
|
|
6
|
+
static async identify(device) {
|
|
7
|
+
const md = await this.getDeviceProp(device,'ManufacturerData');
|
|
8
|
+
if (md && Object.hasOwn(md, 0x004c)) {
|
|
9
|
+
if (md[0x004c].value.slice(0,2).join() == [0x02, 0x15].join()) {
|
|
10
|
+
return this
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async init() {
|
|
17
|
+
await super.init();
|
|
18
|
+
this.initMetadata();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
initMetadata(){
|
|
22
|
+
this.addMetadatum('battery','ratio', 'Battery charge state', (buffer)=>{return buffer[6]})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
propertiesChanged(props){
|
|
26
|
+
super.propertiesChanged(props);
|
|
27
|
+
const buff = this.getServiceData("0000356e-0000-1000-8000-00805f9b34fb");
|
|
28
|
+
if (!buff)
|
|
29
|
+
throw new Error("Unable to get service data for " + this.getDisplayName());
|
|
30
|
+
this.emitData("battery", buff);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getManufacturer(){
|
|
34
|
+
return "Apple Inc. or clone";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getDescription(){
|
|
38
|
+
return `${this.getName()} iBeacon from Apple Inc. or a clone`
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = IBeacon;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const BTSensor = require("../BTSensor");
|
|
2
|
+
function sleep(ms) {
|
|
3
|
+
return new Promise((resolve) => {
|
|
4
|
+
setTimeout(resolve, ms);
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
class JBDBMS extends BTSensor {
|
|
8
|
+
|
|
9
|
+
static TX_RX_SERVICE = "0000ff00-0000-1000-8000-00805f9b34fb"
|
|
10
|
+
static NOTIFY_CHAR_UUID = "0000ff01-0000-1000-8000-00805f9b34fb"
|
|
11
|
+
static WRITE_CHAR_UUID = "0000ff02-0000-1000-8000-00805f9b34fb"
|
|
12
|
+
|
|
13
|
+
constructor(device,config,gattConfig){
|
|
14
|
+
super(device,config,gattConfig)
|
|
15
|
+
this.emitterFunctions=
|
|
16
|
+
[this.getAndEmitBatteryInfo.bind(this),
|
|
17
|
+
this.getAndEmitCellVoltages.bind(this)]
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
static identify(device){
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
jbdCommand(command) {
|
|
24
|
+
return [0xDD, 0xA5, command, 0x00, 0xFF, 0xFF - (command - 1), 0x77]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async sendReadFunctionRequest( command ){
|
|
28
|
+
return await this.txChar.writeValueWithResponse(Buffer.from(this.jbdCommand(command)))
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
async init(){
|
|
32
|
+
await super.init()
|
|
33
|
+
this.addMetadatum('voltage', 'V', 'Total battery voltage',
|
|
34
|
+
(buffer)=>{return buffer.readUInt16BE(4) / 100})
|
|
35
|
+
this.addMetadatum('current', 'A', 'Current flow',
|
|
36
|
+
(buffer)=>{return buffer.readInt16BE(6) / 100} )
|
|
37
|
+
this.addMetadatum('remainingCapacity', 'Ah', 'Remaining battery capacity',
|
|
38
|
+
(buffer)=>{return buffer.readUInt16BE(8) / 100} )
|
|
39
|
+
this.addMetadatum('capacity', 'Ah', 'Battery capacity',
|
|
40
|
+
(buffer)=>{return buffer.readUInt16BE(10) / 100} )
|
|
41
|
+
this.addMetadatum('cycles', '', 'cycles',
|
|
42
|
+
(buffer)=>{return buffer.readUInt16BE(12)} )
|
|
43
|
+
this.addMetadatum('protectionStatus', '', 'Protection Status',
|
|
44
|
+
(buffer)=>{return buffer.readUInt16BE(20)} )
|
|
45
|
+
|
|
46
|
+
this.addMetadatum('SOC', 'ratio', 'State of Charge',
|
|
47
|
+
(buffer)=>{return buffer.readUInt8(23)/100} )
|
|
48
|
+
|
|
49
|
+
this.addMetadatum('FET', '', 'FET Control',
|
|
50
|
+
(buffer)=>{return buffer.readUInt8(24)} )
|
|
51
|
+
|
|
52
|
+
await this.device.connect()
|
|
53
|
+
await this.initCharacteristics()
|
|
54
|
+
const cellsAndTemps = await this.getNumberOfCellsAndTemps()
|
|
55
|
+
this.numberOfCells=cellsAndTemps.cells
|
|
56
|
+
this.numberOfTemps=cellsAndTemps.temps
|
|
57
|
+
|
|
58
|
+
for (let i=0; i<this.numberOfTemps; i++){
|
|
59
|
+
this.addMetadatum(`temp${i}`, 'K', `Temperature${i+1} reading`,
|
|
60
|
+
(buffer)=>{
|
|
61
|
+
return buffer.readUInt16BE(27+(i*2))/10
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (let i=0; i<this.numberOfCells; i++){
|
|
66
|
+
this.addMetadatum(`cell${i}Voltage`, 'V', `Cell ${i+1} voltage`,
|
|
67
|
+
(buffer)=>{return buffer.readUInt16BE((4+(i*2)))/1000} )
|
|
68
|
+
this.addMetadatum(`cell${i}Balance`, 'V', `Cell ${i+1} balance` )
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
hasGATT(){
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
initCharacteristics(){
|
|
77
|
+
return new Promise((resolve,reject )=>{ this.device.connect().then(async ()=>{
|
|
78
|
+
const gattServer = await this.device.gatt()
|
|
79
|
+
const txRxService= await gattServer.getPrimaryService(this.constructor.TX_RX_SERVICE)
|
|
80
|
+
this.rxChar = await txRxService.getCharacteristic(this.constructor.NOTIFY_CHAR_UUID)
|
|
81
|
+
this.txChar = await txRxService.getCharacteristic(this.constructor.WRITE_CHAR_UUID)
|
|
82
|
+
await this.rxChar.startNotifications()
|
|
83
|
+
resolve(this)
|
|
84
|
+
}) .catch((e)=>{ reject(e.message) }) })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async initGATTNotifications(){
|
|
88
|
+
this.intervalID = setInterval(()=>{
|
|
89
|
+
this.emitGATT()
|
|
90
|
+
}, 1000*(this?.pollFreq??60) )
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
emitGATT(){
|
|
94
|
+
this.getAndEmitBatteryInfo()
|
|
95
|
+
setTimeout(()=>{this.getAndEmitCellVoltages()}, 5000)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getNumberOfCellsAndTemps(){
|
|
99
|
+
var b = await this.getBuffer(0x3)
|
|
100
|
+
return {cells:b[25], temps:b[26]}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getBuffer(command){
|
|
104
|
+
|
|
105
|
+
return new Promise( async ( resolve, reject )=>{
|
|
106
|
+
const r = await this.sendReadFunctionRequest(command)
|
|
107
|
+
const result = Buffer.alloc(256)
|
|
108
|
+
var offset = 0
|
|
109
|
+
|
|
110
|
+
const timer = setTimeout(() => {
|
|
111
|
+
clearTimeout(timer)
|
|
112
|
+
reject(new Error(`Response timed out from JBDBMS device ${this.getName()}. `));
|
|
113
|
+
}, 30000);
|
|
114
|
+
|
|
115
|
+
const valChanged = async (buffer) => {
|
|
116
|
+
this.debug(buffer)
|
|
117
|
+
buffer.copy(result,offset)
|
|
118
|
+
if (buffer[buffer.length-1]==0x77){
|
|
119
|
+
this.rxChar.removeAllListeners()
|
|
120
|
+
clearTimeout(timer)
|
|
121
|
+
resolve(result)
|
|
122
|
+
}
|
|
123
|
+
offset+=buffer.length
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
this.rxChar.on('valuechanged', valChanged )
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async initGATTConnection() {
|
|
132
|
+
return this
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getAndEmitBatteryInfo(){
|
|
136
|
+
this.getBuffer(0x03).then((buffer)=>{
|
|
137
|
+
(["current", "voltage", "remainingCapacity", "capacity","cycles", "protectionStatus", "SOC","FET",]).forEach((tag) =>
|
|
138
|
+
this.emitData( tag, buffer )
|
|
139
|
+
)
|
|
140
|
+
for (let i = 0; i<this.numberOfTemps; i++){
|
|
141
|
+
this.emitData(`temp${i}`,buffer)
|
|
142
|
+
}
|
|
143
|
+
const balances = buffer.readUInt32BE(16)
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i<this.numberOfCells; i++){
|
|
146
|
+
this.emit(`cell${i}Balance`,(1<<i & balances)?1:0)
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getAndEmitCellVoltages(){
|
|
152
|
+
this.getBuffer(0x4).then((buffer)=>{
|
|
153
|
+
for (let i=0; i<this.numberOfCells; i++){
|
|
154
|
+
this.emitData(`cell${i}Voltage`,buffer)
|
|
155
|
+
}})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
initGATTInterval(){
|
|
159
|
+
this.initGATTNotifications()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async stopListening(){
|
|
163
|
+
super.stopListening()
|
|
164
|
+
this.rxChar.stopNotifications()
|
|
165
|
+
await this.device.disconnect()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = JBDBMS;
|
|
@@ -77,8 +77,8 @@ class RenogyBattery extends RenogySensor {
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
this.readChar.once('valuechanged', (buffer) => {
|
|
80
|
-
["current", "voltage", "remainingCharge", "capacity"].forEach(tag)
|
|
81
|
-
this.emitData( tag, buffer )
|
|
80
|
+
["current", "voltage", "remainingCharge", "capacity"].forEach((tag)=>
|
|
81
|
+
this.emitData( tag, buffer ))
|
|
82
82
|
|
|
83
83
|
resolve(this)
|
|
84
84
|
})
|
|
@@ -87,7 +87,7 @@ class RenogyBattery extends RenogySensor {
|
|
|
87
87
|
|
|
88
88
|
getAndEmitCellVoltages(){
|
|
89
89
|
return new Promise( async ( resolve, reject )=>{
|
|
90
|
-
this.sendReadFunctionRequest(0x1388,0x11)
|
|
90
|
+
await this.sendReadFunctionRequest(0x1388,0x11)
|
|
91
91
|
|
|
92
92
|
this.readChar.once('valuechanged', (buffer) => {
|
|
93
93
|
for (let i = 0; i++ ; i < this.numberOfCells)
|
|
@@ -99,7 +99,7 @@ class RenogyBattery extends RenogySensor {
|
|
|
99
99
|
|
|
100
100
|
getAndEmitCellTemperatures(){
|
|
101
101
|
return new Promise( async ( resolve, reject )=>{
|
|
102
|
-
this.sendReadFunctionRequest(0x1399,0x22)
|
|
102
|
+
await this.sendReadFunctionRequest(0x1399,0x22)
|
|
103
103
|
|
|
104
104
|
this.readChar.once('valuechanged', buffer => {
|
|
105
105
|
for (let i = 0; i++ ; i < this.numberOfCells)
|
|
@@ -63,8 +63,8 @@ class RenogyInverter extends RenogySensor {
|
|
|
63
63
|
await this.sendReadFunctionRequest(0xfa0, 0x8)
|
|
64
64
|
|
|
65
65
|
this.readChar.once('valuechanged', (buffer) => {
|
|
66
|
-
["ueiVoltage","ueiCurrent", "voltage", "loadCurrent", "frequency","temperature"].forEach(tag)
|
|
67
|
-
this.emitData( tag, buffer )
|
|
66
|
+
["ueiVoltage","ueiCurrent", "voltage", "loadCurrent", "frequency","temperature"].forEach((tag)=>
|
|
67
|
+
this.emitData( tag, buffer ))
|
|
68
68
|
|
|
69
69
|
resolve(this)
|
|
70
70
|
})
|
|
@@ -77,8 +77,8 @@ class RenogyInverter extends RenogySensor {
|
|
|
77
77
|
await this.sendReadFunctionRequest(0x10e9, 0x5)
|
|
78
78
|
|
|
79
79
|
this.readChar.once('valuechanged', (buffer) => {
|
|
80
|
-
["solarVoltage","solarCurrent", "solarPower", "solarChargingStatus", "solarChargingPower"].forEach(tag)
|
|
81
|
-
this.emitData( tag, buffer )
|
|
80
|
+
["solarVoltage","solarCurrent", "solarPower", "solarChargingStatus", "solarChargingPower"].forEach((tag)=>
|
|
81
|
+
this.emitData( tag, buffer ))
|
|
82
82
|
|
|
83
83
|
resolve(this)
|
|
84
84
|
})
|
|
@@ -91,8 +91,8 @@ class RenogyInverter extends RenogySensor {
|
|
|
91
91
|
await this.sendReadFunctionRequest(0x113a, 0x2)
|
|
92
92
|
|
|
93
93
|
this.readChar.once('valuechanged', (buffer) => {
|
|
94
|
-
["loadPower", "chargingCurrent"].forEach(tag)
|
|
95
|
-
this.emitData( tag, buffer )
|
|
94
|
+
["loadPower", "chargingCurrent"].forEach((tag)=>
|
|
95
|
+
this.emitData( tag, buffer ))
|
|
96
96
|
|
|
97
97
|
resolve(this)
|
|
98
98
|
})
|