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.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;
43 private CaddxMessageContext context;
45 public CaddxMessage(CaddxMessageContext context, byte[] message, boolean withChecksum) {
46 if (withChecksum && message.length < 3) {
47 logger.debug("CaddxMessage: The message should be at least 3 bytes long.");
48 throw new IllegalArgumentException("The message should be at least 3 bytes long");
50 if (!withChecksum && message.length < 1) {
51 logger.debug("CaddxMessage: The message should be at least 1 byte long.");
52 throw new IllegalArgumentException("The message should be at least 1 byte long");
56 this.context = context;
59 // Fill in the checksum
61 checksum1In = message[message.length - 2];
62 checksum2In = message[message.length - 1];
63 msg = Arrays.copyOf(message, message.length - 2);
65 byte[] fletcherSum = fletcher(msg);
66 checksum1Calc = fletcherSum[0];
67 checksum2Calc = fletcherSum[1];
69 byte[] fletcherSum = fletcher(msg);
70 checksum1Calc = fletcherSum[0];
71 checksum2Calc = fletcherSum[1];
73 checksum1In = checksum1Calc;
74 checksum2In = checksum2Calc;
77 // Fill in the message
80 // Fill-in the acknowledgement flag
81 if ((message[0] & 0x80) != 0) {
82 hasAcknowledgementFlag = true;
83 message[0] = (byte) (message[0] & 0x7f);
85 hasAcknowledgementFlag = false;
88 // Fill-in the message type
89 CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
91 throw new IllegalArgumentException("Unknown message");
93 caddxMessageType = mt;
95 // Fill-in the properties
96 processCaddxMessage();
99 public CaddxMessage(CaddxMessageContext context, CaddxMessageType type, String data) {
100 int length = type.length;
101 String[] tokens = data.split("\\,");
102 if (length != 1 && tokens.length != length - 1) {
103 logger.debug("token.length should be length-1. token.length={}, length={}", tokens.length, length);
104 throw new IllegalArgumentException("CaddxMessage: data has not the correct format.");
107 this.context = context;
108 byte[] msg = new byte[length];
109 msg[0] = (byte) type.number;
110 for (int i = 0; i < length - 1; i++) {
111 msg[i + 1] = (byte) Integer.decode(tokens[i]).intValue();
114 // Fill-in the checksum
115 byte[] fletcherSum = fletcher(msg);
116 checksum1Calc = fletcherSum[0];
117 checksum2Calc = fletcherSum[1];
118 checksum1In = checksum1Calc;
119 checksum2In = checksum2Calc;
121 // Fill-in the message
124 // Fill-in the acknowledgement flag
125 if ((message[0] & 0x80) != 0) {
126 hasAcknowledgementFlag = true;
127 message[0] = (byte) (message[0] & 0x7f);
129 hasAcknowledgementFlag = false;
132 // Fill-in the message type
133 this.caddxMessageType = type;
135 // Fill-in the properties
136 processCaddxMessage();
139 public CaddxMessageContext getContext() {
143 public void setContext(CaddxMessageContext context) {
144 this.context = context;
147 public byte getChecksum1In() {
151 public byte getChecksum2In() {
155 public byte getChecksum1Calc() {
156 return checksum1Calc;
159 public byte getChecksum2Calc() {
160 return checksum2Calc;
163 public CaddxMessageType getCaddxMessageType() {
164 return caddxMessageType;
167 public byte getMessageType() {
171 public String getName() {
172 StringBuilder sb = new StringBuilder();
173 sb.append(caddxMessageType.name);
174 switch (caddxMessageType) {
175 case ZONE_STATUS_REQUEST:
176 case ZONE_STATUS_MESSAGE:
179 zone = "" + (Integer.parseInt(getPropertyById("zone_number")) + 1);
180 } catch (NumberFormatException e) {
183 sb.append(" [Zone: ");
187 case LOG_EVENT_REQUEST:
188 case LOG_EVENT_MESSAGE:
189 sb.append(" [Event: ");
190 sb.append(getPropertyById("panel_log_event_number"));
193 case PARTITION_STATUS_REQUEST:
194 case PARTITION_STATUS_MESSAGE:
197 partition = "" + (Integer.parseInt(getPropertyById("partition_number")) + 1);
198 } catch (NumberFormatException e) {
201 sb.append(" [Partition: ");
202 sb.append(partition);
208 return sb.toString();
211 public String getPropertyValue(String property) {
212 if (!propertyMap.containsKey(property)) {
213 logger.debug("Message does not contain property [{}]", property);
216 return propertyMap.getOrDefault(property, "");
219 public String getPropertyById(String id) {
220 if (!idMap.containsKey(id)) {
221 logger.debug("Message does not contain id [{}]", id);
224 return idMap.getOrDefault(id, "");
227 public int @Nullable [] getReplyMessageNumbers() {
228 return caddxMessageType.replyMessageNumbers;
231 public CaddxSource getSource() {
232 return getCaddxMessageType().source;
235 public boolean isChecksumCorrect() {
236 return checksum1In == checksum1Calc && checksum2In == checksum2Calc;
239 public boolean isLengthCorrect() {
240 return message.length == caddxMessageType.length;
243 public boolean hasAcknowledgementFlag() {
244 return hasAcknowledgementFlag;
247 public byte[] getMessageFrameBytes(CaddxProtocol protocol) {
248 if (protocol == CaddxProtocol.Binary) {
249 return getMessageFrameBytesInBinary();
251 return getMessageFrameBytesInAscii();
255 public byte[] getMessageBytes() {
260 * Returns a string representation of a CaddxMessage.
262 * @return CaddxMessage string
265 public String toString() {
266 StringBuilder sb = new StringBuilder();
268 CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
270 return "Unknown message type";
273 sb.append(String.format("Context: %s", context.toString()));
274 sb.append(System.lineSeparator());
276 sb.append("Message: ");
277 sb.append(String.format("%2s", Integer.toHexString(message[0])));
280 sb.append(System.lineSeparator());
282 for (CaddxProperty p : mt.properties) {
283 sb.append("\t").append(p.toString(message));
284 sb.append(System.lineSeparator());
287 return sb.toString();
290 private void putByteInBuffer(ByteBuffer frame, byte b) {
292 frame.put((byte) 0x7d);
293 frame.put((byte) 0x5e);
294 } else if (b == 0x7d) {
295 frame.put((byte) 0x7d);
296 frame.put((byte) 0x5d);
302 private byte[] getByteBufferArray(ByteBuffer frame) {
303 if (frame.hasArray()) {
304 return frame.array();
306 byte[] byteArray = new byte[frame.capacity()];
308 frame.get(byteArray);
313 private byte[] getMessageFrameBytesInBinary() {
315 // 1 for the startbyte
317 // 2 for the checksum
318 // n for the count of 0x7d and 0x7e occurrences in the message and checksum
320 for (int i = 0; i < message.length; i++) {
321 if (message[i] == 0x7d || message[i] == 0x7e) {
325 if (checksum1Calc == 0x7d || checksum1Calc == 0x7e) {
328 if (checksum2Calc == 0x7d || checksum2Calc == 0x7e) {
332 ByteBuffer frame = ByteBuffer.allocate(message.length + additional);
335 frame.put((byte) 0x7e);
338 frame.put((byte) message.length);
341 for (int i = 0; i < message.length; i++) {
342 putByteInBuffer(frame, message[i]);
346 putByteInBuffer(frame, checksum1Calc);
349 putByteInBuffer(frame, checksum2Calc);
351 return getByteBufferArray(frame);
354 private byte[] getMessageFrameBytesInAscii() {
355 // Calculate additional bytes
356 // 1 for the start byte
358 // 4 for the checksum
359 // 1 for the stop byte
362 ByteBuffer frame = ByteBuffer.allocate(2 * message.length + additional);
365 frame.put((byte) 0x0a);
368 frame.put(HexUtils.byteToHex((byte) message.length));
371 for (int i = 0; i < message.length; i++) {
372 frame.put(HexUtils.byteToHex(message[i]));
376 frame.put(HexUtils.byteToHex(checksum1Calc));
379 frame.put(HexUtils.byteToHex(checksum2Calc));
382 frame.put((byte) 0x0d);
384 return getByteBufferArray(frame);
388 * Processes the incoming Caddx message and extracts the information.
390 private void processCaddxMessage() {
391 // fill the property lookup hashmaps
392 for (CaddxProperty p : caddxMessageType.properties) {
393 propertyMap.put(p.getName(), p.getValue(message));
395 for (CaddxProperty p : caddxMessageType.properties) {
396 if (!"".equals(p.getId())) {
397 idMap.put(p.getId(), p.getValue(message));
403 * Calculates the Fletcher checksum of the byte array.
405 * @param data The input byte array
406 * @return Byte array with two elements. Checksum1 and Checksum2
408 private byte[] fletcher(byte data[]) {
409 int len = data.length;
410 int sum1 = len, sum2 = len;
411 for (int i = 0; i < len; i++) {
412 int d = data[i] & 0xff;
413 if (0xff - sum1 < d) {
414 sum1 = (sum1 + 1) & 0xff;
416 sum1 = (sum1 + d) & 0xff;
420 if (0xff - sum2 < sum1) {
421 sum2 = (sum2 + 1) & 0xff;
423 sum2 = (sum2 + sum1) & 0xff;
429 return new byte[] { (byte) sum1, (byte) sum2 };