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.apache.commons.lang3.ArrayUtils;
21 import org.openhab.core.io.transport.serial.PortInUseException;
22 import org.openhab.core.io.transport.serial.SerialPort;
23 import org.openhab.core.io.transport.serial.SerialPortEvent;
24 import org.openhab.core.io.transport.serial.SerialPortEventListener;
25 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
26 import org.openhab.core.io.transport.serial.SerialPortManager;
27 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
28 import org.openhab.core.util.HexUtils;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
33 * @author Volker Bier - Initial contribution
35 public class SerialConnection {
36 private final Logger logger = LoggerFactory.getLogger(SerialConnection.class);
38 private SerialPort serialPort;
40 private String portName;
41 private final List<Byte> bytes = new ArrayList<>();
42 private CommandPacket lastSentPacket = null;
43 private Response response = null;
44 private final SerialPortManager serialPortManager;
46 public SerialConnection(String portName, SerialPortManager serialPortManager) {
47 this.portName = portName;
48 this.serialPortManager = serialPortManager;
51 public synchronized void open() throws ConnectException {
54 logger.debug("Trying to open serial connection to port {}...", portName);
56 SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(portName);
57 if (portIdentifier == null) {
58 throw new ConnectException("No such port: " + portName);
62 serialPort = portIdentifier.open("openhab", 3000);
64 logger.debug("Serial connection to port {} opened.", portName);
66 serialPort.setSerialPortParams(38400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
67 SerialPort.PARITY_NONE);
69 serialPort.addEventListener(new SerialPortEventListener() {
71 public void serialEvent(SerialPortEvent event) {
73 if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
76 } catch (IOException ex) {
77 logger.warn("IOException reading from port {}!", portName);
82 serialPort.notifyOnDataAvailable(true);
83 } catch (UnsupportedCommOperationException | TooManyListenersException ex) {
85 throw new ConnectException(ex);
88 } catch (PortInUseException ex) {
89 throw new ConnectException(ex);
93 public synchronized boolean isOpen() {
97 public synchronized void close() {
99 logger.debug("Closing serial connection to port {}...", portName);
101 serialPort.notifyOnDataAvailable(false);
102 serialPort.removeEventListener();
108 // send a packet to the stick and wait for the response
109 public synchronized Response sendPacket(CommandPacket p) throws IOException {
113 synchronized (bytes) {
117 logger.debug("Writing packet to stick: {}", p);
118 serialPort.getOutputStream().write(p.getBytes());
120 final long responseTimeout = p.getResponseTimeout();
122 logger.trace("Waiting {} ms for answer from stick...", responseTimeout);
123 bytes.wait(responseTimeout);
124 } catch (InterruptedException e) {
125 Thread.currentThread().interrupt();
132 logger.debug("Stick answered {} for packet {}.", r, p);
136 logger.warn("Stick skipped packet {}. Connection is not open.", p);
140 private void parseInput() throws IOException {
141 logger.trace("parsing input...");
142 while (serialPort.getInputStream().available() > 0) {
143 byte b = (byte) serialPort.getInputStream().read();
146 logger.trace("input parsed. buffer contains {} bytes.", bytes.size());
150 private void analyzeBuffer() {
151 // drop everything before the beginning of the packet header 0xAA
152 while (!bytes.isEmpty() && bytes.get(0) != (byte) 0xAA) {
153 logger.trace("dropping byte {} from buffer", bytes.get(0));
157 if (logger.isTraceEnabled()) {
158 logger.trace("buffer contains bytes: {}",
159 HexUtils.bytesToHex(ArrayUtils.toPrimitive(bytes.toArray(new Byte[bytes.size()]))));
162 if (bytes.size() > 1) {
163 // second byte should be length byte (has to be either 0x04 or 0x05)
164 int len = bytes.get(1);
165 logger.trace("packet length is {} bytes.", len);
167 if (len != 4 && len != 5) {
168 // invalid length, drop packet
171 } else if (bytes.size() > len + 1) {
172 // we have a complete packet in the buffer, analyze it
173 // third byte should be response type byte (has to be either EASY_CONFIRM or EASY_ACK)
174 byte respType = bytes.get(2);
176 synchronized (bytes) {
177 if (respType == ResponseStatus.EASY_CONFIRM) {
178 logger.trace("response type is EASY_CONFIRM.");
180 long val = bytes.get(0) + bytes.get(1) + bytes.get(2) + bytes.get(3) + bytes.get(4)
182 if (val % 256 == 0) {
183 Response r = ResponseUtil.createResponse(bytes.get(3), bytes.get(4));
184 if (lastSentPacket != null && lastSentPacket.isEasyCheck()) {
187 logger.warn("response type does not match command {}. Skipping response.",
191 logger.warn("invalid response checksum. Skipping response.");
195 } else if (respType == ResponseStatus.EASY_ACK) {
196 logger.trace("response type is EASY_ACK.");
198 long val = bytes.get(0) + bytes.get(1) + bytes.get(2) + bytes.get(3) + bytes.get(4)
199 + bytes.get(5) + bytes.get(6);
200 if (val % 256 == 0) {
201 Response r = ResponseUtil.createResponse(bytes.get(3), bytes.get(4), bytes.get(5));
202 if (r.isResponseFor(lastSentPacket)) {
205 logger.warn("response does not match command channels. Skipping response.");
208 logger.warn("invalid response checksum. Skipping response.");
213 logger.warn("invalid response type {}. Skipping response.", respType);