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;
44 public CaddxMessage(byte[] message, boolean withChecksum) {
45 if (withChecksum && message.length < 3) {
46 logger.debug("CaddxMessage: The message should be at least 3 bytes long.");
47 throw new IllegalArgumentException("The message should be at least 3 bytes long");
49 if (!withChecksum && message.length < 1) {
50 logger.debug("CaddxMessage: The message should be at least 1 byte long.");
51 throw new IllegalArgumentException("The message should be at least 1 byte long");
57 // Fill in the checksum
59 checksum1In = message[message.length - 2];
60 checksum2In = message[message.length - 1];
61 msg = Arrays.copyOf(message, message.length - 2);
63 byte[] fletcherSum = fletcher(msg);
64 checksum1Calc = fletcherSum[0];
65 checksum2Calc = fletcherSum[1];
67 byte[] fletcherSum = fletcher(msg);
68 checksum1Calc = fletcherSum[0];
69 checksum2Calc = fletcherSum[1];
71 checksum1In = checksum1Calc;
72 checksum2In = checksum2Calc;
75 // Fill in the message
78 // Fill-in the acknowledgement flag
79 if ((message[0] & 0x80) != 0) {
80 hasAcknowledgementFlag = true;
81 message[0] = (byte) (message[0] & 0x7f);
83 hasAcknowledgementFlag = false;
86 // Fill-in the message type
87 CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
89 throw new IllegalArgumentException("Unknown message");
91 caddxMessageType = mt;
93 // Fill-in the properties
94 processCaddxMessage();
97 public CaddxMessage(CaddxMessageType type, String data) {
98 int length = type.length;
99 String[] tokens = data.split("\\,");
100 if (length != 1 && tokens.length != length - 1) {
101 logger.debug("token.length should be length-1. token.length={}, length={}", tokens.length, length);
102 throw new IllegalArgumentException("CaddxMessage: data has not the correct format.");
105 byte[] msg = new byte[length];
106 msg[0] = (byte) type.number;
107 for (int i = 0; i < length - 1; i++) {
108 msg[i + 1] = (byte) Integer.decode(tokens[i]).intValue();
111 // Fill-in the checksum
112 byte[] fletcherSum = fletcher(msg);
113 checksum1Calc = fletcherSum[0];
114 checksum2Calc = fletcherSum[1];
115 checksum1In = checksum1Calc;
116 checksum2In = checksum2Calc;
118 // Fill-in the message
121 // Fill-in the acknowledgement flag
122 if ((message[0] & 0x80) != 0) {
123 hasAcknowledgementFlag = true;
124 message[0] = (byte) (message[0] & 0x7f);
126 hasAcknowledgementFlag = false;
129 // Fill-in the message type
130 this.caddxMessageType = type;
132 // Fill-in the properties
133 processCaddxMessage();
136 public byte getChecksum1In() {
140 public byte getChecksum2In() {
144 public byte getChecksum1Calc() {
145 return checksum1Calc;
148 public byte getChecksum2Calc() {
149 return checksum2Calc;
152 public CaddxMessageType getCaddxMessageType() {
153 return caddxMessageType;
156 public byte getMessageType() {
160 public String getName() {
161 StringBuilder sb = new StringBuilder();
162 sb.append(caddxMessageType.name);
163 switch (caddxMessageType) {
164 case ZONE_STATUS_REQUEST:
165 case ZONE_STATUS_MESSAGE:
168 zone = "" + (Integer.parseInt(getPropertyById("zone_number")) + 1);
169 } catch (NumberFormatException e) {
172 sb.append(" [Zone: ");
176 case LOG_EVENT_REQUEST:
177 case LOG_EVENT_MESSAGE:
178 sb.append(" [Event: ");
179 sb.append(getPropertyById("panel_log_event_number"));
182 case PARTITION_STATUS_REQUEST:
183 case PARTITION_STATUS_MESSAGE:
186 partition = "" + (Integer.parseInt(getPropertyById("partition_number")) + 1);
187 } catch (NumberFormatException e) {
190 sb.append(" [Partition: ");
191 sb.append(partition);
197 return sb.toString();
200 public String getPropertyValue(String property) {
201 if (!propertyMap.containsKey(property)) {
202 logger.debug("Message does not contain property [{}]", property);
205 return propertyMap.getOrDefault(property, "");
208 public String getPropertyById(String id) {
209 if (!idMap.containsKey(id)) {
210 logger.debug("Message does not contain id [{}]", id);
213 return idMap.getOrDefault(id, "");
216 public int @Nullable [] getReplyMessageNumbers() {
217 return caddxMessageType.replyMessageNumbers;
220 public CaddxSource getSource() {
221 return getCaddxMessageType().source;
224 public boolean isChecksumCorrect() {
225 return checksum1In == checksum1Calc && checksum2In == checksum2Calc;
228 public boolean isLengthCorrect() {
229 return message.length == caddxMessageType.length;
232 public boolean hasAcknowledgementFlag() {
233 return hasAcknowledgementFlag;
236 public byte[] getMessageFrameBytes(CaddxProtocol protocol) {
237 if (protocol == CaddxProtocol.Binary) {
238 return getMessageFrameBytesInBinary();
240 return getMessageFrameBytesInAscii();
244 public byte[] getMessageBytes() {
249 * Returns a string representation of a CaddxMessage.
251 * @return CaddxMessage string
254 public String toString() {
255 StringBuilder sb = new StringBuilder();
257 CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
259 return "Unknown message type";
262 sb.append("Message: ");
263 sb.append(String.format("%2s", Integer.toHexString(message[0])));
266 sb.append(System.lineSeparator());
268 for (CaddxProperty p : mt.properties) {
269 sb.append("\t").append(p.toString(message));
270 sb.append(System.lineSeparator());
273 return sb.toString();
276 private void putByteInBuffer(ByteBuffer frame, byte b) {
278 frame.put((byte) 0x7d);
279 frame.put((byte) 0x5e);
280 } else if (b == 0x7d) {
281 frame.put((byte) 0x7d);
282 frame.put((byte) 0x5d);
288 private byte[] getByteBufferArray(ByteBuffer frame) {
289 if (frame.hasArray()) {
290 return frame.array();
292 byte[] byteArray = new byte[frame.capacity()];
294 frame.get(byteArray);
299 private byte[] getMessageFrameBytesInBinary() {
301 // 1 for the startbyte
303 // 2 for the checksum
304 // n for the count of 0x7d and 0x7e occurrences in the message and checksum
306 for (int i = 0; i < message.length; i++) {
307 if (message[i] == 0x7d || message[i] == 0x7e) {
311 if (checksum1Calc == 0x7d || checksum1Calc == 0x7e) {
314 if (checksum2Calc == 0x7d || checksum2Calc == 0x7e) {
318 ByteBuffer frame = ByteBuffer.allocate(message.length + additional);
321 frame.put((byte) 0x7e);
324 frame.put((byte) message.length);
327 for (int i = 0; i < message.length; i++) {
328 putByteInBuffer(frame, message[i]);
332 putByteInBuffer(frame, checksum1Calc);
335 putByteInBuffer(frame, checksum2Calc);
337 return getByteBufferArray(frame);
340 private byte[] getMessageFrameBytesInAscii() {
341 // Calculate additional bytes
342 // 1 for the start byte
344 // 4 for the checksum
345 // 1 for the stop byte
348 ByteBuffer frame = ByteBuffer.allocate(2 * message.length + additional);
351 frame.put((byte) 0x0a);
354 frame.put(HexUtils.byteToHex((byte) message.length));
357 for (int i = 0; i < message.length; i++) {
358 frame.put(HexUtils.byteToHex(message[i]));
362 frame.put(HexUtils.byteToHex(checksum1Calc));
365 frame.put(HexUtils.byteToHex(checksum2Calc));
368 frame.put((byte) 0x0d);
370 return getByteBufferArray(frame);
374 * Processes the incoming Caddx message and extracts the information.
376 private void processCaddxMessage() {
377 // fill the property lookup hashmaps
378 for (CaddxProperty p : caddxMessageType.properties) {
379 propertyMap.put(p.getName(), p.getValue(message));
381 for (CaddxProperty p : caddxMessageType.properties) {
382 if (!"".equals(p.getId())) {
383 idMap.put(p.getId(), p.getValue(message));
389 * Calculates the Fletcher checksum of the byte array.
391 * @param data The input byte array
392 * @return Byte array with two elements. Checksum1 and Checksum2
394 private byte[] fletcher(byte data[]) {
395 int len = data.length;
396 int sum1 = len, sum2 = len;
397 for (int i = 0; i < len; i++) {
398 int d = data[i] & 0xff;
399 if (0xff - sum1 < d) {
400 sum1 = (sum1 + 1) & 0xff;
402 sum1 = (sum1 + d) & 0xff;
406 if (0xff - sum2 < sum1) {
407 sum2 = (sum2 + 1) & 0xff;
409 sum2 = (sum2 + sum1) & 0xff;
415 return new byte[] { (byte) sum1, (byte) sum2 };