]> git.basschouten.com Git - openhab-addons.git/blob
0c660b4d028e025f9bb7573879d46afd375414bb
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.nibeheatpump.internal.protocol;
14
15 import java.io.ByteArrayOutputStream;
16
17 import org.openhab.binding.nibeheatpump.internal.NibeHeatPumpException;
18
19 /**
20  * This class contains useful Nibe heat pump protocol utils.
21  *
22  * @author Pauli Anttila - Initial contribution
23  */
24 public class NibeHeatPumpProtocol {
25
26     public static final byte PDU_MIN_LEN = 3;
27
28     public static final byte PDU_CHECKSUM_LEN = 1;
29
30     public static final byte FRAME_START_CHAR_RES = (byte) 0x5C;
31     public static final byte FRAME_START_CHAR_REQ = (byte) 0xC0;
32
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;
38
39     public static final byte REQ_OFFS_CMD = 1;
40     public static final byte REQ_OFFS_LEN = 2;
41
42     public static final byte RES_HEADER_LEN = 5;
43     public static final byte REQ_HEADER_LEN = 3;
44
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;
51
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;
55
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;
60         }
61
62         return false;
63     }
64
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;
69         }
70
71         return false;
72     }
73
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;
77         }
78
79         return false;
80     }
81
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;
85     }
86
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;
90     }
91
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;
96     }
97
98     public static boolean isModbus40WriteRequestPdu(byte[] data) {
99         return data[OFFSET_START] == FRAME_START_CHAR_REQ && data[REQ_OFFS_CMD] == CMD_MODBUS_WRITE_REQ;
100     }
101
102     public static boolean isModbus40ReadRequestPdu(byte[] data) {
103         return data[OFFSET_START] == FRAME_START_CHAR_REQ && data[REQ_OFFS_CMD] == CMD_MODBUS_READ_REQ;
104     }
105
106     public static byte calculateChecksum(byte[] data) {
107         return calculateChecksum(data, 0, data.length);
108     }
109
110     public static byte calculateChecksum(byte[] data, int startIndex, int stopIndex) {
111         byte checksum = 0;
112         // calculate XOR checksum
113         for (int i = startIndex; i < stopIndex; i++) {
114             checksum ^= data[i];
115         }
116         return checksum;
117     }
118
119     public static byte getMessageType(byte[] data) {
120         byte messageType = 0;
121
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];
126         }
127
128         return messageType;
129     }
130
131     public static byte[] checkMessageChecksumAndRemoveDoubles(byte[] data) throws NibeHeatPumpException {
132         int msglen;
133         int startIndex;
134         int stopIndex;
135
136         if (NibeHeatPumpProtocol.isModbus40ReadRequestPdu(data)
137                 || NibeHeatPumpProtocol.isModbus40WriteRequestPdu(data)) {
138             msglen = REQ_HEADER_LEN + data[REQ_OFFS_LEN];
139             startIndex = 0;
140             stopIndex = msglen;
141         } else {
142             msglen = RES_HEADER_LEN + data[RES_OFFS_LEN];
143             startIndex = 2;
144             stopIndex = msglen;
145         }
146
147         final byte checksum = calculateChecksum(data, startIndex, stopIndex);
148         final byte msgChecksum = data[msglen];
149
150         // if checksum is 0x5C (start character), heat pump seems to send 0xC5 checksum
151
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);
155         } else {
156             throw new NibeHeatPumpException(
157                     "Checksum does not match. Checksum=" + (msgChecksum & 0xFF) + ", expected=" + (checksum & 0xFF));
158         }
159     }
160
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];
165
166             // write start char
167             out.write(FRAME_START_CHAR_RES);
168
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) {
173                     // write one 0x5C
174                     out.write(FRAME_START_CHAR_RES);
175
176                     // skip next 0x5C and decrease the length
177                     i++;
178                     newlen--;
179                 } else {
180                     out.write(data[i]);
181                 }
182             }
183
184             // write checksum
185             out.write(data[msglen]);
186
187             // return modified data
188             byte[] newdata = out.toByteArray();
189             newdata[RES_OFFS_LEN] = newlen;
190             return newdata;
191         }
192
193         return data;
194     }
195
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) {
199                 return true;
200             }
201         }
202         return false;
203     }
204 }