signalk-rec-bms 1.0.1 → 1.1.0

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.
Files changed (38) hide show
  1. package/README.md +44 -2
  2. package/package.json +11 -4
  3. package/plugin/index.js +67 -0
  4. package/plugin/lib/connection.js +19 -0
  5. package/plugin/lib/schema.js +59 -0
  6. package/plugin/lib/serial/Layer2.js +69 -0
  7. package/plugin/lib/serial/atlas.json +553 -0
  8. package/plugin/lib/serial/engine.js +89 -0
  9. package/plugin/lib/serial/hexer.js +60 -0
  10. package/plugin/lib/serial/modules/abms.js +297 -0
  11. package/plugin/lib/serial/modules/array.js +393 -0
  12. package/plugin/lib/serial/modules/bat.js +124 -0
  13. package/plugin/lib/serial/modules/cur.js +96 -0
  14. package/plugin/lib/serial/modules/derived.js +79 -0
  15. package/plugin/lib/serial/modules/erro.js +151 -0
  16. package/plugin/lib/serial/modules/outputs.js +152 -0
  17. package/plugin/lib/serial/modules/soc.js +96 -0
  18. package/plugin/lib/serial/modules/temp.js +152 -0
  19. package/plugin/lib/serial/modules/victron.js +241 -0
  20. package/plugin/lib/serial/modules/volt.js +348 -0
  21. package/plugin/lib/serial/serial.js +162 -0
  22. package/plugin/lib/websocket/settings.js +122 -0
  23. package/plugin/lib/websocket/status.js +64 -0
  24. package/plugin/lib/websocket/ws.js +147 -0
  25. package/public/assets/icons/rec_ball-72x72.png +0 -0
  26. package/public/assets/images/bms_serial.png +0 -0
  27. package/public/assets/images/rec-sk.png +0 -0
  28. package/public/assets/images/rs485.jpg +0 -0
  29. package/public/css/components.css +60 -0
  30. package/public/css/core.css +125 -0
  31. package/public/css/responsive.css +200 -0
  32. package/public/css/settings.css +126 -0
  33. package/public/css/towers.css +100 -0
  34. package/public/index.html +135 -0
  35. package/public/js/cells.js +89 -0
  36. package/public/js/main.js +149 -0
  37. package/public/js/settings.js +190 -0
  38. package/index.js +0 -315
package/README.md CHANGED
@@ -1,4 +1,46 @@
1
1
  # signalk-rec-bms
2
- SignalK plugin for REC-BMS
3
2
 
4
- Requires WiFi module with firmware >= SW164_2
3
+ ![alt text](https://github.com/ofernander/signalk-rec-bms/blob/main/public/assets/images/rec-sk.png)
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
16
+
17
+ ## Requirements
18
+
19
+ #### Websocket connection (WiFi):
20
+
21
+ - A REC WiFi module with firmware >= SW164_2, which enables websockets
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
+ ![alt text](https://github.com/ofernander/signalk-rec-bms/blob/main/public/assets/images/rs485.jpg)
30
+
31
+ ![alt text](https://github.com/ofernander/signalk-rec-bms/blob/main/public/assets/images/bms_serial.png)
32
+
33
+ ## Dependencies
34
+
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)
40
+
41
+ Use the SignalK application store (preferred)
42
+
43
+ Run the following command:
44
+ ```bash
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.0.1",
3
+ "version": "1.1.0",
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
+ }
@@ -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://192.168.88.10/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;