]> git.basschouten.com Git - openhab-addons.git/blob
1cd396212b7e7bdd5428f6f7e20f8aef7d981562
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.BroadlinkThermostatBindingConstants.*;
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.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;
43
44 import com.github.mob41.blapi.BLDevice;
45
46 /**
47  * The {@link BroadlinkThermostatDiscoveryService} is responsible for discovering Broadlinkthermostat devices through
48  * Broadcast.
49  *
50  * @author Florian Mueller - Initial contribution
51  */
52 @Component(service = DiscoveryService.class, configurationPid = "discovery.broadlinkthermostat")
53 @NonNullByDefault
54 public class BroadlinkThermostatDiscoveryService extends AbstractDiscoveryService {
55
56     private final Logger logger = LoggerFactory.getLogger(BroadlinkThermostatDiscoveryService.class);
57
58     private final NetworkAddressService networkAddressService;
59
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;
64
65     @Activate
66     public BroadlinkThermostatDiscoveryService(@Reference NetworkAddressService networkAddressService) {
67         super(DISCOVERABLE_THING_TYPES_UIDS, DISCOVERY_TIMEOUT_SECONDS);
68         this.networkAddressService = networkAddressService;
69     }
70
71     private void createScanner() {
72
73         long timestampOfLastScan = getTimestampOfLastScan();
74         BLDevice[] blDevices = new BLDevice[0];
75         try {
76             @Nullable
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);
81             } else {
82                 blDevices = BLDevice.discoverDevices(DISCOVERY_TIMEOUT_SECONDS * 1000);
83             }
84         } catch (IOException e) {
85             logger.debug("Error while trying to discover broadlinkthermostat devices: {}", e.getMessage());
86         }
87         logger.debug("Discovery service found {} broadlinkthermostat devices.", blDevices.length);
88
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());
92
93             ThingUID thingUID;
94             String id = dev.getHost().replaceAll("\\.", "-");
95             logger.debug("Device ID with IP address replacement: {}", id);
96             try {
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.",
101                         dev.getHost());
102             }
103
104             switch (dev.getDeviceDescription()) {
105                 case "Floureon Thermostat":
106                     thingUID = new ThingUID(FLOUREON_THERMOSTAT_THING_TYPE, id);
107                     break;
108                 case "Hysen Thermostat":
109                     thingUID = new ThingUID(HYSEN_THERMOSTAT_THING_TYPE, id);
110                     break;
111                 default:
112                     thingUID = new ThingUID(UNKNOWN_BROADLINKTHERMOSTAT_THING_TYPE, id);
113             }
114
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());
119
120             logger.debug("Property map: {}", properties);
121
122             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
123                     .withLabel(dev.getDeviceDescription() + " (" + id + ")")
124                     .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).build();
125
126             thingDiscovered(discoveryResult);
127         }
128         removeOlderResults(timestampOfLastScan);
129     }
130
131     @Override
132     protected void startScan() {
133         scheduler.execute(this::createScanner);
134     }
135
136     @Override
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);
142         }
143         backgroundDiscoveryFuture = scheduler.scheduleWithFixedDelay(this::createScanner, 0, 60, TimeUnit.SECONDS);
144     }
145
146     @Override
147     protected void stopBackgroundDiscovery() {
148         logger.trace("Stopping background scan for Broadlinkthermostat devices");
149         @Nullable
150         ScheduledFuture<?> backgroundDiscoveryFuture = this.backgroundDiscoveryFuture;
151         if (backgroundDiscoveryFuture != null && !backgroundDiscoveryFuture.isCancelled()) {
152             if (backgroundDiscoveryFuture.cancel(true)) {
153                 this.backgroundDiscoveryFuture = null;
154             }
155         }
156         stopScan();
157     }
158
159     private @Nullable InetAddress getIpAddress() {
160         return getIpFromNetworkAddressService().orElse(null);
161     }
162
163     /**
164      * Uses openHAB's NetworkAddressService to determine the local primary network interface.
165      *
166      * @return local ip or <code>empty</code> if configured primary IP is not set or could not be parsed.
167      */
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();
173         }
174         try {
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();
179         }
180     }
181
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("\\.", "-");
188         } else {
189             logger.debug("DNS name does not match original broadlink name: {}, using it without modification. ",
190                     hostname);
191             return hostname.replaceAll("\\.", "-");
192         }
193     }
194 }