bt-sensors-plugin-sk 1.2.1 → 1.2.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 +15 -5
- package/README.md +36 -7
- package/index.js +9 -2
- package/package.json +2 -2
- package/plugin_defaults.json +21 -2
- package/public/images/SmartShunt_500_nw.png +0 -0
- package/sensor_classes/JBDBMS.js +19 -6
- package/sensor_classes/Junctek.js +148 -0
- package/sensor_classes/KilovaultHLXPlus.js +1 -1
- package/sensor_classes/RemoranWave3.js +228 -0
- package/sensor_classes/Renogy/RenogyConstants.js +2 -1
- package/sensor_classes/Renogy/RenogySensor.js +2 -5
- package/sensor_classes/RenogyRoverClient.js +62 -2
- package/sensor_classes/ShenzhenLiOnBMS.js +26 -16
- package/sensor_classes/Victron/VictronImages.js +1 -1
- package/sensor_classes/Victron/VictronSensor.js +4 -2
- package/sensor_classes/VictronBatteryMonitor.js +2 -2
- package/sensor_classes/VictronDCDCConverter.js +1 -1
- package/sensor_classes/VictronLynxSmartBMS.js +5 -1
- package/sensor_classes/VictronOrionXS.js +1 -1
- package/sensor_classes/VictronSmartLithium.js +1 -1
- package/vsl_patch_17_06_25.patch +13 -0
package/BTSensor.js
CHANGED
|
@@ -465,13 +465,26 @@ class BTSensor extends EventEmitter {
|
|
|
465
465
|
throw new Error("::initGATTNotifications() should be implemented by the BTSensor subclass")
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
deviceConnect() {
|
|
468
|
+
deviceConnect(reconnect=false) {
|
|
469
469
|
|
|
470
470
|
|
|
471
471
|
return connectQueue.enqueue( async ()=>{
|
|
472
472
|
this.debug(`Connecting... ${this.getName()}`)
|
|
473
|
-
await this.device.
|
|
473
|
+
await this.device.helper.callMethod('Connect')
|
|
474
|
+
|
|
474
475
|
this.debug(`Connected to ${this.getName()}`)
|
|
476
|
+
if (!reconnect) {
|
|
477
|
+
this.device.helper.on('PropertiesChanged', (propertiesChanged) => {
|
|
478
|
+
if ('Connected' in propertiesChanged) {
|
|
479
|
+
const { value } = propertiesChanged.Connected
|
|
480
|
+
if (value) {
|
|
481
|
+
this.device.emit('connect', { connected: true })
|
|
482
|
+
} else {
|
|
483
|
+
this.device.emit('disconnect', { connected: false })
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
}
|
|
475
488
|
|
|
476
489
|
try {
|
|
477
490
|
|
|
@@ -787,8 +800,6 @@ class BTSensor extends EventEmitter {
|
|
|
787
800
|
/**
|
|
788
801
|
* Listen to sensor.
|
|
789
802
|
* ::listen() sets up listeners for property changes thru ::propertiesChanged(props)
|
|
790
|
-
* If GATT connections are available and active, function inits the GATT connection and
|
|
791
|
-
* optional GATT connection interval
|
|
792
803
|
*/
|
|
793
804
|
|
|
794
805
|
|
|
@@ -842,7 +853,6 @@ class BTSensor extends EventEmitter {
|
|
|
842
853
|
if (!(path===undefined))
|
|
843
854
|
this.app.handleMessage(id,
|
|
844
855
|
{
|
|
845
|
-
// $source: source,
|
|
846
856
|
updates:
|
|
847
857
|
[{ meta: [{path: preparePath(this, path), value: { units: pathMeta?.unit }}]}]
|
|
848
858
|
})
|
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# Bluetooth Sensors for [Signal K](http://www.signalk.org)
|
|
2
|
-
# Version 1.2.
|
|
2
|
+
# Version 1.2.3
|
|
3
3
|
|
|
4
|
-
## WHAT
|
|
4
|
+
## WHAT'S NEW SINCE VERSION 1.2.2
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Bug fixes Remoran Wave.3, JunctekBMS, and ShenzhenLiOn
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
# Version 1.2.2
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## WHAT'S NEW SINCE VERSION 1.2.1
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
- Junctek BMS and Remoran Wave 3 support (Note: both are not currently field tested)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
- Fixes to ShenzhenLiOn BMS, Victron Orion XS, Victron DC DC Converter, Victron Smart Lithium Classes
|
|
15
15
|
|
|
16
16
|
## WHAT'S NEW SINCE VERSION 1.1.x
|
|
17
17
|
|
|
@@ -25,6 +25,19 @@ It's pretty easy to write and deploy your own sensor class for any currently uns
|
|
|
25
25
|
|
|
26
26
|
- Support for multiple simultaneous GATT connections.
|
|
27
27
|
|
|
28
|
+
|
|
29
|
+
## WHAT IT IS
|
|
30
|
+
|
|
31
|
+
BT Sensors Plugin for Signalk is a lightweight BLE (Bluetooth Low Energy) framework for listening and connecting to Bluetooth sensors on your boat. After discovery and configuration the plugin sends deltas to Signalk paths with values your sensor reports. <br>
|
|
32
|
+
|
|
33
|
+
It runs on any 2.0 or greater SignalK installation but on Linux only. It's been tested on Desktop and headless RPis, OpenPlotter, and Cerbo GX/Ekrano.
|
|
34
|
+
|
|
35
|
+
A typical use case is a Bluetooth thermometer like the Xiaomi LYWSD03MMC, an inexpensive Bluetooth thermometer that runs on a 3V watch battery that can report the current temperature and humidity in your refrigerator or cabin or wherever you want to stick it (no judgement.) <br>
|
|
36
|
+
|
|
37
|
+
The reported temperature can then be displayed on a Signalk app like Kip, WilhelmSK or, with appropriate mapping to NMEA-2000, a NMEA 2000 Multi-function display.
|
|
38
|
+
|
|
39
|
+
It's pretty easy to write and deploy your own sensor class for any currently unsupported sensor. More on that in [the development README](./sensor_classes/DEVELOPMENT.md).
|
|
40
|
+
|
|
28
41
|
## SUPPORTED SENSORS
|
|
29
42
|
|
|
30
43
|
### NOTE
|
|
@@ -44,6 +57,8 @@ It's pretty easy to write and deploy your own sensor class for any currently uns
|
|
|
44
57
|
|Redodo| Rebranded LiTime |
|
|
45
58
|
|Kilovault| [Kilovault HLX+ smart batteries ](https://sunwatts.com/content/manual/KiloVault_HLX_PLUS_Datasheet_06252021%20%281%29.pdf?srsltid=AfmBOooY-cGnC_Qm6V1T9Vg5oZzBCJurS0AOGoWqWeyy-dwz2vA-l1Jb) (Note: Kilovault appears to be out of business as of March 2024) |
|
|
46
59
|
|[Lancol](www.Lancol.com)| [Micro 10C 12V Car Battery Monitor](https://www.lancol.com/product/12v-bluetooth-4-0-battery-tester-micro-10-c/)|
|
|
60
|
+
|[Junctek](https://www.junteks.com)|[Junctek BMS](https://www.junteks.com/pages/product/index) |
|
|
61
|
+
|[Remoran](https://remoran.eu)| [Remoran Wave.3](https://remoran.eu/wave.html)|
|
|
47
62
|
|
|
48
63
|
|
|
49
64
|
### Environmental
|
|
@@ -121,6 +136,19 @@ Finally, restart SK. Plugin should appear in your server plugins list.<br>
|
|
|
121
136
|
> NOTE: "~/.signalk" is the default signalk home on Linux. If you're
|
|
122
137
|
> getting permissions errors executing npm link, try executing "npm link" under sudo.
|
|
123
138
|
|
|
139
|
+
## KNOWN ISSUES
|
|
140
|
+
|
|
141
|
+
### Configuration Panel
|
|
142
|
+
|
|
143
|
+
- Safari 18.1 on OsX produces errors on load that kill the configuration screen. No known cause. Upgrade to most recent Safari or use Chrome.
|
|
144
|
+
- Unsaved sensor configuration changes are lost after selecting a different sensor. Be sure to Save changes for now.
|
|
145
|
+
- Renogy Rover Client, Victron GX, Victron Smart Battery Protect, and Victron VE Bus sensor classes have no default paths currently. Users will need to manually input.
|
|
146
|
+
|
|
147
|
+
### Runtime
|
|
148
|
+
|
|
149
|
+
- IMPORTANT Set `Scan for new devices interval` to `0` after configuration is complete. The plugin will run but in Bluetooth-rich environments, or if you have a long range BT 5.3 device, the system Bluetooth stack may fail after 4 hours or so.
|
|
150
|
+
- There's no way that I know of to remove a SK Path without restarting the server. So if any active paths are changed by the plugin, you'll still see them hanging around in the data browser growing stale until you restart the server.
|
|
151
|
+
|
|
124
152
|
## CONFIGURATION
|
|
125
153
|
|
|
126
154
|
After installing and restarting Signalk you should see a "BT Sensors Plugin" option in the Signalk->Server->Plugin Config page.<br><br>
|
|
@@ -266,6 +294,7 @@ Many thanks to all those who contributed to the project either with code or test
|
|
|
266
294
|
- Arjen
|
|
267
295
|
- SDLee
|
|
268
296
|
- Jordan
|
|
297
|
+
- Jan of SKipper App fame
|
|
269
298
|
|
|
270
299
|
It takes a village. Or more appropriately, an armada. Okay, regatta. But you get the idea.
|
|
271
300
|
|
package/index.js
CHANGED
|
@@ -172,10 +172,19 @@ module.exports = function (app) {
|
|
|
172
172
|
});
|
|
173
173
|
router.post('/removeSensorData', async (req, res) => {
|
|
174
174
|
app.debug(req.body)
|
|
175
|
+
const sensor = sensorMap.get(req.body.mac_address)
|
|
176
|
+
if (!sensor) {
|
|
177
|
+
res.status(404).json({message: "Sensor not found"})
|
|
178
|
+
return
|
|
179
|
+
}
|
|
175
180
|
const i = deviceConfigs.findIndex((p)=>p.mac_address==req.body.mac_address)
|
|
176
181
|
if (i>=0){
|
|
177
182
|
deviceConfigs.splice(i,1)
|
|
178
183
|
}
|
|
184
|
+
|
|
185
|
+
if (sensor.isActive())
|
|
186
|
+
await sensor.stopListening()
|
|
187
|
+
|
|
179
188
|
if (sensorMap.has(req.body.mac_address))
|
|
180
189
|
sensorMap.delete(req.body.mac_address)
|
|
181
190
|
app.savePluginOptions(
|
|
@@ -569,7 +578,6 @@ module.exports = function (app) {
|
|
|
569
578
|
progressID = setInterval(()=>{
|
|
570
579
|
channel.broadcast({"progress":++progress, "maxTimeout": maxTimeout, "deviceCount":foundConfiguredDevices, "totalDevices": deviceConfigs.length},"progress")
|
|
571
580
|
if ( foundConfiguredDevices==deviceConfigs.length){
|
|
572
|
-
app.debug("progress complete")
|
|
573
581
|
progressID,progressTimeoutID = null
|
|
574
582
|
clearTimeout(progressTimeoutID)
|
|
575
583
|
clearInterval(progressID)
|
|
@@ -578,7 +586,6 @@ module.exports = function (app) {
|
|
|
578
586
|
},1000);
|
|
579
587
|
if (progressTimeoutID==null)
|
|
580
588
|
progressTimeoutID = setTimeout(()=> {
|
|
581
|
-
app.debug("progress timed out ")
|
|
582
589
|
if (progressID) {
|
|
583
590
|
|
|
584
591
|
clearInterval(progressID);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bt-sensors-plugin-sk",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "Bluetooth Sensors for Signalk
|
|
3
|
+
"version": "1.2.3",
|
|
4
|
+
"description": "Bluetooth Sensors for Signalk - see https://www.npmjs.com/package/bt-sensors-plugin-sk#supported-sensors for a list of supported sensors",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@rjsf/bootstrap-4": "^5.24.11",
|
package/plugin_defaults.json
CHANGED
|
@@ -96,6 +96,17 @@
|
|
|
96
96
|
"default": "electrical.batteries.{batteryID}.voltage"
|
|
97
97
|
},
|
|
98
98
|
|
|
99
|
+
"power":
|
|
100
|
+
{
|
|
101
|
+
"unit": "W",
|
|
102
|
+
"default": "electrical.batteries.{batteryID}.power"
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
"impedance":
|
|
106
|
+
{
|
|
107
|
+
"unit": "Ohm",
|
|
108
|
+
"default": "electrical.batteries.{batteryID}.impedance"
|
|
109
|
+
},
|
|
99
110
|
"temperature":{
|
|
100
111
|
"unit": "K",
|
|
101
112
|
"default": "electrical.batteries.{batteryID}.temperature"
|
|
@@ -107,13 +118,21 @@
|
|
|
107
118
|
|
|
108
119
|
"capacity":{
|
|
109
120
|
"remaining":{
|
|
110
|
-
"unit":"
|
|
121
|
+
"unit":"C",
|
|
111
122
|
"default": "electrical.batteries.{batteryID}.capacity.remaining"
|
|
112
123
|
},
|
|
113
124
|
"actual":{
|
|
114
|
-
"unit":"
|
|
125
|
+
"unit":"C",
|
|
115
126
|
"default": "electrical.batteries.{batteryID}.capacity.actual"
|
|
116
127
|
},
|
|
128
|
+
"discharge":{
|
|
129
|
+
"unit":"KWh",
|
|
130
|
+
"default": "electrical.batteries.{batteryID}.capacity.dischargeSinceFull"
|
|
131
|
+
},
|
|
132
|
+
"charge":{
|
|
133
|
+
"unit":"KWh",
|
|
134
|
+
"default": "electrical.batteries.{batteryID}.capacity.totalCharge"
|
|
135
|
+
},
|
|
117
136
|
"stateOfCharge":{
|
|
118
137
|
"unit":"ratio",
|
|
119
138
|
"default": "electrical.batteries.{batteryID}.capacity.stateOfCharge"
|
|
Binary file
|
package/sensor_classes/JBDBMS.js
CHANGED
|
@@ -52,10 +52,10 @@ class JBDBMS extends BTSensor {
|
|
|
52
52
|
(buffer)=>{return buffer.readInt16BE(6) / 100}
|
|
53
53
|
|
|
54
54
|
this.addDefaultPath('remainingCapacity','electrical.batteries.capacity.remaining')
|
|
55
|
-
.read=(buffer)=>{return buffer.readUInt16BE(8) / 100}
|
|
55
|
+
.read=(buffer)=>{return (buffer.readUInt16BE(8) / 100)*3600}
|
|
56
56
|
|
|
57
57
|
this.addDefaultPath('capacity','electrical.batteries.capacity.actual')
|
|
58
|
-
.read=(buffer)=>{return buffer.readUInt16BE(10) / 100}
|
|
58
|
+
.read=(buffer)=>{return (buffer.readUInt16BE(10) / 100)*3600}
|
|
59
59
|
|
|
60
60
|
this.addDefaultPath('cycles','electrical.batteries.cycles' )
|
|
61
61
|
.read=(buffer)=>{return buffer.readUInt16BE(12)}
|
|
@@ -139,7 +139,7 @@ class JBDBMS extends BTSensor {
|
|
|
139
139
|
let datasize = -1
|
|
140
140
|
const timer = setTimeout(() => {
|
|
141
141
|
clearTimeout(timer)
|
|
142
|
-
reject(new Error(`Response timed out from JBDBMS device ${this.getName()}. `));
|
|
142
|
+
reject(new Error(`Response timed out (+30s) from JBDBMS device ${this.getName()}. `));
|
|
143
143
|
}, 30000);
|
|
144
144
|
|
|
145
145
|
const valChanged = async (buffer) => {
|
|
@@ -147,13 +147,12 @@ class JBDBMS extends BTSensor {
|
|
|
147
147
|
if (buffer[0]!==0xDD || buffer.length < 5 || buffer[1] !== command)
|
|
148
148
|
reject(`Invalid buffer from ${this.getName()}, not processing.`)
|
|
149
149
|
else
|
|
150
|
-
datasize=buffer[
|
|
150
|
+
datasize=buffer[3]
|
|
151
151
|
}
|
|
152
152
|
buffer.copy(result,offset)
|
|
153
|
-
if (buffer[buffer.length-1]==0x77 && offset+buffer.length-
|
|
153
|
+
if (buffer[buffer.length-1]==0x77 && offset+buffer.length-7==datasize){
|
|
154
154
|
|
|
155
155
|
result = Uint8Array.prototype.slice.call(result, 0, offset+buffer.length)
|
|
156
|
-
this.debug(result)
|
|
157
156
|
this.rxChar.removeAllListeners()
|
|
158
157
|
clearTimeout(timer)
|
|
159
158
|
if (!checkSum(result))
|
|
@@ -196,6 +195,20 @@ async getAndEmitCellVoltages(){
|
|
|
196
195
|
}
|
|
197
196
|
|
|
198
197
|
initGATTInterval(){
|
|
198
|
+
this.device.on("disconnect", ()=>{
|
|
199
|
+
if (this.isActive()) {
|
|
200
|
+
this.debug(`Device disconnected. Attempting to reconnect to ${this.getName()}`)
|
|
201
|
+
try {
|
|
202
|
+
this.deviceConnect(true).then(()=>{
|
|
203
|
+
this.debug(`Device reconnected -- ${this.getName()}`)
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
this.debug(`Error while reconnecting to ${this.getName()}`)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
this.emitGATT()
|
|
199
212
|
this.initGATTNotifications()
|
|
200
213
|
}
|
|
201
214
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
const BTSensor = require("../BTSensor");
|
|
4
|
+
|
|
5
|
+
function bytesToBase10String(bytes){
|
|
6
|
+
let s = ""
|
|
7
|
+
for (let byte of bytes){
|
|
8
|
+
s+=byte.toString(16)
|
|
9
|
+
}
|
|
10
|
+
return s
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class JunctekBMS extends BTSensor{
|
|
14
|
+
static Domain = BTSensor.SensorDomains.electrical
|
|
15
|
+
|
|
16
|
+
constructor(device, config, gattConfig) {
|
|
17
|
+
super(device, config, gattConfig)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static async identify(device){
|
|
21
|
+
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
hasGATT(){
|
|
26
|
+
return true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async initSchema(){
|
|
32
|
+
super.initSchema()
|
|
33
|
+
this.addDefaultParam("batteryID")
|
|
34
|
+
|
|
35
|
+
this.addDefaultPath("voltage","electrical.batteries.voltage")
|
|
36
|
+
|
|
37
|
+
this.addDefaultPath("current","electrical.batteries.current")
|
|
38
|
+
|
|
39
|
+
this.addDefaultPath("power","electrical.batteries.power")
|
|
40
|
+
|
|
41
|
+
this.addDefaultPath("cycles",'electrical.batteries.cycles')
|
|
42
|
+
|
|
43
|
+
this.addDefaultPath("soc",'electrical.batteries.capacity.stateOfCharge')
|
|
44
|
+
this.addDefaultPath("remainingAh",'electrical.batteries.capacity.remaining')
|
|
45
|
+
this.addDefaultPath("timeRemaining",'electrical.batteries.capacity.timeRemaining')
|
|
46
|
+
this.addDefaultPath("discharge",'electrical.batteries.capacity.dischargeSinceFull')
|
|
47
|
+
this.addDefaultPath("charge",'electrical.batteries.capacity.charge')
|
|
48
|
+
this.addDefaultPath("temperature",'electrical.batteries.temperature')
|
|
49
|
+
this.addDefaultPath("actualCapacity",'electrical.batteries.capacity.actual')
|
|
50
|
+
this.addMetadatum('impedance','mOhm', 'measured resistance')
|
|
51
|
+
.default='electrical.batteries.{batteryID}.impedance'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
emitFrom(buffer){
|
|
55
|
+
var value=[], chargeDirection = 1
|
|
56
|
+
|
|
57
|
+
for (let byte of buffer){
|
|
58
|
+
if (byte==0xBB) {
|
|
59
|
+
value=[]
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
if (byte==0xEE){
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
value.push[byte]
|
|
67
|
+
|
|
68
|
+
if (parseInt(byte.toString(16))==NaN){ //not a base-10 number. seriously. that's how Junctek does this.
|
|
69
|
+
const v = parseInt(bytesToBase10String(value))
|
|
70
|
+
switch (byte){
|
|
71
|
+
case 0xC0:{
|
|
72
|
+
emit("voltage",v/100)
|
|
73
|
+
}
|
|
74
|
+
case 0xC1:{
|
|
75
|
+
emit("current",(v/100)*chargeDirection)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case 0xD1:{
|
|
79
|
+
if (byte==0)
|
|
80
|
+
chargeDirection=-1
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case 0xD2:{
|
|
84
|
+
emit("remainingAh",v/1000)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case 0xD3:{
|
|
88
|
+
emit("discharge",v/100000)
|
|
89
|
+
}
|
|
90
|
+
case 0xD4:{
|
|
91
|
+
emit("charge",v/100000)
|
|
92
|
+
}
|
|
93
|
+
case 0xD6:{
|
|
94
|
+
emit("timeRemaining",v*60)
|
|
95
|
+
}
|
|
96
|
+
case 0xD7:{
|
|
97
|
+
emit("impedance",v/100)
|
|
98
|
+
}
|
|
99
|
+
case 0xD8:{
|
|
100
|
+
emit("power",(v/100)*chargeDirection)
|
|
101
|
+
}
|
|
102
|
+
case 0xD9:{
|
|
103
|
+
emit("temperature",v + 173.15) //assume C not F
|
|
104
|
+
}
|
|
105
|
+
case 0xB1:{
|
|
106
|
+
emit("capacityActual",v /10 )
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
emitGATT(){
|
|
113
|
+
this.battCharacteristic.readValue()
|
|
114
|
+
.then((buffer)=>{
|
|
115
|
+
this.emitFrom(buffer)
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
initGATTConnection(){
|
|
119
|
+
return new Promise((resolve,reject )=>{ this.deviceConnect().then(async ()=>{
|
|
120
|
+
if (!this.gattServer) {
|
|
121
|
+
this.gattServer = await this.device.gatt()
|
|
122
|
+
this.battService = await this.gattServer.getPrimaryService("0000fff0-0000-1000-8000-00805f9b34fb")
|
|
123
|
+
this.battCharacteristic = await this.battService.getCharacteristic("0000ffe1-0000-1000-8000-00805f9b34fb")
|
|
124
|
+
}
|
|
125
|
+
resolve(this)
|
|
126
|
+
}) .catch((e)=>{ reject(e.message) }) })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
initGATTNotifications() {
|
|
130
|
+
Promise.resolve(this.battCharacteristic.startNotifications().then(()=>{
|
|
131
|
+
this.battCharacteristic.on('valuechanged', buffer => {
|
|
132
|
+
this.emitFrom(buffer)
|
|
133
|
+
})
|
|
134
|
+
}))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async stopListening(){
|
|
138
|
+
super.stopListening()
|
|
139
|
+
if (this.battCharacteristic && await this.battCharacteristic.isNotifying()) {
|
|
140
|
+
await this.battCharacteristic.stopNotifications()
|
|
141
|
+
this.battCharacteristic=null
|
|
142
|
+
}
|
|
143
|
+
if (await this.device.isConnected()){
|
|
144
|
+
await this.device.disconnect()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
module.exports=JunctekBMS
|
|
@@ -78,7 +78,7 @@ class KilovaultHLXPlus extends BTSensor{
|
|
|
78
78
|
this.addDefaultPath("cycles",'electrical.batteries.cycles')
|
|
79
79
|
.read=(buffer)=>{return buffer.readInt16LE(12)}
|
|
80
80
|
|
|
81
|
-
this.addDefaultPath("soc",'electrical.batteries.capacity
|
|
81
|
+
this.addDefaultPath("soc",'electrical.batteries.capacity.stateOfCharge')
|
|
82
82
|
.read=(buffer)=>{return buffer.readInt16LE(14)}
|
|
83
83
|
|
|
84
84
|
this.addDefaultPath("temperature",'electrical.batteries.temperature')
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
|
|
2
|
+
function arduinoDateDecode (elapsedSeconds) {
|
|
3
|
+
const date = new Date("2000-01-01")
|
|
4
|
+
date.setTime(date.getTime() + 1000 * elapsedSeconds)
|
|
5
|
+
return date
|
|
6
|
+
}
|
|
7
|
+
const errors= {
|
|
8
|
+
0: "Undefined",
|
|
9
|
+
1: "Invalid Battery",
|
|
10
|
+
2: "Overheat",
|
|
11
|
+
3: "Overheat Shutdown",
|
|
12
|
+
4: "Generator lead 1 disconnected",
|
|
13
|
+
5: "Generator lead 2 disconnected",
|
|
14
|
+
6: "Generator lead 3 disconnected",
|
|
15
|
+
7: "Short Circuit"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const eventTypes = {
|
|
19
|
+
0: "Reboot",
|
|
20
|
+
1: "Invalid Battery",
|
|
21
|
+
2: "Overheat",
|
|
22
|
+
3: "Overheat Shutdown",
|
|
23
|
+
4: "Generator lead 1 disconnected",
|
|
24
|
+
5: "Generator lead 2 disconnected",
|
|
25
|
+
6: "Generator lead 3 disconnected",
|
|
26
|
+
7: "Short Circuit",
|
|
27
|
+
255: "Debug"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const states= ["Charging Needed", "Charging", "Floating", "Idle"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
const BTSensor = require("../BTSensor");
|
|
34
|
+
class RemoranWave3 extends BTSensor{
|
|
35
|
+
static Domain = BTSensor.SensorDomains.electrical
|
|
36
|
+
serviceUUID = "81d08df0-c0f8-422a-9d9d-e4379bb1ea3b"
|
|
37
|
+
info1CharUUID = "62c91222-fafe-4f6e-95f0-afc02bd19f2e"
|
|
38
|
+
info2CharUUID = "f5d12d34-4390-486c-b906-24ea8906af71"
|
|
39
|
+
eventUUID = "f12a8e25-59f7-42f2-b7ae-ba96fb25c13c"
|
|
40
|
+
|
|
41
|
+
static async identify(device){
|
|
42
|
+
|
|
43
|
+
const name = await this.getDeviceProp(device,"Name")
|
|
44
|
+
if (name == 'Remoran Wave.3')
|
|
45
|
+
return this
|
|
46
|
+
else
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
hasGATT(){
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
usingGATT(){
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
emitInfo1Data(buffer){
|
|
56
|
+
this.debug(`emitting info 1 data`)
|
|
57
|
+
if (buffer.length < 20) {
|
|
58
|
+
app.debug(`Bad buffer size ${buffer.length}. Buffer size must be 20 bytes or more.`)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
this.emit("versionNumber", buffer.readUInt8(0))
|
|
62
|
+
const errors = buffer.readUInt8(2)
|
|
63
|
+
const errorState = []
|
|
64
|
+
for (var i = 0; i < 8; ++i) {
|
|
65
|
+
var c = 1 << i;
|
|
66
|
+
errors & c && errorState.push(errors[i])
|
|
67
|
+
}
|
|
68
|
+
this.emit("errors", errorState)
|
|
69
|
+
this.emit("state", states[buffer.readUInt8(3)])
|
|
70
|
+
this.emit("rpm", buffer.readUInt32LE(4))
|
|
71
|
+
this.emit( "voltage" , buffer.readFloatLE(8))
|
|
72
|
+
this.emit("current", buffer.readFloatLE(12))
|
|
73
|
+
this.emit( "power", buffer.readFloatLE(16))
|
|
74
|
+
|
|
75
|
+
if (buffer.length > 23) {
|
|
76
|
+
this.emit( "temp", ((buffer.readFloatLE(20))+273.15))
|
|
77
|
+
this.emit( "uptime", buffer.readUInt32LE(24))
|
|
78
|
+
if (versionNumber>1 && buffer.size > 31) {
|
|
79
|
+
this.emit("energy", buffer.readFloatLE(32))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
emitInfo2Data(buffer){
|
|
85
|
+
this.debug(`emitting info 2 data`)
|
|
86
|
+
|
|
87
|
+
if (buffer.size < 12) {
|
|
88
|
+
app.debug(`Bad buffer size ${buffer.length}. Buffer size must be 12 bytes or more.`)
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
this.emit("versionNumber", buffer.readUInt8(0))
|
|
92
|
+
this.emit("temp", ((buffer.readFloatLE(4))+273.15))
|
|
93
|
+
this.emit("uptime", buffer.readUInt32LE(8))
|
|
94
|
+
this.emit("lastBootTime", arduinoDateDecode(buffer.readUInt32LE(12)))
|
|
95
|
+
this.emit("energy", buffer.readFloatLE(16))
|
|
96
|
+
}
|
|
97
|
+
emitEventData(buffer){
|
|
98
|
+
this.debug(`emitting event data`)
|
|
99
|
+
if (buffer.length < 14) {
|
|
100
|
+
this.debug(buffer)
|
|
101
|
+
app.debug(`Bad buffer size ${buffer.length}. Buffer size must be 14 bytes or more.`)
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
const eventType = buffer.readUInt16LE(8)
|
|
105
|
+
var eventDesc = eventType.toString()
|
|
106
|
+
if (Object.hasOwn(eventTypes,eventType))
|
|
107
|
+
eventDesc = eventTypes[eventType]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
this.emit("event",
|
|
111
|
+
{
|
|
112
|
+
firstDate: arduinoDateDecode(buffer.readUInt32LE(0)),
|
|
113
|
+
lastDate: arduinoDateDecode(buffer.readUInt32LE(4)),
|
|
114
|
+
eventType: eventType,
|
|
115
|
+
count: buffer.readUInt16LE(10),
|
|
116
|
+
index: buffer.readUInt16LE(12),
|
|
117
|
+
eventDesc: eventDesc
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
emitGATT(){
|
|
122
|
+
this.info1Characteristic.readValue()
|
|
123
|
+
.then((buffer)=>
|
|
124
|
+
this.emitInfo1Data( buffer)
|
|
125
|
+
)
|
|
126
|
+
this.info2Characteristic.readValue()
|
|
127
|
+
.then((buffer)=>
|
|
128
|
+
this.emitInfo2Data(buffer)
|
|
129
|
+
)
|
|
130
|
+
this.eventCharacteristic.readValue()
|
|
131
|
+
.then((buffer)=>
|
|
132
|
+
this.emitEventData(buffer)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
initSchema(){
|
|
137
|
+
super.initSchema()
|
|
138
|
+
this.addDefaultParam("id")
|
|
139
|
+
.default="RemoranWave3"
|
|
140
|
+
|
|
141
|
+
this.getGATTParams()["useGATT"].default=true
|
|
142
|
+
|
|
143
|
+
this.addMetadatum('errorCodes','', 'charger error codes (array)')
|
|
144
|
+
.default= "electrical.chargers.{id}.errorCodes"
|
|
145
|
+
|
|
146
|
+
this.addMetadatum('state','', 'charger state')
|
|
147
|
+
.default= "electrical.chargers.{id}.state"
|
|
148
|
+
|
|
149
|
+
this.addMetadatum('voltage','V', 'battery voltage')
|
|
150
|
+
.default= "electrical.chargers.{id}.battery.voltage"
|
|
151
|
+
|
|
152
|
+
this.addMetadatum('current','A', 'battery current')
|
|
153
|
+
.default= "electrical.chargers.{id}.battery.current"
|
|
154
|
+
|
|
155
|
+
this.addMetadatum('power','W', 'battery power')
|
|
156
|
+
.default= "electrical.chargers.{id}.battery.power"
|
|
157
|
+
|
|
158
|
+
this.addMetadatum('temp', 'K', 'charger temperature')
|
|
159
|
+
.default= "electrical.chargers.{id}.temperature"
|
|
160
|
+
|
|
161
|
+
this.addMetadatum('energy', 'wh', 'energy created today in Wh')
|
|
162
|
+
.default= "electrical.chargers.{id}.energy"
|
|
163
|
+
|
|
164
|
+
this.addMetadatum('event', '', 'charger event')
|
|
165
|
+
.default= "electrical.chargers.{id}.event"
|
|
166
|
+
|
|
167
|
+
this.addMetadatum('lastBootTime', 's', 'last boot time')
|
|
168
|
+
.default= "electrical.chargers.{id}.lastBootTime"
|
|
169
|
+
|
|
170
|
+
this.addMetadatum('rpm', '', 'revolutions per minute')
|
|
171
|
+
.default= "sensors.{macAndName}.rpm"
|
|
172
|
+
|
|
173
|
+
this.addMetadatum('uptime', 's', 'charger/sensor uptime')
|
|
174
|
+
.default= "sensors.{macAndName}.uptime"
|
|
175
|
+
|
|
176
|
+
this.addMetadatum('versionNumber', '', 'charger/sensor version number')
|
|
177
|
+
.default= "sensors.{macAndName}.version"
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
initGATTConnection(){
|
|
183
|
+
return new Promise((resolve,reject )=>{ this.deviceConnect().then(async ()=>{
|
|
184
|
+
if (!this.gattServer) {
|
|
185
|
+
this.gattServer = await this.device.gatt()
|
|
186
|
+
this.service = await this.gattServer.getPrimaryService(this.serviceUUID)
|
|
187
|
+
this.info1Characteristic = await this.service.getCharacteristic(this.info1CharUUID)
|
|
188
|
+
this.info2Characteristic = await this.service.getCharacteristic(this.info2CharUUID)
|
|
189
|
+
this.eventCharacteristic = await this.service.getCharacteristic(this.eventUUID)
|
|
190
|
+
resolve(this)
|
|
191
|
+
}}) .catch((e)=>{ this.debug(e); reject(e.message) }) })
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
initGATTNotifications() {
|
|
195
|
+
Promise.resolve(this.info1Characteristic.startNotifications().then(()=>{
|
|
196
|
+
this.info1Characteristic.on('valuechanged', buffer => {
|
|
197
|
+
this.emitInfo1Data(buffer)
|
|
198
|
+
})
|
|
199
|
+
}))
|
|
200
|
+
Promise.resolve(this.info2Characteristic.startNotifications().then(()=>{
|
|
201
|
+
this.info2Characteristic.on('valuechanged', buffer => {
|
|
202
|
+
this.emitInfo2Data(buffer)
|
|
203
|
+
})
|
|
204
|
+
}))
|
|
205
|
+
Promise.resolve(this.eventCharacteristic.startNotifications().then(()=>{
|
|
206
|
+
this.eventCharacteristic.on('valuechanged', buffer => {
|
|
207
|
+
this.emitEventData(buffer)
|
|
208
|
+
})
|
|
209
|
+
}))
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async stopNotifications(characteristic){
|
|
213
|
+
if (characteristic && await characteristic.isNotifying()) {
|
|
214
|
+
await characteristic.stopNotifications()
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async stopListening(){
|
|
218
|
+
super.stopListening()
|
|
219
|
+
await this.stopNotifications(this?.info1Characteristic)
|
|
220
|
+
await this.stopNotifications(this?.info2Characteristic)
|
|
221
|
+
await this.stopNotifications(this?.eventCharacteristic)
|
|
222
|
+
if (await this.device.isConnected()){
|
|
223
|
+
await this.device.disconnect()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
module.exports=RemoranWave3
|
|
228
|
+
|
|
@@ -57,9 +57,6 @@ class RenogySensor extends BTSensor{
|
|
|
57
57
|
)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
emitGATT(){
|
|
61
|
-
}
|
|
62
|
-
|
|
63
60
|
getModelName(){
|
|
64
61
|
return this?.modelID??`${this.constructor.name} Unknown model`
|
|
65
62
|
}
|
|
@@ -86,7 +83,6 @@ class RenogySensor extends BTSensor{
|
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
initGATTInterval(){
|
|
89
|
-
this.emitGATT()
|
|
90
86
|
this.intervalID = setInterval(()=>{
|
|
91
87
|
this.emitGATT()
|
|
92
88
|
}, 1000*(this?.pollFreq??60) )
|
|
@@ -121,7 +117,8 @@ class RenogySensor extends BTSensor{
|
|
|
121
117
|
async stopListening(){
|
|
122
118
|
super.stopListening()
|
|
123
119
|
|
|
124
|
-
|
|
120
|
+
if (this.readChar)
|
|
121
|
+
await this.readChar.stopNotifications()
|
|
125
122
|
|
|
126
123
|
if (await this.device.isConnected()){
|
|
127
124
|
await this.device.disconnect()
|
|
@@ -6,53 +6,113 @@ const RenogySensor = require("./Renogy/RenogySensor.js");
|
|
|
6
6
|
const RC=require("./Renogy/RenogyConstants.js")
|
|
7
7
|
|
|
8
8
|
class RenogyRoverClient extends RenogySensor {
|
|
9
|
-
|
|
9
|
+
/*
|
|
10
|
+
"batteryType": "electrical.charger.battery.type",
|
|
11
|
+
"batteryPercentage": "electrical.charger.battery.charge",
|
|
12
|
+
"batteryVoltage": "electrical.charger.battery.voltage",
|
|
13
|
+
"batteryCurrent": "electrical.charger.battery.current",
|
|
14
|
+
"controllerTemperature": "electrical.charger.temperature",
|
|
15
|
+
"batteryTemperature": "electrical.charger.battery.temperature",
|
|
16
|
+
"loadVoltage": "electrical.charger.load.voltage",
|
|
17
|
+
"loadCurrent": "electrical.charger.load.current",
|
|
18
|
+
"loadPower": "electrical.charger.load.power",
|
|
19
|
+
"pvVoltage": "electrical.charger.solar.voltage",
|
|
20
|
+
"pvCurrent": "electrical.charger.solar.current",
|
|
21
|
+
"pvPower": "electrical.charger.solar.power",
|
|
22
|
+
"maxChargingPowerToday": "electrical.charger.today.max",
|
|
23
|
+
"maxDischargingPowerToday": "electrical.charger.discharging.maximum",
|
|
24
|
+
"chargingAmpHoursToday": "electrical.charger.charged.today",
|
|
25
|
+
"powerGenerationToday": "electrical.charger.power.today",
|
|
26
|
+
"powerGenerationTotal": "electrical.charger.power.total",
|
|
27
|
+
"loadStatus": "electrical.charger.load.status",
|
|
28
|
+
"chargingStatus": "electrical.charger.status"
|
|
29
|
+
*/
|
|
10
30
|
|
|
11
31
|
initSchema(){
|
|
12
32
|
//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']
|
|
13
33
|
super.initSchema()
|
|
14
34
|
this.addMetadatum('batteryType', '', "battery type")
|
|
35
|
+
.default="electrical.chargers.{id}.battery.type"
|
|
15
36
|
this.addMetadatum('batteryPercentage', 'ratio', "battery percentage",
|
|
16
37
|
(buffer)=>{return buffer.readUInt16BE(3) })
|
|
38
|
+
.default="electrical.chargers.{id}.battery.soc"
|
|
39
|
+
|
|
17
40
|
this.addMetadatum('batteryVoltage', 'V', "battery voltage",
|
|
18
41
|
(buffer)=>{return buffer.readUInt16BE((5))/10})
|
|
42
|
+
.default="electrical.chargers.{id}.battery.voltage"
|
|
43
|
+
|
|
19
44
|
this.addMetadatum('batteryCurrent', 'A', 'battery current',
|
|
20
45
|
(buffer)=>{return buffer.readUInt16BE((7))/100})
|
|
46
|
+
.default="electrical.chargers.{id}.battery.current"
|
|
47
|
+
|
|
21
48
|
this.addMetadatum('controllerTemperature', 'K', 'controller temperature',
|
|
22
49
|
(buffer)=>{return buffer.readInt8((9))+273.15})
|
|
50
|
+
.default="electrical.chargers.{id}.controller.temperature"
|
|
51
|
+
|
|
23
52
|
this.addMetadatum('batteryTemperature', 'K', 'battery temperature',
|
|
24
53
|
(buffer)=>{return buffer.readInt8((10))+273.15})
|
|
54
|
+
.default="electrical.chargers.{id}.battery.temperature"
|
|
55
|
+
|
|
25
56
|
this.addMetadatum('loadVoltage', 'V', 'load voltage',
|
|
26
57
|
(buffer)=>{return buffer.readUInt16BE((11))/10})
|
|
58
|
+
.default="electrical.chargers.{id}.load.voltage"
|
|
59
|
+
|
|
27
60
|
this.addMetadatum('loadCurrent', 'A', 'load current',
|
|
28
61
|
(buffer)=>{return buffer.readUInt16BE((13))/100})
|
|
62
|
+
.default="electrical.chargers.{id}.load.current"
|
|
29
63
|
this.addMetadatum('loadPower', 'W', 'load power',
|
|
30
64
|
(buffer)=>{return buffer.readUInt16BE((15))})
|
|
65
|
+
.default="electrical.chargers.{id}.load.power"
|
|
31
66
|
this.addMetadatum('pvVoltage', 'V', 'pv voltage',
|
|
32
67
|
(buffer)=>{return buffer.readUInt16BE((17))/10})
|
|
68
|
+
.default="electrical.chargers.{id}.solar.voltage"
|
|
33
69
|
this.addMetadatum('pvCurrent', 'A', 'pv current',
|
|
34
70
|
(buffer)=>{return buffer.readUInt16BE((19))/100})
|
|
71
|
+
.default="electrical.chargers.{id}.solar.current"
|
|
35
72
|
this.addMetadatum('pvPower', 'W', 'pv power',
|
|
36
73
|
(buffer)=>{return buffer.readUInt16BE(21)})
|
|
74
|
+
.default="electrical.chargers.{id}.solar.power"
|
|
37
75
|
this.addMetadatum('maxChargingPowerToday', 'W', 'max charging power today',
|
|
38
76
|
(buffer)=>{return buffer.readUInt16BE(33)})
|
|
77
|
+
.default="electrical.chargers.{id}.charge.max.today"
|
|
39
78
|
this.addMetadatum('maxDischargingPowerToday', 'W', 'max discharging power today',
|
|
40
79
|
(buffer)=>{return buffer.readUInt16BE(35)})
|
|
80
|
+
.default="electrical.chargers.{id}.discharge.max.today"
|
|
41
81
|
this.addMetadatum('chargingAmpHoursToday', 'Ah', 'charging amp hours today',
|
|
42
82
|
(buffer)=>{return buffer.readUInt16BE(37)})
|
|
83
|
+
.default="electrical.chargers.{id}.charge.ampHours.today"
|
|
84
|
+
|
|
43
85
|
this.addMetadatum('dischargingAmpHoursToday', 'Ah', 'discharging amp hours today',
|
|
44
86
|
(buffer)=>{return buffer.readUInt16BE(39)})
|
|
87
|
+
.default="electrical.chargers.{id}.discharge.ampHours.today"
|
|
88
|
+
|
|
45
89
|
this.addMetadatum('powerGenerationToday', 'W', 'power generation today',
|
|
46
90
|
(buffer)=>{return buffer.readUInt16BE(41)})
|
|
91
|
+
.default="electrical.chargers.{id}.power.generated.today"
|
|
92
|
+
|
|
47
93
|
this.addMetadatum('powerConsumptionToday', 'W', 'power consumption today',
|
|
48
94
|
(buffer)=>{return buffer.readUInt16BE(43)})
|
|
95
|
+
.default="electrical.chargers.{id}.power.consumed.today"
|
|
96
|
+
|
|
49
97
|
this.addMetadatum('powerGenerationTotal', 'W', 'power generation total',
|
|
50
98
|
(buffer)=>{return buffer.readUInt32BE(59)})
|
|
99
|
+
.default="electrical.chargers.{id}.power.generated.total"
|
|
100
|
+
|
|
51
101
|
this.addMetadatum('loadStatus', '', 'load status',
|
|
52
102
|
(buffer)=>{return RC.LOAD_STATE[buffer.readUInt8(67)>>7]})
|
|
103
|
+
.default="electrical.chargers.{id}.load.status"
|
|
53
104
|
|
|
54
105
|
this.addMetadatum('chargingStatus', '', 'charging status',
|
|
55
|
-
(buffer)=>{
|
|
106
|
+
(buffer)=>{
|
|
107
|
+
const cs = buffer.readUInt8(68)
|
|
108
|
+
if (Object.hasOwn(RC.CHARGING_STATE,cs))
|
|
109
|
+
return RC.CHARGING_STATE[cs]
|
|
110
|
+
else
|
|
111
|
+
return null
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
.default="electrical.chargers.{id}.charge.status"
|
|
115
|
+
|
|
56
116
|
}
|
|
57
117
|
|
|
58
118
|
retrieveDeviceID(){
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Class to support Batteries with embedded Shenzhen Li-ion Battery Bodyguard Technology BMS
|
|
3
|
-
* Brands include Redodo, Litime PowerQueen and possibly others
|
|
3
|
+
* Brands include Redodo, Litime, PowerQueen and possibly others
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
|
|
@@ -34,19 +34,21 @@ class ShenzhenLiONBMS extends BTSensor{
|
|
|
34
34
|
static async identify(device){
|
|
35
35
|
return null
|
|
36
36
|
}
|
|
37
|
+
async sendCommand(cmd){
|
|
38
|
+
await this.txCharacteristic.writeValueWithResponse( Buffer.from(cmd))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async queryBatteryCommand(){
|
|
42
|
+
await this.sendCommand(this.constructor.Commands.query_battery_status)
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
hasGATT(){
|
|
38
46
|
return true
|
|
39
47
|
}
|
|
40
48
|
usingGATT(){
|
|
41
49
|
return true
|
|
42
50
|
}
|
|
43
|
-
emitGATT(){
|
|
44
|
-
this.characteristic.readValue()
|
|
45
|
-
.then((buffer)=>
|
|
46
|
-
this.emitValuesFrom( buffer)
|
|
47
|
-
)
|
|
48
51
|
|
|
49
|
-
}
|
|
50
52
|
initSchema(){
|
|
51
53
|
super.initSchema()
|
|
52
54
|
this.getGATTParams()["useGATT"].default=true
|
|
@@ -82,7 +84,7 @@ class ShenzhenLiONBMS extends BTSensor{
|
|
|
82
84
|
|
|
83
85
|
for(let cellNum=0; cellNum < this?.numberOfCells??4; cellNum++) {
|
|
84
86
|
this.addMetadatum(`cell${cellNum+1}Voltage`,'V', `cell #${cellNum+1} voltage`,
|
|
85
|
-
(buff)=>{return buff.readInt16LE(16+(cellNum*2)) })
|
|
87
|
+
(buff)=>{return buff.readInt16LE(16+(cellNum*2)) /1000})
|
|
86
88
|
.default=`electrical.batteries.{batteryID}.cells.${cellNum+1}.voltage`
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -97,11 +99,11 @@ class ShenzhenLiONBMS extends BTSensor{
|
|
|
97
99
|
(buff)=>{return buff.readInt16LE(54) + 273.15})
|
|
98
100
|
.default="electrical.batteries.{batteryID}.bms.temperature"
|
|
99
101
|
|
|
100
|
-
this.addDefaultPath('
|
|
101
|
-
.read=(buff)=>{return
|
|
102
|
+
this.addDefaultPath('remaining','electrical.batteries.capacity.remaining')
|
|
103
|
+
.read=(buff)=>{return (buff.readUInt16LE(62)/100)*3600}
|
|
102
104
|
|
|
103
|
-
this.addDefaultPath('
|
|
104
|
-
.read=(buff)=>{return
|
|
105
|
+
this.addDefaultPath('actual','electrical.batteries.capacity.actual')
|
|
106
|
+
.read=(buff)=>{return (buff.readUInt16LE(64)/100)*3600}
|
|
105
107
|
|
|
106
108
|
this.addMetadatum('heat','', 'discharge disabled due to app button = 00000080, heater_error = 00000002',
|
|
107
109
|
(buff)=>{return buff.slice(68,72).reverse().join("")})
|
|
@@ -138,7 +140,6 @@ class ShenzhenLiONBMS extends BTSensor{
|
|
|
138
140
|
this.addDefaultPath( 'soc',"electrical.batteries.capacity.stateOfCharge")
|
|
139
141
|
.read=(buff)=>{return buff.readUInt16LE(90)/100}
|
|
140
142
|
|
|
141
|
-
|
|
142
143
|
this.getJSONSchema().properties.params.required=["batteryID", "numberOfCells" ]
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -155,16 +156,25 @@ class ShenzhenLiONBMS extends BTSensor{
|
|
|
155
156
|
resolve(this)
|
|
156
157
|
})})
|
|
157
158
|
}
|
|
159
|
+
|
|
160
|
+
async initGATTInterval(){
|
|
161
|
+
await this.initGATTNotifications()
|
|
162
|
+
}
|
|
163
|
+
|
|
158
164
|
async initGATTNotifications() {
|
|
159
|
-
await this.txCharacteristic.writeValue( Buffer.from(this.constructor.Commands.query_battery_status))
|
|
160
165
|
await this.rxCharacteristic.startNotifications()
|
|
161
166
|
this.rxCharacteristic.on('valuechanged', buffer => {
|
|
162
167
|
this.emitValuesFrom(buffer)
|
|
163
168
|
})
|
|
169
|
+
this.intervalID=setInterval(
|
|
170
|
+
async ()=>{
|
|
171
|
+
await this.queryBatteryCommand()
|
|
172
|
+
}, (this?.pollFreq??10)*1000
|
|
173
|
+
)
|
|
164
174
|
}
|
|
165
175
|
|
|
166
176
|
async stopListening(){
|
|
167
|
-
super.stopListening()
|
|
177
|
+
super.stopListening() //clears IntervalID as it happens
|
|
168
178
|
if (this.rxCharacteristic && await this.rxCharacteristic.isNotifying()) {
|
|
169
179
|
await this.rxCharacteristic.stopNotifications()
|
|
170
180
|
this.rxCharacteristic=null
|
|
@@ -174,4 +184,4 @@ class ShenzhenLiONBMS extends BTSensor{
|
|
|
174
184
|
}
|
|
175
185
|
}
|
|
176
186
|
}
|
|
177
|
-
module.exports=ShenzhenLiONBMS
|
|
187
|
+
module.exports=ShenzhenLiONBMS
|
|
@@ -130,10 +130,12 @@ function sleep(x) {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
getDescription(){
|
|
133
|
-
//return `<img src="https://www.victronenergy.com/_next/image?url=https%3A%2F%2Fwww.victronenergy.com%2Fupload%2Fproducts%2FSmartShunt%2520500_nw.png&w=1080&q=70"" height="150"
|
|
133
|
+
//return `<img src="https://www.victronenergy.com/_next/image?url=https%3A%2F%2Fwww.victronenergy.com%2Fupload%2Fproducts%2FSmartShunt%2520500_nw.png&w=1080&q=70"" height="150" align=”top” ></img>`
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
return `<img src="../bt-sensors-plugin-sk/images/${this.getImage()}"
|
|
136
|
+
return `<img src="../bt-sensors-plugin-sk/images/${this.getImage()}" width="200" style="float: left;
|
|
137
|
+
margin: 20px;" ></img>
|
|
138
|
+
To get the encryption key for your device, follow the instructions <a href=https://communityarchive.victronenergy.com/questions/187303/victron-bluetooth-advertising-protocol.html target="_victron_encrypt">here</a>`
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
}
|
|
@@ -56,7 +56,7 @@ class VictronBatteryMonitor extends VictronSensor{
|
|
|
56
56
|
|
|
57
57
|
const alarmMD = this.addMetadatum('alarm','', 'alarm',
|
|
58
58
|
(buff,offset=0)=>{return buff.readInt16LE(offset)})
|
|
59
|
-
alarmMD.default='
|
|
59
|
+
alarmMD.default='electrical.batteries.{batteryID}.alarm'
|
|
60
60
|
alarmMD.notify=true
|
|
61
61
|
|
|
62
62
|
this.addMetadatum( 'consumed','Ah', 'amp-hours consumed',
|
|
@@ -196,7 +196,7 @@ class VictronBatteryMonitor extends VictronSensor{
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
getDescription(){
|
|
199
|
-
return super.getDescription()
|
|
199
|
+
return `${super.getDescription()}.<p><p>After setting the encryption key, Save and reselect to configure the value of the Aux field (Secondary Battery, Midpoint or Battery Temperature)`
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
async stopListening(){
|
|
@@ -10,7 +10,7 @@ class VictronDCDCConverter extends VictronSensor{
|
|
|
10
10
|
|
|
11
11
|
initSchema(){
|
|
12
12
|
super.initSchema()
|
|
13
|
-
this.
|
|
13
|
+
this.addDefaultParam("id")
|
|
14
14
|
|
|
15
15
|
this.addMetadatum('deviceState','', 'device state',
|
|
16
16
|
(buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
|
|
@@ -24,9 +24,11 @@ class VictronLynxSmartBMS extends VictronSensor{
|
|
|
24
24
|
|
|
25
25
|
initSchema(){
|
|
26
26
|
super.initSchema()
|
|
27
|
+
this.addDefaultParam("batteryID")
|
|
27
28
|
this.addMetadatum('error','', 'error code',
|
|
28
29
|
(buff)=>{return buff.readUInt8(0)})
|
|
29
|
-
|
|
30
|
+
.default="electrical.batteries.{batteryID}.errorCode"
|
|
31
|
+
|
|
30
32
|
this.addDefaultPath('ttg','electrical.batteries.capacity.timeRemaining')
|
|
31
33
|
.read=(buff)=>{return this.NaNif(buff.readUInt16LE(1),0xFFFF)*60}
|
|
32
34
|
this.addDefaultPath('voltage','electrical.batteries.voltage')
|
|
@@ -36,10 +38,12 @@ class VictronLynxSmartBMS extends VictronSensor{
|
|
|
36
38
|
|
|
37
39
|
this.addMetadatum('ioStatus','','IO Status', //TODO
|
|
38
40
|
(buff)=>{return buff.readUInt16LE(7)})
|
|
41
|
+
.default="electrical.batteries.{batteryID}.IOStatus"
|
|
39
42
|
|
|
40
43
|
this.addMetadatum('warningsAndAlarms','','warnings and alarms')
|
|
41
44
|
|
|
42
45
|
this.addDefaultPath('soc','electrical.batteries.capacity.stateOfCharge')
|
|
46
|
+
|
|
43
47
|
this.addMetadatum('consumedAh','Ah','amp-hours consumed')
|
|
44
48
|
.default="electrical.batteries.{batteryID}.capacity.ampHoursConsumed"
|
|
45
49
|
|
|
@@ -34,7 +34,7 @@ class VictronOrionXS extends VictronSensor{
|
|
|
34
34
|
(buff)=>{return this.NaNif(buff.readUInt16LE(8),0xFFFF)/10})
|
|
35
35
|
.default="electrical.chargers.{id}.input.current"
|
|
36
36
|
this.addMetadatum('deviceOffReason','', 'device off reason',
|
|
37
|
-
(buff)=>{return VC.OffReasons(buff.readUInt32BE(10))})
|
|
37
|
+
(buff)=>{return VC.OffReasons.get(buff.readUInt32BE(10))})
|
|
38
38
|
.default="electrical.chargers.{id}.offReason"
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -50,7 +50,7 @@ class VictronSmartLithium extends VictronSensor{
|
|
|
50
50
|
(buff)=>{return buff.readUInt16LE(4)})
|
|
51
51
|
.default="electrical.batteries.{batteryID}.errors"
|
|
52
52
|
|
|
53
|
-
for (let i=0;i
|
|
53
|
+
for (let i=0;i<8; i++){
|
|
54
54
|
this.addMetadatum(`cell${i+1}Voltage`,'V', `cell ${i+1} voltage`)
|
|
55
55
|
.default=`electrical.batteries.{batteryID}.cell${i+1}.voltage`
|
|
56
56
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
diff --git a/sensor_classes/VictronSmartLithium.js b/sensor_classes/VictronSmartLithium.js
|
|
2
|
+
index 0ed70ad..2988374 100644
|
|
3
|
+
--- a/sensor_classes/VictronSmartLithium.js
|
|
4
|
+
+++ b/sensor_classes/VictronSmartLithium.js
|
|
5
|
+
@@ -50,7 +50,7 @@ class VictronSmartLithium extends VictronSensor{
|
|
6
|
+
(buff)=>{return buff.readUInt16LE(4)})
|
|
7
|
+
.default="electrical.batteries.{batteryID}.errors"
|
|
8
|
+
|
|
9
|
+
- for (let i=0;i++;i<8){
|
|
10
|
+
+ for (let i=0;i<8; i++){
|
|
11
|
+
this.addMetadatum(`cell${i+1}Voltage`,'V', `cell ${i+1} voltage`)
|
|
12
|
+
.default=`electrical.batteries.{batteryID}.cell${i+1}.voltage`
|
|
13
|
+
}
|