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:
172 sb.append(" [Zone: ");
173 sb.append(getPropertyById("zone_number"));
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:
184 sb.append(" [Partition: ");
185 sb.append(getPropertyById("partition_number"));
191 return sb.toString();
194 public String getPropertyValue(String property) {
195 if (!propertyMap.containsKey(property)) {
196 logger.debug("Message does not contain property [{}]", property);
199 return propertyMap.getOrDefault(property, "");
202 public String getPropertyById(String id) {
203 if (!idMap.containsKey(id)) {
204 logger.debug("Message does not contain id [{}]", id);
207 return idMap.getOrDefault(id, "");
210 public int @Nullable [] getReplyMessageNumbers() {
211 return caddxMessageType.replyMessageNumbers;
214 public CaddxSource getSource() {
215 return getCaddxMessageType().source;
218 public boolean isChecksumCorrect() {
219 return checksum1In == checksum1Calc && checksum2In == checksum2Calc;
222 public boolean isLengthCorrect() {
223 return message.length == caddxMessageType.length;
226 public boolean hasAcknowledgementFlag() {
227 return hasAcknowledgementFlag;
230 public byte[] getMessageFrameBytes(CaddxProtocol protocol) {
231 if (protocol == CaddxProtocol.Binary) {
232 return getMessageFrameBytesInBinary();
234 return getMessageFrameBytesInAscii();
238 public byte[] getMessageBytes() {
243 * Returns a string representation of a CaddxMessage.
245 * @return CaddxMessage string
248 public String toString() {
249 StringBuilder sb = new StringBuilder();
251 CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
253 return "Unknown message type";
256 sb.append("Message: ");
257 sb.append(String.format("%2s", Integer.toHexString(message[0])));
260 sb.append(System.lineSeparator());
262 for (CaddxProperty p : mt.properties) {
263 sb.append("\t").append(p.toString(message));
264 sb.append(System.lineSeparator());
267 return sb.toString();
270 private void putByteInBuffer(ByteBuffer frame, byte b) {
272 frame.put((byte) 0x7d);
273 frame.put((byte) 0x5e);
274 } else if (b == 0x7d) {
275 frame.put((byte) 0x7d);
276 frame.put((byte) 0x5d);
282 private byte[] getByteBufferArray(ByteBuffer frame) {
283 if (frame.hasArray()) {
284 return frame.array();
286 byte[] byteArray = new byte[frame.capacity()];
288 frame.get(byteArray);
293 private byte[] getMessageFrameBytesInBinary() {
295 // 1 for the startbyte
297 // 2 for the checksum
298 // n for the count of 0x7d and 0x7e occurrences in the message and checksum
300 for (int i = 0; i < message.length; i++) {
301 if (message[i] == 0x7d || message[i] == 0x7e) {
305 if (checksum1Calc == 0x7d || checksum1Calc == 0x7e) {
308 if (checksum2Calc == 0x7d || checksum2Calc == 0x7e) {
312 ByteBuffer frame = ByteBuffer.allocate(message.length + additional);
315 frame.put((byte) 0x7e);
318 frame.put((byte) message.length);
321 for (int i = 0; i < message.length; i++) {
322 putByteInBuffer(frame, message[i]);
326 putByteInBuffer(frame, checksum1Calc);
329 putByteInBuffer(frame, checksum2Calc);
331 return getByteBufferArray(frame);
334 private byte[] getMessageFrameBytesInAscii() {
335 // Calculate additional bytes
336 // 1 for the start byte
338 // 4 for the checksum
339 // 1 for the stop byte
342 ByteBuffer frame = ByteBuffer.allocate(2 * message.length + additional);
345 frame.put((byte) 0x0a);
348 frame.put(HexUtils.byteToHex((byte) message.length));
351 for (int i = 0; i < message.length; i++) {
352 frame.put(HexUtils.byteToHex(message[i]));
356 frame.put(HexUtils.byteToHex(checksum1Calc));
359 frame.put(HexUtils.byteToHex(checksum2Calc));
362 frame.put((byte) 0x0d);
364 return getByteBufferArray(frame);
368 * Processes the incoming Caddx message and extracts the information.
370 private void processCaddxMessage() {
371 // fill the property lookup hashmaps
372 for (CaddxProperty p : caddxMessageType.properties) {
373 propertyMap.put(p.getName(), p.getValue(message));
375 for (CaddxProperty p : caddxMessageType.properties) {
376 if (!"".equals(p.getId())) {
377 idMap.put(p.getId(), p.getValue(message));
383 * Calculates the Fletcher checksum of the byte array.
385 * @param data The input byte array
386 * @return Byte array with two elements. Checksum1 and Checksum2
388 private byte[] fletcher(byte data[]) {
389 int len = data.length;
390 int sum1 = len, sum2 = len;
391 for (int i = 0; i < len; i++) {
392 int d = data[i] & 0xff;
393 if (0xff - sum1 < d) {
394 sum1 = (sum1 + 1) & 0xff;
396 sum1 = (sum1 + d) & 0xff;
400 if (0xff - sum2 < sum1) {
401 sum2 = (sum2 + 1) & 0xff;
403 sum2 = (sum2 + sum1) & 0xff;
409 return new byte[] { (byte) sum1, (byte) sum2 };