Skip to content

Commit

Permalink
[androiddebugbridge] Added mDNS discovery for Fire TV Stick (openhab#…
Browse files Browse the repository at this point in the history
…11881)

* Added mDNS discovery for Fire TV Stick

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
  • Loading branch information
cweitkamp authored and moesterheld committed Jan 18, 2022
1 parent 80aab31 commit c43744a
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 42 deletions.
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 @@ -78,10 +79,7 @@ public AndroidDebugBridgeHandler(Thing thing) {

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
var currentConfig = config;
if (currentConfig == null) {
return;
}
AndroidDebugBridgeConfiguration currentConfig = config;
try {
if (!adbConnection.isConnected()) {
// try reconnect
Expand Down Expand Up @@ -305,7 +303,7 @@ private void handleMediaControlCommand(ChannelUID channelUID, Command command)

@Override
public void initialize() {
var currentConfig = getConfigAs(AndroidDebugBridgeConfiguration.class);
AndroidDebugBridgeConfiguration currentConfig = getConfigAs(AndroidDebugBridgeConfiguration.class);
config = currentConfig;
var mediaStateJSONConfig = currentConfig.mediaStateJSONConfig;
if (mediaStateJSONConfig != null && !mediaStateJSONConfig.isEmpty()) {
Expand Down Expand Up @@ -340,14 +338,12 @@ public void dispose() {
}

public void checkConnection() {
var currentConfig = config;
if (currentConfig == null) {
return;
}
AndroidDebugBridgeConfiguration currentConfig = config;
try {
logger.debug("Refresh device {} status", currentConfig.ip);
if (adbConnection.isConnected()) {
updateStatus(ThingStatus.ONLINE);
refreshProperties();
refreshStatus();
} else {
try {
Expand All @@ -361,17 +357,39 @@ 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());
} catch (AndroidDebugBridgeDeviceReadException e) {
logger.debug("Refresh properties error: {}", e.getMessage());
}
updateProperties(editProperties);
} 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

0 comments on commit c43744a

Please sign in to comment.