Add Roku TV channels (#11087)
Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
This commit is contained in:
parent
a93b56f2d3
commit
1321049973
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -81,24 +88,46 @@ 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"
|
||||
|
@ -106,6 +135,14 @@ sitemap roku label="Roku" {
|
|||
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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue