2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.roku.internal.communication;
15 import java.io.StringReader;
16 import java.util.List;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
21 import javax.xml.bind.JAXBContext;
22 import javax.xml.bind.JAXBException;
23 import javax.xml.bind.Unmarshaller;
24 import javax.xml.stream.XMLStreamException;
25 import javax.xml.stream.XMLStreamReader;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jetty.client.HttpClient;
29 import org.eclipse.jetty.http.HttpMethod;
30 import org.openhab.binding.roku.internal.RokuHttpException;
31 import org.openhab.binding.roku.internal.dto.ActiveApp;
32 import org.openhab.binding.roku.internal.dto.Apps;
33 import org.openhab.binding.roku.internal.dto.Apps.App;
34 import org.openhab.binding.roku.internal.dto.DeviceInfo;
35 import org.openhab.binding.roku.internal.dto.Player;
36 import org.openhab.binding.roku.internal.dto.TvChannel;
37 import org.openhab.binding.roku.internal.dto.TvChannels;
38 import org.openhab.binding.roku.internal.dto.TvChannels.Channel;
41 * Methods for accessing the HTTP interface of the Roku
43 * @author Michael Lobstein - Initial contribution
46 public class RokuCommunicator {
47 private static final int REQUEST_TIMEOUT = 5000;
49 private final HttpClient httpClient;
51 private final String urlKeyPress;
52 private final String urlLaunchApp;
53 private final String urlLaunchTvChannel;
54 private final String urlQryDevice;
55 private final String urlQryActiveApp;
56 private final String urlQryApps;
57 private final String urlQryPlayer;
58 private final String urlQryActiveTvChannel;
59 private final String urlQryTvChannels;
61 public RokuCommunicator(HttpClient httpClient, String host, int port) {
62 this.httpClient = httpClient;
64 final String baseUrl = "http://" + host + ":" + port;
65 urlKeyPress = baseUrl + "/keypress/";
66 urlLaunchApp = baseUrl + "/launch/";
67 urlLaunchTvChannel = baseUrl + "/launch/tvinput.dtv?ch=";
68 urlQryDevice = baseUrl + "/query/device-info";
69 urlQryActiveApp = baseUrl + "/query/active-app";
70 urlQryApps = baseUrl + "/query/apps";
71 urlQryPlayer = baseUrl + "/query/media-player";
72 urlQryActiveTvChannel = baseUrl + "/query/tv-active-channel";
73 urlQryTvChannels = baseUrl + "/query/tv-channels";
77 * Send a keypress command to the Roku
79 * @param key The key code to send
82 public void keyPress(String key) throws RokuHttpException {
83 postCommand(urlKeyPress + key);
87 * Send a launch app command to the Roku
89 * @param appId The appId of the app to launch
92 public void launchApp(String appId) throws RokuHttpException {
93 postCommand(urlLaunchApp + appId);
97 * Send a TV channel change command to the Roku TV
99 * @param channelNumber The channel number of the channel to tune into, ie: 2.1
102 public void launchTvChannel(String channelNumber) throws RokuHttpException {
103 postCommand(urlLaunchTvChannel + channelNumber);
107 * Send a command to get device-info from the Roku and return a DeviceInfo object
109 * @return A DeviceInfo object populated with information about the connected Roku
110 * @throws RokuHttpException
112 public DeviceInfo getDeviceInfo() throws RokuHttpException {
114 JAXBContext ctx = JAXBUtils.JAXBCONTEXT_DEVICE_INFO;
116 final String response = getCommand(urlQryDevice);
117 Unmarshaller unmarshaller = ctx.createUnmarshaller();
118 if (unmarshaller != null) {
119 XMLStreamReader xsr = JAXBUtils.XMLINPUTFACTORY.createXMLStreamReader(new StringReader(response));
120 DeviceInfo device = (DeviceInfo) unmarshaller.unmarshal(xsr);
121 if (device != null) {
126 throw new RokuHttpException("No DeviceInfo model in response");
127 } catch (JAXBException | XMLStreamException e) {
128 throw new RokuHttpException("Exception creating DeviceInfo Unmarshaller: " + e.getLocalizedMessage());
133 * Send a command to get active-app from the Roku and return an ActiveApp object
135 * @return An ActiveApp object populated with information about the current running app on the Roku
136 * @throws RokuHttpException
138 public ActiveApp getActiveApp() throws RokuHttpException {
140 JAXBContext ctx = JAXBUtils.JAXBCONTEXT_ACTIVE_APP;
142 final String response = getCommand(urlQryActiveApp);
143 Unmarshaller unmarshaller = ctx.createUnmarshaller();
144 if (unmarshaller != null) {
145 XMLStreamReader xsr = JAXBUtils.XMLINPUTFACTORY.createXMLStreamReader(new StringReader(response));
146 ActiveApp activeApp = (ActiveApp) unmarshaller.unmarshal(xsr);
147 if (activeApp != null) {
152 throw new RokuHttpException("No ActiveApp model in response");
153 } catch (JAXBException | XMLStreamException e) {
154 throw new RokuHttpException("Exception creating ActiveApp Unmarshaller: " + e.getLocalizedMessage());
159 * Send a command to get the installed app list from the Roku and return a List of App objects
161 * @return A List of App objects for all apps currently installed on the Roku
162 * @throws RokuHttpException
164 public List<App> getAppList() throws RokuHttpException {
166 JAXBContext ctx = JAXBUtils.JAXBCONTEXT_APPS;
168 final String response = getCommand(urlQryApps);
169 Unmarshaller unmarshaller = ctx.createUnmarshaller();
170 if (unmarshaller != null) {
171 XMLStreamReader xsr = JAXBUtils.XMLINPUTFACTORY.createXMLStreamReader(new StringReader(response));
172 Apps appList = (Apps) unmarshaller.unmarshal(xsr);
173 if (appList != null) {
174 return appList.getApp();
178 throw new RokuHttpException("No AppList model in response");
179 } catch (JAXBException | XMLStreamException e) {
180 throw new RokuHttpException("Exception creating AppList Unmarshaller: " + e.getLocalizedMessage());
185 * Send a command to get media-player from the Roku and return a Player object
187 * @return A Player object populated with information about the current stream playing on the Roku
188 * @throws RokuHttpException
190 public Player getPlayerInfo() throws RokuHttpException {
192 JAXBContext ctx = JAXBUtils.JAXBCONTEXT_PLAYER;
194 final String response = getCommand(urlQryPlayer);
195 Unmarshaller unmarshaller = ctx.createUnmarshaller();
196 if (unmarshaller != null) {
197 XMLStreamReader xsr = JAXBUtils.XMLINPUTFACTORY.createXMLStreamReader(new StringReader(response));
198 Player playerInfo = (Player) unmarshaller.unmarshal(xsr);
199 if (playerInfo != null) {
204 throw new RokuHttpException("No Player info model in response");
205 } catch (JAXBException | XMLStreamException e) {
206 throw new RokuHttpException("Exception creating Player info Unmarshaller: " + e.getLocalizedMessage());
211 * Send a command to get tv-active-channel from the Roku TV and return a TvChannel object
213 * @return A TvChannel object populated with information about the current active TV Channel
214 * @throws RokuHttpException
216 public TvChannel getActiveTvChannel() throws RokuHttpException {
218 JAXBContext ctx = JAXBUtils.JAXBCONTEXT_TVCHANNEL;
220 final String response = getCommand(urlQryActiveTvChannel);
221 Unmarshaller unmarshaller = ctx.createUnmarshaller();
222 if (unmarshaller != null) {
223 XMLStreamReader xsr = JAXBUtils.XMLINPUTFACTORY.createXMLStreamReader(new StringReader(response));
224 TvChannel tvChannelInfo = (TvChannel) unmarshaller.unmarshal(xsr);
225 if (tvChannelInfo != null) {
226 return tvChannelInfo;
230 throw new RokuHttpException("No TvChannel info model in response");
231 } catch (JAXBException | XMLStreamException e) {
232 throw new RokuHttpException("Exception creating TvChannel info Unmarshaller: " + e.getLocalizedMessage());
237 * Send a command to get tv-channels from the Roku TV and return a list of Channel objects
239 * @return A List of Channel objects for all TV channels currently available on the Roku TV
240 * @throws RokuHttpException
242 public List<Channel> getTvChannelList() throws RokuHttpException {
244 JAXBContext ctx = JAXBUtils.JAXBCONTEXT_TVCHANNELS;
246 final String response = getCommand(urlQryTvChannels);
247 Unmarshaller unmarshaller = ctx.createUnmarshaller();
248 if (unmarshaller != null) {
249 XMLStreamReader xsr = JAXBUtils.XMLINPUTFACTORY.createXMLStreamReader(new StringReader(response));
250 TvChannels tvChannels = (TvChannels) unmarshaller.unmarshal(xsr);
251 if (tvChannels != null) {
252 return tvChannels.getChannel();
256 throw new RokuHttpException("No TvChannels info model in response");
257 } catch (JAXBException | XMLStreamException e) {
258 throw new RokuHttpException("Exception creating TvChannel info Unmarshaller: " + e.getLocalizedMessage());
263 * Sends a GET command to the Roku
265 * @param url The url to send with the command embedded in the URI
266 * @return The response content of the http request
267 * @throws RokuHttpException
269 private String getCommand(String url) throws RokuHttpException {
271 return httpClient.newRequest(url).method(HttpMethod.GET).timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS)
272 .send().getContentAsString();
273 } catch (TimeoutException | ExecutionException e) {
274 throw new RokuHttpException("Error executing GET command for URL: " + url, e);
275 } catch (InterruptedException e) {
276 Thread.currentThread().interrupt();
277 throw new RokuHttpException("InterruptedException executing GET command for URL: " + url, e);
282 * Sends a POST command to the Roku
284 * @param url The url to send with the command embedded in the URI
285 * @throws RokuHttpException
287 private void postCommand(String url) throws RokuHttpException {
289 httpClient.POST(url).method(HttpMethod.POST).timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).send();
290 } catch (TimeoutException | ExecutionException e) {
291 throw new RokuHttpException("Error executing POST command, URL: " + url, e);
292 } catch (InterruptedException e) {
293 Thread.currentThread().interrupt();
294 throw new RokuHttpException("InterruptedException executing POST command for URL: " + url, e);