[tivo] Update tivo binding for OH3 (#9302)
* Baseline original code from AndyXMB * Initial updates for OH3 * fix null warnings and add sub-channel handling Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.tivo-${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-tivo" description="TiVo Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.tivo/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -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.tivo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link TiVoBinding} class defines common constants that are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||
* @author Andrew Black (AndyXMB) - Addition of Min / Max Channel and channel scanning properties
|
||||
* @author Michael Lobstein - Updated for OH3
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class TiVoBindingConstants {
|
||||
public static final String BINDING_ID = "tivo";
|
||||
public static final int CONFIG_SOCKET_TIMEOUT_MS = 1000;
|
||||
public static final int INIT_POLLING_DELAY_S = 5;
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_TIVO = new ThingTypeUID(BINDING_ID, "sckt");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_TIVO_CHANNEL_FORCE = "channelForce";
|
||||
public static final String CHANNEL_TIVO_CHANNEL_SET = "channelSet";
|
||||
public static final String CHANNEL_TIVO_IS_RECORDING = "isRecording";
|
||||
public static final String CHANNEL_TIVO_TELEPORT = "menuTeleport";
|
||||
public static final String CHANNEL_TIVO_IRCMD = "irCommand";
|
||||
public static final String CHANNEL_TIVO_KBDCMD = "kbdCommand";
|
||||
public static final String CHANNEL_TIVO_STATUS = "dvrStatus";
|
||||
|
||||
// List of all configuration Properties
|
||||
public static final String CONFIG_HOST = "host";
|
||||
public static final String CONFIG_PORT = "tcpPort";
|
||||
}
|
||||
@@ -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.tivo.internal;
|
||||
|
||||
import static org.openhab.binding.tivo.internal.TiVoBindingConstants.THING_TYPE_TIVO;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tivo.internal.handler.TiVoHandler;
|
||||
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.Component;
|
||||
|
||||
/**
|
||||
* The {@link TiVoHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||
* @author Andrew Black (AndyXMB) - minor updates, removal of unused DiscoveryService functionality.
|
||||
* @author Michael Lobstein - Updated for OH3
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.tivo", service = ThingHandlerFactory.class)
|
||||
public class TiVoHandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_TIVO);
|
||||
|
||||
@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 (thingTypeUID.equals(THING_TYPE_TIVO)) {
|
||||
return new TiVoHandler(thing);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 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.tivo.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.tivo.internal.TiVoBindingConstants.*;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The Class TiVoDiscoveryParticipant.
|
||||
* *
|
||||
*
|
||||
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||
* @author Andrew Black (AndyXMB) - minor updates.
|
||||
* @author Michael Lobstein - Updated for OH3
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "discovery.tivo")
|
||||
public class TiVoDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
private final Logger logger = LoggerFactory.getLogger(TiVoDiscoveryParticipant.class);
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Collections.singleton(THING_TYPE_TIVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return "_tivo-remote._tcp.local.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
|
||||
DiscoveryResult result = null;
|
||||
|
||||
ThingUID uid = getThingUID(service);
|
||||
if (uid != null) {
|
||||
Map<String, Object> properties = new HashMap<>(2);
|
||||
// remove the domain from the name
|
||||
InetAddress ip = getIpAddress(service);
|
||||
if (ip == null) {
|
||||
return null;
|
||||
}
|
||||
String inetAddress = ip.toString().substring(1); // trim leading slash
|
||||
String label = service.getName();
|
||||
int port = service.getPort();
|
||||
|
||||
properties.put(CONFIG_HOST, inetAddress);
|
||||
properties.put(CONFIG_PORT, port);
|
||||
|
||||
result = DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel("Tivo: " + label)
|
||||
.withProperty(CONFIG_HOST, inetAddress).withRepresentationProperty(CONFIG_HOST).build();
|
||||
logger.debug("Created {} for TiVo host '{}' name '{}'", result, inetAddress, label);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant#getThingUID(javax.jmdns.ServiceInfo)
|
||||
*/
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(ServiceInfo service) {
|
||||
if (service.getType() != null) {
|
||||
if (service.getType().equals(getServiceType())) {
|
||||
String uidName = getUIDName(service);
|
||||
return new ThingUID(THING_TYPE_TIVO, uidName);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UID name, replacing any non AlphaNumeric characters with underscores.
|
||||
*
|
||||
* @param service the service
|
||||
* @return the UID name
|
||||
*/
|
||||
private String getUIDName(ServiceInfo service) {
|
||||
return service.getName().replaceAll("[^A-Za-z0-9_]", "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link InetAddress} gets the IP address of the device in v4 or v6 format.
|
||||
*
|
||||
* @param ServiceInfo service
|
||||
* @return InetAddress the IP address
|
||||
*
|
||||
*/
|
||||
private @Nullable InetAddress getIpAddress(ServiceInfo service) {
|
||||
InetAddress address = null;
|
||||
for (InetAddress addr : service.getInet4Addresses()) {
|
||||
return addr;
|
||||
}
|
||||
// Fall back for Inet6addresses
|
||||
for (InetAddress addr : service.getInet6Addresses()) {
|
||||
return addr;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/**
|
||||
* 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.tivo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.tivo.internal.TiVoBindingConstants.*;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tivo.internal.service.TivoConfigData;
|
||||
import org.openhab.binding.tivo.internal.service.TivoStatusData;
|
||||
import org.openhab.binding.tivo.internal.service.TivoStatusData.ConnectionStatus;
|
||||
import org.openhab.binding.tivo.internal.service.TivoStatusProvider;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link TiVoHandler} is the BaseThingHandler responsible for handling commands that are
|
||||
* sent to one of the Tivo's channels.
|
||||
*
|
||||
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||
* @author Andrew Black (AndyXMB) - Updates / compilation corrections. Addition of channel scanning functionality.
|
||||
* @author Michael Lobstein - Updated for OH3
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class TiVoHandler extends BaseThingHandler {
|
||||
private static final Pattern NUMERIC_PATTERN = Pattern.compile("(\\d+)\\.?(\\d+)?");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TiVoHandler.class);
|
||||
private TivoConfigData tivoConfigData = new TivoConfigData();
|
||||
private ConnectionStatus lastConnectionStatus = ConnectionStatus.UNKNOWN;
|
||||
private Optional<TivoStatusProvider> tivoConnection = Optional.empty();
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
|
||||
/**
|
||||
* Instantiates a new TiVo handler.
|
||||
*
|
||||
* @param thing the thing
|
||||
*/
|
||||
public TiVoHandler(Thing thing) {
|
||||
super(thing);
|
||||
logger.debug("TiVoHandler '{}' - creating", getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Handles the commands from the various TiVo channel objects
|
||||
logger.debug("handleCommand '{}', parameter: {}", channelUID, command);
|
||||
|
||||
if (!isInitialized() || !tivoConnection.isPresent()) {
|
||||
logger.debug("handleCommand '{}' device is not initialized yet, command '{}' will be ignored.",
|
||||
getThing().getUID(), channelUID + " " + command);
|
||||
return;
|
||||
}
|
||||
|
||||
TivoStatusData currentStatus = tivoConnection.get().getServiceStatus();
|
||||
String commandKeyword = "";
|
||||
|
||||
String commandParameter = command.toString().toUpperCase();
|
||||
if (command instanceof RefreshType) {
|
||||
// Future enhancement, if we can come up with a sensible set of actions when a REFRESH is issued
|
||||
logger.debug("TiVo '{}' skipping REFRESH command for channel: '{}'.", getThing().getUID(),
|
||||
channelUID.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_TIVO_CHANNEL_FORCE:
|
||||
commandKeyword = "FORCECH";
|
||||
break;
|
||||
case CHANNEL_TIVO_CHANNEL_SET:
|
||||
commandKeyword = "SETCH";
|
||||
break;
|
||||
case CHANNEL_TIVO_TELEPORT:
|
||||
commandKeyword = "TELEPORT";
|
||||
break;
|
||||
case CHANNEL_TIVO_IRCMD:
|
||||
commandKeyword = "IRCODE";
|
||||
break;
|
||||
case CHANNEL_TIVO_KBDCMD:
|
||||
commandKeyword = "KEYBOARD";
|
||||
break;
|
||||
}
|
||||
try {
|
||||
sendCommand(commandKeyword, commandParameter, currentStatus);
|
||||
} catch (InterruptedException e) {
|
||||
// TiVo handler disposed or openHAB exiting, do nothing
|
||||
}
|
||||
}
|
||||
|
||||
public void setStatusOffline() {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Power on device or check network configuration/connection.");
|
||||
}
|
||||
|
||||
private void sendCommand(String commandKeyword, String commandParameter, TivoStatusData currentStatus)
|
||||
throws InterruptedException {
|
||||
if (!tivoConnection.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TivoStatusData deviceStatus = tivoConnection.get().getServiceStatus();
|
||||
TivoStatusData commandResult = null;
|
||||
logger.debug("handleCommand '{}' - {} found!", getThing().getUID(), commandKeyword);
|
||||
// Re-write command keyword if we are in STANDBY, as only IRCODE TIVO will wake the unit from
|
||||
// standby mode
|
||||
if (deviceStatus.getConnectionStatus() == ConnectionStatus.STANDBY && commandKeyword.contentEquals("TELEPORT")
|
||||
&& commandParameter.contentEquals("TIVO")) {
|
||||
String command = "IRCODE " + commandParameter;
|
||||
logger.debug("TiVo '{}' TELEPORT re-mapped to IRCODE as we are in standby: '{}'", getThing().getUID(),
|
||||
command);
|
||||
}
|
||||
// Execute command
|
||||
if (commandKeyword.contentEquals("FORCECH") || commandKeyword.contentEquals("SETCH")) {
|
||||
commandResult = chChannelChange(commandKeyword, commandParameter);
|
||||
} else {
|
||||
commandResult = tivoConnection.get().cmdTivoSend(commandKeyword + " " + commandParameter);
|
||||
}
|
||||
|
||||
// Post processing
|
||||
if (commandResult != null && commandParameter.contentEquals("STANDBY")) {
|
||||
// Force thing state into STANDBY as this command does not return a status when executed
|
||||
commandResult.setConnectionStatus(ConnectionStatus.STANDBY);
|
||||
}
|
||||
|
||||
// Push status updates
|
||||
if (commandResult != null && commandResult.isCmdOk()) {
|
||||
updateTivoStatus(currentStatus, commandResult);
|
||||
}
|
||||
|
||||
if (!tivoConfigData.isKeepConnActive()) {
|
||||
// disconnect once command is complete
|
||||
tivoConnection.get().connTivoDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing a TiVo '{}' with config options", getThing().getUID());
|
||||
|
||||
tivoConfigData = getConfigAs(TivoConfigData.class);
|
||||
|
||||
tivoConfigData.setCfgIdentifier(getThing().getUID().getAsString());
|
||||
tivoConnection = Optional.of(new TivoStatusProvider(tivoConfigData, this));
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
lastConnectionStatus = ConnectionStatus.UNKNOWN;
|
||||
logger.debug("Initializing a TiVo handler for thing '{}' - finished!", getThing().getUID());
|
||||
|
||||
startPollStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing of a TiVo handler for thing '{}'", getThing().getUID());
|
||||
|
||||
ScheduledFuture<?> refreshJob = this.refreshJob;
|
||||
if (refreshJob != null) {
|
||||
refreshJob.cancel(false);
|
||||
this.refreshJob = null;
|
||||
}
|
||||
|
||||
if (tivoConnection.isPresent()) {
|
||||
try {
|
||||
tivoConnection.get().connTivoDisconnect();
|
||||
} catch (InterruptedException e) {
|
||||
// TiVo handler disposed or openHAB exiting, do nothing
|
||||
}
|
||||
tivoConnection = Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link startPollStatus} scheduled job to poll for changes in state.
|
||||
*/
|
||||
private void startPollStatus() {
|
||||
Runnable runnable = () -> {
|
||||
logger.debug("startPollStatus '{}' @ rate of '{}' seconds", getThing().getUID(),
|
||||
tivoConfigData.getPollInterval());
|
||||
tivoConnection.ifPresent(connection -> {
|
||||
try {
|
||||
connection.statusRefresh();
|
||||
} catch (InterruptedException e) {
|
||||
// TiVo handler disposed or openHAB exiting, do nothing
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (tivoConfigData.isKeepConnActive()) {
|
||||
// Run once
|
||||
refreshJob = scheduler.schedule(runnable, INIT_POLLING_DELAY_S, TimeUnit.SECONDS);
|
||||
logger.debug("Status collection '{}' will start in '{}' seconds.", getThing().getUID(),
|
||||
INIT_POLLING_DELAY_S);
|
||||
} else if (tivoConfigData.doPollChanges()) {
|
||||
// Run at intervals
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(runnable, INIT_POLLING_DELAY_S,
|
||||
tivoConfigData.getPollInterval(), TimeUnit.SECONDS);
|
||||
logger.debug("Status polling '{}' will start in '{}' seconds.", getThing().getUID(), INIT_POLLING_DELAY_S);
|
||||
} else {
|
||||
// Just update the status now
|
||||
tivoConnection.ifPresent(connection -> {
|
||||
try {
|
||||
connection.statusRefresh();
|
||||
} catch (InterruptedException e) {
|
||||
// TiVo handler disposed or openHAB exiting, do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link chChannelChange} performs channel changing operations.
|
||||
*
|
||||
* @param commandKeyword the TiVo command object.
|
||||
* @param command the command parameter.
|
||||
* @return TivoStatusData status of the command.
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private TivoStatusData chChannelChange(String commandKeyword, String command) throws InterruptedException {
|
||||
int channel = -1;
|
||||
int subChannel = -1;
|
||||
|
||||
TivoStatusData tmpStatus = tivoConnection.get().getServiceStatus();
|
||||
try {
|
||||
// Parse the channel number and if there is a decimal, the sub-channel number (OTA channels)
|
||||
Matcher matcher = NUMERIC_PATTERN.matcher(command);
|
||||
if (matcher.find()) {
|
||||
if (matcher.groupCount() >= 1) {
|
||||
channel = Integer.parseInt(matcher.group(1).trim());
|
||||
}
|
||||
if (matcher.groupCount() >= 2 && matcher.group(2) != null) {
|
||||
subChannel = Integer.parseInt(matcher.group(2).trim());
|
||||
}
|
||||
} else {
|
||||
// The command string was not a number, throw exception to catch & log below
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
|
||||
String tmpCommand = commandKeyword + " " + channel + ((subChannel != -1) ? (" " + subChannel) : "");
|
||||
logger.debug("chChannelChange '{}' sending command to tivo: '{}'", getThing().getUID(), tmpCommand);
|
||||
|
||||
// Attempt to execute the command on the tivo
|
||||
tivoConnection.get().cmdTivoSend(tmpCommand);
|
||||
TimeUnit.MILLISECONDS.sleep(tivoConfigData.getCmdWaitInterval() * 2);
|
||||
|
||||
tmpStatus = tivoConnection.get().getServiceStatus();
|
||||
|
||||
// Check to see if the command was successful
|
||||
if (tmpStatus.getConnectionStatus() != ConnectionStatus.INIT && tmpStatus.isCmdOk()) {
|
||||
if (tmpStatus.getMsg().contains("CH_STATUS")) {
|
||||
return tmpStatus;
|
||||
}
|
||||
} else if (tmpStatus.getConnectionStatus() != ConnectionStatus.INIT) {
|
||||
logger.warn("TiVo'{}' set channel command failed '{}' with msg '{}'", getThing().getUID(), tmpCommand,
|
||||
tmpStatus.getMsg());
|
||||
switch (tmpStatus.getMsg()) {
|
||||
case "CH_FAILED NO_LIVE":
|
||||
tmpStatus.setChannelNum(channel);
|
||||
tmpStatus.setSubChannelNum(subChannel);
|
||||
return tmpStatus;
|
||||
case "CH_FAILED RECORDING":
|
||||
case "CH_FAILED MISSING_CHANNEL":
|
||||
case "CH_FAILED MALFORMED_CHANNEL":
|
||||
case "CH_FAILED INVALID_CHANNEL":
|
||||
return tmpStatus;
|
||||
case "NO_STATUS_DATA_RETURNED":
|
||||
tmpStatus.setChannelNum(-1);
|
||||
tmpStatus.setSubChannelNum(-1);
|
||||
tmpStatus.setRecording(false);
|
||||
return tmpStatus;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("TiVo'{}' unable to parse channel integer, value sent was: '{}'", getThing().getUID(),
|
||||
command.toString());
|
||||
}
|
||||
return tmpStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link updateTivoStatus} populates the items with the status / channel information.
|
||||
*
|
||||
* @param tivoStatusData the {@link TivoStatusData}
|
||||
*/
|
||||
public void updateTivoStatus(TivoStatusData oldStatusData, TivoStatusData newStatusData) {
|
||||
if (newStatusData.getConnectionStatus() != ConnectionStatus.INIT) {
|
||||
// Update Item Status
|
||||
if (newStatusData.getPubToUI()) {
|
||||
if (oldStatusData.getConnectionStatus() == ConnectionStatus.INIT
|
||||
|| !(oldStatusData.getMsg().contentEquals(newStatusData.getMsg()))) {
|
||||
updateState(CHANNEL_TIVO_STATUS, new StringType(newStatusData.getMsg()));
|
||||
}
|
||||
// If the cmd was successful, publish the channel numbers
|
||||
if (newStatusData.isCmdOk() && newStatusData.getChannelNum() != -1) {
|
||||
if (oldStatusData.getConnectionStatus() == ConnectionStatus.INIT
|
||||
|| oldStatusData.getChannelNum() != newStatusData.getChannelNum()
|
||||
|| oldStatusData.getSubChannelNum() != newStatusData.getSubChannelNum()) {
|
||||
if (newStatusData.getSubChannelNum() == -1) {
|
||||
updateState(CHANNEL_TIVO_CHANNEL_FORCE, new DecimalType(newStatusData.getChannelNum()));
|
||||
updateState(CHANNEL_TIVO_CHANNEL_SET, new DecimalType(newStatusData.getChannelNum()));
|
||||
} else {
|
||||
updateState(CHANNEL_TIVO_CHANNEL_FORCE, new DecimalType(
|
||||
newStatusData.getChannelNum() + "." + newStatusData.getSubChannelNum()));
|
||||
updateState(CHANNEL_TIVO_CHANNEL_SET, new DecimalType(
|
||||
newStatusData.getChannelNum() + "." + newStatusData.getSubChannelNum()));
|
||||
}
|
||||
}
|
||||
updateState(CHANNEL_TIVO_IS_RECORDING, newStatusData.isRecording() ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
|
||||
// Now set the pubToUI flag to false, as we have already published this status
|
||||
if (isLinked(CHANNEL_TIVO_STATUS) || isLinked(CHANNEL_TIVO_CHANNEL_FORCE)
|
||||
|| isLinked(CHANNEL_TIVO_CHANNEL_SET)) {
|
||||
newStatusData.setPubToUI(false);
|
||||
tivoConnection.get().setServiceStatus(newStatusData);
|
||||
}
|
||||
}
|
||||
|
||||
// Update Thing status
|
||||
if (newStatusData.getConnectionStatus() != lastConnectionStatus) {
|
||||
switch (newStatusData.getConnectionStatus()) {
|
||||
case OFFLINE:
|
||||
this.setStatusOffline();
|
||||
break;
|
||||
case ONLINE:
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
break;
|
||||
case STANDBY:
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||
"STANDBY MODE: Send command TIVO to Remote Control Button (IRCODE) item to wakeup.");
|
||||
break;
|
||||
case UNKNOWN:
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
break;
|
||||
case INIT:
|
||||
break;
|
||||
}
|
||||
lastConnectionStatus = newStatusData.getConnectionStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* 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.tivo.internal.service;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The Class {@link TivoConfigData} stores the dynamic configuration parameters used within the {@link TivoHandler } and
|
||||
* {@link TivoConfigStatusProvider}.
|
||||
*
|
||||
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||
* @author Andrew Black (AndyXMB) - minor updates, removal of unused DiscoveryService functionality.
|
||||
* @author Michael Lobstein - Updated for OH3
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class TivoConfigData {
|
||||
private @Nullable String host = null;
|
||||
private int tcpPort = 31339;
|
||||
private int numRetry = 0;
|
||||
private int pollInterval = 30;
|
||||
private boolean pollForChanges = false;
|
||||
private boolean keepConActive = false;
|
||||
private int cmdWaitInterval = 0;
|
||||
private String cfgIdentifier = "";
|
||||
|
||||
/**
|
||||
* {@link toString} returns each of the configuration items as a single concatenated string.
|
||||
*
|
||||
* @return string
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TivoConfigData [host=" + host + ", tcpPort=" + tcpPort + ", numRetry=" + numRetry + ", pollInterval="
|
||||
+ pollInterval + ", pollForChanges=" + pollForChanges + ", keepConActive=" + keepConActive
|
||||
+ ", cmdWaitInterval=" + cmdWaitInterval + ", cfgIdentifier=" + cfgIdentifier + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cfgIdentifier representing the thing name of the TiVo device.
|
||||
*
|
||||
* @return the cfgIdentifier
|
||||
*/
|
||||
public String getCfgIdentifier() {
|
||||
return this.cfgIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cfgIdentifier representing the thing name of the TiVo device.
|
||||
*
|
||||
* @param cfgIdentifier the cfgIdentifier to set
|
||||
*/
|
||||
public void setCfgIdentifier(String cfgIdentifier) {
|
||||
this.cfgIdentifier = cfgIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the host representing the host name or IP address of the device.
|
||||
*
|
||||
* @return the host
|
||||
*/
|
||||
public @Nullable String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* the host representing the host name or IP address of the device.
|
||||
*
|
||||
* @param host the host to set
|
||||
*/
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cfgTcp representing the IP port of the Remote Control Protocol service on the device.
|
||||
*
|
||||
* @return the tcpPort
|
||||
*/
|
||||
public int getTcpPort() {
|
||||
return tcpPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cfgTcp representing the IP port of the Remote Control Protocol service on the device (31339).
|
||||
*
|
||||
* @param tcpPort the tcpPort to set
|
||||
*/
|
||||
public void setTcpPort(int tcpPort) {
|
||||
this.tcpPort = tcpPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the numRetry value. This determines the number of connection attempts made to the IP/Port of the
|
||||
* service and the number of read attempts that are made when a command is submitted to the device, separated by
|
||||
* the
|
||||
* interval specified in the Command Wait Interval.
|
||||
*
|
||||
* @return the numRetry
|
||||
*/
|
||||
public int getNumRetry() {
|
||||
return numRetry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the numRetry value. This determines the number of connection attempts made to the IP/Port of the
|
||||
* service and the number of read attempts that are made when a command is submitted to the device, separated by
|
||||
* the
|
||||
* interval specified in the Command Wait Interval.
|
||||
*
|
||||
* @param numRetry the numRetry to set
|
||||
*/
|
||||
public void setNumRetry(int numRetry) {
|
||||
this.numRetry = numRetry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pollInterval representing the interval in seconds between polling attempts to collect any updated
|
||||
* status information.
|
||||
*
|
||||
* @return the pollInterval
|
||||
*/
|
||||
public int getPollInterval() {
|
||||
return pollInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pollInterval representing the interval in seconds between polling attempts to collect any updated
|
||||
* status information.
|
||||
*
|
||||
* @param pollInterval the pollInterval to set
|
||||
*/
|
||||
public void setPollInterval(int pollInterval) {
|
||||
this.pollInterval = pollInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if is cfg poll changes.
|
||||
*
|
||||
* @return the pollForChanges
|
||||
*/
|
||||
public boolean doPollChanges() {
|
||||
return pollForChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cfg poll changes.
|
||||
*
|
||||
* @param pollForChanges the pollForChanges to set
|
||||
*/
|
||||
public void setPollForChanges(boolean pollForChanges) {
|
||||
this.pollForChanges = pollForChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if is cfg keep conn open.
|
||||
*
|
||||
* @return the keepConActive
|
||||
*/
|
||||
public boolean isKeepConnActive() {
|
||||
return keepConActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cfg keep conn open.
|
||||
*
|
||||
* @param keepConActive the keepConActive to set
|
||||
*/
|
||||
public void setKeepConnActive(boolean keepConActive) {
|
||||
this.keepConActive = keepConActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cfg cmd wait.
|
||||
*
|
||||
* @return the cmdWaitInterval
|
||||
*/
|
||||
public int getCmdWaitInterval() {
|
||||
return cmdWaitInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cfg cmd wait.
|
||||
*
|
||||
* @param cmdWaitInterval the cmdWaitInterval to set
|
||||
*/
|
||||
public void setCmdWaitInterval(int cmdWaitInterval) {
|
||||
this.cmdWaitInterval = cmdWaitInterval;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* 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.tivo.internal.service;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* TivoStatusData class stores the data from the last status query from the TiVo and any other errors / status
|
||||
* codes.
|
||||
*
|
||||
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||
* @author Andrew Black (AndyXMB) - minor updates, removal of unused functions.
|
||||
* @author Michael Lobstein - Updated for OH3
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class TivoStatusData {
|
||||
private boolean cmdOk = false;
|
||||
private Date time = new Date();
|
||||
private int channelNum = -1;
|
||||
private int subChannelNum = -1;
|
||||
private boolean isRecording = false;
|
||||
private String msg = "NO STATUS QUERIED YET";
|
||||
private boolean pubToUI = true;
|
||||
private ConnectionStatus connectionStatus = ConnectionStatus.INIT;
|
||||
|
||||
public TivoStatusData() {
|
||||
}
|
||||
|
||||
/*
|
||||
* {@link TivoStatusData} class stores the data from the last status query from the TiVo and any other errors /
|
||||
* status codes.
|
||||
*
|
||||
* @param cmdOk boolean true = last command executed correctly, false = last command failed with error message
|
||||
*
|
||||
* @param channelNum int = channel number, -1 indicates no channel received. Valid channel range 1-9999.
|
||||
*
|
||||
* @param subChannelNum int = sub-channel number, -1 indicates no sub-channel received. Valid sub-channel range
|
||||
* 1-9999.
|
||||
*
|
||||
* @param isRecording boolean true = indicates the current channel is recording
|
||||
*
|
||||
* @param msg string status message from the TiVo socket
|
||||
*
|
||||
* @param pubToUI boolean true = this status needs to be published to the UI / Thing, false = do not publish (or it
|
||||
* already has been)
|
||||
*
|
||||
* @param connectionStatus ConnectionStatus enum UNKNOWN= test not run/default, OFFLINE = offline, STANDBY = TiVo is
|
||||
* in standby, ONLINE = Online
|
||||
*
|
||||
*/
|
||||
public TivoStatusData(boolean cmdOk, int channelNum, int subChannelNum, boolean isRecording, String msg,
|
||||
boolean pubToUI, ConnectionStatus connectionStatus) {
|
||||
this.cmdOk = cmdOk;
|
||||
this.time = new Date();
|
||||
this.channelNum = channelNum;
|
||||
this.subChannelNum = subChannelNum;
|
||||
this.isRecording = isRecording;
|
||||
this.msg = msg;
|
||||
this.pubToUI = pubToUI;
|
||||
this.connectionStatus = connectionStatus;
|
||||
}
|
||||
|
||||
public enum ConnectionStatus {
|
||||
INIT,
|
||||
UNKNOWN,
|
||||
OFFLINE,
|
||||
STANDBY,
|
||||
ONLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link TivoStatusData} class stores the data from the last status query from the TiVo and any other errors /
|
||||
* status codes.
|
||||
*
|
||||
* @param cmdOk boolean true = last command executed correctly, false = last command failed with error message
|
||||
* @param channelNum int = channel number, -1 indicates no channel received. Valid channel range 1-9999.
|
||||
* @param msg string status message from the TiVo socket
|
||||
* @param pubToUI boolean true = this status needs to be published to the UI, false = do not publish (or it
|
||||
* already has been)
|
||||
* @param connectionStatus enum UNKNOWN= test not run/default, OFFLINE = offline, STANDBY = TiVo is in standby
|
||||
* , ONLINE = Online
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TivoStatusData [cmdOk=" + cmdOk + ", time=" + time + ", channelNum=" + channelNum + ", subChannelNum="
|
||||
+ subChannelNum + ", msg=" + msg + ", pubToUI=" + pubToUI + ", connectionStatus=" + connectionStatus
|
||||
+ "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link isCmdOK} indicates if the last command executed correctly.
|
||||
*
|
||||
* @return cmdOk boolean true = executed correctly, false = last command failed with error message
|
||||
*/
|
||||
public boolean isCmdOk() {
|
||||
return cmdOk;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link} sets the value indicating if the last command executed correctly.
|
||||
*
|
||||
* @param cmdOk boolean true = executed correctly, false = last command failed with error message
|
||||
*/
|
||||
public void setCmdOk(boolean cmdOk) {
|
||||
this.cmdOk = cmdOk;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link getChannelNum} gets the channel number, -1 indicates no channel received. Valid channel range 1-9999.
|
||||
*
|
||||
* @return the channel number
|
||||
*/
|
||||
public int getChannelNum() {
|
||||
return channelNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link setChannelNum} sets the channel number, -1 indicates no channel received. Valid channel range 1-9999.
|
||||
*
|
||||
* @param channelNum the new channel number
|
||||
*/
|
||||
public void setChannelNum(int channelNum) {
|
||||
this.channelNum = channelNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link getSubChannelNum} gets the sub channel number, -1 indicates no sub channel received. Valid channel range
|
||||
* 1-9999.
|
||||
*
|
||||
* @return the sub channel number
|
||||
*/
|
||||
public int getSubChannelNum() {
|
||||
return subChannelNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link setSubChannelNum} sets the sub channel number, -1 indicates no sub channel received. Valid channel range
|
||||
* 1-9999.
|
||||
*
|
||||
* @param subChannelNum the new sub channel number
|
||||
*/
|
||||
public void setSubChannelNum(int subChannelNum) {
|
||||
this.subChannelNum = subChannelNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link setRecording} set to true if current channel is recording
|
||||
*
|
||||
* @param isRecording true = current channel is recording
|
||||
*/
|
||||
public void setRecording(boolean isRecording) {
|
||||
this.isRecording = isRecording;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link getPubToUI} get status indicating if current channel is recording
|
||||
*
|
||||
* @return isRecording true = current channel is recording
|
||||
*/
|
||||
public boolean isRecording() {
|
||||
return isRecording;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link getMsg} gets status message string
|
||||
*
|
||||
* @return msg string
|
||||
*/
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link setPubToUI} set to true if this status needs to be published to the channel / UI / Thing, false = do not
|
||||
* publish (or it already has been).
|
||||
*
|
||||
* @param pubToUI true = publish status to the channel objects
|
||||
*/
|
||||
public void setPubToUI(boolean pubToUI) {
|
||||
this.pubToUI = pubToUI;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link getPubToUI} get status indicating that the event needs to be published to the channel / UI / Thing, false
|
||||
* = do not publish (or it already has been).
|
||||
*
|
||||
* @return pubToUI true = publish status to the channel objects
|
||||
*/
|
||||
public boolean getPubToUI() {
|
||||
return pubToUI;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link setConnectionStatus} indicates the state of the connection / connection tests. Drives online/offline state
|
||||
* of the
|
||||
* Thing and connection process.
|
||||
*
|
||||
* @param connectionStatus enum UNKNOWN= test not run/default, OFFLINE = offline, STANDBY = TiVo is in standby,
|
||||
* ONLINE = Online
|
||||
*/
|
||||
public void setConnectionStatus(ConnectionStatus connectionStatus) {
|
||||
this.connectionStatus = connectionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link getConnectionStatus} returns the state of the connection / connection tests. Drives online/offline state
|
||||
* of the
|
||||
* Thing and connection process.
|
||||
*
|
||||
* @return ConnectionStatus enum UNKNOWN= test not run/default, OFFLINE = offline, STANDBY = TiVo is in standby,
|
||||
* ONLINE = Online
|
||||
*/
|
||||
public ConnectionStatus getConnectionStatus() {
|
||||
return connectionStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
/**
|
||||
* 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.tivo.internal.service;
|
||||
|
||||
import static org.openhab.binding.tivo.internal.TiVoBindingConstants.CONFIG_SOCKET_TIMEOUT_MS;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tivo.internal.handler.TiVoHandler;
|
||||
import org.openhab.binding.tivo.internal.service.TivoStatusData.ConnectionStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* TivoStatusProvider class to maintain a connection out to the Tivo, monitor and process status messages returned..
|
||||
*
|
||||
* @author Jayson Kubilis - Initial contribution
|
||||
* @author Andrew Black - Updates / compilation corrections
|
||||
* @author Michael Lobstein - Updated for OH3
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class TivoStatusProvider {
|
||||
private static final Pattern TIVO_STATUS_PATTERN = Pattern.compile("^CH_STATUS (\\d{4}) (?:(\\d{4}))?");
|
||||
private static final int TIMEOUT_SEC = 3000;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TivoStatusProvider.class);
|
||||
private @Nullable Socket tivoSocket = null;
|
||||
private @Nullable PrintStream streamWriter = null;
|
||||
private @Nullable StreamReader streamReader = null;
|
||||
private @Nullable TiVoHandler tivoHandler = null;
|
||||
private TivoStatusData tivoStatusData = new TivoStatusData();
|
||||
private TivoConfigData tivoConfigData = new TivoConfigData();
|
||||
private final String thingUid;
|
||||
|
||||
/**
|
||||
* Instantiates a new TivoConfigStatusProvider.
|
||||
*
|
||||
* @param tivoConfigData {@link TivoConfigData} configuration data for the specific thing.
|
||||
* @param tivoStatusData {@link TivoStatusData} status data for the specific thing.
|
||||
* @param tivoHandler {@link TivoHandler} parent handler object for the TivoConfigStatusProvider.
|
||||
*
|
||||
*/
|
||||
|
||||
public TivoStatusProvider(TivoConfigData tivoConfigData, TiVoHandler tivoHandler) {
|
||||
this.tivoStatusData = new TivoStatusData(false, -1, -1, false, "INITIALISING", false, ConnectionStatus.UNKNOWN);
|
||||
this.tivoConfigData = tivoConfigData;
|
||||
this.tivoHandler = tivoHandler;
|
||||
this.thingUid = tivoHandler.getThing().getUID().getAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link statusRefresh} initiates a connection to the TiVo. When a new connection is made and the TiVo is online,
|
||||
* the current channel is always returned. The connection is then closed (allows the socket to be used by other
|
||||
* devices).
|
||||
*
|
||||
* @return {@link TivoStatusData} object
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public void statusRefresh() throws InterruptedException {
|
||||
if (tivoStatusData.getConnectionStatus() != ConnectionStatus.INIT) {
|
||||
logger.debug(" statusRefresh '{}' - EXISTING status data - '{}'", tivoConfigData.getCfgIdentifier(),
|
||||
tivoStatusData.toString());
|
||||
}
|
||||
connTivoConnect();
|
||||
doNappTime();
|
||||
if (!tivoConfigData.isKeepConnActive()) {
|
||||
connTivoDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link cmdTivoSend} sends a command to the Tivo.
|
||||
*
|
||||
* @param tivoCommand the complete command string (KEYWORD + PARAMETERS e.g. SETCH 102) to send.
|
||||
* @return {@link TivoStatusData} status data object, contains the result of the command.
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public @Nullable TivoStatusData cmdTivoSend(String tivoCommand) throws InterruptedException {
|
||||
boolean connected = connTivoConnect();
|
||||
PrintStream streamWriter = this.streamWriter;
|
||||
|
||||
if (!connected || streamWriter == null) {
|
||||
return new TivoStatusData(false, -1, -1, false, "CONNECTION FAILED", false, ConnectionStatus.OFFLINE);
|
||||
}
|
||||
logger.debug("TiVo '{}' - sending command: '{}'", tivoConfigData.getCfgIdentifier(), tivoCommand);
|
||||
int repeatCount = 1;
|
||||
// Handle special keyboard "repeat" commands
|
||||
if (tivoCommand.contains("*")) {
|
||||
repeatCount = Integer.parseInt(tivoCommand.substring(tivoCommand.indexOf("*") + 1));
|
||||
tivoCommand = tivoCommand.substring(0, tivoCommand.indexOf("*"));
|
||||
logger.debug("TiVo '{}' - repeating command: '{}' for '{}' times", tivoConfigData.getCfgIdentifier(),
|
||||
tivoCommand, repeatCount);
|
||||
}
|
||||
for (int i = 1; i <= repeatCount; i++) {
|
||||
// Send the command
|
||||
streamWriter.println(tivoCommand.toString() + "\r");
|
||||
if (streamWriter.checkError()) {
|
||||
logger.debug("TiVo '{}' - called cmdTivoSend and encountered an IO error",
|
||||
tivoConfigData.getCfgIdentifier());
|
||||
tivoStatusData = new TivoStatusData(false, -1, -1, false, "CONNECTION FAILED", false,
|
||||
ConnectionStatus.OFFLINE);
|
||||
connTivoReconnect();
|
||||
}
|
||||
}
|
||||
return tivoStatusData;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link statusParse} processes the {@link TivoStatusData} status message returned from the TiVo.
|
||||
*
|
||||
* For channel status messages form 'CH_STATUS channel reason' or 'CH_STATUS channel sub-channel reason' calls
|
||||
* {@link getParsedChannel} and returns the channel number (if a match is found in a valid formatted message).
|
||||
*
|
||||
* @param rawStatus string representing the message text returned by the TiVo
|
||||
* @return TivoStatusData object conditionally populated based upon the raw status message
|
||||
*/
|
||||
private TivoStatusData statusParse(String rawStatus) {
|
||||
logger.debug(" statusParse '{}' - running on string '{}'", tivoConfigData.getCfgIdentifier(), rawStatus);
|
||||
|
||||
if (rawStatus.contentEquals("COMMAND_TIMEOUT")) {
|
||||
// Ignore COMMAND_TIMEOUT, they occur a few seconds after each successful command, just return existing
|
||||
// status again
|
||||
return this.tivoStatusData;
|
||||
} else {
|
||||
switch (rawStatus) {
|
||||
case "":
|
||||
return new TivoStatusData(false, -1, -1, false, "NO_STATUS_DATA_RETURNED", false,
|
||||
tivoStatusData.getConnectionStatus());
|
||||
case "LIVETV_READY":
|
||||
return new TivoStatusData(true, -1, -1, false, "LIVETV_READY", true, ConnectionStatus.ONLINE);
|
||||
case "CH_FAILED NO_LIVE":
|
||||
return new TivoStatusData(false, -1, -1, false, "CH_FAILED NO_LIVE", true,
|
||||
ConnectionStatus.STANDBY);
|
||||
case "CH_FAILED RECORDING":
|
||||
case "CH_FAILED MISSING_CHANNEL":
|
||||
case "CH_FAILED MALFORMED_CHANNEL":
|
||||
case "CH_FAILED INVALID_CHANNEL":
|
||||
return new TivoStatusData(false, -1, -1, false, rawStatus, true, ConnectionStatus.ONLINE);
|
||||
case "INVALID_COMMAND":
|
||||
return new TivoStatusData(false, -1, -1, false, "INVALID_COMMAND", false, ConnectionStatus.ONLINE);
|
||||
case "CONNECTION_RETRIES_EXHAUSTED":
|
||||
return new TivoStatusData(false, -1, -1, false, "CONNECTION_RETRIES_EXHAUSTED", true,
|
||||
ConnectionStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
// Only other documented status is in the form 'CH_STATUS channel reason' or
|
||||
// 'CH_STATUS channel sub-channel reason'
|
||||
Matcher matcher = TIVO_STATUS_PATTERN.matcher(rawStatus);
|
||||
int chNum = -1; // -1 used globally to indicate channel number error
|
||||
int subChNum = -1;
|
||||
boolean isRecording = false;
|
||||
|
||||
if (matcher.find()) {
|
||||
logger.debug(" statusParse '{}' - groups '{}' with group count of '{}'", tivoConfigData.getCfgIdentifier(),
|
||||
matcher.group(), matcher.groupCount());
|
||||
if (matcher.groupCount() == 1 || matcher.groupCount() == 2) {
|
||||
chNum = Integer.parseInt(matcher.group(1).trim());
|
||||
logger.debug(" statusParse '{}' - parsed channel '{}'", tivoConfigData.getCfgIdentifier(), chNum);
|
||||
}
|
||||
if (matcher.groupCount() == 2) {
|
||||
subChNum = Integer.parseInt(matcher.group(2).trim());
|
||||
logger.debug(" statusParse '{}' - parsed sub-channel '{}'", tivoConfigData.getCfgIdentifier(),
|
||||
subChNum);
|
||||
}
|
||||
|
||||
if (rawStatus.contains("RECORDING")) {
|
||||
isRecording = true;
|
||||
}
|
||||
|
||||
rawStatus = rawStatus.replace(" REMOTE", "");
|
||||
rawStatus = rawStatus.replace(" LOCAL", "");
|
||||
return new TivoStatusData(true, chNum, subChNum, isRecording, rawStatus, true, ConnectionStatus.ONLINE);
|
||||
}
|
||||
logger.warn(" TiVo '{}' - Unhandled/unexpected status message: '{}'", tivoConfigData.getCfgIdentifier(),
|
||||
rawStatus);
|
||||
return new TivoStatusData(false, -1, -1, false, rawStatus, false, tivoStatusData.getConnectionStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link connIsConnected} returns the connection state of the Socket, streamWriter and streamReader objects.
|
||||
*
|
||||
* @return true = connection exists and all objects look OK, false = connection does not exist or a problem has
|
||||
* occurred
|
||||
*
|
||||
*/
|
||||
private boolean connIsConnected() {
|
||||
Socket tivoSocket = this.tivoSocket;
|
||||
PrintStream streamWriter = this.streamWriter;
|
||||
|
||||
if (tivoSocket == null) {
|
||||
logger.debug(" connIsConnected '{}' - FALSE: tivoSocket=null", tivoConfigData.getCfgIdentifier());
|
||||
return false;
|
||||
} else if (!tivoSocket.isConnected()) {
|
||||
logger.debug(" connIsConnected '{}' - FALSE: tivoSocket.isConnected=false",
|
||||
tivoConfigData.getCfgIdentifier());
|
||||
return false;
|
||||
} else if (tivoSocket.isClosed()) {
|
||||
logger.debug(" connIsConnected '{}' - FALSE: tivoSocket.isClosed=true", tivoConfigData.getCfgIdentifier());
|
||||
return false;
|
||||
} else if (streamWriter == null) {
|
||||
logger.debug(" connIsConnected '{}' - FALSE: tivoIOSendCommand=null", tivoConfigData.getCfgIdentifier());
|
||||
return false;
|
||||
} else if (streamWriter.checkError()) {
|
||||
logger.debug(" connIsConnected '{}' - FALSE: tivoIOSendCommand.checkError()=true",
|
||||
tivoConfigData.getCfgIdentifier());
|
||||
return false;
|
||||
} else if (streamReader == null) {
|
||||
logger.debug(" connIsConnected '{}' - FALSE: streamReader=null", tivoConfigData.getCfgIdentifier());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link connTivoConnect} manages the creation / retry process of the socket connection.
|
||||
*
|
||||
* @return true = connected, false = not connected
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public boolean connTivoConnect() throws InterruptedException {
|
||||
for (int iL = 1; iL <= tivoConfigData.getNumRetry(); iL++) {
|
||||
logger.debug(" connTivoConnect '{}' - starting connection process '{}' of '{}'.",
|
||||
tivoConfigData.getCfgIdentifier(), iL, tivoConfigData.getNumRetry());
|
||||
|
||||
// Sort out the socket connection
|
||||
if (connSocketConnect()) {
|
||||
logger.debug(" connTivoConnect '{}' - Socket created / connection made.",
|
||||
tivoConfigData.getCfgIdentifier());
|
||||
StreamReader streamReader = this.streamReader;
|
||||
if (streamReader != null && streamReader.isAlive()) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
logger.debug(" connTivoConnect '{}' - Socket creation failed.", tivoConfigData.getCfgIdentifier());
|
||||
TiVoHandler tivoHandler = this.tivoHandler;
|
||||
if (tivoHandler != null) {
|
||||
tivoHandler.setStatusOffline();
|
||||
}
|
||||
}
|
||||
// Sleep and retry
|
||||
doNappTime();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link connTivoReconnect} disconnect and reconnect the socket connection to the TiVo.
|
||||
*
|
||||
* @return boolean true = connection succeeded, false = connection failed
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public boolean connTivoReconnect() throws InterruptedException {
|
||||
connTivoDisconnect();
|
||||
doNappTime();
|
||||
return connTivoConnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link connTivoDisconnect} cleanly closes the socket connection and dependent objects
|
||||
*
|
||||
*/
|
||||
public void connTivoDisconnect() throws InterruptedException {
|
||||
TiVoHandler tivoHandler = this.tivoHandler;
|
||||
StreamReader streamReader = this.streamReader;
|
||||
PrintStream streamWriter = this.streamWriter;
|
||||
Socket tivoSocket = this.tivoSocket;
|
||||
|
||||
logger.debug(" connTivoSocket '{}' - requested to disconnect/cleanup connection objects",
|
||||
tivoConfigData.getCfgIdentifier());
|
||||
|
||||
// if isCfgKeepConnOpen = false, don't set status to OFFLINE since the socket is closed after each command
|
||||
if (tivoHandler != null && tivoConfigData.isKeepConnActive()) {
|
||||
tivoHandler.setStatusOffline();
|
||||
}
|
||||
|
||||
if (streamWriter != null) {
|
||||
streamWriter.close();
|
||||
this.streamWriter = null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (tivoSocket != null) {
|
||||
tivoSocket.close();
|
||||
this.tivoSocket = null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug(" TiVo '{}' - I/O exception while disconnecting: '{}'. Connection closed.",
|
||||
tivoConfigData.getCfgIdentifier(), e.getMessage());
|
||||
}
|
||||
|
||||
if (streamReader != null) {
|
||||
streamReader.interrupt();
|
||||
streamReader.join(TIMEOUT_SEC);
|
||||
this.streamReader = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link connSocketConnect} opens a Socket connection to the TiVo. Creates a {@link StreamReader} (Input)
|
||||
* thread to read the responses from the TiVo and a PrintStream (Output) {@link cmdTivoSend}
|
||||
* to send commands to the device.
|
||||
*
|
||||
* @param pConnect true = make a new connection , false = close existing connection
|
||||
* @return boolean true = connection succeeded, false = connection failed
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private synchronized boolean connSocketConnect() throws InterruptedException {
|
||||
logger.debug(" connSocketConnect '{}' - attempting connection to host '{}', port '{}'",
|
||||
tivoConfigData.getCfgIdentifier(), tivoConfigData.getHost(), tivoConfigData.getTcpPort());
|
||||
|
||||
if (connIsConnected()) {
|
||||
logger.debug(" connSocketConnect '{}' - already connected to host '{}', port '{}'",
|
||||
tivoConfigData.getCfgIdentifier(), tivoConfigData.getHost(), tivoConfigData.getTcpPort());
|
||||
return true;
|
||||
} else {
|
||||
// something is wrong, so force a disconnect/clean up so we can try again
|
||||
connTivoDisconnect();
|
||||
}
|
||||
|
||||
try {
|
||||
Socket tivoSocket = new Socket(tivoConfigData.getHost(), tivoConfigData.getTcpPort());
|
||||
tivoSocket.setKeepAlive(true);
|
||||
tivoSocket.setSoTimeout(CONFIG_SOCKET_TIMEOUT_MS);
|
||||
tivoSocket.setReuseAddress(true);
|
||||
|
||||
if (tivoSocket.isConnected() && !tivoSocket.isClosed()) {
|
||||
if (streamWriter == null) {
|
||||
streamWriter = new PrintStream(tivoSocket.getOutputStream(), false);
|
||||
}
|
||||
if (this.streamReader == null) {
|
||||
StreamReader streamReader = new StreamReader(tivoSocket.getInputStream());
|
||||
streamReader.start();
|
||||
this.streamReader = streamReader;
|
||||
}
|
||||
this.tivoSocket = tivoSocket;
|
||||
} else {
|
||||
logger.debug(" connSocketConnect '{}' - socket creation failed to host '{}', port '{}'",
|
||||
tivoConfigData.getCfgIdentifier(), tivoConfigData.getHost(), tivoConfigData.getTcpPort());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (UnknownHostException e) {
|
||||
logger.debug(" TiVo '{}' - while connecting, unexpected host error: '{}'",
|
||||
tivoConfigData.getCfgIdentifier(), e.getMessage());
|
||||
} catch (IOException e) {
|
||||
if (tivoStatusData.getConnectionStatus() != ConnectionStatus.OFFLINE) {
|
||||
logger.debug(" TiVo '{}' - I/O exception while connecting: '{}'", tivoConfigData.getCfgIdentifier(),
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link doNappTime} sleeps for the period specified by the getCmdWaitInterval parameter. Primarily used to allow
|
||||
* the TiVo time to process responses after a command is issued.
|
||||
*
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public void doNappTime() throws InterruptedException {
|
||||
TimeUnit.MILLISECONDS.sleep(tivoConfigData.getCmdWaitInterval());
|
||||
}
|
||||
|
||||
public TivoStatusData getServiceStatus() {
|
||||
return tivoStatusData;
|
||||
}
|
||||
|
||||
public void setServiceStatus(TivoStatusData tivoStatusData) {
|
||||
this.tivoStatusData = tivoStatusData;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link StreamReader} data stream reader that reads the status data returned from the TiVo.
|
||||
*
|
||||
*/
|
||||
public class StreamReader extends Thread {
|
||||
private @Nullable BufferedReader bufferedReader = null;
|
||||
|
||||
/**
|
||||
* {@link StreamReader} construct a data stream reader that reads the status data returned from the TiVo via a
|
||||
* BufferedReader.
|
||||
*
|
||||
* @param inputStream socket input stream.
|
||||
* @throws IOException
|
||||
*/
|
||||
public StreamReader(InputStream inputStream) {
|
||||
this.setName("OH-binding-" + thingUid + "-" + tivoConfigData.getHost() + ":" + tivoConfigData.getTcpPort());
|
||||
this.bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
this.setDaemon(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
logger.debug("streamReader {} is running. ", tivoConfigData.getCfgIdentifier());
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
String receivedData = null;
|
||||
BufferedReader reader = bufferedReader;
|
||||
if (reader == null) {
|
||||
throw new IOException("streamReader failed: input stream is null");
|
||||
}
|
||||
|
||||
try {
|
||||
receivedData = reader.readLine();
|
||||
} catch (SocketTimeoutException e) {
|
||||
// Do nothing. Just allow the thread to check if it has to stop.
|
||||
}
|
||||
|
||||
if (receivedData != null) {
|
||||
logger.debug("TiVo {} data received: {}", tivoConfigData.getCfgIdentifier(), receivedData);
|
||||
TivoStatusData commandResult = statusParse(receivedData);
|
||||
TiVoHandler handler = tivoHandler;
|
||||
if (handler != null) {
|
||||
handler.updateTivoStatus(tivoStatusData, commandResult);
|
||||
}
|
||||
tivoStatusData = commandResult;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
closeBufferedReader();
|
||||
logger.debug("TiVo {} is disconnected. ", tivoConfigData.getCfgIdentifier(), e);
|
||||
}
|
||||
closeBufferedReader();
|
||||
logger.debug("streamReader {} is stopped. ", tivoConfigData.getCfgIdentifier());
|
||||
}
|
||||
|
||||
private void closeBufferedReader() {
|
||||
BufferedReader reader = bufferedReader;
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
this.bufferedReader = null;
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing bufferedReader: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="tivo" 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>TiVo DVR Binding</name>
|
||||
<description>Controls TiVo DVRs that support the TiVo TCP Control Protocol v1.1</description>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="tivo"
|
||||
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="sckt">
|
||||
<label>TiVo DVR</label>
|
||||
<description>Monitor and control your TiVo via DIRECT SOCKET commands leveraging the TIVO protocol 1.1 specification.
|
||||
The TiVo TCP Control Protocol is an ASCII-based command protocol for remote control of a TiVo DVR over a TCP local
|
||||
network connection. The commands allow control of channel changes, user interface navigation and allow the client to
|
||||
send simulated remote control button presses to the Digital Video Recorder.</description>
|
||||
<channels>
|
||||
<channel id="channelSet" typeId="channelSet"/>
|
||||
<channel id="channelForce" typeId="channelForce"/>
|
||||
<channel id="isRecording" typeId="isRecording"/>
|
||||
<channel id="menuTeleport" typeId="menuTeleport"/>
|
||||
<channel id="irCommand" typeId="irCommand"/>
|
||||
<channel id="kbdCommand" typeId="kbdCommand"/>
|
||||
<channel id="dvrStatus" typeId="dvrStatus"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>host</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="host" type="text" required="true">
|
||||
<label>Address</label>
|
||||
<description>The IP address or host name of your TiVo DVR</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="tcpPort" type="integer" max="65535" min="1" required="true">
|
||||
<default>31339</default>
|
||||
<label>TCP Port</label>
|
||||
<description>The
|
||||
TCP port number used to connect to the TiVo. <![CDATA[ <b> ]]>Default:
|
||||
31339<![CDATA[ </b> ]]></description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="numRetry" type="integer" max="20" min="0" required="true">
|
||||
<default>5</default>
|
||||
<label>Connection Retries</label>
|
||||
<description>The
|
||||
number of times to attempt reconnection to the TiVo box, if there is a connection failure.<![CDATA[ <b> ]]>Default:
|
||||
5<![CDATA[ </b> ]]></description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="keepConActive" type="boolean" required="true">
|
||||
<default>true</default>
|
||||
<label>Keep Connection Open</label>
|
||||
<description>Keep
|
||||
connection to the TiVo open. Recommended for monitoring the TiVo for changes in TV channels. Disable if other
|
||||
applications that use the Remote Control Protocol port will also be used e.g. mobile remote control applications.<![CDATA[ <b> ]]>Default:
|
||||
True (Enabled)<![CDATA[ </b> ]]></description>
|
||||
</parameter>
|
||||
<parameter name="pollForChanges" type="boolean" required="true">
|
||||
<default>true</default>
|
||||
<label>Poll for Channel Changes</label>
|
||||
<description>Check
|
||||
TiVo for channel changes. Enable if openHAB and a physical remote control (or other services use the Remote Control
|
||||
Protocol) will be used. <![CDATA[ <b> ]]>Default:
|
||||
True (Enabled)<![CDATA[ </b> ]]></description>
|
||||
</parameter>
|
||||
<parameter name="pollInterval" type="integer" max="65535" min="3" required="true">
|
||||
<default>10</default>
|
||||
<label>Polling Interval (Seconds)</label>
|
||||
<description>Number
|
||||
of seconds between polling jobs to update status information from the TiVo. <![CDATA[ <b> ]]>Default:
|
||||
10<![CDATA[ </b> ]]></description>
|
||||
</parameter>
|
||||
<parameter name="cmdWaitInterval" type="integer" max="500" min="0" required="true">
|
||||
<default>200</default>
|
||||
<label>Command Wait Interval (Milliseconds)</label>
|
||||
<description>Period
|
||||
to wait AFTER a command is sent to the TiVo in milliseconds, before checking that the command has completed. <![CDATA[ <b> ]]>Default:
|
||||
200<![CDATA[ </b> ]]></description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="channelSet">
|
||||
<item-type>Number</item-type>
|
||||
<label>Current Channel - Request</label>
|
||||
<description>Displays the current channel number. When changed (SETCH), tunes the DVR to the specified channel (unless
|
||||
a recording is in progress on all available tuners). The TiVo must be in Live TV mode for this command to work. Type:
|
||||
Number (1-9999) [Decimals allowed for OTA sub-channels], DisplayFormat: %d</description>
|
||||
<state min="1" max="9999" step="0.1" pattern="%d"/>
|
||||
</channel-type>
|
||||
<channel-type id="channelForce">
|
||||
<item-type>Number</item-type>
|
||||
<label>Current Channel - Forced</label>
|
||||
<description>Displays the current channel number. When changed (FORCECH), tunes the DVR to the specified channel,
|
||||
cancelling any recordings in progress if necessary i.e. all tuners are already in use / recording. The TiVo must be
|
||||
in Live TV mode for this command to work. Type: Number (1-9999) [Decimals allowed for OTA sub-channels],
|
||||
DisplayFormat: %d</description>
|
||||
<state min="1" max="9999" step="0.1" pattern="%d"></state>
|
||||
</channel-type>
|
||||
<channel-type id="isRecording">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Recording</label>
|
||||
<description>Indicates if the current channel is recording.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="menuTeleport">
|
||||
<item-type>String</item-type>
|
||||
<label>Change Menu Screen</label>
|
||||
<description>Change(TELEPORT) to one of the following TiVo menu screens: TIVO (Home), LIVE TV, GUIDE, NOW PLAYING (My
|
||||
Shows), NETFLIX. Type: String</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="TIVO">TIVO</option>
|
||||
<option value="LIVETV">LIVETV</option>
|
||||
<option value="GUIDE">GUIDE</option>
|
||||
<option value="NOWPLAYING">NOWPLAYING</option>
|
||||
<option value="NETFLIX">NETFLIX</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="irCommand">
|
||||
<item-type>String</item-type>
|
||||
<label>Remote Control Button</label>
|
||||
<description>Send a simulated button push (IRCODE) from the remote control to the TiVo. See Appendix A in document TCP
|
||||
Remote Protocol 1.1 for supported codes. Type: String</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
<channel-type id="kbdCommand">
|
||||
<item-type>String</item-type>
|
||||
<label>Keyboard Command</label>
|
||||
<description>Sends a code (KEYBOARD) corresponding to a keyboard key press to the TiVo e.g. A-Z. See Appendix A in
|
||||
document TCP Remote Protocol 1.1 for supported characters and special character codes. Type: String</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
<channel-type id="dvrStatus">
|
||||
<item-type>String</item-type>
|
||||
<label>TiVo Status</label>
|
||||
<description>Action return code / channel information returned by the TiVo. Type: String {ReadOnly)</description>
|
||||
<state readOnly="true" pattern="%s"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user