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