]> git.basschouten.com Git - openhab-addons.git/blob
ef0de0dcd0b9a31d306228f470045bf61bffec1a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.androiddebugbridge.internal;
14
15 import static org.openhab.binding.androiddebugbridge.internal.AndroidDebugBridgeBindingConstants.*;
16
17 import java.io.IOException;
18 import java.net.Inet4Address;
19 import java.net.InetAddress;
20 import java.net.NetworkInterface;
21 import java.net.SocketException;
22 import java.util.Collections;
23 import java.util.Dictionary;
24 import java.util.Enumeration;
25 import java.util.HashMap;
26 import java.util.Map;
27 import java.util.concurrent.ExecutionException;
28 import java.util.concurrent.TimeoutException;
29 import java.util.function.Function;
30 import java.util.stream.Collectors;
31
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.openhab.core.config.discovery.AbstractDiscoveryService;
35 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
36 import org.openhab.core.config.discovery.DiscoveryService;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingUID;
39 import org.osgi.service.cm.Configuration;
40 import org.osgi.service.cm.ConfigurationAdmin;
41 import org.osgi.service.component.annotations.Activate;
42 import org.osgi.service.component.annotations.Component;
43 import org.osgi.service.component.annotations.Reference;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * The {@link AndroidDebugBridgeDiscoveryService} discover Android ADB Instances in the network.
49  *
50  * @author Miguel Alvarez - Initial contribution
51  */
52 @NonNullByDefault
53 @Component(service = DiscoveryService.class, configurationPid = "discovery.androiddebugbridge")
54 public class AndroidDebugBridgeDiscoveryService extends AbstractDiscoveryService {
55     static final int TIMEOUT_MS = 60000;
56     private static final long DISCOVERY_RESULT_TTL_SEC = 300;
57     public static final String LOCAL_INTERFACE_IP = "127.0.0.1";
58     public static final int MAX_RETRIES = 2;
59     private final Logger logger = LoggerFactory.getLogger(AndroidDebugBridgeDiscoveryService.class);
60     private final ConfigurationAdmin admin;
61     private boolean discoveryRunning = false;
62
63     @Activate
64     public AndroidDebugBridgeDiscoveryService(@Reference ConfigurationAdmin admin) {
65         super(SUPPORTED_THING_TYPES, TIMEOUT_MS, false);
66         this.admin = admin;
67     }
68
69     @Override
70     protected void startScan() {
71         logger.debug("scan started: searching android devices");
72         discoveryRunning = true;
73         Enumeration<NetworkInterface> nets;
74         AndroidDebugBridgeBindingConfiguration configuration = getConfig();
75         if (configuration == null) {
76             return;
77         }
78         try {
79             nets = NetworkInterface.getNetworkInterfaces();
80             for (NetworkInterface netint : Collections.list(nets)) {
81                 Enumeration<InetAddress> inetAddresses = netint.getInetAddresses();
82                 for (InetAddress inetAddress : Collections.list(inetAddresses)) {
83                     if (!discoveryRunning) {
84                         break;
85                     }
86                     if (!(inetAddress instanceof Inet4Address)
87                             || inetAddress.getHostAddress().equals(LOCAL_INTERFACE_IP)) {
88                         continue;
89                     }
90                     String[] ipParts = inetAddress.getHostAddress().split("\\.");
91                     for (int i = configuration.discoveryIpRangeMin; i <= configuration.discoveryIpRangeMax; i++) {
92                         if (!discoveryRunning) {
93                             break;
94                         }
95                         ipParts[3] = Integer.toString(i);
96                         String currentIp = String.join(".", ipParts);
97                         try {
98                             var currentAddress = InetAddress.getByName(currentIp);
99                             logger.debug("address: {}", currentIp);
100                             if (currentAddress.isReachable(configuration.discoveryReachableMs)) {
101                                 logger.debug("Reachable ip: {}", currentIp);
102                                 int retries = 0;
103                                 while (retries < MAX_RETRIES) {
104                                     try {
105                                         discoverWithADB(currentIp, configuration.discoveryPort);
106                                     } catch (AndroidDebugBridgeDeviceReadException | TimeoutException e) {
107                                         retries++;
108                                         if (retries < MAX_RETRIES) {
109                                             logger.debug("retrying - pending {}", MAX_RETRIES - retries);
110                                             continue;
111                                         }
112                                         throw e;
113                                     }
114                                     break;
115                                 }
116                             }
117                         } catch (IOException | AndroidDebugBridgeDeviceException | AndroidDebugBridgeDeviceReadException
118                                 | TimeoutException | ExecutionException e) {
119                             logger.debug("Error connecting to device at {}: {}", currentIp, e.getMessage());
120                         }
121                     }
122                 }
123             }
124         } catch (SocketException | InterruptedException e) {
125             logger.warn("Error while discovering: {}", e.getMessage());
126         }
127     }
128
129     private void discoverWithADB(String ip, int port) throws InterruptedException, AndroidDebugBridgeDeviceException,
130             AndroidDebugBridgeDeviceReadException, TimeoutException, ExecutionException {
131         var device = new AndroidDebugBridgeDevice(scheduler);
132         device.configure(ip, port, 10, 0);
133         try {
134             device.connect();
135             logger.debug("connected adb at {}:{}", ip, port);
136             String serialNo = device.getSerialNo();
137             String model = device.getModel();
138             String androidVersion = device.getAndroidVersion();
139             String brand = device.getBrand();
140             logger.debug("discovered: {} - {} - {} - {}", model, serialNo, androidVersion, brand);
141             onDiscoverResult(serialNo, ip, port, model, androidVersion, brand);
142         } finally {
143             device.disconnect();
144         }
145     }
146
147     @Override
148     protected void stopScan() {
149         super.stopScan();
150         discoveryRunning = false;
151         logger.debug("scan stopped");
152     }
153
154     private void onDiscoverResult(String serialNo, String ip, int port, String model, String androidVersion,
155             String brand) {
156         Map<String, Object> properties = new HashMap<>();
157         properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNo);
158         properties.put(PARAMETER_IP, ip);
159         properties.put(PARAMETER_PORT, port);
160         properties.put(Thing.PROPERTY_MODEL_ID, model);
161         properties.put(Thing.PROPERTY_VENDOR, brand);
162         properties.put(Thing.PROPERTY_FIRMWARE_VERSION, androidVersion);
163         thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_ANDROID_DEVICE, serialNo))
164                 .withTTL(DISCOVERY_RESULT_TTL_SEC).withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER)
165                 .withProperties(properties).withLabel(String.format("%s (%s)", model, serialNo)).build());
166     }
167
168     private @Nullable AndroidDebugBridgeBindingConfiguration getConfig() {
169         try {
170             Configuration configOnline = admin.getConfiguration(BINDING_CONFIGURATION_PID, null);
171             if (configOnline != null) {
172                 Dictionary<String, Object> props = configOnline.getProperties();
173                 if (props != null) {
174                     Map<String, Object> propMap = Collections.list(props.keys()).stream()
175                             .collect(Collectors.toMap(Function.identity(), props::get));
176                     return new org.openhab.core.config.core.Configuration(propMap)
177                             .as(AndroidDebugBridgeBindingConfiguration.class);
178                 }
179             }
180         } catch (IOException e) {
181             logger.warn("Unable to read configuration: {}", e.getMessage());
182         }
183         return null;
184     }
185 }