mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[roku] binding - initial implementation (#9571)
* Roku binding - initial implementation * update channel names to camelCase * review changes * spelling * update README.md Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
This commit is contained in:
parent
05c16b0395
commit
08833c7c79
@ -217,6 +217,7 @@
|
||||
/bundles/org.openhab.binding.rfxcom/ @martinvw @paulianttila
|
||||
/bundles/org.openhab.binding.rme/ @kgoderis
|
||||
/bundles/org.openhab.binding.robonect/ @reyem
|
||||
/bundles/org.openhab.binding.roku/ @mlobstein
|
||||
/bundles/org.openhab.binding.rotel/ @lolodomo
|
||||
/bundles/org.openhab.binding.russound/ @tmrobert8
|
||||
/bundles/org.openhab.binding.sagercaster/ @clinique
|
||||
|
@ -1071,6 +1071,11 @@
|
||||
<artifactId>org.openhab.binding.robonect</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.roku</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.rotel</artifactId>
|
||||
|
13
bundles/org.openhab.binding.roku/NOTICE
Normal file
13
bundles/org.openhab.binding.roku/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
111
bundles/org.openhab.binding.roku/README.md
Normal file
111
bundles/org.openhab.binding.roku/README.md
Normal file
@ -0,0 +1,111 @@
|
||||
# Roku Binding
|
||||
|
||||
This binding connects Roku streaming media players and Roku TVs to openHAB.
|
||||
The Roku device must support the Roku ECP protocol REST API.
|
||||
|
||||
## Supported Things
|
||||
|
||||
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.
|
||||
Multiple Things can be added if more than one Roku is to be controlled.
|
||||
|
||||
## Discovery
|
||||
|
||||
Auto-discovery is supported if the Roku can be located on the local network using SSDP.
|
||||
Otherwise the thing must be manually added.
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding has no configuration options, all configuration is done at Thing level.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
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. |
|
||||
|
||||
## Channels
|
||||
|
||||
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. |
|
||||
|
||||
Some Notes:
|
||||
|
||||
* The values for `activeApp`, `playMode`, `timeElapsed` & `timeTotal` refresh automatically per the configured `refresh` interval (10 seconds minimum).
|
||||
|
||||
**List of available button commands for Roku streaming devices:**
|
||||
Home
|
||||
Rev
|
||||
Fwd
|
||||
Play
|
||||
Select
|
||||
Left
|
||||
Right
|
||||
Up
|
||||
Down
|
||||
Back
|
||||
InstantReplay
|
||||
Info
|
||||
Backspace
|
||||
Search
|
||||
Enter
|
||||
FindRemote
|
||||
|
||||
**List of additional button commands for Roku TVs:**
|
||||
ChannelUp
|
||||
ChannelDown
|
||||
VolumeUp
|
||||
VolumeDown
|
||||
VolumeMute
|
||||
InputTuner
|
||||
InputHDMI1
|
||||
InputHDMI2
|
||||
InputHDMI3
|
||||
InputHDMI4
|
||||
InputAV1
|
||||
PowerOff
|
||||
|
||||
## Full Example
|
||||
|
||||
roku.things:
|
||||
|
||||
```java
|
||||
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.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" }
|
||||
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.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"
|
||||
}
|
||||
}
|
||||
```
|
17
bundles/org.openhab.binding.roku/pom.xml
Normal file
17
bundles/org.openhab.binding.roku/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.roku</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Roku Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.roku-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-roku" description="Roku Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.roku/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link RokuBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RokuBindingConstants {
|
||||
public static final String BINDING_ID = "roku";
|
||||
public static final String PROPERTY_UUID = "uuid";
|
||||
public static final String PROPERTY_HOST_NAME = "hostName";
|
||||
public static final String PROPERTY_PORT = "port";
|
||||
public static final String PROPERTY_MODEL_NAME = "Model Name";
|
||||
public static final String PROPERTY_MODEL_NUMBER = "Model Number";
|
||||
public static final String PROPERTY_DEVICE_LOCAITON = "Device Location";
|
||||
public static final String PROPERTY_SERIAL_NUMBER = "Serial Number";
|
||||
public static final String PROPERTY_DEVICE_ID = "Device Id";
|
||||
public static final String PROPERTY_SOFTWARE_VERSION = "Software Version";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_ROKU_PLAYER = new ThingTypeUID(BINDING_ID, "roku_player");
|
||||
public static final ThingTypeUID THING_TYPE_ROKU_TV = new ThingTypeUID(BINDING_ID, "roku_tv");
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ROKU_PLAYER,
|
||||
THING_TYPE_ROKU_TV);
|
||||
|
||||
// List of all Channel id's
|
||||
public static final String ACTIVE_APP = "activeApp";
|
||||
public static final String BUTTON = "button";
|
||||
public static final String PLAY_MODE = "playMode";
|
||||
public static final String TIME_ELAPSED = "timeElapsed";
|
||||
public static final String TIME_TOTAL = "timeTotal";
|
||||
|
||||
// Units of measurement of the data delivered by the API
|
||||
public static final Unit<Time> API_SECONDS_UNIT = Units.SECOND;
|
||||
|
||||
public static final String STOP = "stop";
|
||||
public static final String CLOSE = "close";
|
||||
public static final String EMPTY = "";
|
||||
public static final String ROKU_HOME = "Roku Home";
|
||||
public static final String ROKU_HOME_ID = "-1";
|
||||
public static final String ROKU_HOME_BUTTON = "Home";
|
||||
public static final String NON_DIGIT_PATTERN = "[^\\d]";
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link RokuConfiguration} is the class used to match the
|
||||
* thing configuration.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RokuConfiguration {
|
||||
public @Nullable String hostName;
|
||||
public Integer port = 8060;
|
||||
public Integer refresh = 10;
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import static org.openhab.binding.roku.internal.RokuBindingConstants.SUPPORTED_THING_TYPES_UIDS;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.roku.internal.handler.RokuHandler;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link RokuHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.roku")
|
||||
public class RokuHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final RokuStateDescriptionOptionProvider stateDescriptionProvider;
|
||||
|
||||
@Activate
|
||||
public RokuHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||
final @Reference RokuStateDescriptionOptionProvider provider) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.stateDescriptionProvider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
RokuHandler handler = new RokuHandler(thing, httpClient, stateDescriptionProvider);
|
||||
return handler;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link RokuHttpException} extends Exception
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RokuHttpException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RokuHttpException(String errorMessage) {
|
||||
super(errorMessage);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Dynamic provider of state options while leaving other state description fields as original.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, RokuStateDescriptionOptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class RokuStateDescriptionOptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
|
||||
@Reference
|
||||
protected void setChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
|
||||
protected void unsetChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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.communication;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Implementation for a static use of JAXBContext as singleton instance.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JAXBUtils {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(JAXBUtils.class);
|
||||
|
||||
public static final @Nullable JAXBContext JAXBCONTEXT_ACTIVE_APP = initJAXBContextActiveApp();
|
||||
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();
|
||||
|
||||
private static @Nullable JAXBContext initJAXBContextActiveApp() {
|
||||
try {
|
||||
return JAXBContext.newInstance(ActiveApp.class);
|
||||
} catch (JAXBException e) {
|
||||
LOGGER.error("Exception creating JAXBContext for active app: {}", e.getLocalizedMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable JAXBContext initJAXBContextApps() {
|
||||
try {
|
||||
return JAXBContext.newInstance(Apps.class);
|
||||
} catch (JAXBException e) {
|
||||
LOGGER.error("Exception creating JAXBContext for app list: {}", e.getLocalizedMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable JAXBContext initJAXBContextDeviceInfo() {
|
||||
try {
|
||||
return JAXBContext.newInstance(DeviceInfo.class);
|
||||
} catch (JAXBException e) {
|
||||
LOGGER.error("Exception creating JAXBContext for device info: {}", e.getLocalizedMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable JAXBContext initJAXBContextPlayer() {
|
||||
try {
|
||||
return JAXBContext.newInstance(Player.class);
|
||||
} catch (JAXBException e) {
|
||||
LOGGER.error("Exception creating JAXBContext for player info: {}", e.getLocalizedMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
/**
|
||||
* 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.communication;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.roku.internal.RokuHttpException;
|
||||
import org.openhab.binding.roku.internal.dto.ActiveApp;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Methods for accessing the HTTP interface of the Roku
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RokuCommunicator {
|
||||
private final Logger logger = LoggerFactory.getLogger(RokuCommunicator.class);
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private final String urlKeyPress;
|
||||
private final String urlLaunchApp;
|
||||
private final String urlQryDevice;
|
||||
private final String urlQryActiveApp;
|
||||
private final String urlQryApps;
|
||||
private final String urlQryPlayer;
|
||||
|
||||
public RokuCommunicator(HttpClient httpClient, String host, int port) {
|
||||
this.httpClient = httpClient;
|
||||
|
||||
final String baseUrl = "http://" + host + ":" + port;
|
||||
urlKeyPress = baseUrl + "/keypress/";
|
||||
urlLaunchApp = baseUrl + "/launch/";
|
||||
urlQryDevice = baseUrl + "/query/device-info";
|
||||
urlQryActiveApp = baseUrl + "/query/active-app";
|
||||
urlQryApps = baseUrl + "/query/apps";
|
||||
urlQryPlayer = baseUrl + "/query/media-player";
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a keypress command to the Roku
|
||||
*
|
||||
* @param key The key code to send
|
||||
*
|
||||
*/
|
||||
public void keyPress(String key) throws RokuHttpException {
|
||||
postCommand(urlKeyPress + key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a launch app command to the Roku
|
||||
*
|
||||
* @param appId The appId of the app to launch
|
||||
*
|
||||
*/
|
||||
public void launchApp(String appId) throws RokuHttpException {
|
||||
postCommand(urlLaunchApp + appId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to get device-info from the Roku and return a DeviceInfo object
|
||||
*
|
||||
* @return A DeviceInfo object populated with information about the connected Roku
|
||||
* @throws RokuHttpException
|
||||
*/
|
||||
public DeviceInfo getDeviceInfo() throws RokuHttpException {
|
||||
try {
|
||||
JAXBContext ctx = JAXBUtils.JAXBCONTEXT_DEVICE_INFO;
|
||||
if (ctx != null) {
|
||||
Unmarshaller unmarshaller = ctx.createUnmarshaller();
|
||||
if (unmarshaller != null) {
|
||||
DeviceInfo device = (DeviceInfo) unmarshaller.unmarshal(new StringReader(getCommand(urlQryDevice)));
|
||||
if (device != null) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RokuHttpException("No DeviceInfo model in response");
|
||||
} catch (JAXBException e) {
|
||||
throw new RokuHttpException("Exception creating DeviceInfo Unmarshaller: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to get active-app from the Roku and return an ActiveApp object
|
||||
*
|
||||
* @return An ActiveApp object populated with information about the current running app on the Roku
|
||||
* @throws RokuHttpException
|
||||
*/
|
||||
public ActiveApp getActiveApp() throws RokuHttpException {
|
||||
try {
|
||||
JAXBContext ctx = JAXBUtils.JAXBCONTEXT_ACTIVE_APP;
|
||||
if (ctx != null) {
|
||||
Unmarshaller unmarshaller = ctx.createUnmarshaller();
|
||||
if (unmarshaller != null) {
|
||||
ActiveApp activeApp = (ActiveApp) unmarshaller
|
||||
.unmarshal(new StringReader(getCommand(urlQryActiveApp)));
|
||||
if (activeApp != null) {
|
||||
return activeApp;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RokuHttpException("No ActiveApp model in response");
|
||||
} catch (JAXBException e) {
|
||||
throw new RokuHttpException("Exception creating ActiveApp Unmarshaller: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to get the installed app list from the Roku and return a List of App objects
|
||||
*
|
||||
* @return A List of App objects for all apps currently installed on the Roku
|
||||
* @throws RokuHttpException
|
||||
*/
|
||||
public List<App> getAppList() throws RokuHttpException {
|
||||
try {
|
||||
JAXBContext ctx = JAXBUtils.JAXBCONTEXT_APPS;
|
||||
if (ctx != null) {
|
||||
Unmarshaller unmarshaller = ctx.createUnmarshaller();
|
||||
if (unmarshaller != null) {
|
||||
Apps appList = (Apps) unmarshaller.unmarshal(new StringReader(getCommand(urlQryApps)));
|
||||
if (appList != null) {
|
||||
return appList.getApp();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RokuHttpException("No AppList model in response");
|
||||
} catch (JAXBException e) {
|
||||
throw new RokuHttpException("Exception creating AppList Unmarshaller: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to get media-player from the Roku and return a Player object
|
||||
*
|
||||
* @return A Player object populated with information about the current stream playing on the Roku
|
||||
* @throws RokuHttpException
|
||||
*/
|
||||
public Player getPlayerInfo() throws RokuHttpException {
|
||||
try {
|
||||
JAXBContext ctx = JAXBUtils.JAXBCONTEXT_PLAYER;
|
||||
if (ctx != null) {
|
||||
Unmarshaller unmarshaller = ctx.createUnmarshaller();
|
||||
if (unmarshaller != null) {
|
||||
Player playerInfo = (Player) unmarshaller.unmarshal(new StringReader(getCommand(urlQryPlayer)));
|
||||
if (playerInfo != null) {
|
||||
return playerInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RokuHttpException("No Player info model in response");
|
||||
} catch (JAXBException e) {
|
||||
throw new RokuHttpException("Exception creating Player info Unmarshaller: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a GET command to the Roku
|
||||
*
|
||||
* @param url The url to send with the command embedded in the URI
|
||||
* @return The response content of the http request
|
||||
*/
|
||||
private String getCommand(String url) {
|
||||
try {
|
||||
return httpClient.GET(url).getContentAsString();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.debug("Error executing player GET command, URL: {}, {} ", url, e.getMessage());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a POST command to the Roku
|
||||
*
|
||||
* @param url The url to send with the command embedded in the URI
|
||||
* @throws RokuHttpException
|
||||
*/
|
||||
private void postCommand(String url) throws RokuHttpException {
|
||||
try {
|
||||
httpClient.POST(url).method(HttpMethod.POST).send();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new RokuHttpException("Error executing player POST command, URL: " + url + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,264 @@
|
||||
/**
|
||||
* 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.discovery;
|
||||
|
||||
import static org.openhab.binding.roku.internal.RokuBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.roku.internal.RokuHttpException;
|
||||
import org.openhab.binding.roku.internal.communication.RokuCommunicator;
|
||||
import org.openhab.binding.roku.internal.dto.DeviceInfo;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RokuDiscoveryService} is responsible for discovery of Roku devices on the local network
|
||||
*
|
||||
* @author William Welliver - Initial contribution
|
||||
* @author Dan Cunningham - Refactoring and Improvements
|
||||
* @author Michael Lobstein - Modified for Roku binding
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.roku")
|
||||
public class RokuDiscoveryService extends AbstractDiscoveryService {
|
||||
private final Logger logger = LoggerFactory.getLogger(RokuDiscoveryService.class);
|
||||
private static final String ROKU_DISCOVERY_MESSAGE = "M-SEARCH * HTTP/1.1\r\n" + "Host: 239.255.255.250:1900\r\n"
|
||||
+ "Man: \"ssdp:discover\"\r\n" + "ST: roku:ecp\r\n" + "\r\n";
|
||||
|
||||
private static final Pattern USN_PATTERN = Pattern.compile("^(uuid:roku:)?ecp:([0-9a-zA-Z]{1,16})");
|
||||
|
||||
private static final Pattern IP_HOST_PATTERN = Pattern
|
||||
.compile("([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}):([0-9]{1,5})");
|
||||
|
||||
private static final String ROKU_SSDP_MATCH = "uuid:roku:ecp";
|
||||
private static final int BACKGROUND_SCAN_INTERVAL_SECONDS = 300;
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private @Nullable ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
@Activate
|
||||
public RokuDiscoveryService(final @Reference HttpClientFactory httpClientFactory) {
|
||||
super(SUPPORTED_THING_TYPES_UIDS, 30, true);
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startBackgroundDiscovery() {
|
||||
stopBackgroundDiscovery();
|
||||
scheduledFuture = scheduler.scheduleWithFixedDelay(this::doNetworkScan, 0, BACKGROUND_SCAN_INTERVAL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopBackgroundDiscovery() {
|
||||
ScheduledFuture<?> scheduledFuture = this.scheduledFuture;
|
||||
if (scheduledFuture != null) {
|
||||
scheduledFuture.cancel(true);
|
||||
}
|
||||
this.scheduledFuture = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
doNetworkScan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all network interfaces, send the discovery broadcast and process responses.
|
||||
*
|
||||
*/
|
||||
private synchronized void doNetworkScan() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
|
||||
while (nets.hasMoreElements()) {
|
||||
NetworkInterface ni = nets.nextElement();
|
||||
try (DatagramSocket socket = sendDiscoveryBroacast(ni)) {
|
||||
if (socket != null) {
|
||||
scanResposesForKeywords(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error discovering devices", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a SSDP discovery message into the network to find provided services.
|
||||
*
|
||||
* @return The Socket where answers to the discovery broadcast arrive
|
||||
*/
|
||||
private @Nullable DatagramSocket sendDiscoveryBroacast(NetworkInterface ni) {
|
||||
try {
|
||||
InetAddress m = InetAddress.getByName("239.255.255.250");
|
||||
final int port = 1900;
|
||||
|
||||
if (!ni.isUp() || !ni.supportsMulticast()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Enumeration<InetAddress> addrs = ni.getInetAddresses();
|
||||
InetAddress a = null;
|
||||
while (addrs.hasMoreElements()) {
|
||||
a = addrs.nextElement();
|
||||
if (a instanceof Inet4Address) {
|
||||
break;
|
||||
} else {
|
||||
a = null;
|
||||
}
|
||||
}
|
||||
if (a == null) {
|
||||
logger.debug("No ipv4 address on {}", ni.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create the discovery message packet
|
||||
byte[] requestMessage = ROKU_DISCOVERY_MESSAGE.getBytes(StandardCharsets.UTF_8);
|
||||
DatagramPacket datagramPacket = new DatagramPacket(requestMessage, requestMessage.length, m, port);
|
||||
|
||||
// Create socket and send the discovery message
|
||||
DatagramSocket socket = new DatagramSocket();
|
||||
socket.setSoTimeout(3000);
|
||||
socket.send(datagramPacket);
|
||||
return socket;
|
||||
} catch (IOException e) {
|
||||
logger.debug("sendDiscoveryBroacast() got IOException: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans all messages that arrive on the socket and process those that come from a Roku.
|
||||
*
|
||||
* @param socket The socket where answers to the discovery broadcast arrive
|
||||
*/
|
||||
private void scanResposesForKeywords(DatagramSocket socket) {
|
||||
byte[] receiveData = new byte[1024];
|
||||
do {
|
||||
DatagramPacket packet = new DatagramPacket(receiveData, receiveData.length);
|
||||
try {
|
||||
socket.receive(packet);
|
||||
} catch (SocketTimeoutException e) {
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
logger.debug("Got exception while trying to receive UPnP packets: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
String response = new String(packet.getData(), StandardCharsets.UTF_8);
|
||||
if (response.contains(ROKU_SSDP_MATCH)) {
|
||||
parseResponseCreateThing(response);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the response from the Roku into a DiscoveryResult.
|
||||
*
|
||||
*/
|
||||
private void parseResponseCreateThing(String response) {
|
||||
DiscoveryResult result;
|
||||
|
||||
String label = "Roku";
|
||||
String uuid = null;
|
||||
String host = null;
|
||||
int port = -1;
|
||||
|
||||
try (Scanner scanner = new Scanner(response)) {
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
String[] pair = line.split(":", 2);
|
||||
if (pair.length != 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String key = pair[0].toLowerCase();
|
||||
String value = pair[1].trim();
|
||||
logger.debug("key: {} value: {}.", key, value);
|
||||
switch (key) {
|
||||
case "location":
|
||||
host = value;
|
||||
Matcher matchIp = IP_HOST_PATTERN.matcher(value);
|
||||
if (matchIp.find()) {
|
||||
host = matchIp.group(1);
|
||||
port = Integer.parseInt(matchIp.group(2));
|
||||
}
|
||||
break;
|
||||
case "usn":
|
||||
Matcher matchUid = USN_PATTERN.matcher(value);
|
||||
if (matchUid.find()) {
|
||||
uuid = matchUid.group(2);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (host == null || port == -1 || uuid == null) {
|
||||
logger.debug("Bad Format from Roku, received data was: {}", response);
|
||||
return;
|
||||
} else {
|
||||
logger.debug("Found Roku, uuid: {} host: {}", uuid, host);
|
||||
}
|
||||
|
||||
uuid = uuid.replace(":", "").toLowerCase();
|
||||
|
||||
ThingUID thingUid = new ThingUID(THING_TYPE_ROKU_PLAYER, uuid);
|
||||
|
||||
// Try to query the device using discovered host and port to get extended device info
|
||||
try {
|
||||
RokuCommunicator communicator = new RokuCommunicator(httpClient, host, port);
|
||||
DeviceInfo device = communicator.getDeviceInfo();
|
||||
label = device.getModelName() + " " + device.getModelNumber();
|
||||
if (device.isTv()) {
|
||||
thingUid = new ThingUID(THING_TYPE_ROKU_TV, uuid);
|
||||
}
|
||||
} catch (RokuHttpException e) {
|
||||
logger.debug("Unable to retrieve Roku device-info. Exception: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
result = DiscoveryResultBuilder.create(thingUid).withLabel(label).withRepresentationProperty(PROPERTY_UUID)
|
||||
.withProperty(PROPERTY_UUID, uuid).withProperty(PROPERTY_HOST_NAME, host)
|
||||
.withProperty(PROPERTY_PORT, port).build();
|
||||
this.thingDiscovered(result);
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* 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.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlValue;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Maps the XML response from the Roku HTTP endpoint '/query/active-app' (Active app info)
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "active-app")
|
||||
public class ActiveApp {
|
||||
@XmlElement
|
||||
private ActiveApp.App app = new App();
|
||||
|
||||
@XmlElement
|
||||
private ActiveApp.Screensaver screensaver = new Screensaver();
|
||||
|
||||
public ActiveApp.App getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(ActiveApp.App value) {
|
||||
this.app = value;
|
||||
}
|
||||
|
||||
public ActiveApp.Screensaver getScreensaver() {
|
||||
return screensaver;
|
||||
}
|
||||
|
||||
public void setScreensaver(ActiveApp.Screensaver value) {
|
||||
this.screensaver = value;
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class App {
|
||||
@XmlValue
|
||||
private String value = "";
|
||||
|
||||
@XmlAttribute(name = "id")
|
||||
private String id = "-1";
|
||||
|
||||
@XmlAttribute(name = "type")
|
||||
private String type = "";
|
||||
|
||||
@XmlAttribute(name = "version")
|
||||
private String version = "";
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String value) {
|
||||
this.type = value;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String value) {
|
||||
this.version = value;
|
||||
}
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class Screensaver {
|
||||
@XmlValue
|
||||
private String value = "";
|
||||
|
||||
@XmlAttribute(name = "id")
|
||||
private int id = -1;
|
||||
|
||||
@XmlAttribute(name = "type")
|
||||
private String type = "";
|
||||
|
||||
@XmlAttribute(name = "version")
|
||||
private String version = "";
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String value) {
|
||||
this.type = value;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String value) {
|
||||
this.version = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlValue;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Maps the XML response from the Roku HTTP endpoint '/query/apps' (List of installed apps)
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "apps")
|
||||
public class Apps {
|
||||
@XmlElement
|
||||
private List<Apps.App> app = new ArrayList<Apps.App>();
|
||||
|
||||
public List<Apps.App> getApp() {
|
||||
return this.app;
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class App {
|
||||
@XmlValue
|
||||
private String value = "";
|
||||
|
||||
@XmlAttribute(name = "id")
|
||||
private String id = "-1";
|
||||
|
||||
@XmlAttribute(name = "type")
|
||||
private String type = "";
|
||||
|
||||
@XmlAttribute(name = "version")
|
||||
private String version = "";
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String value) {
|
||||
this.type = value;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String value) {
|
||||
this.version = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,662 @@
|
||||
/**
|
||||
* 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/device-info' (Device information)
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "device-info")
|
||||
public class DeviceInfo {
|
||||
@XmlElement(name = "udn")
|
||||
private String udn = "";
|
||||
@XmlElement(name = "serial-number")
|
||||
private String serialNumber = "";
|
||||
@XmlElement(name = "device-id")
|
||||
private String deviceId = "";
|
||||
@XmlElement(name = "advertising-id")
|
||||
private String advertisingId = "";
|
||||
@XmlElement(name = "vendor-name")
|
||||
private String vendorName = "";
|
||||
@XmlElement(name = "model-name")
|
||||
private String modelName = "";
|
||||
@XmlElement(name = "model-number")
|
||||
private String modelNumber = "";
|
||||
@XmlElement(name = "model-region")
|
||||
private String modelRegion = "";
|
||||
@XmlElement(name = "is-tv")
|
||||
private boolean isTv = false;
|
||||
@XmlElement(name = "is-stick")
|
||||
private boolean isStick = false;
|
||||
@XmlElement(name = "ui-resolution")
|
||||
private String uiResolution = "";
|
||||
@XmlElement(name = "supports-ethernet")
|
||||
private boolean supportsEthernet = false;
|
||||
@XmlElement(name = "wifi-mac")
|
||||
private String wifiMac = "";
|
||||
@XmlElement(name = "wifi-driver")
|
||||
private String wifiDriver = "";
|
||||
@XmlElement(name = "has-wifi-extender")
|
||||
private boolean hasWifiExtender = false;
|
||||
@XmlElement(name = "has-wifi-5G-support")
|
||||
private boolean hasWifi5GSupport = false;
|
||||
@XmlElement(name = "can-use-wifi-extender")
|
||||
private boolean canUseWifiExtender = false;
|
||||
@XmlElement(name = "ethernet-mac")
|
||||
private String ethernetMac = "";
|
||||
@XmlElement(name = "network-type")
|
||||
private String networkType = "";
|
||||
@XmlElement(name = "friendly-device-name")
|
||||
private String friendlyDeviceName = "";
|
||||
@XmlElement(name = "friendly-model-name")
|
||||
private String friendlyModelName = "";
|
||||
@XmlElement(name = "default-device-name")
|
||||
private String defaultDeviceName = "";
|
||||
@XmlElement(name = "user-device-name")
|
||||
private String userDeviceName = "";
|
||||
@XmlElement(name = "user-device-location")
|
||||
private String userDeviceLocation = "";
|
||||
@XmlElement(name = "build-number")
|
||||
private String buildNumber = "";
|
||||
@XmlElement(name = "software-version")
|
||||
private String softwareVersion = "";
|
||||
@XmlElement(name = "software-build")
|
||||
private String softwareBuild = "";
|
||||
@XmlElement(name = "secure-device")
|
||||
private boolean secureDevice = false;
|
||||
@XmlElement(name = "language")
|
||||
private String language = "";
|
||||
@XmlElement(name = "country")
|
||||
private String country = "";
|
||||
@XmlElement(name = "locale")
|
||||
private String locale = "";
|
||||
@XmlElement(name = "time-zone-auto")
|
||||
private boolean timeZoneAuto = false;
|
||||
@XmlElement(name = "time-zone")
|
||||
private String timeZone = "";
|
||||
@XmlElement(name = "time-zone-name")
|
||||
private String timeZoneName = "";
|
||||
@XmlElement(name = "time-zone-tz")
|
||||
private String timeZoneTz = "";
|
||||
@XmlElement(name = "time-zone-offset")
|
||||
private int timeZoneOffset = 0;
|
||||
@XmlElement(name = "clock-format")
|
||||
private String clockFormat = "";
|
||||
@XmlElement(name = "uptime")
|
||||
private int uptime = 0;
|
||||
@XmlElement(name = "power-mode")
|
||||
private String powerMode = "";
|
||||
@XmlElement(name = "supports-suspend")
|
||||
private boolean supportsSuspend = false;
|
||||
@XmlElement(name = "supports-find-remote")
|
||||
private boolean supportsFindRemote = false;
|
||||
@XmlElement(name = "find-remote-is-possible")
|
||||
private boolean findRemoteIsPossible = false;
|
||||
@XmlElement(name = "supports-audio-guide")
|
||||
private boolean supportsAudioGuide = false;
|
||||
@XmlElement(name = "supports-rva")
|
||||
private boolean supportsRva = false;
|
||||
@XmlElement(name = "developer-enabled")
|
||||
private boolean developerEnabled = false;
|
||||
@XmlElement(name = "keyed-developer-id")
|
||||
private String keyedDeveloperId = "";
|
||||
@XmlElement(name = "search-enabled")
|
||||
private boolean searchEnabled = false;
|
||||
@XmlElement(name = "search-channels-enabled")
|
||||
private boolean searchChannelsEnabled = false;
|
||||
@XmlElement(name = "voice-search-enabled")
|
||||
private boolean voiceSearchEnabled = false;
|
||||
@XmlElement(name = "notifications-enabled")
|
||||
private boolean notificationsEnabled = false;
|
||||
@XmlElement(name = "notifications-first-use")
|
||||
private boolean notificationsFirstUse = false;
|
||||
@XmlElement(name = "supports-private-listening")
|
||||
private boolean supportsPrivateListening = false;
|
||||
@XmlElement(name = "headphones-connected")
|
||||
private boolean headphonesConnected = false;
|
||||
@XmlElement(name = "supports-ecs-textedit")
|
||||
private boolean supportsEcsTextedit = false;
|
||||
@XmlElement(name = "supports-ecs-microphone")
|
||||
private boolean supportsEcsMicrophone = false;
|
||||
@XmlElement(name = "supports-wake-on-wlan")
|
||||
private boolean supportsWakeOnWlan = false;
|
||||
@XmlElement(name = "has-play-on-roku")
|
||||
private boolean hasPlayOnRoku = false;
|
||||
@XmlElement(name = "has-mobile-screensaver")
|
||||
private boolean hasMobileScreensaver = false;
|
||||
@XmlElement(name = "support-url")
|
||||
private String supportUrl = "";
|
||||
@XmlElement(name = "grandcentral-version")
|
||||
private String grandcentralVersion = "";
|
||||
@XmlElement(name = "trc-version")
|
||||
private String trcVersion = "";
|
||||
@XmlElement(name = "trc-channel-version")
|
||||
private String trcChannelVersion = "";
|
||||
@XmlElement(name = "davinci-version")
|
||||
private String davinciVersion = "";
|
||||
|
||||
public String getUdn() {
|
||||
return udn;
|
||||
}
|
||||
|
||||
public void setUdn(String value) {
|
||||
this.udn = value;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public void setSerialNumber(String value) {
|
||||
this.serialNumber = value;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeviceId(String value) {
|
||||
this.deviceId = value;
|
||||
}
|
||||
|
||||
public String getAdvertisingId() {
|
||||
return advertisingId;
|
||||
}
|
||||
|
||||
public void setAdvertisingId(String value) {
|
||||
this.advertisingId = value;
|
||||
}
|
||||
|
||||
public String getVendorName() {
|
||||
return vendorName;
|
||||
}
|
||||
|
||||
public void setVendorName(String value) {
|
||||
this.vendorName = value;
|
||||
}
|
||||
|
||||
public String getModelName() {
|
||||
return modelName;
|
||||
}
|
||||
|
||||
public void setModelName(String value) {
|
||||
this.modelName = value;
|
||||
}
|
||||
|
||||
public String getModelNumber() {
|
||||
return modelNumber;
|
||||
}
|
||||
|
||||
public void setModelNumber(String value) {
|
||||
this.modelNumber = value;
|
||||
}
|
||||
|
||||
public String getModelRegion() {
|
||||
return modelRegion;
|
||||
}
|
||||
|
||||
public void setModelRegion(String value) {
|
||||
this.modelRegion = value;
|
||||
}
|
||||
|
||||
public boolean isTv() {
|
||||
return isTv;
|
||||
}
|
||||
|
||||
public void setIsTv(boolean value) {
|
||||
this.isTv = value;
|
||||
}
|
||||
|
||||
public boolean isStick() {
|
||||
return isStick;
|
||||
}
|
||||
|
||||
public void setIsStick(boolean value) {
|
||||
this.isStick = value;
|
||||
}
|
||||
|
||||
public String getUiResolution() {
|
||||
return uiResolution;
|
||||
}
|
||||
|
||||
public void setUiResolution(String value) {
|
||||
this.uiResolution = value;
|
||||
}
|
||||
|
||||
public boolean isSupportsEthernet() {
|
||||
return supportsEthernet;
|
||||
}
|
||||
|
||||
public void setSupportsEthernet(boolean value) {
|
||||
this.supportsEthernet = value;
|
||||
}
|
||||
|
||||
public String getWifiMac() {
|
||||
return wifiMac;
|
||||
}
|
||||
|
||||
public void setWifiMac(String value) {
|
||||
this.wifiMac = value;
|
||||
}
|
||||
|
||||
public String getWifiDriver() {
|
||||
return wifiDriver;
|
||||
}
|
||||
|
||||
public void setWifiDriver(String value) {
|
||||
this.wifiDriver = value;
|
||||
}
|
||||
|
||||
public boolean isHasWifiExtender() {
|
||||
return hasWifiExtender;
|
||||
}
|
||||
|
||||
public void setHasWifiExtender(boolean value) {
|
||||
this.hasWifiExtender = value;
|
||||
}
|
||||
|
||||
public boolean isHasWifi5GSupport() {
|
||||
return hasWifi5GSupport;
|
||||
}
|
||||
|
||||
public void setHasWifi5GSupport(boolean value) {
|
||||
this.hasWifi5GSupport = value;
|
||||
}
|
||||
|
||||
public boolean isCanUseWifiExtender() {
|
||||
return canUseWifiExtender;
|
||||
}
|
||||
|
||||
public void setCanUseWifiExtender(boolean value) {
|
||||
this.canUseWifiExtender = value;
|
||||
}
|
||||
|
||||
public String getEthernetMac() {
|
||||
return ethernetMac;
|
||||
}
|
||||
|
||||
public void setEthernetMac(String value) {
|
||||
this.ethernetMac = value;
|
||||
}
|
||||
|
||||
public String getNetworkType() {
|
||||
return networkType;
|
||||
}
|
||||
|
||||
public void setNetworkType(String value) {
|
||||
this.networkType = value;
|
||||
}
|
||||
|
||||
public String getFriendlyDeviceName() {
|
||||
return friendlyDeviceName;
|
||||
}
|
||||
|
||||
public void setFriendlyDeviceName(String value) {
|
||||
this.friendlyDeviceName = value;
|
||||
}
|
||||
|
||||
public String getFriendlyModelName() {
|
||||
return friendlyModelName;
|
||||
}
|
||||
|
||||
public void setFriendlyModelName(String value) {
|
||||
this.friendlyModelName = value;
|
||||
}
|
||||
|
||||
public String getDefaultDeviceName() {
|
||||
return defaultDeviceName;
|
||||
}
|
||||
|
||||
public void setDefaultDeviceName(String value) {
|
||||
this.defaultDeviceName = value;
|
||||
}
|
||||
|
||||
public String getUserDeviceName() {
|
||||
return userDeviceName;
|
||||
}
|
||||
|
||||
public void setUserDeviceName(String value) {
|
||||
this.userDeviceName = value;
|
||||
}
|
||||
|
||||
public String getUserDeviceLocation() {
|
||||
return userDeviceLocation;
|
||||
}
|
||||
|
||||
public void setUserDeviceLocation(String value) {
|
||||
this.userDeviceLocation = value;
|
||||
}
|
||||
|
||||
public String getBuildNumber() {
|
||||
return buildNumber;
|
||||
}
|
||||
|
||||
public void setBuildNumber(String value) {
|
||||
this.buildNumber = value;
|
||||
}
|
||||
|
||||
public String getSoftwareVersion() {
|
||||
return softwareVersion;
|
||||
}
|
||||
|
||||
public void setSoftwareVersion(String value) {
|
||||
this.softwareVersion = value;
|
||||
}
|
||||
|
||||
public String getSoftwareBuild() {
|
||||
return softwareBuild;
|
||||
}
|
||||
|
||||
public void setSoftwareBuild(String value) {
|
||||
this.softwareBuild = value;
|
||||
}
|
||||
|
||||
public boolean isSecureDevice() {
|
||||
return secureDevice;
|
||||
}
|
||||
|
||||
public void setSecureDevice(boolean value) {
|
||||
this.secureDevice = value;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(String value) {
|
||||
this.language = value;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public void setCountry(String value) {
|
||||
this.country = value;
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public void setLocale(String value) {
|
||||
this.locale = value;
|
||||
}
|
||||
|
||||
public boolean isTimeZoneAuto() {
|
||||
return timeZoneAuto;
|
||||
}
|
||||
|
||||
public void setTimeZoneAuto(boolean value) {
|
||||
this.timeZoneAuto = value;
|
||||
}
|
||||
|
||||
public String getTimeZone() {
|
||||
return timeZone;
|
||||
}
|
||||
|
||||
public void setTimeZone(String value) {
|
||||
this.timeZone = value;
|
||||
}
|
||||
|
||||
public String getTimeZoneName() {
|
||||
return timeZoneName;
|
||||
}
|
||||
|
||||
public void setTimeZoneName(String value) {
|
||||
this.timeZoneName = value;
|
||||
}
|
||||
|
||||
public String getTimeZoneTz() {
|
||||
return timeZoneTz;
|
||||
}
|
||||
|
||||
public void setTimeZoneTz(String value) {
|
||||
this.timeZoneTz = value;
|
||||
}
|
||||
|
||||
public int getTimeZoneOffset() {
|
||||
return timeZoneOffset;
|
||||
}
|
||||
|
||||
public void setTimeZoneOffset(int value) {
|
||||
this.timeZoneOffset = value;
|
||||
}
|
||||
|
||||
public String getClockFormat() {
|
||||
return clockFormat;
|
||||
}
|
||||
|
||||
public void setClockFormat(String value) {
|
||||
this.clockFormat = value;
|
||||
}
|
||||
|
||||
public int getUptime() {
|
||||
return uptime;
|
||||
}
|
||||
|
||||
public void setUptime(int value) {
|
||||
this.uptime = value;
|
||||
}
|
||||
|
||||
public String getPowerMode() {
|
||||
return powerMode;
|
||||
}
|
||||
|
||||
public void setPowerMode(String value) {
|
||||
this.powerMode = value;
|
||||
}
|
||||
|
||||
public boolean isSupportsSuspend() {
|
||||
return supportsSuspend;
|
||||
}
|
||||
|
||||
public void setSupportsSuspend(boolean value) {
|
||||
this.supportsSuspend = value;
|
||||
}
|
||||
|
||||
public boolean isSupportsFindRemote() {
|
||||
return supportsFindRemote;
|
||||
}
|
||||
|
||||
public void setSupportsFindRemote(boolean value) {
|
||||
this.supportsFindRemote = value;
|
||||
}
|
||||
|
||||
public boolean isFindRemoteIsPossible() {
|
||||
return findRemoteIsPossible;
|
||||
}
|
||||
|
||||
public void setFindRemoteIsPossible(boolean value) {
|
||||
this.findRemoteIsPossible = value;
|
||||
}
|
||||
|
||||
public boolean isSupportsAudioGuide() {
|
||||
return supportsAudioGuide;
|
||||
}
|
||||
|
||||
public void setSupportsAudioGuide(boolean value) {
|
||||
this.supportsAudioGuide = value;
|
||||
}
|
||||
|
||||
public boolean isSupportsRva() {
|
||||
return supportsRva;
|
||||
}
|
||||
|
||||
public void setSupportsRva(boolean value) {
|
||||
this.supportsRva = value;
|
||||
}
|
||||
|
||||
public boolean isDeveloperEnabled() {
|
||||
return developerEnabled;
|
||||
}
|
||||
|
||||
public void setDeveloperEnabled(boolean value) {
|
||||
this.developerEnabled = value;
|
||||
}
|
||||
|
||||
public String getKeyedDeveloperId() {
|
||||
return keyedDeveloperId;
|
||||
}
|
||||
|
||||
public void setKeyedDeveloperId(String value) {
|
||||
this.keyedDeveloperId = value;
|
||||
}
|
||||
|
||||
public boolean isSearchEnabled() {
|
||||
return searchEnabled;
|
||||
}
|
||||
|
||||
public void setSearchEnabled(boolean value) {
|
||||
this.searchEnabled = value;
|
||||
}
|
||||
|
||||
public boolean isSearchChannelsEnabled() {
|
||||
return searchChannelsEnabled;
|
||||
}
|
||||
|
||||
public void setSearchChannelsEnabled(boolean value) {
|
||||
this.searchChannelsEnabled = value;
|
||||
}
|
||||
|
||||
public boolean isVoiceSearchEnabled() {
|
||||
return voiceSearchEnabled;
|
||||
}
|
||||
|
||||
public void setVoiceSearchEnabled(boolean value) {
|
||||
this.voiceSearchEnabled = value;
|
||||
}
|
||||
|
||||
public boolean isNotificationsEnabled() {
|
||||
return notificationsEnabled;
|
||||
}
|
||||
|
||||
public void setNotificationsEnabled(boolean value) {
|
||||
this.notificationsEnabled = value;
|
||||
}
|
||||
|
||||
public boolean isNotificationsFirstUse() {
|
||||
return notificationsFirstUse;
|
||||
}
|
||||
|
||||
public void setNotificationsFirstUse(boolean value) {
|
||||
this.notificationsFirstUse = value;
|
||||
}
|
||||
|
||||
public boolean isSupportsPrivateListening() {
|
||||
return supportsPrivateListening;
|
||||
}
|
||||
|
||||
public void setSupportsPrivateListening(boolean value) {
|
||||
this.supportsPrivateListening = value;
|
||||
}
|
||||
|
||||
public boolean isHeadphonesConnected() {
|
||||
return headphonesConnected;
|
||||
}
|
||||
|
||||
public void setHeadphonesConnected(boolean value) {
|
||||
this.headphonesConnected = value;
|
||||
}
|
||||
|
||||
public boolean isSupportsEcsTextedit() {
|
||||
return supportsEcsTextedit;
|
||||
}
|
||||
|
||||
public void setSupportsEcsTextedit(boolean value) {
|
||||
this.supportsEcsTextedit = value;
|
||||
}
|
||||
|
||||
public boolean isSupportsEcsMicrophone() {
|
||||
return supportsEcsMicrophone;
|
||||
}
|
||||
|
||||
public void setSupportsEcsMicrophone(boolean value) {
|
||||
this.supportsEcsMicrophone = value;
|
||||
}
|
||||
|
||||
public boolean isSupportsWakeOnWlan() {
|
||||
return supportsWakeOnWlan;
|
||||
}
|
||||
|
||||
public void setSupportsWakeOnWlan(boolean value) {
|
||||
this.supportsWakeOnWlan = value;
|
||||
}
|
||||
|
||||
public boolean isHasPlayOnRoku() {
|
||||
return hasPlayOnRoku;
|
||||
}
|
||||
|
||||
public void setHasPlayOnRoku(boolean value) {
|
||||
this.hasPlayOnRoku = value;
|
||||
}
|
||||
|
||||
public boolean isHasMobileScreensaver() {
|
||||
return hasMobileScreensaver;
|
||||
}
|
||||
|
||||
public void setHasMobileScreensaver(boolean value) {
|
||||
this.hasMobileScreensaver = value;
|
||||
}
|
||||
|
||||
public String getSupportUrl() {
|
||||
return supportUrl;
|
||||
}
|
||||
|
||||
public void setSupportUrl(String value) {
|
||||
this.supportUrl = value;
|
||||
}
|
||||
|
||||
public String getGrandcentralVersion() {
|
||||
return grandcentralVersion;
|
||||
}
|
||||
|
||||
public void setGrandcentralVersion(String value) {
|
||||
this.grandcentralVersion = value;
|
||||
}
|
||||
|
||||
public String getTrcVersion() {
|
||||
return trcVersion;
|
||||
}
|
||||
|
||||
public void setTrcVersion(String value) {
|
||||
this.trcVersion = value;
|
||||
}
|
||||
|
||||
public String getTrcChannelVersion() {
|
||||
return trcChannelVersion;
|
||||
}
|
||||
|
||||
public void setTrcChannelVersion(String value) {
|
||||
this.trcChannelVersion = value;
|
||||
}
|
||||
|
||||
public String getDavinciVersion() {
|
||||
return davinciVersion;
|
||||
}
|
||||
|
||||
public void setDavinciVersion(String value) {
|
||||
this.davinciVersion = value;
|
||||
}
|
||||
}
|
@ -0,0 +1,380 @@
|
||||
/**
|
||||
* 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.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Maps the XML response from the Roku HTTP endpoint '/query/media-player' (Current stream playback meta-data)
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "player")
|
||||
public class Player {
|
||||
@XmlElement(name = "plugin")
|
||||
private Player.Plugin plugin = new Plugin();
|
||||
|
||||
@XmlElement(name = "format")
|
||||
private Player.Format format = new Format();
|
||||
|
||||
@XmlElement(name = "buffering")
|
||||
private Player.Buffering buffering = new Buffering();
|
||||
|
||||
@XmlElement(name = "new_stream")
|
||||
private Player.NewStream newStream = new NewStream();
|
||||
|
||||
@XmlElement(name = "position")
|
||||
private String position = "";
|
||||
|
||||
@XmlElement(name = "duration")
|
||||
private String duration = "";
|
||||
|
||||
@XmlElement(name = "is_live")
|
||||
private boolean isLive = false;
|
||||
|
||||
@XmlElement(name = "runtime")
|
||||
private String runtime = "";
|
||||
|
||||
@XmlElement(name = "stream_segment")
|
||||
private Player.StreamSegment streamSegment = new StreamSegment();
|
||||
|
||||
@XmlAttribute(name = "error")
|
||||
private Boolean error = false;
|
||||
|
||||
@XmlAttribute(name = "state")
|
||||
private String state = "";
|
||||
|
||||
public Player.Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
public void setPlugin(Player.Plugin value) {
|
||||
this.plugin = value;
|
||||
}
|
||||
|
||||
public Player.Format getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public void setFormat(Player.Format value) {
|
||||
this.format = value;
|
||||
}
|
||||
|
||||
public Player.Buffering getBuffering() {
|
||||
return buffering;
|
||||
}
|
||||
|
||||
public void setBuffering(Player.Buffering value) {
|
||||
this.buffering = value;
|
||||
}
|
||||
|
||||
public Player.NewStream getNewStream() {
|
||||
return newStream;
|
||||
}
|
||||
|
||||
public void setNewStream(Player.NewStream value) {
|
||||
this.newStream = value;
|
||||
}
|
||||
|
||||
public String getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(String value) {
|
||||
this.position = value;
|
||||
}
|
||||
|
||||
public String getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(String value) {
|
||||
this.duration = value;
|
||||
}
|
||||
|
||||
public boolean isIsLive() {
|
||||
return isLive;
|
||||
}
|
||||
|
||||
public void setIsLive(boolean value) {
|
||||
this.isLive = value;
|
||||
}
|
||||
|
||||
public String getRuntime() {
|
||||
return runtime;
|
||||
}
|
||||
|
||||
public void setRuntime(String value) {
|
||||
this.runtime = value;
|
||||
}
|
||||
|
||||
public Player.StreamSegment getStreamSegment() {
|
||||
return streamSegment;
|
||||
}
|
||||
|
||||
public void setStreamSegment(Player.StreamSegment value) {
|
||||
this.streamSegment = value;
|
||||
}
|
||||
|
||||
public Boolean isError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(Boolean value) {
|
||||
this.error = value;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String value) {
|
||||
this.state = value;
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class Buffering {
|
||||
@XmlAttribute(name = "current")
|
||||
private int current = -1;
|
||||
|
||||
@XmlAttribute(name = "max")
|
||||
private int max = -1;
|
||||
|
||||
@XmlAttribute(name = "target")
|
||||
private int target = -1;
|
||||
|
||||
public int getCurrent() {
|
||||
return current;
|
||||
}
|
||||
|
||||
public void setCurrent(int value) {
|
||||
this.current = value;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMax(int value) {
|
||||
this.max = value;
|
||||
}
|
||||
|
||||
public int getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(int value) {
|
||||
this.target = value;
|
||||
}
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "")
|
||||
public static class Format {
|
||||
@XmlAttribute(name = "audio")
|
||||
private String audio = "";
|
||||
|
||||
@XmlAttribute(name = "captions")
|
||||
private String captions = "";
|
||||
|
||||
@XmlAttribute(name = "container")
|
||||
private String container = "";
|
||||
|
||||
@XmlAttribute(name = "drm")
|
||||
private String drm = "";
|
||||
|
||||
@XmlAttribute(name = "video")
|
||||
private String video = "";
|
||||
|
||||
@XmlAttribute(name = "video_res")
|
||||
private String videoRes = "";
|
||||
|
||||
public String getAudio() {
|
||||
return audio;
|
||||
}
|
||||
|
||||
public void setAudio(String value) {
|
||||
this.audio = value;
|
||||
}
|
||||
|
||||
public String getCaptions() {
|
||||
return captions;
|
||||
}
|
||||
|
||||
public void setCaptions(String value) {
|
||||
this.captions = value;
|
||||
}
|
||||
|
||||
public String getContainer() {
|
||||
return container;
|
||||
}
|
||||
|
||||
public void setContainer(String value) {
|
||||
this.container = value;
|
||||
}
|
||||
|
||||
public String getDrm() {
|
||||
return drm;
|
||||
}
|
||||
|
||||
public void setDrm(String value) {
|
||||
this.drm = value;
|
||||
}
|
||||
|
||||
public String getVideo() {
|
||||
return video;
|
||||
}
|
||||
|
||||
public void setVideo(String value) {
|
||||
this.video = value;
|
||||
}
|
||||
|
||||
public String getVideoRes() {
|
||||
return videoRes;
|
||||
}
|
||||
|
||||
public void setVideoRes(String value) {
|
||||
this.videoRes = value;
|
||||
}
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class NewStream {
|
||||
@XmlAttribute(name = "speed")
|
||||
private String speed = "";
|
||||
|
||||
public String getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public void setSpeed(String value) {
|
||||
this.speed = value;
|
||||
}
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class Plugin {
|
||||
@XmlAttribute(name = "bandwidth")
|
||||
private String bandwidth = "";
|
||||
|
||||
@XmlAttribute(name = "id")
|
||||
private int id = -1;
|
||||
|
||||
@XmlAttribute(name = "name")
|
||||
private String name = "";
|
||||
|
||||
public String getBandwidth() {
|
||||
return bandwidth;
|
||||
}
|
||||
|
||||
public void setBandwidth(String value) {
|
||||
this.bandwidth = value;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String value) {
|
||||
this.name = value;
|
||||
}
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "")
|
||||
public static class StreamSegment {
|
||||
@XmlAttribute(name = "bitrate")
|
||||
private int bitrate = -1;
|
||||
|
||||
@XmlAttribute(name = "height")
|
||||
private int height = -1;
|
||||
|
||||
@XmlAttribute(name = "media_sequence")
|
||||
private int mediaSequence = -1;
|
||||
|
||||
@XmlAttribute(name = "segment_type")
|
||||
private String segmentType = "";
|
||||
|
||||
@XmlAttribute(name = "time")
|
||||
private int time = -1;
|
||||
|
||||
@XmlAttribute(name = "width")
|
||||
private int width = -1;
|
||||
|
||||
public int getBitrate() {
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
public void setBitrate(int value) {
|
||||
this.bitrate = value;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(int value) {
|
||||
this.height = value;
|
||||
}
|
||||
|
||||
public int getMediaSequence() {
|
||||
return mediaSequence;
|
||||
}
|
||||
|
||||
public void setMediaSequence(int value) {
|
||||
this.mediaSequence = value;
|
||||
}
|
||||
|
||||
public String getSegmentType() {
|
||||
return segmentType;
|
||||
}
|
||||
|
||||
public void setSegmentType(String value) {
|
||||
this.segmentType = value;
|
||||
}
|
||||
|
||||
public int getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(int value) {
|
||||
this.time = value;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(int value) {
|
||||
this.width = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* 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.handler;
|
||||
|
||||
import static org.openhab.binding.roku.internal.RokuBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
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.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.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RokuHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RokuHandler extends BaseThingHandler {
|
||||
private static final int DEFAULT_REFRESH_PERIOD_SEC = 10;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RokuHandler.class);
|
||||
private final HttpClient httpClient;
|
||||
private final RokuStateDescriptionOptionProvider stateDescriptionProvider;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private @Nullable ScheduledFuture<?> appListJob;
|
||||
|
||||
private RokuCommunicator communicator;
|
||||
private DeviceInfo deviceInfo = new DeviceInfo();
|
||||
private int refreshInterval = DEFAULT_REFRESH_PERIOD_SEC;
|
||||
|
||||
private Object sequenceLock = new Object();
|
||||
|
||||
public RokuHandler(Thing thing, HttpClient httpClient,
|
||||
RokuStateDescriptionOptionProvider stateDescriptionProvider) {
|
||||
super(thing);
|
||||
this.httpClient = httpClient;
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
this.communicator = new RokuCommunicator(httpClient, EMPTY, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing Roku handler");
|
||||
RokuConfiguration config = getConfigAs(RokuConfiguration.class);
|
||||
|
||||
final @Nullable String host = config.hostName;
|
||||
|
||||
if (host != null && !EMPTY.equals(host)) {
|
||||
this.communicator = new RokuCommunicator(httpClient, host, config.port);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Host Name must be specified");
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.refresh >= 10) {
|
||||
refreshInterval = config.refresh;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
try {
|
||||
deviceInfo = communicator.getDeviceInfo();
|
||||
thing.setProperty(PROPERTY_MODEL_NAME, deviceInfo.getModelName());
|
||||
thing.setProperty(PROPERTY_MODEL_NUMBER, deviceInfo.getModelNumber());
|
||||
thing.setProperty(PROPERTY_DEVICE_LOCAITON, deviceInfo.getUserDeviceLocation());
|
||||
thing.setProperty(PROPERTY_SERIAL_NUMBER, deviceInfo.getSerialNumber());
|
||||
thing.setProperty(PROPERTY_DEVICE_ID, deviceInfo.getDeviceId());
|
||||
thing.setProperty(PROPERTY_SOFTWARE_VERSION, deviceInfo.getSoftwareVersion());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (RokuHttpException e) {
|
||||
logger.debug("Unable to retrieve Roku device-info. Exception: {}", e.getMessage(), e);
|
||||
}
|
||||
startAutomaticRefresh();
|
||||
startAppListRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the job to periodically get status updates from the Roku
|
||||
*/
|
||||
private void startAutomaticRefresh() {
|
||||
ScheduledFuture<?> refreshJob = this.refreshJob;
|
||||
if (refreshJob == null || refreshJob.isCancelled()) {
|
||||
this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshPlayerState, 0, refreshInterval,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a status update from the Roku and update the channels
|
||||
*/
|
||||
private void refreshPlayerState() {
|
||||
synchronized (sequenceLock) {
|
||||
try {
|
||||
ActiveApp activeApp = communicator.getActiveApp();
|
||||
updateState(ACTIVE_APP, new StringType(activeApp.getApp().getId()));
|
||||
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)));
|
||||
|
||||
// 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);
|
||||
}
|
||||
} catch (RokuHttpException e) {
|
||||
logger.debug("Unable to retrieve Roku media-player info. Exception: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the job to periodically update list of apps installed on the the Roku
|
||||
*/
|
||||
private void startAppListRefresh() {
|
||||
ScheduledFuture<?> appListJob = this.appListJob;
|
||||
if (appListJob == null || appListJob.isCancelled()) {
|
||||
this.appListJob = scheduler.scheduleWithFixedDelay(this::refreshAppList, 10, 600, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the dropdown that lists all apps installed on the Roku
|
||||
*/
|
||||
private void refreshAppList() {
|
||||
synchronized (sequenceLock) {
|
||||
try {
|
||||
List<App> appList = communicator.getAppList();
|
||||
|
||||
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));
|
||||
|
||||
appList.forEach(app -> {
|
||||
appListOptions.add(new StateOption(app.getId(), app.getValue()));
|
||||
});
|
||||
|
||||
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), ACTIVE_APP),
|
||||
appListOptions);
|
||||
|
||||
} catch (RokuHttpException e) {
|
||||
logger.debug("Unable to retrieve Roku installed app-list. Exception: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
ScheduledFuture<?> refreshJob = this.refreshJob;
|
||||
if (refreshJob != null) {
|
||||
refreshJob.cancel(true);
|
||||
this.refreshJob = null;
|
||||
}
|
||||
|
||||
ScheduledFuture<?> appListJob = this.appListJob;
|
||||
if (appListJob != null) {
|
||||
appListJob.cancel(true);
|
||||
this.appListJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
logger.debug("Unsupported refresh command: {}", command);
|
||||
} else if (channelUID.getId().equals(BUTTON)) {
|
||||
synchronized (sequenceLock) {
|
||||
try {
|
||||
communicator.keyPress(command.toString());
|
||||
} 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(ACTIVE_APP)) {
|
||||
synchronized (sequenceLock) {
|
||||
try {
|
||||
String appId = command.toString();
|
||||
// Roku Home(-1) is not a real appId, just press the home button instead
|
||||
if (!ROKU_HOME_ID.equals(appId)) {
|
||||
communicator.launchApp(appId);
|
||||
} else {
|
||||
communicator.keyPress(ROKU_HOME_BUTTON);
|
||||
}
|
||||
} catch (RokuHttpException e) {
|
||||
logger.debug("Unable to launch app on Roku, appId: {}, Exception: {}", command, e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Unsupported command: {}", command);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="roku" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Roku Binding</name>
|
||||
<description>Controls Roku Streaming Media Players and TVs</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:roku:rokuconfig">
|
||||
<parameter name="hostName" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Host Name/IP Address</label>
|
||||
<description>Host Name or IP Address of the Roku device</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" min="1" max="65535" required="true">
|
||||
<label>Port</label>
|
||||
<description>Port for the ECP Connector of the Roku device</description>
|
||||
<default>8060</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" min="10" required="false" unit="s">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the Refresh Interval in Seconds</description>
|
||||
<default>10</default>
|
||||
<unitLabel>s</unitLabel>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="roku"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- Roku Player Thing -->
|
||||
<thing-type id="roku_player">
|
||||
<label>Roku</label>
|
||||
<description>
|
||||
A Roku Streaming Media Player
|
||||
</description>
|
||||
|
||||
<channels>
|
||||
<channel id="activeApp" typeId="activeApp"/>
|
||||
<channel id="button" typeId="button"/>
|
||||
<channel id="playMode" typeId="playMode"/>
|
||||
<channel id="timeElapsed" typeId="timeElapsed"/>
|
||||
<channel id="timeTotal" typeId="timeTotal"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="Model Name">unknown</property>
|
||||
<property name="Model Number">unknown</property>
|
||||
<property name="Device Location">unknown</property>
|
||||
<property name="Serial Number">unknown</property>
|
||||
<property name="Device Id">unknown</property>
|
||||
<property name="Software Version">unknown</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>uuid</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:roku:rokuconfig"/>
|
||||
</thing-type>
|
||||
|
||||
<!-- Roku TV Thing -->
|
||||
<thing-type id="roku_tv">
|
||||
<label>Roku TV</label>
|
||||
<description>
|
||||
A Roku Streaming Media TV
|
||||
</description>
|
||||
|
||||
<channels>
|
||||
<channel id="activeApp" typeId="activeApp"/>
|
||||
<channel id="button" typeId="buttonTv"/>
|
||||
<channel id="playMode" typeId="playMode"/>
|
||||
<channel id="timeElapsed" typeId="timeElapsed"/>
|
||||
<channel id="timeTotal" typeId="timeTotal"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="Model Name">unknown</property>
|
||||
<property name="Model Number">unknown</property>
|
||||
<property name="Device Location">unknown</property>
|
||||
<property name="Serial Number">unknown</property>
|
||||
<property name="Device Id">unknown</property>
|
||||
<property name="Software Version">unknown</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>uuid</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:roku:rokuconfig"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="button">
|
||||
<item-type>String</item-type>
|
||||
<label>Remote Button</label>
|
||||
<description>A Remote Button Press to Send to the Roku</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="Home">Home</option>
|
||||
<option value="Rev">Reverse</option>
|
||||
<option value="Fwd">Forward</option>
|
||||
<option value="Play">Play</option>
|
||||
<option value="Select">Select</option>
|
||||
<option value="Left">Left</option>
|
||||
<option value="Right">Right</option>
|
||||
<option value="Down">Down</option>
|
||||
<option value="Up">Up</option>
|
||||
<option value="Back">Back</option>
|
||||
<option value="InstantReplay">Instant Replay</option>
|
||||
<option value="Info">Info</option>
|
||||
<option value="Backspace">Backspace</option>
|
||||
<option value="Search">Search</option>
|
||||
<option value="Enter">Enter</option>
|
||||
<option value="FindRemote">Find Remote</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="buttonTv">
|
||||
<item-type>String</item-type>
|
||||
<label>Remote Button</label>
|
||||
<description>A Remote Button Press to Send to the Roku TV</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="Home">Home</option>
|
||||
<option value="Rev">Reverse</option>
|
||||
<option value="Fwd">Forward</option>
|
||||
<option value="Play">Play</option>
|
||||
<option value="Select">Select</option>
|
||||
<option value="Left">Left</option>
|
||||
<option value="Right">Right</option>
|
||||
<option value="Down">Down</option>
|
||||
<option value="Up">Up</option>
|
||||
<option value="Back">Back</option>
|
||||
<option value="InstantReplay">Instant Replay</option>
|
||||
<option value="Info">Info</option>
|
||||
<option value="Backspace">Backspace</option>
|
||||
<option value="Search">Search</option>
|
||||
<option value="Enter">Enter</option>
|
||||
<option value="FindRemote">Find Remote</option>
|
||||
<option value="VolumeUp">Volume Up</option>
|
||||
<option value="VolumeDown">Volume Down</option>
|
||||
<option value="VolumeMute">Volume Mute</option>
|
||||
<option value="ChannelUp">Channel Up</option>
|
||||
<option value="Channel Down">Channel Down</option>
|
||||
<option value="InputTuner">Input Tuner</option>
|
||||
<option value="InputHDMI1">Input HDMI1</option>
|
||||
<option value="InputHDMI2">Input HDMI2</option>
|
||||
<option value="InputHDMI3">Input HDMI3</option>
|
||||
<option value="InputHDMI4">Input HDMI4</option>
|
||||
<option value="InputAV1">Input AV1</option>
|
||||
<option value="PowerOff">Power Off</option>
|
||||
</options>
|
||||
</state>
|
||||
</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="playMode">
|
||||
<item-type>String</item-type>
|
||||
<label>Play Mode</label>
|
||||
<description>The Current Playback Mode</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="timeElapsed">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Playback Time</label>
|
||||
<description>The Current Playback Time Elapsed</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="timeTotal">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Total Time</label>
|
||||
<description>The Total Length of the Current Title</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -248,6 +248,7 @@
|
||||
<module>org.openhab.binding.rfxcom</module>
|
||||
<module>org.openhab.binding.rme</module>
|
||||
<module>org.openhab.binding.robonect</module>
|
||||
<module>org.openhab.binding.roku</module>
|
||||
<module>org.openhab.binding.rotel</module>
|
||||
<module>org.openhab.binding.russound</module>
|
||||
<module>org.openhab.binding.sagercaster</module>
|
||||
|
Loading…
Reference in New Issue
Block a user