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.elerotransmitterstick.internal.stick;
15 import java.io.IOException;
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.TooManyListenersException;
20 import org.openhab.core.io.transport.serial.PortInUseException;
21 import org.openhab.core.io.transport.serial.SerialPort;
22 import org.openhab.core.io.transport.serial.SerialPortEvent;
23 import org.openhab.core.io.transport.serial.SerialPortEventListener;
24 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
25 import org.openhab.core.io.transport.serial.SerialPortManager;
26 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
27 import org.openhab.core.util.HexUtils;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
32 * @author Volker Bier - Initial contribution
34 public class SerialConnection {
35 private final Logger logger = LoggerFactory.getLogger(SerialConnection.class);
37 private SerialPort serialPort;
39 private String portName;
40 private final List<Byte> bytes = new ArrayList<>();
41 private CommandPacket lastSentPacket = null;
42 private Response response = null;
43 private final SerialPortManager serialPortManager;
45 public SerialConnection(String portName, SerialPortManager serialPortManager) {
46 this.portName = portName;
47 this.serialPortManager = serialPortManager;
50 public synchronized void open() throws ConnectException {
53 logger.debug("Trying to open serial connection to port {}...", portName);
55 SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(portName);
56 if (portIdentifier == null) {
57 throw new ConnectException("No such port: " + portName);
61 serialPort = portIdentifier.open("openhab", 3000);
63 logger.debug("Serial connection to port {} opened.", portName);
65 serialPort.setSerialPortParams(38400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
66 SerialPort.PARITY_NONE);
68 serialPort.addEventListener(new SerialPortEventListener() {
70 public void serialEvent(SerialPortEvent event) {
72 if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
75 } catch (IOException ex) {
76 logger.warn("IOException reading from port {}!", portName);
81 serialPort.notifyOnDataAvailable(true);
82 } catch (UnsupportedCommOperationException | TooManyListenersException ex) {
84 throw new ConnectException(ex);
87 } catch (PortInUseException ex) {
88 throw new ConnectException(ex);
92 public synchronized boolean isOpen() {
96 public synchronized void close() {
98 logger.debug("Closing serial connection to port {}...", portName);
100 serialPort.notifyOnDataAvailable(false);
101 serialPort.removeEventListener();
107 // send a packet to the stick and wait for the response
108 public synchronized Response sendPacket(CommandPacket p) throws IOException {
112 synchronized (bytes) {
116 logger.debug("Writing packet to stick: {}", p);
117 serialPort.getOutputStream().write(p.getBytes());
119 final long responseTimeout = p.getResponseTimeout();
121 logger.trace("Waiting {} ms for answer from stick...", responseTimeout);
122 bytes.wait(responseTimeout);
123 } catch (InterruptedException e) {
124 Thread.currentThread().interrupt();
131 logger.debug("Stick answered {} for packet {}.", r, p);
135 logger.warn("Stick skipped packet {}. Connection is not open.", p);
139 private void parseInput() throws IOException {
140 logger.trace("parsing input...");
141 while (serialPort.getInputStream().available() > 0) {
142 byte b = (byte) serialPort.getInputStream().read();
145 logger.trace("input parsed. buffer contains {} bytes.", bytes.size());
149 private void analyzeBuffer() {
150 // drop everything before the beginning of the packet header 0xAA
151 while (!bytes.isEmpty() && bytes.get(0) != (byte) 0xAA) {
152 logger.trace("dropping byte {} from buffer", bytes.get(0));
156 if (logger.isTraceEnabled()) {
158 byte[] primeBytes = new byte[bytes.size()];
159 for (Byte b : bytes.toArray(new Byte[bytes.size()])) {
160 primeBytes[j++] = b.byteValue();
163 logger.trace("buffer contains bytes: {}", HexUtils.bytesToHex(primeBytes));
166 if (bytes.size() > 1) {
167 // second byte should be length byte (has to be either 0x04 or 0x05)
168 int len = bytes.get(1);
169 logger.trace("packet length is {} bytes.", len);
171 if (len != 4 && len != 5) {
172 // invalid length, drop packet
175 } else if (bytes.size() > len + 1) {
176 // we have a complete packet in the buffer, analyze it
177 // third byte should be response type byte (has to be either EASY_CONFIRM or EASY_ACK)
178 byte respType = bytes.get(2);
180 synchronized (bytes) {
181 if (respType == ResponseStatus.EASY_CONFIRM) {
182 logger.trace("response type is EASY_CONFIRM.");
184 long val = bytes.get(0) + bytes.get(1) + bytes.get(2) + bytes.get(3) + bytes.get(4)
186 if (val % 256 == 0) {
187 Response r = ResponseUtil.createResponse(bytes.get(3), bytes.get(4));
188 if (lastSentPacket != null && lastSentPacket.isEasyCheck()) {
191 logger.warn("response type does not match command {}. Skipping response.",
195 logger.warn("invalid response checksum. Skipping response.");
199 } else if (respType == ResponseStatus.EASY_ACK) {
200 logger.trace("response type is EASY_ACK.");
202 long val = bytes.get(0) + bytes.get(1) + bytes.get(2) + bytes.get(3) + bytes.get(4)
203 + bytes.get(5) + bytes.get(6);
204 if (val % 256 == 0) {
205 Response r = ResponseUtil.createResponse(bytes.get(3), bytes.get(4), bytes.get(5));
206 if (r.isResponseFor(lastSentPacket)) {
209 logger.warn("response does not match command channels. Skipping response.");
212 logger.warn("invalid response checksum. Skipping response.");
217 logger.warn("invalid response type {}. Skipping response.", respType);