]> git.basschouten.com Git - openhab-addons.git/blob
67cb46bd87b6e1edb40e775535045c73d0c51307
[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.pilight.internal.discovery;
14
15 import java.io.*;
16 import java.net.*;
17 import java.nio.charset.StandardCharsets;
18 import java.util.*;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.pilight.internal.PilightBindingConstants;
25 import org.openhab.core.config.discovery.AbstractDiscoveryService;
26 import org.openhab.core.config.discovery.DiscoveryResult;
27 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
28 import org.openhab.core.config.discovery.DiscoveryService;
29 import org.openhab.core.thing.ThingTypeUID;
30 import org.openhab.core.thing.ThingUID;
31 import org.osgi.service.component.annotations.Component;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * The {@link PilightBridgeDiscoveryService} is responsible for discovering new pilight daemons on the network
37  * by sending a ssdp multicast request via udp.
38  *
39  * @author Niklas Dörfler - Initial contribution
40  */
41 @NonNullByDefault
42 @Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.pilight")
43 public class PilightBridgeDiscoveryService extends AbstractDiscoveryService {
44
45     private static final int AUTODISCOVERY_SEARCH_TIME_SEC = 5;
46     private static final int AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC = 60 * 10;
47
48     private static final String SSDP_DISCOVERY_REQUEST_MESSAGE = "M-SEARCH * HTTP/1.1\r\n"
49             + "Host:239.255.255.250:1900\r\n" + "ST:urn:schemas-upnp-org:service:pilight:1\r\n"
50             + "Man:\"ssdp:discover\"\r\n" + "MX:3\r\n\r\n";
51     public static final String SSDP_MULTICAST_ADDRESS = "239.255.255.250";
52     public static final int SSDP_PORT = 1900;
53     public static final int SSDP_WAIT_TIMEOUT = 2000; // in milliseconds
54
55     private final Logger logger = LoggerFactory.getLogger(PilightBridgeDiscoveryService.class);
56
57     private @Nullable ScheduledFuture<?> backgroundDiscoveryJob;
58
59     public PilightBridgeDiscoveryService() throws IllegalArgumentException {
60         super(getSupportedThingTypeUIDs(), AUTODISCOVERY_SEARCH_TIME_SEC, true);
61     }
62
63     public static Set<ThingTypeUID> getSupportedThingTypeUIDs() {
64         return Collections.singleton(PilightBindingConstants.THING_TYPE_BRIDGE);
65     }
66
67     @Override
68     protected void startScan() {
69         logger.debug("Pilight bridge discovery scan started");
70         removeOlderResults(getTimestampOfLastScan());
71         try {
72             List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
73             for (NetworkInterface nic : interfaces) {
74                 Enumeration<InetAddress> inetAddresses = nic.getInetAddresses();
75                 for (InetAddress inetAddress : Collections.list(inetAddresses)) {
76                     if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
77                         DatagramSocket ssdp = new DatagramSocket(
78                                 new InetSocketAddress(inetAddress.getHostAddress(), 0));
79                         byte[] buff = SSDP_DISCOVERY_REQUEST_MESSAGE.getBytes(StandardCharsets.UTF_8);
80                         DatagramPacket sendPack = new DatagramPacket(buff, buff.length);
81                         sendPack.setAddress(InetAddress.getByName(SSDP_MULTICAST_ADDRESS));
82                         sendPack.setPort(SSDP_PORT);
83                         ssdp.send(sendPack);
84                         ssdp.setSoTimeout(SSDP_WAIT_TIMEOUT);
85
86                         boolean loop = true;
87                         while (loop) {
88                             DatagramPacket recvPack = new DatagramPacket(new byte[1024], 1024);
89                             ssdp.receive(recvPack);
90                             byte[] recvData = recvPack.getData();
91
92                             final Scanner scanner = new Scanner(new ByteArrayInputStream(recvData),
93                                     StandardCharsets.UTF_8);
94                             loop = scanner.findAll("Location:([0-9.]+):(.*)").peek(matchResult -> {
95                                 final String server = matchResult.group(1);
96                                 final Integer port = Integer.parseInt(matchResult.group(2));
97                                 final String bridgeName = server.replace(".", "") + "" + port;
98
99                                 logger.debug("Found pilight daemon at {}:{}", server, port);
100
101                                 Map<String, Object> properties = new HashMap<>();
102                                 properties.put(PilightBindingConstants.PROPERTY_IP_ADDRESS, server);
103                                 properties.put(PilightBindingConstants.PROPERTY_PORT, port);
104                                 properties.put(PilightBindingConstants.PROPERTY_NAME, bridgeName);
105
106                                 ThingUID uid = new ThingUID(PilightBindingConstants.THING_TYPE_BRIDGE, bridgeName);
107
108                                 DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
109                                         .withRepresentationProperty(PilightBindingConstants.PROPERTY_NAME)
110                                         .withLabel("Pilight Bridge (" + server + ")").build();
111
112                                 thingDiscovered(result);
113                             }).count() == 0;
114                         }
115                     }
116                 }
117             }
118         } catch (IOException e) {
119             if (e.getMessage() != null && !"Receive timed out".equals(e.getMessage())) {
120                 logger.warn("Unable to enumerate the local network interfaces {}", e.getMessage());
121             }
122         }
123     }
124
125     @Override
126     protected synchronized void stopScan() {
127         super.stopScan();
128         removeOlderResults(getTimestampOfLastScan());
129     }
130
131     @Override
132     protected void startBackgroundDiscovery() {
133         logger.debug("Start Pilight device background discovery");
134         final @Nullable ScheduledFuture<?> backgroundDiscoveryJob = this.backgroundDiscoveryJob;
135         if (backgroundDiscoveryJob == null || backgroundDiscoveryJob.isCancelled()) {
136             this.backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 5,
137                     AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC, TimeUnit.SECONDS);
138         }
139     }
140
141     @Override
142     protected void stopBackgroundDiscovery() {
143         logger.debug("Stop Pilight device background discovery");
144         final @Nullable ScheduledFuture<?> backgroundDiscoveryJob = this.backgroundDiscoveryJob;
145         if (backgroundDiscoveryJob != null) {
146             backgroundDiscoveryJob.cancel(true);
147             this.backgroundDiscoveryJob = null;
148         }
149     }
150 }