]> git.basschouten.com Git - openhab-addons.git/blob
def10860c6a9bea8a8c0f4d7117a13a2733a0f7a
[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.irobot.internal.discovery;
14
15 import java.io.IOException;
16 import java.net.DatagramPacket;
17 import java.net.DatagramSocket;
18 import java.net.InetAddress;
19 import java.net.UnknownHostException;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.irobot.internal.IRobotBindingConstants;
29 import org.openhab.binding.irobot.internal.dto.IdentProtocol;
30 import org.openhab.binding.irobot.internal.dto.IdentProtocol.IdentData;
31 import org.openhab.core.config.discovery.AbstractDiscoveryService;
32 import org.openhab.core.config.discovery.DiscoveryResult;
33 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
34 import org.openhab.core.config.discovery.DiscoveryService;
35 import org.openhab.core.net.NetUtil;
36 import org.openhab.core.thing.ThingUID;
37 import org.osgi.service.component.annotations.Component;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import com.google.gson.JsonParseException;
42
43 /**
44  * Discovery service for iRobots
45  *
46  * @author Pavel Fedin - Initial contribution
47  *
48  */
49 @Component(service = DiscoveryService.class, configurationPid = "discovery.irobot")
50 @NonNullByDefault
51 public class IRobotDiscoveryService extends AbstractDiscoveryService {
52
53     private final Logger logger = LoggerFactory.getLogger(IRobotDiscoveryService.class);
54     private final Runnable scanner;
55     private @Nullable ScheduledFuture<?> backgroundFuture;
56
57     public IRobotDiscoveryService() {
58         super(Collections.singleton(IRobotBindingConstants.THING_TYPE_ROOMBA), 30, true);
59         scanner = createScanner();
60     }
61
62     @Override
63     protected void startBackgroundDiscovery() {
64         stopBackgroundScan();
65         backgroundFuture = scheduler.scheduleWithFixedDelay(scanner, 0, 60, TimeUnit.SECONDS);
66     }
67
68     @Override
69     protected void stopBackgroundDiscovery() {
70         stopBackgroundScan();
71         super.stopBackgroundDiscovery();
72     }
73
74     private void stopBackgroundScan() {
75         ScheduledFuture<?> scan = backgroundFuture;
76
77         if (scan != null) {
78             scan.cancel(true);
79             backgroundFuture = null;
80         }
81     }
82
83     @Override
84     protected void startScan() {
85         scheduler.execute(scanner);
86     }
87
88     private Runnable createScanner() {
89         return () -> {
90             long timestampOfLastScan = getTimestampOfLastScan();
91             for (InetAddress broadcastAddress : getBroadcastAddresses()) {
92                 logger.debug("Starting broadcast for {}", broadcastAddress.toString());
93
94                 try (DatagramSocket socket = IdentProtocol.sendRequest(broadcastAddress)) {
95                     while (receivePacketAndDiscover(socket)) {
96                     }
97                 } catch (IOException e) {
98                     logger.warn("Error sending broadcast: {}", e.toString());
99                 }
100             }
101
102             removeOlderResults(timestampOfLastScan);
103         };
104     }
105
106     private List<InetAddress> getBroadcastAddresses() {
107         ArrayList<InetAddress> addresses = new ArrayList<>();
108
109         for (String broadcastAddress : NetUtil.getAllBroadcastAddresses()) {
110             try {
111                 addresses.add(InetAddress.getByName(broadcastAddress));
112             } catch (UnknownHostException e) {
113                 // The broadcastAddress is supposed to be raw IP, not a hostname, like 192.168.0.255.
114                 // Getting UnknownHost on it would be totally strange, some internal system error.
115                 logger.warn("Error broadcasting to {}: {}", broadcastAddress, e.getMessage());
116             }
117         }
118
119         return addresses;
120     }
121
122     private boolean receivePacketAndDiscover(DatagramSocket socket) {
123         DatagramPacket incomingPacket;
124
125         try {
126             incomingPacket = IdentProtocol.receiveResponse(socket);
127         } catch (IOException e) {
128             // This is not really an error, eventually we get a timeout
129             // due to a loop in the caller
130             return false;
131         }
132
133         String host = incomingPacket.getAddress().toString().substring(1);
134         IdentProtocol.IdentData ident;
135
136         try {
137             ident = IdentProtocol.decodeResponse(incomingPacket);
138         } catch (JsonParseException e) {
139             logger.warn("Malformed IDENT reply from {}!", host);
140             return true;
141         }
142
143         // This check comes from Roomba980-Python
144         if (ident.ver < IdentData.MIN_SUPPORTED_VERSION) {
145             logger.warn("Found unsupported iRobot \"{}\" version {} at {}", ident.robotname, ident.ver, host);
146             return true;
147         }
148
149         if (ident.product.equals(IdentData.PRODUCT_ROOMBA)) {
150             ThingUID thingUID = new ThingUID(IRobotBindingConstants.THING_TYPE_ROOMBA, host.replace('.', '_'));
151             DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withProperty("ipaddress", host)
152                     .withRepresentationProperty("ipaddress").withLabel("iRobot " + ident.robotname).build();
153
154             thingDiscovered(result);
155         }
156
157         return true;
158     }
159 }