signalk-rec-bms 1.0.0 → 1.0.2
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 +17 -0
- package/index.js +66 -216
- package/package.json +5 -1
- package/schema.js +106 -0
- package/settings.js +160 -0
- package/status.js +112 -0
package/README.md
CHANGED
|
@@ -1,2 +1,19 @@
|
|
|
1
1
|
# signalk-rec-bms
|
|
2
|
+
|
|
2
3
|
SignalK plugin for REC-BMS
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- 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)
|
|
9
|
+
|
|
10
|
+
## Dependencies
|
|
11
|
+
|
|
12
|
+
- npm package: [websocket](https://www.npmjs.com/package/websocket)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
1. Use the SignalK application store
|
|
17
|
+
2. Run the following command:
|
|
18
|
+
```bash
|
|
19
|
+
npm install signalk-rec-bms
|
package/index.js
CHANGED
|
@@ -1,94 +1,87 @@
|
|
|
1
1
|
const WebSocket = require('ws');
|
|
2
|
+
const status = require('./status');
|
|
3
|
+
const settings = require('./settings');
|
|
4
|
+
const schema = require('./schema');
|
|
2
5
|
|
|
3
6
|
module.exports = function(app) {
|
|
4
7
|
let ws;
|
|
5
|
-
const initialReconnectInterval = 5000;
|
|
8
|
+
const initialReconnectInterval = 5000;
|
|
6
9
|
let currentReconnectInterval = initialReconnectInterval;
|
|
7
|
-
const heartbeatInterval = 5000;
|
|
10
|
+
const heartbeatInterval = 5000;
|
|
8
11
|
let pingInterval;
|
|
9
12
|
let shouldReconnect = true;
|
|
13
|
+
let heartbeatTimeout;
|
|
10
14
|
|
|
11
15
|
var plugin = {
|
|
12
|
-
id: "rec-bms
|
|
13
|
-
name: "REC
|
|
16
|
+
id: "signalk-rec-bms",
|
|
17
|
+
name: "SignalK-REC-BMS",
|
|
14
18
|
description: "SignalK plugin for REC-BMS",
|
|
15
|
-
schema:
|
|
16
|
-
type: "object",
|
|
17
|
-
properties: {
|
|
18
|
-
bmsUrl: {
|
|
19
|
-
title: "BMS WebSocket URL",
|
|
20
|
-
type: "string",
|
|
21
|
-
default: "ws://bms-ip/ws"
|
|
22
|
-
},
|
|
23
|
-
deltaPrefix: {
|
|
24
|
-
title: "Delta",
|
|
25
|
-
type: "string",
|
|
26
|
-
default: "electrical.batteries.bms"
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
},
|
|
19
|
+
schema: schema,
|
|
30
20
|
start: function(options) {
|
|
31
|
-
app.debug(
|
|
21
|
+
app.debug(`[signalk-rec-bms] (${new Date().toISOString()}) START function invoked with options: ${JSON.stringify(options)}`);
|
|
32
22
|
app.setPluginStatus("Connecting to BMS WebSocket…");
|
|
33
23
|
shouldReconnect = true;
|
|
34
24
|
currentReconnectInterval = initialReconnectInterval;
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
vesselId = app.getSelfId();
|
|
40
|
-
} else if (app.selfId) {
|
|
41
|
-
vesselId = app.selfId;
|
|
42
|
-
} else {
|
|
43
|
-
vesselId = "self";
|
|
44
|
-
}
|
|
45
|
-
const context = "vessels." + vesselId;
|
|
46
|
-
app.debug("REC BMS Plugin: Using vessel context: " + context);
|
|
26
|
+
let vesselId = typeof app.getSelfId === "function" ? app.getSelfId() : app.selfId || "self";
|
|
27
|
+
const context = `vessels.${vesselId}`;
|
|
28
|
+
app.debug(`[signalk-rec-bms] (${new Date().toISOString()}) Using vessel context: ${context}`);
|
|
47
29
|
|
|
48
|
-
// Get the user-defined delta prefix (or default if not provided)
|
|
49
30
|
const prefix = options.deltaPrefix || "electrical.batteries.bms";
|
|
50
|
-
app.debug(
|
|
31
|
+
app.debug(`[signalk-rec-bms] (${new Date().toISOString()}) Using delta prefix: ${prefix}`);
|
|
32
|
+
|
|
33
|
+
function resetHeartbeat() {
|
|
34
|
+
clearTimeout(heartbeatTimeout);
|
|
35
|
+
heartbeatTimeout = setTimeout(() => {
|
|
36
|
+
app.debug("[signalk-rec-bms] Heartbeat timeout. Terminating connection.");
|
|
37
|
+
if (ws) {
|
|
38
|
+
ws.terminate();
|
|
39
|
+
}
|
|
40
|
+
}, heartbeatInterval);
|
|
41
|
+
}
|
|
51
42
|
|
|
52
|
-
// Function to (re)connect.
|
|
53
43
|
function connect() {
|
|
54
|
-
app.debug(
|
|
44
|
+
app.debug(`[signalk-rec-bms] (${new Date().toISOString()}) Attempting to connect to ${options.bmsUrl}`);
|
|
55
45
|
ws = new WebSocket(options.bmsUrl);
|
|
56
46
|
|
|
57
47
|
ws.on('open', () => {
|
|
58
|
-
app.debug("
|
|
59
|
-
app.setPluginStatus("Connected to BMS");
|
|
60
|
-
// Reset reconnection interval on successful connection.
|
|
48
|
+
app.debug("[signalk-rec-bms] WebSocket connection opened");
|
|
49
|
+
app.setPluginStatus("Connected to BMS - waiting for serial number");
|
|
61
50
|
currentReconnectInterval = initialReconnectInterval;
|
|
62
|
-
|
|
51
|
+
|
|
63
52
|
pingInterval = setInterval(() => {
|
|
64
53
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
65
54
|
ws.ping();
|
|
66
|
-
app.debug("
|
|
55
|
+
app.debug("[signalk-rec-bms] Sent ping");
|
|
67
56
|
}
|
|
68
57
|
}, heartbeatInterval);
|
|
58
|
+
|
|
59
|
+
resetHeartbeat();
|
|
69
60
|
});
|
|
70
61
|
|
|
71
62
|
ws.on('pong', () => {
|
|
72
|
-
app.debug("
|
|
63
|
+
app.debug("[signalk-rec-bms] Received pong");
|
|
73
64
|
});
|
|
74
65
|
|
|
75
66
|
ws.on('message', (data) => {
|
|
67
|
+
resetHeartbeat();
|
|
76
68
|
let message = data;
|
|
69
|
+
|
|
77
70
|
if (typeof message !== 'string') {
|
|
78
71
|
if (Buffer.isBuffer(message)) {
|
|
79
72
|
message = message.toString();
|
|
80
73
|
} else {
|
|
81
|
-
app.debug("
|
|
74
|
+
app.debug("[signalk-rec-bms] Received payload is not a string or Buffer");
|
|
82
75
|
return;
|
|
83
76
|
}
|
|
84
77
|
}
|
|
85
|
-
|
|
78
|
+
|
|
79
|
+
app.debug(`[signalk-rec-bms] (${new Date().toISOString()}) Received message: ${message}`);
|
|
86
80
|
|
|
87
81
|
try {
|
|
88
82
|
const parsedData = JSON.parse(message);
|
|
89
|
-
app.debug("
|
|
83
|
+
app.debug("[signalk-rec-bms] JSON parsed successfully");
|
|
90
84
|
|
|
91
|
-
// Build the delta update with proper context and timestamp.
|
|
92
85
|
let delta = {
|
|
93
86
|
context: context,
|
|
94
87
|
updates: [{
|
|
@@ -97,208 +90,65 @@ module.exports = function(app) {
|
|
|
97
90
|
}]
|
|
98
91
|
};
|
|
99
92
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (match) {
|
|
105
|
-
let hours = parseInt(match[1], 10);
|
|
106
|
-
let minutes = match[2] ? parseInt(match[2], 10) : 0;
|
|
107
|
-
return hours * 60 + minutes;
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Process "status" messages.
|
|
113
|
-
if (parsedData.type === "status" && parsedData.bms_array) {
|
|
114
|
-
let master = parsedData.bms_array.master;
|
|
115
|
-
let power = (master.vbat !== undefined && master.ibat !== undefined)
|
|
116
|
-
? master.vbat * master.ibat : null;
|
|
117
|
-
let cellVoltageDifference = master.maxcell - master.mincell;
|
|
118
|
-
|
|
119
|
-
delta.updates[0].values.push(
|
|
120
|
-
{ path: `${prefix}.MinCellVoltage`, value: master.mincell },
|
|
121
|
-
{ path: `${prefix}.MaxCellVoltage`, value: master.maxcell },
|
|
122
|
-
{ path: `${prefix}.CellVoltageDifference`, value: cellVoltageDifference },
|
|
123
|
-
{ path: `${prefix}.Current`, value: master.ibat },
|
|
124
|
-
{ path: `${prefix}.Temperature`, value: master.tmax },
|
|
125
|
-
{ path: `${prefix}.Voltage`, value: master.vbat },
|
|
126
|
-
{ path: `${prefix}.StateOfCharge`, value: master.soc },
|
|
127
|
-
{ path: `${prefix}.StateOfHealth`, value: master.soh },
|
|
128
|
-
{ path: `${prefix}.TimeRemaining`, value: extractMinutes(master.time_remaining) },
|
|
129
|
-
{ path: `${prefix}.ChargeDirection`, value: master.st_naprav },
|
|
130
|
-
{ path: `${prefix}.ErrorPresent`, value: master.erro.present },
|
|
131
|
-
{ path: `${prefix}.ErrorAddress`, value: master.erro.addr },
|
|
132
|
-
{ path: `${prefix}.ErrorStatus`, value: master.erro.st },
|
|
133
|
-
{ path: `${prefix}.ErrorConnection`, value: master.erro.con_st },
|
|
134
|
-
{ path: `${prefix}.ErrorMessage`, value: master.error },
|
|
135
|
-
{ path: `${prefix}.Power`, value: power },
|
|
136
|
-
{ path: `${prefix}.MasterTime`, value: master.time },
|
|
137
|
-
{ path: `${prefix}.MasterDate`, value: master.date }
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
if (parsedData.bms_array.slave) {
|
|
141
|
-
let slave = parsedData.bms_array.slave["0"];
|
|
142
|
-
if (slave) {
|
|
143
|
-
delta.updates[0].values.push(
|
|
144
|
-
{ path: `${prefix}.SlaveAddress`, value: slave.address },
|
|
145
|
-
{ path: `${prefix}.SlaveTempSensorStatus`, value: slave.st_temp },
|
|
146
|
-
{ path: `${prefix}.BMSTemperature`, value: slave.temp_bms },
|
|
147
|
-
{ path: `${prefix}.SlaveCellCount`, value: slave.st_celic }
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
Object.entries(slave.temp || {}).forEach(([key, value], index) => {
|
|
151
|
-
delta.updates[0].values.push({
|
|
152
|
-
path: `${prefix}.CellTemperature${index + 1}`,
|
|
153
|
-
value
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
Object.entries(slave.res || {}).forEach(([key, value], index) => {
|
|
158
|
-
delta.updates[0].values.push({
|
|
159
|
-
path: `${prefix}.CellResistance${index + 1}`,
|
|
160
|
-
value
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
Object.entries(slave.nap || {}).forEach(([key, value], index) => {
|
|
165
|
-
delta.updates[0].values.push({
|
|
166
|
-
path: `${prefix}.CellVoltage${index + 1}`,
|
|
167
|
-
value
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
93
|
+
if (parsedData.type === "status") {
|
|
94
|
+
app.debug("[signalk-rec-bms] Processing STATUS message...");
|
|
95
|
+
const statusValues = status(parsedData, prefix, options.statusDeltas) || [];
|
|
96
|
+
delta.updates[0].values.push(...statusValues);
|
|
172
97
|
}
|
|
173
98
|
|
|
174
|
-
// Process "settings" messages.
|
|
175
99
|
if (parsedData.type === "settings") {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
? parsedData.cmax * parsedData.cans : null;
|
|
180
|
-
|
|
181
|
-
delta.updates[0].values.push(
|
|
182
|
-
{ path: `${prefix}.MinVoltageThreshold`, value: parsedData.cmin },
|
|
183
|
-
{ path: `${prefix}.MaxVoltageThreshold`, value: parsedData.cmax },
|
|
184
|
-
{ path: `${prefix}.Tmax`, value: parsedData.tmax },
|
|
185
|
-
{ path: `${prefix}.BVoltage`, value: parsedData.bvol },
|
|
186
|
-
{ path: `${prefix}.BMinVoltage`, value: parsedData.bmin },
|
|
187
|
-
{ path: `${prefix}.TBalance`, value: parsedData.tbal },
|
|
188
|
-
{ path: `${prefix}.Tmin`, value: parsedData.tmin },
|
|
189
|
-
{ path: `${prefix}.Capacity`, value: parsedData.capa },
|
|
190
|
-
{ path: `${prefix}.ChargeVoltage`, value: parsedData.char },
|
|
191
|
-
{ path: `${prefix}.Ioff`, value: parsedData.ioff },
|
|
192
|
-
{ path: `${prefix}.ChargeHysteresis`, value: parsedData.chis },
|
|
193
|
-
{ path: `${prefix}.VoltageDiffThreshold`, value: parsedData.razl },
|
|
194
|
-
{ path: `${prefix}.MaxH`, value: parsedData.maxh },
|
|
195
|
-
{ path: `${prefix}.MinH`, value: parsedData.minh },
|
|
196
|
-
{ path: `${prefix}.BMSThreshold`, value: parsedData.bmth },
|
|
197
|
-
{ path: `${prefix}.Ioja`, value: parsedData.ioja },
|
|
198
|
-
{ path: `${prefix}.Soch`, value: parsedData.soch },
|
|
199
|
-
{ path: `${prefix}.Op2L`, value: parsedData.op2l },
|
|
200
|
-
{ path: `${prefix}.Op2H`, value: parsedData.op2h },
|
|
201
|
-
{ path: `${prefix}.Re1L`, value: parsedData.re1l },
|
|
202
|
-
{ path: `${prefix}.Re1H`, value: parsedData.re1h },
|
|
203
|
-
{ path: `${prefix}.ChargeMode`, value: parsedData.chac },
|
|
204
|
-
{ path: `${prefix}.DischargeMode`, value: parsedData.dchc },
|
|
205
|
-
{ path: `${prefix}.MaxChargeCurrent`, value: parsedData.maxc },
|
|
206
|
-
{ path: `${prefix}.MaxDischargeCurrent`, value: parsedData.maxd },
|
|
207
|
-
{ path: `${prefix}.LowVoltageCutoff`, value: parsedData.clow },
|
|
208
|
-
//{ path: `${prefix}.StateOfChargeS`, value: parsedData.socs },
|
|
209
|
-
{ path: `${prefix}.CycleCount`, value: parsedData.cycl },
|
|
210
|
-
{ path: `${prefix}.Cans`, value: parsedData.cans },
|
|
211
|
-
{ path: `${prefix}.Chemistry`, value: parsedData.chem },
|
|
212
|
-
{ path: `${prefix}.Strn`, value: parsedData.strn },
|
|
213
|
-
{ path: `${prefix}.Re1T`, value: parsedData.re1t },
|
|
214
|
-
{ path: `${prefix}.Op2T`, value: parsedData.op2t },
|
|
215
|
-
{ path: `${prefix}.Re1V`, value: parsedData.re1v },
|
|
216
|
-
{ path: `${prefix}.Op2V`, value: parsedData.op2v },
|
|
217
|
-
{ path: `${prefix}.Cfvc`, value: parsedData.cfvc },
|
|
218
|
-
{ path: `${prefix}.Rsbr`, value: parsedData.rsbr },
|
|
219
|
-
{ path: `${prefix}.BMSClockTime`, value: parsedData.mcu_time },
|
|
220
|
-
{ path: `${prefix}.BMSClockDate`, value: parsedData.mcu_date },
|
|
221
|
-
{ path: `${prefix}.BMSName`, value: parsedData.bms_name },
|
|
222
|
-
{ path: `${prefix}.Address`, value: parsedData.addr },
|
|
223
|
-
{ path: `${prefix}.TUnit`, value: parsedData.tunit },
|
|
224
|
-
{ path: `${prefix}.AhUsed`, value: parsedData.Ah },
|
|
225
|
-
{ path: `${prefix}.Current`, value: parsedData.cur },
|
|
226
|
-
{ path: `${prefix}.NewLogEveryMidnight`, value: parsedData.new_log_every_midnight },
|
|
227
|
-
{ path: `${prefix}.Out`, value: parsedData.out },
|
|
228
|
-
{ path: `${prefix}.AhRemaining`, value: AhRemaining },
|
|
229
|
-
{ path: `${prefix}.MaxChargeVoltage`, value: maxChargeVoltage }
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
if (parsedData.baud) {
|
|
233
|
-
delta.updates[0].values.push(
|
|
234
|
-
{ path: `${prefix}.LcdBaudRate`, value: parsedData.baud.lcd },
|
|
235
|
-
{ path: `${prefix}.ComBaudRate`, value: parsedData.baud.com }
|
|
236
|
-
);
|
|
237
|
-
}
|
|
100
|
+
app.debug("[signalk-rec-bms] Processing SETTINGS message...");
|
|
101
|
+
const settingsValues = settings(parsedData, prefix, options.settingsDeltas) || [];
|
|
102
|
+
delta.updates[0].values.push(...settingsValues);
|
|
238
103
|
|
|
239
|
-
if (parsedData.
|
|
240
|
-
|
|
241
|
-
{ path: `${prefix}.ErrorPresenceFlag`, value: parsedData.err.p },
|
|
242
|
-
{ path: `${prefix}.ErrorNumber`, value: parsedData.err.num }
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (parsedData.nnc) {
|
|
247
|
-
delta.updates[0].values.push(
|
|
248
|
-
{ path: `${prefix}.NearestNeighborBMS`, value: parsedData.nnc.bms },
|
|
249
|
-
{ path: `${prefix}.NearestNeighborCell`, value: parsedData.nnc.cell }
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (parsedData.vnc) {
|
|
254
|
-
delta.updates[0].values.push(
|
|
255
|
-
{ path: `${prefix}.VNCBMS`, value: parsedData.vnc.bms },
|
|
256
|
-
{ path: `${prefix}.VNCCell`, value: parsedData.vnc.cell }
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (parsedData.toor) {
|
|
261
|
-
delta.updates[0].values.push(
|
|
262
|
-
{ path: `${prefix}.ToorBMS`, value: parsedData.toor.bms },
|
|
263
|
-
{ path: `${prefix}.ToorCell`, value: parsedData.toor.cell }
|
|
264
|
-
);
|
|
104
|
+
if (parsedData.bms_name) {
|
|
105
|
+
app.setPluginStatus(`Connected to BMS: ${parsedData.bms_name}`);
|
|
265
106
|
}
|
|
266
107
|
}
|
|
267
108
|
|
|
268
109
|
delta.updates[0].values = delta.updates[0].values.filter(item => item.value !== null && item.value !== undefined);
|
|
269
110
|
|
|
270
|
-
|
|
271
|
-
|
|
111
|
+
if (delta.updates[0].values.length > 0) {
|
|
112
|
+
app.debug(`[signalk-rec-bms] (${new Date().toISOString()}) Sending delta: ${JSON.stringify(delta)}`);
|
|
113
|
+
app.handleMessage(plugin.id, delta, 'v1');
|
|
114
|
+
} else {
|
|
115
|
+
app.debug("[signalk-rec-bms] No valid deltas to send.");
|
|
116
|
+
}
|
|
272
117
|
} catch (err) {
|
|
273
|
-
app.error(
|
|
118
|
+
app.error(`[signalk-rec-bms] Failed to parse JSON: ${err.message}`);
|
|
274
119
|
}
|
|
275
120
|
});
|
|
276
121
|
|
|
277
122
|
ws.on('close', () => {
|
|
278
|
-
app.debug("
|
|
123
|
+
app.debug("[signalk-rec-bms] WebSocket connection closed");
|
|
279
124
|
app.setPluginStatus("BMS disconnected - ERROR");
|
|
280
125
|
clearInterval(pingInterval);
|
|
126
|
+
|
|
281
127
|
if (shouldReconnect) {
|
|
282
|
-
app.debug(
|
|
128
|
+
app.debug(`[signalk-rec-bms] Reconnecting in ${currentReconnectInterval}ms`);
|
|
283
129
|
setTimeout(connect, currentReconnectInterval);
|
|
284
|
-
// Exponential backoff, up to a maximum of 60 seconds.
|
|
285
130
|
currentReconnectInterval = Math.min(currentReconnectInterval * 2, 60000);
|
|
286
131
|
}
|
|
287
132
|
});
|
|
288
133
|
|
|
289
134
|
ws.on('error', (error) => {
|
|
290
|
-
app.error(
|
|
291
|
-
app.setPluginStatus(
|
|
135
|
+
app.error(`[signalk-rec-bms] WebSocket error: ${error.message}`);
|
|
136
|
+
app.setPluginStatus(`Error: ${error.message} - ERROR`);
|
|
292
137
|
clearInterval(pingInterval);
|
|
293
138
|
if (ws) {
|
|
294
139
|
ws.terminate();
|
|
295
140
|
}
|
|
141
|
+
if (shouldReconnect) {
|
|
142
|
+
app.debug(`[signalk-rec-bms] Reconnecting in ${currentReconnectInterval}ms due to error.`);
|
|
143
|
+
setTimeout(connect, currentReconnectInterval);
|
|
144
|
+
currentReconnectInterval = Math.min(currentReconnectInterval * 2, 60000);
|
|
145
|
+
}
|
|
296
146
|
});
|
|
297
147
|
}
|
|
298
148
|
|
|
299
|
-
// Start by connecting.
|
|
300
149
|
connect();
|
|
301
150
|
},
|
|
151
|
+
|
|
302
152
|
stop: function() {
|
|
303
153
|
shouldReconnect = false;
|
|
304
154
|
clearInterval(pingInterval);
|
|
@@ -306,7 +156,7 @@ module.exports = function(app) {
|
|
|
306
156
|
ws.close();
|
|
307
157
|
ws = null;
|
|
308
158
|
}
|
|
309
|
-
app.debug("
|
|
159
|
+
app.debug("[signalk-rec-bms] Plugin stopped");
|
|
310
160
|
app.setPluginStatus("Stopped");
|
|
311
161
|
}
|
|
312
162
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-rec-bms",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "SignalK plugin for REC-BMS",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"ws": "^8.18.1"
|
|
20
20
|
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/ofernander/signalk-rec-bms"
|
|
24
|
+
},
|
|
21
25
|
"engines": {
|
|
22
26
|
"node": ">=10"
|
|
23
27
|
}
|
package/schema.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
type: "object",
|
|
3
|
+
properties: {
|
|
4
|
+
bmsUrl: {
|
|
5
|
+
title: "BMS WebSocket URL",
|
|
6
|
+
type: "string",
|
|
7
|
+
default: "ws://WiFi-module-ip/ws"
|
|
8
|
+
},
|
|
9
|
+
deltaPrefix: {
|
|
10
|
+
title: "Delta Path Prefix",
|
|
11
|
+
type: "string",
|
|
12
|
+
default: "electrical.batteries.bms"
|
|
13
|
+
},
|
|
14
|
+
statusDeltas: {
|
|
15
|
+
title: "Status Delta Options",
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
vbat: { type: "boolean", title: "Voltage", default: true },
|
|
19
|
+
ibat: { type: "boolean", title: "Current", default: true },
|
|
20
|
+
wbat: { type: "boolean", title: "Power", default: true },
|
|
21
|
+
mincell: { type: "boolean", title: "Min Cell Voltage", default: true },
|
|
22
|
+
maxcell: { type: "boolean", title: "Max Cell Voltage", default: true },
|
|
23
|
+
res: { type: "boolean", title: "Cell Resistance (all)", default: true },
|
|
24
|
+
temp: { type: "boolean", title: "Cell Temperature (all)", default: true },
|
|
25
|
+
nap: { type: "boolean", title: "Cell Voltage (all)", default: true },
|
|
26
|
+
cvd: { type: "boolean", title: "Cell Voltage Difference", default: true },
|
|
27
|
+
soc: { type: "boolean", title: "State Of Charge", default: true },
|
|
28
|
+
soh: { type: "boolean", title: "State Of Health", default: true },
|
|
29
|
+
time_remaining: { type: "boolean", title: "Time to 0% SOC Remaining", default: true },
|
|
30
|
+
temp_bms: { type: "boolean", title: "BMS Temperature", default: true },
|
|
31
|
+
erro_present: { type: "boolean", title: "Error Present", default: false },
|
|
32
|
+
erro_addr: { type: "boolean", title: "Error Address", default: false },
|
|
33
|
+
erro_con_st: { type: "boolean", title: "Error Connection State", default: false },
|
|
34
|
+
erro_st: { type: "boolean", title: "Error Status", default: false },
|
|
35
|
+
error: { type: "boolean", title: "Error Message", default: false },
|
|
36
|
+
slaveAddress: { type: "boolean", title: "Slave Address", default: false },
|
|
37
|
+
st_celic: { type: "boolean", title: "Slave Cell Count", default: false },
|
|
38
|
+
st_temp: { type: "boolean", title: "Slave Temp Sensor Status", default: false },
|
|
39
|
+
st_naprav: { type: "boolean", title: "UNKNOWN", default: false },
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
settingsDeltas: {
|
|
43
|
+
title: "Settings Delta Options",
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
AhR: { type: "boolean", title: "Amp Hours Remaining", default: true },
|
|
47
|
+
bvol: { type: "boolean", title: "Balancing END Voltage", default: false },
|
|
48
|
+
bmin: { type: "boolean", title: "Balancing START Voltage", default: false },
|
|
49
|
+
razl: { type: "boolean", title: "Max Allowed Cell Voltage Difference", default: false },
|
|
50
|
+
char: { type: "boolean", title: "End Of Charge Voltage", default: false },
|
|
51
|
+
chis: { type: "boolean", title: "End of Charging Voltage Hysteresis", default: false },
|
|
52
|
+
cmax: { type: "boolean", title: "Max Allowed Cell Voltage", default: false },
|
|
53
|
+
maxh: { type: "boolean", title: "Max Allowed Cell Voltage Hysteresis", default: false },
|
|
54
|
+
cmin: { type: "boolean", title: "Min Allowed Cell Voltage", default: false },
|
|
55
|
+
minh: { type: "boolean", title: "Min Allowed Cell Voltage Hysteresis", default: false },
|
|
56
|
+
clow: { type: "boolean", title: "Min Allowed Cell Discharge", default: false },
|
|
57
|
+
cfvc: { type: "boolean", title: "Float Voltage Coefficient", default: false },
|
|
58
|
+
ioja: { type: "boolean", title: "Current Sensor Coefficient", default: false },
|
|
59
|
+
ioff: { type: "boolean", title: "Current Sensor Offset", default: false },
|
|
60
|
+
maxc: { type: "boolean", title: "Max Charge Current", default: false },
|
|
61
|
+
maxd: { type: "boolean", title: "Max Discharge Current", default: false },
|
|
62
|
+
chac: { type: "boolean", title: "Charging Coefficient", default: false },
|
|
63
|
+
dchc: { type: "boolean", title: "Discharge Coefficient", default: false },
|
|
64
|
+
tmin: { type: "boolean", title: "minAllowedChargeTemp", default: false },
|
|
65
|
+
tmax: { type: "boolean", title: "maxAllowedChargeTemp", default: false },
|
|
66
|
+
tbal: { type: "boolean", title: "maxAllowedBMSTemp", default: false },
|
|
67
|
+
bmth: { type: "boolean", title: "maxAllowedBMSHysteresis", default: false },
|
|
68
|
+
mcu_date: { type: "boolean", title: "BMS Date", default: false },
|
|
69
|
+
mcu_time: { type: "boolean", title: "BMS Time", default: false },
|
|
70
|
+
bms_name: { type: "boolean", title: "BMS Name", default: true },
|
|
71
|
+
cycl: { type: "boolean", title: "Battery Cycle Count", default: true },
|
|
72
|
+
capa: { type: "boolean", title: "Capacity", default: false },
|
|
73
|
+
chem: { type: "boolean", title: "Chemistry", default: false },
|
|
74
|
+
addr: { type: "boolean", title: "Address", default: false },
|
|
75
|
+
tunit: { type: "boolean", title: "T-unit", default: false },
|
|
76
|
+
cmin: { type: "boolean", title: "Min Allowed Cell Voltage", default: false },
|
|
77
|
+
cmax: { type: "boolean", title: "Max Allowed Cell Voltage", default: false },
|
|
78
|
+
//cur: { type: "boolean", title: "Current 2", default: false },
|
|
79
|
+
new_log_every_midnight: { type: "boolean", title: "New Log Every Midnight", default: false },
|
|
80
|
+
out: { type: "boolean", title: "Out", default: false },
|
|
81
|
+
cans: { type: "boolean", title: "Can Bus Protocol", default: false },
|
|
82
|
+
lcd: { type: "boolean", title: "LCD Baud Rate", default: false },
|
|
83
|
+
com: { type: "boolean", title: "Com Baud Rate", default: false },
|
|
84
|
+
p: { type: "boolean", title: "Error Presence Flag", default: false },
|
|
85
|
+
num: { type: "boolean", title: "Error Number", default: false },
|
|
86
|
+
op2h: { type: "boolean", title: "Op2H", default: false },
|
|
87
|
+
op2l: { type: "boolean", title: "Op2L", default: false },
|
|
88
|
+
op2t: { type: "boolean", title: "Op2T", default: false },
|
|
89
|
+
op2v: { type: "boolean", title: "Op2V", default: false },
|
|
90
|
+
re1h: { type: "boolean", title: "Re1H", default: false },
|
|
91
|
+
re1l: { type: "boolean", title: "Re1L", default: false },
|
|
92
|
+
re1t: { type: "boolean", title: "Re1T", default: false },
|
|
93
|
+
re1v: { type: "boolean", title: "Re1V", default: false },
|
|
94
|
+
socs: { type: "boolean", title: "SoCs", default: false },
|
|
95
|
+
soch: { type: "boolean", title: "SoCh", default: false },
|
|
96
|
+
strn: { type: "boolean", title: "Strn", default: false },
|
|
97
|
+
rsbr: { type: "boolean", title: "Rsbr", default: false },
|
|
98
|
+
errorGroup: { type: "boolean", title: "Error Group", default: false },
|
|
99
|
+
nncGroup: { type: "boolean", title: "Nearest Neighbor Group", default: false },
|
|
100
|
+
vncGroup: { type: "boolean", title: "Vnc Group", default: false },
|
|
101
|
+
toorGroup: { type: "boolean", title: "Toor Group", default: false },
|
|
102
|
+
baudGroup: { type: "boolean", title: "Baud Group", default: false }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
package/settings.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
module.exports = function(parsedData, prefix, config) {
|
|
2
|
+
if (parsedData.type !== "settings") return [];
|
|
3
|
+
|
|
4
|
+
const settings = parsedData;
|
|
5
|
+
const values = [];
|
|
6
|
+
|
|
7
|
+
// derived data - NOT FROM BMS
|
|
8
|
+
if (config.AhR) {
|
|
9
|
+
const AhR = (parsedData.capa !== undefined && parsedData.Ah !== undefined)
|
|
10
|
+
? parsedData.capa - parsedData.Ah : null;
|
|
11
|
+
values.push({ path: `${prefix}.ampHourRemaining`, value: AhR });
|
|
12
|
+
}
|
|
13
|
+
// data from WiFi module WebSocket under SETTINGS message
|
|
14
|
+
if (config.cmin) {
|
|
15
|
+
values.push({ path: `${prefix}.minCellVoltage`, value: parsedData.cmin });
|
|
16
|
+
}
|
|
17
|
+
if (config.cmax) {
|
|
18
|
+
values.push({ path: `${prefix}.maxCellVoltage`, value: parsedData.cmax });
|
|
19
|
+
}
|
|
20
|
+
if (config.tmax) {
|
|
21
|
+
values.push({ path: `${prefix}.maxAllowedCellTemp`, value: parsedData.tmax });
|
|
22
|
+
}
|
|
23
|
+
if (config.bvol) {
|
|
24
|
+
values.push({ path: `${prefix}.balEndVoltage`, value: parsedData.bvol });
|
|
25
|
+
}
|
|
26
|
+
if (config.bmin) {
|
|
27
|
+
values.push({ path: `${prefix}.balStartVoltage`, value: parsedData.bmin });
|
|
28
|
+
}
|
|
29
|
+
if (config.tbal) {
|
|
30
|
+
values.push({ path: `${prefix}.maxAllowedBMSTemp`, value: parsedData.tbal });
|
|
31
|
+
}
|
|
32
|
+
if (config.tmin) {
|
|
33
|
+
values.push({ path: `${prefix}.minAllowedCellTemp`, value: parsedData.tmin });
|
|
34
|
+
}
|
|
35
|
+
if (config.capa) {
|
|
36
|
+
values.push({ path: `${prefix}.capacity`, value: parsedData.capa });
|
|
37
|
+
}
|
|
38
|
+
if (config.char) {
|
|
39
|
+
values.push({ path: `${prefix}.endChargeVoltage`, value: parsedData.char });
|
|
40
|
+
}
|
|
41
|
+
if (config.ioff) {
|
|
42
|
+
values.push({ path: `${prefix}.currentOffset`, value: parsedData.ioff });
|
|
43
|
+
}
|
|
44
|
+
if (config.chis) {
|
|
45
|
+
values.push({ path: `${prefix}.endChargeHysteresis`, value: parsedData.chis });
|
|
46
|
+
}
|
|
47
|
+
if (config.razl) {
|
|
48
|
+
values.push({ path: `${prefix}.maxAllowedCellVoltDiff`, value: parsedData.razl });
|
|
49
|
+
}
|
|
50
|
+
if (config.maxh) {
|
|
51
|
+
values.push({ path: `${prefix}.maxAllowedVoltageHysteresis`, value: parsedData.maxh });
|
|
52
|
+
}
|
|
53
|
+
if (config.minh) {
|
|
54
|
+
values.push({ path: `${prefix}.minAllowedVoltageHysteresis`, value: parsedData.minh });
|
|
55
|
+
}
|
|
56
|
+
if (config.bmth) {
|
|
57
|
+
values.push({ path: `${prefix}.maxAllowedBMSTempHysteresis`, value: parsedData.bmth });
|
|
58
|
+
}
|
|
59
|
+
if (config.ioja) {
|
|
60
|
+
values.push({ path: `${prefix}.currentSensorCoefficient`, value: parsedData.ioja });
|
|
61
|
+
}
|
|
62
|
+
if (config.soch) {
|
|
63
|
+
values.push({ path: `${prefix}.soch`, value: parsedData.soch });
|
|
64
|
+
}
|
|
65
|
+
if (config.op2l) {
|
|
66
|
+
values.push({ path: `${prefix}.opto2Low`, value: parsedData.op2l });
|
|
67
|
+
}
|
|
68
|
+
if (config.op2h) {
|
|
69
|
+
values.push({ path: `${prefix}.opto2High`, value: parsedData.op2h });
|
|
70
|
+
}
|
|
71
|
+
if (config.re1l) {
|
|
72
|
+
values.push({ path: `${prefix}.relay1Low`, value: parsedData.re1l });
|
|
73
|
+
}
|
|
74
|
+
if (config.re1h) {
|
|
75
|
+
values.push({ path: `${prefix}.relay1High`, value: parsedData.re1h });
|
|
76
|
+
}
|
|
77
|
+
if (config.chac) {
|
|
78
|
+
values.push({ path: `${prefix}.chargeCoefficient`, value: parsedData.chac });
|
|
79
|
+
}
|
|
80
|
+
if (config.dchc) {
|
|
81
|
+
values.push({ path: `${prefix}.dischargeCoefficient`, value: parsedData.dchc });
|
|
82
|
+
}
|
|
83
|
+
if (config.maxc) {
|
|
84
|
+
values.push({ path: `${prefix}.maxChargeCurrent`, value: parsedData.maxc });
|
|
85
|
+
}
|
|
86
|
+
if (config.maxd) {
|
|
87
|
+
values.push({ path: `${prefix}.maxDischargeCurrent`, value: parsedData.maxd });
|
|
88
|
+
}
|
|
89
|
+
if (config.clow) {
|
|
90
|
+
values.push({ path: `${prefix}.minDischargeCellVoltage`, value: parsedData.clow });
|
|
91
|
+
}
|
|
92
|
+
if (config.socs) {
|
|
93
|
+
values.push({ path: `${prefix}.socs`, value: parsedData.socs });
|
|
94
|
+
}
|
|
95
|
+
if (config.cycl) {
|
|
96
|
+
values.push({ path: `${prefix}.cycleCount`, value: parsedData.cycl });
|
|
97
|
+
}
|
|
98
|
+
if (config.cans) {
|
|
99
|
+
values.push({ path: `${prefix}.canbusSetting`, value: parsedData.cans });
|
|
100
|
+
}
|
|
101
|
+
if (config.chem) {
|
|
102
|
+
values.push({ path: `${prefix}.chemistry`, value: parsedData.chem });
|
|
103
|
+
}
|
|
104
|
+
if (config.strn) {
|
|
105
|
+
values.push({ path: `${prefix}.numberOfInverterDevices`, value: parsedData.strn });
|
|
106
|
+
}
|
|
107
|
+
if (config.mcu_time) {
|
|
108
|
+
values.push({ path: `${prefix}.mcuTime`, value: parsedData.mcu_time });
|
|
109
|
+
}
|
|
110
|
+
if (config.mcu_date) {
|
|
111
|
+
values.push({ path: `${prefix}.mcuDate`, value: parsedData.mcu_date });
|
|
112
|
+
}
|
|
113
|
+
if (config.bms_name) {
|
|
114
|
+
values.push({ path: `${prefix}.bmsName`, value: parsedData.bms_name });
|
|
115
|
+
}
|
|
116
|
+
if (config.addr) {
|
|
117
|
+
values.push({ path: `${prefix}.address`, value: parsedData.addr });
|
|
118
|
+
}
|
|
119
|
+
if (config.tunit) {
|
|
120
|
+
values.push({ path: `${prefix}.temperatureUnit`, value: parsedData.tunit });
|
|
121
|
+
}
|
|
122
|
+
if (config.Ah) {
|
|
123
|
+
values.push({ path: `${prefix}.Ah`, value: parsedData.Ah });
|
|
124
|
+
}
|
|
125
|
+
//if (config.cur) {
|
|
126
|
+
// values.push({ path: `${prefix}.current2`, value: parsedData.cur });
|
|
127
|
+
//}
|
|
128
|
+
if (config.new_log_every_midnight) {
|
|
129
|
+
values.push({ path: `${prefix}.newLogEveryMidnight`, value: parsedData.new_log_every_midnight });
|
|
130
|
+
}
|
|
131
|
+
if (config.out) {
|
|
132
|
+
values.push({ path: `${prefix}.outputStatus`, value: parsedData.out });
|
|
133
|
+
}
|
|
134
|
+
if (parsedData.err && config.errorGroup) {
|
|
135
|
+
Object.entries(parsedData.err).forEach(([childKey, childValue]) => {
|
|
136
|
+
values.push({ path: `${prefix}.err.${childKey}`, value: childValue });
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (parsedData.nnc && config.nncGroup) {
|
|
140
|
+
Object.entries(parsedData.nnc).forEach(([childKey, childValue]) => {
|
|
141
|
+
values.push({ path: `${prefix}.nnc.${childKey}`, value: childValue });
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (parsedData.vnc && config.vncGroup) {
|
|
145
|
+
Object.entries(parsedData.vnc).forEach(([childKey, childValue]) => {
|
|
146
|
+
values.push({ path: `${prefix}.vnc.${childKey}`, value: childValue });
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (parsedData.toor && config.toorGroup) {
|
|
150
|
+
Object.entries(parsedData.toor).forEach(([childKey, childValue]) => {
|
|
151
|
+
values.push({ path: `${prefix}.toor.${childKey}`, value: childValue });
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (parsedData.baud && config.baudGroup) {
|
|
155
|
+
Object.entries(parsedData.baud).forEach(([childKey, childValue]) => {
|
|
156
|
+
values.push({ path: `${prefix}.${childKey}`, value: childValue });
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return values;
|
|
160
|
+
};
|
package/status.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module.exports = function(parsedData, prefix, config) {
|
|
2
|
+
const values = [];
|
|
3
|
+
if (!parsedData.bms_array || !parsedData.bms_array.master) return values;
|
|
4
|
+
|
|
5
|
+
const master = parsedData.bms_array.master;
|
|
6
|
+
|
|
7
|
+
// convert time into seconds
|
|
8
|
+
function extractMinutes(timeString) {
|
|
9
|
+
if (!timeString) return null;
|
|
10
|
+
const match = timeString.match(/(\d+)\s*h\s*(\d+)?\s*min/);
|
|
11
|
+
if (match) {
|
|
12
|
+
const hours = parseInt(match[1], 10);
|
|
13
|
+
const minutes = match[2] ? parseInt(match[2], 10) : 0;
|
|
14
|
+
return hours * 60 + minutes;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// derived data - NOT FROM BMS
|
|
20
|
+
if (config.wbat) {
|
|
21
|
+
const power = (master.vbat !== undefined && master.ibat !== undefined)
|
|
22
|
+
? master.vbat * master.ibat : null;
|
|
23
|
+
values.push({ path: `${prefix}.power`, value: power });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// data from WiFi module WebSocket under SETTINGS message
|
|
27
|
+
if (config.mincell) {
|
|
28
|
+
values.push({ path: `${prefix}.minCellVoltage`, value: master.mincell });
|
|
29
|
+
}
|
|
30
|
+
if (config.maxcell) {
|
|
31
|
+
values.push({ path: `${prefix}.maxCellVoltage`, value: master.maxcell });
|
|
32
|
+
}
|
|
33
|
+
if (config.cvd) {
|
|
34
|
+
values.push({ path: `${prefix}.cellVoltageDifference`, value: master.maxcell - master.mincell });
|
|
35
|
+
}
|
|
36
|
+
if (config.ibat) {
|
|
37
|
+
values.push({ path: `${prefix}.current`, value: master.ibat });
|
|
38
|
+
}
|
|
39
|
+
if (config.tmax) {
|
|
40
|
+
values.push({ path: `${prefix}.temperature`, value: master.tmax });
|
|
41
|
+
}
|
|
42
|
+
if (config.vbat) {
|
|
43
|
+
values.push({ path: `${prefix}.voltage`, value: master.vbat });
|
|
44
|
+
}
|
|
45
|
+
if (config.soc) {
|
|
46
|
+
values.push({ path: `${prefix}.stateOfCharge`, value: master.soc });
|
|
47
|
+
}
|
|
48
|
+
if (config.soh) {
|
|
49
|
+
values.push({ path: `${prefix}.stateOfHealth`, value: master.soh });
|
|
50
|
+
}
|
|
51
|
+
if (config.time_remaining) {
|
|
52
|
+
values.push({ path: `${prefix}.timeRemaining`, value: extractMinutes(master.time_remaining) });
|
|
53
|
+
}
|
|
54
|
+
if (config.st_naprav) {
|
|
55
|
+
values.push({ path: `${prefix}.chargeDirection`, value: master.st_naprav });
|
|
56
|
+
}
|
|
57
|
+
if (master.erro) {
|
|
58
|
+
if (config.erro_present) {
|
|
59
|
+
values.push({ path: `${prefix}.errorPresent`, value: master.erro.present });
|
|
60
|
+
}
|
|
61
|
+
if (config.erro_addr) {
|
|
62
|
+
values.push({ path: `${prefix}.errorAddress`, value: master.erro.addr });
|
|
63
|
+
}
|
|
64
|
+
if (config.erro_st) {
|
|
65
|
+
values.push({ path: `${prefix}.errorStatus`, value: master.erro.st });
|
|
66
|
+
}
|
|
67
|
+
if (config.erro_con_st) {
|
|
68
|
+
values.push({ path: `${prefix}.errorConnection`, value: master.erro.con_st });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (config.error) {
|
|
72
|
+
values.push({ path: `${prefix}.errorMessage`, value: master.error });
|
|
73
|
+
}
|
|
74
|
+
if (config.mcu_time) {
|
|
75
|
+
values.push({ path: `${prefix}.mcuTime`, value: master.time });
|
|
76
|
+
}
|
|
77
|
+
if (config.mcu_date) {
|
|
78
|
+
values.push({ path: `${prefix}.mcuDate`, value: master.date });
|
|
79
|
+
}
|
|
80
|
+
if (parsedData.bms_array.slave && parsedData.bms_array.slave["0"]) {
|
|
81
|
+
const slave = parsedData.bms_array.slave["0"];
|
|
82
|
+
|
|
83
|
+
if (config.slaveAddress) {
|
|
84
|
+
values.push({ path: `${prefix}.slaveAddress`, value: slave.address });
|
|
85
|
+
}
|
|
86
|
+
if (config.st_temp) {
|
|
87
|
+
values.push({ path: `${prefix}.slaveTempSensorStatus`, value: slave.st_temp });
|
|
88
|
+
}
|
|
89
|
+
if (config.temp_bms) {
|
|
90
|
+
values.push({ path: `${prefix}.bmsTemperature`, value: slave.temp_bms });
|
|
91
|
+
}
|
|
92
|
+
if (config.st_celic) {
|
|
93
|
+
values.push({ path: `${prefix}.slaveCellCount`, value: slave.st_celic });
|
|
94
|
+
}
|
|
95
|
+
if (config.temp) {
|
|
96
|
+
Object.entries(slave.temp || {}).forEach(([key, value], index) => {
|
|
97
|
+
values.push({ path: `${prefix}.cellTemperature${index + 1}`, value });
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (config.res) {
|
|
101
|
+
Object.entries(slave.res || {}).forEach(([key, value], index) => {
|
|
102
|
+
values.push({ path: `${prefix}.cellResistance${index + 1}`, value });
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (config.nap) {
|
|
106
|
+
Object.entries(slave.nap || {}).forEach(([key, value], index) => {
|
|
107
|
+
values.push({ path: `${prefix}.cellVoltage${index + 1}`, value });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return values;
|
|
112
|
+
};
|