]> git.basschouten.com Git - openhab-addons.git/blob
391105c88fae08a328fca0e653a5b3868da3ab9b
[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.powermax.internal.connector;
14
15 import java.io.IOException;
16 import java.io.InterruptedIOException;
17 import java.util.Arrays;
18
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.openhab.binding.powermax.internal.message.PowermaxCommManager;
21 import org.openhab.binding.powermax.internal.message.PowermaxReceiveType;
22 import org.openhab.core.util.HexUtils;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 /**
27  * A class that reads messages from the Visonic alarm panel in a dedicated thread
28  *
29  * @author Laurent Garnier - Initial contribution
30  */
31 @NonNullByDefault
32 public class PowermaxReaderThread extends Thread {
33
34     private static final int READ_BUFFER_SIZE = 20;
35     private static final int MAX_MSG_SIZE = 0xC0;
36
37     private final Logger logger = LoggerFactory.getLogger(PowermaxReaderThread.class);
38
39     private final PowermaxConnector connector;
40
41     /**
42      * Constructor
43      *
44      * @param connector the object that should handle the received message
45      * @param threadName the name of the thread
46      */
47     public PowermaxReaderThread(PowermaxConnector connector, String threadName) {
48         super(threadName);
49         this.connector = connector;
50     }
51
52     @Override
53     public void run() {
54         logger.info("Data listener started");
55
56         byte[] readDataBuffer = new byte[READ_BUFFER_SIZE];
57         byte[] dataBuffer = new byte[MAX_MSG_SIZE];
58         int index = 0;
59         int msgLen = 0;
60         boolean variableLen = false;
61
62         try {
63             while (!Thread.interrupted()) {
64                 int len = connector.read(readDataBuffer);
65                 if (len > 0) {
66                     for (int i = 0; i < len; i++) {
67                         if (index >= MAX_MSG_SIZE) {
68                             // too many bytes received, try to find new start
69                             if (logger.isDebugEnabled()) {
70                                 byte[] logData = Arrays.copyOf(dataBuffer, index);
71                                 logger.debug("Truncating message {}", HexUtils.bytesToHex(logData));
72                             }
73                             index = 0;
74                         }
75
76                         if (index == 0 && readDataBuffer[i] == 0x0D) {
77                             // Preamble
78
79                             dataBuffer[index++] = readDataBuffer[i];
80                         } else if (index > 0) {
81                             dataBuffer[index++] = readDataBuffer[i];
82
83                             if (index == 2) {
84                                 try {
85                                     PowermaxReceiveType msgType = PowermaxReceiveType.fromCode(readDataBuffer[i]);
86                                     msgLen = msgType.getLength();
87                                     variableLen = ((readDataBuffer[i] & 0x000000FF) > 0x10) && (msgLen == 0);
88                                 } catch (IllegalArgumentException arg0) {
89                                     msgLen = 0;
90                                     variableLen = false;
91                                 }
92                             } else if (index == 5 && variableLen) {
93                                 msgLen = (readDataBuffer[i] & 0x000000FF) + 7;
94                             } else if ((msgLen == 0 && readDataBuffer[i] == 0x0A) || (index == msgLen)) {
95                                 // Postamble
96
97                                 if (readDataBuffer[i] != 0x0A && dataBuffer[index - 1] == 0x43) {
98                                     // adjust message length for 0x43
99                                     msgLen++;
100                                 } else if (checkCRC(dataBuffer, index)) {
101                                     // whole message received with a right CRC
102
103                                     byte[] msg = new byte[index];
104                                     msg = Arrays.copyOf(dataBuffer, index);
105
106                                     connector.setWaitingForResponse(System.currentTimeMillis());
107                                     connector.handleIncomingMessage(msg);
108
109                                     // find new preamble
110                                     index = 0;
111                                 } else if (msgLen == 0) {
112                                     // CRC check failed for a message with an unknown length
113                                     logger.debug("Message length is now {} but message is apparently not complete",
114                                             index + 1);
115                                 } else {
116                                     // CRC check failed for a message with a known length
117
118                                     connector.setWaitingForResponse(System.currentTimeMillis());
119
120                                     // find new preamble
121                                     index = 0;
122                                 }
123                             }
124                         }
125                     }
126                 }
127             }
128         } catch (InterruptedIOException e) {
129             Thread.currentThread().interrupt();
130             logger.debug("Interrupted via InterruptedIOException");
131         } catch (IOException e) {
132             logger.debug("Reading failed: {}", e.getMessage(), e);
133             connector.handleCommunicationFailure(e.getMessage());
134         } catch (Exception e) {
135             String msg = e.getMessage() != null ? e.getMessage() : e.toString();
136             logger.debug("Error reading or processing message: {}", msg, e);
137             connector.handleCommunicationFailure(msg);
138         }
139
140         logger.info("Data listener stopped");
141     }
142
143     /**
144      * Check if the CRC inside a received message is valid or not
145      *
146      * @param data the buffer containing the message
147      * @param len the size of the message in the buffer
148      *
149      * @return true if the CRC is valid or false if not
150      */
151     private boolean checkCRC(byte[] data, int len) {
152         // Messages of type 0xF1 are always sent with a bad CRC (possible panel bug?)
153         if (len == 9 && (data[1] & 0xFF) == 0xF1) {
154             return true;
155         }
156
157         byte checksum = PowermaxCommManager.computeCRC(data, len);
158         byte expected = data[len - 2];
159         if (checksum != expected) {
160             byte[] logData = Arrays.copyOf(data, len);
161             logger.warn("Powermax alarm binding: message CRC check failed (expected {}, got {}, message {})",
162                     String.format("%02X", expected), String.format("%02X", checksum), HexUtils.bytesToHex(logData));
163         }
164         return (checksum == expected);
165     }
166 }