]> git.basschouten.com Git - openhab-addons.git/blob
86b88123ca0953831faf6395ad402fbe2f8bff3c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.caddx.internal;
14
15 import java.nio.ByteBuffer;
16 import java.util.Arrays;
17 import java.util.HashMap;
18 import java.util.Map;
19
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;
25
26 /**
27  * A class that represents the Caddx Alarm Messages.
28  *
29  * @author Georgios Moutsos - Initial contribution
30  */
31 @NonNullByDefault
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
44     /**
45      * Constructor.
46      *
47      * @param message
48      *            - the message received
49      */
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");
54         }
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");
58         }
59
60         // Received data
61         byte[] msg = message;
62
63         // Fill in the checksum
64         if (withChecksum) {
65             checksum1In = message[message.length - 2];
66             checksum2In = message[message.length - 1];
67             msg = Arrays.copyOf(message, message.length - 2);
68
69             byte[] fletcherSum = fletcher(msg);
70             checksum1Calc = fletcherSum[0];
71             checksum2Calc = fletcherSum[1];
72         } else {
73             byte[] fletcherSum = fletcher(msg);
74             checksum1Calc = fletcherSum[0];
75             checksum2Calc = fletcherSum[1];
76
77             checksum1In = checksum1Calc;
78             checksum2In = checksum2Calc;
79         }
80
81         // Fill in the message
82         this.message = msg;
83
84         // Fill-in the acknowledgement flag
85         if ((message[0] & 0x80) != 0) {
86             hasAcknowledgementFlag = true;
87             message[0] = (byte) (message[0] & 0x7f);
88         } else {
89             hasAcknowledgementFlag = false;
90         }
91
92         // Fill-in the message type
93         CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
94         if (mt == null) {
95             throw new IllegalArgumentException("Unknown message");
96         }
97         caddxMessageType = mt;
98
99         // Fill-in the properties
100         processCaddxMessage();
101     }
102
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.");
109         }
110
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();
115         }
116
117         // Fill-in the checksum
118         byte[] fletcherSum = fletcher(msg);
119         checksum1Calc = fletcherSum[0];
120         checksum2Calc = fletcherSum[1];
121         checksum1In = checksum1Calc;
122         checksum2In = checksum2Calc;
123
124         // Fill-in the message
125         this.message = msg;
126
127         // Fill-in the acknowledgement flag
128         if ((message[0] & 0x80) != 0) {
129             hasAcknowledgementFlag = true;
130             message[0] = (byte) (message[0] & 0x7f);
131         } else {
132             hasAcknowledgementFlag = false;
133         }
134
135         // Fill-in the message type
136         this.caddxMessageType = type;
137
138         // Fill-in the properties
139         processCaddxMessage();
140     }
141
142     public byte getChecksum1In() {
143         return checksum1In;
144     }
145
146     public byte getChecksum2In() {
147         return checksum2In;
148     }
149
150     public byte getChecksum1Calc() {
151         return checksum1Calc;
152     }
153
154     public byte getChecksum2Calc() {
155         return checksum2Calc;
156     }
157
158     public CaddxMessageType getCaddxMessageType() {
159         return caddxMessageType;
160     }
161
162     public byte getMessageType() {
163         return message[0];
164     }
165
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"));
174                 sb.append("]");
175                 break;
176             case LOG_EVENT_REQUEST:
177             case LOG_EVENT_MESSAGE:
178                 sb.append(" [Event: ");
179                 sb.append(getPropertyById("panel_log_event_number"));
180                 sb.append("]");
181                 break;
182             case PARTITION_STATUS_REQUEST:
183             case PARTITION_STATUS_MESSAGE:
184                 sb.append(" [Partition: ");
185                 sb.append(getPropertyById("partition_number"));
186                 sb.append("]");
187                 break;
188             default:
189                 break;
190         }
191         return sb.toString();
192     }
193
194     public String getPropertyValue(String property) {
195         if (!propertyMap.containsKey(property)) {
196             logger.debug("Message does not contain property [{}]", property);
197             return "";
198         }
199         return propertyMap.get(property);
200     }
201
202     public String getPropertyById(String id) {
203         if (!idMap.containsKey(id)) {
204             logger.debug("Message does not contain id [{}]", id);
205             return "";
206         }
207         return idMap.get(id);
208     }
209
210     public int @Nullable [] getReplyMessageNumbers() {
211         return caddxMessageType.replyMessageNumbers;
212     }
213
214     public CaddxSource getSource() {
215         return getCaddxMessageType().source;
216     }
217
218     public boolean isChecksumCorrect() {
219         return checksum1In == checksum1Calc && checksum2In == checksum2Calc;
220     }
221
222     public boolean isLengthCorrect() {
223         return message.length == caddxMessageType.length;
224     }
225
226     public boolean hasAcknowledgementFlag() {
227         return hasAcknowledgementFlag;
228     }
229
230     public byte[] getMessageFrameBytes(CaddxProtocol protocol) {
231         if (protocol == CaddxProtocol.Binary) {
232             return getMessageFrameBytesInBinary();
233         } else {
234             return getMessageFrameBytesInAscii();
235         }
236     }
237
238     public byte[] getMessageBytes() {
239         return message;
240     }
241
242     /**
243      * Returns a string representation of a CaddxMessage.
244      *
245      * @return CaddxMessage string
246      */
247     @Override
248     public String toString() {
249         StringBuilder sb = new StringBuilder();
250
251         CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
252         if (mt == null) {
253             return "Unknown message type";
254         }
255
256         sb.append("Message: ");
257         sb.append(String.format("%2s", Integer.toHexString(message[0])));
258         sb.append(" ");
259         sb.append(mt.name);
260         sb.append(System.lineSeparator());
261
262         for (CaddxProperty p : mt.properties) {
263             sb.append("\t").append(p.toString(message));
264             sb.append(System.lineSeparator());
265         }
266
267         return sb.toString();
268     }
269
270     private void putByteInBuffer(ByteBuffer frame, byte b) {
271         if (b == 0x7e) {
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);
277         } else {
278             frame.put(b);
279         }
280     }
281
282     private byte[] getByteBufferArray(ByteBuffer frame) {
283         if (frame.hasArray()) {
284             return frame.array();
285         } else {
286             byte[] byteArray = new byte[frame.capacity()];
287             frame.position(0);
288             frame.get(byteArray);
289             return byteArray;
290         }
291     }
292
293     private byte[] getMessageFrameBytesInBinary() {
294         // Calculate bytes
295         // 1 for the startbyte
296         // 1 for the length
297         // 2 for the checksum
298         // n for the count of 0x7d and 0x7e occurrences in the message and checksum
299         int additional = 4;
300         for (int i = 0; i < message.length; i++) {
301             if (message[i] == 0x7d || message[i] == 0x7e) {
302                 additional++;
303             }
304         }
305         if (checksum1Calc == 0x7d || checksum1Calc == 0x7e) {
306             additional++;
307         }
308         if (checksum2Calc == 0x7d || checksum2Calc == 0x7e) {
309             additional++;
310         }
311
312         ByteBuffer frame = ByteBuffer.allocate(message.length + additional);
313
314         // start character
315         frame.put((byte) 0x7e);
316
317         // message length
318         frame.put((byte) message.length);
319
320         // message
321         for (int i = 0; i < message.length; i++) {
322             putByteInBuffer(frame, message[i]);
323         }
324
325         // 1st checksum byte
326         putByteInBuffer(frame, checksum1Calc);
327
328         // 2nd checksum byte
329         putByteInBuffer(frame, checksum2Calc);
330
331         return getByteBufferArray(frame);
332     }
333
334     private byte[] getMessageFrameBytesInAscii() {
335         // Calculate additional bytes
336         // 1 for the start byte
337         // 2 for the length
338         // 4 for the checksum
339         // 1 for the stop byte
340         int additional = 8;
341
342         ByteBuffer frame = ByteBuffer.allocate(2 * message.length + additional);
343
344         // start character
345         frame.put((byte) 0x0a);
346
347         // message length
348         frame.put(HexUtils.byteToHex((byte) message.length));
349
350         // message
351         for (int i = 0; i < message.length; i++) {
352             frame.put(HexUtils.byteToHex(message[i]));
353         }
354
355         // Checksum 1st byte
356         frame.put(HexUtils.byteToHex(checksum1Calc));
357
358         // Checksum 2nd byte
359         frame.put(HexUtils.byteToHex(checksum2Calc));
360
361         // Stop character
362         frame.put((byte) 0x0d);
363
364         return getByteBufferArray(frame);
365     }
366
367     /**
368      * Processes the incoming Caddx message and extracts the information.
369      */
370     private void processCaddxMessage() {
371         // fill the property lookup hashmaps
372         for (CaddxProperty p : caddxMessageType.properties) {
373             propertyMap.put(p.getName(), p.getValue(message));
374         }
375         for (CaddxProperty p : caddxMessageType.properties) {
376             if (!"".equals(p.getId())) {
377                 idMap.put(p.getId(), p.getValue(message));
378             }
379         }
380     }
381
382     /**
383      * Calculates the Fletcher checksum of the byte array.
384      *
385      * @param data The input byte array
386      * @return Byte array with two elements. Checksum1 and Checksum2
387      */
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;
395             }
396             sum1 = (sum1 + d) & 0xff;
397             if (sum1 == 0xff) {
398                 sum1 = 0;
399             }
400             if (0xff - sum2 < sum1) {
401                 sum2 = (sum2 + 1) & 0xff;
402             }
403             sum2 = (sum2 + sum1) & 0xff;
404             if (sum2 == 0xff) {
405                 sum2 = 0;
406             }
407         }
408
409         return new byte[] { (byte) sum1, (byte) sum2 };
410     }
411 }