]> git.basschouten.com Git - openhab-addons.git/commitdiff
[roku] Add power, powerState, player, and activeAppName (#15542)
authormorph166955 <53797132+morph166955@users.noreply.github.com>
Tue, 17 Oct 2023 22:10:17 +0000 (17:10 -0500)
committerGitHub <noreply@github.com>
Tue, 17 Oct 2023 22:10:17 +0000 (00:10 +0200)
* 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>
bundles/org.openhab.binding.roku/README.md
bundles/org.openhab.binding.roku/src/main/java/org/openhab/binding/roku/internal/RokuBindingConstants.java
bundles/org.openhab.binding.roku/src/main/java/org/openhab/binding/roku/internal/handler/RokuHandler.java
bundles/org.openhab.binding.roku/src/main/resources/OH-INF/config/config.xml
bundles/org.openhab.binding.roku/src/main/resources/OH-INF/i18n/roku.properties
bundles/org.openhab.binding.roku/src/main/resources/OH-INF/thing/roku.xml
bundles/org.openhab.binding.roku/src/main/resources/OH-INF/update/update.xml [new file with mode: 0644]

index d846a87384c63a2d8b507690109d556c21f7a83d..fa3f7836700bf20b974a1652e3cb869dab0af16d 100644 (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
     }
 }
 ```
index dd17c892868c4218c83f7384bfbbe4f1e692c579..344b7fd43e91b1439ce894b1b219f75860602cd9 100644 (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";
 }
index 041e87e87e79095f25c57f3c72187ab41b82d30c..bb886851704ccef7f1c4e72c12a9717d76f2e10b 100644 (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);
         }
index c69acf8ea883402fa634778889316c0a1c3519f5..a45dcb20f6935eeab8871d7c5c49984786aaaf5b 100644 (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>
index 1a4ebccda7b7a0df777fc1024142259f897580bd..dd6c31dbe37821a95538fa5f5237f27b901b89fd 100644 (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
index d1487988711d590b44a00d6723896258a2ca4a4a..ffbb68d18036b77e6ef5c0498e0707f5e1307464 100644 (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>
                </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>
                                <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>
                <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>
diff --git a/bundles/org.openhab.binding.roku/src/main/resources/OH-INF/update/update.xml b/bundles/org.openhab.binding.roku/src/main/resources/OH-INF/update/update.xml
new file mode 100644 (file)
index 0000000..8979f15
--- /dev/null
@@ -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>