Skip to content

Commit f59159e

Browse files
committed
added JBD correct checksum calculation and tests
1 parent 775a826 commit f59159e

3 files changed

Lines changed: 221 additions & 25 deletions

File tree

bms-jbd-rs485/src/main/java/com/airepublic/bmstoinverter/bms/jbd/rs485/JBDBmsRS485Descriptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public Class<? extends BMS> getBMSClass() {
4242

4343
@Override
4444
public Port createPort(final BMSConfig config) {
45-
final Port port = new JSerialCommPort(config.getPortLocator(), config.getBaudRate(), 8, 1, SerialPort.NO_PARITY, new byte[] { (byte) 165 }, FrameDefinition.create("SACLDVO"));
45+
final Port port = new JSerialCommPort(config.getPortLocator(), config.getBaudRate(), 8, 1, SerialPort.NO_PARITY, new byte[] { (byte) 0xDD }, FrameDefinition.create("SCOLDVVO"));
4646
return port;
4747
}
4848

bms-jbd-rs485/src/main/java/com/airepublic/bmstoinverter/bms/jbd/rs485/JBDBmsRS485Processor.java

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,12 @@ public class JBDBmsRS485Processor extends BMS {
3939
return false;
4040
}
4141

42-
final int command = buffer.get(1);
43-
final int length = buffer.get(3);
44-
final byte[] data = new byte[length];
45-
buffer.get(data, 4, length);
46-
47-
// calculate checksum
48-
int sum = 0x0000;
49-
sum -= (byte) command;
50-
51-
for (final byte element : data) {
52-
sum -= element;
53-
}
42+
buffer.rewind();
43+
final byte[] data = buffer.array();
44+
final short checksum = compute(data);
5445

5546
// compare the checksum
56-
return buffer.get(length + 4) == (byte) (sum >> 8 & 0xFF) && buffer.get(length + 5) == (byte) (sum & 0xFF);
47+
return data[data.length - 3] == (byte) (checksum >> 8 & 0x00FF) && data[data.length - 2] == (byte) (checksum & 0x00FF);
5748
};
5849

