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

[androiddebugbridge] Added mDNS discovery for Fire TV Stick #11881

Merged
merged 4 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions bundles/org.openhab.binding.androiddebugbridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ This is a sample of the mediaStateJSONConfig thing configuration:
`[{"name": "com.amazon.tv.launcher", "mode": "idle"},{"name": "org.jellyfin.androidtv", "mode": "wake_lock", "wakeLockPlayStates": [2,3]},{"name": "com.amazon.firetv.youtube", "mode": "wake_lock", "wakeLockPlayStates": [2]}]`

## Record/Send input events

As the execution of key events takes a while, you can use input events as an alternative way to control your device.

They are pretty device specific, so you should use the record-input and recorded-input channels to store/send those events.

An example of what you can do:

* You can send the command `UP` to the `record-input` channel the binding will then capture the events you send through your remote for the defined recordDuration config for the thing, so press once the UP key on your remote and wait a while.
* Now that you have recorded your input, you can send the `UP` command to the `recorded-input` event and it will send the recorded event to the android device.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

<feature name="openhab-binding-androiddebugbridge" description="Android Debug Bridge Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-mdns</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.androiddebugbridge/${project.version}</bundle>
</feature>
</features>
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
*/
package org.openhab.binding.androiddebugbridge.internal;

import java.util.Collections;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand All @@ -32,7 +31,7 @@ public class AndroidDebugBridgeBindingConstants {

// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_ANDROID_DEVICE = new ThingTypeUID(BINDING_ID, "android");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_ANDROID_DEVICE);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ANDROID_DEVICE);
// List of all Channel ids
public static final String KEY_EVENT_CHANNEL = "key-event";
public static final String TEXT_CHANNEL = "text";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.concurrent.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -88,7 +92,7 @@ public class AndroidDebugBridgeDevice {
private @Nullable AdbConnection connection;
private @Nullable Future<String> commandFuture;

AndroidDebugBridgeDevice(ScheduledExecutorService scheduler) {
public AndroidDebugBridgeDevice(ScheduledExecutorService scheduler) {
this.scheduler = scheduler;
}

Expand Down Expand Up @@ -260,10 +264,12 @@ public int getPowerWakeLock() throws InterruptedException, AndroidDebugBridgeDev
try {
return Integer.parseInt(lockResp.replace("\n", "").split("=")[1].trim());
} catch (NumberFormatException e) {
logger.debug("Unable to parse device wake lock: {}", e.getMessage());
String message = String.format("Unable to parse device wake-lock '%s'", lockResp);
logger.debug("{}: {}", message, e.getMessage());
throw new AndroidDebugBridgeDeviceReadException(message);
}
}
throw new AndroidDebugBridgeDeviceReadException("Unable to read wake lock");
throw new AndroidDebugBridgeDeviceReadException(String.format("Unable to read wake-lock '%s'", lockResp));
}

private void setVolume(int stream, int volume)
Expand Down Expand Up @@ -291,11 +297,16 @@ public String getSerialNo() throws AndroidDebugBridgeDeviceException, Interrupte
return getDeviceProp("ro.serialno");
}

public String getMacAddress() throws AndroidDebugBridgeDeviceException, InterruptedException,
AndroidDebugBridgeDeviceReadException, TimeoutException, ExecutionException {
return getDeviceProp("ro.boot.wifimacaddr").toLowerCase();
}

