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.
Files changed (41) hide show
  1. package/README.md +32 -5
  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 +152 -0
  37. package/public/js/settings.js +190 -0
  38. package/index.js +0 -165
  39. package/schema.js +0 -106
  40. package/settings.js +0 -160
  41. package/status.js +0 -112
package/README.md CHANGED
@@ -1,19 +1,46 @@
1
1
  # signalk-rec-bms
2
2
 
3
- SignalK plugin for REC-BMS
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
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
- [Download firmware update](https://www.rec-bms.com/wp-content/uploads/2025/01/Wi-Fi-FW-Update_SW164_2.zip)
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)
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
- ## Installation
41
+ Use the SignalK application store (preferred)
15
42
 
16
- 1. Use the SignalK application store
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.0.2",
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
+ }
@@ -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;