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.rfxcom/ @martinvw @paulianttila
|
||||||
/bundles/org.openhab.binding.rme/ @kgoderis
|
/bundles/org.openhab.binding.rme/ @kgoderis
|
||||||
/bundles/org.openhab.binding.robonect/ @reyem
|
/bundles/org.openhab.binding.robonect/ @reyem
|
||||||
|
/bundles/org.openhab.binding.roku/ @mlobstein
|
||||||
/bundles/org.openhab.binding.rotel/ @lolodomo
|
/bundles/org.openhab.binding.rotel/ @lolodomo
|
||||||
/bundles/org.openhab.binding.russound/ @tmrobert8
|
/bundles/org.openhab.binding.russound/ @tmrobert8
|
||||||
/bundles/org.openhab.binding.sagercaster/ @clinique
|
/bundles/org.openhab.binding.sagercaster/ @clinique
|
||||||
|
@ -1071,6 +1071,11 @@
|
|||||||
<artifactId>org.openhab.binding.robonect</artifactId>
|
<artifactId>org.openhab.binding.robonect</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.binding.roku</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
<artifactId>org.openhab.binding.rotel</artifactId>
|
<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.rfxcom</module>
|
||||||
<module>org.openhab.binding.rme</module>
|
<module>org.openhab.binding.rme</module>
|
||||||
<module>org.openhab.binding.robonect</module>
|
<module>org.openhab.binding.robonect</module>
|
||||||
|
<module>org.openhab.binding.roku</module>
|
||||||
<module>org.openhab.binding.rotel</module>
|
<module>org.openhab.binding.rotel</module>
|
||||||
<module>org.openhab.binding.russound</module>
|
<module>org.openhab.binding.russound</module>
|
||||||
<module>org.openhab.binding.sagercaster</module>
|
<module>org.openhab.binding.sagercaster</module>
|
||||||
|
Loading…
Reference in New Issue
Block a user