2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.irobot.internal.discovery;
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;
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;
41 import com.google.gson.JsonParseException;
44 * Discovery service for iRobots
46 * @author Pavel Fedin - Initial contribution
49 @Component(service = DiscoveryService.class, configurationPid = "discovery.irobot")
51 public class IRobotDiscoveryService extends AbstractDiscoveryService {
53 private final Logger logger = LoggerFactory.getLogger(IRobotDiscoveryService.class);
54 private final Runnable scanner;
55 private @Nullable ScheduledFuture<?> backgroundFuture;
57 public IRobotDiscoveryService() {
58 super(Collections.singleton(IRobotBindingConstants.THING_TYPE_ROOMBA), 30, true);
59 scanner = createScanner();
63 protected void startBackgroundDiscovery() {
65 backgroundFuture = scheduler.scheduleWithFixedDelay(scanner, 0, 60, TimeUnit.SECONDS);
69 protected void stopBackgroundDiscovery() {
71 super.stopBackgroundDiscovery();
74 private void stopBackgroundScan() {
75 ScheduledFuture<?> scan = backgroundFuture;
79 backgroundFuture = null;
84 protected void startScan() {
85 scheduler.execute(scanner);
88 private Runnable createScanner() {
90 long timestampOfLastScan = getTimestampOfLastScan();
91 for (InetAddress broadcastAddress : getBroadcastAddresses()) {
92 logger.debug("Starting broadcast for {}", broadcastAddress.toString());
94 try (DatagramSocket socket = IdentProtocol.sendRequest(broadcastAddress)) {
95 while (receivePacketAndDiscover(socket)) {
97 } catch (IOException e) {
98 logger.warn("Error sending broadcast: {}", e.toString());
102 removeOlderResults(timestampOfLastScan);
106 private List<InetAddress> getBroadcastAddresses() {
107 ArrayList<InetAddress> addresses = new ArrayList<>();
109 for (String broadcastAddress : NetUtil.getAllBroadcastAddresses()) {
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());
122 private boolean receivePacketAndDiscover(DatagramSocket socket) {
123 DatagramPacket incomingPacket;
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
133 String host = incomingPacket.getAddress().toString().substring(1);
134 IdentProtocol.IdentData ident;
137 ident = IdentProtocol.decodeResponse(incomingPacket);
138 } catch (JsonParseException e) {
139 logger.warn("Malformed IDENT reply from {}!", host);
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);
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();
154 thingDiscovered(result);