]> git.basschouten.com Git - openhab-addons.git/blob
8055763283351b0e3cf455914c56ace9f777e706
[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.oppo.internal.communication;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.nio.charset.StandardCharsets;
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.oppo.internal.OppoException;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * Abstract class for communicating with the Oppo player
32  *
33  * @author Laurent Garnier - Initial contribution
34  * @author Michael Lobstein - Adapted for the Oppo binding
35  */
36 @NonNullByDefault
37 public abstract class OppoConnector {
38     private static final Pattern QRY_PATTERN = Pattern.compile("^@(Q[A-Z0-9]{2}|VUP|VDN) OK (.*)$");
39     private static final Pattern STUS_PATTERN = Pattern.compile("^@(U[A-Z0-9]{2}) (.*)$");
40
41     private static final String NOP_OK = "@NOP OK";
42     private static final String NOP = "NOP";
43     private static final String OK = "OK";
44
45     private final Logger logger = LoggerFactory.getLogger(OppoConnector.class);
46
47     private String beginCmd = "#";
48     private String endCmd = "\r";
49
50     /** The output stream */
51     protected @Nullable OutputStream dataOut;
52
53     /** The input stream */
54     protected @Nullable InputStream dataIn;
55
56     /** true if the connection is established, false if not */
57     private boolean connected;
58
59     private @Nullable Thread readerThread;
60
61     private final List<OppoMessageEventListener> listeners = new ArrayList<>();
62
63     /**
64      * Called when using direct IP connection for 83/93/95/103/105
65      * overrides the command message preamble and removes the CR at the end
66      */
67     public void overrideCmdPreamble(boolean override) {
68         if (override) {
69             this.beginCmd = "REMOTE ";
70             this.endCmd = "";
71         }
72     }
73
74     /**
75      * Get whether the connection is established or not
76      *
77      * @return true if the connection is established
78      */
79     public boolean isConnected() {
80         return connected;
81     }
82
83     /**
84      * Set whether the connection is established or not
85      *
86      * @param connected true if the connection is established
87      */
88     protected void setConnected(boolean connected) {
89         this.connected = connected;
90     }
91
92     /**
93      * Set the thread that handles the feedback messages
94      *
95      * @param readerThread the thread
96      */
97     protected void setReaderThread(Thread readerThread) {
98         this.readerThread = readerThread;
99     }
100
101     /**
102      * Open the connection with the Oppo player
103      *
104      * @throws OppoException - In case of any problem
105      */
106     public abstract void open() throws OppoException;
107
108     /**
109      * Close the connection with the Oppo player
110      */
111     public abstract void close();
112
113     /**
114      * Stop the thread that handles the feedback messages and close the opened input and output streams
115      */
116     protected void cleanup() {
117         Thread readerThread = this.readerThread;
118         OutputStream dataOut = this.dataOut;
119         if (dataOut != null) {
120             try {
121                 dataOut.close();
122             } catch (IOException e) {
123                 logger.debug("Error closing dataOut: {}", e.getMessage());
124             }
125             this.dataOut = null;
126         }
127         InputStream dataIn = this.dataIn;
128         if (dataIn != null) {
129             try {
130                 dataIn.close();
131             } catch (IOException e) {
132                 logger.debug("Error closing dataIn: {}", e.getMessage());
133             }
134             this.dataIn = null;
135         }
136         if (readerThread != null) {
137             readerThread.interrupt();
138             this.readerThread = null;
139             try {
140                 readerThread.join(3000);
141             } catch (InterruptedException e) {
142                 logger.warn("Error joining readerThread: {}", e.getMessage());
143             }
144         }
145     }
146
147     /**
148      * Reads some number of bytes from the input stream and stores them into the buffer array b. The number of bytes
149      * actually read is returned as an integer.
150      *
151      * @param dataBuffer the buffer into which the data is read.
152      *
153      * @return the total number of bytes read into the buffer, or -1 if there is no more data because the end of the
154      *         stream has been reached.
155      *
156      * @throws OppoException - If the input stream is null, if the first byte cannot be read for any reason
157      *             other than the end of the file, if the input stream has been closed, or if some other I/O error
158      *             occurs.
159      */
160     protected int readInput(byte[] dataBuffer) throws OppoException {
161         InputStream dataIn = this.dataIn;
162         if (dataIn == null) {
163             throw new OppoException("readInput failed: input stream is null");
164         }
165         try {
166             return dataIn.read(dataBuffer);
167         } catch (IOException e) {
168             throw new OppoException("readInput failed: " + e.getMessage(), e);
169         }
170     }
171
172     /**
173      * Request the Oppo controller to execute a command and pass in a value
174      *
175      * @param cmd the command to execute
176      * @param value the string value to pass with the command
177      *
178      * @throws OppoException - In case of any problem
179      */
180     public void sendCommand(OppoCommand cmd, @Nullable String value) throws OppoException {
181         sendCommand(cmd.getValue() + " " + value);
182     }
183
184     /**
185      * Request the Oppo controller to execute a command that does not specify a value
186      *
187      * @param cmd the command to execute
188      *
189      * @throws OppoException - In case of any problem
190      */
191     public void sendCommand(OppoCommand cmd) throws OppoException {
192         sendCommand(cmd.getValue());
193     }
194
195     /**
196      * Request the Oppo controller to execute a raw command string
197      *
198      * @param command the command string to run
199      *
200      * @throws OppoException - In case of any problem
201      */
202     public void sendCommand(String command) throws OppoException {
203         String messageStr = beginCmd + command + endCmd;
204         logger.debug("Sending command: {}", messageStr);
205
206         OutputStream dataOut = this.dataOut;
207         if (dataOut == null) {
208             throw new OppoException("Send command \"" + messageStr + "\" failed: output stream is null");
209         }
210         try {
211             dataOut.write(messageStr.getBytes(StandardCharsets.US_ASCII));
212             dataOut.flush();
213         } catch (IOException e) {
214             logger.debug("Send command \"{}\" failed: {}", messageStr, e.getMessage());
215             throw new OppoException("Send command \"" + command + "\" failed: " + e.getMessage(), e);
216         }
217     }
218
219     /**
220      * Add a listener to the list of listeners to be notified with events
221      *
222      * @param listener the listener
223      */
224     public void addEventListener(OppoMessageEventListener listener) {
225         listeners.add(listener);
226     }
227
228     /**
229      * Remove a listener from the list of listeners to be notified with events
230      *
231      * @param listener the listener
232      */
233     public void removeEventListener(OppoMessageEventListener listener) {
234         listeners.remove(listener);
235     }
236
237     /**
238      * Analyze an incoming message and dispatch corresponding (key, value) to the event listeners
239      *
240      * @param incomingMessage the received message
241      */
242     public void handleIncomingMessage(byte[] incomingMessage) {
243         String message = new String(incomingMessage, StandardCharsets.US_ASCII).trim();
244
245         logger.debug("handleIncomingMessage: {}", message);
246
247         if (NOP_OK.equals(message)) {
248             dispatchKeyValue(NOP, OK);
249             return;
250         }
251
252         // Player sent an OK response to a query: @QDT OK DVD-VIDEO or a volume update @VUP OK 100
253         Matcher matcher = QRY_PATTERN.matcher(message);
254         if (matcher.find()) {
255             // pull out the inquiry type and the remainder of the message
256             dispatchKeyValue(matcher.group(1), matcher.group(2));
257             return;
258         }
259
260         // Player sent a status update ie: @UTC 000 000 T 00:00:01
261         matcher = STUS_PATTERN.matcher(message);
262         if (matcher.find()) {
263             // pull out the update type and the remainder of the message
264             dispatchKeyValue(matcher.group(1), matcher.group(2));
265             return;
266         }
267
268         logger.debug("unhandled message: {}", message);
269     }
270
271     /**
272      * Dispatch an event (key, value) to the event listeners
273      *
274      * @param key the key
275      * @param value the value
276      */
277     private void dispatchKeyValue(String key, String value) {
278         OppoMessageEvent event = new OppoMessageEvent(this, key, value);
279         listeners.forEach(l -> l.onNewMessageEvent(event));
280     }
281 }