bt-sensors-plugin-sk 1.1.0-beta.2.1.4.5 → 1.1.0-beta.2.2.0.1
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 +15 -6
- package/README.md +11 -0
- package/index.js +29 -10
- package/package.json +2 -2
- package/sensor_classes/Aranet/AranetSensor.js +24 -0
- package/sensor_classes/Aranet2.js +47 -0
- package/sensor_classes/Aranet4.js +53 -0
- package/sensor_classes/BlackListedDevice.js +1 -0
- package/sensor_classes/MopekaTankSensor.js +8 -5
- package/sensor_classes/Renogy/CRC.js +51 -0
- package/sensor_classes/Renogy/RenogyConstants.js +32 -0
- package/sensor_classes/Renogy/RenogySensor.js +122 -0
- package/sensor_classes/RenogyBattery.js +114 -0
- package/sensor_classes/RenogyInverter.js +113 -0
- package/sensor_classes/RenogyRoverClient.js +133 -0
- package/sensor_classes/UNKNOWN.js +9 -0
- package/sensor_classes/Victron/VictronSensor.js +2 -2
- package/sensor_classes/XiaomiMiBeacon.js +3 -0
package/BTSensor.js
CHANGED
|
@@ -374,16 +374,21 @@ class BTSensor extends EventEmitter {
|
|
|
374
374
|
* NOTE: The function mucks about with node-ble internal functions to help make sure the
|
|
375
375
|
* DBUS connection stays alive, doesn't tax resources and doesn't spit out spurious errors.
|
|
376
376
|
*/
|
|
377
|
-
|
|
377
|
+
initPropertiesChanged(){
|
|
378
378
|
|
|
379
379
|
this.propertiesChanged.bind(this)
|
|
380
380
|
this.device.helper._prepare()
|
|
381
381
|
this.device.helper.on("PropertiesChanged",
|
|
382
382
|
((props)=> {
|
|
383
|
-
|
|
383
|
+
try{
|
|
384
|
+
this.propertiesChanged(props)
|
|
385
|
+
}
|
|
386
|
+
catch(error){
|
|
387
|
+
this.debug(`Error occured on ${this.getNameAndAddress()}: ${error?.message??error}`)
|
|
388
|
+
this.debug(error)
|
|
389
|
+
}
|
|
384
390
|
}))
|
|
385
391
|
}
|
|
386
|
-
|
|
387
392
|
//END instance initialization functions
|
|
388
393
|
|
|
389
394
|
//Metadata functions
|
|
@@ -569,8 +574,12 @@ class BTSensor extends EventEmitter {
|
|
|
569
574
|
|
|
570
575
|
|
|
571
576
|
listen(){
|
|
572
|
-
|
|
573
|
-
|
|
577
|
+
try{
|
|
578
|
+
this.initPropertiesChanged()
|
|
579
|
+
this.propertiesChanged(this.currentProperties)
|
|
580
|
+
} catch(e){
|
|
581
|
+
this.debug(e)
|
|
582
|
+
}
|
|
574
583
|
if (this.usingGATT()){
|
|
575
584
|
this.initGATTConnection().then(async ()=>{
|
|
576
585
|
this.emitGATT()
|
|
@@ -606,7 +615,7 @@ class BTSensor extends EventEmitter {
|
|
|
606
615
|
NaNif(v1,v2) { return this.constructor.NaNif(v1,v2) }
|
|
607
616
|
|
|
608
617
|
valueIfVariant(obj){
|
|
609
|
-
if (obj
|
|
618
|
+
if (obj?.constructor && obj.constructor.name=='Variant')
|
|
610
619
|
return obj.value
|
|
611
620
|
else
|
|
612
621
|
return obj
|
package/README.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Bluetooth Sensors for [Signal K](http://www.signalk.org)
|
|
2
2
|
|
|
3
|
+
## BETA 2.2.0
|
|
4
|
+
|
|
5
|
+
### What's New
|
|
6
|
+
Support for Aranet4 environment sensor and Renogy Rover/Wanderer Controllers. Untested support for Renogy Battery and Inverter clients. If you have a Renogy Battery or Inverter with bluetooth support, please give it a try and let me know how it goes.
|
|
7
|
+
|
|
8
|
+
### RENOGY NOTES
|
|
9
|
+
|
|
10
|
+
The class of Renogy Devices cannot be reliably identified from their Bluetooth advertisements. <br>
|
|
11
|
+
|
|
12
|
+
On the plugin config page, You will need to select the device from the Device dropdown, then select the appropriate class from the Class dropdown. After that, you will need to hit the Submit button. On restart of the plugin, the plugin should recognize the device. If you've selected the appropriate class (RenogyRoverClient for example), you should see configs for paths. <br>
|
|
13
|
+
|
|
3
14
|
|
|
4
15
|
## WHAT IT IS
|
|
5
16
|
|
package/index.js
CHANGED
|
@@ -102,6 +102,8 @@ module.exports = function (app) {
|
|
|
102
102
|
const sensor = new c(device,config?.params, config?.gattParams)
|
|
103
103
|
sensor.debug=app.debug
|
|
104
104
|
await sensor.init()
|
|
105
|
+
app.debug(`instantiated ${await BTSensor.getDeviceProp(device,"Address")}`)
|
|
106
|
+
|
|
105
107
|
return sensor
|
|
106
108
|
}
|
|
107
109
|
}} catch(error){
|
|
@@ -111,8 +113,17 @@ module.exports = function (app) {
|
|
|
111
113
|
app.setPluginError(msg)
|
|
112
114
|
}
|
|
113
115
|
//if we're here ain't got no class for the device
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
var sensor
|
|
117
|
+
if (config.params?.sensorClass){
|
|
118
|
+
const c = classMap.get(config.params.sensorClass)
|
|
119
|
+
c.debug=app.debug
|
|
120
|
+
sensor = new c(device,config?.params, config?.gattParams)
|
|
121
|
+
sensor.debug=app.debug
|
|
122
|
+
await sensor.init()
|
|
123
|
+
} else{
|
|
124
|
+
sensor = new (classMap.get('UNKNOWN'))(device)
|
|
125
|
+
await sensor.init()
|
|
126
|
+
}
|
|
116
127
|
return sensor
|
|
117
128
|
}
|
|
118
129
|
|
|
@@ -151,11 +162,14 @@ module.exports = function (app) {
|
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
function loadClassMap() {
|
|
154
|
-
|
|
155
|
-
|
|
165
|
+
const _classMap = utilities_sk.loadClasses(path.join(__dirname, 'sensor_classes'))
|
|
166
|
+
classMap = new Map([..._classMap].filter(([k, v]) => !k.startsWith("_") ))
|
|
167
|
+
classMap.get('UNKNOWN').classMap=new Map([...classMap].filter(([k, v]) => !v.isSystem )) // share the classMap with Unknown for configuration purposes
|
|
156
168
|
|
|
157
|
-
|
|
169
|
+
}
|
|
158
170
|
|
|
171
|
+
app.debug(`Loading plugin ${packageInfo.version}`)
|
|
172
|
+
|
|
159
173
|
plugin.schema = {
|
|
160
174
|
type: "object",
|
|
161
175
|
description: "NOTE: \n 1) Plugin must be enabled to configure your sensors. \n"+
|
|
@@ -310,8 +324,10 @@ module.exports = function (app) {
|
|
|
310
324
|
})
|
|
311
325
|
.catch((e)=>{
|
|
312
326
|
if (s)
|
|
313
|
-
s.stopListening()
|
|
327
|
+
s.stopListening()
|
|
328
|
+
|
|
314
329
|
app.debug(`Unable to communicate with device ${deviceNameAndAddress(config)} Reason: ${e?.message??e}`)
|
|
330
|
+
app.debug(e)
|
|
315
331
|
reject( e?.message??e )
|
|
316
332
|
})})
|
|
317
333
|
}
|
|
@@ -344,13 +360,16 @@ module.exports = function (app) {
|
|
|
344
360
|
createSensor(adapter, deviceConfig).then((sensor)=>{
|
|
345
361
|
deviceConfig.sensor=sensor
|
|
346
362
|
if (deviceConfig.active) {
|
|
347
|
-
|
|
348
|
-
|
|
363
|
+
if (deviceConfig.paths){
|
|
364
|
+
createPaths(deviceConfig)
|
|
365
|
+
initPaths(deviceConfig)
|
|
366
|
+
}
|
|
349
367
|
const result = Promise.resolve(deviceConfig.sensor.listen())
|
|
350
368
|
result.then(() => {
|
|
351
369
|
app.debug(`Listening for changes from ${deviceConfig.sensor.getDisplayName()}`);
|
|
352
370
|
app.setPluginStatus(`Initial scan complete. Listening to ${++found} sensors.`);
|
|
353
371
|
})
|
|
372
|
+
|
|
354
373
|
}
|
|
355
374
|
|
|
356
375
|
})
|
|
@@ -412,11 +431,11 @@ module.exports = function (app) {
|
|
|
412
431
|
adapter = await bluetooth.getAdapter(app.settings?.btAdapter??adapterID)
|
|
413
432
|
await startScanner()
|
|
414
433
|
if (starts>0){
|
|
415
|
-
app.debug(
|
|
434
|
+
app.debug(`Plugin ${packageInfo.version} restarting...`);
|
|
416
435
|
if (plugin.schema.properties.peripherals.items.dependencies)
|
|
417
436
|
plugin.schema.properties.peripherals.items.dependencies.mac_address.oneOf=[]
|
|
418
437
|
} else {
|
|
419
|
-
app.debug(`
|
|
438
|
+
app.debug(`Plugin ${packageInfo.version} started` )
|
|
420
439
|
|
|
421
440
|
}
|
|
422
441
|
starts++
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bt-sensors-plugin-sk",
|
|
3
|
-
"version": "1.1.0-beta.2.
|
|
4
|
-
"description": "Bluetooth Sensors for Signalk -- support for Victron devices, RuuviTag, Xiaomi, ATC and Inkbird, Ultrasonic, Mopeka tank
|
|
3
|
+
"version": "1.1.0-beta.2.2.0.1",
|
|
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 (new), Aranet4 environment sensors, and Govee GVH51xx temp sensors",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"dbus-next": "^0.10.2",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const BTSensor = require("../../BTSensor");
|
|
2
|
+
class AranetSensor extends BTSensor{
|
|
3
|
+
COLOR = {
|
|
4
|
+
1: 'Green',
|
|
5
|
+
2: 'Yellow',
|
|
6
|
+
3: 'Red'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
constructor(device, config={}){
|
|
10
|
+
super(device, config)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static async identify(device){
|
|
14
|
+
const md = await this.getDeviceProp(device,"ManufacturerData")
|
|
15
|
+
|
|
16
|
+
if (md && md[0x0702]) {
|
|
17
|
+
return this
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
module.exports=AranetSensor
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const AranetSensor = require("./Aranet/AranetSensor");
|
|
2
|
+
class Aranet2 extends AranetSensor{
|
|
3
|
+
|
|
4
|
+
constructor(device, config={}){
|
|
5
|
+
super(device,config)
|
|
6
|
+
}
|
|
7
|
+
static async identify(device){
|
|
8
|
+
const name = await this.getDeviceProp(device,"Name")
|
|
9
|
+
|
|
10
|
+
if ((await super.identify(device)!=null) &&
|
|
11
|
+
name.toLowerCase().startsWith("aranet2")) {
|
|
12
|
+
console.log("Aranet2 not currently supported")
|
|
13
|
+
return null //not supported for now
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async init() {
|
|
17
|
+
await super.init()
|
|
18
|
+
this.initMetadata()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
initMetadata(){
|
|
22
|
+
this.addMetadatum('c02', '', 'c02 concentration',
|
|
23
|
+
(buff)=>{return ((buff.readUInt16LE(8)))})
|
|
24
|
+
this.addMetadatum('temp','K', 'temperature',
|
|
25
|
+
(buff)=>{return parseFloat((273.15+(buff.readInt16LBE(15))/1000).toFixed(2))})
|
|
26
|
+
|
|
27
|
+
this.addMetadatum("pressure","","atmospheric pressure",
|
|
28
|
+
(buff)=>{return ((buff.readUInt16LE(13)))/10})
|
|
29
|
+
this.addMetadatum('batteryStrength', 'ratio', 'sensor battery strength',
|
|
30
|
+
(buff)=>{return ((buff.readUInt8(12))/100)})
|
|
31
|
+
|
|
32
|
+
this.addMetadatum('humidity','ratio', 'humidity',
|
|
33
|
+
(buff)=>{return ((buff.readUInt16LE(8))/10000)})
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
propertiesChanged(props){
|
|
37
|
+
super.propertiesChanged(props)
|
|
38
|
+
const buff = this.getManufacturerData(0x0702)
|
|
39
|
+
this.emitData("temp", buff)
|
|
40
|
+
this.emitData("humidity", buff)
|
|
41
|
+
this.emitData("pressure", buff)
|
|
42
|
+
this.emitData("batteryStrength", buff)
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
module.exports=Aranet2
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const AranetSensor = require("./Aranet/AranetSensor");
|
|
2
|
+
class Aranet4 extends AranetSensor{
|
|
3
|
+
|
|
4
|
+
constructor(device, config={}){
|
|
5
|
+
super(device,config)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static async identify(device){
|
|
9
|
+
const name = await this.getDeviceProp(device,"Name")
|
|
10
|
+
|
|
11
|
+
if ((await super.identify(device) != null) &&
|
|
12
|
+
name.toLowerCase().startsWith("aranet4")) {
|
|
13
|
+
return this
|
|
14
|
+
}
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async init() {
|
|
19
|
+
await super.init()
|
|
20
|
+
this.initMetadata()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
initMetadata(){
|
|
24
|
+
this.addMetadatum('co2', 'ppm', 'co2 concentration',
|
|
25
|
+
(buff)=>{return ((buff.readUInt16LE(8)))})
|
|
26
|
+
this.addMetadatum('temp','K', 'temperature',
|
|
27
|
+
(buff)=>{return parseFloat((273.15+(buff.readInt16LE(10))/20).toFixed(2))})
|
|
28
|
+
|
|
29
|
+
this.addMetadatum("pressure","hPa","atmospheric pressure",
|
|
30
|
+
(buff)=>{return ((buff.readUInt16LE(12)))/10})
|
|
31
|
+
|
|
32
|
+
this.addMetadatum('humidity','ratio', 'humidity',
|
|
33
|
+
(buff)=>{return ((buff.readUInt8(14))/100)})
|
|
34
|
+
this.addMetadatum('batteryStrength', 'ratio', 'sensor battery strength',
|
|
35
|
+
(buff)=>{return ((buff.readUInt8(15))/100)})
|
|
36
|
+
this.addMetadatum('color', '', 'Warning color (G Y R)',
|
|
37
|
+
(buff)=>{return this.COLOR[buff.readUInt8(16)]})
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
propertiesChanged(props){
|
|
41
|
+
super.propertiesChanged(props)
|
|
42
|
+
const buff = this.getManufacturerData(0x0702)
|
|
43
|
+
this.emitData("co2", buff)
|
|
44
|
+
this.emitData("temp", buff)
|
|
45
|
+
this.emitData("humidity", buff)
|
|
46
|
+
this.emitData("pressure", buff)
|
|
47
|
+
this.emitData("batteryStrength", buff)
|
|
48
|
+
this.emitData("color", buff)
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
module.exports=Aranet4
|
|
@@ -258,20 +258,22 @@ class MopekaTankSensor extends BTSensor{
|
|
|
258
258
|
const md = this.valueIfVariant(this.getManufacturerData(this.constructor.manufacturerID))
|
|
259
259
|
this.modelID = md[0]
|
|
260
260
|
this.initMetadata()
|
|
261
|
-
|
|
262
261
|
}
|
|
263
262
|
|
|
264
263
|
getMedium(){
|
|
265
264
|
return Media[this?.medium??'PROPANE']
|
|
266
265
|
}
|
|
266
|
+
getTankHeight(){
|
|
267
|
+
return this?.tankHeight??304.8 //Assume a foot
|
|
268
|
+
}
|
|
267
269
|
|
|
268
270
|
_tankLevel( rawLevel ){
|
|
269
271
|
const coefs= this.getMedium().coefficients
|
|
270
|
-
return rawLevel * (coefs[0] + (coefs[1] * (this.temp-
|
|
272
|
+
return rawLevel * (coefs[0] + (coefs[1] * (this.temp-233.15)) + (coefs[2] * ((this.temp-233.15)^2)))
|
|
271
273
|
}
|
|
272
274
|
|
|
273
275
|
initMetadata(){
|
|
274
|
-
|
|
276
|
+
var md = this.addMetadatum("medium","","type of liquid in tank")
|
|
275
277
|
md.isParam=true
|
|
276
278
|
md.enum=Object.keys(Media)
|
|
277
279
|
|
|
@@ -293,8 +295,8 @@ class MopekaTankSensor extends BTSensor{
|
|
|
293
295
|
return this.temp
|
|
294
296
|
}).bind(this)
|
|
295
297
|
)
|
|
296
|
-
this.addMetadatum("tankLevel","
|
|
297
|
-
(buffer)=>{ return this._tankLevel(((buffer.readUInt16LE(3))&0x3FFF))}
|
|
298
|
+
this.addMetadatum("tankLevel","ratio","tank level",
|
|
299
|
+
(buffer)=>{ return (this._tankLevel(((buffer.readUInt16LE(3))&0x3FFF)))/this.getTankHeight()}
|
|
298
300
|
)
|
|
299
301
|
this.addMetadatum("readingQuality","","quality of read",
|
|
300
302
|
(buffer)=>{ return buffer.readUInt8(4)>>6}
|
|
@@ -312,6 +314,7 @@ class MopekaTankSensor extends BTSensor{
|
|
|
312
314
|
if (props.ManufacturerData)
|
|
313
315
|
this.emitValuesFrom( this.getManufacturerData(this.constructor.manufacturerID) )
|
|
314
316
|
}
|
|
317
|
+
|
|
315
318
|
getName(){
|
|
316
319
|
if (this.name)
|
|
317
320
|
return this.name
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const CRC16_LOW_BYTES = [
|
|
2
|
+
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04,
|
|
3
|
+
0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8,
|
|
4
|
+
0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
|
|
5
|
+
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10,
|
|
6
|
+
0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
|
|
7
|
+
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
|
|
8
|
+
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
|
|
9
|
+
0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0,
|
|
10
|
+
0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
|
|
11
|
+
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
|
|
12
|
+
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C,
|
|
13
|
+
0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
|
|
14
|
+
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54,
|
|
15
|
+
0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
|
|
16
|
+
0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
|
|
17
|
+
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const CRC16_HIGH_BYTES = [
|
|
21
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
22
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
23
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
24
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
25
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
26
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
27
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
28
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
29
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
30
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
31
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
32
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
33
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
|
34
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
35
|
+
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
|
36
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
// Calculate CRC-16 for Modbus
|
|
40
|
+
function crc16Modbus(data){
|
|
41
|
+
var crc_high = 0xFF
|
|
42
|
+
var crc_low = 0xFF
|
|
43
|
+
|
|
44
|
+
for (const byte of data){
|
|
45
|
+
const index = crc_high ^ byte
|
|
46
|
+
crc_high = crc_low ^ CRC16_HIGH_BYTES[index]
|
|
47
|
+
crc_low = CRC16_LOW_BYTES[index]
|
|
48
|
+
}
|
|
49
|
+
return Buffer.from([crc_high, crc_low]).readUInt16BE()
|
|
50
|
+
}
|
|
51
|
+
module.exports = crc16Modbus
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
RENOGY_CONSTANTS={
|
|
2
|
+
FUNCTION:
|
|
3
|
+
{
|
|
4
|
+
3: "READ",
|
|
5
|
+
6: "WRITE"
|
|
6
|
+
},
|
|
7
|
+
|
|
8
|
+
CHARGING_STATE:
|
|
9
|
+
{
|
|
10
|
+
0: 'Deactivated',
|
|
11
|
+
1: 'Activated',
|
|
12
|
+
2: 'MPPT',
|
|
13
|
+
3: 'Equalizing',
|
|
14
|
+
4: 'Boost',
|
|
15
|
+
5: 'Floating',
|
|
16
|
+
6: 'Current limiting'
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
LOAD_STATE: {
|
|
20
|
+
0: 'Off',
|
|
21
|
+
1: 'On'
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
BATTERY_TYPE: {
|
|
25
|
+
1: 'Flooded/Open',
|
|
26
|
+
2: 'Sealed',
|
|
27
|
+
3: 'Gel',
|
|
28
|
+
4: 'Lithium',
|
|
29
|
+
5: 'Custom'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
module.exports=RENOGY_CONSTANTS
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
ported from https://github.com/cyrils/renogy-bt
|
|
3
|
+
*/
|
|
4
|
+
const BTSensor = require("../../BTSensor.js");
|
|
5
|
+
const VC = require('./RenogyConstants.js');
|
|
6
|
+
const crc16Modbus = require('./CRC.js')
|
|
7
|
+
class RenogySensor extends BTSensor{
|
|
8
|
+
|
|
9
|
+
static ALIAS_PREFIX = 'BT-TH'
|
|
10
|
+
static TX_SERVICE = "0000ffd0-0000-1000-8000-00805f9b34fb"
|
|
11
|
+
static RX_SERVICE = "0000fff0-0000-1000-8000-00805f9b34fb"
|
|
12
|
+
static NOTIFY_CHAR_UUID = "0000fff1-0000-1000-8000-00805f9b34fb"
|
|
13
|
+
static WRITE_CHAR_UUID = "0000ffd1-0000-1000-8000-00805f9b34fb"
|
|
14
|
+
static READ_FUNC = 3
|
|
15
|
+
static WRITE_FUNC = 6
|
|
16
|
+
constructor(device,config,gattConfig){
|
|
17
|
+
super(device,config,gattConfig)
|
|
18
|
+
}
|
|
19
|
+
static async getReadWriteCharacteristics(device){
|
|
20
|
+
const gattServer = await device.gatt()
|
|
21
|
+
const txService= await gattServer.getPrimaryService(this.TX_SERVICE)
|
|
22
|
+
const rxService= await gattServer.getPrimaryService(this.RX_SERVICE)
|
|
23
|
+
const rxChar = await rxService.getCharacteristic(this.NOTIFY_CHAR_UUID)
|
|
24
|
+
const txChar = await txService.getCharacteristic(this.WRITE_CHAR_UUID)
|
|
25
|
+
return {read: rxChar, write: txChar}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static async sendReadFunctionRequest(writeCharacteristic, deviceID, writeReq, words){
|
|
29
|
+
var b = Buffer.alloc(8)
|
|
30
|
+
b.writeUInt8(deviceID,0)
|
|
31
|
+
b.writeUInt8(this.READ_FUNC,1)
|
|
32
|
+
b.writeUInt16BE(writeReq,2)
|
|
33
|
+
b.writeUInt16BE(words,4)
|
|
34
|
+
b.writeUInt16BE(crc16Modbus(b.subarray(0,6)),6)
|
|
35
|
+
|
|
36
|
+
await writeCharacteristic.writeValueWithResponse(b, 0)
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
static identify(device){
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async init(){
|
|
44
|
+
await super.init()
|
|
45
|
+
var md = this.addMetadatum('refreshInterval','','refresh interval')
|
|
46
|
+
md.isParam = true
|
|
47
|
+
|
|
48
|
+
md = this.addMetadatum('deviceID', '', 'ID of device')
|
|
49
|
+
md.isParam = true
|
|
50
|
+
|
|
51
|
+
await this.device.connect()
|
|
52
|
+
const rw = await this.constructor.getReadWriteCharacteristics(this.device)
|
|
53
|
+
|
|
54
|
+
this.readChar = rw.read
|
|
55
|
+
this.writeChar = rw.write
|
|
56
|
+
await this.readChar.startNotifications()
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
emitGATT(){
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getModelName(){
|
|
64
|
+
return this?.modelID??`${this.constructor.name} Unknown model`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getName(){
|
|
68
|
+
return `Renogy ${this.getModelName()}`
|
|
69
|
+
}
|
|
70
|
+
propertiesChanged(props){
|
|
71
|
+
super.propertiesChanged(props)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getAllEmitterFunctions(){
|
|
75
|
+
return []
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getDeviceID()
|
|
79
|
+
{
|
|
80
|
+
return this?.deviceID??0xFF
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async sendReadFunctionRequest(writeReq, words){
|
|
84
|
+
this.constructor.sendReadFunctionRequest(
|
|
85
|
+
this.writeChar, this.getDeviceID(), writeReq, words)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async initGATTNotifications(){
|
|
89
|
+
this.getAllEmitterFunctions().forEach(async (emitter)=>
|
|
90
|
+
await emitter()
|
|
91
|
+
)
|
|
92
|
+
this.intervalID = setInterval(()=>{
|
|
93
|
+
this.getAllEmitterFunctions().forEach(async (emitter)=>
|
|
94
|
+
await emitter()
|
|
95
|
+
)
|
|
96
|
+
}, 1000*(this?.refreshInterval??60) )
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async initGATTConnection() {
|
|
101
|
+
return this
|
|
102
|
+
}
|
|
103
|
+
usingGATT(){
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
hasGATT(){
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async stopListening(){
|
|
111
|
+
super.stopListening()
|
|
112
|
+
|
|
113
|
+
await this.readChar.stopNotifications()
|
|
114
|
+
|
|
115
|
+
if (await this.device.isConnected()){
|
|
116
|
+
await this.device.disconnect()
|
|
117
|
+
this.debug(`Disconnected from ${ this.getName()}`)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
module.exports=RenogySensor
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const RenogySensor = require("./Renogy/RenogySensor.js");
|
|
6
|
+
class RenogyBattery extends RenogySensor {
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async init(){
|
|
11
|
+
await super.init()
|
|
12
|
+
this.numberOfCells = await this.retrieveNumberOfCells()
|
|
13
|
+
this.deviceID = await this.retrieveDeviceID()
|
|
14
|
+
this.initMetadata()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async getAllEmitterFunctions(){
|
|
18
|
+
return [this.getAndEmitBatteryInfo.bind(this),
|
|
19
|
+
this.getAndEmitCellTemperatures.bind(this),
|
|
20
|
+
this.getAndEmitCellVoltages.bind(this)]
|
|
21
|
+
}
|
|
22
|
+
initMetadata(){
|
|
23
|
+
|
|
24
|
+
this.addMetadatum('numberOfCells','', 'number of cells')
|
|
25
|
+
this.addMetadatum('current','A','current',
|
|
26
|
+
(buffer)=>{return buffer.readInt16BE(3)/100})
|
|
27
|
+
|
|
28
|
+
this.addMetadatum('voltage','V','voltage',
|
|
29
|
+
(buffer)=>{return buffer.readUInt16BE(5)/10})
|
|
30
|
+
|
|
31
|
+
this.addMetadatum('remainingCharge', 'Ah', 'remaining charge', //TODO: units
|
|
32
|
+
(buffer)=>{return buffer.readUInt32BE(7)/1000})
|
|
33
|
+
|
|
34
|
+
this.addMetadatum('capacity','Ah', 'capacity',
|
|
35
|
+
(buffer)=>{return buffer.readUInt32BE(11)/1000})
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i++ ; i < this.numberOfCells) {
|
|
38
|
+
this.addMetadatum(`cellVoltage${i}`, 'V', `cell #${i} voltage`,
|
|
39
|
+
(buffer)=>{ return buffer.readUInt16(5+ i*2) }
|
|
40
|
+
)
|
|
41
|
+
this.addMetadatum(`cellTemp${i}`, 'K', `cell #${i} temperature`,
|
|
42
|
+
(buffer)=>{ return buffer.readUInt16(5+ i*2)+273.15 }
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async initGATTConnection() {
|
|
47
|
+
await super.initGATTConnection()
|
|
48
|
+
this.emit('numberOfCells', this.numberOfCells)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
retrieveNumberOfCells(){
|
|
52
|
+
|
|
53
|
+
return new Promise( async ( resolve, reject )=>{
|
|
54
|
+
await this.sendReadFunctionRequest(0x1388,0x11)
|
|
55
|
+
|
|
56
|
+
const valChanged = async (buffer) => {
|
|
57
|
+
resolve(buffer.readUInt16(3))
|
|
58
|
+
}
|
|
59
|
+
this.readChar.once('valuechanged', valChanged )
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
retrieveDeviceID(){
|
|
63
|
+
return new Promise( async ( resolve, reject )=>{
|
|
64
|
+
this.sendFunctionRequest(0x14, 0x67)
|
|
65
|
+
|
|
66
|
+
const valChanged = async (buffer) => {
|
|
67
|
+
resolve((buffer.readUInt8(4)))
|
|
68
|
+
}
|
|
69
|
+
this.readChar.once('valuechanged', valChanged )
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getAndEmitBatteryInfo(){
|
|
74
|
+
return new Promise( async ( resolve, reject )=>{
|
|
75
|
+
|
|
76
|
+
await this.sendReadFunctionRequest(0x13b2, 0x6)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
this.readChar.once('valuechanged', (buffer) => {
|
|
80
|
+
["current", "voltage", "remainingCharge", "capacity"].forEach(tag)
|
|
81
|
+
this.emitData( tag, buffer )
|
|
82
|
+
|
|
83
|
+
resolve(this)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getAndEmitCellVoltages(){
|
|
89
|
+
return new Promise( async ( resolve, reject )=>{
|
|
90
|
+
this.sendReadFunctionRequest(0x1388,0x11)
|
|
91
|
+
|
|
92
|
+
this.readChar.once('valuechanged', (buffer) => {
|
|
93
|
+
for (let i = 0; i++ ; i < this.numberOfCells)
|
|
94
|
+
this.emitData(`cellVoltage${i}`, buffer)
|
|
95
|
+
resolve(this)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getAndEmitCellTemperatures(){
|
|
101
|
+
return new Promise( async ( resolve, reject )=>{
|
|
102
|
+
this.sendReadFunctionRequest(0x1399,0x22)
|
|
103
|
+
|
|
104
|
+
this.readChar.once('valuechanged', buffer => {
|
|
105
|
+
for (let i = 0; i++ ; i < this.numberOfCells)
|
|
106
|
+
this.emitData(`cellTemp${i}`, buffer)
|
|
107
|
+
resolve(this)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
module.exports=RenogyBattery
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const RenogySensor = require("./Renogy/RenogySensor.js");
|
|
6
|
+
const RC=require("./Renogy/RenogyConstants.js")
|
|
7
|
+
class RenogyInverter extends RenogySensor {
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async init(){
|
|
11
|
+
await super.init()
|
|
12
|
+
this.initMetadata()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
initMetadata(){
|
|
16
|
+
this.addMetadatum('batteryType', '', "battery type")
|
|
17
|
+
|
|
18
|
+
this.addMetadatum('ueiVoltage','V','UEI Voltage',
|
|
19
|
+
(buffer)=>{return buffer.readUInt16BE(3)/10})
|
|
20
|
+
this.addMetadatum('ueiCurrent','V','UEI current',
|
|
21
|
+
(buffer)=>{return buffer.readUInt16BE(5)/10})
|
|
22
|
+
this.addMetadatum('voltage','V','voltage',
|
|
23
|
+
(buffer)=>{return buffer.readUInt16BE(7)/10})
|
|
24
|
+
this.addMetadatum('loadCurrent','A','load current',
|
|
25
|
+
(buffer)=>{return buffer.readUInt16BE(9)})
|
|
26
|
+
this.addMetadatum('frequency','hz','frequency',
|
|
27
|
+
(buffer)=>{return buffer.readUInt16BE(11)/100})
|
|
28
|
+
this.addMetadatum('temperature','K','temperature',
|
|
29
|
+
(buffer)=>{return (buffer.readUInt16BE(13)/10)+273.15})
|
|
30
|
+
|
|
31
|
+
this.addMetadatum('solarVoltage','V', 'solar voltage',
|
|
32
|
+
(buffer)=>{return buffer.readUInt16BE(3)/10})
|
|
33
|
+
this.addMetadatum('solarCurrent','A', 'solar current',
|
|
34
|
+
(buffer)=>{return buffer.readUInt16BE(5)/10})
|
|
35
|
+
this.addMetadatum('solarPower','W', 'solar power',
|
|
36
|
+
(buffer)=>{return buffer.readUInt16BE(7)})
|
|
37
|
+
this.addMetadatum('solarChargingStatus', '', 'solar charging state',
|
|
38
|
+
(buffer)=>{return RC.CHARGING_STATE[buffer.readUInt16BE(9)]})
|
|
39
|
+
this.addMetadatum('solarChargingPower', 'W', 'solar charging power',
|
|
40
|
+
(buffer)=>{return buffer.readUInt16BE(11)})
|
|
41
|
+
|
|
42
|
+
this.addMetadatum('loadPower','W','load power',
|
|
43
|
+
(buffer)=>{return buffer.readUInt16BE(3)})
|
|
44
|
+
this.addMetadatum('chargingCurrent','A','charging current',
|
|
45
|
+
(buffer)=>{return buffer.readUInt16BE(5)/10})
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getBatteryType(){
|
|
50
|
+
return new Promise( async ( resolve, reject )=>{
|
|
51
|
+
this.sendReadFunctionRequest( 0xe004, 0x01)
|
|
52
|
+
|
|
53
|
+
const valChanged = async (buffer) => {
|
|
54
|
+
resolve(RC.BATTERY_TYPE[(buffer.readUInt8(4))])
|
|
55
|
+
}
|
|
56
|
+
this.readChar.once('valuechanged', valChanged )
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getAndEmitInverterStats(){
|
|
61
|
+
return new Promise( async ( resolve, reject )=>{
|
|
62
|
+
|
|
63
|
+
await this.sendReadFunctionRequest(0xfa0, 0x8)
|
|
64
|
+
|
|
65
|
+
this.readChar.once('valuechanged', (buffer) => {
|
|
66
|
+
["ueiVoltage","ueiCurrent", "voltage", "loadCurrent", "frequency","temperature"].forEach(tag)
|
|
67
|
+
this.emitData( tag, buffer )
|
|
68
|
+
|
|
69
|
+
resolve(this)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getAndEmitSolarCharging(){
|
|
75
|
+
return new Promise( async ( resolve, reject )=>{
|
|
76
|
+
|
|
77
|
+
await this.sendReadFunctionRequest(0x10e9, 0x5)
|
|
78
|
+
|
|
79
|
+
this.readChar.once('valuechanged', (buffer) => {
|
|
80
|
+
["solarVoltage","solarCurrent", "solarPower", "solarChargingStatus", "solarChargingPower"].forEach(tag)
|
|
81
|
+
this.emitData( tag, buffer )
|
|
82
|
+
|
|
83
|
+
resolve(this)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getAndEmitInverterLoad(){
|
|
89
|
+
return new Promise( async ( resolve, reject )=>{
|
|
90
|
+
|
|
91
|
+
await this.sendReadFunctionRequest(0x113a, 0x2)
|
|
92
|
+
|
|
93
|
+
this.readChar.once('valuechanged', (buffer) => {
|
|
94
|
+
["loadPower", "chargingCurrent"].forEach(tag)
|
|
95
|
+
this.emitData( tag, buffer )
|
|
96
|
+
|
|
97
|
+
resolve(this)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
getAllEmitterFunctions(){
|
|
102
|
+
return [
|
|
103
|
+
this.getAndEmitInverterLoad.bind(this),
|
|
104
|
+
this.getAndEmitInverterStats.bind(this),
|
|
105
|
+
this.getAndEmitSolarCharging.bind(this)]
|
|
106
|
+
}
|
|
107
|
+
async initGATTConnection() {
|
|
108
|
+
await super.initGATTConnection()
|
|
109
|
+
this.batteryType = await this.getBatteryType()
|
|
110
|
+
this.emit('batteryType', this.batteryType)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
module.exports=RenogyInverter
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const RenogySensor = require("./Renogy/RenogySensor.js");
|
|
6
|
+
const RC=require("./Renogy/RenogyConstants.js")
|
|
7
|
+
const crc16Modbus = require('./Renogy/CRC.js')
|
|
8
|
+
|
|
9
|
+
class RenogyRoverClient extends RenogySensor {
|
|
10
|
+
|
|
11
|
+
async init(){
|
|
12
|
+
await super.init()
|
|
13
|
+
this.initMetadata()
|
|
14
|
+
this.modelID=await this.retrieveModelID()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
initMetadata(){
|
|
18
|
+
//Buffer(73) [1, 3, 68, 32, 32, 82, 78, 71, 45, 67, 84, 82, 76, 45, 87, 78, 68, 51, 48, 7, 140, 0, 132, 0, 126, 0, 120, 0, 111, 0, 106, 100, 50, 0, 5, 0, 120, 0, 120, 0, 28, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 5, 0, 5, 2, 148, 0, 5, 206, 143, 34, 228, buffer: ArrayBuffer(8192), byteLength: 73, byteOffset: 6144, length: 73, Symbol(Symbol.toStringTag): 'Uint8Array']
|
|
19
|
+
|
|
20
|
+
this.addMetadatum('batteryType', '', "battery type")
|
|
21
|
+
this.addMetadatum('batteryPercentage', 'ratio', "battery percentage",
|
|
22
|
+
(buffer)=>{return buffer.readUInt16BE(3) })
|
|
23
|
+
this.addMetadatum('batteryVoltage', 'V', "battery voltage",
|
|
24
|
+
(buffer)=>{return buffer.readUInt16BE((5))/10})
|
|
25
|
+
this.addMetadatum('batteryCurrent', 'A', 'battery current',
|
|
26
|
+
(buffer)=>{return buffer.readUInt16BE((7))/100})
|
|
27
|
+
this.addMetadatum('controllerTemperature', 'K', 'controller temperature',
|
|
28
|
+
(buffer)=>{return buffer.readInt8((9))+273.15})
|
|
29
|
+
this.addMetadatum('batteryTemperature', 'K', 'battery temperature',
|
|
30
|
+
(buffer)=>{return buffer.readInt8((10))+273.15})
|
|
31
|
+
this.addMetadatum('loadVoltage', 'V', 'load voltage',
|
|
32
|
+
(buffer)=>{return buffer.readUInt16BE((11))/10})
|
|
33
|
+
this.addMetadatum('loadCurrent', 'A', 'load current',
|
|
34
|
+
(buffer)=>{return buffer.readUInt16BE((13))/100})
|
|
35
|
+
this.addMetadatum('loadPower', 'W', 'load power',
|
|
36
|
+
(buffer)=>{return buffer.readUInt16BE((15))})
|
|
37
|
+
this.addMetadatum('pvVoltage', 'V', 'pv voltage',
|
|
38
|
+
(buffer)=>{return buffer.readUInt16BE((17))/10})
|
|
39
|
+
this.addMetadatum('pvCurrent', 'A', 'pv current',
|
|
40
|
+
(buffer)=>{return buffer.readUInt16BE((19))/100})
|
|
41
|
+
this.addMetadatum('pvPower', 'W', 'pv power',
|
|
42
|
+
(buffer)=>{return buffer.readUInt16BE(21)})
|
|
43
|
+
this.addMetadatum('maxChargingPowerToday', 'W', 'max charging power today',
|
|
44
|
+
(buffer)=>{return buffer.readUInt16BE(33)})
|
|
45
|
+
this.addMetadatum('maxDischargingPowerToday', 'W', 'max discharging power today',
|
|
46
|
+
(buffer)=>{return buffer.readUInt16BE(35)})
|
|
47
|
+
this.addMetadatum('chargingAmpHoursToday', 'Ah', 'charging amp hours today',
|
|
48
|
+
(buffer)=>{return buffer.readUInt16BE(37)})
|
|
49
|
+
this.addMetadatum('dischargingAmpHoursToday', 'Ah', 'discharging amp hours today',
|
|
50
|
+
(buffer)=>{return buffer.readUInt16BE(39)})
|
|
51
|
+
this.addMetadatum('powerGenerationToday', 'W', 'power generation today',
|
|
52
|
+
(buffer)=>{return buffer.readUInt16BE(41)})
|
|
53
|
+
this.addMetadatum('powerConsumptionToday', 'W', 'power consumption today',
|
|
54
|
+
(buffer)=>{return buffer.readUInt16BE(43)})
|
|
55
|
+
this.addMetadatum('powerGenerationTotal', 'W', 'power generation total',
|
|
56
|
+
(buffer)=>{return buffer.readUInt32BE(59)})
|
|
57
|
+
this.addMetadatum('loadStatus', '', 'load status',
|
|
58
|
+
(buffer)=>{return RC.LOAD_STATE[buffer.readUInt8(67)>>7]})
|
|
59
|
+
|
|
60
|
+
this.addMetadatum('chargingStatus', '', 'charging status',
|
|
61
|
+
(buffer)=>{return RC.CHARGING_STATE[buffer.readUInt8(68)]})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
retrieveDeviceID(){
|
|
65
|
+
return new Promise( async ( resolve, reject )=>{
|
|
66
|
+
this.sendReadFunctionRequest(0x1A, 0x1)
|
|
67
|
+
|
|
68
|
+
const valChanged = async (buffer) => {
|
|
69
|
+
resolve((buffer.readUInt8(4)))
|
|
70
|
+
}
|
|
71
|
+
this.readChar.once('valuechanged', valChanged )
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
retrieveBatteryType(){
|
|
76
|
+
return new Promise( async ( resolve, reject )=>{
|
|
77
|
+
//Buffer(7) [255, 3, 2, 0, 1, 80, 80, buffer: ArrayBuffer(8192), byteLength: 7, byteOffset: 864, length: 7, Symbol(Symbol.toStringTag): 'Uint8Array']
|
|
78
|
+
|
|
79
|
+
this.sendReadFunctionRequest(0xe004, 0x01)
|
|
80
|
+
|
|
81
|
+
const valChanged = async (buffer) => {
|
|
82
|
+
resolve(RC.BATTERY_TYPE[(buffer.readUInt8(4))])
|
|
83
|
+
}
|
|
84
|
+
this.readChar.once('valuechanged', valChanged )
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async retrieveModelID(){
|
|
90
|
+
return new Promise( async ( resolve, reject )=>{
|
|
91
|
+
|
|
92
|
+
await this.sendReadFunctionRequest(0x0c,0x08)
|
|
93
|
+
|
|
94
|
+
this.readChar.once('valuechanged', async (buffer) => {
|
|
95
|
+
if (buffer[2]!=0x10)
|
|
96
|
+
reject("Unknown error retrieving model ID") //???
|
|
97
|
+
const model = buffer.subarray(3,17).toString().trim()
|
|
98
|
+
resolve(model)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
async initGATTConnection() {
|
|
103
|
+
await super.initGATTConnection()
|
|
104
|
+
this.batteryType = await this.retrieveBatteryType()
|
|
105
|
+
this.emit('batteryType', this.batteryType)
|
|
106
|
+
if (!this.deviceID)
|
|
107
|
+
this.deviceID = await this.retrieveDeviceID()
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
getAllEmitterFunctions(){
|
|
113
|
+
return [this.getAndEmitChargeInfo.bind(this)]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async getAndEmitChargeInfo(){
|
|
117
|
+
return new Promise( async ( resolve, reject )=>{
|
|
118
|
+
try {
|
|
119
|
+
this.sendReadFunctionRequest(0x100, 0x22)
|
|
120
|
+
|
|
121
|
+
this.readChar.once('valuechanged', buffer => {
|
|
122
|
+
this.emitValuesFrom(buffer)
|
|
123
|
+
resolve(this)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
} catch (error) {
|
|
127
|
+
reject(error?.message??error)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
}
|
|
133
|
+
module.exports=RenogyRoverClient
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const BTSensor = require("../BTSensor");
|
|
2
2
|
class UNKNOWN extends BTSensor{
|
|
3
|
+
static isSystem = true
|
|
4
|
+
|
|
3
5
|
static identify(device){
|
|
4
6
|
return null
|
|
5
7
|
}
|
|
@@ -7,6 +9,13 @@ class UNKNOWN extends BTSensor{
|
|
|
7
9
|
await super.init()
|
|
8
10
|
if (!this.currentProperties.Name)
|
|
9
11
|
this.currentProperties.Name= `Unknown device from ${this.getManufacturer()}`
|
|
12
|
+
var md =this.addMetadatum("sensorClass",'',"Sensor Class")
|
|
13
|
+
md.isParam=true
|
|
14
|
+
md.enum=Array.from(this.constructor.classMap.keys())
|
|
15
|
+
//md.enumNames=this.constructor.classMap
|
|
16
|
+
|
|
10
17
|
}
|
|
18
|
+
|
|
19
|
+
|
|
11
20
|
}
|
|
12
21
|
module.exports=UNKNOWN
|
|
@@ -53,7 +53,7 @@ function sleep(x) {
|
|
|
53
53
|
await super.init()
|
|
54
54
|
const md =this.addMetadatum('encryptionKey','', "Encryption Key")
|
|
55
55
|
md.isParam = true
|
|
56
|
-
this.model_id=this.getManufacturerData(0x2e1)
|
|
56
|
+
this.model_id=this.getManufacturerData(0x2e1)?.readUInt16LE(2)??"Unknown"
|
|
57
57
|
}
|
|
58
58
|
alarmReason(alarmValue){
|
|
59
59
|
return this.constructor.AlarmReason[alarmValue]
|
|
@@ -64,7 +64,7 @@ function sleep(x) {
|
|
|
64
64
|
|
|
65
65
|
decrypt(data){
|
|
66
66
|
if (!this.encryptionKey)
|
|
67
|
-
throw Error("Unable to decrypt: no encryption key set")
|
|
67
|
+
throw new Error("Unable to decrypt: no encryption key set")
|
|
68
68
|
|
|
69
69
|
const encMethod = 'aes-128-ctr';
|
|
70
70
|
const iv = data.readUInt16LE(5);
|
|
@@ -166,6 +166,9 @@ class XiaomiMiBeacon extends BTSensor{
|
|
|
166
166
|
if (this.usingGATT()) return
|
|
167
167
|
const data = this.getServiceData(this.constructor.SERVICE_MIBEACON)
|
|
168
168
|
var dec
|
|
169
|
+
if (!this.encryptionKey){
|
|
170
|
+
throw new Error(`${this.getNameAndAddress()} requires an encryption key.`)
|
|
171
|
+
}
|
|
169
172
|
if (this.encryptionVersion >= 4) {
|
|
170
173
|
dec = this.decryptV4and5(data)
|
|
171
174
|
} else {
|