From 9b3adfc3727e08947c6d57421c1c947e9c8e1a45 Mon Sep 17 00:00:00 2001 From: pali Date: Sat, 24 Apr 2021 13:43:01 +0300 Subject: [PATCH] [nibeheatpump] Data parsing fix (#9958) * Fixed escaped message parsing * Removed also Apache commons dependency * Generic improvements Signed-off-by: Pauli Anttila --- .../internal/connection/SerialConnector.java | 6 +- .../message/ModbusDataReadOutMessage.java | 64 ++++----- .../message/ModbusReadRequestMessage.java | 8 +- .../message/ModbusReadResponseMessage.java | 39 ++---- .../message/ModbusWriteRequestMessage.java | 19 +-- .../message/ModbusWriteResponseMessage.java | 21 +-- .../message/NibeHeatPumpBaseMessage.java | 14 +- .../protocol/NibeHeatPumpProtocol.java | 127 ++++++++++++------ .../protocol/NibeHeatPumpProtocolStates.java | 6 +- .../message/ModbusDataReadOutMessageTest.java | 31 +++++ .../ModbusReadResponseMessageTest.java | 35 +++-- 11 files changed, 208 insertions(+), 162 deletions(-) diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/connection/SerialConnector.java b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/connection/SerialConnector.java index d1db2ebb0..f3a4c3eb8 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/connection/SerialConnector.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/connection/SerialConnector.java @@ -191,7 +191,7 @@ public class SerialConnector extends NibeHeatPumpBaseConnector { @Override public void sendAck() { try { - byte addr = msg().get(NibeHeatPumpProtocol.OFFSET_ADR); + byte addr = msg().get(NibeHeatPumpProtocol.RES_OFFS_ADR); sendAckToNibe(addr); } catch (IOException e) { sendErrorToListeners(e.getMessage()); @@ -215,7 +215,7 @@ public class SerialConnector extends NibeHeatPumpBaseConnector { sendDataToNibe(writeQueue.remove(0)); } else { // no messages to send, send ack to pump - byte addr = msg().get(NibeHeatPumpProtocol.OFFSET_ADR); + byte addr = msg().get(NibeHeatPumpProtocol.RES_OFFS_ADR); sendAckToNibe(addr); } } catch (IOException e) { @@ -230,7 +230,7 @@ public class SerialConnector extends NibeHeatPumpBaseConnector { sendDataToNibe(readQueue.remove(0)); } else { // no messages to send, send ack to pump - byte addr = msg().get(NibeHeatPumpProtocol.OFFSET_ADR); + byte addr = msg().get(NibeHeatPumpProtocol.RES_OFFS_ADR); sendAckToNibe(addr); } } catch (IOException e) { diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusDataReadOutMessage.java b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusDataReadOutMessage.java index 3a90e4897..1f246baf8 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusDataReadOutMessage.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusDataReadOutMessage.java @@ -25,7 +25,7 @@ import org.openhab.binding.nibeheatpump.internal.protocol.NibeHeatPumpProtocol; */ public class ModbusDataReadOutMessage extends NibeHeatPumpBaseMessage { - private List values; + private List values = new ArrayList<>(); private ModbusDataReadOutMessage(MessageBuilder builder) { super.msgType = MessageType.MODBUS_DATA_READ_OUT_MSG; @@ -42,27 +42,44 @@ public class ModbusDataReadOutMessage extends NibeHeatPumpBaseMessage { @Override public void encodeMessage(byte[] data) throws NibeHeatPumpException { - values = parseMessage(data); + if (NibeHeatPumpProtocol.isModbus40DataReadOut(data)) { + super.encodeMessage(data); + final int msglen = NibeHeatPumpProtocol.RES_HEADER_LEN + rawMessage[NibeHeatPumpProtocol.RES_OFFS_LEN]; + + values.clear(); + + try { + for (int i = NibeHeatPumpProtocol.RES_OFFS_DATA; i < (msglen - 1); i += 4) { + + int id = ((rawMessage[i + 1] & 0xFF) << 8 | (rawMessage[i + 0] & 0xFF)); + int value = (rawMessage[i + 3] & 0xFF) << 8 | (rawMessage[i + 2] & 0xFF); + + if (id != 0xFFFF) { + values.add(new ModbusValue(id, value)); + } + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new NibeHeatPumpException("Error occurred during data parsing", e); + } + } else { + throw new NibeHeatPumpException("Not Modbus data readout message"); + } } @Override public byte[] decodeMessage() { - return createDataReadOutPdu(values); - } - - private byte[] createDataReadOutPdu(List values) { byte datalen = (byte) (values.size() * 4); - byte msglen = (byte) (6 + datalen); + byte msglen = (byte) (NibeHeatPumpProtocol.RES_HEADER_LEN + datalen + NibeHeatPumpProtocol.PDU_CHECKSUM_LEN); byte[] data = new byte[msglen]; - data[0] = NibeHeatPumpProtocol.FRAME_START_CHAR_FROM_NIBE; + data[0] = NibeHeatPumpProtocol.FRAME_START_CHAR_RES; data[1] = 0x00; data[2] = NibeHeatPumpProtocol.ADR_MODBUS40; data[3] = NibeHeatPumpProtocol.CMD_MODBUS_DATA_MSG; data[4] = datalen; - int i = NibeHeatPumpProtocol.OFFSET_DATA; + int i = NibeHeatPumpProtocol.RES_OFFS_DATA; for (ModbusValue value : values) { @@ -77,7 +94,6 @@ public class ModbusDataReadOutMessage extends NibeHeatPumpBaseMessage { } data[msglen - 1] = NibeHeatPumpProtocol.calculateChecksum(data, 2, msglen); - return data; } @@ -89,34 +105,6 @@ public class ModbusDataReadOutMessage extends NibeHeatPumpBaseMessage { return str; } - private List parseMessage(byte[] data) throws NibeHeatPumpException { - if (NibeHeatPumpProtocol.isModbus40DataReadOut(data)) { - super.encodeMessage(data); - final int msglen = 5 + rawMessage[NibeHeatPumpProtocol.OFFSET_LEN]; - - List vals = new ArrayList<>(); - - try { - for (int i = NibeHeatPumpProtocol.OFFSET_DATA; i < (msglen - 1); i += 4) { - - int id = ((rawMessage[i + 1] & 0xFF) << 8 | (rawMessage[i + 0] & 0xFF)); - int value = (rawMessage[i + 3] & 0xFF) << 8 | (rawMessage[i + 2] & 0xFF); - - if (id != 0xFFFF) { - vals.add(new ModbusValue(id, value)); - } - } - } catch (ArrayIndexOutOfBoundsException e) { - throw new NibeHeatPumpException("Error occurred during data parsing", e); - } - - return vals; - - } else { - throw new NibeHeatPumpException("Not Modbus data readout message"); - } - } - public static class MessageBuilder { private List values = new ArrayList<>(); diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadRequestMessage.java b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadRequestMessage.java index 797705056..b8230659e 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadRequestMessage.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadRequestMessage.java @@ -41,7 +41,7 @@ public class ModbusReadRequestMessage extends NibeHeatPumpBaseMessage { public void encodeMessage(byte[] data) throws NibeHeatPumpException { if (NibeHeatPumpProtocol.isModbus40ReadRequestPdu(data)) { super.encodeMessage(data); - coilAddress = (data[4] & 0xFF) << 8 | (data[3] & 0xFF); + coilAddress = (rawMessage[4] & 0xFF) << 8 | (rawMessage[3] & 0xFF); } else { throw new NibeHeatPumpException("Not Read Request message"); } @@ -49,12 +49,8 @@ public class ModbusReadRequestMessage extends NibeHeatPumpBaseMessage { @Override public byte[] decodeMessage() { - return createModbus40ReadPdu(coilAddress); - } - - private byte[] createModbus40ReadPdu(int coilAddress) { byte[] data = new byte[6]; - data[0] = NibeHeatPumpProtocol.FRAME_START_CHAR_TO_NIBE; + data[0] = NibeHeatPumpProtocol.FRAME_START_CHAR_REQ; data[1] = NibeHeatPumpProtocol.CMD_MODBUS_READ_REQ; data[2] = (byte) 0x02; // data len data[3] = (byte) (coilAddress & 0xFF); diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadResponseMessage.java b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadResponseMessage.java index 8ec86cbe9..8f852bb2f 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadResponseMessage.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadResponseMessage.java @@ -58,21 +58,25 @@ public class ModbusReadResponseMessage extends NibeHeatPumpBaseMessage { @Override public void encodeMessage(byte[] data) throws NibeHeatPumpException { - super.encodeMessage(data); + if (NibeHeatPumpProtocol.isModbus40ReadResponse(data)) { + super.encodeMessage(data); + coilAddress = ((rawMessage[NibeHeatPumpProtocol.RES_OFFS_DATA + 1] & 0xFF) << 8 + | (rawMessage[NibeHeatPumpProtocol.RES_OFFS_DATA + 0] & 0xFF)); + value = (rawMessage[NibeHeatPumpProtocol.RES_OFFS_DATA + 5] & 0xFF) << 24 + | (rawMessage[NibeHeatPumpProtocol.RES_OFFS_DATA + 4] & 0xFF) << 16 + | (rawMessage[NibeHeatPumpProtocol.RES_OFFS_DATA + 3] & 0xFF) << 8 + | (rawMessage[NibeHeatPumpProtocol.RES_OFFS_DATA + 2] & 0xFF); - coilAddress = (data[3] & 0xFF) << 8 | (data[4] & 0xFF); - parseMessage(data); + } else { + throw new NibeHeatPumpException("Not Read Response message"); + } } @Override public byte[] decodeMessage() { - return createModbusReadResponsePdu(coilAddress, value); - } - - private byte[] createModbusReadResponsePdu(int coilAddress, int value) { byte[] data = new byte[12]; - data[0] = NibeHeatPumpProtocol.FRAME_START_CHAR_FROM_NIBE; + data[0] = NibeHeatPumpProtocol.FRAME_START_CHAR_RES; data[1] = 0x00; data[2] = NibeHeatPumpProtocol.ADR_MODBUS40; data[3] = NibeHeatPumpProtocol.CMD_MODBUS_READ_RESP; @@ -93,30 +97,13 @@ public class ModbusReadResponseMessage extends NibeHeatPumpBaseMessage { @Override public String toString() { - String str = ""; - - str += super.toString(); + String str = super.toString(); str += ", Coil address = " + coilAddress; str += ", Value = " + value; return str; } - private void parseMessage(byte[] data) throws NibeHeatPumpException { - if (NibeHeatPumpProtocol.isModbus40ReadResponse(data)) { - super.encodeMessage(data); - coilAddress = ((data[NibeHeatPumpProtocol.OFFSET_DATA + 1] & 0xFF) << 8 - | (data[NibeHeatPumpProtocol.OFFSET_DATA + 0] & 0xFF)); - value = (data[NibeHeatPumpProtocol.OFFSET_DATA + 5] & 0xFF) << 24 - | (data[NibeHeatPumpProtocol.OFFSET_DATA + 4] & 0xFF) << 16 - | (data[NibeHeatPumpProtocol.OFFSET_DATA + 3] & 0xFF) << 8 - | (data[NibeHeatPumpProtocol.OFFSET_DATA + 2] & 0xFF); - - } else { - throw new NibeHeatPumpException("Not Read Response message"); - } - } - public static class MessageBuilder { private int coilAddress; private int value; diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusWriteRequestMessage.java b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusWriteRequestMessage.java index ade7e176a..88339093f 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusWriteRequestMessage.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusWriteRequestMessage.java @@ -55,8 +55,9 @@ public class ModbusWriteRequestMessage extends NibeHeatPumpBaseMessage { public void encodeMessage(byte[] data) throws NibeHeatPumpException { if (NibeHeatPumpProtocol.isModbus40WriteRequestPdu(data)) { super.encodeMessage(data); - coilAddress = (data[4] & 0xFF) << 8 | (data[3] & 0xFF); - value = (data[8] & 0xFF) << 24 | (data[7] & 0xFF) << 16 | (data[6] & 0xFF) << 8 | (data[5] & 0xFF); + coilAddress = (rawMessage[4] & 0xFF) << 8 | (rawMessage[3] & 0xFF); + value = (rawMessage[8] & 0xFF) << 24 | (rawMessage[7] & 0xFF) << 16 | (rawMessage[6] & 0xFF) << 8 + | (rawMessage[5] & 0xFF); } else { throw new NibeHeatPumpException("Not Write Request message"); } @@ -64,17 +65,13 @@ public class ModbusWriteRequestMessage extends NibeHeatPumpBaseMessage { @Override public byte[] decodeMessage() { - return createModbus40WritePdu(coilAddress, value); - } - - private byte[] createModbus40WritePdu(int coildAddress, int value) { byte[] data = new byte[10]; - data[0] = NibeHeatPumpProtocol.FRAME_START_CHAR_TO_NIBE; + data[0] = NibeHeatPumpProtocol.FRAME_START_CHAR_REQ; data[1] = NibeHeatPumpProtocol.CMD_MODBUS_WRITE_REQ; data[2] = (byte) 0x06; // data len - data[3] = (byte) (coildAddress & 0xFF); - data[4] = (byte) ((coildAddress >> 8) & 0xFF); + data[3] = (byte) (coilAddress & 0xFF); + data[4] = (byte) ((coilAddress >> 8) & 0xFF); data[5] = (byte) (value & 0xFF); data[6] = (byte) ((value >> 8) & 0xFF); data[7] = (byte) ((value >> 16) & 0xFF); @@ -86,9 +83,7 @@ public class ModbusWriteRequestMessage extends NibeHeatPumpBaseMessage { @Override public String toString() { - String str = ""; - - str += super.toString(); + String str = super.toString(); str += ", Coil address = " + coilAddress; str += ", Value = " + value; diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusWriteResponseMessage.java b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusWriteResponseMessage.java index d54cc0b0c..96b2bb4ab 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusWriteResponseMessage.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/ModbusWriteResponseMessage.java @@ -36,7 +36,12 @@ public class ModbusWriteResponseMessage extends NibeHeatPumpBaseMessage { @Override public void encodeMessage(byte[] data) throws NibeHeatPumpException { - result = modbus40WriteSuccess(data); + if (NibeHeatPumpProtocol.isModbus40WriteResponsePdu(data)) { + super.encodeMessage(data); + result = rawMessage[NibeHeatPumpProtocol.RES_OFFS_DATA] == 1; + } else { + throw new NibeHeatPumpException("Not Write Response message"); + } } public boolean isSuccessfull() { @@ -45,13 +50,9 @@ public class ModbusWriteResponseMessage extends NibeHeatPumpBaseMessage { @Override public byte[] decodeMessage() { - return createModbusWriteResponsePdu(result); - } - - private byte[] createModbusWriteResponsePdu(boolean result) { byte[] data = new byte[7]; - data[0] = NibeHeatPumpProtocol.FRAME_START_CHAR_FROM_NIBE; + data[0] = NibeHeatPumpProtocol.FRAME_START_CHAR_RES; data[1] = 0x00; data[2] = NibeHeatPumpProtocol.ADR_MODBUS40; data[3] = NibeHeatPumpProtocol.CMD_MODBUS_WRITE_RESP; @@ -70,14 +71,6 @@ public class ModbusWriteResponseMessage extends NibeHeatPumpBaseMessage { return str; } - private boolean modbus40WriteSuccess(byte[] data) throws NibeHeatPumpException { - if (NibeHeatPumpProtocol.isModbus40WriteResponsePdu(data)) { - super.encodeMessage(data); - return data[NibeHeatPumpProtocol.OFFSET_DATA] == 1; - } - throw new NibeHeatPumpException("Not Write Response message"); - } - public static class MessageBuilder { private boolean result; diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/NibeHeatPumpBaseMessage.java b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/NibeHeatPumpBaseMessage.java index 86b8a23e9..c6fd92cb4 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/NibeHeatPumpBaseMessage.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/message/NibeHeatPumpBaseMessage.java @@ -56,7 +56,6 @@ public abstract class NibeHeatPumpBaseMessage implements NibeHeatPumpMessage { public byte[] rawMessage; public MessageType msgType = MessageType.UNKNOWN; - public byte msgId; public NibeHeatPumpBaseMessage() { } @@ -67,12 +66,15 @@ public abstract class NibeHeatPumpBaseMessage implements NibeHeatPumpMessage { @Override public void encodeMessage(byte[] data) throws NibeHeatPumpException { - data = NibeHeatPumpProtocol.checkMessageChecksumAndRemoveDoubles(data); - rawMessage = data; - msgId = data[1]; + if (data.length >= NibeHeatPumpProtocol.PDU_MIN_LEN) { + byte[] d = NibeHeatPumpProtocol.checkMessageChecksumAndRemoveDoubles(data); + rawMessage = d.clone(); - byte messageTypeByte = NibeHeatPumpProtocol.getMessageType(data); - msgType = NibeHeatPumpBaseMessage.getMessageType(messageTypeByte); + byte messageTypeByte = NibeHeatPumpProtocol.getMessageType(d); + msgType = NibeHeatPumpBaseMessage.getMessageType(messageTypeByte); + } else { + throw new NibeHeatPumpException("Too short message"); + } } @Override diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/protocol/NibeHeatPumpProtocol.java b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/protocol/NibeHeatPumpProtocol.java index 0ef1c8101..309f5a312 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/protocol/NibeHeatPumpProtocol.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/protocol/NibeHeatPumpProtocol.java @@ -12,7 +12,8 @@ */ package org.openhab.binding.nibeheatpump.internal.protocol; -import org.apache.commons.lang3.ArrayUtils; +import java.io.ByteArrayOutputStream; + import org.openhab.binding.nibeheatpump.internal.NibeHeatPumpException; /** @@ -22,14 +23,24 @@ import org.openhab.binding.nibeheatpump.internal.NibeHeatPumpException; */ public class NibeHeatPumpProtocol { - public static final byte FRAME_START_CHAR_FROM_NIBE = (byte) 0x5C; - public static final byte FRAME_START_CHAR_TO_NIBE = (byte) 0xC0; + public static final byte PDU_MIN_LEN = 3; + + public static final byte PDU_CHECKSUM_LEN = 1; + + public static final byte FRAME_START_CHAR_RES = (byte) 0x5C; + public static final byte FRAME_START_CHAR_REQ = (byte) 0xC0; public static final byte OFFSET_START = 0; - public static final byte OFFSET_ADR = 2; - public static final byte OFFSET_CMD = 3; - public static final byte OFFSET_LEN = 4; - public static final byte OFFSET_DATA = 5; + public static final byte RES_OFFS_ADR = 2; + public static final byte RES_OFFS_CMD = 3; + public static final byte RES_OFFS_LEN = 4; + public static final byte RES_OFFS_DATA = 5; + + public static final byte REQ_OFFS_CMD = 1; + public static final byte REQ_OFFS_LEN = 2; + + public static final byte RES_HEADER_LEN = 5; + public static final byte REQ_HEADER_LEN = 3; public static final byte CMD_RMU_DATA_MSG = (byte) 0x62; public static final byte CMD_MODBUS_DATA_MSG = (byte) 0x68; @@ -43,53 +54,53 @@ public class NibeHeatPumpProtocol { public static final byte ADR_MODBUS40 = (byte) 0x20; public static boolean isModbus40DataReadOut(byte[] data) { - if (data[OFFSET_START] == FRAME_START_CHAR_FROM_NIBE && data[1] == (byte) 0x00 - && data[OFFSET_ADR] == ADR_MODBUS40) { - return data[OFFSET_CMD] == CMD_MODBUS_DATA_MSG && data[OFFSET_LEN] >= (byte) 0x50; + if (data[OFFSET_START] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00 + && data[RES_OFFS_ADR] == ADR_MODBUS40) { + return data[RES_OFFS_CMD] == CMD_MODBUS_DATA_MSG && data[RES_OFFS_LEN] >= (byte) 0x50; } return false; } public static boolean isModbus40ReadResponse(byte[] data) { - if (data[OFFSET_START] == FRAME_START_CHAR_FROM_NIBE && data[1] == (byte) 0x00 - && data[OFFSET_ADR] == ADR_MODBUS40) { - return data[OFFSET_CMD] == CMD_MODBUS_READ_RESP && data[OFFSET_LEN] >= (byte) 0x06; + if (data[OFFSET_START] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00 + && data[RES_OFFS_ADR] == ADR_MODBUS40) { + return data[RES_OFFS_CMD] == CMD_MODBUS_READ_RESP && data[RES_OFFS_LEN] >= (byte) 0x06; } return false; } public static boolean isRmu40DataReadOut(byte[] data) { - if (data[0] == FRAME_START_CHAR_FROM_NIBE && data[1] == (byte) 0x00 && data[OFFSET_ADR] == ADR_RMU40) { - return data[OFFSET_CMD] == CMD_RMU_DATA_MSG && data[OFFSET_LEN] >= (byte) 0x18; + if (data[0] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00 && data[RES_OFFS_ADR] == ADR_RMU40) { + return data[RES_OFFS_CMD] == CMD_RMU_DATA_MSG && data[RES_OFFS_LEN] >= (byte) 0x18; } return false; } public static boolean isModbus40WriteResponsePdu(byte[] data) { - return data[OFFSET_START] == FRAME_START_CHAR_FROM_NIBE && data[1] == (byte) 0x00 - && data[OFFSET_ADR] == ADR_MODBUS40 && data[OFFSET_CMD] == CMD_MODBUS_WRITE_RESP; + return data[OFFSET_START] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00 + && data[RES_OFFS_ADR] == ADR_MODBUS40 && data[RES_OFFS_CMD] == CMD_MODBUS_WRITE_RESP; } public static boolean isModbus40WriteTokenPdu(byte[] data) { - return data[0] == FRAME_START_CHAR_FROM_NIBE && data[1] == (byte) 0x00 && data[OFFSET_ADR] == ADR_MODBUS40 - && data[OFFSET_CMD] == CMD_MODBUS_WRITE_REQ && data[OFFSET_LEN] == 0x00; + return data[0] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00 && data[RES_OFFS_ADR] == ADR_MODBUS40 + && data[RES_OFFS_CMD] == CMD_MODBUS_WRITE_REQ && data[RES_OFFS_LEN] == 0x00; } public static boolean isModbus40ReadTokenPdu(byte[] data) { - return data[OFFSET_START] == FRAME_START_CHAR_FROM_NIBE && data[1] == (byte) 0x00 - && data[OFFSET_ADR] == ADR_MODBUS40 && data[OFFSET_CMD] == CMD_MODBUS_READ_REQ - && data[OFFSET_LEN] == 0x00; + return data[OFFSET_START] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00 + && data[RES_OFFS_ADR] == ADR_MODBUS40 && data[RES_OFFS_CMD] == CMD_MODBUS_READ_REQ + && data[RES_OFFS_LEN] == 0x00; } public static boolean isModbus40WriteRequestPdu(byte[] data) { - return data[0] == FRAME_START_CHAR_TO_NIBE && data[1] == CMD_MODBUS_WRITE_REQ; + return data[OFFSET_START] == FRAME_START_CHAR_REQ && data[REQ_OFFS_CMD] == CMD_MODBUS_WRITE_REQ; } public static boolean isModbus40ReadRequestPdu(byte[] data) { - return data[OFFSET_START] == FRAME_START_CHAR_TO_NIBE && data[1] == CMD_MODBUS_READ_REQ; + return data[OFFSET_START] == FRAME_START_CHAR_REQ && data[REQ_OFFS_CMD] == CMD_MODBUS_READ_REQ; } public static byte calculateChecksum(byte[] data) { @@ -108,10 +119,10 @@ public class NibeHeatPumpProtocol { public static byte getMessageType(byte[] data) { byte messageType = 0; - if (data[NibeHeatPumpProtocol.OFFSET_START] == NibeHeatPumpProtocol.FRAME_START_CHAR_FROM_NIBE) { - messageType = data[NibeHeatPumpProtocol.OFFSET_CMD]; - } else if (data[NibeHeatPumpProtocol.OFFSET_START] == NibeHeatPumpProtocol.FRAME_START_CHAR_TO_NIBE) { - messageType = data[1]; + if (data[NibeHeatPumpProtocol.OFFSET_START] == NibeHeatPumpProtocol.FRAME_START_CHAR_RES) { + messageType = data[RES_OFFS_CMD]; + } else if (data[NibeHeatPumpProtocol.OFFSET_START] == NibeHeatPumpProtocol.FRAME_START_CHAR_REQ) { + messageType = data[REQ_OFFS_CMD]; } return messageType; @@ -124,11 +135,11 @@ public class NibeHeatPumpProtocol { if (NibeHeatPumpProtocol.isModbus40ReadRequestPdu(data) || NibeHeatPumpProtocol.isModbus40WriteRequestPdu(data)) { - msglen = 3 + data[2]; + msglen = REQ_HEADER_LEN + data[REQ_OFFS_LEN]; startIndex = 0; stopIndex = msglen; } else { - msglen = 5 + data[OFFSET_LEN]; + msglen = RES_HEADER_LEN + data[RES_OFFS_LEN]; startIndex = 2; stopIndex = msglen; } @@ -138,24 +149,56 @@ public class NibeHeatPumpProtocol { // if checksum is 0x5C (start character), heat pump seems to send 0xC5 checksum - if (checksum == msgChecksum || (checksum == FRAME_START_CHAR_FROM_NIBE && msgChecksum == (byte) 0xC5)) { + if (checksum == msgChecksum || (checksum == FRAME_START_CHAR_RES && msgChecksum == (byte) 0xC5)) { // if data contains 0x5C (start character), data seems to contains double 0x5C characters - - // let's remove doubles - for (int i = 1; i < msglen; i++) { - if (data[i] == FRAME_START_CHAR_FROM_NIBE) { - data = ArrayUtils.remove(data, i); - msglen--; - - // fix message len - data[OFFSET_LEN]--; - } - } + return removeEscapedDuplicates(data, msglen); } else { throw new NibeHeatPumpException( "Checksum does not match. Checksum=" + (msgChecksum & 0xFF) + ", expected=" + (checksum & 0xFF)); } + } + + private static byte[] removeEscapedDuplicates(byte[] data, int msglen) { + if (dataContainsEscapedDuplicates(data, msglen)) { + ByteArrayOutputStream out = new ByteArrayOutputStream(msglen); + byte newlen = data[RES_OFFS_LEN]; + + // write start char + out.write(FRAME_START_CHAR_RES); + + // remove all duplicates between start char and checksum bytes + // checksum byte can't be 0x5C as it's set to 0xC5 in this case by the heat pump + for (int i = 1; i < msglen; i++) { + if (data[i] == FRAME_START_CHAR_RES && data[i + 1] == FRAME_START_CHAR_RES) { + // write one 0x5C + out.write(FRAME_START_CHAR_RES); + + // skip next 0x5C and decrease the length + i++; + newlen--; + } else { + out.write(data[i]); + } + } + + // write checksum + out.write(data[msglen]); + + // return modified data + byte[] newdata = out.toByteArray(); + newdata[RES_OFFS_LEN] = newlen; + return newdata; + } return data; } + + private static boolean dataContainsEscapedDuplicates(byte[] data, int msglen) { + for (int i = 1; i < msglen; i++) { + if (data[i] == FRAME_START_CHAR_RES && data[i + 1] == FRAME_START_CHAR_RES) { + return true; + } + } + return false; + } } diff --git a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/protocol/NibeHeatPumpProtocolStates.java b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/protocol/NibeHeatPumpProtocolStates.java index 7ed443570..8f52aaa41 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/protocol/NibeHeatPumpProtocolStates.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/main/java/org/openhab/binding/nibeheatpump/internal/protocol/NibeHeatPumpProtocolStates.java @@ -34,7 +34,7 @@ public enum NibeHeatPumpProtocolStates implements NibeHeatPumpProtocolState { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Received byte: {}", String.format("%02X", b)); } - if (b == NibeHeatPumpProtocol.FRAME_START_CHAR_FROM_NIBE) { + if (b == NibeHeatPumpProtocol.FRAME_START_CHAR_RES) { LOGGER.trace("Frame start found"); context.msg().clear(); context.msg().put(b); @@ -144,7 +144,7 @@ public enum NibeHeatPumpProtocolStates implements NibeHeatPumpProtocolState { int len = byteBuffer.remaining(); if (len >= 1) { - if (byteBuffer.get(0) != NibeHeatPumpProtocol.FRAME_START_CHAR_FROM_NIBE) { + if (byteBuffer.get(0) != NibeHeatPumpProtocol.FRAME_START_CHAR_RES) { return msgStatus.INVALID; } @@ -155,7 +155,7 @@ public enum NibeHeatPumpProtocolStates implements NibeHeatPumpProtocolState { } if (len >= 6) { - int datalen = byteBuffer.get(NibeHeatPumpProtocol.OFFSET_LEN); + int datalen = byteBuffer.get(NibeHeatPumpProtocol.RES_OFFS_LEN); // check if all bytes received if (len < datalen + 6) { diff --git a/bundles/org.openhab.binding.nibeheatpump/src/test/java/org/openhab/binding/nibeheatpump/internal/message/ModbusDataReadOutMessageTest.java b/bundles/org.openhab.binding.nibeheatpump/src/test/java/org/openhab/binding/nibeheatpump/internal/message/ModbusDataReadOutMessageTest.java index 926a9ef43..892ca812a 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/test/java/org/openhab/binding/nibeheatpump/internal/message/ModbusDataReadOutMessageTest.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/test/java/org/openhab/binding/nibeheatpump/internal/message/ModbusDataReadOutMessageTest.java @@ -92,6 +92,37 @@ public class ModbusDataReadOutMessageTest { checkRegisters(message, expectedValues); } + @Test + public void parseHeavilyEscapedModbusDataReadOutMessageTest() throws NibeHeatPumpException { + final String message = "5C0020685401A81F0100A86400FDA7D003449C1E004F9CA000509C7800519C0301529C1B01879C14014E9CC601479C010115B9B0FF3AB94B00C9AF0000489C0D014C9CE7004B9C0000FFFF0000FFFF00005C5C5C5C5C5C5C5C41"; + + @SuppressWarnings("serial") + final ArrayList expectedValues = new ArrayList() { + { + add(new ModbusValue(43009, 287)); + add(new ModbusValue(43008, 100)); + add(new ModbusValue(43005, 976)); + add(new ModbusValue(40004, 30)); + add(new ModbusValue(40015, 160)); + add(new ModbusValue(40016, 120)); + add(new ModbusValue(40017, 259)); + add(new ModbusValue(40018, 283)); + add(new ModbusValue(40071, 276)); + add(new ModbusValue(40014, 454)); + add(new ModbusValue(40007, 257)); + add(new ModbusValue(47381, 65456)); + add(new ModbusValue(47418, 75)); + add(new ModbusValue(45001, 0)); + add(new ModbusValue(40008, 269)); + add(new ModbusValue(40012, 231)); + add(new ModbusValue(40011, 0)); + add(new ModbusValue(23644, 23644)); + } + }; + + checkRegisters(message, expectedValues); + } + @Test public void specialLen1Test() throws NibeHeatPumpException { final String message = "5C00206851449C2500489CFC004C9CF1004E9CC7014D9C0B024F9C2500509C3300519C0B01529C5C5C01569C3100C9AF000001A80C01FDA716FAFAA9070098A91B1BFFFF0000A0A9CA02FFFF00009CA99212FFFF0000BE"; diff --git a/bundles/org.openhab.binding.nibeheatpump/src/test/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadResponseMessageTest.java b/bundles/org.openhab.binding.nibeheatpump/src/test/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadResponseMessageTest.java index 0dfeee461..e7f44f4c6 100644 --- a/bundles/org.openhab.binding.nibeheatpump/src/test/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadResponseMessageTest.java +++ b/bundles/org.openhab.binding.nibeheatpump/src/test/java/org/openhab/binding/nibeheatpump/internal/message/ModbusReadResponseMessageTest.java @@ -25,12 +25,11 @@ import org.openhab.core.util.HexUtils; */ public class ModbusReadResponseMessageTest { - private final int coilAddress = 513; - private final int value = 100992003; - private final String okMessage = "5C00206A060102030405064B"; - @Test public void createMessageTest() throws NibeHeatPumpException { + final int coilAddress = 513; + final int value = 100992003; + final String okMessage = "5C00206A060102030405064B"; ModbusReadResponseMessage m = new ModbusReadResponseMessage.MessageBuilder().coilAddress(coilAddress) .value(value).build(); byte[] byteMessage = m.decodeMessage(); @@ -39,23 +38,35 @@ public class ModbusReadResponseMessageTest { @Test public void parseMessageTest() throws NibeHeatPumpException { - byte[] msg = HexUtils.hexToBytes(okMessage); - ModbusReadResponseMessage m = (ModbusReadResponseMessage) MessageFactory.getMessage(msg); + final int coilAddress = 513; + final int value = 100992003; + final String message = "5C00206A060102030405064B"; + ModbusReadResponseMessage m = (ModbusReadResponseMessage) MessageFactory + .getMessage(HexUtils.hexToBytes(message)); assertEquals(coilAddress, m.getCoilAddress()); assertEquals(value, m.getValue()); } @Test public void badCrcTest() { - final String strMessage = "5C00206A060102030405064C"; - final byte[] byteMessage = HexUtils.hexToBytes(strMessage); - assertThrows(NibeHeatPumpException.class, () -> MessageFactory.getMessage(byteMessage)); + final String message = "5C00206A060102030405064C"; + assertThrows(NibeHeatPumpException.class, () -> MessageFactory.getMessage(HexUtils.hexToBytes(message))); } @Test public void notReadResponseMessageTest() { - final String strMessage = "5C00206B060102030405064A"; - final byte[] byteMessage = HexUtils.hexToBytes(strMessage); - assertThrows(NibeHeatPumpException.class, () -> new ModbusReadResponseMessage(byteMessage)); + final String message = "5C00206B060102030405064A"; + assertThrows(NibeHeatPumpException.class, () -> new ModbusReadResponseMessage(HexUtils.hexToBytes(message))); + } + + @Test + public void parseEscapedMessageTest() throws NibeHeatPumpException { + final int coilAddress = 513; + final int value = 0x05E65C; + final String message = "5C00206A0701025C5CE60500AD"; + ModbusReadResponseMessage m = (ModbusReadResponseMessage) MessageFactory + .getMessage(HexUtils.hexToBytes(message)); + assertEquals(coilAddress, m.getCoilAddress()); + assertEquals(value, m.getValue()); } }