]> git.basschouten.com Git - openhab-addons.git/blob
673c86182d18470d369039a188aaca019fa8d5d3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.io.UnsupportedEncodingException;
19 import java.util.Arrays;
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.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;
39
40 /**
41  * Class for communicating with Sony Projectors through a serial connection
42  *
43  * @author Laurent Garnier - Initial contribution
44  */
45 @NonNullByDefault
46 public class SonyProjectorSerialConnector extends SonyProjectorConnector implements SerialPortEventListener {
47
48     private final Logger logger = LoggerFactory.getLogger(SonyProjectorSerialConnector.class);
49
50     private static final int BAUD_RATE = 38400;
51     private static final long READ_TIMEOUT_MS = TimeUnit.MILLISECONDS.toMillis(3500);
52
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;
59
60     private String serialPortName;
61     private SerialPortManager serialPortManager;
62
63     private @Nullable SerialPort serialPort;
64
65     /**
66      * Constructor
67      *
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
71      */
72     public SonyProjectorSerialConnector(SerialPortManager serialPortManager, String serialPortName,
73             SonyProjectorModel model) {
74         this(serialPortManager, serialPortName, model, false);
75     }
76
77     /**
78      * Constructor
79      *
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
84      */
85     public SonyProjectorSerialConnector(SerialPortManager serialPortManager, String serialPortName,
86             SonyProjectorModel model, boolean simu) {
87         super(model, simu);
88
89         this.serialPortManager = serialPortManager;
90         this.serialPortName = serialPortName;
91     }
92
93     @Override
94     public synchronized void open() throws SonyProjectorException {
95         if (!connected) {
96             logger.debug("Opening serial connection on port {}", serialPortName);
97             try {
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");
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 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: {}",
147                         e.getMessage(), e);
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");
156             }
157         }
158     }
159
160     @Override
161     public synchronized void close() {
162         if (connected) {
163             logger.debug("Closing serial connection");
164             SerialPort serialPort = this.serialPort;
165             if (serialPort != null) {
166                 serialPort.removeEventListener();
167             }
168             super.close();
169             if (serialPort != null) {
170                 serialPort.close();
171                 this.serialPort = null;
172             }
173             connected = false;
174         }
175     }
176
177     @Override
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;
188         return message;
189     }
190
191     @Override
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];
199         int index = 0;
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));
205             if (len > 0) {
206                 for (int i = 0; i < len; i++) {
207                     if (dataBuffer[i] == START_CODE) {
208                         startCodeReached = true;
209                     }
210                     if (startCodeReached) {
211                         if (index < 8) {
212                             message[index++] = dataBuffer[i];
213                         }
214                         if (dataBuffer[i] == END_CODE) {
215                             endCodeReached = true;
216                         }
217                     }
218                 }
219             }
220             timeout = (System.currentTimeMillis() - startTimeRead) > READ_TIMEOUT_MS;
221         }
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");
225         }
226         logger.debug("readResponse: {}", HexUtils.bytesToHex(message));
227
228         return message;
229     }
230
231     @Override
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");
236         }
237
238         // Check START CODE
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");
243         }
244
245         // Check END CODE
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");
250         }
251
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");
257         }
258
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");
266             }
267         } else if (responseMessage[3] == TYPE_ACK) {
268             // ACK
269             byte[] errorCode = Arrays.copyOfRange(responseMessage, 1, 3);
270             if (!Arrays.equals(errorCode, SonyProjectorSerialError.COMPLETE.getDataCode())) {
271                 String msg = "KO";
272                 try {
273                     SonyProjectorSerialError error = SonyProjectorSerialError.getFromDataCode(errorCode);
274                     msg = error.getMessage();
275                 } catch (SonyProjectorException e) {
276                 }
277                 logger.debug("{} received in response", msg);
278                 throw new SonyProjectorException(msg + " received in response");
279             }
280         } else {
281             logger.debug("Unexpected TYPE in response: {}", Integer.toHexString(responseMessage[3] & 0x000000FF));
282             throw new SonyProjectorException("Unexpected TYPE in response");
283         }
284     }
285
286     private byte computeCheckSum(byte[] message) {
287         byte result = 0;
288         for (int i = 1; i <= 5; i++) {
289             result |= (message[i] & 0x000000FF);
290         }
291         return result;
292     }
293
294     @Override
295     protected byte[] getResponseData(byte[] responseMessage) {
296         return Arrays.copyOfRange(responseMessage, 4, 6);
297     }
298
299     @Override
300     public void serialEvent(SerialPortEvent serialPortEvent) {
301         try {
302             logger.debug("RXTX library CPU load workaround, sleep forever");
303             Thread.sleep(Long.MAX_VALUE);
304         } catch (InterruptedException e) {
305             Thread.currentThread().interrupt();
306         }
307     }
308 }