5950
public static class DataEntry {
@@ -94,7 +85,7 @@ public void setData(final ByteBuffer data) {
9485
}
9586

9687
@Override
97-
protected void collectData(final Port port) throws TooManyInvalidFramesException, NoDataAvailableException, IOException {
88+
public void collectData(final Port port) throws TooManyInvalidFramesException, NoDataAvailableException, IOException {
9889
sendMessage(port, 0x03);
9990
sendMessage(port, 0x04);
10091
sendMessage(port, 0x05);
@@ -135,8 +126,9 @@ private List<ByteBuffer> sendMessage(final Port port, final int command) throws
135126
if (valid) {
136127
receiveBuffer.rewind();
137128
final int length = receiveBuffer.get(3);
138-
receiveBuffer.position(3);
129+
receiveBuffer.position(4);
139130
final byte[] dataBytes = new byte[length];
131+
receiveBuffer.get(dataBytes);
140132
final ByteBuffer data = ByteBuffer.wrap(dataBytes).order(ByteOrder.BIG_ENDIAN);
141133

142134
switch (receiveBuffer.get(1)) {
@@ -151,6 +143,7 @@ private List<ByteBuffer> sendMessage(final Port port, final int command) throws
151143
}
152144
break;
153145
case 0x05: {
146+
readHardwareVersion(pack, data);
154147
done = true;
155148
}
156149
break;
@@ -213,54 +206,73 @@ private List<ByteBuffer> sendMessage(final Port port, final int command) throws
213206
private void readStatus(final BatteryPack pack, final ByteBuffer data) {
214207
// total voltage (10mV)
215208
pack.packVoltage = data.getShort() / 10;
209+
LOG.debug("Pack voltage: {} V", pack.packVoltage / 10.0);
216210
// current (10mAh)
217211
pack.packCurrent = data.getShort() / 10;
212+
LOG.debug("Pack current: {} A", pack.packCurrent / 10.0);
218213
// remaining capacity (10mAh)
219214
pack.remainingCapacitymAh = data.getShort() * 10;
215+
LOG.debug("Remaining capacity: {} Ah", pack.remainingCapacitymAh / 1000.0);
220216
// nominal capacity (10mAh)
221217
pack.ratedCapacitymAh = data.getShort() * 10;
218+
LOG.debug("Rated capacity: {} Ah", pack.ratedCapacitymAh / 1000.0);
222219
// bms cycles
223220
pack.bmsCycles = data.getShort();
221+
LOG.debug("BMS Cycles: {}", pack.bmsCycles);
224222
// production date (not mapped)
225223
data.getShort();
226224
// balancing states
227225
final int balanceStates = data.getInt();
228226

229227
for (int idx = 0; idx < 32; idx++) {
230228
pack.cellBalanceState[idx] = BitUtil.bit(balanceStates, idx);
229+
LOG.debug("Cell {} balance state: {}", idx + 1, pack.cellBalanceState[idx]);
231230
}
232231

233232
// protection status
234233
readAlarms(pack, data.getShort());
235234
// software version
236235
final byte sw = data.get();
237236
pack.softwareVersion = String.format("%02X", sw >> 4) + "." + String.format("%02X", sw & 0x0F);
237+
LOG.debug("Software version: {}", pack.softwareVersion);
238238

239239
// remaining SOC (1%)
240240
pack.packSOC = data.get() * 10;
241+
LOG.debug("Pack SOC: {} %", pack.packSOC / 10.0);
241242

242243
final byte mosState = data.get();
243244
pack.chargeMOSState = BitUtil.bit(mosState, 0);
245+
LOG.debug("Charge MOS State: {}", pack.chargeMOSState);
244246
pack.dischargeMOSState = BitUtil.bit(mosState, 1);
247+
LOG.debug("Discharge MOS State: {}", pack.dischargeMOSState);
245248

246249
// number of battery strings
247250
data.get();
248251

249252
// number of temperature sensors
250253
pack.numOfTempSensors = data.get();
254+
LOG.debug("Number of temperature sensors: {}", pack.numOfTempSensors);
251255

252-
// temperature
253-
pack.tempAverage = 0;
254-
for (int i = 0; i < pack.numOfTempSensors; i++) {
255-
pack.tempAverage += data.getShort() - 2731;
256+
if (pack.numOfTempSensors != 0) {
257+
// temperature
258+
pack.tempAverage = 0;
259+
260+
for (int i = 0; i < pack.numOfTempSensors; i++) {
261+
pack.tempAverage += data.getShort() - 2731;
262+
}
263+
264+
pack.tempAverage = pack.tempAverage / pack.numOfTempSensors;
265+
LOG.debug("Average temperature: {} °C", pack.tempAverage / 10.0);
256266
}
257-
pack.tempAverage = pack.tempAverage / pack.numOfTempSensors;
258267
}
259268

260269

261270
private void readCellVoltages(final BatteryPack pack, final ByteBuffer data) {
262-
for (int cellNo = 0; cellNo < data.capacity(); cellNo++) {
271+
pack.numberOfCells = data.capacity() / 2;
272+
273+
for (int cellNo = 0; cellNo < pack.numberOfCells; cellNo++) {
263274
pack.cellVmV[cellNo] = data.getShort();
275+
LOG.debug("Cell {} voltage: {} V", cellNo + 1, pack.cellVmV[cellNo] / 1000.0);
264276
}
265277
}
266278

@@ -271,6 +283,18 @@ private void readAlarms(final BatteryPack pack, final short short1) {
271283
}
272284

273285

286+
private void readHardwareVersion(final BatteryPack pack, final ByteBuffer data) {
287+
String hardwareVersion = "";
288+
289+
for (int i = 0; i < data.capacity(); i++) {
290+
hardwareVersion += (char) data.get(); // ascii chars
291+
}
292+
293+
pack.hardwareVersion = hardwareVersion;
294+
LOG.debug("Hardware version: {}", pack.hardwareVersion);
295+
}
296+
297+
274298
ByteBuffer prepareSendFrame(final int command, final int length, final byte[] data) {
275299
final ByteBuffer sendFrame = ByteBuffer.allocate(7 + length).order(ByteOrder.BIG_ENDIAN);
276300
sendFrame.put((byte) 0xDD); // Start flag
@@ -295,9 +319,35 @@ ByteBuffer prepareSendFrame(final int command, final int length, final byte[] da
295319
}
296320

297321

322+
public static short compute(final byte[] frame) {
323+
if (frame == null || frame.length < 5) {
324+
throw new IllegalArgumentException("Invalid frame or index range");
325+
}
326+
327+
short sum = 0;
328+
for (int i = 2; i < frame.length - 3; i++) {
329+
sum += frame[i] & 0xFF;
330+
}
331+
332+
sum &= 0xFFFF;
333+
final int checksum = ~sum + 1 & 0xFFFF;
334+
335+
return (short) checksum;
336+
}
337+
338+
298339
public static void main(final String[] args) {
299-
final JBDBmsRS485Processor p = new JBDBmsRS485Processor();
300-
final ByteBuffer sendFrame = p.prepareSendFrame(0x03, 0, new byte[] {});
301-
System.out.println(Port.printBuffer(sendFrame));
340+
final byte[] frame = new byte[] {
341+
(byte) 0xDD, (byte) 0x03, (byte) 0x00, (byte) 0x28, (byte) 0x15, (byte) 0x84, (byte) 0x03, (byte) 0xA4,
342+
(byte) 0xC8, (byte) 0xE3, (byte) 0x80, (byte) 0xE8, (byte) 0x00, (byte) 0x10, (byte) 0x31, (byte) 0x3D,
343+
(byte) 0xBA, (byte) 0xAA, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x54, (byte) 0x64,
344+
(byte) 0xCB, (byte) 0x10, (byte) 0x04, (byte) 0x0B, (byte) 0x49, (byte) 0x0B, (byte) 0x62, (byte) 0x0B,
345+
(byte) 0xFD, (byte) 0x0B, (byte) 0x57, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xE8,
346+
(byte) 0xC8, (byte) 0xE3, (byte) 0x00, (byte) 0x00, (byte) 0xF4, (byte) 0xCE, (byte) 0x77
347+
};
348+
349+
final short checksum = compute(frame);
350+
System.out.printf("Calculated checksum: 0x%02X%n", checksum & 0xFFFF);
302351
}
352+
303353
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package com.airepublic.bmstoinverter.jbd.rs485;
2+
3+
import java.io.IOException;
4+
import java.nio.ByteBuffer;
5+
6+
import org.junit.jupiter.api.Assertions;
7+
import org.junit.jupiter.api.BeforeAll;
8+
import org.junit.jupiter.api.Test;
9+
import org.mockito.Mockito;
10+
11+
import com.airepublic.bmstoinverter.bms.jbd.rs485.JBDBmsRS485Descriptor;
12+
import com.airepublic.bmstoinverter.bms.jbd.rs485.JBDBmsRS485Processor;
13+
import com.airepublic.bmstoinverter.core.BMSConfig;
14+
import com.airepublic.bmstoinverter.core.NoDataAvailableException;
15+
import com.airepublic.bmstoinverter.core.Port;
16+
import com.airepublic.bmstoinverter.core.protocol.rs485.FrameDefinition;
17+
import com.airepublic.bmstoinverter.protocol.rs485.JSerialCommPort;
18+
import com.fazecast.jSerialComm.SerialPort;
19+
import com.fazecast.jSerialComm.SerialPortEvent;
20+
21+
public class JBDBmsRS485ProcessorTest {
22+
private final static JBDBmsRS485Processor processor = new JBDBmsRS485Processor();
23+
private final static JBDBmsRS485Descriptor descriptor = new JBDBmsRS485Descriptor() {
24+
@Override
25+
public Port createPort(final BMSConfig config) {
26+
return port;
27+
}
28+
};
29+
30+
private final static BMSConfig bmsConfig = new BMSConfig(1, "port0", 9600, 200, descriptor);
31+
private final static JSerialCommPort port = new JSerialCommPort(bmsConfig.getPortLocator(), bmsConfig.getBaudRate(), 8, 1, SerialPort.NO_PARITY, new byte[] { (byte) 0xDD }, FrameDefinition.create("SCOLDVVO")) {
32+
@Override
33+
public boolean isOpen() {
34+
return true;
35+
}
36+
37+
38+
@Override
39+
public void sendFrame(final ByteBuffer frame) throws IOException {
40+
// do nothing
41+
}
42+
43+
44+
@Override
45+
public void clearBuffers() {
46+
// do nothing
47+
}
48+
};
49+
private final SerialPort serialPort = Mockito.mock(SerialPort.class);
50+
51+
@BeforeAll
52+
public static void setupOnce() {
53+
processor.initialize(bmsConfig);
54+
}
55+
56+
57+
@Test
58+
public void testReadFrame03() throws Throwable {
59+
final byte[] frame = new byte[] {
60+
(byte) 0xDD, 0x03, 0x00, 0x1B, 0x17, 0x00, 0x00, 0x00, 0x02, (byte) 0xD0, 0x03, (byte) 0xE8,
61+
0x00, 0x00, 0x20, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x48,
62+
0x03, 0x0F, 0x02, 0x0B, 0x76, 0x0B, (byte) 0x82, (byte) 0xFB, (byte) 0xFF, 0x77
63+
};
64+
final SerialPortEvent event = new SerialPortEvent(serialPort, SerialPort.LISTENING_EVENT_DATA_RECEIVED, frame);
65+
port.serialEvent(event);
66+
67+
// expect exception collecting data
68+
Assertions.assertThrowsExactly(NoDataAvailableException.class, () -> {
69+
processor.collectData(port);
70+
});
71+
72+
Assertions.assertEquals(588, processor.getBatteryPack(0).packVoltage);
73+
Assertions.assertEquals(720, processor.getBatteryPack(0).packSOC);
74+
Assertions.assertEquals(209, processor.getBatteryPack(0).tempAverage);
75+
}
76+
77+
78+
@Test
79+
public void testReadFrame04() throws Throwable {
80+
final byte[] frame = new byte[] { (byte) 0xDD, (byte) 0x04, (byte) 0x00, (byte) 0x1E, (byte) 0x0F, (byte) 0x66, (byte) 0x0F, (byte) 0x63, (byte) 0x0F, (byte) 0x63, (byte) 0x0F, (byte) 0x64, (byte) 0x0F, (byte) 0x3E, (byte) 0x0F, (byte) 0x63, (byte) 0x0F, (byte) 0x37, (byte) 0x0F, (byte) 0x5B, (byte) 0x0F, (byte) 0x65, (byte) 0x0F, (byte) 0x3B, (byte) 0x0F, (byte) 0x63, (byte) 0x0F, (byte) 0x63, (byte) 0x0F, (byte) 0x3C, (byte) 0x0F,
81+
(byte) 0x66, (byte) 0x0F, (byte) 0x3D, (byte) 0xF9, (byte) 0xF9, (byte) 0x77 };
82+
final SerialPortEvent event = new SerialPortEvent(serialPort, SerialPort.LISTENING_EVENT_DATA_RECEIVED, frame);
83+
port.serialEvent(event);
84+
85+
// expect exception collecting data
86+
Assertions.assertThrowsExactly(NoDataAvailableException.class, () -> {
87+
processor.collectData(port);
88+
});
89+
90+
Assertions.assertEquals(3900, processor.getBatteryPack(0).cellVmV[12]);
91+
}
92+
93+
94+
@Test
95+
public void testReadFrame05() throws Throwable {
96+
final byte[] frame = new byte[] { (byte) 0xDD, (byte) 0x05, (byte) 0x00, (byte) 0x0A, (byte) 0x30, (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x35, (byte) 0x36, (byte) 0x37, (byte) 0x38, (byte) 0x39, (byte) 0xFD, (byte) 0xE9, (byte) 0x77 };
97+
final SerialPortEvent event = new SerialPortEvent(serialPort, SerialPort.LISTENING_EVENT_DATA_RECEIVED, frame);
98+
port.serialEvent(event);
99+
100+
// expect exception collecting data
101+
Assertions.assertThrowsExactly(NoDataAvailableException.class, () -> {
102+
processor.collectData(port);
103+
});
104+
105+
Assertions.assertEquals("0123456789", processor.getBatteryPack(0).hardwareVersion);
106+
}
107+
108+
109+
@Test
110+
public void testChecksum03() {
111+
final byte[] frame = new byte[] {
112+
(byte) 0xDD, 0x03, 0x00, 0x1B, 0x17, 0x00, 0x00, 0x00, 0x02, (byte) 0xD0, 0x03, (byte) 0xE8,
113+
0x00, 0x00, 0x20, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x48,
114+
0x03, 0x0F, 0x02, 0x0B, 0x76, 0x0B, (byte) 0x82, (byte) 0xFB, (byte) 0xFF, 0x77
115+
};
116+
117+
final short checksum = JBDBmsRS485Processor.compute(frame);
118+
119+
System.out.printf("Calculated checksum = 0x%04X%n", checksum);
120+
Assertions.assertEquals((short) 0xFBFF, checksum);
121+
}
122+
123+
124+
@Test
125+
public void testChecksum04() {
126+
final byte[] frame = new byte[] { (byte) 0xDD, (byte) 0x04, (byte) 0x00, (byte) 0x1E, (byte) 0x0F, (byte) 0x66, (byte) 0x0F, (byte) 0x63, (byte) 0x0F, (byte) 0x63, (byte) 0x0F, (byte) 0x64, (byte) 0x0F, (byte) 0x3E, (byte) 0x0F, (byte) 0x63, (byte) 0x0F, (byte) 0x37, (byte) 0x0F, (byte) 0x5B, (byte) 0x0F, (byte) 0x65, (byte) 0x0F, (byte) 0x3B, (byte) 0x0F, (byte) 0x63, (byte) 0x0F, (byte) 0x63, (byte) 0x0F, (byte) 0x3C, (byte) 0x0F,
127+
(byte) 0x66, (byte) 0x0F, (byte) 0x3D, (byte) 0xF9, (byte) 0xF9, (byte) 0x77 };
128+
129+
final short checksum = JBDBmsRS485Processor.compute(frame);
130+
131+
System.out.printf("Calculated checksum = 0x%04X%n", checksum);
132+
Assertions.assertEquals((short) 0xF9F9, checksum);
133+
}
134+
135+
136+
@Test
137+
public void testChecksum05() {
138+
final byte[] frame = new byte[] { (byte) 0xDD, (byte) 0x05, (byte) 0x00, (byte) 0x0A, (byte) 0x30, (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x35, (byte) 0x36, (byte) 0x37, (byte) 0x38, (byte) 0x39, (byte) 0xFD, (byte) 0xE9, (byte) 0x77 };
139+
140+
final short checksum = JBDBmsRS485Processor.compute(frame);
141+
142+
System.out.printf("Calculated checksum = 0x%04X%n", checksum);
143+
Assertions.assertEquals((short) 0xFDE9, checksum);
144+
}
145+
146+
}

0 commit comments

Comments
 (0)