private String getDeviceProp(String name) throws AndroidDebugBridgeDeviceException, InterruptedException,
AndroidDebugBridgeDeviceReadException, TimeoutException, ExecutionException {
var propValue = runAdbShell("getprop", name, "&&", "sleep", "0.3").replace("\n", "").replace("\r", "");
if (propValue.length() == 0) {
throw new AndroidDebugBridgeDeviceReadException("Unable to get device property");
throw new AndroidDebugBridgeDeviceReadException(String.format("Unable to get device property '%s'", name));
}
return propValue;
}
Expand Down Expand Up @@ -382,7 +393,7 @@ public void connect() throws AndroidDebugBridgeDeviceException, InterruptedExcep
sock.connect(new InetSocketAddress(ip, port), (int) TimeUnit.SECONDS.toMillis(15));
} catch (IOException e) {
logger.debug("Error connecting to {}: [{}] {}", ip, e.getClass().getName(), e.getMessage());
if (e.getMessage().equals("Socket closed")) {
if ("Socket closed".equals(e.getMessage())) {
// Connection aborted by us
throw new InterruptedException();
}
Expand Down Expand Up @@ -415,14 +426,12 @@ private String runAdbShell(int commandTimeout, String... args)
var byteArrayOutputStream = new ByteArrayOutputStream();
String cmd = String.join(" ", args);
logger.debug("{} - shell:{}", ip, cmd);
try {
AdbStream stream = adb.open("shell:" + cmd);
try (AdbStream stream = adb.open("shell:" + cmd)) {
do {
byteArrayOutputStream.writeBytes(stream.read());
} while (!stream.isClosed());
} catch (IOException e) {
String message = e.getMessage();
if (message != null && !message.equals("Stream closed")) {
if (!"Stream closed".equals(e.getMessage())) {
throw e;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.openhab.binding.androiddebugbridge.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.androiddebugbridge.internal.discovery.AndroidDebugBridgeDiscoveryService;

/**
* The {@link AndroidDebugBridgeDiscoveryService} discover Android ADB Instances in the network.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.openhab.binding.androiddebugbridge.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.androiddebugbridge.internal.discovery.AndroidDebugBridgeDiscoveryService;

/**
* The {@link AndroidDebugBridgeDiscoveryService} discover Android ADB Instances in the network.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -70,12 +71,21 @@ public class AndroidDebugBridgeHandler extends BaseThingHandler {
private @Nullable ScheduledFuture<?> connectionCheckerSchedule;
private AndroidDebugBridgeMediaStatePackageConfig @Nullable [] packageConfigs = null;
private boolean deviceAwake = false;
/** Prevent a dispose/init cycle while this flag is set. Use for property updates */
private boolean ignoreConfigurationUpdate;

public AndroidDebugBridgeHandler(Thing thing) {
super(thing);
this.adbConnection = new AndroidDebugBridgeDevice(scheduler);
}

@Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
if (!ignoreConfigurationUpdate) {
super.handleConfigurationUpdate(configurationParameters);
}
}

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
var currentConfig = config;
Expand Down Expand Up @@ -348,6 +358,7 @@ public void checkConnection() {
logger.debug("Refresh device {} status", currentConfig.ip);
if (adbConnection.isConnected()) {
updateStatus(ThingStatus.ONLINE);
refreshProperties();
refreshStatus();
} else {
try {
Expand All @@ -361,17 +372,41 @@ public void checkConnection() {
}
if (adbConnection.isConnected()) {
updateStatus(ThingStatus.ONLINE);
refreshProperties();
refreshStatus();
}
}
} catch (InterruptedException ignored) {
} catch (AndroidDebugBridgeDeviceException | ExecutionException e) {
} catch (AndroidDebugBridgeDeviceException | AndroidDebugBridgeDeviceReadException | ExecutionException e) {
logger.debug("Connection checker error: {}", e.getMessage());
adbConnection.disconnect();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}

private void refreshProperties() throws InterruptedException, AndroidDebugBridgeDeviceException,
AndroidDebugBridgeDeviceReadException, ExecutionException {
// Add some information about the device
try {
Map<String, String> editProperties = editProperties();
editProperties.put(Thing.PROPERTY_SERIAL_NUMBER, adbConnection.getSerialNo());
editProperties.put(Thing.PROPERTY_MODEL_ID, adbConnection.getModel());
editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, adbConnection.getAndroidVersion());
editProperties.put(Thing.PROPERTY_VENDOR, adbConnection.getBrand());
try {
editProperties.put(Thing.PROPERTY_MAC_ADDRESS, adbConnection.getMacAddress());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the ro.boot.wifimacaddr property is not available or accessible on all devices but currently it looks like the Mac address is the only unique identifier available in both discovery service implementations.

} catch (AndroidDebugBridgeDeviceReadException e) {
logger.debug("Refresh properties error: {}", e.getMessage());
}
ignoreConfigurationUpdate = true;
updateProperties(editProperties);
ignoreConfigurationUpdate = false;
cweitkamp marked this conversation as resolved.
Show resolved Hide resolved
} catch (TimeoutException e) {
logger.debug("Refresh properties error: Timeout");
return;
}
}

private void refreshStatus() throws InterruptedException, AndroidDebugBridgeDeviceException, ExecutionException {
boolean awakeState;
boolean prevDeviceAwake = deviceAwake;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.androiddebugbridge.internal;
package org.openhab.binding.androiddebugbridge.internal.discovery;

import static org.openhab.binding.androiddebugbridge.internal.AndroidDebugBridgeBindingConstants.*;

Expand All @@ -22,7 +22,6 @@
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
Expand All @@ -31,6 +30,10 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.androiddebugbridge.internal.AndroidDebugBridgeBindingConfiguration;
import org.openhab.binding.androiddebugbridge.internal.AndroidDebugBridgeDevice;
import org.openhab.binding.androiddebugbridge.internal.AndroidDebugBridgeDeviceException;
import org.openhab.binding.androiddebugbridge.internal.AndroidDebugBridgeDeviceReadException;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
Expand All @@ -52,6 +55,7 @@
@NonNullByDefault
@Component(service = DiscoveryService.class, configurationPid = "discovery.androiddebugbridge")
public class AndroidDebugBridgeDiscoveryService extends AbstractDiscoveryService {

static final int TIMEOUT_MS = 60000;
private static final long DISCOVERY_RESULT_TTL_SEC = 300;
public static final String LOCAL_INTERFACE_IP = "127.0.0.1";
Expand All @@ -61,7 +65,7 @@ public class AndroidDebugBridgeDiscoveryService extends AbstractDiscoveryService
private boolean discoveryRunning = false;

@Activate
public AndroidDebugBridgeDiscoveryService(@Reference ConfigurationAdmin admin) {
public AndroidDebugBridgeDiscoveryService(final @Reference ConfigurationAdmin admin) {
super(SUPPORTED_THING_TYPES, TIMEOUT_MS, false);
this.admin = admin;
}
Expand Down Expand Up @@ -102,7 +106,8 @@ protected void startScan() {
int retries = 0;
while (retries < MAX_RETRIES) {
try {
discoverWithADB(currentIp, configuration.discoveryPort);
discoverWithADB(currentIp, configuration.discoveryPort,
new String(netint.getHardwareAddress()).toLowerCase());
} catch (AndroidDebugBridgeDeviceReadException | TimeoutException e) {
retries++;
if (retries < MAX_RETRIES) {
Expand All @@ -126,8 +131,9 @@ protected void startScan() {
}
}

private void discoverWithADB(String ip, int port) throws InterruptedException, AndroidDebugBridgeDeviceException,
AndroidDebugBridgeDeviceReadException, TimeoutException, ExecutionException {
private void discoverWithADB(String ip, int port, String macAddress)
throws InterruptedException, AndroidDebugBridgeDeviceException, AndroidDebugBridgeDeviceReadException,
TimeoutException, ExecutionException {
var device = new AndroidDebugBridgeDevice(scheduler);
device.configure(ip, port, 10, 0);
try {
Expand All @@ -137,8 +143,8 @@ private void discoverWithADB(String ip, int port) throws InterruptedException, A
String model = device.getModel();
String androidVersion = device.getAndroidVersion();
String brand = device.getBrand();
logger.debug("discovered: {} - {} - {} - {}", model, serialNo, androidVersion, brand);
onDiscoverResult(serialNo, ip, port, model, androidVersion, brand);
logger.debug("discovered: {} - {} - {} - {} - {}", model, serialNo, androidVersion, brand, macAddress);
onDiscoverResult(serialNo, ip, port, model, androidVersion, brand, macAddress);
} finally {
device.disconnect();
}
Expand All @@ -152,17 +158,23 @@ protected void stopScan() {
}

private void onDiscoverResult(String serialNo, String ip, int port, String model, String androidVersion,
String brand) {
Map<String, Object> properties = new HashMap<>();
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNo);
properties.put(PARAMETER_IP, ip);
properties.put(PARAMETER_PORT, port);
properties.put(Thing.PROPERTY_MODEL_ID, model);
properties.put(Thing.PROPERTY_VENDOR, brand);
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, androidVersion);
thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_ANDROID_DEVICE, serialNo))
.withTTL(DISCOVERY_RESULT_TTL_SEC).withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER)
.withProperties(properties).withLabel(String.format("%s (%s)", model, serialNo)).build());
String brand, String macAddress) {
String friendlyName = String.format("%s (%s)", model, ip);
thingDiscovered(
DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_ANDROID_DEVICE, macAddress.replaceAll(":", ""))) //
.withProperties(Map.of( //
PARAMETER_IP, ip, //
PARAMETER_PORT, port, //
Thing.PROPERTY_MAC_ADDRESS, macAddress, //
Thing.PROPERTY_SERIAL_NUMBER, serialNo, //
Thing.PROPERTY_MODEL_ID, model, //
Thing.PROPERTY_VENDOR, brand, //
Thing.PROPERTY_FIRMWARE_VERSION, androidVersion //
)) //
.withLabel(friendlyName) //
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS) //
.withTTL(DISCOVERY_RESULT_TTL_SEC) //
.build());
}

private @Nullable AndroidDebugBridgeBindingConfiguration getConfig() {
Expand Down
Loading