2 * Copyright (c) 2010-2020 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.caddx.internal;
15 import java.nio.ByteBuffer;
16 import java.util.Arrays;
17 import java.util.HashMap;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.core.util.HexUtils;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
27 * A class that represents the Caddx Alarm Messages.
29 * @author Georgios Moutsos - Initial contribution
32 public class CaddxMessage {
33 private final Logger logger = LoggerFactory.getLogger(CaddxMessage.class);
34 private final CaddxMessageType caddxMessageType;
35 private final Map<String, String> propertyMap = new HashMap<>();
36 private final Map<String, String> idMap = new HashMap<>();
37 private final byte[] message;
38 private final boolean hasAcknowledgementFlag;
39 private final byte checksum1In;
40 private final byte checksum2In;
41 private final byte checksum1Calc;
42 private final byte checksum2Calc;
48 * - the message received
50 public CaddxMessage(byte[] message, boolean withChecksum) {
51 if (withChecksum && message.length < 3) {
52 logger.debug("CaddxMessage: The message should be at least 3 bytes long.");
53 throw new IllegalArgumentException("The message should be at least 3 bytes long");
55 if (!withChecksum && message.length < 1) {
56 logger.debug("CaddxMessage: The message should be at least 1 byte long.");
57 throw new IllegalArgumentException("The message should be at least 1 byte long");
63 // Fill in the checksum
65 checksum1In = message[message.length - 2];
66 checksum2In = message[message.length - 1];
67 msg = Arrays.copyOf(message, message.length - 2);
69 byte[] fletcherSum = fletcher(msg);
70 checksum1Calc = fletcherSum[0];
71 checksum2Calc = fletcherSum[1];
73 byte[] fletcherSum = fletcher(msg);
74 checksum1Calc = fletcherSum[0];
75 checksum2Calc = fletcherSum[1];
77 checksum1In = checksum1Calc;
78 checksum2In = checksum2Calc;
81 // Fill in the message
84 // Fill-in the acknowledgement flag
85 if ((message[0] & 0x80) != 0) {
86 hasAcknowledgementFlag = true;
87 message[0] = (byte) (message[0] & 0x7f);
89 hasAcknowledgementFlag = false;
92 // Fill-in the message type
93 CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
95 throw new IllegalArgumentException("Unknown message");
97 caddxMessageType = mt;
99 // Fill-in the properties
100 processCaddxMessage();
103 public CaddxMessage(CaddxMessageType type, String data) {
104 int length = type.length;
105 String[] tokens = data.split("\\,");
106 if (length != 1 && tokens.length != length - 1) {
107 logger.debug("token.length should be length-1. token.length={}, length={}", tokens.length, length);
108 throw new IllegalArgumentException("CaddxMessage: data has not the correct format.");
111 byte[] msg = new byte[length];
112 msg[0] = (byte) type.number;
113 for (int i = 0; i < length - 1; i++) {
114 msg[i + 1] = (byte) Integer.decode(tokens[i]).intValue();
117 // Fill-in the checksum
118 byte[] fletcherSum = fletcher(msg);
119 checksum1Calc = fletcherSum[0];
120 checksum2Calc = fletcherSum[1];
121 checksum1In = checksum1Calc;
122 checksum2In = checksum2Calc;
124 // Fill-in the message
127 // Fill-in the acknowledgement flag
128 if ((message[0] & 0x80) != 0) {
129 hasAcknowledgementFlag = true;
130 message[0] = (byte) (message[0] & 0x7f);
132 hasAcknowledgementFlag = false;
135 // Fill-in the message type
136 this.caddxMessageType = type;
138 // Fill-in the properties
139 processCaddxMessage();
142 public byte getChecksum1In() {
146 public byte getChecksum2In() {
150 public byte getChecksum1Calc() {
151 return checksum1Calc;
154 public byte getChecksum2Calc() {
155 return checksum2Calc;
158 public CaddxMessageType getCaddxMessageType() {
159 return caddxMessageType;
162 public byte getMessageType() {
166 public String getName() {
167 StringBuilder sb = new StringBuilder();
168 sb.append(caddxMessageType.name);
169 switch (caddxMessageType) {
170 case ZONE_STATUS_REQUEST:
171 case ZONE_STATUS_MESSAGE:
174 zone = "" + (Integer.parseInt(getPropertyById("zone_number")) + 1);
175 } catch (NumberFormatException e) {
178 sb.append(" [Zone: ");
182 case LOG_EVENT_REQUEST:
183 case LOG_EVENT_MESSAGE:
184 sb.append(" [Event: ");
185 sb.append(getPropertyById("panel_log_event_number"));
188 case PARTITION_STATUS_REQUEST:
189 case PARTITION_STATUS_MESSAGE:
192 partition = "" + (Integer.parseInt(getPropertyById("partition_number")) + 1);
193 } catch (NumberFormatException e) {
196 sb.append(" [Partition: ");
197 sb.append(partition);
203 return sb.toString();
206 public String getPropertyValue(String property) {
207 if (!propertyMap.containsKey(property)) {
208 logger.debug("Message does not contain property [{}]", property);
211 return propertyMap.getOrDefault(property, "");
214 public String getPropertyById(String id) {
215 if (!idMap.containsKey(id)) {
216 logger.debug("Message does not contain id [{}]", id);
219 return idMap.getOrDefault(id, "");
222 public int @Nullable [] getReplyMessageNumbers() {
223 return caddxMessageType.replyMessageNumbers;
226 public CaddxSource getSource() {
227 return getCaddxMessageType().source;
230 public boolean isChecksumCorrect() {
231 return checksum1In == checksum1Calc && checksum2In == checksum2Calc;
234 public boolean isLengthCorrect() {
235 return message.length == caddxMessageType.length;
238 public boolean hasAcknowledgementFlag() {
239 return hasAcknowledgementFlag;
242 public byte[] getMessageFrameBytes(CaddxProtocol protocol) {
243 if (protocol == CaddxProtocol.Binary) {
244 return getMessageFrameBytesInBinary();
246 return getMessageFrameBytesInAscii();
250 public byte[] getMessageBytes() {
255 * Returns a string representation of a CaddxMessage.
257 * @return CaddxMessage string
260 public String toString() {
261 StringBuilder sb = new StringBuilder();
263 CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
265 return "Unknown message type";
268 sb.append("Message: ");
269 sb.append(String.format("%2s", Integer.toHexString(message[0])));
272 sb.append(System.lineSeparator());
274 for (CaddxProperty p : mt.properties) {
275 sb.append("\t").append(p.toString(message));
276 sb.append(System.lineSeparator());
279 return sb.toString();
282 private void putByteInBuffer(ByteBuffer frame, byte b) {
284 frame.put((byte) 0x7d);
285 frame.put((byte) 0x5e);
286 } else if (b == 0x7d) {
287 frame.put((byte) 0x7d);
288 frame.put((byte) 0x5d);
294 private byte[] getByteBufferArray(ByteBuffer frame) {
295 if (frame.hasArray()) {
296 return frame.array();
298 byte[] byteArray = new byte[frame.capacity()];
300 frame.get(byteArray);
305 private byte[] getMessageFrameBytesInBinary() {
307 // 1 for the startbyte
309 // 2 for the checksum
310 // n for the count of 0x7d and 0x7e occurrences in the message and checksum
312 for (int i = 0; i < message.length; i++) {
313 if (message[i] == 0x7d || message[i] == 0x7e) {
317 if (checksum1Calc == 0x7d || checksum1Calc == 0x7e) {
320 if (checksum2Calc == 0x7d || checksum2Calc == 0x7e) {
324 ByteBuffer frame = ByteBuffer.allocate(message.length + additional);
327 frame.put((byte) 0x7e);
330 frame.put((byte) message.length);
333 for (int i = 0; i < message.length; i++) {
334 putByteInBuffer(frame, message[i]);
338 putByteInBuffer(frame, checksum1Calc);
341 putByteInBuffer(frame, checksum2Calc);
343 return getByteBufferArray(frame);
346 private byte[] getMessageFrameBytesInAscii() {
347 // Calculate additional bytes
348 // 1 for the start byte
350 // 4 for the checksum
351 // 1 for the stop byte
354 ByteBuffer frame = ByteBuffer.allocate(2 * message.length + additional);
357 frame.put((byte) 0x0a);
360 frame.put(HexUtils.byteToHex((byte) message.length));
363 for (int i = 0; i < message.length; i++) {
364 frame.put(HexUtils.byteToHex(message[i]));
368 frame.put(HexUtils.byteToHex(checksum1Calc));
371 frame.put(HexUtils.byteToHex(checksum2Calc));
374 frame.put((byte) 0x0d);
376 return getByteBufferArray(frame);
380 * Processes the incoming Caddx message and extracts the information.
382 private void processCaddxMessage() {
383 // fill the property lookup hashmaps
384 for (CaddxProperty p : caddxMessageType.properties) {
385 propertyMap.put(p.getName(), p.getValue(message));
387 for (CaddxProperty p : caddxMessageType.properties) {
388 if (!"".equals(p.getId())) {
389 idMap.put(p.getId(), p.getValue(message));
395 * Calculates the Fletcher checksum of the byte array.
397 * @param data The input byte array
398 * @return Byte array with two elements. Checksum1 and Checksum2
400 private byte[] fletcher(byte data[]) {
401 int len = data.length;
402 int sum1 = len, sum2 = len;
403 for (int i = 0; i < len; i++) {
404 int d = data[i] & 0xff;
405 if (0xff - sum1 < d) {
406 sum1 = (sum1 + 1) & 0xff;
408 sum1 = (sum1 + d) & 0xff;
412 if (0xff - sum2 < sum1) {
413 sum2 = (sum2 + 1) & 0xff;
415 sum2 = (sum2 + sum1) & 0xff;
421 return new byte[] { (byte) sum1, (byte) sum2 };