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.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;
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;
42 import com.google.gson.JsonParseException;
45 * Discovery service for iRobots
47 * @author Pavel Fedin - Initial contribution
50 @Component(service = DiscoveryService.class, configurationPid = "discovery.irobot")
52 public class IRobotDiscoveryService extends AbstractDiscoveryService {
54 private final Logger logger = LoggerFactory.getLogger(IRobotDiscoveryService.class);
55 private final Runnable scanner;
56 private @Nullable ScheduledFuture<?> backgroundFuture;
58 public IRobotDiscoveryService() {
59 super(Collections.singleton(IRobotBindingConstants.THING_TYPE_ROOMBA), 30, true);
60 scanner = createScanner();
64 protected void startBackgroundDiscovery() {
66 backgroundFuture = scheduler.scheduleWithFixedDelay(scanner, 0, 60, TimeUnit.SECONDS);
70 protected void stopBackgroundDiscovery() {
72 super.stopBackgroundDiscovery();
75 private void stopBackgroundScan() {
76 ScheduledFuture<?> scan = backgroundFuture;
80 backgroundFuture = null;
85 protected void startScan() {
86 scheduler.execute(scanner);
89 private Runnable createScanner() {
91 long timestampOfLastScan = getTimestampOfLastScan();
92 for (InetAddress broadcastAddress : getBroadcastAddresses()) {
93 logger.debug("Starting broadcast for {}", broadcastAddress.toString());
95 try (DatagramSocket socket = IdentProtocol.sendRequest(broadcastAddress)) {
96 DatagramPacket incomingPacket;
98 while ((incomingPacket = receivePacket(socket)) != null) {
99 discover(incomingPacket);
101 } catch (IOException e) {
102 logger.warn("Error sending broadcast: {}", e.toString());
106 removeOlderResults(timestampOfLastScan);
110 private List<InetAddress> getBroadcastAddresses() {
111 ArrayList<InetAddress> addresses = new ArrayList<>();
113 for (String broadcastAddress : NetUtil.getAllBroadcastAddresses()) {
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());
126 private @Nullable DatagramPacket receivePacket(DatagramSocket socket) {
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
136 private void discover(DatagramPacket incomingPacket) {
137 String host = incomingPacket.getAddress().toString().substring(1);
138 String reply = new String(incomingPacket.getData(), StandardCharsets.UTF_8);
140 logger.trace("Received IDENT from {}: {}", host, reply);
142 IdentProtocol.IdentData ident;
145 ident = IdentProtocol.decodeResponse(reply);
146 } catch (JsonParseException e) {
147 logger.warn("Malformed IDENT reply from {}!", host);
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);
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();
162 thingDiscovered(result);