]> git.basschouten.com Git - openhab-addons.git/blob
cf8b69fe5284e37cb4dd77f29c906df43eaf4c1b
[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
72         long timestampOfLastScan = getTimestampOfLastScan();
73         BLDevice[] blDevices = new BLDevice[0];
74         try {
75             @Nullable
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);
80             } else {
81                 blDevices = BLDevice.discoverDevices(DISCOVERY_TIMEOUT_SECONDS * 1000);
82             }
83         } catch (IOException e) {
84             logger.debug("Error while trying to discover broadlink devices: {}", e.getMessage());
85         }
86         logger.debug("Discovery service found {} broadlink devices.", blDevices.length);
87
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());
91
92             ThingUID thingUID;
93             String id = dev.getHost().replaceAll("\\.", "-");
94             logger.debug("Device ID with IP address replacement: {}", id);
95             try {
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.",
100                         dev.getHost());
101             }
102             var deviceDescription = dev.getDeviceDescription();
103             switch (deviceDescription) {
104                 case "Floureon Thermostat":
105                     thingUID = new ThingUID(FLOUREON_THERMOSTAT_THING_TYPE, id);
106                     break;
107                 case "Hysen Thermostat":
108                     thingUID = new ThingUID(HYSEN_THERMOSTAT_THING_TYPE, id);
109                     break;
110                 case "RM Mini":
111                     thingUID = new ThingUID(RM_UNIVERSAL_REMOTE_THING_TYPE, id);
112                     break;
113                 default:
114                     logger.debug("Unknown device description '{}'.", deviceDescription);
115                     continue;
116             }
117
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());
122
123             logger.debug("Thing {} property map: {}", thingUID, properties);
124
125             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
126                     .withLabel(dev.getDeviceDescription() + " (" + id + ")")
127                     .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
128
129             thingDiscovered(discoveryResult);
130         }
131         removeOlderResults(timestampOfLastScan);
132     }
133
134     @Override
135     protected void startScan() {
136         scheduler.execute(this::createScanner);
137     }
138
139     @Override
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);
145         }
146         backgroundDiscoveryFuture = scheduler.scheduleWithFixedDelay(this::createScanner, 0, 60, TimeUnit.SECONDS);
147     }
148
149     @Override
150     protected void stopBackgroundDiscovery() {
151         logger.trace("Stopping background scan for Broadlink devices");
152         @Nullable
153         ScheduledFuture<?> backgroundDiscoveryFuture = this.backgroundDiscoveryFuture;
154         if (backgroundDiscoveryFuture != null && !backgroundDiscoveryFuture.isCancelled()) {
155             if (backgroundDiscoveryFuture.cancel(true)) {
156                 this.backgroundDiscoveryFuture = null;
157             }
158         }
159         stopScan();
160     }
161
162     private @Nullable InetAddress getIpAddress() {
163         return getIpFromNetworkAddressService().orElse(null);
164     }
165
166     /**
167      * Uses openHAB's NetworkAddressService to determine the local primary network interface.
168      *
169      * @return local ip or <code>empty</code> if configured primary IP is not set or could not be parsed.
170      */
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();
176         }
177         try {
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();
182         }
183     }
184
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("\\.", "-");
191         } else {
192             logger.debug("DNS name does not match original broadlink name: {}, using it without modification. ",
193                     hostname);
194             return hostname.replaceAll("\\.", "-");
195         }
196     }
197 }