Skip to content

Commit

Permalink
Add Roku TV channels (openhab#11087)
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
  • Loading branch information
mlobstein authored and thinkingstone committed Nov 7, 2021
1 parent ef1afcb commit 1c03d9d
Show file tree
Hide file tree
Showing 8 changed files with 737 additions and 37 deletions.
67 changes: 52 additions & 15 deletions bundles/org.openhab.binding.roku/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The Roku device must support the Roku ECP protocol REST API.

There are two supported thing types, which represent either a standalone Roku device or a Roku TV.
A supported Roku streaming media player or streaming stick uses the `roku_player` id and a supported Roku TV uses the `roku_tv` id.
The binding functionality is the same for both types, but the Roku TV type adds additional button commands to the button channel dropdown.
The Roku TV type adds additional channels and button commands to the button channel dropdown for TV specific functionality.
Multiple Things can be added if more than one Roku is to be controlled.

## Discovery
Expand All @@ -33,17 +33,24 @@ The thing has a few configuration parameters:

The following channels are available:

| Channel ID | Item Type | Description |
|-----------------|-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| activeApp | String | A dropdown containing a list of all apps installed on the Roku. The app currently running is automatically selected. The list updates every 10 minutes. |
| button | String | Sends a remote control command the Roku. See list of available commands below. |
| playMode | String | The current playback mode ie: stop, play, pause (ReadOnly). |
| timeElapsed | Number:Time | The total number of seconds of playback time elapsed for the current playing title (ReadOnly). |
| timeTotal | Number:Time | The total length of the current playing title in seconds (ReadOnly). This data is not provided by all streaming apps. |
| Channel ID | Item Type | Description |
|--------------------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| activeApp | String | A dropdown containing a list of all apps installed on the Roku. The app currently running is automatically selected. The list updates every 10 minutes. |
| button | String | Sends a remote control command the Roku. See list of available commands below. |
| playMode | String | The current playback mode ie: stop, play, pause (ReadOnly). |
| timeElapsed | Number:Time | The total number of seconds of playback time elapsed for the current playing title (ReadOnly). |
| timeTotal | Number:Time | The total length of the current playing title in seconds (ReadOnly). This data is not provided by all streaming apps. |
| activeChannel | String | A dropdown containing a list of available TV channels on the Roku TV. The channel currently tuned is automatically selected. The list updates every 10 minutes. |
| signalMode | String | The signal type of the current TV channel, ie: 1080i (ReadOnly). |
| signalQuality | Number:Dimensionless | The signal quality of the current TV channel, 0-100% (ReadOnly). |
| channelName | String | The name of the channel currently selected (ReadOnly). |
| programTitle | String | The name of the current TV program (ReadOnly). |
| programDescription | String | The description of the current TV program (ReadOnly). |
| programRating | String | The TV parental guideline rating of the current TV program (ReadOnly). |

Some Notes:

* The values for `activeApp`, `playMode`, `timeElapsed` & `timeTotal` refresh automatically per the configured `refresh` interval (10 seconds minimum).
* The values for `activeApp`, `playMode`, `timeElapsed`, `timeTotal`, `activeChannel`, `signalMode`, `signalQuality`, `channelName`, `programTitle`, `programDescription` & `programRating` refresh automatically per the configured `refresh` interval (10 seconds minimum).

**List of available button commands for Roku streaming devices:**
Home
Expand Down Expand Up @@ -81,31 +88,61 @@ PowerOff

roku.things:

```java
```
// Roku streaming media player
roku:roku_player:myplayer1 "My Roku" [ hostName="192.168.10.1", refresh=10 ]
roku:roku_tv:myplayer1 "My Roku TV" [ hostName="192.168.10.1", refresh=10 ]
// Roku TV
roku:roku_tv:mytv1 "My Roku TV" [ hostName="192.168.10.1", refresh=10 ]
```

roku.items:

```java
String Player_ActiveApp "Current App: [%s]" { channel="roku:roku_player:myplayer1:activeApp" }
String Player_Button "Send Command to Roku" { channel="roku:roku_player:myplayer1:button" }
```
// Roku streaming media player items:
String Player_ActiveApp "Current App: [%s]" { channel="roku:roku_player:myplayer1:activeApp" }
String Player_Button "Send Command to Roku" { channel="roku:roku_player:myplayer1:button" }
String Player_PlayMode "Status: [%s]" { channel="roku:roku_player:myplayer1:playMode" }
Number:Time Player_TimeElapsed "Elapsed Time: [%d %unit%]" { channel="roku:roku_player:myplayer1:timeElapsed" }
Number:Time Player_TimeTotal "Total Time: [%d %unit%]" { channel="roku:roku_player:myplayer1:timeTotal" }
// Roku TV items:
String Player_ActiveApp "Current App: [%s]" { channel="roku:roku_tv:mytv1:activeApp" }
String Player_Button "Send Command to Roku" { channel="roku:roku_tv:mytv1:button" }
String Player_PlayMode "Status: [%s]" { channel="roku:roku_tv:mytv1:playMode" }
Number:Time Player_TimeElapsed "Elapsed Time: [%d %unit%]" { channel="roku:roku_tv:mytv1:timeElapsed" }
Number:Time Player_TimeTotal "Total Time: [%d %unit%]" { channel="roku:roku_tv:mytv1:timeTotal" }
String Player_ActiveChannel "Current Channel: [%s]" { channel="roku:roku_tv:mytv1:activeChannel" }
String Player_SignalMode "Signal Mode: [%s]" { channel="roku:roku_tv:mytv1:signalMode" }
Number Player_SignalQuality "Signal Quality: [%d %%]" { channel="roku:roku_tv:mytv1:signalQuality" }
String Player_ChannelName "Channel Name: [%s]" { channel="roku:roku_tv:mytv1:channelName" }
String Player_ProgramTitle "Program Title: [%s]" { channel="roku:roku_tv:mytv1:programTitle" }
String Player_ProgramDescription "Program Description: [%s]" { channel="roku:roku_tv:mytv1:programDescription" }
String Player_ProgramRating "Program Rating: [%s]" { channel="roku:roku_tv:mytv1:programRating" }
```

roku.sitemap:

```perl
```
sitemap roku label="Roku" {
Frame label="My Roku" {
Selection item=Player_ActiveApp icon="screen"
Selection item=Player_Button icon="screen"
Text item=Player_PlayMode
Text item=Player_TimeElapsed icon="time"
Text item=Player_TimeTotal icon="time"
// The following items apply to Roku TVs only
Selection item=Player_ActiveChannel icon="screen"
Text item=Player_SignalMode
Text item=Player_SignalQuality
Text item=Player_ChannelName
Text item=Player_ProgramTitle
Text item=Player_ProgramDescription
Text item=Player_ProgramRating
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Set;

import javax.measure.Unit;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Time;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand Down Expand Up @@ -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";
Expand All @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
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;

Expand All @@ -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() {
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
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;

Expand All @@ -47,21 +50,27 @@ 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;

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";
}

/**
Expand All @@ -84,6 +93,16 @@ public void launchApp(String appId) throws RokuHttpException {
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
*
Expand Down Expand Up @@ -188,6 +207,58 @@ public Player getPlayerInfo() throws RokuHttpException {
}
}

/**
* 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
*
Expand Down
Loading

0 comments on commit 1c03d9d

Please sign in to comment.