]> git.basschouten.com Git - openhab-addons.git/blob
5941a666c4dc90e6927eda333b1f83abe02af6cf
[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             return SVDRPDiskStatus.parse(res.getMessage());
223         } else {
224             throw new SVDRPParseResponseException(res);
225         }
226     }
227
228     /**
229      * Retrieve EPG Event from SVDRPClient
230      *
231      * @param type Type of EPG Event (now, next)
232      * @return SVDRP EPG Event
233      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
234      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
235      */
236     @Override
237     public SVDRPEpgEvent getEpgEvent(SVDRPEpgEvent.TYPE type)
238             throws SVDRPConnectionException, SVDRPParseResponseException {
239         SVDRPResponse res = null;
240         SVDRPChannel channel = this.getCurrentSVDRPChannel();
241         switch (type) {
242             case NOW:
243                 res = execute(String.format("LSTE %s %s", channel.getNumber(), "now"));
244                 break;
245             case NEXT:
246                 res = execute(String.format("LSTE %s %s", channel.getNumber(), "next"));
247                 break;
248         }
249
250         if (res != null && res.getCode() == 215) {
251             return SVDRPEpgEvent.parse(res.getMessage());
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             return SVDRPVolume.parse(res.getMessage());
274         } else {
275             throw new SVDRPParseResponseException(res);
276         }
277     }
278
279     /**
280      * Set volume on SVDRP Client
281      *
282      * @param newVolume Volume in Percent
283      * @return SVDRP Volume Object
284      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
285      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
286      */
287     @Override
288     public SVDRPVolume setSVDRPVolume(int newVolume) throws SVDRPConnectionException, SVDRPParseResponseException {
289         SVDRPResponse res = null;
290
291         double newVolumeDouble = newVolume * 255 / 100;
292         res = execute(String.format("VOLU %s", String.valueOf(Math.round(newVolumeDouble))));
293
294         if (res.getCode() == 250) {
295             return SVDRPVolume.parse(res.getMessage());
296         } else {
297             throw new SVDRPParseResponseException(res);
298         }
299     }
300
301     /**
302      * Send Key command to SVDRP Client
303      *
304      * @param key Key Command to send
305      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
306      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
307      */
308     @Override
309     public void sendSVDRPKey(String key) throws SVDRPConnectionException, SVDRPParseResponseException {
310         SVDRPResponse res = null;
311
312         res = execute(String.format("HITK %s", key));
313
314         if (res.getCode() != 250) {
315             throw new SVDRPParseResponseException(res);
316         }
317     }
318
319     /**
320      * Send Message to SVDRP Client
321      *
322      * @param message Message to send
323      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
324      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
325      */
326     @Override
327     public void sendSVDRPMessage(String message) throws SVDRPConnectionException, SVDRPParseResponseException {
328         SVDRPResponse res = null;
329
330         res = execute(String.format("MESG %s", message));
331
332         if (res.getCode() != 250) {
333             throw new SVDRPParseResponseException(res);
334         }
335     }
336
337     /**
338      * Retrieve current Channel from SVDRP Client
339      *
340      * @return SVDRPChannel object
341      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
342      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
343      */
344     @Override
345     public SVDRPChannel getCurrentSVDRPChannel() throws SVDRPConnectionException, SVDRPParseResponseException {
346         SVDRPResponse res = null;
347
348         res = execute("CHAN");
349
350         if (res.getCode() == 250) {
351             return SVDRPChannel.parse(res.getMessage());
352         } else {
353             throw new SVDRPParseResponseException(res);
354         }
355     }
356
357     /**
358      * Change current Channel on SVDRP Client
359      *
360      * @param number Channel to be set
361      * @return SVDRPChannel object
362      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
363      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
364      */
365     @Override
366     public SVDRPChannel setSVDRPChannel(int number) throws SVDRPConnectionException, SVDRPParseResponseException {
367         SVDRPResponse res = null;
368
369         res = execute(String.format("CHAN %s", number));
370
371         if (res.getCode() == 250) {
372             return SVDRPChannel.parse(res.getMessage());
373         } else {
374             throw new SVDRPParseResponseException(res);
375         }
376     }
377
378     /**
379      * Retrieve from SVDRP Client if a recording is currently active
380      *
381      * @return is currently a recording active
382      * @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
383      * @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
384      */
385     @Override
386     public boolean isRecordingActive() throws SVDRPConnectionException, SVDRPParseResponseException {
387         SVDRPResponse res = null;
388
389         res = execute("LSTT");
390
391         if (res.getCode() == 250) {
392             SVDRPTimerList timers = SVDRPTimerList.parse(res.getMessage());
393             return timers.isRecordingActive();
394         } else if (res.getCode() == 550) {
395             // Error 550 is "No timers defined". Therefore there cannot be an active recording
396             return false;
397         } else {
398             throw new SVDRPParseResponseException(res);
399         }
400     }
401
402     /**
403      * Retrieve VDR Version from SVDRP Client
404      *
405      * @return VDR Version
406      * @throws SVDRPException thrown if something's not OK with SVDRP call
407      */
408     @Override
409     public String getSVDRPVersion() {
410         return version;
411     }
412 }