]> git.basschouten.com Git - openhab-addons.git/blob
6e875d6754878376b83729414cc1afceeefab27c
[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                 String zone;
173                 try {
174                     zone = "" + (Integer.parseInt(getPropertyById("zone_number")) + 1);
175                 } catch (NumberFormatException e) {
176                     zone = "";
177                 }
178                 sb.append(" [Zone: ");
179                 sb.append(zone);
180                 sb.append("]");
181                 break;
182             case LOG_EVENT_REQUEST:
183             case LOG_EVENT_MESSAGE:
184                 sb.append(" [Event: ");
185                 sb.append(getPropertyById("panel_log_event_number"));
186                 sb.append("]");
187                 break;
188             case PARTITION_STATUS_REQUEST:
189             case PARTITION_STATUS_MESSAGE:
190                 String partition;
191                 try {
192                     partition = "" + (Integer.parseInt(getPropertyById("partition_number")) + 1);
193                 } catch (NumberFormatException e) {
194                     partition = "";
195                 }
196                 sb.append(" [Partition: ");
197                 sb.append(partition);
198                 sb.append("]");
199                 break;
200             default:
201                 break;
202         }
203         return sb.toString();
204     }
205
206     public String getPropertyValue(String property) {
207         if (!propertyMap.containsKey(property)) {
208             logger.debug("Message does not contain property [{}]", property);
209             return "";
210         }
211         return propertyMap.getOrDefault(property, "");
212     }
213
214     public String getPropertyById(String id) {
215         if (!idMap.containsKey(id)) {
216             logger.debug("Message does not contain id [{}]", id);
217             return "";
218         }
219         return idMap.getOrDefault(id, "");
220     }
221
222     public int @Nullable [] getReplyMessageNumbers() {
223         return caddxMessageType.replyMessageNumbers;
224     }
225
226     public CaddxSource getSource() {
227         return getCaddxMessageType().source;
228     }
229
230     public boolean isChecksumCorrect() {
231         return checksum1In == checksum1Calc && checksum2In == checksum2Calc;
232     }
233
234     public boolean isLengthCorrect() {
235         return message.length == caddxMessageType.length;
236     }
237
238     public boolean hasAcknowledgementFlag() {
239         return hasAcknowledgementFlag;
240     }
241
242     public byte[] getMessageFrameBytes(CaddxProtocol protocol) {
243         if (protocol == CaddxProtocol.Binary) {
244             return getMessageFrameBytesInBinary();
245         } else {
246             return getMessageFrameBytesInAscii();
247         }
248     }
249
250     public byte[] getMessageBytes() {
251         return message;
252     }
253
254     /**
255      * Returns a string representation of a CaddxMessage.
256      *
257      * @return CaddxMessage string
258      */
259     @Override
260     public String toString() {
261         StringBuilder sb = new StringBuilder();
262
263         CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
264         if (mt == null) {
265             return "Unknown message type";
266         }
267
268         sb.append("Message: ");
269         sb.append(String.format("%2s", Integer.toHexString(message[0])));
270         sb.append(" ");
271         sb.append(mt.name);
272         sb.append(System.lineSeparator());
273
274         for (CaddxProperty p : mt.properties) {
275             sb.append("\t").append(p.toString(message));
276             sb.append(System.lineSeparator());
277         }
278
279         return sb.toString();
280     }
281
282     private void putByteInBuffer(ByteBuffer frame, byte b) {
283         if (b == 0x7e) {
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);
289         } else {
290             frame.put(b);
291         }
292     }
293
294     private byte[] getByteBufferArray(ByteBuffer frame) {
295         if (frame.hasArray()) {
296             return frame.array();
297         } else {
298             byte[] byteArray = new byte[frame.capacity()];
299             frame.position(0);
300             frame.get(byteArray);
301             return byteArray;
302         }
303     }
304
305     private byte[] getMessageFrameBytesInBinary() {
306         // Calculate bytes
307         // 1 for the startbyte
308         // 1 for the length
309         // 2 for the checksum
310         // n for the count of 0x7d and 0x7e occurrences in the message and checksum
311         int additional = 4;
312         for (int i = 0; i < message.length; i++) {
313             if (message[i] == 0x7d || message[i] == 0x7e) {
314                 additional++;
315             }
316         }
317         if (checksum1Calc == 0x7d || checksum1Calc == 0x7e) {
318             additional++;
319         }
320         if (checksum2Calc == 0x7d || checksum2Calc == 0x7e) {
321             additional++;
322         }
323
324         ByteBuffer frame = ByteBuffer.allocate(message.length + additional);
325
326         // start character
327         frame.put((byte) 0x7e);
328
329         // message length
330         frame.put((byte) message.length);
331
332         // message
333         for (int i = 0; i < message.length; i++) {
334             putByteInBuffer(frame, message[i]);
335         }
336
337         // 1st checksum byte
338         putByteInBuffer(frame, checksum1Calc);
339
340         // 2nd checksum byte
341         putByteInBuffer(frame, checksum2Calc);
342
343         return getByteBufferArray(frame);
344     }
345
346     private byte[] getMessageFrameBytesInAscii() {
347         // Calculate additional bytes
348         // 1 for the start byte
349         // 2 for the length
350         // 4 for the checksum
351         // 1 for the stop byte
352         int additional = 8;
353
354         ByteBuffer frame = ByteBuffer.allocate(2 * message.length + additional);
355
356         // start character
357         frame.put((byte) 0x0a);
358
359         // message length
360         frame.put(HexUtils.byteToHex((byte) message.length));
361
362         // message
363         for (int i = 0; i < message.length; i++) {
364             frame.put(HexUtils.byteToHex(message[i]));
365         }
366
367         // Checksum 1st byte
368         frame.put(HexUtils.byteToHex(checksum1Calc));
369
370         // Checksum 2nd byte
371         frame.put(HexUtils.byteToHex(checksum2Calc));
372
373         // Stop character
374         frame.put((byte) 0x0d);
375
376         return getByteBufferArray(frame);
377     }
378
379     /**
380      * Processes the incoming Caddx message and extracts the information.
381      */
382     private void processCaddxMessage() {
383         // fill the property lookup hashmaps
384         for (CaddxProperty p : caddxMessageType.properties) {
385             propertyMap.put(p.getName(), p.getValue(message));
386         }
387         for (CaddxProperty p : caddxMessageType.properties) {
388             if (!"".equals(p.getId())) {
389                 idMap.put(p.getId(), p.getValue(message));
390             }
391         }
392     }
393
394     /**
395      * Calculates the Fletcher checksum of the byte array.
396      *
397      * @param data The input byte array
398      * @return Byte array with two elements. Checksum1 and Checksum2
399      */
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;
407             }
408             sum1 = (sum1 + d) & 0xff;
409             if (sum1 == 0xff) {
410                 sum1 = 0;
411             }
412             if (0xff - sum2 < sum1) {
413                 sum2 = (sum2 + 1) & 0xff;
414             }
415             sum2 = (sum2 + sum1) & 0xff;
416             if (sum2 == 0xff) {
417                 sum2 = 0;
418             }
419         }
420
421         return new byte[] { (byte) sum1, (byte) sum2 };
422     }
423 }