2 * Copyright (c) 2010-2023 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.BroadlinkBindingConstants.*;
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.core.config.discovery.AbstractDiscoveryService;
30 import org.openhab.core.config.discovery.DiscoveryResult;
31 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
32 import org.openhab.core.config.discovery.DiscoveryService;
33 import org.openhab.core.net.NetworkAddressService;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingTypeUID;
36 import org.openhab.core.thing.ThingUID;
37 import org.osgi.service.component.annotations.Activate;
38 import org.osgi.service.component.annotations.Component;
39 import org.osgi.service.component.annotations.Reference;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 import com.github.mob41.blapi.BLDevice;
46 * The {@link BroadlinkDiscoveryService} is responsible for discovering Broadlink devices through
49 * @author Florian Mueller - Initial contribution
51 @Component(service = DiscoveryService.class, configurationPid = "discovery.broadlinkthermostat")
53 public class BroadlinkDiscoveryService extends AbstractDiscoveryService {
55 private final Logger logger = LoggerFactory.getLogger(BroadlinkDiscoveryService.class);
57 private final NetworkAddressService networkAddressService;
59 private static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Set.of(FLOUREON_THERMOSTAT_THING_TYPE,
60 RM_UNIVERSAL_REMOTE_THING_TYPE);
61 private static final int DISCOVERY_TIMEOUT_SECONDS = 30;
62 private @Nullable ScheduledFuture<?> backgroundDiscoveryFuture;
65 public BroadlinkDiscoveryService(@Reference NetworkAddressService networkAddressService) {
66 super(DISCOVERABLE_THING_TYPES_UIDS, DISCOVERY_TIMEOUT_SECONDS);
67 this.networkAddressService = networkAddressService;
70 private void createScanner() {
72 long timestampOfLastScan = getTimestampOfLastScan();
73 BLDevice[] blDevices = new BLDevice[0];
76 InetAddress sourceAddress = getIpAddress();
77 if (sourceAddress != null) {
78 logger.debug("Using source address {} for sending out broadcast request.", sourceAddress);
79 blDevices = BLDevice.discoverDevices(sourceAddress, 0, DISCOVERY_TIMEOUT_SECONDS * 1000);
81 blDevices = BLDevice.discoverDevices(DISCOVERY_TIMEOUT_SECONDS * 1000);
83 } catch (IOException e) {
84 logger.debug("Error while trying to discover broadlink devices: {}", e.getMessage());
86 logger.debug("Discovery service found {} broadlink devices.", blDevices.length);
88 for (BLDevice dev : blDevices) {
89 logger.debug("Broadlink device {} of type {} with Host {} and MAC {}", dev.getDeviceDescription(),
90 Integer.toHexString(dev.getDeviceType()), dev.getHost(), dev.getMac());
93 String id = dev.getHost().replaceAll("\\.", "-");
94 logger.debug("Device ID with IP address replacement: {}", id);
96 id = getHostnameWithoutDomain(InetAddress.getByName(dev.getHost()).getHostName());
97 logger.debug("Device ID with DNS name: {}", id);
98 } catch (UnknownHostException e) {
99 logger.debug("Discovered device with IP {} does not have a DNS name, using IP as thing UID.",
102 var deviceDescription = dev.getDeviceDescription();
103 switch (deviceDescription) {
104 case "Floureon Thermostat":
105 thingUID = new ThingUID(FLOUREON_THERMOSTAT_THING_TYPE, id);
107 case "Hysen Thermostat":
108 thingUID = new ThingUID(HYSEN_THERMOSTAT_THING_TYPE, id);
111 thingUID = new ThingUID(RM_UNIVERSAL_REMOTE_THING_TYPE, id);
114 logger.debug("Unknown device description '{}'.", deviceDescription);
118 Map<String, Object> properties = new HashMap<>();
119 properties.put(HOST, dev.getHost());
120 properties.put(Thing.PROPERTY_MAC_ADDRESS, dev.getMac().getMacString());
121 properties.put(DESCRIPTION, dev.getDeviceDescription());
123 logger.debug("Thing {} property map: {}", thingUID, properties);
125 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
126 .withLabel(dev.getDeviceDescription() + " (" + id + ")")
127 .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
129 thingDiscovered(discoveryResult);
131 removeOlderResults(timestampOfLastScan);
135 protected void startScan() {
136 scheduler.execute(this::createScanner);
140 protected void startBackgroundDiscovery() {
141 logger.trace("Starting background scan for Broadlink devices");
142 ScheduledFuture<?> currentBackgroundDiscoveryFuture = backgroundDiscoveryFuture;
143 if (currentBackgroundDiscoveryFuture != null) {
144 currentBackgroundDiscoveryFuture.cancel(true);
146 backgroundDiscoveryFuture = scheduler.scheduleWithFixedDelay(this::createScanner, 0, 60, TimeUnit.SECONDS);
150 protected void stopBackgroundDiscovery() {
151 logger.trace("Stopping background scan for Broadlink devices");
153 ScheduledFuture<?> backgroundDiscoveryFuture = this.backgroundDiscoveryFuture;
154 if (backgroundDiscoveryFuture != null && !backgroundDiscoveryFuture.isCancelled()) {
155 if (backgroundDiscoveryFuture.cancel(true)) {
156 this.backgroundDiscoveryFuture = null;
162 private @Nullable InetAddress getIpAddress() {
163 return getIpFromNetworkAddressService().orElse(null);
167 * Uses openHAB's NetworkAddressService to determine the local primary network interface.
169 * @return local ip or <code>empty</code> if configured primary IP is not set or could not be parsed.
171 private Optional<InetAddress> getIpFromNetworkAddressService() {
172 String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
173 if (ipAddress == null) {
174 logger.warn("No network interface could be found.");
175 return Optional.empty();
178 return Optional.of(InetAddress.getByName(ipAddress));
179 } catch (UnknownHostException e) {
180 logger.warn("Configured primary IP cannot be parsed: {} Details: {}", ipAddress, e.getMessage());
181 return Optional.empty();
185 private String getHostnameWithoutDomain(String hostname) {
186 String broadlinkRegex = "BroadLink-OEM[-A-Za-z0-9]{12}.*";
187 if (hostname.matches(broadlinkRegex)) {
188 String[] dotSeparatedString = hostname.split("\\.");
189 logger.debug("Found original broadlink DNS name {}, removing domain", hostname);
190 return dotSeparatedString[0].replaceAll("\\.", "-");
192 logger.debug("DNS name does not match original broadlink name: {}, using it without modification. ",
194 return hostname.replaceAll("\\.", "-");