]> git.basschouten.com Git - openhab-addons.git/blob
669a6112b80b41d1ace9c3cf47997f91c23d21d2
[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.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                 }
107                 if (localIn != null) {
108                     localIn.close();
109                 }
110                 localSocket.close();
111             } catch (IOException ex) {
112             }
113             throw new SVDRPConnectionException(e.getMessage(), e);
114         }
115     }
116
117     /**
118      * Close VDR Socket Connection
119      *
120      * @throws IOException if an IO Error occurs
121      */
122     @Override
123     public void closeConnection() throws SVDRPConnectionException, SVDRPParseResponseException {
124         Socket localSocket = socket;
125         BufferedWriter localOut = out;
126         BufferedReader localIn = in;
127         /*
128          * socket on vdr stays in FIN_WAIT2 without closing connection
129          */
130         try {
131             if (localOut != null) {
132                 localOut.write("QUIT");
133                 localOut.newLine();
134                 localOut.flush();
135                 localOut.close();
136             }
137             if (localIn != null) {
138                 localIn.close();
139             }
140             if (localSocket != null) {
141                 localSocket.close();
142             }
143         } catch (IOException e) {
144             throw new SVDRPConnectionException(e.getMessage(), e);
145         }
146     }
147
148     /**
149      *
150      * execute SVDRP Call
151      *
152      * @param command SVDRP command to execute
153      * @return response of SVDRPCall
154      * @throws SVDRPException exception from SVDRP call
155      */
156     private SVDRPResponse execute(@Nullable String command)
157             throws SVDRPConnectionException, SVDRPParseResponseException {
158         BufferedWriter localOut = out;
159         BufferedReader localIn = in;
160
161         StringBuilder message = new StringBuilder();
162         Matcher matcher = null;
163
164         int code;
165         try {
166             if (command != null) {
167                 if (localOut == null) {
168                     throw new SVDRPConnectionException("OutputStream is null!");
169                 } else {
170                     localOut.write(command);
171                     localOut.newLine();
172                     localOut.flush();
173                 }
174             }
175
176             if (localIn != null) {
177                 code = -1;
178                 String line = null;
179                 boolean cont = true;
180                 while (cont && (line = localIn.readLine()) != null) {
181                     matcher = PATTERN_WELCOME.matcher(line);
182                     if (matcher.matches() && matcher.groupCount() > 2) {
183                         if (code < 0) {
184                             code = Integer.parseInt(matcher.group(1));
185                         }
186                         if (" ".equals(matcher.group(2))) {
187                             cont = false;
188                         }
189                         message.append(matcher.group(3));
190                         if (cont) {
191                             message.append(System.lineSeparator());
192                         }
193                     } else {
194                         cont = false;
195                     }
196                 }
197                 return new SVDRPResponse(code, message.toString());
198             } else {
199                 throw new SVDRPConnectionException("SVDRP Input Stream is Null");
200             }
201         } catch (IOException ioe) {
202             throw new SVDRPConnectionException(ioe.getMessage(), ioe);
203         } catch (NumberFormatException ne) {
204             throw new SVDRPParseResponseException(ne.getMessage(), ne);
205         }
206     }
207
208     /**
209      * Retrieve Disk Status from SVDRP Client
210      *
211      * @return SVDRP Disk Status
212      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
213      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
214      */
215     @Override
216     public SVDRPDiskStatus getDiskStatus() throws SVDRPConnectionException, SVDRPParseResponseException {
217         SVDRPResponse res = null;
218
219         res = execute("STAT disk");
220
221         if (res.getCode() == 250) {
222             SVDRPDiskStatus status = SVDRPDiskStatus.parse(res.getMessage());
223             return status;
224         } else {
225             throw new SVDRPParseResponseException(res);
226         }
227     }
228
229     /**
230      * Retrieve EPG Event from SVDRPClient
231      *
232      * @param type Type of EPG Event (now, next)
233      * @return SVDRP EPG Event
234      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
235      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
236      */
237     @Override
238     public SVDRPEpgEvent getEpgEvent(SVDRPEpgEvent.TYPE type)
239             throws SVDRPConnectionException, SVDRPParseResponseException {
240         SVDRPResponse res = null;
241         SVDRPChannel channel = this.getCurrentSVDRPChannel();
242         switch (type) {
243             case NOW:
244                 res = execute(String.format("LSTE %s %s", channel.getNumber(), "now"));
245                 break;
246             case NEXT:
247                 res = execute(String.format("LSTE %s %s", channel.getNumber(), "next"));
248                 break;
249         }
250
251         if (res != null && res.getCode() == 215) {
252             SVDRPEpgEvent entry = SVDRPEpgEvent.parse(res.getMessage());
253             return entry;
254         } else if (res != null) {
255             throw new SVDRPParseResponseException(res);
256         } else {
257             throw new SVDRPConnectionException("SVDRPResponse is Null");
258         }
259     }
260
261     /**
262      * Retrieve current volume from SVDRP Client
263      *
264      * @return SVDRP Volume Object
265      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
266      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
267      */
268     @Override
269     public SVDRPVolume getSVDRPVolume() throws SVDRPConnectionException, SVDRPParseResponseException {
270         SVDRPResponse res = null;
271
272         res = execute("VOLU");
273
274         if (res.getCode() == 250) {
275             SVDRPVolume volume = SVDRPVolume.parse(res.getMessage());
276             return volume;
277         } else {
278             throw new SVDRPParseResponseException(res);
279         }
280     }
281
282     /**
283      * Set volume on SVDRP Client
284      *
285      * @param newVolume Volume in Percent
286      * @return SVDRP Volume Object
287      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
288      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
289      */
290     @Override
291     public SVDRPVolume setSVDRPVolume(int newVolume) throws SVDRPConnectionException, SVDRPParseResponseException {
292         SVDRPResponse res = null;
293
294         double newVolumeDouble = newVolume * 255 / 100;
295         res = execute(String.format("VOLU %s", String.valueOf(Math.round(newVolumeDouble))));
296
297         if (res.getCode() == 250) {
298             SVDRPVolume volume = SVDRPVolume.parse(res.getMessage());
299             return volume;
300         } else {
301             throw new SVDRPParseResponseException(res);
302         }
303     }
304
305     /**
306      * Send Key command to SVDRP Client
307      *
308      * @param key Key Command to send
309      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
310      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
311      */
312     @Override
313     public void sendSVDRPKey(String key) throws SVDRPConnectionException, SVDRPParseResponseException {
314         SVDRPResponse res = null;
315
316         res = execute(String.format("HITK %s", key));
317
318         if (res.getCode() != 250) {
319             throw new SVDRPParseResponseException(res);
320         }
321     }
322
323     /**
324      * Send Message to SVDRP Client
325      *
326      * @param message Message to send
327      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
328      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
329      */
330     @Override
331     public void sendSVDRPMessage(String message) throws SVDRPConnectionException, SVDRPParseResponseException {
332         SVDRPResponse res = null;
333
334         res = execute(String.format("MESG %s", message));
335
336         if (res.getCode() != 250) {
337             throw new SVDRPParseResponseException(res);
338         }
339     }
340
341     /**
342      * Retrieve current Channel from SVDRP Client
343      *
344      * @return SVDRPChannel object
345      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
346      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
347      */
348     @Override
349     public SVDRPChannel getCurrentSVDRPChannel() throws SVDRPConnectionException, SVDRPParseResponseException {
350         SVDRPResponse res = null;
351
352         res = execute("CHAN");
353
354         if (res.getCode() == 250) {
355             SVDRPChannel channel = SVDRPChannel.parse(res.getMessage());
356             return channel;
357         } else {
358             throw new SVDRPParseResponseException(res);
359         }
360     }
361
362     /**
363      * Change current Channel on SVDRP Client
364      *
365      * @param number Channel to be set
366      * @return SVDRPChannel object
367      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
368      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
369      */
370     @Override
371     public SVDRPChannel setSVDRPChannel(int number) throws SVDRPConnectionException, SVDRPParseResponseException {
372         SVDRPResponse res = null;
373
374         res = execute(String.format("CHAN %s", number));
375
376         if (res.getCode() == 250) {
377             SVDRPChannel channel = SVDRPChannel.parse(res.getMessage());
378             return channel;
379         } else {
380             throw new SVDRPParseResponseException(res);
381         }
382     }
383
384     /**
385      * Retrieve from SVDRP Client if a recording is currently active
386      *
387      * @return is currently a recording active
388      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
389      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
390      */
391     @Override
392     public boolean isRecordingActive() throws SVDRPConnectionException, SVDRPParseResponseException {
393         SVDRPResponse res = null;
394
395         res = execute("LSTT");
396
397         if (res.getCode() == 250) {
398             SVDRPTimerList timers = SVDRPTimerList.parse(res.getMessage());
399             return timers.isRecordingActive();
400         } else if (res.getCode() == 550) {
401             // Error 550 is "No timers defined". Therefore there cannot be an active recording
402             return false;
403         } else {
404             throw new SVDRPParseResponseException(res);
405         }
406     }
407
408     /**
409      * Retrieve VDR Version from SVDRP Client
410      *
411      * @return VDR Version
412      * @throws SVDRPException thrown if something's not OK with SVDRP call
413      */
414     @Override
415     public String getSVDRPVersion() {
416         return version;
417     }
418 }