[roku] Check for ECP Limited Mode (#17925)

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
This commit is contained in:
mlobstein 2024-12-19 13:38:08 -06:00 committed by GitHub
parent 254f2f5886
commit 5d40a719f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 72 additions and 5 deletions

View File

@ -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.

View File

@ -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);
}
}

View File

@ -28,6 +28,7 @@ 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.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;
@ -47,6 +48,7 @@ import org.slf4j.LoggerFactory;
@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;
@ -283,8 +285,12 @@ public class RokuCommunicator {
*/
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) {

View File

@ -26,6 +26,7 @@ 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.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;
@ -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();
@ -175,18 +177,20 @@ public class RokuHandler extends BaseThingHandler {
}
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);
@ -208,9 +212,13 @@ public class RokuHandler extends BaseThingHandler {
}
} 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);
@ -221,6 +229,7 @@ public class RokuHandler extends BaseThingHandler {
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,
@ -229,10 +238,21 @@ public class RokuHandler extends BaseThingHandler {
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");
}
}
}

View File

@ -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