[roku] Add power, powerState, player, and activeAppName (#15542)

* Adds power and powerState.  Updates refresh to allow 1 second refresh.
* Add activeAppName channel
* Add Player channel for transport control

Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
This commit is contained in:
morph166955 2023-10-17 17:10:17 -05:00 committed by GitHub
parent 74a9de313a
commit 729a19a8da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 170 additions and 10 deletions

View File

@ -23,11 +23,11 @@ The binding has no configuration options, all configuration is done at Thing lev
The thing has a few configuration parameters:
| Parameter | Description |
|-----------|------------------------------------------------------------------------------------------------------------|
| hostName | The host name or IP address of the Roku device. Mandatory. |
| port | The port on the Roku that listens for http connections. Default 8060 |
| refresh | Overrides the refresh interval for player status updates. Optional, the default and minimum is 10 seconds. |
| Parameter | Description |
|-----------|--------------------------------------------------------------------------------------------------------------------------|
| hostName | The host name or IP address of the Roku device. Mandatory. |
| port | The port on the Roku that listens for http connections. Default 8060 |
| refresh | Overrides the refresh interval for player status updates. Optional, the default is 10 seconds and minimum is 1 second. |
## Channels
@ -36,7 +36,9 @@ 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. |
| activeAppName | String | The name of the current app (ReadOnly). |
| button | String | Sends a remote control command the Roku. See list of available commands below. |
| control | Player | Control Playback e.g. play/pause/next/previous |
| 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. |
@ -47,6 +49,8 @@ The following channels are available:
| 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). |
| power | Switch | Controls the power for the TV. |
| powerState | String | The current power state for the TV. (ReadOnly - ie PowerOn, DisplayOff, Ready, etc.) |
Some Notes:
@ -83,7 +87,7 @@ InputHDMI3
InputHDMI4
InputAV1
PowerOff
PowerOn
POWERON _(NOTE: POWERON needs to be completely capitalized due to a bug with older Roku devices)_
## Full Example
@ -104,7 +108,9 @@ roku:roku_tv:mytv1 "My Roku TV" [ hostName="192.168.10.1", refresh=10 ]
// Roku streaming media player items:
String Player_ActiveApp "Current App: [%s]" { channel="roku:roku_player:myplayer1:activeApp" }
String Player_ActiveAppName "Current App Name: [%s]" { channel="roku:roku_player:myplayer1:activeAppName" }
String Player_Button "Send Command to Roku" { channel="roku:roku_player:myplayer1:button" }
Player Player_Control "Control" { channel="roku:roku_player:myplayer1:control" }
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" }
@ -112,7 +118,9 @@ Number:Time Player_TimeTotal "Total Time: [%d %unit%]" { channel="roku:roku_
// Roku TV items:
String Player_ActiveApp "Current App: [%s]" { channel="roku:roku_tv:mytv1:activeApp" }
String Player_ActiveAppName "Current App Name: [%s]" { channel="roku:roku_tv:mytv1:activeAppName" }
String Player_Button "Send Command to Roku" { channel="roku:roku_tv:mytv1:button" }
Player Player_Control "Control" { channel="roku:roku_tv:mytv1:control" }
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" }
@ -123,6 +131,8 @@ String Player_ChannelName "Channel Name: [%s]" { channel="roku:rok
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" }
Switch Player_Power "Power: [%s]" { channel="roku:roku_tv:mytv1:power" }
String Player_PowerState "Power State: [%s] { channel="roku:roku_tv:mytv1:powerState" }
```
@ -132,7 +142,9 @@ String Player_ProgramRating "Program Rating: [%s]" { channel="roku:rok
sitemap roku label="Roku" {
Frame label="My Roku" {
Selection item=Player_ActiveApp icon="screen"
Text item=Player_ActiveAppName
Selection item=Player_Button icon="screen"
Default item=Player_Control
Text item=Player_PlayMode
Text item=Player_TimeElapsed icon="time"
Text item=Player_TimeTotal icon="time"
@ -144,6 +156,8 @@ sitemap roku label="Roku" {
Text item=Player_ProgramTitle
Text item=Player_ProgramDescription
Text item=Player_ProgramRating
Switch item=Player_Power
Text item=Player_PowerState
}
}
```

View File

@ -49,7 +49,9 @@ public class RokuBindingConstants {
// List of all Channel id's
public static final String ACTIVE_APP = "activeApp";
public static final String ACTIVE_APPNAME = "activeAppName";
public static final String BUTTON = "button";
public static final String CONTROL = "control";
public static final String PLAY_MODE = "playMode";
public static final String TIME_ELAPSED = "timeElapsed";
public static final String TIME_TOTAL = "timeTotal";
@ -60,11 +62,14 @@ public class RokuBindingConstants {
public static final String PROGRAM_TITLE = "programTitle";
public static final String PROGRAM_DESCRIPTION = "programDescription";
public static final String PROGRAM_RATING = "programRating";
public static final String POWER = "power";
public static final String POWER_STATE = "powerState";
// 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 PLAY = "play";
public static final String STOP = "stop";
public static final String CLOSE = "close";
public static final String EMPTY = "";
@ -72,7 +77,11 @@ public class RokuBindingConstants {
public static final String ROKU_HOME_ID = "-1";
public static final String ROKU_HOME_ID_562859 = "562859";
public static final String ROKU_HOME_BUTTON = "Home";
public static final String ROKU_PLAY_BUTTON = "Play";
public static final String ROKU_NEXT_BUTTON = "Fwd";
public static final String ROKU_PREV_BUTTON = "Rev";
public static final String NON_DIGIT_PATTERN = "[^\\d]";
public static final String TV_APP = "tvinput.dtv";
public static final String TV_INPUT = "tvinput";
public static final String POWER_ON = "POWERON";
}

View File

@ -15,7 +15,9 @@ package org.openhab.binding.roku.internal.handler;
import static org.openhab.binding.roku.internal.RokuBindingConstants.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@ -31,6 +33,9 @@ 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.NextPreviousType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PlayPauseType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
@ -68,6 +73,7 @@ public class RokuHandler extends BaseThingHandler {
private DeviceInfo deviceInfo = new DeviceInfo();
private int refreshInterval = DEFAULT_REFRESH_PERIOD_SEC;
private boolean tvActive = false;
private Map<String, String> appMap = new HashMap<>();
private Object sequenceLock = new Object();
@ -94,7 +100,7 @@ public class RokuHandler extends BaseThingHandler {
return;
}
if (config.refresh >= 10) {
if (config.refresh >= 1) {
refreshInterval = config.refresh;
}
@ -134,6 +140,17 @@ public class RokuHandler extends BaseThingHandler {
synchronized (sequenceLock) {
String activeAppId = ROKU_HOME_ID;
try {
if (thingTypeUID.equals(THING_TYPE_ROKU_TV)) {
try {
deviceInfo = communicator.getDeviceInfo();
String powerMode = deviceInfo.getPowerMode();
updateState(POWER_STATE, new StringType(powerMode));
updateState(POWER, OnOffType.from(POWER_ON.equalsIgnoreCase(powerMode)));
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku device-info.", e);
}
}
activeAppId = communicator.getActiveApp().getApp().getId();
// 562859 is now reported when on the home screen, reset to -1
@ -142,6 +159,8 @@ public class RokuHandler extends BaseThingHandler {
}
updateState(ACTIVE_APP, new StringType(activeAppId));
updateState(ACTIVE_APPNAME, new StringType(appMap.get(activeAppId)));
if (TV_APP.equals(activeAppId)) {
tvActive = true;
} else {
@ -167,6 +186,8 @@ public class RokuHandler extends BaseThingHandler {
Player playerInfo = communicator.getPlayerInfo();
// When nothing playing, 'close' is reported, replace with 'stop'
updateState(PLAY_MODE, new StringType(playerInfo.getState().replaceAll(CLOSE, STOP)));
updateState(CONTROL,
PLAY.equalsIgnoreCase(playerInfo.getState()) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
// Remove non-numeric from string, ie: ' ms'
String position = playerInfo.getPosition().replaceAll(NON_DIGIT_PATTERN, EMPTY);
@ -229,18 +250,22 @@ public class RokuHandler extends BaseThingHandler {
synchronized (sequenceLock) {
try {
List<App> appList = communicator.getAppList();
Map<String, String> appMap = new HashMap<>();
List<StateOption> appListOptions = new ArrayList<>();
// Roku Home will be selected in the drop-down any time an app is not running.
appListOptions.add(new StateOption(ROKU_HOME_ID, ROKU_HOME));
appMap.put(ROKU_HOME_ID, ROKU_HOME);
appList.forEach(app -> {
appListOptions.add(new StateOption(app.getId(), app.getValue()));
appMap.put(app.getId(), app.getValue());
});
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), ACTIVE_APP),
appListOptions);
this.appMap = appMap;
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku installed app-list. Exception: {}", e.getMessage(), e);
}
@ -320,6 +345,41 @@ public class RokuHandler extends BaseThingHandler {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
} else if (POWER.equals(channelUID.getId())) {
synchronized (sequenceLock) {
if (command instanceof OnOffType) {
try {
if (command.equals(OnOffType.ON)) {
communicator.keyPress(POWER_ON);
} else {
communicator.keyPress("PowerOff");
}
} catch (RokuHttpException e) {
logger.debug("Unable to send keypress to Roku, key: {}, Exception: {}", command,
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
}
} else if (channelUID.getId().equals(CONTROL)) {
synchronized (sequenceLock) {
try {
if (command instanceof PlayPauseType) {
communicator.keyPress(ROKU_PLAY_BUTTON);
} else if (command instanceof NextPreviousType) {
if (command == NextPreviousType.NEXT) {
communicator.keyPress(ROKU_NEXT_BUTTON);
} else if (command == NextPreviousType.PREVIOUS) {
communicator.keyPress(ROKU_PREV_BUTTON);
}
} else {
logger.warn("Unknown control command: {}", command);
}
} catch (RokuHttpException e) {
logger.debug("Unable to send control cmd to Roku, cmd: {}, Exception: {}", command, e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
} else {
logger.debug("Unsupported command: {}", command);
}

View File

@ -16,7 +16,7 @@
<default>8060</default>
<advanced>true</advanced>
</parameter>
<parameter name="refresh" type="integer" min="10" required="false" unit="s">
<parameter name="refresh" type="integer" min="1" required="false" unit="s">
<label>Refresh Interval</label>
<description>Specifies the Refresh Interval in Seconds</description>
<default>10</default>

View File

@ -23,6 +23,8 @@ thing-type.config.roku.rokuconfig.refresh.description = Specifies the Refresh In
channel-type.roku.activeApp.label = Active App
channel-type.roku.activeApp.description = The Currently Running App on the Roku
channel-type.roku.activeAppName.label = Active App Name
channel-type.roku.activeAppName.description = The Currently Running App on the Roku
channel-type.roku.activeChannel.label = Active Channel
channel-type.roku.activeChannel.description = The TV Channel Currently Selected on the Roku TV
channel-type.roku.button.label = Remote Button
@ -73,11 +75,18 @@ channel-type.roku.buttonTv.state.option.InputHDMI3 = Input HDMI3
channel-type.roku.buttonTv.state.option.InputHDMI4 = Input HDMI4
channel-type.roku.buttonTv.state.option.InputAV1 = Input AV1
channel-type.roku.buttonTv.state.option.PowerOff = Power Off
channel-type.roku.buttonTv.state.option.PowerOn = Power On
channel-type.roku.buttonTv.state.option.POWERON = Power On
channel-type.roku.channelName.label = Channel Name
channel-type.roku.channelName.description = The Name of the Channel Currently Selected
channel-type.roku.control.label = Control
channel-type.roku.control.description = Control playback e.g. Play/Pause/Next/Previous
channel-type.roku.playMode.label = Play Mode
channel-type.roku.playMode.description = The Current Playback Mode
channel-type.roku.powerState.label = Power State
channel-type.roku.powerState.description = Power State of the TV
channel-type.roku.powerState.state.option.PowerOn = Power On
channel-type.roku.powerState.state.option.DisplayOff = Display Off
channel-type.roku.powerState.state.option.Ready = Ready
channel-type.roku.programDescription.label = Program Description
channel-type.roku.programDescription.description = The Description of the Current TV Program
channel-type.roku.programRating.label = Program Rating

View File

@ -13,7 +13,9 @@
<channels>
<channel id="activeApp" typeId="activeApp"/>
<channel id="activeAppName" typeId="activeAppName"/>
<channel id="button" typeId="button"/>
<channel id="control" typeId="control"/>
<channel id="playMode" typeId="playMode"/>
<channel id="timeElapsed" typeId="timeElapsed"/>
<channel id="timeTotal" typeId="timeTotal"/>
@ -26,6 +28,7 @@
<property name="Serial Number">unknown</property>
<property name="Device Id">unknown</property>
<property name="Software Version">unknown</property>
<property name="thingTypeVersion">1</property>
</properties>
<representation-property>uuid</representation-property>
@ -41,8 +44,11 @@
</description>
<channels>
<channel id="power" typeId="system.power"/>
<channel id="activeApp" typeId="activeApp"/>
<channel id="activeAppName" typeId="activeAppName"/>
<channel id="button" typeId="buttonTv"/>
<channel id="control" typeId="control"/>
<channel id="playMode" typeId="playMode"/>
<channel id="timeElapsed" typeId="timeElapsed"/>
<channel id="timeTotal" typeId="timeTotal"/>
@ -53,6 +59,7 @@
<channel id="programTitle" typeId="programTitle"/>
<channel id="programDescription" typeId="programDescription"/>
<channel id="programRating" typeId="programRating"/>
<channel id="powerState" typeId="powerState"/>
</channels>
<properties>
@ -62,6 +69,7 @@
<property name="Serial Number">unknown</property>
<property name="Device Id">unknown</property>
<property name="Software Version">unknown</property>
<property name="thingTypeVersion">1</property>
</properties>
<representation-property>uuid</representation-property>
@ -129,17 +137,31 @@
<option value="InputHDMI4">Input HDMI4</option>
<option value="InputAV1">Input AV1</option>
<option value="PowerOff">Power Off</option>
<option value="PowerOn">Power On</option>
<option value="POWERON">Power On</option>
</options>
</state>
</channel-type>
<channel-type id="control">
<item-type>Player</item-type>
<label>Control</label>
<description>Control playback e.g. Play/Pause/Next/Previous</description>
<category>Player</category>
</channel-type>
<channel-type id="activeApp">
<item-type>String</item-type>
<label>Active App</label>
<description>The Currently Running App on the Roku</description>
</channel-type>
<channel-type id="activeAppName">
<item-type>String</item-type>
<label>Active App Name</label>
<description>The Currently Running App on the Roku</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="playMode">
<item-type>String</item-type>
<label>Play Mode</label>
@ -209,4 +231,16 @@
<state readOnly="true"/>
</channel-type>
<channel-type id="powerState" advanced="true">
<item-type>String</item-type>
<label>Power State</label>
<description>Power State of the TV</description>
<state readOnly="true">
<options>
<option value="PowerOn">Power On</option>
<option value="DisplayOff">Display Off</option>
<option value="Ready">Ready</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
<thing-type uid="roku:roku_player">
<instruction-set targetVersion="1">
<add-channel id="activeAppName">
<type>roku:activeAppName</type>
</add-channel>
<add-channel id="control">
<type>roku:control</type>
</add-channel>
</instruction-set>
</thing-type>
<thing-type uid="roku:roku_tv">
<instruction-set targetVersion="1">
<add-channel id="activeAppName">
<type>roku:activeAppName</type>
</add-channel>
<add-channel id="power">
<type>system:power</type>
</add-channel>
<add-channel id="powerState">
<type>roku:powerState</type>
</add-channel>
<add-channel id="control">
<type>roku:control</type>
</add-channel>
</instruction-set>
</thing-type>
</update:update-descriptions>