Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[roku] Check for ECP Limited Mode #17925

Merged
merged 2 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bundles/org.openhab.binding.roku/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
This binding connects Roku streaming media players and Roku TVs to openHAB.
The Roku device must support the Roku ECP protocol REST API.

In order for the binding to control the Roku, the following setting:
**Settings-> System-> Advanced system settings-> Control by mobile apps**
must be configured as `Enabled` or `Permissive`.

## Supported Things

There are two supported thing types, which represent either a standalone Roku device or a Roku TV.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2024 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 RokuLimitedModeException} extends RokuHttpException
*
* @author Michael Lobstein - Initial contribution
*/
@NonNullByDefault
public class RokuLimitedModeException extends RokuHttpException {
private static final long serialVersionUID = 1L;

public RokuLimitedModeException(String errorMessage, Throwable t) {
super(errorMessage, t);
}

public RokuLimitedModeException(String errorMessage) {
super(errorMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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.RokuLimitedModeException;
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;
Expand All @@ -47,6 +48,7 @@
@NonNullByDefault
public class RokuCommunicator {
private static final int REQUEST_TIMEOUT = 5000;
private static final String LIMITED_MODE_RESPONSE = "ECP command not allowed";

private final Logger logger = LoggerFactory.getLogger(RokuCommunicator.class);
private final HttpClient httpClient;
Expand Down Expand Up @@ -283,8 +285,12 @@ public List<Channel> getTvChannelList() throws RokuHttpException {
*/
private String getCommand(String url) throws RokuHttpException {
try {
return httpClient.newRequest(url).method(HttpMethod.GET).timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS)
.send().getContentAsString();
final String response = httpClient.newRequest(url).method(HttpMethod.GET)
.timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).send().getContentAsString();
if (response != null && response.contains(LIMITED_MODE_RESPONSE)) {
throw new RokuLimitedModeException(url + ": " + response);
}
return response != null ? response : "";
} catch (TimeoutException | ExecutionException e) {
throw new RokuHttpException("Error executing GET command for URL: " + url, e);
} catch (InterruptedException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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.RokuLimitedModeException;
import org.openhab.binding.roku.internal.RokuStateDescriptionOptionProvider;
import org.openhab.binding.roku.internal.communication.RokuCommunicator;
import org.openhab.binding.roku.internal.dto.Apps.App;
Expand Down Expand Up @@ -73,6 +74,7 @@ public class RokuHandler extends BaseThingHandler {
private DeviceInfo deviceInfo = new DeviceInfo();
private int refreshInterval = DEFAULT_REFRESH_PERIOD_SEC;
private boolean tvActive = false;
private int limitedMode = -1;
private Map<String, String> appMap = new HashMap<>();

private Object sequenceLock = new Object();
Expand Down Expand Up @@ -175,18 +177,20 @@ private void refreshPlayerState() {
}
tvActive = false;
}
updateStatus(ThingStatus.ONLINE);
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku active-app info. Exception: {}", e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
return;
}

// On the home app and when using the TV or TV inputs, do not update the play mode or time channels
if (!ROKU_HOME_ID.equals(activeAppId) && !activeAppId.contains(TV_INPUT)) {
// if in limitedMode, keep checking getPlayerInfo to see if the error goes away
if ((!ROKU_HOME_ID.equals(activeAppId) && !activeAppId.contains(TV_INPUT)) || limitedMode != 0) {
try {
Player playerInfo = communicator.getPlayerInfo();
limitedMode = 0;
// When nothing playing, 'close' is reported, replace with 'stop'
updateState(PLAY_MODE, new StringType(playerInfo.getState().replaceAll(CLOSE, STOP)));
updateState(PLAY_MODE, new StringType(playerInfo.getState().replace(CLOSE, STOP)));
updateState(CONTROL,
PLAY.equalsIgnoreCase(playerInfo.getState()) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);

Expand All @@ -208,9 +212,13 @@ private void refreshPlayerState() {
}
} catch (NumberFormatException e) {
logger.debug("Unable to parse playerInfo integer value. Exception: {}", e.getMessage());
} catch (RokuLimitedModeException e) {
logger.debug("RokuLimitedModeException: {}", e.getMessage());
limitedMode = 1;
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku media-player info. Exception: {}", e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
return;
}
} else {
updateState(PLAY_MODE, UnDefType.UNDEF);
Expand All @@ -221,6 +229,7 @@ private void refreshPlayerState() {
if (thingTypeUID.equals(THING_TYPE_ROKU_TV) && tvActive) {
try {
TvChannel tvChannel = communicator.getActiveTvChannel();
limitedMode = 0;
updateState(ACTIVE_CHANNEL, new StringType(tvChannel.getChannel().getNumber()));
updateState(SIGNAL_MODE, new StringType(tvChannel.getChannel().getSignalMode()));
updateState(SIGNAL_QUALITY,
Expand All @@ -229,10 +238,21 @@ private void refreshPlayerState() {
updateState(PROGRAM_TITLE, new StringType(tvChannel.getChannel().getProgramTitle()));
updateState(PROGRAM_DESCRIPTION, new StringType(tvChannel.getChannel().getProgramDescription()));
updateState(PROGRAM_RATING, new StringType(tvChannel.getChannel().getProgramRatings()));
} catch (RokuLimitedModeException e) {
logger.debug("RokuLimitedModeException: {}", e.getMessage());
limitedMode = 1;
} catch (RokuHttpException e) {
logger.debug("Unable to retrieve Roku tv-active-channel info. Exception: {}", e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
return;
}
}

if (limitedMode < 1) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.limited");
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,7 @@ channel-type.roku.timeElapsed.label = Playback Time
channel-type.roku.timeElapsed.description = The Current Playback Time Elapsed
channel-type.roku.timeTotal.label = Total Time
channel-type.roku.timeTotal.description = The Total Length of the Current Title

# error status descriptions

error.limited = Roku device is configured incorrectly - see README