signalk-rec-bms 1.0.2 → 1.1.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/README.md +32 -5
- package/package.json +11 -4
- package/plugin/index.js +67 -0
- package/plugin/lib/connection.js +19 -0
- package/plugin/lib/schema.js +59 -0
- package/plugin/lib/serial/Layer2.js +69 -0
- package/plugin/lib/serial/atlas.json +553 -0
- package/plugin/lib/serial/engine.js +89 -0
- package/plugin/lib/serial/hexer.js +60 -0
- package/plugin/lib/serial/modules/abms.js +297 -0
- package/plugin/lib/serial/modules/array.js +393 -0
- package/plugin/lib/serial/modules/bat.js +124 -0
- package/plugin/lib/serial/modules/cur.js +96 -0
- package/plugin/lib/serial/modules/derived.js +79 -0
- package/plugin/lib/serial/modules/erro.js +151 -0
- package/plugin/lib/serial/modules/outputs.js +152 -0
- package/plugin/lib/serial/modules/soc.js +96 -0
- package/plugin/lib/serial/modules/temp.js +152 -0
- package/plugin/lib/serial/modules/victron.js +241 -0
- package/plugin/lib/serial/modules/volt.js +348 -0
- package/plugin/lib/serial/serial.js +162 -0
- package/plugin/lib/websocket/settings.js +122 -0
- package/plugin/lib/websocket/status.js +64 -0
- package/plugin/lib/websocket/ws.js +147 -0
- package/public/assets/icons/rec_ball-72x72.png +0 -0
- package/public/assets/images/bms_serial.png +0 -0
- package/public/assets/images/rec-sk.png +0 -0
- package/public/assets/images/rs485.jpg +0 -0
- package/public/css/components.css +60 -0
- package/public/css/core.css +125 -0
- package/public/css/responsive.css +200 -0
- package/public/css/settings.css +126 -0
- package/public/css/towers.css +100 -0
- package/public/index.html +135 -0
- package/public/js/cells.js +89 -0
- package/public/js/main.js +152 -0
- package/public/js/settings.js +190 -0
- package/index.js +0 -165
- package/schema.js +0 -106
- package/settings.js +0 -160
- package/status.js +0 -112
package/README.md
CHANGED
|
@@ -1,19 +1,46 @@
|
|
|
1
1
|
# signalk-rec-bms
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Reads all avabile data streams from BMS and publishes to SignalK
|
|
8
|
+
- Supports Websocket connection from the REC WiFi module with firmare >= SW164_2
|
|
9
|
+
- Supports serial connection directly from BMS
|
|
10
|
+
- Can set BMS settings when using serial connection
|
|
11
|
+
- Live status & settings page within SignalK
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
Tested with the 4 cell ABMS model only
|
|
4
16
|
|
|
5
17
|
## Requirements
|
|
6
18
|
|
|
19
|
+
#### Websocket connection (WiFi):
|
|
20
|
+
|
|
7
21
|
- A REC WiFi module with firmware >= SW164_2, which enables websockets
|
|
8
|
-
|
|
22
|
+
|
|
23
|
+
[Firmware SW164_2](https://www.rec-bms.com/wp-content/uploads/2025/01/Wi-Fi-FW-Update_SW164_2.zip)
|
|
24
|
+
|
|
25
|
+
#### Serial connection:
|
|
26
|
+
|
|
27
|
+
- RS485 to USB adapter with 5v output. The REC-BMS serial chip is not powered internally and you must supply 5v to power it.
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+

|
|
9
32
|
|
|
10
33
|
## Dependencies
|
|
11
34
|
|
|
12
35
|
- npm package: [websocket](https://www.npmjs.com/package/websocket)
|
|
36
|
+
- npm package: [serialport](https://www.npmjs.com/package/serialport)
|
|
37
|
+
- npm package: [crc](https://www.npmjs.com/package/crc)
|
|
38
|
+
|
|
39
|
+
## Installation (choose one)
|
|
13
40
|
|
|
14
|
-
|
|
41
|
+
Use the SignalK application store (preferred)
|
|
15
42
|
|
|
16
|
-
|
|
17
|
-
2. Run the following command:
|
|
43
|
+
Run the following command:
|
|
18
44
|
```bash
|
|
19
45
|
npm install signalk-rec-bms
|
|
46
|
+
```
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-rec-bms",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "SignalK plugin for REC-BMS",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "plugin/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
8
|
},
|
|
9
9
|
"keywords": [
|
|
10
|
+
"signalk-webapp",
|
|
10
11
|
"signalk-node-server-plugin",
|
|
11
12
|
"signalk-category-hardware"
|
|
12
13
|
],
|
|
@@ -16,7 +17,9 @@
|
|
|
16
17
|
},
|
|
17
18
|
"license": "ISC",
|
|
18
19
|
"dependencies": {
|
|
19
|
-
"ws": "^8.18.1"
|
|
20
|
+
"ws": "^8.18.1",
|
|
21
|
+
"serialport": "^13.0.0",
|
|
22
|
+
"crc": "^4.3.2"
|
|
20
23
|
},
|
|
21
24
|
"repository": {
|
|
22
25
|
"type": "git",
|
|
@@ -24,5 +27,9 @@
|
|
|
24
27
|
},
|
|
25
28
|
"engines": {
|
|
26
29
|
"node": ">=10"
|
|
30
|
+
},
|
|
31
|
+
"signalk": {
|
|
32
|
+
"appIcon": "./assets/icons/rec_ball-72x72.png",
|
|
33
|
+
"displayName": "SignalK-REC-BMS"
|
|
27
34
|
}
|
|
28
|
-
}
|
|
35
|
+
}
|
package/plugin/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const schema = require('./lib/schema');
|
|
4
|
+
const connection = require('./lib/connection');
|
|
5
|
+
|
|
6
|
+
module.exports = function(app) {
|
|
7
|
+
let conn;
|
|
8
|
+
let pluginOptions;
|
|
9
|
+
let commsHandler;
|
|
10
|
+
|
|
11
|
+
function publishDelta(delta) {
|
|
12
|
+
let mergedConfig = {};
|
|
13
|
+
if (pluginOptions && pluginOptions.deltas) {
|
|
14
|
+
if (pluginOptions.deltas.common) {
|
|
15
|
+
Object.assign(mergedConfig, pluginOptions.deltas.common);
|
|
16
|
+
}
|
|
17
|
+
if (pluginOptions.deltas.ws) {
|
|
18
|
+
Object.assign(mergedConfig, pluginOptions.deltas.ws);
|
|
19
|
+
}
|
|
20
|
+
if (pluginOptions.deltas.serial) {
|
|
21
|
+
Object.assign(mergedConfig, pluginOptions.deltas.serial);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const precision = 5;
|
|
26
|
+
const factor = Math.pow(10, precision);
|
|
27
|
+
|
|
28
|
+
delta.updates.forEach(update => {
|
|
29
|
+
update.values.forEach(item => {
|
|
30
|
+
if (typeof item.value === "number") {
|
|
31
|
+
item.value = Math.round(item.value * factor) / factor;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (delta && delta.updates && delta.updates.some(u => u.values.length > 0)) {
|
|
37
|
+
app.debug(`[INDEX] Publishing delta: ${JSON.stringify(delta)}`);
|
|
38
|
+
app.handleMessage("signalk-rec-bms", delta, "v1");
|
|
39
|
+
} else {
|
|
40
|
+
app.debug("[INDEX] Delta empty after processing, not publishing.");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var plugin = {
|
|
45
|
+
id: "signalk-rec-bms",
|
|
46
|
+
name: "SignalK-REC-BMS",
|
|
47
|
+
description: "SignalK plugin for REC-BMS",
|
|
48
|
+
schema: schema,
|
|
49
|
+
start: function(options) {
|
|
50
|
+
pluginOptions = options;
|
|
51
|
+
app.debug(`[INDEX] START invoked with options: ${JSON.stringify(options)}`);
|
|
52
|
+
app.setPluginStatus("Connecting to BMS…");
|
|
53
|
+
conn = connection(options, app, publishDelta);
|
|
54
|
+
conn.start(options);
|
|
55
|
+
},
|
|
56
|
+
stop: function() {
|
|
57
|
+
if (conn) {
|
|
58
|
+
conn.stop();
|
|
59
|
+
conn = null;
|
|
60
|
+
}
|
|
61
|
+
app.debug("[INDEX] Plugin stopped");
|
|
62
|
+
app.setPluginStatus("Stopped");
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return plugin;
|
|
67
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = function(options, app, publishDelta) {
|
|
4
|
+
let connModule;
|
|
5
|
+
const connectionType = options.connection && options.connection.connectionType;
|
|
6
|
+
app.debug(`[CONNECTION] connectionType is: ${connectionType}`);
|
|
7
|
+
|
|
8
|
+
if (connectionType === "websocket") {
|
|
9
|
+
app.debug("[CONNECTION] Loading websocket connection module");
|
|
10
|
+
connModule = require('./websocket/ws')(app, publishDelta);
|
|
11
|
+
} else if (connectionType === "serial") {
|
|
12
|
+
app.debug("[CONNECTION] Loading serial connection module");
|
|
13
|
+
connModule = require('./serial/serial')(app, publishDelta);
|
|
14
|
+
} else {
|
|
15
|
+
throw new Error("Unsupported connection type: " + connectionType);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return connModule;
|
|
19
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
type: "object",
|
|
3
|
+
properties: {
|
|
4
|
+
connection: {
|
|
5
|
+
title: "Connection Settings",
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
connectionType: {
|
|
9
|
+
title: "Connection Type",
|
|
10
|
+
type: "string",
|
|
11
|
+
enum: ["websocket", "serial"],
|
|
12
|
+
default: "websocket"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
websocket: {
|
|
17
|
+
title: "WebSocket Connection Settings",
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
websocketURL: {
|
|
21
|
+
title: "WebSocket URL",
|
|
22
|
+
type: "string",
|
|
23
|
+
default: "ws://WiFi_module_ip/ws"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
serial: {
|
|
28
|
+
title: "Serial Connection Settings",
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
device: {
|
|
32
|
+
title: "Serial Device",
|
|
33
|
+
type: "string",
|
|
34
|
+
default: "/dev/ttyUSB0"
|
|
35
|
+
},
|
|
36
|
+
baudRate: {
|
|
37
|
+
title: "Baud Rate",
|
|
38
|
+
type: "number",
|
|
39
|
+
default: 115200
|
|
40
|
+
},
|
|
41
|
+
targetAddress: {
|
|
42
|
+
title: "Target (Destination) Address",
|
|
43
|
+
type: "number",
|
|
44
|
+
default: 2
|
|
45
|
+
},
|
|
46
|
+
senderAddress: {
|
|
47
|
+
title: "Sender Address",
|
|
48
|
+
type: "number",
|
|
49
|
+
default: 0
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
deltaPrefix: {
|
|
54
|
+
title: "Delta Path Prefix",
|
|
55
|
+
type: "string",
|
|
56
|
+
default: "electrical.batteries.bms"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const { crc16 } = require('crc');
|
|
3
|
+
|
|
4
|
+
class Layer2 {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.buffer = [];
|
|
7
|
+
this.stats = {
|
|
8
|
+
tx: 0,
|
|
9
|
+
txBytes: 0,
|
|
10
|
+
rx: 0,
|
|
11
|
+
rxBytes: 0,
|
|
12
|
+
badCRC: 0
|
|
13
|
+
};
|
|
14
|
+
this.hooks = {
|
|
15
|
+
"data": () => {}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
on(hook, callback) {
|
|
20
|
+
this.hooks[hook] = callback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
push(block) {
|
|
24
|
+
for (let i = 0; i < block.length; i++) {
|
|
25
|
+
this.stats.rxBytes++;
|
|
26
|
+
this.buffer.push(block.readUInt8(i));
|
|
27
|
+
}
|
|
28
|
+
while (this.packetForward());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
packetForward() {
|
|
32
|
+
while (this.buffer.length > 0 && this.buffer[0] !== 0x55) {
|
|
33
|
+
this.buffer.shift();
|
|
34
|
+
}
|
|
35
|
+
if (this.buffer.length >= 7) {
|
|
36
|
+
let length = this.buffer[3];
|
|
37
|
+
let packet = [];
|
|
38
|
+
if (this.buffer.length < length + 7) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
for (let i = 0; i < length + 7; i++)
|
|
42
|
+
packet.push(this.buffer[i]);
|
|
43
|
+
packet = Buffer.from(packet);
|
|
44
|
+
if (packet.readUInt8(packet.length - 1) !== 0xAA) {
|
|
45
|
+
this.buffer.shift();
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
let packetCRC = packet.readUInt16BE(packet.length - 3);
|
|
49
|
+
let crc = crc16(packet.slice(1, packet.length - 3));
|
|
50
|
+
this.stats.rx++;
|
|
51
|
+
if (crc !== packetCRC) {
|
|
52
|
+
this.stats.badCRC++;
|
|
53
|
+
for (let i = 0; i < packet.length; i++)
|
|
54
|
+
this.buffer.shift();
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
this.hooks["data"](packet);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
}
|
|
61
|
+
for (let i = 0; i < length + 7; i++)
|
|
62
|
+
this.buffer.shift();
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = Layer2;
|