2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.vdr.internal.svdrp;
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;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
29 * The {@link SVDRPClientImpl} encapsulates all calls to the SVDRP interface of a VDR
31 * @author Matthias Klocke - Initial contribution
34 public class SVDRPClientImpl implements SVDRPClient {
37 private int port = 6419;
38 private String charset = "UTF-8";
39 private String version = "";
41 private static final String WELCOME_MESSAGE = "([0-9]{3})([ -])(.*)";
42 private static final Pattern PATTERN_WELCOME = Pattern.compile(WELCOME_MESSAGE);
44 private static final int TIMEOUT_MS = 3000;
46 private @Nullable Socket socket = null;
47 private @Nullable BufferedWriter out = null;
48 private @Nullable BufferedReader in = null;
50 public SVDRPClientImpl(String host, int port) {
56 public SVDRPClientImpl(String host, int port, String charset) {
60 this.charset = charset;
65 * Open VDR Socket Connection
67 * @throws IOException if an IO Error occurs
70 public void openConnection() throws SVDRPConnectionException, SVDRPParseResponseException {
71 Socket localSocket = socket;
72 BufferedWriter localOut = out;
73 BufferedReader localIn = in;
75 if (localSocket == null || localSocket.isClosed()) {
76 localSocket = new Socket();
80 InetSocketAddress isa = new InetSocketAddress(host, port);
81 localSocket.connect(isa, TIMEOUT_MS);
82 localSocket.setSoTimeout(TIMEOUT_MS);
84 localOut = new BufferedWriter(new OutputStreamWriter(localSocket.getOutputStream(), charset), 8192);
86 localIn = new BufferedReader(new InputStreamReader(localSocket.getInputStream(), charset), 8192);
89 // read welcome message and init version & charset
90 SVDRPResponse res = null;
94 if (res.getCode() == 220) {
95 SVDRPWelcome welcome = SVDRPWelcome.parse(res.getMessage());
96 this.charset = welcome.getCharset();
97 this.version = welcome.getVersion();
99 throw new SVDRPParseResponseException(res);
101 } catch (IOException e) {
102 // cleanup after timeout
104 if (localOut != null)
109 } catch (IOException ex) {
111 throw new SVDRPConnectionException(e.getMessage(), e);
116 * Close VDR Socket Connection
118 * @throws IOException if an IO Error occurs
121 public void closeConnection() throws SVDRPConnectionException, SVDRPParseResponseException {
122 Socket localSocket = socket;
123 BufferedWriter localOut = out;
124 BufferedReader localIn = in;
126 * socket on vdr stays in FIN_WAIT2 without closing connection
129 if (localOut != null) {
130 localOut.write("QUIT");
135 if (localIn != null) {
138 if (localSocket != null) {
141 } catch (IOException e) {
142 throw new SVDRPConnectionException(e.getMessage(), e);
150 * @param command SVDRP command to execute
151 * @return response of SVDRPCall
152 * @throws SVDRPException exception from SVDRP call
154 private SVDRPResponse execute(@Nullable String command)
155 throws SVDRPConnectionException, SVDRPParseResponseException {
156 BufferedWriter localOut = out;
157 BufferedReader localIn = in;
159 StringBuilder message = new StringBuilder();
160 Matcher matcher = null;
164 if (command != null) {
165 if (localOut == null) {
166 throw new SVDRPConnectionException("OutputStream is null!");
168 localOut.write(command);
174 if (localIn != null) {
178 while (cont && (line = localIn.readLine()) != null) {
179 matcher = PATTERN_WELCOME.matcher(line);
180 if (matcher.matches() && matcher.groupCount() > 2) {
182 code = Integer.parseInt(matcher.group(1));
184 if (" ".equals(matcher.group(2))) {
187 message.append(matcher.group(3));
189 message.append(System.lineSeparator());
195 return new SVDRPResponse(code, message.toString());
197 throw new SVDRPConnectionException("SVDRP Input Stream is Null");
199 } catch (IOException ioe) {
200 throw new SVDRPConnectionException(ioe.getMessage(), ioe);
201 } catch (NumberFormatException ne) {
202 throw new SVDRPParseResponseException(ne.getMessage(), ne);
207 * Retrieve Disk Status from SVDRP Client
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
214 public SVDRPDiskStatus getDiskStatus() throws SVDRPConnectionException, SVDRPParseResponseException {
215 SVDRPResponse res = null;
217 res = execute("STAT disk");
219 if (res.getCode() == 250) {
220 SVDRPDiskStatus status = SVDRPDiskStatus.parse(res.getMessage());
223 throw new SVDRPParseResponseException(res);
228 * Retrieve EPG Event from SVDRPClient
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
236 public SVDRPEpgEvent getEpgEvent(SVDRPEpgEvent.TYPE type)
237 throws SVDRPConnectionException, SVDRPParseResponseException {
238 SVDRPResponse res = null;
239 SVDRPChannel channel = this.getCurrentSVDRPChannel();
242 res = execute(String.format("LSTE %s %s", channel.getNumber(), "now"));
245 res = execute(String.format("LSTE %s %s", channel.getNumber(), "next"));
249 if (res != null && res.getCode() == 215) {
250 SVDRPEpgEvent entry = SVDRPEpgEvent.parse(res.getMessage());
252 } else if (res != null) {
253 throw new SVDRPParseResponseException(res);
255 throw new SVDRPConnectionException("SVDRPResponse is Null");
260 * Retrieve current volume from SVDRP Client
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
267 public SVDRPVolume getSVDRPVolume() throws SVDRPConnectionException, SVDRPParseResponseException {
268 SVDRPResponse res = null;
270 res = execute("VOLU");
272 if (res.getCode() == 250) {
273 SVDRPVolume volume = SVDRPVolume.parse(res.getMessage());
276 throw new SVDRPParseResponseException(res);
281 * Set volume on SVDRP Client
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
289 public SVDRPVolume setSVDRPVolume(int newVolume) throws SVDRPConnectionException, SVDRPParseResponseException {
290 SVDRPResponse res = null;
292 double newVolumeDouble = newVolume * 255 / 100;
293 res = execute(String.format("VOLU %s", String.valueOf(Math.round(newVolumeDouble))));
295 if (res.getCode() == 250) {
296 SVDRPVolume volume = SVDRPVolume.parse(res.getMessage());
299 throw new SVDRPParseResponseException(res);
304 * Send Key command to SVDRP Client
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
311 public void sendSVDRPKey(String key) throws SVDRPConnectionException, SVDRPParseResponseException {
312 SVDRPResponse res = null;
314 res = execute(String.format("HITK %s", key));
316 if (res.getCode() != 250) {
317 throw new SVDRPParseResponseException(res);
322 * Send Message to SVDRP Client
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
329 public void sendSVDRPMessage(String message) throws SVDRPConnectionException, SVDRPParseResponseException {
330 SVDRPResponse res = null;
332 res = execute(String.format("MESG %s", message));
334 if (res.getCode() != 250) {
335 throw new SVDRPParseResponseException(res);
340 * Retrieve current Channel from SVDRP Client
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
347 public SVDRPChannel getCurrentSVDRPChannel() throws SVDRPConnectionException, SVDRPParseResponseException {
348 SVDRPResponse res = null;
350 res = execute("CHAN");
352 if (res.getCode() == 250) {
353 SVDRPChannel channel = SVDRPChannel.parse(res.getMessage());
356 throw new SVDRPParseResponseException(res);
361 * Change current Channel on SVDRP Client
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
369 public SVDRPChannel setSVDRPChannel(int number) throws SVDRPConnectionException, SVDRPParseResponseException {
370 SVDRPResponse res = null;
372 res = execute(String.format("CHAN %s", number));
374 if (res.getCode() == 250) {
375 SVDRPChannel channel = SVDRPChannel.parse(res.getMessage());
378 throw new SVDRPParseResponseException(res);
383 * Retrieve from SVDRP Client if a recording is currently active
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
390 public boolean isRecordingActive() throws SVDRPConnectionException, SVDRPParseResponseException {
391 SVDRPResponse res = null;
393 res = execute("LSTT");
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
402 throw new SVDRPParseResponseException(res);
407 * Retrieve VDR Version from SVDRP Client
409 * @return VDR Version
410 * @throws SVDRPException thrown if something's not OK with SVDRP call
413 public String getSVDRPVersion() {