Add Roku TV channels (#11087)

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
This commit is contained in:
mlobstein
2021-08-11 05:06:40 -05:00
committed by GitHub
parent a93b56f2d3
commit 1321049973
8 changed files with 737 additions and 37 deletions

View File

@@ -15,6 +15,7 @@ package org.openhab.binding.roku.internal;
import java.util.Set;
import javax.measure.Unit;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Time;
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -52,9 +53,17 @@ public class RokuBindingConstants {
public static final String PLAY_MODE = "playMode";
public static final String TIME_ELAPSED = "timeElapsed";
public static final String TIME_TOTAL = "timeTotal";
public static final String ACTIVE_CHANNEL = "activeChannel";
public static final String SIGNAL_MODE = "signalMode";
public static final String SIGNAL_QUALITY = "signalQuality";
public static final String CHANNEL_NAME = "channelName";
public static final String PROGRAM_TITLE = "programTitle";
public static final String PROGRAM_DESCRIPTION = "programDescription";
public static final String PROGRAM_RATING = "programRating";
// Units of measurement of the data delivered by the API
public static final Unit<Time> API_SECONDS_UNIT = Units.SECOND;
public static final Unit<Dimensionless> API_PERCENT_UNIT = Units.PERCENT;
public static final String STOP = "stop";
public static final String CLOSE = "close";
@@ -63,4 +72,6 @@ public class RokuBindingConstants {
public static final String ROKU_HOME_ID = "-1";
public static final String ROKU_HOME_BUTTON = "Home";
public static final String NON_DIGIT_PATTERN = "[^\\d]";
public static final String TV_APP = "tvinput.dtv";
public static final String TV_INPUT = "tvinput";
}

View File

@@ -22,6 +22,8 @@ import org.openhab.binding.roku.internal.dto.ActiveApp;
import org.openhab.binding.roku.internal.dto.Apps;
import org.openhab.binding.roku.internal.dto.DeviceInfo;
import org.openhab.binding.roku.internal.dto.Player;
import org.openhab.binding.roku.internal.dto.TvChannel;
import org.openhab.binding.roku.internal.dto.TvChannels;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,6 +41,8 @@ public class JAXBUtils {
public static final @Nullable JAXBContext JAXBCONTEXT_APPS = initJAXBContextApps();
public static final @Nullable JAXBContext JAXBCONTEXT_DEVICE_INFO = initJAXBContextDeviceInfo();
public static final @Nullable JAXBContext JAXBCONTEXT_PLAYER = initJAXBContextPlayer();
public static final @Nullable JAXBContext JAXBCONTEXT_TVCHANNEL = initJAXBContextTvChannel();
public static final @Nullable JAXBContext JAXBCONTEXT_TVCHANNELS = initJAXBContextTvChannels();
public static final XMLInputFactory XMLINPUTFACTORY = initXMLInputFactory();
private static @Nullable JAXBContext initJAXBContextActiveApp() {
@@ -77,6 +81,24 @@ public class JAXBUtils {
}
}
private static @Nullable JAXBContext initJAXBContextTvChannel() {
try {
return JAXBContext.newInstance(TvChannel.class);
} catch (JAXBException e) {
LOGGER.error("Exception creating JAXBContext for TvChannel info: {}", e.getLocalizedMessage(), e);
return null;
}
}
private static @Nullable JAXBContext initJAXBContextTvChannels() {
try {
return JAXBContext.newInstance(TvChannels.class);
} catch (JAXBException e) {
LOGGER.error("Exception creating JAXBContext for TvChannels info: {}", e.getLocalizedMessage(), e);
return null;
}
}
private static XMLInputFactory initXMLInputFactory() {
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);

View File

@@ -32,6 +32,9 @@ import org.openhab.binding.roku.internal.dto.Apps;
import org.openhab.binding.roku.internal.dto.Apps.App;
import org.openhab.binding.roku.internal.dto.DeviceInfo;
import org.openhab.binding.roku.internal.dto.Player;
import org.openhab.binding.roku.internal.dto.TvChannel;
import org.openhab.binding.roku.internal.dto.TvChannels;
import org.openhab.binding.roku.internal.dto.TvChannels.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,10 +50,13 @@ public class RokuCommunicator {
private final String urlKeyPress;
private final String urlLaunchApp;
private final String urlLaunchTvChannel;
private final String urlQryDevice;
private final String urlQryActiveApp;
private final String urlQryApps;
private final String urlQryPlayer;
private final String urlQryActiveTvChannel;
private final String urlQryTvChannels;
public RokuCommunicator(HttpClient httpClient, String host, int port) {
this.httpClient = httpClient;
@@ -58,10 +64,13 @@ public class RokuCommunicator {
final String baseUrl = "http://" + host + ":" + port;
urlKeyPress = baseUrl + "/keypress/";
urlLaunchApp = baseUrl + "/launch/";
urlLaunchTvChannel = baseUrl + "/launch/tvinput.dtv?ch=";
urlQryDevice = baseUrl + "/query/device-info";
urlQryActiveApp = baseUrl + "/query/active-app";
urlQryApps = baseUrl + "/query/apps";
urlQryPlayer = baseUrl + "/query/media-player";
urlQryActiveTvChannel = baseUrl + "/query/tv-active-channel";
urlQryTvChannels = baseUrl + "/query/tv-channels";
}
/**
@@ -84,6 +93,16 @@ public class RokuCommunicator {
postCommand(urlLaunchApp + appId);
}
/**
* Send a TV channel change command to the Roku TV
*
* @param channelNumber The channel number of the channel to tune into, ie: 2.1
*
*/
public void launchTvChannel(String channelNumber) throws RokuHttpException {
postCommand(urlLaunchTvChannel + channelNumber);
}
/**
* Send a command to get device-info from the Roku and return a DeviceInfo object
*
@@ -188,6 +207,58 @@ public class RokuCommunicator {
}
}
/**
* Send a command to get tv-active-channel from the Roku TV and return a TvChannel object
*
* @return A TvChannel object populated with information about the current active TV Channel
* @throws RokuHttpException
*/
public TvChannel getActiveTvChannel() throws RokuHttpException {
try {
JAXBContext ctx = JAXBUtils.JAXBCONTEXT_TVCHANNEL;
if (ctx != null) {
Unmarshaller unmarshaller = ctx.createUnmarshaller();
if (unmarshaller != null) {
XMLStreamReader xsr = JAXBUtils.XMLINPUTFACTORY
.createXMLStreamReader(new StringReader(getCommand(urlQryActiveTvChannel)));
TvChannel tvChannelInfo = (TvChannel) unmarshaller.unmarshal(xsr);
if (tvChannelInfo != null) {
return tvChannelInfo;
}
}
}
throw new RokuHttpException("No TvChannel info model in response");
} catch (JAXBException | XMLStreamException e) {
throw new RokuHttpException("Exception creating TvChannel info Unmarshaller: " + e.getLocalizedMessage());
}
}
/**
* Send a command to get tv-channels from the Roku TV and return a list of Channel objects
*
* @return A List of Channel objects for all TV channels currently available on the Roku TV
* @throws RokuHttpException
*/
public List<Channel> getTvChannelList() throws RokuHttpException {
try {
JAXBContext ctx = JAXBUtils.JAXBCONTEXT_TVCHANNELS;
if (ctx != null) {
Unmarshaller unmarshaller = ctx.createUnmarshaller();
if (unmarshaller != null) {
XMLStreamReader xsr = JAXBUtils.XMLINPUTFACTORY
.createXMLStreamReader(new StringReader(getCommand(urlQryTvChannels)));
TvChannels tvChannels = (TvChannels) unmarshaller.unmarshal(xsr);
if (tvChannels != null) {
return tvChannels.getChannel();
}
}
}
throw new RokuHttpException("No TvChannels info model in response");
} catch (JAXBException | XMLStreamException e) {
throw new RokuHttpException("Exception creating TvChannel info Unmarshaller: " + e.getLocalizedMessage());
}
}
/**
* Sends a GET command to the Roku
*

View File

@@ -0,0 +1,309 @@
/**
* 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.roku.internal.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Maps the XML response from the Roku HTTP endpoint '/query/tv-active-channel' (Active TV channel information)
*
* @author Michael Lobstein - Initial contribution
*/
@NonNullByDefault
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "tv-channel")
public class TvChannel {
@XmlElement
private TvChannel.Channel channel = new Channel();
public TvChannel.Channel getChannel() {
return this.channel;
}
public void setChannel(TvChannel.Channel value) {
this.channel = value;
}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Channel {
@XmlElement(name = "number")
private String number = "";
@XmlElement(name = "name")
private String name = "";
@XmlElement(name = "type")
private String type = "";
@XmlElement(name = "user-hidden")
private boolean userHidden = false;
@XmlElement(name = "user-favorite")
private boolean userFavorite = false;
@XmlElement(name = "physical-channel")
private int physicalChannel = 0;
@XmlElement(name = "physical-frequency")
private int physicalFrequency = 0;
@XmlElement(name = "active-input")
private boolean activeInput = false;
@XmlElement(name = "signal-state")
private String signalState = "";
@XmlElement(name = "signal-mode")
private String signalMode = "";
@XmlElement(name = "signal-quality")
private int signalQuality = 0;
@XmlElement(name = "signal-strength")
private int signalStrength = 0;
@XmlElement(name = "signal-stalled-pts-cnt")
private int signalStalledPtsCnt = 0;
@XmlElement(name = "program-title")
private String programTitle = "";
@XmlElement(name = "program-description")
private String programDescription = "";
@XmlElement(name = "program-ratings")
private String programRatings = "";
@XmlElement(name = "program-is-blocked")
private boolean programIsBlocked = false;
@XmlElement(name = "program-analog-audio")
private String programAnalogAudio = "";
@XmlElement(name = "program-digital-audio")
private String programDigitalAudio = "";
@XmlElement(name = "program-audio-languages")
private String programAudioLanguages = "";
@XmlElement(name = "program-audio-formats")
private String programAudioFormats = "";
@XmlElement(name = "program-audio-language")
private String programAudioLanguage = "";
@XmlElement(name = "program-audio-format")
private String programAudioFormat = "";
@XmlElement(name = "program-has-cc")
private boolean programHasCc = false;
public String getNumber() {
return number;
}
public void setNumber(String value) {
this.number = value;
}
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getType() {
return type;
}
public void setType(String value) {
this.type = value;
}
public boolean isUserHidden() {
return userHidden;
}
public void setUserHidden(boolean value) {
this.userHidden = value;
}
public boolean isUserFavorite() {
return userFavorite;
}
public void setUserFavorite(boolean value) {
this.userFavorite = value;
}
public int getPhysicalChannel() {
return physicalChannel;
}
public void setPhysicalChannel(int value) {
this.physicalChannel = value;
}
public int getPhysicalFrequency() {
return physicalFrequency;
}
public void setPhysicalFrequency(int value) {
this.physicalFrequency = value;
}
public boolean isActiveInput() {
return activeInput;
}
public void setActiveInput(boolean value) {
this.activeInput = value;
}
public String getSignalState() {
return signalState;
}
public void setSignalState(String value) {
this.signalState = value;
}
public String getSignalMode() {
return signalMode;
}
public void setSignalMode(String value) {
this.signalMode = value;
}
public int getSignalQuality() {
return signalQuality;
}
public void setSignalQuality(int value) {
this.signalQuality = value;
}
public int getSignalStrength() {
return signalStrength;
}
public void setSignalStrength(int value) {
this.signalStrength = value;
}
public int getSignalStalledPtsCnt() {
return signalStalledPtsCnt;
}
public void setSignalStalledPtsCnt(int value) {
this.signalStalledPtsCnt = value;
}
public String getProgramTitle() {
return programTitle;
}
public void setProgramTitle(String value) {
this.programTitle = value;
}
public String getProgramDescription() {
return programDescription;
}
public void setProgramDescription(String value) {
this.programDescription = value;
}
public String getProgramRatings() {
return programRatings;
}
public void setProgramRatings(String value) {
this.programRatings = value;
}
public boolean isProgramIsBlocked() {
return programIsBlocked;
}
public void setProgramIsBlocked(boolean value) {
this.programIsBlocked = value;
}
public String getProgramAnalogAudio() {
return programAnalogAudio;
}
public void setProgramAnalogAudio(String value) {
this.programAnalogAudio = value;
}
public String getProgramDigitalAudio() {
return programDigitalAudio;
}
public void setProgramDigitalAudio(String value) {
this.programDigitalAudio = value;
}
public String getProgramAudioLanguages() {
return programAudioLanguages;
}
public void setProgramAudioLanguages(String value) {
this.programAudioLanguages = value;
}
public String getProgramAudioFormats() {
return programAudioFormats;
}
public void setProgramAudioFormats(String value) {
this.programAudioFormats = value;
}
public String getProgramAudioLanguage() {
return programAudioLanguage;
}
public void setProgramAudioLanguage(String value) {
this.programAudioLanguage = value;
}
public String getProgramAudioFormat() {
return programAudioFormat;
}
public void setProgramAudioFormat(String value) {
this.programAudioFormat = value;
}
public boolean isProgramHasCc() {
return programHasCc;
}
public void setProgramHasCc(boolean value) {
this.programHasCc = value;
}
}
}

View File

@@ -0,0 +1,121 @@
/**
* 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.roku.internal.dto;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Maps the XML response from the Roku HTTP endpoint '/query/tv-channels' (List of available TV channels)
*
* @author Michael Lobstein - Initial contribution
*/
@NonNullByDefault
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "tv-channels")
public class TvChannels {
@XmlElement
private List<TvChannels.Channel> channel = new ArrayList<TvChannels.Channel>();
public List<TvChannels.Channel> getChannel() {
return this.channel;
}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Channel {
@XmlElement(name = "number")
private String number = "";
@XmlElement(name = "name")
private String name = "";
@XmlElement(name = "type")
private String type = "";
@XmlElement(name = "user-hidden")
private boolean userHidden = false;
@XmlElement(name = "user-favorite")
private boolean userFavorite = false;
@XmlElement(name = "physical-channel")
private int physicalChannel = 0;
@XmlElement(name = "physical-frequency")
private int physicalFrequency = 0;
public String getNumber() {
return number;
}
public void setNumber(String value) {
this.number = value;
}
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getType() {
return type;
}
public void setType(String value) {
this.type = value;
}
public boolean isUserHidden() {
return userHidden;
}
public void setUserHidden(boolean value) {
this.userHidden = value;
}
public boolean isUserFavorite() {
return userFavorite;
}
public void setUserFavorite(boolean value) {
this.userFavorite = value;
}
public int getPhysicalChannel() {
return physicalChannel;
}
public void setPhysicalChannel(int value) {
this.physicalChannel = value;
}
public int getPhysicalFrequency() {
return physicalFrequency;
}
public void setPhysicalFrequency(int value) {
this.physicalFrequency = value;
}
}
}

View File

@@ -26,16 +26,18 @@ import org.openhab.binding.roku.internal.RokuConfiguration;
import org.openhab.binding.roku.internal.RokuHttpException;
import org.openhab.binding.roku.internal.RokuStateDescriptionOptionProvider;
import org.openhab.binding.roku.internal.communication.RokuCommunicator;
import org.openhab.binding.roku.internal.dto.ActiveApp;
import org.openhab.binding.roku.internal.dto.Apps.App;
import org.openhab.binding.roku.internal.dto.DeviceInfo;
import org.openhab.binding.roku.internal.dto.Player;
import org.openhab.binding.roku.internal.dto.TvChannel;
import org.openhab.binding.roku.internal.dto.TvChannels.Channel;
import org.openhab.core.library.types.QuantityType;
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.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
@@ -61,9 +63,11 @@ public class RokuHandler extends BaseThingHandler {
private @Nullable ScheduledFuture<?> refreshJob;
private @Nullable ScheduledFuture<?> appListJob;
private ThingTypeUID thingTypeUID = THING_TYPE_ROKU_PLAYER;
private RokuCommunicator communicator;
private DeviceInfo deviceInfo = new DeviceInfo();
private int refreshInterval = DEFAULT_REFRESH_PERIOD_SEC;
private boolean tvActive = false;
private Object sequenceLock = new Object();
@@ -79,6 +83,7 @@ public class RokuHandler extends BaseThingHandler {
public void initialize() {
logger.debug("Initializing Roku handler");
RokuConfiguration config = getConfigAs(RokuConfiguration.class);
this.thingTypeUID = this.getThing().getThingTypeUID();
final @Nullable String host = config.hostName;
@@ -127,37 +132,76 @@ public class RokuHandler extends BaseThingHandler {
*/
private void refreshPlayerState() {
synchronized (sequenceLock) {
String activeAppId = ROKU_HOME_ID;
try {
ActiveApp activeApp = communicator.getActiveApp();
updateState(ACTIVE_APP, new StringType(activeApp.getApp().getId()));
activeAppId = communicator.getActiveApp().getApp().getId();
updateState(ACTIVE_APP, new StringType(activeAppId));
if (TV_APP.equals(activeAppId)) {
tvActive = true;
} else {
if (tvActive) {
updateState(SIGNAL_MODE, UnDefType.UNDEF);
updateState(SIGNAL_QUALITY, UnDefType.UNDEF);
updateState(CHANNEL_NAME, UnDefType.UNDEF);
updateState(PROGRAM_TITLE, UnDefType.UNDEF);
updateState(PROGRAM_DESCRIPTION, UnDefType.UNDEF);
updateState(PROGRAM_RATING, UnDefType.UNDEF);
}
tvActive = false;
}
updateStatus(ThingStatus.ONLINE);
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku active-app info. Exception: {}", e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
try {
Player playerInfo = communicator.getPlayerInfo();
// When nothing playing, 'close' is reported, replace with 'stop'
updateState(PLAY_MODE, new StringType(playerInfo.getState().replaceAll(CLOSE, STOP)));
// On the home app and when using the TV or TV inputs, do not update the play mode or time channels
if (!ROKU_HOME_ID.equals(activeAppId) && !activeAppId.contains(TV_INPUT)) {
try {
Player playerInfo = communicator.getPlayerInfo();
// When nothing playing, 'close' is reported, replace with 'stop'
updateState(PLAY_MODE, new StringType(playerInfo.getState().replaceAll(CLOSE, STOP)));
// Remove non-numeric from string, ie: ' ms'
String position = playerInfo.getPosition().replaceAll(NON_DIGIT_PATTERN, EMPTY);
if (!EMPTY.equals(position)) {
updateState(TIME_ELAPSED, new QuantityType<>(Integer.parseInt(position) / 1000, API_SECONDS_UNIT));
} else {
updateState(TIME_ELAPSED, UnDefType.UNDEF);
}
// Remove non-numeric from string, ie: ' ms'
String position = playerInfo.getPosition().replaceAll(NON_DIGIT_PATTERN, EMPTY);
if (!EMPTY.equals(position)) {
updateState(TIME_ELAPSED,
new QuantityType<>(Integer.parseInt(position) / 1000, API_SECONDS_UNIT));
} else {
updateState(TIME_ELAPSED, UnDefType.UNDEF);
}
String duration = playerInfo.getDuration().replaceAll(NON_DIGIT_PATTERN, EMPTY);
if (!EMPTY.equals(duration)) {
updateState(TIME_TOTAL, new QuantityType<>(Integer.parseInt(duration) / 1000, API_SECONDS_UNIT));
} else {
updateState(TIME_TOTAL, UnDefType.UNDEF);
String duration = playerInfo.getDuration().replaceAll(NON_DIGIT_PATTERN, EMPTY);
if (!EMPTY.equals(duration)) {
updateState(TIME_TOTAL,
new QuantityType<>(Integer.parseInt(duration) / 1000, API_SECONDS_UNIT));
} else {
updateState(TIME_TOTAL, UnDefType.UNDEF);
}
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku media-player info. Exception: {}", e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
} else {
updateState(PLAY_MODE, UnDefType.UNDEF);
updateState(TIME_ELAPSED, UnDefType.UNDEF);
updateState(TIME_TOTAL, UnDefType.UNDEF);
}
if (thingTypeUID.equals(THING_TYPE_ROKU_TV) && tvActive) {
try {
TvChannel tvChannel = communicator.getActiveTvChannel();
updateState(ACTIVE_CHANNEL, new StringType(tvChannel.getChannel().getNumber()));
updateState(SIGNAL_MODE, new StringType(tvChannel.getChannel().getSignalMode()));
updateState(SIGNAL_QUALITY,
new QuantityType<>(tvChannel.getChannel().getSignalQuality(), API_PERCENT_UNIT));
updateState(CHANNEL_NAME, new StringType(tvChannel.getChannel().getName()));
updateState(PROGRAM_TITLE, new StringType(tvChannel.getChannel().getProgramTitle()));
updateState(PROGRAM_DESCRIPTION, new StringType(tvChannel.getChannel().getProgramDescription()));
updateState(PROGRAM_RATING, new StringType(tvChannel.getChannel().getProgramRatings()));
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku tv-active-channel info. Exception: {}", e.getMessage(), e);
}
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku media-player info. Exception: {}", e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
}
@@ -194,6 +238,26 @@ public class RokuHandler extends BaseThingHandler {
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku installed app-list. Exception: {}", e.getMessage(), e);
}
if (thingTypeUID.equals(THING_TYPE_ROKU_TV)) {
try {
List<Channel> channelsList = communicator.getTvChannelList();
List<StateOption> channelListOptions = new ArrayList<>();
channelsList.forEach(channel -> {
if (!channel.isUserHidden()) {
channelListOptions.add(new StateOption(channel.getNumber(),
channel.getNumber() + " - " + channel.getName()));
}
});
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), ACTIVE_CHANNEL),
channelListOptions);
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku tv-channels. Exception: {}", e.getMessage(), e);
}
}
}
}
@@ -240,6 +304,16 @@ public class RokuHandler extends BaseThingHandler {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
} else if (channelUID.getId().equals(ACTIVE_CHANNEL)) {
synchronized (sequenceLock) {
try {
communicator.launchTvChannel(command.toString());
} catch (RokuHttpException e) {
logger.debug("Unable to change channel on Roku TV, channelNumber: {}, Exception: {}", command,
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
} else {
logger.debug("Unsupported command: {}", command);
}

View File

@@ -46,6 +46,13 @@
<channel id="playMode" typeId="playMode"/>
<channel id="timeElapsed" typeId="timeElapsed"/>
<channel id="timeTotal" typeId="timeTotal"/>
<channel id="activeChannel" typeId="activeChannel"/>
<channel id="signalMode" typeId="signalMode"/>
<channel id="signalQuality" typeId="signalQuality"/>
<channel id="channelName" typeId="channelName"/>
<channel id="programTitle" typeId="programTitle"/>
<channel id="programDescription" typeId="programDescription"/>
<channel id="programRating" typeId="programRating"/>
</channels>
<properties>
@@ -153,4 +160,52 @@
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="activeChannel">
<item-type>String</item-type>
<label>Active Channel</label>
<description>The TV Channel Currently Selected on the Roku TV</description>
</channel-type>
<channel-type id="signalMode">
<item-type>String</item-type>
<label>Signal Mode</label>
<description>The Signal Type of the Current TV Channel, ie: 1080i</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="signalQuality">
<item-type>Number:Dimensionless</item-type>
<label>Signal Quality</label>
<description>The Signal Quality of the Current TV Channel</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="channelName">
<item-type>String</item-type>
<label>Channel Name</label>
<description>The Name of the Channel Currently Selected</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="programTitle">
<item-type>String</item-type>
<label>Program Title</label>
<description>The Name of the Current TV Program</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="programDescription">
<item-type>String</item-type>
<label>Program Description</label>
<description>The Description of the Current TV Program</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="programRating">
<item-type>String</item-type>
<label>Program Rating</label>
<description>The TV Parental Guideline Rating of the Current TV Program</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>