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