]> git.basschouten.com Git - openhab-addons.git/blob
150d4b223ae7b4368066a7c147651a09e447fc47
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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     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");
48         }
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");
52         }
53
54         // Received data
55         byte[] msg = message;
56
57         // Fill in the checksum
58         if (withChecksum) {
59             checksum1In = message[message.length - 2];
60             checksum2In = message[message.length - 1];
61             msg = Arrays.copyOf(message, message.length - 2);
62
63             byte[] fletcherSum = fletcher(msg);
64             checksum1Calc = fletcherSum[0];
65             checksum2Calc = fletcherSum[1];
66         } else {
67             byte[] fletcherSum = fletcher(msg);
68             checksum1Calc = fletcherSum[0];
69             checksum2Calc = fletcherSum[1];
70
71             checksum1In = checksum1Calc;
72             checksum2In = checksum2Calc;
73         }
74
75         // Fill in the message
76         this.message = msg;
77
78         // Fill-in the acknowledgement flag
79         if ((message[0] & 0x80) != 0) {
80             hasAcknowledgementFlag = true;
81             message[0] = (byte) (message[0] & 0x7f);
82         } else {
83             hasAcknowledgementFlag = false;
84         }
85
86         // Fill-in the message type
87         CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
88         if (mt == null) {
89             throw new IllegalArgumentException("Unknown message");
90         }
91         caddxMessageType = mt;
92
93         // Fill-in the properties
94         processCaddxMessage();
95     }
96
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.");
103         }
104
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();
109         }
110
111         // Fill-in the checksum
112         byte[] fletcherSum = fletcher(msg);
113         checksum1Calc = fletcherSum[0];
114         checksum2Calc = fletcherSum[1];
115         checksum1In = checksum1Calc;
116         checksum2In = checksum2Calc;
117
118         // Fill-in the message
119         this.message = msg;
120
121         // Fill-in the acknowledgement flag
122         if ((message[0] & 0x80) != 0) {
123             hasAcknowledgementFlag = true;
124             message[0] = (byte) (message[0] & 0x7f);
125         } else {
126             hasAcknowledgementFlag = false;
127         }
128
129         // Fill-in the message type
130         this.caddxMessageType = type;
131
132         // Fill-in the properties
133         processCaddxMessage();
134     }
135
136     public byte getChecksum1In() {
137         return checksum1In;
138     }
139
140     public byte getChecksum2In() {
141         return checksum2In;
142     }
143
144     public byte getChecksum1Calc() {
145         return checksum1Calc;
146     }
147
148     public byte getChecksum2Calc() {
149         return checksum2Calc;
150     }
151
152     public CaddxMessageType getCaddxMessageType() {
153         return caddxMessageType;
154     }
155
156     public byte getMessageType() {
157         return message[0];
158     }
159
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:
166                 String zone;
167                 try {
168                     zone = "" + (Integer.parseInt(getPropertyById("zone_number")) + 1);
169                 } catch (NumberFormatException e) {
170                     zone = "";
171                 }
172                 sb.append(" [Zone: ");
173                 sb.append(zone);
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                 String partition;
185                 try {
186                     partition = "" + (Integer.parseInt(getPropertyById("partition_number")) + 1);
187                 } catch (NumberFormatException e) {
188                     partition = "";
189                 }
190                 sb.append(" [Partition: ");
191                 sb.append(partition);
192                 sb.append("]");
193                 break;
194             default:
195                 break;
196         }
197         return sb.toString();
198     }
199
200     public String getPropertyValue(String property) {
201         if (!propertyMap.containsKey(property)) {
202             logger.debug("Message does not contain property [{}]", property);
203             return "";
204         }
205         return propertyMap.getOrDefault(property, "");
206     }
207
208     public String getPropertyById(String id) {
209         if (!idMap.containsKey(id)) {
210             logger.debug("Message does not contain id [{}]", id);
211             return "";
212         }
213         return idMap.getOrDefault(id, "");
214     }
215
216     public int @Nullable [] getReplyMessageNumbers() {
217         return caddxMessageType.replyMessageNumbers;
218     }
219
220     public CaddxSource getSource() {
221         return getCaddxMessageType().source;
222     }
223
224     public boolean isChecksumCorrect() {
225         return checksum1In == checksum1Calc && checksum2In == checksum2Calc;
226     }
227
228     public boolean isLengthCorrect() {
229         return message.length == caddxMessageType.length;
230     }
231
232     public boolean hasAcknowledgementFlag() {
233         return hasAcknowledgementFlag;
234     }
235
236     public byte[] getMessageFrameBytes(CaddxProtocol protocol) {
237         if (protocol == CaddxProtocol.Binary) {
238             return getMessageFrameBytesInBinary();
239         } else {
240             return getMessageFrameBytesInAscii();
241         }
242     }
243
244     public byte[] getMessageBytes() {
245         return message;
246     }
247
248     /**
249      * Returns a string representation of a CaddxMessage.
250      *
251      * @return CaddxMessage string
252      */
253     @Override
254     public String toString() {
255         StringBuilder sb = new StringBuilder();
256
257         CaddxMessageType mt = CaddxMessageType.valueOfMessageType(message[0]);
258         if (mt == null) {
259             return "Unknown message type";
260         }
261
262         sb.append("Message: ");
263         sb.append(String.format("%2s", Integer.toHexString(message[0])));
264         sb.append(" ");
265         sb.append(mt.name);
266         sb.append(System.lineSeparator());
267
268         for (CaddxProperty p : mt.properties) {
269             sb.append("\t").append(p.toString(message));
270             sb.append(System.lineSeparator());
271         }
272
273         return sb.toString();
274     }
275
276     private void putByteInBuffer(ByteBuffer frame, byte b) {
277         if (b == 0x7e) {
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);
283         } else {
284             frame.put(b);
285         }
286     }
287
288     private byte[] getByteBufferArray(ByteBuffer frame) {
289         if (frame.hasArray()) {
290             return frame.array();
291         } else {
292             byte[] byteArray = new byte[frame.capacity()];
293             frame.position(0);
294             frame.get(byteArray);
295             return byteArray;
296         }
297     }
298
299     private byte[] getMessageFrameBytesInBinary() {
300         // Calculate bytes
301         // 1 for the startbyte
302         // 1 for the length
303         // 2 for the checksum
304         // n for the count of 0x7d and 0x7e occurrences in the message and checksum
305         int additional = 4;
306         for (int i = 0; i < message.length; i++) {
307             if (message[i] == 0x7d || message[i] == 0x7e) {
308                 additional++;
309             }
310         }
311         if (checksum1Calc == 0x7d || checksum1Calc == 0x7e) {
312             additional++;
313         }
314         if (checksum2Calc == 0x7d || checksum2Calc == 0x7e) {
315             additional++;
316         }
317
318         ByteBuffer frame = ByteBuffer.allocate(message.length + additional);
319
320         // start character
321         frame.put((byte) 0x7e);
322
323         // message length
324         frame.put((byte) message.length);
325
326         // message
327         for (int i = 0; i < message.length; i++) {
328             putByteInBuffer(frame, message[i]);
329         }
330
331         // 1st checksum byte
332         putByteInBuffer(frame, checksum1Calc);
333
334         // 2nd checksum byte
335         putByteInBuffer(frame, checksum2Calc);
336
337         return getByteBufferArray(frame);
338     }
339
340     private byte[] getMessageFrameBytesInAscii() {
341         // Calculate additional bytes
342         // 1 for the start byte
343         // 2 for the length
344         // 4 for the checksum
345         // 1 for the stop byte
346         int additional = 8;
347
348         ByteBuffer frame = ByteBuffer.allocate(2 * message.length + additional);
349
350         // start character
351         frame.put((byte) 0x0a);
352
353         // message length
354         frame.put(HexUtils.byteToHex((byte) message.length));
355
356         // message
357         for (int i = 0; i < message.length; i++) {
358             frame.put(HexUtils.byteToHex(message[i]));
359         }
360
361         // Checksum 1st byte
362         frame.put(HexUtils.byteToHex(checksum1Calc));
363
364         // Checksum 2nd byte
365         frame.put(HexUtils.byteToHex(checksum2Calc));
366
367         // Stop character
368         frame.put((byte) 0x0d);
369
370         return getByteBufferArray(frame);
371     }
372
373     /**
374      * Processes the incoming Caddx message and extracts the information.
375      */
376     private void processCaddxMessage() {
377         // fill the property lookup hashmaps
378         for (CaddxProperty p : caddxMessageType.properties) {
379             propertyMap.put(p.getName(), p.getValue(message));
380         }
381         for (CaddxProperty p : caddxMessageType.properties) {
382             if (!"".equals(p.getId())) {
383                 idMap.put(p.getId(), p.getValue(message));
384             }
385         }
386     }
387
388     /**
389      * Calculates the Fletcher checksum of the byte array.
390      *
391      * @param data The input byte array
392      * @return Byte array with two elements. Checksum1 and Checksum2
393      */
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;
401             }
402             sum1 = (sum1 + d) & 0xff;
403             if (sum1 == 0xff) {
404                 sum1 = 0;
405             }
406             if (0xff - sum2 < sum1) {
407                 sum2 = (sum2 + 1) & 0xff;
408             }
409             sum2 = (sum2 + sum1) & 0xff;
410             if (sum2 == 0xff) {
411                 sum2 = 0;
412             }
413         }
414
415         return new byte[] { (byte) sum1, (byte) sum2 };
416     }
417 }