[vdr] Initial contribution (#9947)

Signed-off-by: Matthias Klocke <matthias.klocke@gmx.net>
This commit is contained in:
MatthiasKlocke
2021-04-11 20:02:00 +02:00
committed by GitHub
parent fc81909596
commit 5d5cd780a5
31 changed files with 2655 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.vdr-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-vdr" description="VDR Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.vdr/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link VDRBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class VDRBindingConstants {
private static final String BINDING_ID = "vdr";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_VDR = new ThingTypeUID(BINDING_ID, "vdr");
public static final String CHANNEL_UID_POWER = "power";
public static final String CHANNEL_UID_MESSAGE = "message";
public static final String CHANNEL_UID_CHANNEL = "channel";
public static final String CHANNEL_UID_CHANNEL_NAME = "channelName";
public static final String CHANNEL_UID_VOLUME = "volume";
public static final String CHANNEL_UID_KEYCODE = "keyCode";
public static final String CHANNEL_UID_RECORDING = "recording";
public static final String CHANNEL_UID_DISKUSAGE = "diskUsage";
public static final String CHANNEL_UID_CURRENT_EVENT_TITLE = "currentEventTitle";
public static final String CHANNEL_UID_CURRENT_EVENT_SUBTITLE = "currentEventSubTitle";
public static final String CHANNEL_UID_CURRENT_EVENT_DURATION = "currentEventDuration";
public static final String CHANNEL_UID_CURRENT_EVENT_BEGIN = "currentEventBegin";
public static final String CHANNEL_UID_CURRENT_EVENT_END = "currentEventEnd";
public static final String CHANNEL_UID_NEXT_EVENT_TITLE = "nextEventTitle";
public static final String CHANNEL_UID_NEXT_EVENT_SUBTITLE = "nextEventSubTitle";
public static final String CHANNEL_UID_NEXT_EVENT_DURATION = "nextEventDuration";
public static final String CHANNEL_UID_NEXT_EVENT_BEGIN = "nextEventBegin";
public static final String CHANNEL_UID_NEXT_EVENT_END = "nextEventEnd";
public static final String KEY_CODE_POWER = "Power";
public static final String PROPERTY_VERSION = "version";
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link VDRConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class VDRConfiguration {
private String host = "localhost";
private int port = 6419;
private Integer refresh = 60;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public Integer getRefresh() {
return refresh;
}
public void setRefresh(Integer refresh) {
this.refresh = refresh;
}
}

View File

@@ -0,0 +1,317 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.vdr.internal.svdrp.SVDRPChannel;
import org.openhab.binding.vdr.internal.svdrp.SVDRPClient;
import org.openhab.binding.vdr.internal.svdrp.SVDRPClientImpl;
import org.openhab.binding.vdr.internal.svdrp.SVDRPConnectionException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPDiskStatus;
import org.openhab.binding.vdr.internal.svdrp.SVDRPEpgEvent;
import org.openhab.binding.vdr.internal.svdrp.SVDRPException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPParseResponseException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPVolume;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link VDRHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class VDRHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(VDRHandler.class);
private final TimeZoneProvider timeZoneProvider;
private VDRConfiguration config = new VDRConfiguration();
private @Nullable ScheduledFuture<?> refreshThreadFuture = null;
public VDRHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
super(thing);
this.timeZoneProvider = timeZoneProvider;
}
/**
* when disposing refresh thread has to be cancelled
*/
@Override
public void dispose() {
stopRefreshThread(true);
}
/**
* Initialization of {@link VDRHandler}
*/
@Override
public void initialize() {
config = getConfigAs(VDRConfiguration.class);
updateStatus(ThingStatus.UNKNOWN);
// initialize and schedule refresh thread
scheduleRefreshThread();
}
/**
* Update Thing's properties (e. g. VDR Version)
*
* @param client the {@link SVDRPClient} to be used for properties update
*/
public void updateProperties(SVDRPClient client) {
Map<String, String> properties = editProperties();
// set vdr version to properties of thing
String version = client.getSVDRPVersion();
properties.put(VDRBindingConstants.PROPERTY_VERSION, version.toString());
// persist changes only if there are any changes.
if (!editProperties().equals(properties)) {
updateProperties(properties);
}
}
/**
* Handling Commands for {@link VDRHandler}
*
* @param channelUID channel command was executed for
* @param command command to execute
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
SVDRPClient con = new SVDRPClientImpl(config.getHost(), config.getPort());
try {
con.openConnection();
if (command instanceof RefreshType) {
this.onVDRRefresh();
} else {
State result = UnDefType.NULL;
String cmd = command.toString();
switch (channelUID.getId()) {
case VDRBindingConstants.CHANNEL_UID_MESSAGE:
con.sendSVDRPMessage(cmd);
break;
case VDRBindingConstants.CHANNEL_UID_POWER:
con.sendSVDRPKey(VDRBindingConstants.KEY_CODE_POWER);
break;
case VDRBindingConstants.CHANNEL_UID_CHANNEL:
SVDRPChannel channel = con.setSVDRPChannel(Integer.parseInt(cmd));
result = new DecimalType(channel.getNumber());
updateState(channelUID, result);
break;
case VDRBindingConstants.CHANNEL_UID_VOLUME:
SVDRPVolume volume = con.setSVDRPVolume(Integer.parseInt(cmd));
result = new PercentType(volume.getVolume());
updateState(channelUID, result);
break;
case VDRBindingConstants.CHANNEL_UID_KEYCODE:
con.sendSVDRPKey(cmd.trim());
break;
}
}
} catch (SVDRPParseResponseException e) {
logger.trace("VDR handleCommand for Thing {}, ChannelUID {}, Parse Response failed. Message: {}",
this.getThing().getUID(), channelUID, e.getMessage());
} catch (SVDRPConnectionException e) {
logger.debug("VDR handleCommand for Thing {}, ChannelUID {}, Connection failed. Message: {}",
this.getThing().getUID(), channelUID, e.getMessage());
} finally {
try {
con.closeConnection();
} catch (SVDRPException ex) {
logger.trace("Error on VDR handleCommand while closing SVDRP Connection for Thing : {} with message {}",
this.getThing().getUID(), ex.getMessage());
}
}
}
/**
* Refresh data from SVDRPClient (Polling)
*/
private void onVDRRefresh() {
SVDRPClient con = new SVDRPClientImpl(config.getHost(), config.getPort());
Thing thing = getThing();
try {
con.openConnection();
updateStatus(ThingStatus.ONLINE);
updateProperties(con);
thing.getChannels().stream().map(c -> c.getUID()).filter(this::isLinked).forEach(channelUID -> {
try {
logger.trace("updateChannel: {}", channelUID);
SVDRPEpgEvent entry;
State result = UnDefType.NULL;
switch (channelUID.getId()) {
case VDRBindingConstants.CHANNEL_UID_RECORDING:
boolean isRecording = con.isRecordingActive();
if (isRecording) {
result = OnOffType.ON;
} else {
result = OnOffType.OFF;
}
break;
case VDRBindingConstants.CHANNEL_UID_VOLUME:
SVDRPVolume volume = con.getSVDRPVolume();
result = new PercentType(volume.getVolume());
break;
case VDRBindingConstants.CHANNEL_UID_CHANNEL:
SVDRPChannel channel = con.getCurrentSVDRPChannel();
result = new DecimalType(channel.getNumber());
break;
case VDRBindingConstants.CHANNEL_UID_CHANNEL_NAME:
SVDRPChannel svdrpChannel = con.getCurrentSVDRPChannel();
result = new StringType(svdrpChannel.getName());
break;
case VDRBindingConstants.CHANNEL_UID_POWER:
SVDRPDiskStatus status = con.getDiskStatus();
if (status.getPercentUsed() >= 0) {
result = OnOffType.ON;
} else {
result = OnOffType.OFF;
}
break;
case VDRBindingConstants.CHANNEL_UID_DISKUSAGE:
SVDRPDiskStatus diskStatus = con.getDiskStatus();
result = new DecimalType(diskStatus.getPercentUsed());
break;
case VDRBindingConstants.CHANNEL_UID_CURRENT_EVENT_TITLE:
entry = con.getEpgEvent(SVDRPEpgEvent.TYPE.NOW);
result = new StringType(entry.getTitle());
break;
case VDRBindingConstants.CHANNEL_UID_CURRENT_EVENT_SUBTITLE:
entry = con.getEpgEvent(SVDRPEpgEvent.TYPE.NOW);
result = new StringType(entry.getSubtitle());
break;
case VDRBindingConstants.CHANNEL_UID_CURRENT_EVENT_DURATION:
entry = con.getEpgEvent(SVDRPEpgEvent.TYPE.NOW);
result = new QuantityType<>(entry.getDuration(), Units.MINUTE);
break;
case VDRBindingConstants.CHANNEL_UID_CURRENT_EVENT_BEGIN:
entry = con.getEpgEvent(SVDRPEpgEvent.TYPE.NOW);
result = new DateTimeType(LocalDateTime.ofInstant(entry.getBegin(), ZoneId.systemDefault())
.atZone(timeZoneProvider.getTimeZone()));
break;
case VDRBindingConstants.CHANNEL_UID_CURRENT_EVENT_END:
entry = con.getEpgEvent(SVDRPEpgEvent.TYPE.NOW);
result = new DateTimeType(LocalDateTime.ofInstant(entry.getEnd(), ZoneId.systemDefault())
.atZone(timeZoneProvider.getTimeZone()));
break;
case VDRBindingConstants.CHANNEL_UID_NEXT_EVENT_TITLE:
entry = con.getEpgEvent(SVDRPEpgEvent.TYPE.NEXT);
result = new StringType(entry.getTitle());
break;
case VDRBindingConstants.CHANNEL_UID_NEXT_EVENT_SUBTITLE:
entry = con.getEpgEvent(SVDRPEpgEvent.TYPE.NEXT);
result = new StringType(entry.getSubtitle());
break;
case VDRBindingConstants.CHANNEL_UID_NEXT_EVENT_DURATION:
entry = con.getEpgEvent(SVDRPEpgEvent.TYPE.NEXT);
result = new QuantityType<>(entry.getDuration(), Units.MINUTE);
break;
case VDRBindingConstants.CHANNEL_UID_NEXT_EVENT_BEGIN:
entry = con.getEpgEvent(SVDRPEpgEvent.TYPE.NEXT);
result = new DateTimeType(LocalDateTime.ofInstant(entry.getBegin(), ZoneId.systemDefault())
.atZone(timeZoneProvider.getTimeZone()));
break;
case VDRBindingConstants.CHANNEL_UID_NEXT_EVENT_END:
entry = con.getEpgEvent(SVDRPEpgEvent.TYPE.NEXT);
result = new DateTimeType(LocalDateTime.ofInstant(entry.getEnd(), ZoneId.systemDefault())
.atZone(timeZoneProvider.getTimeZone()));
break;
}
updateState(channelUID, result);
} catch (SVDRPParseResponseException e) {
logger.trace("VDR Refresh for Thing {}, ChannelUID {}, Parse Response failed. Message: {}",
this.getThing().getUID(), channelUID, e.getMessage());
updateState(channelUID, UnDefType.UNDEF);
} catch (SVDRPConnectionException e) {
logger.debug("VDR Refresh for Thing {}, ChannelUID {}, Connection failed. Message: {}",
this.getThing().getUID(), channelUID, e.getMessage());
}
});
} catch (SVDRPConnectionException ce) {
if (thing.getStatus() == ThingStatus.ONLINE) {
// also update power channel when thing is offline before setting it offline
thing.getChannels().stream().map(c -> c.getUID()).filter(this::isLinked).forEach(channelUID -> {
if (VDRBindingConstants.CHANNEL_UID_POWER.equals(channelUID.getIdWithoutGroup())) {
updateState(channelUID, OnOffType.OFF);
}
});
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ce.getMessage());
} catch (SVDRPParseResponseException se) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, se.getMessage());
} finally {
try {
con.closeConnection();
} catch (SVDRPException e) {
logger.trace("Error on VDR Refresh while closing SVDRP Connection for Thing : {} with message {}",
this.getThing().getUID(), e.getMessage());
}
}
}
/**
* Schedules the refresh thread
*/
private void scheduleRefreshThread() {
refreshThreadFuture = scheduler.scheduleWithFixedDelay(this::onVDRRefresh, 3, config.getRefresh(),
TimeUnit.SECONDS);
}
/**
* Stops the refresh thread.
*
* @param force if set to true thread cancellation will be forced
*/
private void stopRefreshThread(boolean force) {
ScheduledFuture<?> refreshThread = refreshThreadFuture;
if (refreshThread != null)
refreshThread.cancel(force);
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal;
import static org.openhab.binding.vdr.internal.VDRBindingConstants.THING_TYPE_VDR;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link VDRHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.vdr", service = ThingHandlerFactory.class)
public class VDRHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_VDR);
private final TimeZoneProvider timeZoneProvider;
@Activate
public VDRHandlerFactory(final @Reference TimeZoneProvider timeZoneProvider) {
this.timeZoneProvider = timeZoneProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_VDR.equals(thingTypeUID)) {
return new VDRHandler(thing, timeZoneProvider);
}
return null;
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SVDRPChannel} contains SVDRP Response Data for Channels
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class SVDRPChannel {
private int number;
private String name = "";
private SVDRPChannel() {
}
/**
* parse object from SVDRP Client Response
*
* @param message SVDRP Client Response
* @return Channel Object
* @throws SVDRPParseResponseException thrown if response data is not parseable
*/
public static SVDRPChannel parse(String message) throws SVDRPParseResponseException {
String number = message.substring(0, message.indexOf(" "));
String name = message.substring(message.indexOf(" ") + 1, message.length());
SVDRPChannel channel = new SVDRPChannel();
try {
channel.setNumber(Integer.parseInt(number));
} catch (NumberFormatException e) {
throw new SVDRPParseResponseException(e.getMessage(), e);
}
channel.setName(name);
return channel;
}
/**
* Get Channel Number
*
* @return Channel Number
*/
public int getNumber() {
return number;
}
/**
* Set Channel Number
*
* @param number Channel Number
*/
public void setNumber(int number) {
this.number = number;
}
/**
* Get Channel Name
*
* @return Channel Name
*/
public String getName() {
return name;
}
/**
* Set Channel Name
*
* @param name Channel Name
*/
public void setName(String name) {
this.name = name;
}
/**
* String Representation of SVDRPChannel Object
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (number >= 0) {
sb.append("Number: " + String.valueOf(number) + System.lineSeparator());
}
sb.append("Name: " + name + System.lineSeparator());
return sb.toString();
}
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SVDRPClient} encapsulates all calls to the SVDRP interface of a VDR
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public interface SVDRPClient {
/**
*
* Open VDR Socket Connection
*
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public void openConnection() throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Close VDR Socket Connection
*
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public void closeConnection() throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Retrieve Disk Status from SVDRP Client
*
* @return SVDRP Disk Status
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public SVDRPDiskStatus getDiskStatus() throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Retrieve EPG Event from SVDRPClient
*
* @param type Type of EPG Event (now, next)
* @return SVDRP EPG Event
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public SVDRPEpgEvent getEpgEvent(SVDRPEpgEvent.TYPE type)
throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Retrieve current volume from SVDRP Client
*
* @return SVDRP Volume Object
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public SVDRPVolume getSVDRPVolume() throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Set volume on SVDRP Client
*
* @param newVolume Volume in Percent
* @return SVDRP Volume Object
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public SVDRPVolume setSVDRPVolume(int newVolume) throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Send Key command to SVDRP Client
*
* @param key Key Command to send
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public void sendSVDRPKey(String key) throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Send Message to SVDRP Client
*
* @param message Message to send
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public void sendSVDRPMessage(String message) throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Retrieve current Channel from SVDRP Client
*
* @return SVDRPChannel object
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public SVDRPChannel getCurrentSVDRPChannel() throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Change current Channel on SVDRP Client
*
* @param number Channel to be set
* @return SVDRPChannel object
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public SVDRPChannel setSVDRPChannel(int number) throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Retrieve from SVDRP Client if a recording is currently active
*
* @return is currently a recording active
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public boolean isRecordingActive() throws SVDRPConnectionException, SVDRPParseResponseException;
/**
* Retrieve VDR Version from SVDRP Client
*
* @return VDR Version
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
public String getSVDRPVersion();
}

View File

@@ -0,0 +1,416 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link SVDRPClientImpl} encapsulates all calls to the SVDRP interface of a VDR
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class SVDRPClientImpl implements SVDRPClient {
private String host;
private int port = 6419;
private String charset = "UTF-8";
private String version = "";
private static final String WELCOME_MESSAGE = "([0-9]{3})([ -])(.*)";
private static final Pattern PATTERN_WELCOME = Pattern.compile(WELCOME_MESSAGE);
private static final int TIMEOUT_MS = 3000;
private @Nullable Socket socket = null;
private @Nullable BufferedWriter out = null;
private @Nullable BufferedReader in = null;
public SVDRPClientImpl(String host, int port) {
super();
this.host = host;
this.port = port;
}
public SVDRPClientImpl(String host, int port, String charset) {
super();
this.host = host;
this.port = port;
this.charset = charset;
}
/**
*
* Open VDR Socket Connection
*
* @throws IOException if an IO Error occurs
*/
@Override
public void openConnection() throws SVDRPConnectionException, SVDRPParseResponseException {
Socket localSocket = socket;
BufferedWriter localOut = out;
BufferedReader localIn = in;
if (localSocket == null || localSocket.isClosed()) {
localSocket = new Socket();
socket = localSocket;
}
try {
InetSocketAddress isa = new InetSocketAddress(host, port);
localSocket.connect(isa, TIMEOUT_MS);
localSocket.setSoTimeout(TIMEOUT_MS);
localOut = new BufferedWriter(new OutputStreamWriter(localSocket.getOutputStream(), charset), 8192);
out = localOut;
localIn = new BufferedReader(new InputStreamReader(localSocket.getInputStream(), charset), 8192);
in = localIn;
// read welcome message and init version & charset
SVDRPResponse res = null;
res = execute(null);
if (res.getCode() == 220) {
SVDRPWelcome welcome = SVDRPWelcome.parse(res.getMessage());
this.charset = welcome.getCharset();
this.version = welcome.getVersion();
} else {
throw new SVDRPParseResponseException(res);
}
} catch (IOException e) {
// cleanup after timeout
try {
if (localOut != null)
localOut.close();
if (localIn != null)
localIn.close();
localSocket.close();
} catch (IOException ex) {
}
throw new SVDRPConnectionException(e.getMessage(), e);
}
}
/**
* Close VDR Socket Connection
*
* @throws IOException if an IO Error occurs
*/
@Override
public void closeConnection() throws SVDRPConnectionException, SVDRPParseResponseException {
Socket localSocket = socket;
BufferedWriter localOut = out;
BufferedReader localIn = in;
/*
* socket on vdr stays in FIN_WAIT2 without closing connection
*/
try {
if (localOut != null) {
localOut.write("QUIT");
localOut.newLine();
localOut.flush();
localOut.close();
}
if (localIn != null) {
localIn.close();
}
if (localSocket != null) {
localSocket.close();
}
} catch (IOException e) {
throw new SVDRPConnectionException(e.getMessage(), e);
}
}
/**
*
* execute SVDRP Call
*
* @param command SVDRP command to execute
* @return response of SVDRPCall
* @throws SVDRPException exception from SVDRP call
*/
private SVDRPResponse execute(@Nullable String command)
throws SVDRPConnectionException, SVDRPParseResponseException {
BufferedWriter localOut = out;
BufferedReader localIn = in;
StringBuilder message = new StringBuilder();
Matcher matcher = null;
int code;
try {
if (command != null) {
if (localOut == null) {
throw new SVDRPConnectionException("OutputStream is null!");
} else {
localOut.write(command);
localOut.newLine();
localOut.flush();
}
}
if (localIn != null) {
code = -1;
String line = null;
boolean cont = true;
while (cont && (line = localIn.readLine()) != null) {
matcher = PATTERN_WELCOME.matcher(line);
if (matcher.matches() && matcher.groupCount() > 2) {
if (code < 0) {
code = Integer.parseInt(matcher.group(1));
}
if (" ".equals(matcher.group(2))) {
cont = false;
}
message.append(matcher.group(3));
if (cont) {
message.append(System.lineSeparator());
}
} else {
cont = false;
}
}
return new SVDRPResponse(code, message.toString());
} else {
throw new SVDRPConnectionException("SVDRP Input Stream is Null");
}
} catch (IOException ioe) {
throw new SVDRPConnectionException(ioe.getMessage(), ioe);
} catch (NumberFormatException ne) {
throw new SVDRPParseResponseException(ne.getMessage(), ne);
}
}
/**
* Retrieve Disk Status from SVDRP Client
*
* @return SVDRP Disk Status
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
@Override
public SVDRPDiskStatus getDiskStatus() throws SVDRPConnectionException, SVDRPParseResponseException {
SVDRPResponse res = null;
res = execute("STAT disk");
if (res.getCode() == 250) {
SVDRPDiskStatus status = SVDRPDiskStatus.parse(res.getMessage());
return status;
} else {
throw new SVDRPParseResponseException(res);
}
}
/**
* Retrieve EPG Event from SVDRPClient
*
* @param type Type of EPG Event (now, next)
* @return SVDRP EPG Event
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
@Override
public SVDRPEpgEvent getEpgEvent(SVDRPEpgEvent.TYPE type)
throws SVDRPConnectionException, SVDRPParseResponseException {
SVDRPResponse res = null;
SVDRPChannel channel = this.getCurrentSVDRPChannel();
switch (type) {
case NOW:
res = execute(String.format("LSTE %s %s", channel.getNumber(), "now"));
break;
case NEXT:
res = execute(String.format("LSTE %s %s", channel.getNumber(), "next"));
break;
}
if (res != null && res.getCode() == 215) {
SVDRPEpgEvent entry = SVDRPEpgEvent.parse(res.getMessage());
return entry;
} else if (res != null) {
throw new SVDRPParseResponseException(res);
} else {
throw new SVDRPConnectionException("SVDRPResponse is Null");
}
}
/**
* Retrieve current volume from SVDRP Client
*
* @return SVDRP Volume Object
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
@Override
public SVDRPVolume getSVDRPVolume() throws SVDRPConnectionException, SVDRPParseResponseException {
SVDRPResponse res = null;
res = execute("VOLU");
if (res.getCode() == 250) {
SVDRPVolume volume = SVDRPVolume.parse(res.getMessage());
return volume;
} else {
throw new SVDRPParseResponseException(res);
}
}
/**
* Set volume on SVDRP Client
*
* @param newVolume Volume in Percent
* @return SVDRP Volume Object
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
@Override
public SVDRPVolume setSVDRPVolume(int newVolume) throws SVDRPConnectionException, SVDRPParseResponseException {
SVDRPResponse res = null;
double newVolumeDouble = newVolume * 255 / 100;
res = execute(String.format("VOLU %s", String.valueOf(Math.round(newVolumeDouble))));
if (res.getCode() == 250) {
SVDRPVolume volume = SVDRPVolume.parse(res.getMessage());
return volume;
} else {
throw new SVDRPParseResponseException(res);
}
}
/**
* Send Key command to SVDRP Client
*
* @param key Key Command to send
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
@Override
public void sendSVDRPKey(String key) throws SVDRPConnectionException, SVDRPParseResponseException {
SVDRPResponse res = null;
res = execute(String.format("HITK %s", key));
if (res.getCode() != 250) {
throw new SVDRPParseResponseException(res);
}
}
/**
* Send Message to SVDRP Client
*
* @param message Message to send
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
@Override
public void sendSVDRPMessage(String message) throws SVDRPConnectionException, SVDRPParseResponseException {
SVDRPResponse res = null;
res = execute(String.format("MESG %s", message));
if (res.getCode() != 250) {
throw new SVDRPParseResponseException(res);
}
}
/**
* Retrieve current Channel from SVDRP Client
*
* @return SVDRPChannel object
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
@Override
public SVDRPChannel getCurrentSVDRPChannel() throws SVDRPConnectionException, SVDRPParseResponseException {
SVDRPResponse res = null;
res = execute("CHAN");
if (res.getCode() == 250) {
SVDRPChannel channel = SVDRPChannel.parse(res.getMessage());
return channel;
} else {
throw new SVDRPParseResponseException(res);
}
}
/**
* Change current Channel on SVDRP Client
*
* @param number Channel to be set
* @return SVDRPChannel object
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
@Override
public SVDRPChannel setSVDRPChannel(int number) throws SVDRPConnectionException, SVDRPParseResponseException {
SVDRPResponse res = null;
res = execute(String.format("CHAN %s", number));
if (res.getCode() == 250) {
SVDRPChannel channel = SVDRPChannel.parse(res.getMessage());
return channel;
} else {
throw new SVDRPParseResponseException(res);
}
}
/**
* Retrieve from SVDRP Client if a recording is currently active
*
* @return is currently a recording active
* @throws SVDRPConnectionException thrown if connection to VDR failed or was not possible
* @throws SVDRPParseResponseException thrown if something's not OK with SVDRP response
*/
@Override
public boolean isRecordingActive() throws SVDRPConnectionException, SVDRPParseResponseException {
SVDRPResponse res = null;
res = execute("LSTT");
if (res.getCode() == 250) {
SVDRPTimerList timers = SVDRPTimerList.parse(res.getMessage());
return timers.isRecordingActive();
} else if (res.getCode() == 550) {
// Error 550 is "No timers defined". Therefore there cannot be an active recording
return false;
} else {
throw new SVDRPParseResponseException(res);
}
}
/**
* Retrieve VDR Version from SVDRP Client
*
* @return VDR Version
* @throws SVDRPException thrown if something's not OK with SVDRP call
*/
@Override
public String getSVDRPVersion() {
return version;
}
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link SVDRPConnectionException} is thrown when SVDRP Connection cannot be established
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class SVDRPConnectionException extends SVDRPException {
private static final long serialVersionUID = 2825596676109860370L;
public SVDRPConnectionException(@Nullable String message) {
super(message);
}
public SVDRPConnectionException(@Nullable String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SVDRPDiskStatus} contains SVDRP Response Data for DiskStatus queries
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class SVDRPDiskStatus {
private static final Pattern PATTERN_DISK_STATUS = Pattern.compile("([\\d]*)MB ([\\d]*)MB ([\\d]*)%");
private long megaBytesFree = -1;
private long megaBytesTotal = -1;
private int percentUsed = -1;
private SVDRPDiskStatus() {
}
/**
* parse object from SVDRP Client Response
*
* @param message SVDRP Client Response
* @return Disk Status Object
* @throws SVDRPParseResponseException thrown if response data is not parseable
*/
public static SVDRPDiskStatus parse(String message) throws SVDRPParseResponseException {
SVDRPDiskStatus status = new SVDRPDiskStatus();
Matcher matcher = PATTERN_DISK_STATUS.matcher(message);
if (matcher.find() && matcher.groupCount() == 3) {
status.setMegaBytesTotal(Long.parseLong(matcher.group(1)));
status.setMegaBytesFree(Long.parseLong(matcher.group(2)));
status.setPercentUsed(Integer.parseInt(matcher.group(3)));
}
return status;
}
/**
* Get Megabytes Free on Disk
*
* @return megabytes free
*/
public long getMegaBytesFree() {
return megaBytesFree;
}
/**
* Set Megabytes Free on Disk
*
* @param megaBytesFree megabytes free
*/
public void setMegaBytesFree(long megaBytesFree) {
this.megaBytesFree = megaBytesFree;
}
/**
* Get Megabytes Total on Disk
*
* @return megabytes total
*/
public long getMegaBytesTotal() {
return megaBytesTotal;
}
/**
* Set Megabytes Total on Disk
*
* @param megaBytesTotal megabytes total
*/
public void setMegaBytesTotal(long megaBytesTotal) {
this.megaBytesTotal = megaBytesTotal;
}
/**
* Get Percentage Used on Disk
*
* @return percentage used
*/
public int getPercentUsed() {
return percentUsed;
}
/**
* Set Percentage Used on Disk
*
* @param percentUsed percentage used
*/
public void setPercentUsed(int percentUsed) {
this.percentUsed = percentUsed;
}
/**
* String Representation of SVDRPDiskStatus Object
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (megaBytesTotal >= 0) {
sb.append("Total: " + megaBytesTotal + "MB" + System.lineSeparator());
}
if (megaBytesFree >= 0) {
sb.append("Free: " + megaBytesFree + "MB" + System.lineSeparator());
}
if (percentUsed >= 0) {
sb.append("Free: " + percentUsed + "%" + System.lineSeparator());
}
return sb.toString();
}
}

View File

@@ -0,0 +1,211 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SVDRPEpgEvent} contains SVDRP Response Data for an EPG Event
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class SVDRPEpgEvent {
public enum TYPE {
NOW,
NEXT
}
private String title = "";
private String subtitle = "";
private Instant begin = Instant.now();
private Instant end = Instant.now();
private int duration;
private SVDRPEpgEvent() {
}
/**
* parse object from SVDRP Client Response
*
* @param message SVDRP Client Response
* @return SVDRPEpgEvent Object
* @throws SVDRPParseResponseException thrown if response data is not parseable
*/
public static SVDRPEpgEvent parse(String message) throws SVDRPParseResponseException {
SVDRPEpgEvent entry = new SVDRPEpgEvent();
StringTokenizer st = new StringTokenizer(message, System.lineSeparator());
while (st.hasMoreTokens()) {
String line = st.nextToken();
if (line.length() >= 1 && !line.startsWith("End")) {
switch (line.charAt(0)) {
case 'T':
entry.setTitle(line.substring(1).trim());
break;
case 'S':
entry.setSubtitle(line.substring(1).trim());
break;
case 'E':
StringTokenizer lt = new StringTokenizer(line.substring(1).trim(), " ");
lt.nextToken(); // event id
try {
long begin = Long.parseLong(lt.nextToken());
entry.setBegin(Instant.ofEpochSecond(begin));
} catch (NumberFormatException | NoSuchElementException e) {
throw new SVDRPParseResponseException("Begin: " + e.getMessage(), e);
}
try {
entry.setDuration(Integer.parseInt(lt.nextToken()) / 60);
} catch (NumberFormatException | NoSuchElementException e) {
throw new SVDRPParseResponseException("Duration: " + e.getMessage(), e);
}
entry.setEnd(entry.getBegin().plus(entry.getDuration(), ChronoUnit.MINUTES));
default:
break;
}
} else if (!line.startsWith("End")) {
throw new SVDRPParseResponseException("EPG Event Line corrupt: " + line);
}
}
return entry;
}
/**
* Get Title of EPG Event
*
* @return Event Title
*/
public String getTitle() {
return title;
}
/**
* Set Title of EPG Event
*
* @param title Event Title
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Get Subtitle of EPG Event
*
* @return Event Subtitle
*/
public String getSubtitle() {
return subtitle;
}
/**
* Set Subtitle of EPG Event
*
* @param subtitle Event Subtitle
*/
public void setSubtitle(String subtitle) {
this.subtitle = subtitle;
}
/**
* Get Begin of EPG Event
*
* @return Event Begin
*/
public Instant getBegin() {
return begin;
}
/**
* Set Begin of EPG Event
*
* @param begin Event Begin
*/
public void setBegin(Instant begin) {
this.begin = begin;
}
/**
* Get End of EPG Event
*
* @return Event End
*/
public Instant getEnd() {
return end;
}
/**
* Set End of EPG Event
*
* @param end Event End
*/
public void setEnd(Instant end) {
this.end = end;
}
/**
* Get Duration of EPG Event in Minutes
*
* @return Event Duration in Minutes
*/
public int getDuration() {
return duration;
}
/**
* Set Duration of EPG Event in Minutes
*
* @param duration Event Duration in Minutes
*/
public void setDuration(int duration) {
this.duration = duration;
}
/**
* String Representation of SVDRPDiskStatus Object
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Title: ");
sb.append(title);
sb.append(System.lineSeparator());
sb.append("Subtitle: ");
sb.append(subtitle);
sb.append(System.lineSeparator());
sb.append("Begin: ");
sb.append(begin);
sb.append(System.lineSeparator());
sb.append("End: ");
sb.append(end);
sb.append(System.lineSeparator());
if (duration > -1) {
sb.append("Duration: ");
sb.append(duration);
sb.append(System.lineSeparator());
}
return sb.toString();
}
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link SVDRPException} is thrown in case of Failure of SVDRP Handling
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public abstract class SVDRPException extends Exception {
private static final long serialVersionUID = 3816136415994156427L;
public SVDRPException(@Nullable String message) {
super(message);
}
public SVDRPException(@Nullable String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link SVDRPParseResponseException} is thrown if a SVDRP Response cannot be parsed as expected
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class SVDRPParseResponseException extends SVDRPException {
private static final long serialVersionUID = 631229205838438373L;
public SVDRPParseResponseException(SVDRPResponse response) {
super(response.getMessage());
}
public SVDRPParseResponseException(SVDRPResponse response, Throwable cause) {
super(response.getMessage(), cause);
}
public SVDRPParseResponseException(@Nullable String message) {
super(message);
String newMessage = message;
if (newMessage == null) {
newMessage = "Null Value on Exception Message";
}
}
public SVDRPParseResponseException(@Nullable String message, Throwable cause) {
super(message, cause);
String newMessage = message;
if (newMessage == null) {
newMessage = "Null Value on Exception Message";
}
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SVDRPResponse} represents a general Object returned by an SVDRP Client Call
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class SVDRPResponse {
private int code;
private String message;
public SVDRPResponse(int code, String response) {
this.code = code;
this.message = response;
}
/**
* Get Status Code of SVDRP Response
*
* @return Status Code of SVDRP Response
*/
public int getCode() {
return code;
}
/**
* Get Message of SVDRP Response
*
* @return Message of SVDRP Response
*/
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SVDRPTimerList} contains SVDRP Response Data for a Timer list
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class SVDRPTimerList {
private List<String> timers = new ArrayList<String>();
/**
* parse object from SVDRP Client Response
*
* @param message SVDRP Client Response
* @return Timer List Object
* @throws SVDRPParseResponseException thrown if response data is not parseable
*/
public static SVDRPTimerList parse(String message) {
SVDRPTimerList timers = new SVDRPTimerList();
List<String> lines = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(message, System.lineSeparator());
while (st.hasMoreTokens()) {
String timer = st.nextToken();
lines.add(timer);
}
timers.setTimers(lines);
return timers;
}
/**
* Is there currently an active Recording on SVDRP Client
*
* @return returns true if there is an active recording
*/
public boolean isRecordingActive() {
for (String line : timers) {
String timerContent = line.substring(line.indexOf(" ") + 1);
String timerStatus = timerContent.substring(0, timerContent.indexOf(":"));
byte b = Byte.parseByte(timerStatus);
if (((b >> 3) & 0x0001) == 1) {
return true;
}
}
return false;
}
/**
* Set timers object of SVDRPTimerList
*
* @param timers timers to set
*/
private void setTimers(List<String> timers) {
this.timers = timers;
}
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SVDRPVolume} contains SVDRP Response Data for Volume Object
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class SVDRPVolume {
private int volume = -1;
private SVDRPVolume() {
}
/**
* parse object from SVDRP Client Response
*
* @param message SVDRP Client Response
* @return Volume Object
* @throws SVDRPParseResponseException thrown if response data is not parseable
*/
public static SVDRPVolume parse(String message) throws SVDRPParseResponseException {
SVDRPVolume volume = new SVDRPVolume();
try {
String vol = message.substring(message.lastIndexOf(" ") + 1, message.length());
if ("mute".equals(vol)) {
volume.setVolume(0);
} else {
int val = Integer.parseInt(vol);
val = val * 100 / 255;
volume.setVolume(val);
}
} catch (NumberFormatException nex) {
throw new SVDRPParseResponseException(nex.getMessage(), nex);
} catch (IndexOutOfBoundsException ie) {
throw new SVDRPParseResponseException(ie.getMessage(), ie);
}
return volume;
}
/**
* Get Volume in Percent
*
* @param volume Volume in Percent
*/
private void setVolume(int volume) {
this.volume = volume;
}
/**
* Set Volume in Percent
*
* @return Volume in Percent
*/
public int getVolume() {
return volume;
}
/**
* String Representation of SVDRPDiskStatus Object
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (volume > -1) {
sb.append("Volume: ");
sb.append(String.valueOf(volume));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal.svdrp;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SVDRPWelcome} contains SVDRP Response Data that is sent after Connection has been established
*
* @author Matthias Klocke - Initial contribution
*/
@NonNullByDefault
public class SVDRPWelcome {
private String version = "";
private String charset = "";
private String dateAndTime = "";
private SVDRPWelcome() {
}
/**
* Parse SVDRPResponse into SVDRPWelcome Object
*
* @param message SVDRP Client Response
* Example: VDRHOST SVDRP VideoDiskRecorder 2.4.5; Sat Jan 9 22:28:11 2021; UTF-8
* @return Welcome Object
* @throws SVDRPParseResponseException thrown if response data is not parseable
*/
public static SVDRPWelcome parse(String message) throws SVDRPParseResponseException {
SVDRPWelcome welcome = new SVDRPWelcome();
StringTokenizer st = new StringTokenizer(message, ";");
try {
String hostAndVersion = st.nextToken();
String dateAndTime = st.nextToken();
String charset = st.nextToken();
welcome.setCharset(charset.trim());
welcome.setVersion(hostAndVersion.substring(hostAndVersion.lastIndexOf(" ")).trim());
welcome.setDateAndTime(dateAndTime.trim());
} catch (NoSuchElementException nex) {
throw new SVDRPParseResponseException(nex.getMessage(), nex);
}
return welcome;
}
/**
* Get VDR version
*
* @return VDR version String
*/
public String getVersion() {
return version;
}
/**
* Set VDR version
*
* @param version VDR version String
*/
public void setVersion(String version) {
this.version = version;
}
/**
* Get VDR Charset
*
* @return VDR charset
*/
public String getCharset() {
return charset;
}
/**
* Set VDR Charset
*
* @param charset VDR charset
*/
public void setCharset(String charset) {
this.charset = charset;
}
/**
* Get VDR Date and Time String
*
* @return VDR Date and Time String
*/
public String getDateAndTime() {
return dateAndTime;
}
/**
* Set VDR Date and Time String
*
* @param dateAndTime VDR Date and Time String
*/
public void setDateAndTime(String dateAndTime) {
this.dateAndTime = dateAndTime;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="vdr" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>VDR Binding</name>
<description>The Video Disk Recorder (VDR) binding allows to control your own Video Disk Recorder
(https://www.tvdr.de).</description>
</binding:binding>

View File

@@ -0,0 +1,202 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="vdr" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="vdr">
<label>VDR</label>
<description>VDR - The Video Disk Recorder (https://tvdr.de)</description>
<channels>
<channel id="power" typeId="system.power"/>
<channel id="channel" typeId="vdrChannel"/>
<channel id="channelName" typeId="vdrChannelName"/>
<channel id="volume" typeId="system.volume"/>
<channel id="recording" typeId="vdrRecordingActive"/>
<channel id="diskUsage" typeId="vdrDiskUsage"/>
<channel id="message" typeId="vdrMessage"/>
<channel id="keyCode" typeId="vdrKeyCode"/>
<channel id="currentEventTitle" typeId="vdrEventTitle">
<label>Current EPG Event Title</label>
</channel>
<channel id="currentEventSubTitle" typeId="vdrEventSubTitle">
<label>Current EPG Event Sub Title</label>
</channel>
<channel id="currentEventBegin" typeId="vdrEventBegin">
<label>Current EPG Event Begin</label>
</channel>
<channel id="currentEventEnd" typeId="vdrEventEnd">
<label>Current EPG Event End</label>
</channel>
<channel id="currentEventDuration" typeId="vdrEventDuration">
<label>Current EPG Event Duration in Minutes</label>
</channel>
<channel id="nextEventTitle" typeId="vdrEventTitle">
<label>Next EPG Event Title</label>
</channel>
<channel id="nextEventSubTitle" typeId="vdrEventSubTitle">
<label>Next EPG Event Sub Title</label>
</channel>
<channel id="nextEventBegin" typeId="vdrEventBegin">
<label>Next EPG Event Begin</label>
</channel>
<channel id="nextEventEnd" typeId="vdrEventEnd">
<label>Next EPG Event End</label>
</channel>
<channel id="nextEventDuration" typeId="vdrEventDuration">
<label>Next EPG Event Duration in Minutes</label>
</channel>
</channels>
<properties>
<property name="version">VDR Version</property>
</properties>
<config-description>
<parameter name="host" type="text" required="true">
<context>network-address</context>
<label>Hostname</label>
<description>Hostname or IP Address of VDR instance</description>
</parameter>
<parameter name="port" type="integer" required="false">
<label>SVDRP Port</label>
<description>SVDRP Port of VDR instance</description>
<default>6419</default>
</parameter>
<parameter name="refresh" type="integer" min="0" unit="s">
<label>Refresh Interval</label>
<description>Interval in seconds the data from VDR instance is refreshed</description>
<advanced>true</advanced>
<default>30</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="vdrMessage">
<item-type>String</item-type>
<label>Display Message</label>
<description>Send Message to be displayed on VDR</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="vdrChannel">
<item-type>Number</item-type>
<label>Channel Number</label>
<description>Current Channel Number</description>
<state readOnly="false" pattern="%d"/>
</channel-type>
<channel-type id="vdrChannelName">
<item-type>String</item-type>
<label>Channel Name</label>
<description>Current Channel Name</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="vdrRecordingActive">
<item-type>Switch</item-type>
<label>Recording Active</label>
<description>ON if a recording is active</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="vdrDiskUsage">
<item-type>Number</item-type>
<label>Disk Usage</label>
<description>Current Disk Usage in %</description>
<state readOnly="true" min="0" max="100" step="1" pattern="%d %%"/>
</channel-type>
<channel-type id="vdrKeyCode">
<item-type>String</item-type>
<label>VDR Key Code</label>
<description>Send Key Code of Remote Control to VDR</description>
<command>
<options>
<option value="Up">Up</option>
<option value="Down">Down</option>
<option value="Menu">Menu</option>
<option value="Ok">Ok</option>
<option value="Back">Back</option>
<option value="Left">Left</option>
<option value="Right">Right</option>
<option value="Red">Red</option>
<option value="Green">Green</option>
<option value="Yellow">Yellow</option>
<option value="Blue">Blue</option>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="Info">Info</option>
<option value="Play/Pause">Play/Pause</option>
<option value="Play">Play</option>
<option value="Pause">Pause</option>
<option value="Stop">Stop</option>
<option value="Record">Record</option>
<option value="FastFwd">FastFwd</option>
<option value="FastRew">FastRew</option>
<option value="Next">Next</option>
<option value="Prev">Prev</option>
<option value="Power">Power</option>
<option value="Channel+">Channel+</option>
<option value="Channel-">Channel-</option>
<option value="PrevChannel">PrevChannel</option>
<option value="Volume+">Volume+</option>
<option value="Volume-">Volume-</option>
<option value="Mute">Mute</option>
<option value="Audio">Audio</option>
<option value="Subtitles">Subtitles</option>
<option value="Schedule">Schedule</option>
<option value="Channels">Channels</option>
<option value="Timers">Timers</option>
<option value="Recordings">Recordings</option>
<option value="Setup">Setup</option>
<option value="Commands">Commands</option>
<option value="User0">User0</option>
<option value="User1">User1</option>
<option value="User2">User2</option>
<option value="User3">User3</option>
<option value="User4">User4</option>
<option value="User5">User5</option>
<option value="User6">User6</option>
<option value="User7">User7</option>
<option value="User8">User8</option>
<option value="User9">User9</option>
</options>
</command>
</channel-type>
<channel-type id="vdrEventTitle">
<item-type>String</item-type>
<label>Event Title</label>
<description>Title of EPG Event</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="vdrEventSubTitle">
<item-type>String</item-type>
<label>Event Sub Title</label>
<description>Sub Title of EPG Event</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="vdrEventBegin">
<item-type>DateTime</item-type>
<label>Event Start Time</label>
<description>Start Time of EPG Event</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="vdrEventEnd">
<item-type>DateTime</item-type>
<label>Event End Time</label>
<description>End Time of EPG Event</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="vdrEventDuration">
<item-type>Number:Time</item-type>
<label>Event Duration</label>
<description>Duration of EPG Event in Minutes</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.vdr.internal.svdrp.SVDRPChannel;
import org.openhab.binding.vdr.internal.svdrp.SVDRPException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPParseResponseException;
/**
* Specific unit tests to check if {@link SVDRPChannel} parses SVDRP responses correctly
*
* @author Matthias Klocke - Initial contribution
*
*/
@NonNullByDefault
public class SVDRPChannelTest {
private final String channelResponseOk = "3 WDR HD Bielefeld";
private final String channelResponseParseError = "250WDR HD Bielefeld";
@Test
public void testParseChannelData() throws SVDRPException {
SVDRPChannel channel = SVDRPChannel.parse(channelResponseOk);
assertEquals("WDR HD Bielefeld", channel.getName());
assertEquals(3, channel.getNumber());
}
@Test
public void testParseExceptionChannelData() {
assertThrows(SVDRPParseResponseException.class, () -> {
SVDRPChannel.parse(channelResponseParseError);
});
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.vdr.internal.svdrp.SVDRPChannel;
import org.openhab.binding.vdr.internal.svdrp.SVDRPDiskStatus;
import org.openhab.binding.vdr.internal.svdrp.SVDRPException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPParseResponseException;
/**
* Specific unit tests to check if {@link SVDRPDiskStatus} parses SVDRP responses correctly
*
* @author Matthias Klocke - Initial contribution
*
*/
@NonNullByDefault
public class SVDRPDiskStatusTest {
private final String diskStatusResponseOk = "411266MB 30092MB 92%";
private final String diskStatusResponseParseError1 = "411266MB 30092MB 92%";
private final String diskStatusResponseParseError2 = "411266MB 30092 92%";
private final String diskStatusResponseParseError3 = "42b3MB 30092MB 92%";
@Test
public void testParseDiskStatus() throws SVDRPException {
SVDRPDiskStatus diskStatus = SVDRPDiskStatus.parse(diskStatusResponseOk);
assertEquals(411266, diskStatus.getMegaBytesTotal());
assertEquals(30092, diskStatus.getMegaBytesFree());
assertEquals(92, diskStatus.getPercentUsed());
}
@Test
public void testParseExceptionDiskStatus() {
assertThrows(SVDRPParseResponseException.class, () -> {
SVDRPChannel.parse(diskStatusResponseParseError1);
});
assertThrows(SVDRPParseResponseException.class, () -> {
SVDRPChannel.parse(diskStatusResponseParseError2);
});
assertThrows(SVDRPParseResponseException.class, () -> {
SVDRPChannel.parse(diskStatusResponseParseError3);
});
}
}

View File

@@ -0,0 +1,105 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.text.ParseException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.vdr.internal.svdrp.SVDRPEpgEvent;
import org.openhab.binding.vdr.internal.svdrp.SVDRPException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPParseResponseException;
/**
* Specific unit tests to check if {@link SVDRPEpgEvent} parses SVDRP responses correctly
*
* @author Matthias Klocke - Initial contribution
*
*/
@NonNullByDefault
public class SVDRPEpgEventTest {
private final String epgResponseComplete = "C S19.2E-1-1201-28326 WDR HD Bielefeld\n"
+ "E 9886 1610391600 900 4E F\n" + "T Tagesschau\n" + "S Aktuelle Nachrichten aus der Welt\n"
+ "D Themen u.a.:|* Corona-Pandemie in Deutschland: Verschärfter Lockdown bundesweit in Kraft|* Entmachtung des US-Präsidenten: Demokraten planen Schritte gegen Trump|* Wintereinbruch in Bosnien-Herzegowina: Dramatische Lage der Flüchtlinge an der Grenze zu Kroatien\n"
+ "G 20 80\n" + "X 2 03 deu stereo\n" + "X 2 03 deu ohne Audiodeskription\n"
+ "X 3 01 deu Teletext-Untertitel\n" + "X 3 20 deu mit DVB-Untertitel\n" + "X 5 0B deu HD-Video\n"
+ "V 1610391600\n" + "e\n" + "c\n" + "End of EPG data";
private final String epgMissingSubtitle = "C S19.2E-1-1201-28326 WDR HD Bielefeld\n"
+ "E 9886 1610391600 900 4E F\n" + "T Tagesschau\n"
+ "D Themen u.a.:|* Corona-Pandemie in Deutschland: Verschärfter Lockdown bundesweit in Kraft|* Entmachtung des US-Präsidenten: Demokraten planen Schritte gegen Trump|* Wintereinbruch in Bosnien-Herzegowina: Dramatische Lage der Flüchtlinge an der Grenze zu Kroatien\n"
+ "G 20 80\n" + "X 2 03 deu stereo\n" + "X 2 03 deu ohne Audiodeskription\n"
+ "X 3 01 deu Teletext-Untertitel\n" + "X 3 20 deu mit DVB-Untertitel\n" + "X 5 0B deu HD-Video\n"
+ "V 1610391600\n" + "e\n" + "c\n" + "End of EPG data";
private final String epgParseError = "E 9999999999999999999999999";
private final String epgCorruptDate = "C S19.2E-1-1201-28326 WDR HD Bielefeld\n" + "E 9886 2a10391600 900 4E F\n"
+ "T Tagesschau\n"
+ "D Themen u.a.:|* Corona-Pandemie in Deutschland: Verschärfter Lockdown bundesweit in Kraft|* Entmachtung des US-Präsidenten: Demokraten planen Schritte gegen Trump|* Wintereinbruch in Bosnien-Herzegowina: Dramatische Lage der Flüchtlinge an der Grenze zu Kroatien\n"
+ "G 20 80\n" + "X 2 03 deu stereo\n" + "X 2 03 deu ohne Audiodeskription\n"
+ "X 3 01 deu Teletext-Untertitel\n" + "X 3 20 deu mit DVB-Untertitel\n" + "X 5 0B deu HD-Video\n"
+ "V 1610391600\n" + "e\n" + "c\n" + "End of EPG data";
private final String epgMissingEnd = "C S19.2E-1-1201-28326 WDR HD Bielefeld\n" + "E 9886 1610391600 900 4E F\n"
+ "T Tagesschau\n"
+ "D Themen u.a.:|* Corona-Pandemie in Deutschland: Verschärfter Lockdown bundesweit in Kraft|* Entmachtung des US-Präsidenten: Demokraten planen Schritte gegen Trump|* Wintereinbruch in Bosnien-Herzegowina: Dramatische Lage der Flüchtlinge an der Grenze zu Kroatien\n"
+ "G 20 80\n" + "X 2 03 deu stereo\n" + "X 2 03 deu ohne Audiodeskription\n"
+ "X 3 01 deu Teletext-Untertitel\n" + "X 3 20 deu mit DVB-Untertitel\n" + "X 5 0B deu HD-Video\n"
+ "V 1610391600\n" + "e\n" + "c\n";
@Test
public void testParseEpgEventComplete() throws SVDRPException, ParseException {
SVDRPEpgEvent event = SVDRPEpgEvent.parse(epgResponseComplete);
assertEquals("Tagesschau", event.getTitle());
assertEquals("Aktuelle Nachrichten aus der Welt", event.getSubtitle());
assertEquals(15, event.getDuration());
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
assertEquals(ZonedDateTime.parse("2021-01-11 19:00:00 UTC", dtf).toInstant(), event.getBegin());
assertEquals(ZonedDateTime.parse("2021-01-11 19:15:00 UTC", dtf).toInstant(), event.getEnd());
}
@Test
public void testParseEpgEventMissingSubtitle() throws SVDRPException {
SVDRPEpgEvent event = SVDRPEpgEvent.parse(epgMissingSubtitle);
assertEquals("Tagesschau", event.getTitle());
assertEquals("", event.getSubtitle());
}
@Test
public void testParseEpgEventCorruptDate() {
assertThrows(SVDRPParseResponseException.class, () -> {
SVDRPEpgEvent.parse(epgCorruptDate);
});
}
@Test
public void testParseEpgEventMissingEnd() throws SVDRPException, ParseException {
SVDRPEpgEvent event = SVDRPEpgEvent.parse(epgMissingEnd);
assertEquals("Tagesschau", event.getTitle());
assertEquals("", event.getSubtitle());
assertEquals(15, event.getDuration());
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
assertEquals(ZonedDateTime.parse("2021-01-11 19:00:00 UTC", dtf).toInstant(), event.getBegin());
assertEquals(ZonedDateTime.parse("2021-01-11 19:15:00 UTC", dtf).toInstant(), event.getEnd());
}
@Test
public void testParseExceptionVolumeData() {
assertThrows(SVDRPParseResponseException.class, () -> {
SVDRPEpgEvent.parse(epgParseError);
});
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.vdr.internal.svdrp.SVDRPException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPTimerList;
/**
* Specific unit tests to check if {@link SVDRPTimerList} parses SVDRP responses correctly
*
* @author Matthias Klocke - Initial contribution
*
*/
@NonNullByDefault
public class SVDRPTimerListTest {
private final String timerListResponseTimerActive = "1 1:1:2021-01-12:2013:2110:50:99:Charité (1/6)~Eiserne Lunge:Test\n"
+ "2 9:1:2021-01-12:2058:2200:50:99:Charité (2/6)~Blutsauger:Test";
private final String timerListResponseTimerNotActive = "1 1:1:2021-01-12:2013:2110:50:99:Charité (1/6)~Eiserne Lunge:Test\n"
+ "2 1:1:2021-01-12:2058:2200:50:99:Charité (2/6)~Blutsauger:Test";
@Test
public void testParseTimerList() throws SVDRPException {
SVDRPTimerList list = SVDRPTimerList.parse(timerListResponseTimerActive);
assertEquals(true, list.isRecordingActive());
list = SVDRPTimerList.parse(timerListResponseTimerNotActive);
assertEquals(false, list.isRecordingActive());
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.vdr.internal.svdrp.SVDRPException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPParseResponseException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPVolume;
/**
* Specific unit tests to check if {@link SVDRPVolume} parses SVDRP responses correctly
*
* @author Matthias Klocke - Initial contribution
*
*/
@NonNullByDefault
public class SVDRPVolumeTest {
private final String volumeResponseOk = "Audio volume is 255";
private final String volumeResponseMute = "Audio is mute";
private final String volumeResponseParseError1 = "Audiovolumeis255";
private final String volumeResponseParseError2 = "Audio volume is 255x";
@Test
public void testParseVolumeData() throws SVDRPException {
SVDRPVolume volume = SVDRPVolume.parse(volumeResponseOk);
assertEquals(100, volume.getVolume());
volume = SVDRPVolume.parse(volumeResponseMute);
assertEquals(0, volume.getVolume());
}
@Test
public void testParseExceptionVolumeData() {
assertThrows(SVDRPParseResponseException.class, () -> {
SVDRPVolume.parse(volumeResponseParseError1);
});
assertThrows(SVDRPParseResponseException.class, () -> {
SVDRPVolume.parse(volumeResponseParseError2);
});
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.vdr.internal;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.vdr.internal.svdrp.SVDRPException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPParseResponseException;
import org.openhab.binding.vdr.internal.svdrp.SVDRPVolume;
import org.openhab.binding.vdr.internal.svdrp.SVDRPWelcome;
/**
* Specific unit tests to check if {@link SVDRPWelcome} parses SVDRP responses correctly
*
* @author Matthias Klocke - Initial contribution
*
*/
@NonNullByDefault
public class SVDRPWelcomeTest {
private final String welcomeResponseOk = "srv SVDRP VideoDiskRecorder 2.5.1; Mon Jan 11 19:46:54 2021; UTF-8";
private final String welcomeResponseParseError1 = "srv SVDRP VideoDiskRecorder 2.5.1; Mon Jan 11 19:46:54 2021 UTF-8";
private final String welcomeResponseParseError2 = "srv SVDRP VideoDiskRecorder2.5.1; Mon Jan 11 19:46:54 2021 UTF-8";
@Test
public void testParseWelcomeData() throws SVDRPException {
SVDRPWelcome welcome = SVDRPWelcome.parse(welcomeResponseOk);
assertEquals("UTF-8", welcome.getCharset());
assertEquals("2.5.1", welcome.getVersion());
assertEquals("Mon Jan 11 19:46:54 2021", welcome.getDateAndTime());
}
@Test
public void testParseExceptionVolumeData() {
assertThrows(SVDRPParseResponseException.class, () -> {
SVDRPVolume.parse(welcomeResponseParseError1);
});
assertThrows(SVDRPParseResponseException.class, () -> {
SVDRPVolume.parse(welcomeResponseParseError2);
});
}
}