2 * Copyright (c) 2010-2022 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.bondhome.internal.handler;
15 import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
16 import static org.openhab.core.thing.Thing.*;
18 import java.net.InetAddress;
19 import java.net.UnknownHostException;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashSet;
25 import java.util.concurrent.ScheduledExecutorService;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.bondhome.internal.BondException;
32 import org.openhab.binding.bondhome.internal.api.BPUPListener;
33 import org.openhab.binding.bondhome.internal.api.BPUPUpdate;
34 import org.openhab.binding.bondhome.internal.api.BondDeviceState;
35 import org.openhab.binding.bondhome.internal.api.BondHttpApi;
36 import org.openhab.binding.bondhome.internal.api.BondSysVersion;
37 import org.openhab.binding.bondhome.internal.config.BondBridgeConfiguration;
38 import org.openhab.binding.bondhome.internal.discovery.BondDiscoveryService;
39 import org.openhab.core.common.ThreadPoolManager;
40 import org.openhab.core.config.core.Configuration;
41 import org.openhab.core.io.net.http.HttpClientFactory;
42 import org.openhab.core.thing.Bridge;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.binding.BaseBridgeHandler;
48 import org.openhab.core.thing.binding.ThingHandler;
49 import org.openhab.core.thing.binding.ThingHandlerService;
50 import org.openhab.core.types.Command;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
55 * The {@link BondBridgeHandler} is responsible for handling commands, which are
56 * sent to one of the channels.
58 * @author Sara Geleskie Damiano - Initial contribution
61 public class BondBridgeHandler extends BaseBridgeHandler {
63 private final Logger logger = LoggerFactory.getLogger(BondBridgeHandler.class);
65 // Get a dedicated threadpool for the long-running listener thread.
66 // Intent is to not permanently tie up the common scheduler pool.
67 private final ScheduledExecutorService bondScheduler = ThreadPoolManager.getScheduledPool("bondBridgeHandler");
68 private final BPUPListener udpListener;
69 private final BondHttpApi api;
71 private @Nullable BondBridgeConfiguration config;
73 private @Nullable BondDiscoveryService discoveryService;
75 private final Set<BondDeviceHandler> handlers = Collections.synchronizedSet(new HashSet<>());
77 private @Nullable ScheduledFuture<?> initializer;
79 public BondBridgeHandler(Bridge bridge, final HttpClientFactory httpClientFactory) {
81 udpListener = new BPUPListener(this);
82 api = new BondHttpApi(this, httpClientFactory);
83 logger.debug("Created a BondBridgeHandler for thing '{}'", getThing().getUID());
87 public void handleCommand(ChannelUID channelUID, Command command) {
88 // Not needed, all commands are handled in the {@link BondDeviceHandler}
92 public void initialize() {
93 config = getConfigAs(BondBridgeConfiguration.class);
95 // set the thing status to UNKNOWN temporarily
96 updateStatus(ThingStatus.UNKNOWN);
98 this.initializer = scheduler.schedule(this::initializeThing, 0L, TimeUnit.MILLISECONDS);
101 private void initializeThing() {
102 BondBridgeConfiguration localConfig = config;
103 if (localConfig.localToken == null) {
104 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
105 "@text/offline.conf-error.incorrect-local-token");
106 this.initializer = null;
109 if (localConfig.ipAddress == null) {
111 String lookupAddress = localConfig.serialNumber + ".local";
112 logger.debug("Attempting to get IP address for Bond Bridge {}", lookupAddress);
113 InetAddress ia = InetAddress.getByName(lookupAddress);
114 String ip = ia.getHostAddress();
115 Configuration c = editConfiguration();
116 c.put(CONFIG_IP_ADDRESS, ip);
117 updateConfiguration(c);
118 config = getConfigAs(BondBridgeConfiguration.class);
119 } catch (UnknownHostException ignored) {
120 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
121 "@text/offline.conf-error.unknown-host");
122 this.initializer = null;
127 InetAddress.getByName(localConfig.ipAddress);
128 } catch (UnknownHostException ignored) {
129 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
130 "@text/offline.conf-error.invalid-host");
131 this.initializer = null;
136 // Ask the bridge its current status and update the properties with the info
137 // This will also set the thing status to online/offline based on whether it
138 // succeeds in getting the properties from the bridge.
139 updateBridgeProperties();
140 this.initializer = null;
144 public void dispose() {
145 // The listener should already have been stopped when the last child was
147 // but we'll call the stop here for good measure.
148 stopUDPListenerJob();
149 ScheduledFuture<?> localInitializer = initializer;
150 if (localInitializer != null) {
151 localInitializer.cancel(true);
155 private synchronized void startUDPListenerJob() {
156 if (udpListener.isRunning()) {
159 logger.debug("Started listener job");
160 udpListener.start(bondScheduler);
163 private synchronized void stopUDPListenerJob() {
164 logger.trace("Stopping UDP listener job");
165 if (udpListener.isRunning()) {
166 udpListener.shutdown();
167 logger.debug("UDP listener job stopped");
172 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
173 super.childHandlerInitialized(childHandler, childThing);
174 if (childHandler instanceof BondDeviceHandler) {
175 BondDeviceHandler handler = (BondDeviceHandler) childHandler;
176 synchronized (handlers) {
177 // Start the BPUP update service after the first child device is added
178 startUDPListenerJob();
179 if (!handlers.contains(handler)) {
180 handlers.add(handler);
187 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
188 if (childHandler instanceof BondDeviceHandler) {
189 BondDeviceHandler handler = (BondDeviceHandler) childHandler;
190 synchronized (handlers) {
191 handlers.remove(handler);
192 if (handlers.isEmpty()) {
193 // Stop the update service when the last child is removed
194 stopUDPListenerJob();
198 super.childHandlerDisposed(childHandler, childThing);
202 * Forwards a push update to a device
204 * @param the {@link BPUPUpdate object}
206 public void forwardUpdateToThing(BPUPUpdate pushUpdate) {
208 updateStatus(ThingStatus.ONLINE);
210 BondDeviceState updateState = pushUpdate.deviceState;
211 String topic = pushUpdate.topic;
212 String deviceId = null;
213 String topicType = null;
215 String parts[] = topic.split("/");
217 topicType = parts[2];
219 // We can't use getThingByUID because we don't know the type of device and thus
220 // don't know the full uid (that is we cannot tell a fan from a fireplace, etc,
221 // from the contents of the update)
222 if (deviceId != null) {
223 if (topicType != null && "state".equals(topicType)) {
224 synchronized (handlers) {
225 for (BondDeviceHandler handler : handlers) {
226 String handlerDeviceId = handler.getDeviceId();
227 if (handlerDeviceId.equalsIgnoreCase(deviceId)) {
228 handler.updateChannelsFromState(updateState);
234 logger.trace("could not read topic type from push update or type was not state.");
237 logger.warn("Can not read device Id from push update.");
242 * Returns the Id of the bridge associated with the handler
244 public String getBridgeId() {
245 String serialNumber = config.serialNumber;
246 return serialNumber == null ? "" : serialNumber;
250 * Returns the Ip Address of the bridge associated with the handler as a string
252 public @Nullable String getBridgeIpAddress() {
253 return config.ipAddress;
257 * Returns the local token of the bridge associated with the handler as a string
259 public String getBridgeToken() {
260 String localToken = config.localToken;
261 return localToken == null ? "" : localToken;
265 * Returns the api instance
267 public BondHttpApi getBridgeAPI() {
272 * Set the bridge status offline.
274 * Called by the dependents to set the bridge offline when repeated requests
277 * NOTE: This does NOT stop the UDP listener job, which will keep pinging the
278 * bridge's IP once a minute. The listener job will set the bridge back online
279 * if it receives a proper response from the bridge.
281 public void setBridgeOffline(ThingStatusDetail detail, String description) {
282 updateStatus(ThingStatus.OFFLINE, detail, description);
286 * Set the bridge status back online.
288 * Called by the UDP listener when it gets a proper response.
290 public void setBridgeOnline(String bridgeAddress) {
291 BondBridgeConfiguration localConfig = config;
292 if (localConfig.ipAddress == null || !localConfig.ipAddress.equals(bridgeAddress)) {
293 logger.debug("IP address of Bond {} has changed to {}", localConfig.serialNumber, bridgeAddress);
294 Configuration c = editConfiguration();
295 c.put(CONFIG_IP_ADDRESS, bridgeAddress);
296 updateConfiguration(c);
297 updateBridgeProperties();
300 // don't bother updating on every keepalive packet
301 if (getThing().getStatus() != ThingStatus.ONLINE) {
302 updateBridgeProperties();
306 private void updateProperty(Map<String, String> thingProperties, String key, @Nullable String value) {
310 thingProperties.put(key, value);
313 private void updateBridgeProperties() {
314 BondSysVersion myVersion;
316 myVersion = api.getBridgeVersion();
317 } catch (BondException e) {
318 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
321 // Update all the thing properties based on the result
322 Map<String, String> thingProperties = editProperties();
323 updateProperty(thingProperties, PROPERTY_VENDOR, myVersion.make);
324 updateProperty(thingProperties, PROPERTY_MODEL_ID, myVersion.model);
325 updateProperty(thingProperties, PROPERTY_SERIAL_NUMBER, myVersion.bondid);
326 updateProperty(thingProperties, PROPERTY_FIRMWARE_VERSION, myVersion.firmwareVersion);
327 updateProperties(thingProperties);
328 updateStatus(ThingStatus.ONLINE);
329 BondDiscoveryService localDiscoveryService = discoveryService;
330 if (localDiscoveryService != null) {
331 localDiscoveryService.discoverNow();
336 public Collection<Class<? extends ThingHandlerService>> getServices() {
337 return Collections.singleton(BondDiscoveryService.class);
340 public void setDiscoveryService(BondDiscoveryService discoveryService) {
341 this.discoveryService = discoveryService;