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