]> git.basschouten.com Git - openhab-addons.git/blob
06a6dfe78e9ea62254cf5ef823afc88cd840a3fb
[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.elerotransmitterstick.internal.stick;
14
15 import java.io.IOException;
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.TooManyListenersException;
19
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;
31
32 /**
33  * @author Volker Bier - Initial contribution
34  */
35 public class SerialConnection {
36     private final Logger logger = LoggerFactory.getLogger(SerialConnection.class);
37
38     private SerialPort serialPort;
39     private boolean open;
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;
45
46     public SerialConnection(String portName, SerialPortManager serialPortManager) {
47         this.portName = portName;
48         this.serialPortManager = serialPortManager;
49     }
50
51     public synchronized void open() throws ConnectException {
52         try {
53             if (!open) {
54                 logger.debug("Trying to open serial connection to port {}...", portName);
55
56                 SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(portName);
57                 if (portIdentifier == null) {
58                     throw new ConnectException("No such port: " + portName);
59                 }
60
61                 try {
62                     serialPort = portIdentifier.open("openhab", 3000);
63                     open = true;
64                     logger.debug("Serial connection to port {} opened.", portName);
65
66                     serialPort.setSerialPortParams(38400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
67                             SerialPort.PARITY_NONE);
68
69                     serialPort.addEventListener(new SerialPortEventListener() {
70                         @Override
71                         public void serialEvent(SerialPortEvent event) {
72                             try {
73                                 if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
74                                     parseInput();
75                                 }
76                             } catch (IOException ex) {
77                                 logger.warn("IOException reading from port {}!", portName);
78                             }
79                         }
80                     });
81
82                     serialPort.notifyOnDataAvailable(true);
83                 } catch (UnsupportedCommOperationException | TooManyListenersException ex) {
84                     close();
85                     throw new ConnectException(ex);
86                 }
87             }
88         } catch (PortInUseException ex) {
89             throw new ConnectException(ex);
90         }
91     }
92
93     public synchronized boolean isOpen() {
94         return open;
95     }
96
97     public synchronized void close() {
98         if (open) {
99             logger.debug("Closing serial connection to port {}...", portName);
100
101             serialPort.notifyOnDataAvailable(false);
102             serialPort.removeEventListener();
103             serialPort.close();
104             open = false;
105         }
106     }
107
108     // send a packet to the stick and wait for the response
109     public synchronized Response sendPacket(CommandPacket p) throws IOException {
110         if (open) {
111             Response r = null;
112
113             synchronized (bytes) {
114                 response = null;
115                 lastSentPacket = p;
116
117                 logger.debug("Writing packet to stick: {}", p);
118                 serialPort.getOutputStream().write(p.getBytes());
119
120                 final long responseTimeout = p.getResponseTimeout();
121                 try {
122                     logger.trace("Waiting {} ms for answer from stick...", responseTimeout);
123                     bytes.wait(responseTimeout);
124                 } catch (InterruptedException e) {
125                     Thread.currentThread().interrupt();
126                 }
127
128                 r = response;
129                 response = null;
130             }
131
132             logger.debug("Stick answered {} for packet {}.", r, p);
133             return r;
134         }
135
136         logger.warn("Stick skipped packet {}. Connection is not open.", p);
137         return null;
138     }
139
140     private void parseInput() throws IOException {
141         logger.trace("parsing input...");
142         while (serialPort.getInputStream().available() > 0) {
143             byte b = (byte) serialPort.getInputStream().read();
144             bytes.add(b);
145         }
146         logger.trace("input parsed. buffer contains {} bytes.", bytes.size());
147         analyzeBuffer();
148     }
149
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));
154             bytes.remove(0);
155         }
156
157         if (logger.isTraceEnabled()) {
158             logger.trace("buffer contains bytes: {}",
159                     HexUtils.bytesToHex(ArrayUtils.toPrimitive(bytes.toArray(new Byte[bytes.size()]))));
160         }
161
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);
166
167             if (len != 4 && len != 5) {
168                 // invalid length, drop packet
169                 bytes.remove(0);
170                 analyzeBuffer();
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);
175
176                 synchronized (bytes) {
177                     if (respType == ResponseStatus.EASY_CONFIRM) {
178                         logger.trace("response type is EASY_CONFIRM.");
179
180                         long val = bytes.get(0) + bytes.get(1) + bytes.get(2) + bytes.get(3) + bytes.get(4)
181                                 + bytes.get(5);
182                         if (val % 256 == 0) {
183                             Response r = ResponseUtil.createResponse(bytes.get(3), bytes.get(4));
184                             if (lastSentPacket != null && lastSentPacket.isEasyCheck()) {
185                                 response = r;
186                             } else {
187                                 logger.warn("response type does not match command {}. Skipping response.",
188                                         lastSentPacket);
189                             }
190                         } else {
191                             logger.warn("invalid response checksum. Skipping response.");
192                         }
193
194                         bytes.notify();
195                     } else if (respType == ResponseStatus.EASY_ACK) {
196                         logger.trace("response type is EASY_ACK.");
197
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)) {
203                                 response = r;
204                             } else {
205                                 logger.warn("response does not match command channels. Skipping response.");
206                             }
207                         } else {
208                             logger.warn("invalid response checksum. Skipping response.");
209                         }
210
211                         bytes.notify();
212                     } else {
213                         logger.warn("invalid response type {}. Skipping response.", respType);
214                     }
215                 }
216
217                 bytes.remove(0);
218                 analyzeBuffer();
219             }
220         }
221     }
222 }