feat(solarman): add Modbus RTU frame with CRC16
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b99414d829
commit
04a17041ec
2 files changed
+101
No files matched your search
@@ -0,0 +1,54 @@
|
||||
package home.gateway.adapters.solarman;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** Modbus RTU frame: unit id, PDU, CRC16 with the low byte first. */
|
||||
final class ModbusRtuFrame {
|
||||
|
||||
private static final int CRC_LENGTH = 2;
|
||||
private static final int MIN_LENGTH = 1 + 1 + CRC_LENGTH;
|
||||
|
||||
private ModbusRtuFrame() {
|
||||
}
|
||||
|
||||
static byte[] build(int unitId, byte[] pdu) {
|
||||
byte[] frame = new byte[1 + pdu.length + CRC_LENGTH];
|
||||
frame[0] = (byte) unitId;
|
||||
System.arraycopy(pdu, 0, frame, 1, pdu.length);
|
||||
int crc = crc16(frame, frame.length - CRC_LENGTH);
|
||||
frame[frame.length - 2] = (byte) crc;
|
||||
frame[frame.length - 1] = (byte) (crc >> 8);
|
||||
return frame;
|
||||
}
|
||||
|
||||
static boolean hasValidCrc(byte[] frame) {
|
||||
if (frame.length < MIN_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
int crc = crc16(frame, frame.length - CRC_LENGTH);
|
||||
return frame[frame.length - 2] == (byte) crc && frame[frame.length - 1] == (byte) (crc >> 8);
|
||||
}
|
||||
|
||||
static byte[] parse(byte[] frame, int expectedUnitId) throws IOException {
|
||||
if (!hasValidCrc(frame)) {
|
||||
throw new IOException("Modbus RTU frame has a bad CRC or is too short");
|
||||
}
|
||||
if ((frame[0] & 0xFF) != expectedUnitId) {
|
||||
throw new IOException("Modbus RTU frame from unit " + (frame[0] & 0xFF) + ", expected " + expectedUnitId);
|
||||
}
|
||||
byte[] pdu = new byte[frame.length - 1 - CRC_LENGTH];
|
||||
System.arraycopy(frame, 1, pdu, 0, pdu.length);
|
||||
return pdu;
|
||||
}
|
||||
|
||||
static int crc16(byte[] data, int length) {
|
||||
int crc = 0xFFFF;
|
||||
for (int i = 0; i < length; i++) {
|
||||
crc ^= data[i] & 0xFF;
|
||||
for (int bit = 0; bit < 8; bit++) {
|
||||
crc = (crc & 1) != 0 ? (crc >>> 1) ^ 0xA001 : crc >>> 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package home.gateway.adapters.solarman;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ModbusRtuFrameTest {
|
||||
|
||||
private static final byte[] READ_10_AT_0 = {0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, (byte) 0xC5, (byte) 0xCD};
|
||||
|
||||
@Test
|
||||
void buildsTheStandardExampleWithCrcLowByteFirst() {
|
||||
byte[] pdu = {0x03, 0x00, 0x00, 0x00, 0x0A};
|
||||
assertArrayEquals(READ_10_AT_0, ModbusRtuFrame.build(1, pdu));
|
||||
}
|
||||
|
||||
@Test
|
||||
void crcOfAKnownResponse() {
|
||||
byte[] frame = {0x01, 0x04, 0x02, (byte) 0xFF, (byte) 0xFF};
|
||||
assertEquals(0x80B8, ModbusRtuFrame.crc16(frame, frame.length));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parsesBackThePdu() throws IOException {
|
||||
assertArrayEquals(new byte[] {0x03, 0x00, 0x00, 0x00, 0x0A}, ModbusRtuFrame.parse(READ_10_AT_0, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsBadCrc() {
|
||||
byte[] frame = READ_10_AT_0.clone();
|
||||
frame[7] ^= 1;
|
||||
assertThrows(IOException.class, () -> ModbusRtuFrame.parse(frame, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsOtherUnit() {
|
||||
assertThrows(IOException.class, () -> ModbusRtuFrame.parse(READ_10_AT_0, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsTooShortFrame() {
|
||||
assertThrows(IOException.class, () -> ModbusRtuFrame.parse(new byte[] {0x01, 0x03}, 1));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user