]> git.basschouten.com Git - openhab-addons.git/blob
2b9460f50f5b1729fd8a119f3684637210e74298
[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.sonyprojector.internal.communication.serial;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.util.Arrays;
19 import java.util.Objects;
20 import java.util.TooManyListenersException;
21 import java.util.concurrent.TimeUnit;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.sonyprojector.internal.SonyProjectorModel;
26 import org.openhab.binding.sonyprojector.internal.communication.SonyProjectorConnector;
27 import org.openhab.binding.sonyprojector.internal.communication.SonyProjectorItem;
28 import org.openhab.core.i18n.CommunicationException;
29 import org.openhab.core.i18n.ConnectionException;
30 import org.openhab.core.io.transport.serial.PortInUseException;
31 import org.openhab.core.io.transport.serial.SerialPort;
32 import org.openhab.core.io.transport.serial.SerialPortEvent;
33 import org.openhab.core.io.transport.serial.SerialPortEventListener;
34 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
35 import org.openhab.core.io.transport.serial.SerialPortManager;
36 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
37 import org.openhab.core.util.HexUtils;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Class for communicating with Sony Projectors through a serial connection
43  *
44  * @author Laurent Garnier - Initial contribution
45  */
46 @NonNullByDefault
47 public class SonyProjectorSerialConnector extends SonyProjectorConnector implements SerialPortEventListener {
48
49     private final Logger logger = LoggerFactory.getLogger(SonyProjectorSerialConnector.class);
50
51     private static final int BAUD_RATE = 38400;
52     private static final long READ_TIMEOUT_MS = TimeUnit.MILLISECONDS.toMillis(3500);
53
54     protected static final byte START_CODE = (byte) 0xA9;
55     protected static final byte END_CODE = (byte) 0x9A;
56     private static final byte GET = (byte) 0x01;
57     private static final byte SET = (byte) 0x00;
58     protected static final byte TYPE_ACK = (byte) 0x03;
59     private static final byte TYPE_ITEM = (byte) 0x02;
60
61     private String serialPortName;
62     private SerialPortManager serialPortManager;
63
64     private @Nullable SerialPort serialPort;
65
66     /**
67      * Constructor
68      *
69      * @param serialPortManager the serial port manager
70      * @param serialPortName the serial port name to be used
71      * @param model the projector model in use
72      */
73     public SonyProjectorSerialConnector(SerialPortManager serialPortManager, String serialPortName,
74             SonyProjectorModel model) {
75         this(serialPortManager, serialPortName, model, false);
76     }
77
78     /**
79      * Constructor
80      *
81      * @param serialPortManager the serial port manager
82      * @param serialPortName the serial port name to be used
83      * @param model the projector model in use
84      * @param simu whether the communication is simulated or real
85      */
86     public SonyProjectorSerialConnector(SerialPortManager serialPortManager, String serialPortName,
87             SonyProjectorModel model, boolean simu) {
88         super(model, simu);
89
90         this.serialPortManager = serialPortManager;
91         this.serialPortName = serialPortName;
92     }
93
94     @Override
95     public synchronized void open() throws ConnectionException {
96         if (!connected) {
97             logger.debug("Opening serial connection on port {}", serialPortName);
98             try {
99                 SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
100                 if (portIdentifier == null) {
101                     throw new ConnectionException("@text/exception.invalid-serial-port", serialPortName);
102                 }
103
104                 SerialPort commPort = portIdentifier.open(this.getClass().getName(), 2000);
105
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);
111
112                 InputStream dataIn = commPort.getInputStream();
113                 OutputStream dataOut = commPort.getOutputStream();
114
115                 if (dataOut != null) {
116                     dataOut.flush();
117                 }
118                 if (dataIn != null && dataIn.markSupported()) {
119                     try {
120                         dataIn.reset();
121                     } catch (IOException e) {
122                     }
123                 }
124
125                 // RXTX serial port library causes high CPU load
126                 // Start event listener, which will just sleep and slow down event
127                 // loop
128                 try {
129                     commPort.addEventListener(this);
130                     commPort.notifyOnDataAvailable(true);
131                 } catch (TooManyListenersException e) {
132                     logger.debug("Too Many Listeners Exception: {}", e.getMessage(), e);
133                 }
134
135                 this.serialPort = commPort;
136                 this.dataIn = dataIn;
137                 this.dataOut = dataOut;
138
139                 connected = true;
140
141                 logger.debug("Serial connection opened");
142             } catch (PortInUseException | UnsupportedCommOperationException | IOException e) {
143                 throw new ConnectionException("@text/exception.opening-serial-connection-failed", e);
144             }
145         }
146     }
147
148     @Override
149     public synchronized void close() {
150         if (connected) {
151             logger.debug("Closing serial connection");
152             SerialPort serialPort = this.serialPort;
153             if (serialPort != null) {
154                 serialPort.removeEventListener();
155             }
156             super.close();
157             if (serialPort != null) {
158                 serialPort.close();
159                 this.serialPort = null;
160             }
161             connected = false;
162         }
163     }
164
165     @Override
166     protected byte[] buildMessage(byte[] itemCode, boolean getCommand, byte[] data) {
167         byte[] message = new byte[8];
168         message[0] = START_CODE;
169         message[1] = itemCode[0];
170         message[2] = itemCode[1];
171         message[3] = getCommand ? GET : SET;
172         message[4] = data[0];
173         message[5] = data[1];
174         message[6] = computeCheckSum(message);
175         message[7] = END_CODE;
176         return message;
177     }
178
179     @Override
180     protected synchronized byte[] readResponse() throws CommunicationException {
181         logger.debug("readResponse (timeout = {} ms)...", READ_TIMEOUT_MS);
182         byte[] message = new byte[8];
183         boolean startCodeReached = false;
184         boolean endCodeReached = false;
185         boolean timeout = false;
186         byte[] dataBuffer = new byte[8];
187         int index = 0;
188         long startTimeRead = System.currentTimeMillis();
189         while (!endCodeReached && !timeout) {
190             logger.trace("readResponse readInput...");
191             int len = readInput(dataBuffer);
192             logger.trace("readResponse readInput {} => {}", len, HexUtils.bytesToHex(dataBuffer));
193             if (len > 0) {
194                 for (int i = 0; i < len; i++) {
195                     if (dataBuffer[i] == START_CODE) {
196                         startCodeReached = true;
197                     }
198                     if (startCodeReached) {
199                         if (index < 8) {
200                             message[index++] = dataBuffer[i];
201                         }
202                         if (dataBuffer[i] == END_CODE) {
203                             endCodeReached = true;
204                         }
205                     }
206                 }
207             }
208             timeout = (System.currentTimeMillis() - startTimeRead) > READ_TIMEOUT_MS;
209         }
210         if (!endCodeReached && timeout) {
211             logger.debug("readResponse timeout: only {} bytes read after {} ms", index, READ_TIMEOUT_MS);
212             throw new CommunicationException("readResponse failed: timeout");
213         }
214         logger.debug("readResponse: {}", HexUtils.bytesToHex(message));
215
216         return message;
217     }
218
219     @Override
220     protected void validateResponse(byte[] responseMessage, SonyProjectorItem item) throws CommunicationException {
221         if (responseMessage.length != 8) {
222             logger.debug("Unexpected response data length: {}", responseMessage.length);
223             throw new CommunicationException("Unexpected response data length");
224         }
225
226         // Check START CODE
227         if (responseMessage[0] != START_CODE) {
228             logger.debug("Unexpected message START CODE in response: {} rather than {}",
229                     Integer.toHexString(responseMessage[0] & 0x000000FF), Integer.toHexString(START_CODE & 0x000000FF));
230             throw new CommunicationException("Unexpected message START CODE in response");
231         }
232
233         // Check END CODE
234         if (responseMessage[7] != END_CODE) {
235             logger.debug("Unexpected  message END CODE in response: {} rather than {}",
236                     Integer.toHexString(responseMessage[7] & 0x000000FF), Integer.toHexString(END_CODE & 0x000000FF));
237             throw new CommunicationException("Unexpected message END CODE in response");
238         }
239
240         byte checksum = computeCheckSum(responseMessage);
241         if (responseMessage[6] != checksum) {
242             logger.debug("Invalid check sum in response: {} rather than {}",
243                     Integer.toHexString(responseMessage[6] & 0x000000FF), Integer.toHexString(checksum & 0x000000FF));
244             throw new CommunicationException("Invalid check sum in response");
245         }
246
247         if (responseMessage[3] == TYPE_ITEM) {
248             // Item number should be the same as used for sending
249             byte[] itemResponseMsg = Arrays.copyOfRange(responseMessage, 1, 3);
250             byte[] itemSentMsg = Objects.requireNonNull(item.getCode());
251             if (!Arrays.equals(itemResponseMsg, itemSentMsg)) {
252                 logger.debug("Unexpected item number in response: {} rather than {}",
253                         HexUtils.bytesToHex(itemResponseMsg), HexUtils.bytesToHex(itemSentMsg));
254                 throw new CommunicationException("Unexpected item number in response");
255             }
256         } else if (responseMessage[3] == TYPE_ACK) {
257             // ACK
258             byte[] errorCode = Arrays.copyOfRange(responseMessage, 1, 3);
259             if (!Arrays.equals(errorCode, SonyProjectorSerialError.COMPLETE.getDataCode())) {
260                 String msg = "KO";
261                 try {
262                     SonyProjectorSerialError error = SonyProjectorSerialError.getFromDataCode(errorCode);
263                     msg = error.getMessage();
264                 } catch (CommunicationException e) {
265                 }
266                 logger.debug("{} received in response", msg);
267                 throw new CommunicationException(msg + " received in response");
268             }
269         } else {
270             logger.debug("Unexpected TYPE in response: {}", Integer.toHexString(responseMessage[3] & 0x000000FF));
271             throw new CommunicationException("Unexpected TYPE in response");
272         }
273     }
274
275     private byte computeCheckSum(byte[] message) {
276         byte result = 0;
277         for (int i = 1; i <= 5; i++) {
278             result |= (message[i] & 0x000000FF);
279         }
280         return result;
281     }
282
283     @Override
284     protected byte[] getResponseData(byte[] responseMessage) {
285         return Arrays.copyOfRange(responseMessage, 4, 6);
286     }
287
288     @Override
289     public void serialEvent(SerialPortEvent serialPortEvent) {
290         try {
291             logger.debug("RXTX library CPU load workaround, sleep forever");
292             Thread.sleep(Long.MAX_VALUE);
293         } catch (InterruptedException e) {
294             Thread.currentThread().interrupt();
295         }
296     }
297 }