2 * Copyright (c) 2010-2021 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.sonyprojector.internal.communication.serial;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.io.UnsupportedEncodingException;
19 import java.util.Arrays;
20 import java.util.TooManyListenersException;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.sonyprojector.internal.SonyProjectorException;
26 import org.openhab.binding.sonyprojector.internal.SonyProjectorModel;
27 import org.openhab.binding.sonyprojector.internal.communication.SonyProjectorConnector;
28 import org.openhab.binding.sonyprojector.internal.communication.SonyProjectorItem;
29 import org.openhab.core.io.transport.serial.PortInUseException;
30 import org.openhab.core.io.transport.serial.SerialPort;
31 import org.openhab.core.io.transport.serial.SerialPortEvent;
32 import org.openhab.core.io.transport.serial.SerialPortEventListener;
33 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
34 import org.openhab.core.io.transport.serial.SerialPortManager;
35 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
36 import org.openhab.core.util.HexUtils;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * Class for communicating with Sony Projectors through a serial connection
43 * @author Laurent Garnier - Initial contribution
46 public class SonyProjectorSerialConnector extends SonyProjectorConnector implements SerialPortEventListener {
48 private final Logger logger = LoggerFactory.getLogger(SonyProjectorSerialConnector.class);
50 private static final int BAUD_RATE = 38400;
51 private static final long READ_TIMEOUT_MS = TimeUnit.MILLISECONDS.toMillis(3500);
53 protected static final byte START_CODE = (byte) 0xA9;
54 protected static final byte END_CODE = (byte) 0x9A;
55 private static final byte GET = (byte) 0x01;
56 private static final byte SET = (byte) 0x00;
57 protected static final byte TYPE_ACK = (byte) 0x03;
58 private static final byte TYPE_ITEM = (byte) 0x02;
60 private String serialPortName;
61 private SerialPortManager serialPortManager;
63 private @Nullable SerialPort serialPort;
68 * @param serialPortManager the serial port manager
69 * @param serialPortName the serial port name to be used
70 * @param model the projector model in use
72 public SonyProjectorSerialConnector(SerialPortManager serialPortManager, String serialPortName,
73 SonyProjectorModel model) {
74 this(serialPortManager, serialPortName, model, false);
80 * @param serialPortManager the serial port manager
81 * @param serialPortName the serial port name to be used
82 * @param model the projector model in use
83 * @param simu whether the communication is simulated or real
85 public SonyProjectorSerialConnector(SerialPortManager serialPortManager, String serialPortName,
86 SonyProjectorModel model, boolean simu) {
89 this.serialPortManager = serialPortManager;
90 this.serialPortName = serialPortName;
94 public synchronized void open() throws SonyProjectorException {
96 logger.debug("Opening serial connection on port {}", serialPortName);
98 SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
99 if (portIdentifier == null) {
100 logger.debug("Opening serial connection failed: No Such Port: {}", serialPortName);
101 throw new SonyProjectorException("Opening serial connection failed: No Such Port");
104 SerialPort commPort = portIdentifier.open(this.getClass().getName(), 2000);
106 commPort.setSerialPortParams(BAUD_RATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
107 SerialPort.PARITY_EVEN);
108 commPort.enableReceiveThreshold(8);
109 commPort.enableReceiveTimeout(100);
110 commPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
112 InputStream dataIn = commPort.getInputStream();
113 OutputStream dataOut = commPort.getOutputStream();
115 if (dataOut != null) {
118 if (dataIn != null && dataIn.markSupported()) {
121 } catch (IOException e) {
125 // RXTX serial port library causes high CPU load
126 // Start event listener, which will just sleep and slow down event
129 commPort.addEventListener(this);
130 commPort.notifyOnDataAvailable(true);
131 } catch (TooManyListenersException e) {
132 logger.debug("Too Many Listeners Exception: {}", e.getMessage(), e);
135 this.serialPort = commPort;
136 this.dataIn = dataIn;
137 this.dataOut = dataOut;
141 logger.debug("Serial connection opened");
142 } catch (PortInUseException e) {
143 logger.debug("Opening serial connection failed: Port in Use Exception: {}", e.getMessage(), e);
144 throw new SonyProjectorException("Opening serial connection failed: Port in Use Exception");
145 } catch (UnsupportedCommOperationException e) {
146 logger.debug("Opening serial connection failed: Unsupported Comm Operation Exception: {}",
148 throw new SonyProjectorException(
149 "Opening serial connection failed: Unsupported Comm Operation Exception");
150 } catch (UnsupportedEncodingException e) {
151 logger.debug("Opening serial connection failed: Unsupported Encoding Exception: {}", e.getMessage(), e);
152 throw new SonyProjectorException("Opening serial connection failed: Unsupported Encoding Exception");
153 } catch (IOException e) {
154 logger.debug("Opening serial connection failed: IO Exception: {}", e.getMessage(), e);
155 throw new SonyProjectorException("Opening serial connection failed: IO Exception");
161 public synchronized void close() {
163 logger.debug("Closing serial connection");
164 SerialPort serialPort = this.serialPort;
165 if (serialPort != null) {
166 serialPort.removeEventListener();
169 if (serialPort != null) {
171 this.serialPort = null;
178 protected byte[] buildMessage(SonyProjectorItem item, boolean getCommand, byte[] data) {
179 byte[] message = new byte[8];
180 message[0] = START_CODE;
181 message[1] = item.getCode()[0];
182 message[2] = item.getCode()[1];
183 message[3] = getCommand ? GET : SET;
184 message[4] = data[0];
185 message[5] = data[1];
186 message[6] = computeCheckSum(message);
187 message[7] = END_CODE;
192 protected synchronized byte[] readResponse() throws SonyProjectorException {
193 logger.debug("readResponse (timeout = {} ms)...", READ_TIMEOUT_MS);
194 byte[] message = new byte[8];
195 boolean startCodeReached = false;
196 boolean endCodeReached = false;
197 boolean timeout = false;
198 byte[] dataBuffer = new byte[8];
200 long startTimeRead = System.currentTimeMillis();
201 while (!endCodeReached && !timeout) {
202 logger.trace("readResponse readInput...");
203 int len = readInput(dataBuffer);
204 logger.trace("readResponse readInput {} => {}", len, HexUtils.bytesToHex(dataBuffer));
206 for (int i = 0; i < len; i++) {
207 if (dataBuffer[i] == START_CODE) {
208 startCodeReached = true;
210 if (startCodeReached) {
212 message[index++] = dataBuffer[i];
214 if (dataBuffer[i] == END_CODE) {
215 endCodeReached = true;
220 timeout = (System.currentTimeMillis() - startTimeRead) > READ_TIMEOUT_MS;
222 if (!endCodeReached && timeout) {
223 logger.debug("readResponse timeout: only {} bytes read after {} ms", index, READ_TIMEOUT_MS);
224 throw new SonyProjectorException("readResponse failed: timeout");
226 logger.debug("readResponse: {}", HexUtils.bytesToHex(message));
232 protected void validateResponse(byte[] responseMessage, SonyProjectorItem item) throws SonyProjectorException {
233 if (responseMessage.length != 8) {
234 logger.debug("Unexpected response data length: {}", responseMessage.length);
235 throw new SonyProjectorException("Unexpected response data length");
239 if (responseMessage[0] != START_CODE) {
240 logger.debug("Unexpected message START CODE in response: {} rather than {}",
241 Integer.toHexString(responseMessage[0] & 0x000000FF), Integer.toHexString(START_CODE & 0x000000FF));
242 throw new SonyProjectorException("Unexpected message START CODE in response");
246 if (responseMessage[7] != END_CODE) {
247 logger.debug("Unexpected message END CODE in response: {} rather than {}",
248 Integer.toHexString(responseMessage[7] & 0x000000FF), Integer.toHexString(END_CODE & 0x000000FF));
249 throw new SonyProjectorException("Unexpected message END CODE in response");
252 byte checksum = computeCheckSum(responseMessage);
253 if (responseMessage[6] != checksum) {
254 logger.debug("Invalid check sum in response: {} rather than {}",
255 Integer.toHexString(responseMessage[6] & 0x000000FF), Integer.toHexString(checksum & 0x000000FF));
256 throw new SonyProjectorException("Invalid check sum in response");
259 if (responseMessage[3] == TYPE_ITEM) {
260 // Item number should be the same as used for sending
261 byte[] itemResponseMsg = Arrays.copyOfRange(responseMessage, 1, 3);
262 if (!Arrays.equals(itemResponseMsg, item.getCode())) {
263 logger.debug("Unexpected item number in response: {} rather than {}",
264 HexUtils.bytesToHex(itemResponseMsg), HexUtils.bytesToHex(item.getCode()));
265 throw new SonyProjectorException("Unexpected item number in response");
267 } else if (responseMessage[3] == TYPE_ACK) {
269 byte[] errorCode = Arrays.copyOfRange(responseMessage, 1, 3);
270 if (!Arrays.equals(errorCode, SonyProjectorSerialError.COMPLETE.getDataCode())) {
273 SonyProjectorSerialError error = SonyProjectorSerialError.getFromDataCode(errorCode);
274 msg = error.getMessage();
275 } catch (SonyProjectorException e) {
277 logger.debug("{} received in response", msg);
278 throw new SonyProjectorException(msg + " received in response");
281 logger.debug("Unexpected TYPE in response: {}", Integer.toHexString(responseMessage[3] & 0x000000FF));
282 throw new SonyProjectorException("Unexpected TYPE in response");
286 private byte computeCheckSum(byte[] message) {
288 for (int i = 1; i <= 5; i++) {
289 result |= (message[i] & 0x000000FF);
295 protected byte[] getResponseData(byte[] responseMessage) {
296 return Arrays.copyOfRange(responseMessage, 4, 6);
300 public void serialEvent(SerialPortEvent serialPortEvent) {
302 logger.debug("RXTX library CPU load workaround, sleep forever");
303 Thread.sleep(Long.MAX_VALUE);
304 } catch (InterruptedException e) {
305 Thread.currentThread().interrupt();