2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.powermax.internal.connector;
15 import java.io.IOException;
16 import java.io.InterruptedIOException;
17 import java.util.Arrays;
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;
27 * A class that reads messages from the Visonic alarm panel in a dedicated thread
29 * @author Laurent Garnier - Initial contribution
32 public class PowermaxReaderThread extends Thread {
34 private static final int READ_BUFFER_SIZE = 20;
35 private static final int MAX_MSG_SIZE = 0xC0;
37 private final Logger logger = LoggerFactory.getLogger(PowermaxReaderThread.class);
39 private final PowermaxConnector connector;
44 * @param in the input stream
45 * @param connector the object that should handle the received message
46 * @param threadName the name of the thread
48 public PowermaxReaderThread(PowermaxConnector connector, String threadName) {
50 this.connector = connector;
55 logger.info("Data listener started");
57 byte[] readDataBuffer = new byte[READ_BUFFER_SIZE];
58 byte[] dataBuffer = new byte[MAX_MSG_SIZE];
61 boolean variableLen = false;
64 while (!Thread.interrupted()) {
65 int len = connector.read(readDataBuffer);
67 for (int i = 0; i < len; i++) {
68 if (index >= MAX_MSG_SIZE) {
69 // too many bytes received, try to find new start
70 if (logger.isDebugEnabled()) {
71 byte[] logData = Arrays.copyOf(dataBuffer, index);
72 logger.debug("Truncating message {}", HexUtils.bytesToHex(logData));
77 if (index == 0 && readDataBuffer[i] == 0x0D) {
80 dataBuffer[index++] = readDataBuffer[i];
81 } else if (index > 0) {
82 dataBuffer[index++] = readDataBuffer[i];
86 PowermaxReceiveType msgType = PowermaxReceiveType.fromCode(readDataBuffer[i]);
87 msgLen = msgType.getLength();
88 variableLen = ((readDataBuffer[i] & 0x000000FF) > 0x10) && (msgLen == 0);
89 } catch (IllegalArgumentException arg0) {
93 } else if (index == 5 && variableLen) {
94 msgLen = (readDataBuffer[i] & 0x000000FF) + 7;
95 } else if ((msgLen == 0 && readDataBuffer[i] == 0x0A) || (index == msgLen)) {
98 if (readDataBuffer[i] != 0x0A && dataBuffer[index - 1] == 0x43) {
99 // adjust message length for 0x43
101 } else if (checkCRC(dataBuffer, index)) {
102 // whole message received with a right CRC
104 byte[] msg = new byte[index];
105 msg = Arrays.copyOf(dataBuffer, index);
107 connector.setWaitingForResponse(System.currentTimeMillis());
108 connector.handleIncomingMessage(msg);
112 } else if (msgLen == 0) {
113 // CRC check failed for a message with an unknown length
114 logger.debug("Message length is now {} but message is apparently not complete",
117 // CRC check failed for a message with a known length
119 connector.setWaitingForResponse(System.currentTimeMillis());
129 } catch (InterruptedIOException e) {
130 Thread.currentThread().interrupt();
131 logger.debug("Interrupted via InterruptedIOException");
132 } catch (IOException e) {
133 logger.debug("Reading failed: {}", e.getMessage(), e);
134 connector.handleCommunicationFailure(e.getMessage());
135 } catch (Exception e) {
136 String msg = e.getMessage() != null ? e.getMessage() : e.toString();
137 logger.debug("Error reading or processing message: {}", msg, e);
138 connector.handleCommunicationFailure(msg);
141 logger.info("Data listener stopped");
145 * Check if the CRC inside a received message is valid or not
147 * @param data the buffer containing the message
148 * @param len the size of the message in the buffer
150 * @return true if the CRC is valid or false if not
152 private boolean checkCRC(byte[] data, int len) {
153 // Messages of type 0xF1 are always sent with a bad CRC (possible panel bug?)
154 if (len == 9 && (data[1] & 0xFF) == 0xF1) {
158 byte checksum = PowermaxCommManager.computeCRC(data, len);
159 byte expected = data[len - 2];
160 if (checksum != expected) {
161 byte[] logData = Arrays.copyOf(data, len);
162 logger.warn("Powermax alarm binding: message CRC check failed (expected {}, got {}, message {})",
163 String.format("%02X", expected), String.format("%02X", checksum), HexUtils.bytesToHex(logData));
165 return (checksum == expected);