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.broadlinkthermostat.internal.discovery;
15 import static org.openhab.binding.broadlinkthermostat.internal.BroadlinkThermostatBindingConstants.*;
17 import java.io.IOException;
18 import java.net.InetAddress;
19 import java.net.UnknownHostException;
20 import java.util.HashMap;
22 import java.util.Optional;
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.broadlinkthermostat.internal.BroadlinkThermostatBindingConstants;
30 import org.openhab.core.config.discovery.AbstractDiscoveryService;
31 import org.openhab.core.config.discovery.DiscoveryResult;
32 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
33 import org.openhab.core.config.discovery.DiscoveryService;
34 import org.openhab.core.net.NetworkAddressService;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingTypeUID;
37 import org.openhab.core.thing.ThingUID;
38 import org.osgi.service.component.annotations.Activate;
39 import org.osgi.service.component.annotations.Component;
40 import org.osgi.service.component.annotations.Reference;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
44 import com.github.mob41.blapi.BLDevice;
47 * The {@link BroadlinkThermostatDiscoveryService} is responsible for discovering Broadlinkthermostat devices through
50 * @author Florian Mueller - Initial contribution
52 @Component(service = DiscoveryService.class, configurationPid = "discovery.broadlinkthermostat")
54 public class BroadlinkThermostatDiscoveryService extends AbstractDiscoveryService {
56 private final Logger logger = LoggerFactory.getLogger(BroadlinkThermostatDiscoveryService.class);
58 private final NetworkAddressService networkAddressService;
60 private static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Set.of(FLOUREON_THERMOSTAT_THING_TYPE,
61 UNKNOWN_BROADLINKTHERMOSTAT_THING_TYPE);
62 private static final int DISCOVERY_TIMEOUT_SECONDS = 30;
63 private @Nullable ScheduledFuture<?> backgroundDiscoveryFuture;
66 public BroadlinkThermostatDiscoveryService(@Reference NetworkAddressService networkAddressService) {
67 super(DISCOVERABLE_THING_TYPES_UIDS, DISCOVERY_TIMEOUT_SECONDS);
68 this.networkAddressService = networkAddressService;
71 private void createScanner() {
73 long timestampOfLastScan = getTimestampOfLastScan();
74 BLDevice[] blDevices = new BLDevice[0];
77 InetAddress sourceAddress = getIpAddress();
78 if (sourceAddress != null) {
79 logger.debug("Using source address {} for sending out broadcast request.", sourceAddress);
80 blDevices = BLDevice.discoverDevices(sourceAddress, 0, DISCOVERY_TIMEOUT_SECONDS * 1000);
82 blDevices = BLDevice.discoverDevices(DISCOVERY_TIMEOUT_SECONDS * 1000);
84 } catch (IOException e) {
85 logger.debug("Error while trying to discover broadlinkthermostat devices: {}", e.getMessage());
87 logger.debug("Discovery service found {} broadlinkthermostat devices.", blDevices.length);
89 for (BLDevice dev : blDevices) {
90 logger.debug("Broadlinkthermostat device {} of type {} with Host {} and MAC {}", dev.getDeviceDescription(),
91 Integer.toHexString(dev.getDeviceType()), dev.getHost(), dev.getMac());
94 String id = dev.getHost().replaceAll("\\.", "-");
95 logger.debug("Device ID with IP address replacement: {}", id);
97 id = getHostnameWithoutDomain(InetAddress.getByName(dev.getHost()).getHostName());
98 logger.debug("Device ID with DNS name: {}", id);
99 } catch (UnknownHostException e) {
100 logger.debug("Discovered device with IP {} does not have a DNS name, using IP as thing UID.",
104 switch (dev.getDeviceDescription()) {
105 case "Floureon Thermostat":
106 thingUID = new ThingUID(FLOUREON_THERMOSTAT_THING_TYPE, id);
108 case "Hysen Thermostat":
109 thingUID = new ThingUID(HYSEN_THERMOSTAT_THING_TYPE, id);
112 thingUID = new ThingUID(UNKNOWN_BROADLINKTHERMOSTAT_THING_TYPE, id);
115 Map<String, Object> properties = new HashMap<>();
116 properties.put(BroadlinkThermostatBindingConstants.HOST, dev.getHost());
117 properties.put(Thing.PROPERTY_MAC_ADDRESS, dev.getMac().getMacString());
118 properties.put(BroadlinkThermostatBindingConstants.DESCRIPTION, dev.getDeviceDescription());
120 logger.debug("Property map: {}", properties);
122 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
123 .withLabel(dev.getDeviceDescription() + " (" + id + ")")
124 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
126 thingDiscovered(discoveryResult);
128 removeOlderResults(timestampOfLastScan);
132 protected void startScan() {
133 scheduler.execute(this::createScanner);
137 protected void startBackgroundDiscovery() {
138 logger.trace("Starting background scan for Broadlinkthermostat devices");
139 ScheduledFuture<?> currentBackgroundDiscoveryFuture = backgroundDiscoveryFuture;
140 if (currentBackgroundDiscoveryFuture != null) {
141 currentBackgroundDiscoveryFuture.cancel(true);
143 backgroundDiscoveryFuture = scheduler.scheduleWithFixedDelay(this::createScanner, 0, 60, TimeUnit.SECONDS);
147 protected void stopBackgroundDiscovery() {
148 logger.trace("Stopping background scan for Broadlinkthermostat devices");
150 ScheduledFuture<?> backgroundDiscoveryFuture = this.backgroundDiscoveryFuture;
151 if (backgroundDiscoveryFuture != null && !backgroundDiscoveryFuture.isCancelled()) {
152 if (backgroundDiscoveryFuture.cancel(true)) {
153 this.backgroundDiscoveryFuture = null;
159 private @Nullable InetAddress getIpAddress() {
160 return getIpFromNetworkAddressService().orElse(null);
164 * Uses openHAB's NetworkAddressService to determine the local primary network interface.
166 * @return local ip or <code>empty</code> if configured primary IP is not set or could not be parsed.
168 private Optional<InetAddress> getIpFromNetworkAddressService() {
169 String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
170 if (ipAddress == null) {
171 logger.warn("No network interface could be found.");
172 return Optional.empty();
175 return Optional.of(InetAddress.getByName(ipAddress));
176 } catch (UnknownHostException e) {
177 logger.warn("Configured primary IP cannot be parsed: {} Details: {}", ipAddress, e.getMessage());
178 return Optional.empty();
182 private String getHostnameWithoutDomain(String hostname) {
183 String broadlinkthermostatRegex = "BroadLink-OEM[-A-Za-z0-9]{12}.*";
184 if (hostname.matches(broadlinkthermostatRegex)) {
185 String[] dotSeparatedString = hostname.split("\\.");
186 logger.debug("Found original broadlink DNS name {}, removing domain", hostname);
187 return dotSeparatedString[0].replaceAll("\\.", "-");
189 logger.debug("DNS name does not match original broadlink name: {}, using it without modification. ",
191 return hostname.replaceAll("\\.", "-");