2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.nibeheatpump.internal.protocol;
15 import java.io.ByteArrayOutputStream;
17 import org.openhab.binding.nibeheatpump.internal.NibeHeatPumpException;
20 * This class contains useful Nibe heat pump protocol utils.
22 * @author Pauli Anttila - Initial contribution
24 public class NibeHeatPumpProtocol {
26 public static final byte PDU_MIN_LEN = 3;
28 public static final byte PDU_CHECKSUM_LEN = 1;
30 public static final byte FRAME_START_CHAR_RES = (byte) 0x5C;
31 public static final byte FRAME_START_CHAR_REQ = (byte) 0xC0;
33 public static final byte OFFSET_START = 0;
34 public static final byte RES_OFFS_ADR = 2;
35 public static final byte RES_OFFS_CMD = 3;
36 public static final byte RES_OFFS_LEN = 4;
37 public static final byte RES_OFFS_DATA = 5;
39 public static final byte REQ_OFFS_CMD = 1;
40 public static final byte REQ_OFFS_LEN = 2;
42 public static final byte RES_HEADER_LEN = 5;
43 public static final byte REQ_HEADER_LEN = 3;
45 public static final byte CMD_RMU_DATA_MSG = (byte) 0x62;
46 public static final byte CMD_MODBUS_DATA_MSG = (byte) 0x68;
47 public static final byte CMD_MODBUS_READ_REQ = (byte) 0x69;
48 public static final byte CMD_MODBUS_READ_RESP = (byte) 0x6A;
49 public static final byte CMD_MODBUS_WRITE_REQ = (byte) 0x6B;
50 public static final byte CMD_MODBUS_WRITE_RESP = (byte) 0x6C;
52 public static final byte ADR_SMS40 = (byte) 0x16;
53 public static final byte ADR_RMU40 = (byte) 0x19;
54 public static final byte ADR_MODBUS40 = (byte) 0x20;
56 public static boolean isModbus40DataReadOut(byte[] data) {
57 if (data[OFFSET_START] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00
58 && data[RES_OFFS_ADR] == ADR_MODBUS40) {
59 return data[RES_OFFS_CMD] == CMD_MODBUS_DATA_MSG && data[RES_OFFS_LEN] >= (byte) 0x50;
65 public static boolean isModbus40ReadResponse(byte[] data) {
66 if (data[OFFSET_START] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00
67 && data[RES_OFFS_ADR] == ADR_MODBUS40) {
68 return data[RES_OFFS_CMD] == CMD_MODBUS_READ_RESP && data[RES_OFFS_LEN] >= (byte) 0x06;
74 public static boolean isRmu40DataReadOut(byte[] data) {
75 if (data[0] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00 && data[RES_OFFS_ADR] == ADR_RMU40) {
76 return data[RES_OFFS_CMD] == CMD_RMU_DATA_MSG && data[RES_OFFS_LEN] >= (byte) 0x18;
82 public static boolean isModbus40WriteResponsePdu(byte[] data) {
83 return data[OFFSET_START] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00
84 && data[RES_OFFS_ADR] == ADR_MODBUS40 && data[RES_OFFS_CMD] == CMD_MODBUS_WRITE_RESP;
87 public static boolean isModbus40WriteTokenPdu(byte[] data) {
88 return data[0] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00 && data[RES_OFFS_ADR] == ADR_MODBUS40
89 && data[RES_OFFS_CMD] == CMD_MODBUS_WRITE_REQ && data[RES_OFFS_LEN] == 0x00;
92 public static boolean isModbus40ReadTokenPdu(byte[] data) {
93 return data[OFFSET_START] == FRAME_START_CHAR_RES && data[1] == (byte) 0x00
94 && data[RES_OFFS_ADR] == ADR_MODBUS40 && data[RES_OFFS_CMD] == CMD_MODBUS_READ_REQ
95 && data[RES_OFFS_LEN] == 0x00;
98 public static boolean isModbus40WriteRequestPdu(byte[] data) {
99 return data[OFFSET_START] == FRAME_START_CHAR_REQ && data[REQ_OFFS_CMD] == CMD_MODBUS_WRITE_REQ;
102 public static boolean isModbus40ReadRequestPdu(byte[] data) {
103 return data[OFFSET_START] == FRAME_START_CHAR_REQ && data[REQ_OFFS_CMD] == CMD_MODBUS_READ_REQ;
106 public static byte calculateChecksum(byte[] data) {
107 return calculateChecksum(data, 0, data.length);
110 public static byte calculateChecksum(byte[] data, int startIndex, int stopIndex) {
112 // calculate XOR checksum
113 for (int i = startIndex; i < stopIndex; i++) {
119 public static byte getMessageType(byte[] data) {
120 byte messageType = 0;
122 if (data[NibeHeatPumpProtocol.OFFSET_START] == NibeHeatPumpProtocol.FRAME_START_CHAR_RES) {
123 messageType = data[RES_OFFS_CMD];
124 } else if (data[NibeHeatPumpProtocol.OFFSET_START] == NibeHeatPumpProtocol.FRAME_START_CHAR_REQ) {
125 messageType = data[REQ_OFFS_CMD];
131 public static byte[] checkMessageChecksumAndRemoveDoubles(byte[] data) throws NibeHeatPumpException {
136 if (NibeHeatPumpProtocol.isModbus40ReadRequestPdu(data)
137 || NibeHeatPumpProtocol.isModbus40WriteRequestPdu(data)) {
138 msglen = REQ_HEADER_LEN + data[REQ_OFFS_LEN];
142 msglen = RES_HEADER_LEN + data[RES_OFFS_LEN];
147 final byte checksum = calculateChecksum(data, startIndex, stopIndex);
148 final byte msgChecksum = data[msglen];
150 // if checksum is 0x5C (start character), heat pump seems to send 0xC5 checksum
152 if (checksum == msgChecksum || (checksum == FRAME_START_CHAR_RES && msgChecksum == (byte) 0xC5)) {
153 // if data contains 0x5C (start character), data seems to contains double 0x5C characters
154 return removeEscapedDuplicates(data, msglen);
156 throw new NibeHeatPumpException(
157 "Checksum does not match. Checksum=" + (msgChecksum & 0xFF) + ", expected=" + (checksum & 0xFF));
161 private static byte[] removeEscapedDuplicates(byte[] data, int msglen) {
162 if (dataContainsEscapedDuplicates(data, msglen)) {
163 ByteArrayOutputStream out = new ByteArrayOutputStream(msglen);
164 byte newlen = data[RES_OFFS_LEN];
167 out.write(FRAME_START_CHAR_RES);
169 // remove all duplicates between start char and checksum bytes
170 // checksum byte can't be 0x5C as it's set to 0xC5 in this case by the heat pump
171 for (int i = 1; i < msglen; i++) {
172 if (data[i] == FRAME_START_CHAR_RES && data[i + 1] == FRAME_START_CHAR_RES) {
174 out.write(FRAME_START_CHAR_RES);
176 // skip next 0x5C and decrease the length
185 out.write(data[msglen]);
187 // return modified data
188 byte[] newdata = out.toByteArray();
189 newdata[RES_OFFS_LEN] = newlen;
196 private static boolean dataContainsEscapedDuplicates(byte[] data, int msglen) {
197 for (int i = 1; i < msglen; i++) {
198 if (data[i] == FRAME_START_CHAR_RES && data[i + 1] == FRAME_START_CHAR_RES) {