]> git.basschouten.com Git - openhab-addons.git/blob
7c1676d1b95554305bbd1ca4f7643ed7d735cd26
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.network.internal.discovery;
14
15 import static org.openhab.binding.network.internal.NetworkBindingConstants.*;
16
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.concurrent.ExecutorService;
23 import java.util.concurrent.Executors;
24 import java.util.concurrent.TimeUnit;
25 import java.util.stream.Collectors;
26 import java.util.stream.Stream;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.network.internal.NetworkBindingConfiguration;
31 import org.openhab.binding.network.internal.PresenceDetection;
32 import org.openhab.binding.network.internal.PresenceDetectionListener;
33 import org.openhab.binding.network.internal.PresenceDetectionValue;
34 import org.openhab.binding.network.internal.utils.NetworkUtils;
35 import org.openhab.core.config.core.Configuration;
36 import org.openhab.core.config.discovery.AbstractDiscoveryService;
37 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
38 import org.openhab.core.config.discovery.DiscoveryService;
39 import org.openhab.core.thing.ThingUID;
40 import org.osgi.service.component.annotations.Activate;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Deactivate;
43 import org.osgi.service.component.annotations.Modified;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * The {@link NetworkDiscoveryService} is responsible for discovering devices on
49  * the current Network. It uses every Network Interface which is connected to a network.
50  * It tries common TCP ports to connect to, ICMP pings and ARP pings.
51  *
52  * @author Marc Mettke - Initial contribution
53  * @author David Graeff - Rewritten
54  */
55 @NonNullByDefault
56 @Component(service = DiscoveryService.class, configurationPid = "discovery.network")
57 public class NetworkDiscoveryService extends AbstractDiscoveryService implements PresenceDetectionListener {
58     static final int PING_TIMEOUT_IN_MS = 500;
59     static final int MAXIMUM_IPS_PER_INTERFACE = 255;
60     private static final long DISCOVERY_RESULT_TTL = TimeUnit.MINUTES.toSeconds(10);
61     private final Logger logger = LoggerFactory.getLogger(NetworkDiscoveryService.class);
62
63     // TCP port 548 (Apple Filing Protocol (AFP))
64     // TCP port 554 (Windows share / Linux samba)
65     // TCP port 1025 (Xbox / MS-RPC)
66     private Set<Integer> tcpServicePorts = Collections
67             .unmodifiableSet(Stream.of(80, 548, 554, 1025).collect(Collectors.toSet()));
68     private Integer scannedIPcount = 0;
69     private @Nullable ExecutorService executorService = null;
70     private final NetworkBindingConfiguration configuration = new NetworkBindingConfiguration();
71     private final NetworkUtils networkUtils = new NetworkUtils();
72
73     public NetworkDiscoveryService() {
74         super(SUPPORTED_THING_TYPES_UIDS, (int) Math.round(
75                 new NetworkUtils().getNetworkIPs(MAXIMUM_IPS_PER_INTERFACE).size() * (PING_TIMEOUT_IN_MS / 1000.0)),
76                 false);
77     }
78
79     @Override
80     @Activate
81     public void activate(@Nullable Map<String, @Nullable Object> config) {
82         super.activate(config);
83         modified(config);
84     }
85
86     @Override
87     @Modified
88     protected void modified(@Nullable Map<String, @Nullable Object> config) {
89         super.modified(config);
90         // We update instead of replace the configuration object, so that if the user updates the
91         // configuration, the values are automatically available in all handlers. Because they all
92         // share the same instance.
93         configuration.update(new Configuration(config).as(NetworkBindingConfiguration.class));
94     }
95
96     @Override
97     @Deactivate
98     protected void deactivate() {
99         if (executorService != null) {
100             executorService.shutdown();
101         }
102         super.deactivate();
103     }
104
105     @Override
106     public void partialDetectionResult(PresenceDetectionValue value) {
107         final String ip = value.getHostAddress();
108         if (value.isPingReachable()) {
109             newPingDevice(ip);
110         } else if (value.isTCPServiceReachable()) {
111             List<Integer> tcpServices = value.getReachableTCPports();
112             for (int port : tcpServices) {
113                 newServiceDevice(ip, port);
114             }
115         }
116     }
117
118     @Override
119     public void finalDetectionResult(PresenceDetectionValue value) {
120     }
121
122     /**
123      * Starts the DiscoveryThread for each IP on each interface on the network
124      */
125     @Override
126     protected void startScan() {
127         if (executorService == null) {
128             executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
129         }
130         final ExecutorService service = executorService;
131         if (service == null) {
132             return;
133         }
134         removeOlderResults(getTimestampOfLastScan(), null);
135         logger.trace("Starting Network Device Discovery");
136
137         final Set<String> networkIPs = networkUtils.getNetworkIPs(MAXIMUM_IPS_PER_INTERFACE);
138         scannedIPcount = 0;
139
140         for (String ip : networkIPs) {
141             final PresenceDetection s = new PresenceDetection(this, 2000);
142             s.setHostname(ip);
143             s.setIOSDevice(true);
144             s.setUseDhcpSniffing(false);
145             s.setTimeout(PING_TIMEOUT_IN_MS);
146             // Ping devices
147             s.setUseIcmpPing(true);
148             s.setUseArpPing(true, configuration.arpPingToolPath, configuration.arpPingUtilMethod);
149             // TCP devices
150             s.setServicePorts(tcpServicePorts);
151
152             service.execute(() -> {
153                 Thread.currentThread().setName("Discovery thread " + ip);
154                 s.performPresenceDetection(true);
155                 synchronized (scannedIPcount) {
156                     scannedIPcount += 1;
157                     if (scannedIPcount == networkIPs.size()) {
158                         logger.trace("Scan of {} IPs successful", scannedIPcount);
159                         stopScan();
160                     }
161                 }
162             });
163         }
164     }
165
166     @Override
167     protected synchronized void stopScan() {
168         super.stopScan();
169         final ExecutorService service = executorService;
170         if (service == null) {
171             return;
172         }
173
174         try {
175             service.awaitTermination(PING_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
176         } catch (InterruptedException e) {
177             Thread.currentThread().interrupt(); // Reset interrupt flag
178         }
179         service.shutdown();
180         executorService = null;
181     }
182
183     public static ThingUID createServiceUID(String ip, int tcpPort) {
184         // uid must not contains dots
185         return new ThingUID(SERVICE_DEVICE, ip.replace('.', '_') + "_" + String.valueOf(tcpPort));
186     }
187
188     /**
189      * Submit newly discovered devices. This method is called by the spawned threads in {@link startScan}.
190      *
191      * @param ip The device IP
192      * @param tcpPort The TCP port
193      */
194     public void newServiceDevice(String ip, int tcpPort) {
195         logger.trace("Found reachable service for device with IP address {} on port {}", ip, tcpPort);
196
197         String label;
198         // TCP port 548 (Apple Filing Protocol (AFP))
199         // TCP port 554 (Windows share / Linux samba)
200         // TCP port 1025 (Xbox / MS-RPC)
201         switch (tcpPort) {
202             case 80:
203                 label = "Device providing a Webserver";
204                 break;
205             case 548:
206                 label = "Device providing the Apple AFP Service";
207                 break;
208             case 554:
209                 label = "Device providing Network/Samba Shares";
210                 break;
211             case 1025:
212                 label = "Device providing Xbox/MS-RPC Capability";
213                 break;
214             default:
215                 label = "Network Device";
216         }
217         label += " (" + ip + ":" + tcpPort + ")";
218
219         Map<String, Object> properties = new HashMap<>();
220         properties.put(PARAMETER_HOSTNAME, ip);
221         properties.put(PARAMETER_PORT, tcpPort);
222         thingDiscovered(DiscoveryResultBuilder.create(createServiceUID(ip, tcpPort)).withTTL(DISCOVERY_RESULT_TTL)
223                 .withProperties(properties).withLabel(label).build());
224     }
225
226     public static ThingUID createPingUID(String ip) {
227         // uid must not contains dots
228         return new ThingUID(PING_DEVICE, ip.replace('.', '_'));
229     }
230
231     /**
232      * Submit newly discovered devices. This method is called by the spawned threads in {@link startScan}.
233      *
234      * @param ip The device IP
235      */
236     public void newPingDevice(String ip) {
237         logger.trace("Found pingable network device with IP address {}", ip);
238
239         Map<String, Object> properties = new HashMap<>();
240         properties.put(PARAMETER_HOSTNAME, ip);
241         thingDiscovered(DiscoveryResultBuilder.create(createPingUID(ip)).withTTL(DISCOVERY_RESULT_TTL)
242                 .withProperties(properties).withLabel("Network Device (" + ip + ")").build());
243     }
244 }