]> git.basschouten.com Git - openhab-addons.git/blob
8b54a019805632818c70bfc335b8bd22834b118f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.broadlinkthermostat.internal.discovery;
14
15 import static org.openhab.binding.broadlinkthermostat.internal.BroadlinkBindingConstants.*;
16
17 import java.io.IOException;
18 import java.net.InetAddress;
19 import java.net.UnknownHostException;
20 import java.util.HashMap;
21 import java.util.Map;
22 import java.util.Optional;
23 import java.util.Set;
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.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;
42
43 import com.github.mob41.blapi.BLDevice;
44
45 /**
46  * The {@link BroadlinkDiscoveryService} is responsible for discovering Broadlink devices through
47  * Broadcast.
48  *
49  * @author Florian Mueller - Initial contribution
50  */
51 @Component(service = DiscoveryService.class, configurationPid = "discovery.broadlinkthermostat")
52 @NonNullByDefault
53 public class BroadlinkDiscoveryService extends AbstractDiscoveryService {
54
55     private final Logger logger = LoggerFactory.getLogger(BroadlinkDiscoveryService.class);
56
57     private final NetworkAddressService networkAddressService;
58
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;
63
64     @Activate
65     public BroadlinkDiscoveryService(@Reference NetworkAddressService networkAddressService) {
66         super(DISCOVERABLE_THING_TYPES_UIDS, DISCOVERY_TIMEOUT_SECONDS);
67         this.networkAddressService = networkAddressService;
68     }
69
70     private void createScanner() {
71         long timestampOfLastScan = getTimestampOfLastScan();
72         BLDevice[] blDevices = new BLDevice[0];
73         try {
74             @Nullable
75             InetAddress sourceAddress = getIpAddress();
76             if (sourceAddress != null) {
77                 logger.debug("Using source address {} for sending out broadcast request.", sourceAddress);
78                 blDevices = BLDevice.discoverDevices(sourceAddress, 0, DISCOVERY_TIMEOUT_SECONDS * 1000);
79             } else {
80                 blDevices = BLDevice.discoverDevices(DISCOVERY_TIMEOUT_SECONDS * 1000);
81             }
82         } catch (IOException e) {
83             logger.debug("Error while trying to discover broadlink devices: {}", e.getMessage());
84         }
85         logger.debug("Discovery service found {} broadlink devices.", blDevices.length);
86
87         for (BLDevice dev : blDevices) {
88             logger.debug("Broadlink device {} of type {} with Host {} and MAC {}", dev.getDeviceDescription(),
89                     Integer.toHexString(dev.getDeviceType()), dev.getHost(), dev.getMac());
90
91             ThingUID thingUID;
92             String id = dev.getHost().replace(".", "-");
93             logger.debug("Device ID with IP address replacement: {}", id);
94             try {
95                 id = getHostnameWithoutDomain(InetAddress.getByName(dev.getHost()).getHostName());
96                 logger.debug("Device ID with DNS name: {}", id);
97             } catch (UnknownHostException e) {
98                 logger.debug("Discovered device with IP {} does not have a DNS name, using IP as thing UID.",
99                         dev.getHost());
100             }
101             var deviceDescription = dev.getDeviceDescription();
102             switch (deviceDescription) {
103                 case "Floureon Thermostat":
104                     thingUID = new ThingUID(FLOUREON_THERMOSTAT_THING_TYPE, id);
105                     break;
106                 case "Hysen Thermostat":
107                     thingUID = new ThingUID(HYSEN_THERMOSTAT_THING_TYPE, id);
108                     break;
109                 case "RM Mini":
110                     thingUID = new ThingUID(RM_UNIVERSAL_REMOTE_THING_TYPE, id);
111                     break;
112                 default:
113                     logger.debug("Unknown device description '{}'.", deviceDescription);
114                     continue;
115             }
116
117             Map<String, Object> properties = new HashMap<>();
118             properties.put(HOST, dev.getHost());
119             properties.put(Thing.PROPERTY_MAC_ADDRESS, dev.getMac().getMacString());
120             properties.put(DESCRIPTION, dev.getDeviceDescription());
121
122             logger.debug("Thing {} property map: {}", thingUID, properties);
123
124             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
125                     .withLabel(dev.getDeviceDescription() + " (" + id + ")")
126                     .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
127
128             thingDiscovered(discoveryResult);
129         }
130         removeOlderResults(timestampOfLastScan);
131     }
132
133     @Override
134     protected void startScan() {
135         scheduler.execute(this::createScanner);
136     }
137
138     @Override
139     protected void startBackgroundDiscovery() {
140         logger.trace("Starting background scan for Broadlink devices");
141         ScheduledFuture<?> currentBackgroundDiscoveryFuture = backgroundDiscoveryFuture;
142         if (currentBackgroundDiscoveryFuture != null) {
143             currentBackgroundDiscoveryFuture.cancel(true);
144         }
145         backgroundDiscoveryFuture = scheduler.scheduleWithFixedDelay(this::createScanner, 0, 60, TimeUnit.SECONDS);
146     }
147
148     @Override
149     protected void stopBackgroundDiscovery() {
150         logger.trace("Stopping background scan for Broadlink devices");
151         @Nullable
152         ScheduledFuture<?> backgroundDiscoveryFuture = this.backgroundDiscoveryFuture;
153         if (backgroundDiscoveryFuture != null && !backgroundDiscoveryFuture.isCancelled()) {
154             if (backgroundDiscoveryFuture.cancel(true)) {
155                 this.backgroundDiscoveryFuture = null;
156             }
157         }
158         stopScan();
159     }
160
161     private @Nullable InetAddress getIpAddress() {
162         return getIpFromNetworkAddressService().orElse(null);
163     }
164
165     /**
166      * Uses openHAB's NetworkAddressService to determine the local primary network interface.
167      *
168      * @return local ip or <code>empty</code> if configured primary IP is not set or could not be parsed.
169      */
170     private Optional<InetAddress> getIpFromNetworkAddressService() {
171         String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
172         if (ipAddress == null) {
173             logger.warn("No network interface could be found.");
174             return Optional.empty();
175         }
176         try {
177             return Optional.of(InetAddress.getByName(ipAddress));
178         } catch (UnknownHostException e) {
179             logger.warn("Configured primary IP cannot be parsed: {} Details: {}", ipAddress, e.getMessage());
180             return Optional.empty();
181         }
182     }
183
184     private String getHostnameWithoutDomain(String hostname) {
185         String broadlinkRegex = "BroadLink-OEM[-A-Za-z0-9]{12}.*";
186         if (hostname.matches(broadlinkRegex)) {
187             String[] dotSeparatedString = hostname.split("\\.");
188             logger.debug("Found original broadlink DNS name {}, removing domain", hostname);
189             return dotSeparatedString[0].replace(".", "-");
190         } else {
191             logger.debug("DNS name does not match original broadlink name: {}, using it without modification. ",
192                     hostname);
193             return hostname.replace(".", "-");
194         }
195     }
196 }