]> git.basschouten.com Git - openhab-addons.git/blob
ce633b5ebef43b3e2a0426ce737c1c676bee3da9
[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.vdr.internal.svdrp;
14
15 import java.io.BufferedReader;
16 import java.io.BufferedWriter;
17 import java.io.IOException;
18 import java.io.InputStreamReader;
19 import java.io.OutputStreamWriter;
20 import java.net.InetSocketAddress;
21 import java.net.Socket;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27
28 /**
29  * The {@link SVDRPClientImpl} encapsulates all calls to the SVDRP interface of a VDR
30  *
31  * @author Matthias Klocke - Initial contribution
32  */
33 @NonNullByDefault
34 public class SVDRPClientImpl implements SVDRPClient {
35
36     private String host;
37     private int port = 6419;
38     private String charset = "UTF-8";
39     private String version = "";
40
41     private static final String WELCOME_MESSAGE = "([0-9]{3})([ -])(.*)";
42     private static final Pattern PATTERN_WELCOME = Pattern.compile(WELCOME_MESSAGE);
43
44     private static final int TIMEOUT_MS = 3000;
45
46     private @Nullable Socket socket = null;
47     private @Nullable BufferedWriter out = null;
48     private @Nullable BufferedReader in = null;
49
50     public SVDRPClientImpl(String host, int port) {
51         super();
52         this.host = host;
53         this.port = port;
54     }
55
56     public SVDRPClientImpl(String host, int port, String charset) {
57         super();
58         this.host = host;
59         this.port = port;
60         this.charset = charset;
61     }
62
63     /**
64      *
65      * Open VDR Socket Connection
66      *
67      * @throws IOException if an IO Error occurs
68      */
69     @Override
70     public void openConnection() throws SVDRPConnectionException, SVDRPParseResponseException {
71         Socket localSocket = socket;
72         BufferedWriter localOut = out;
73         BufferedReader localIn = in;
74
75         if (localSocket == null || localSocket.isClosed()) {
76             localSocket = new Socket();
77             socket = localSocket;
78         }
79         try {
80             InetSocketAddress isa = new InetSocketAddress(host, port);
81             localSocket.connect(isa, TIMEOUT_MS);
82             localSocket.setSoTimeout(TIMEOUT_MS);
83
84             localOut = new BufferedWriter(new OutputStreamWriter(localSocket.getOutputStream(), charset), 8192);
85             out = localOut;
86             localIn = new BufferedReader(new InputStreamReader(localSocket.getInputStream(), charset), 8192);
87             in = localIn;
88
89             // read welcome message and init version & charset
90             SVDRPResponse res = null;
91
92             res = execute(null);
93
94             if (res.getCode() == 220) {
95                 SVDRPWelcome welcome = SVDRPWelcome.parse(res.getMessage());
96                 this.charset = welcome.getCharset();
97                 this.version = welcome.getVersion();
98             } else {
99                 throw new SVDRPParseResponseException(res);
100             }
101         } catch (IOException e) {
102             // cleanup after timeout
103             try {
104                 if (localOut != null)
105                     localOut.close();
106                 if (localIn != null)
107                     localIn.close();
108                 localSocket.close();
109             } catch (IOException ex) {
110             }
111             throw new SVDRPConnectionException(e.getMessage(), e);
112         }
113     }
114
115     /**
116      * Close VDR Socket Connection
117      *
118      * @throws IOException if an IO Error occurs
119      */
120     @Override
121     public void closeConnection() throws SVDRPConnectionException, SVDRPParseResponseException {
122         Socket localSocket = socket;
123         BufferedWriter localOut = out;
124         BufferedReader localIn = in;
125         /*
126          * socket on vdr stays in FIN_WAIT2 without closing connection
127          */
128         try {
129             if (localOut != null) {
130                 localOut.write("QUIT");
131                 localOut.newLine();
132                 localOut.flush();
133                 localOut.close();
134             }
135             if (localIn != null) {
136                 localIn.close();
137             }
138             if (localSocket != null) {
139                 localSocket.close();
140             }
141         } catch (IOException e) {
142             throw new SVDRPConnectionException(e.getMessage(), e);
143         }
144     }
145
146     /**
147      *
148      * execute SVDRP Call
149      *
150      * @param command SVDRP command to execute
151      * @return response of SVDRPCall
152      * @throws SVDRPException exception from SVDRP call
153      */
154     private SVDRPResponse execute(@Nullable String command)
155             throws SVDRPConnectionException, SVDRPParseResponseException {
156         BufferedWriter localOut = out;
157         BufferedReader localIn = in;
158
159         StringBuilder message = new StringBuilder();
160         Matcher matcher = null;
161
162         int code;
163         try {
164             if (command != null) {
165                 if (localOut == null) {
166                     throw new SVDRPConnectionException("OutputStream is null!");
167                 } else {
168                     localOut.write(command);
169                     localOut.newLine();
170                     localOut.flush();
171                 }
172             }
173
174             if (localIn != null) {
175                 code = -1;
176                 String line = null;
177                 boolean cont = true;
178                 while (cont && (line = localIn.readLine()) != null) {
179                     matcher = PATTERN_WELCOME.matcher(line);
180                     if (matcher.matches() && matcher.groupCount() > 2) {
181                         if (code < 0) {
182                             code = Integer.parseInt(matcher.group(1));
183                         }
184                         if (" ".equals(matcher.group(2))) {
185                             cont = false;
186                         }
187                         message.append(matcher.group(3));
188                         if (cont) {
189                             message.append(System.lineSeparator());
190                         }
191                     } else {
192                         cont = false;
193                     }
194                 }
195                 return new SVDRPResponse(code, message.toString());
196             } else {
197                 throw new SVDRPConnectionException("SVDRP Input Stream is Null");
198             }
199         } catch (IOException ioe) {
200             throw new SVDRPConnectionException(ioe.getMessage(), ioe);
201         } catch (NumberFormatException ne) {
202             throw new SVDRPParseResponseException(ne.getMessage(), ne);
203         }
204     }
205
206     /**
207      * Retrieve Disk Status from SVDRP Client
208      *
209      * @return SVDRP Disk Status
210      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
211      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
212      */
213     @Override
214     public SVDRPDiskStatus getDiskStatus() throws SVDRPConnectionException, SVDRPParseResponseException {
215         SVDRPResponse res = null;
216
217         res = execute("STAT disk");
218
219         if (res.getCode() == 250) {
220             SVDRPDiskStatus status = SVDRPDiskStatus.parse(res.getMessage());
221             return status;
222         } else {
223             throw new SVDRPParseResponseException(res);
224         }
225     }
226
227     /**
228      * Retrieve EPG Event from SVDRPClient
229      *
230      * @param type Type of EPG Event (now, next)
231      * @return SVDRP EPG Event
232      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
233      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
234      */
235     @Override
236     public SVDRPEpgEvent getEpgEvent(SVDRPEpgEvent.TYPE type)
237             throws SVDRPConnectionException, SVDRPParseResponseException {
238         SVDRPResponse res = null;
239         SVDRPChannel channel = this.getCurrentSVDRPChannel();
240         switch (type) {
241             case NOW:
242                 res = execute(String.format("LSTE %s %s", channel.getNumber(), "now"));
243                 break;
244             case NEXT:
245                 res = execute(String.format("LSTE %s %s", channel.getNumber(), "next"));
246                 break;
247         }
248
249         if (res != null && res.getCode() == 215) {
250             SVDRPEpgEvent entry = SVDRPEpgEvent.parse(res.getMessage());
251             return entry;
252         } else if (res != null) {
253             throw new SVDRPParseResponseException(res);
254         } else {
255             throw new SVDRPConnectionException("SVDRPResponse is Null");
256         }
257     }
258
259     /**
260      * Retrieve current volume from SVDRP Client
261      *
262      * @return SVDRP Volume Object
263      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
264      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
265      */
266     @Override
267     public SVDRPVolume getSVDRPVolume() throws SVDRPConnectionException, SVDRPParseResponseException {
268         SVDRPResponse res = null;
269
270         res = execute("VOLU");
271
272         if (res.getCode() == 250) {
273             SVDRPVolume volume = SVDRPVolume.parse(res.getMessage());
274             return volume;
275         } else {
276             throw new SVDRPParseResponseException(res);
277         }
278     }
279
280     /**
281      * Set volume on SVDRP Client
282      *
283      * @param newVolume Volume in Percent
284      * @return SVDRP Volume Object
285      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
286      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
287      */
288     @Override
289     public SVDRPVolume setSVDRPVolume(int newVolume) throws SVDRPConnectionException, SVDRPParseResponseException {
290         SVDRPResponse res = null;
291
292         double newVolumeDouble = newVolume * 255 / 100;
293         res = execute(String.format("VOLU %s", String.valueOf(Math.round(newVolumeDouble))));
294
295         if (res.getCode() == 250) {
296             SVDRPVolume volume = SVDRPVolume.parse(res.getMessage());
297             return volume;
298         } else {
299             throw new SVDRPParseResponseException(res);
300         }
301     }
302
303     /**
304      * Send Key command to SVDRP Client
305      *
306      * @param key Key Command to send
307      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
308      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
309      */
310     @Override
311     public void sendSVDRPKey(String key) throws SVDRPConnectionException, SVDRPParseResponseException {
312         SVDRPResponse res = null;
313
314         res = execute(String.format("HITK %s", key));
315
316         if (res.getCode() != 250) {
317             throw new SVDRPParseResponseException(res);
318         }
319     }
320
321     /**
322      * Send Message to SVDRP Client
323      *
324      * @param message Message to send
325      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
326      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
327      */
328     @Override
329     public void sendSVDRPMessage(String message) throws SVDRPConnectionException, SVDRPParseResponseException {
330         SVDRPResponse res = null;
331
332         res = execute(String.format("MESG %s", message));
333
334         if (res.getCode() != 250) {
335             throw new SVDRPParseResponseException(res);
336         }
337     }
338
339     /**
340      * Retrieve current Channel from SVDRP Client
341      *
342      * @return SVDRPChannel object
343      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
344      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
345      */
346     @Override
347     public SVDRPChannel getCurrentSVDRPChannel() throws SVDRPConnectionException, SVDRPParseResponseException {
348         SVDRPResponse res = null;
349
350         res = execute("CHAN");
351
352         if (res.getCode() == 250) {
353             SVDRPChannel channel = SVDRPChannel.parse(res.getMessage());
354             return channel;
355         } else {
356             throw new SVDRPParseResponseException(res);
357         }
358     }
359
360     /**
361      * Change current Channel on SVDRP Client
362      *
363      * @param number Channel to be set
364      * @return SVDRPChannel object
365      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
366      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
367      */
368     @Override
369     public SVDRPChannel setSVDRPChannel(int number) throws SVDRPConnectionException, SVDRPParseResponseException {
370         SVDRPResponse res = null;
371
372         res = execute(String.format("CHAN %s", number));
373
374         if (res.getCode() == 250) {
375             SVDRPChannel channel = SVDRPChannel.parse(res.getMessage());
376             return channel;
377         } else {
378             throw new SVDRPParseResponseException(res);
379         }
380     }
381
382     /**
383      * Retrieve from SVDRP Client if a recording is currently active
384      *
385      * @return is currently a recording active
386      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
387      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
388      */
389     @Override
390     public boolean isRecordingActive() throws SVDRPConnectionException, SVDRPParseResponseException {
391         SVDRPResponse res = null;
392
393         res = execute("LSTT");
394
395         if (res.getCode() == 250) {
396             SVDRPTimerList timers = SVDRPTimerList.parse(res.getMessage());
397             return timers.isRecordingActive();
398         } else if (res.getCode() == 550) {
399             // Error 550 is "No timers defined". Therefore there cannot be an active recording
400             return false;
401         } else {
402             throw new SVDRPParseResponseException(res);
403         }
404     }
405
406     /**
407      * Retrieve VDR Version from SVDRP Client
408      *
409      * @return VDR Version
410      * @throws SVDRPException thrown if something's not OK with SVDRP call
411      */
412     @Override
413     public String getSVDRPVersion() {
414         return version;
415     }
416 }