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