]> git.basschouten.com Git - openhab-addons.git/blob
7c28cf3412ddfc8738cd219e97352708556ee6d3
[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.bondhome.internal.handler;
14
15 import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
16 import static org.openhab.core.thing.Thing.*;
17
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;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.concurrent.ScheduledExecutorService;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28
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;
53
54 /**
55  * The {@link BondBridgeHandler} is responsible for handling commands, which are
56  * sent to one of the channels.
57  *
58  * @author Sara Geleskie Damiano - Initial contribution
59  */
60 @NonNullByDefault
61 public class BondBridgeHandler extends BaseBridgeHandler {
62
63     private final Logger logger = LoggerFactory.getLogger(BondBridgeHandler.class);
64
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;
70
71     private BondBridgeConfiguration config = new BondBridgeConfiguration();
72
73     private @Nullable BondDiscoveryService discoveryService;
74
75     private final Set<BondDeviceHandler> handlers = Collections.synchronizedSet(new HashSet<>());
76
77     private @Nullable ScheduledFuture<?> initializer;
78
79     public BondBridgeHandler(Bridge bridge, final HttpClientFactory httpClientFactory) {
80         super(bridge);
81         udpListener = new BPUPListener(this);
82         api = new BondHttpApi(this, httpClientFactory);
83         logger.debug("Created a BondBridgeHandler for thing '{}'", getThing().getUID());
84     }
85
86     @Override
87     public void handleCommand(ChannelUID channelUID, Command command) {
88         // Not needed, all commands are handled in the {@link BondDeviceHandler}
89     }
90
91     @Override
92     public void initialize() {
93         config = getConfigAs(BondBridgeConfiguration.class);
94
95         // set the thing status to UNKNOWN temporarily
96         updateStatus(ThingStatus.UNKNOWN);
97
98         this.initializer = scheduler.schedule(this::initializeThing, 0L, TimeUnit.MILLISECONDS);
99     }
100
101     private void initializeThing() {
102         if (config.localToken.isEmpty()) {
103             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
104                     "@text/offline.conf-error.incorrect-local-token");
105             this.initializer = null;
106             return;
107         }
108         if (config.ipAddress.isEmpty()) {
109             try {
110                 String lookupAddress = config.serialNumber + ".local";
111                 logger.debug("Attempting to get IP address for Bond Bridge {}", lookupAddress);
112                 InetAddress ia = InetAddress.getByName(lookupAddress);
113                 String ip = ia.getHostAddress();
114                 Configuration c = editConfiguration();
115                 c.put(CONFIG_IP_ADDRESS, ip);
116                 updateConfiguration(c);
117                 config = getConfigAs(BondBridgeConfiguration.class);
118             } catch (UnknownHostException ignored) {
119                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
120                         "@text/offline.conf-error.unknown-host");
121                 this.initializer = null;
122                 return;
123             }
124         } else {
125             try {
126                 InetAddress.getByName(config.ipAddress);
127             } catch (UnknownHostException ignored) {
128                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
129                         "@text/offline.conf-error.invalid-host");
130                 this.initializer = null;
131                 return;
132             }
133         }
134
135         // Ask the bridge its current status and update the properties with the info
136         // This will also set the thing status to online/offline based on whether it
137         // succeeds in getting the properties from the bridge.
138         updateBridgeProperties();
139         this.initializer = null;
140     }
141
142     @Override
143     public void dispose() {
144         // The listener should already have been stopped when the last child was
145         // disposed,
146         // but we'll call the stop here for good measure.
147         stopUDPListenerJob();
148         ScheduledFuture<?> localInitializer = initializer;
149         if (localInitializer != null) {
150             localInitializer.cancel(true);
151         }
152     }
153
154     private synchronized void startUDPListenerJob() {
155         if (udpListener.isRunning()) {
156             return;
157         }
158         logger.debug("Started listener job");
159         udpListener.start(bondScheduler);
160     }
161
162     private synchronized void stopUDPListenerJob() {
163         logger.trace("Stopping UDP listener job");
164         if (udpListener.isRunning()) {
165             udpListener.shutdown();
166             logger.debug("UDP listener job stopped");
167         }
168     }
169
170     @Override
171     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
172         super.childHandlerInitialized(childHandler, childThing);
173         if (childHandler instanceof BondDeviceHandler) {
174             BondDeviceHandler handler = (BondDeviceHandler) childHandler;
175             synchronized (handlers) {
176                 // Start the BPUP update service after the first child device is added
177                 startUDPListenerJob();
178                 if (!handlers.contains(handler)) {
179                     handlers.add(handler);
180                 }
181             }
182         }
183     }
184
185     @Override
186     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
187         if (childHandler instanceof BondDeviceHandler) {
188             BondDeviceHandler handler = (BondDeviceHandler) childHandler;
189             synchronized (handlers) {
190                 handlers.remove(handler);
191                 if (handlers.isEmpty()) {
192                     // Stop the update service when the last child is removed
193                     stopUDPListenerJob();
194                 }
195             }
196         }
197         super.childHandlerDisposed(childHandler, childThing);
198     }
199
200     /**
201      * Forwards a push update to a device
202      *
203      * @param the {@link BPUPUpdate object}
204      */
205     public void forwardUpdateToThing(BPUPUpdate pushUpdate) {
206         updateStatus(ThingStatus.ONLINE);
207
208         BondDeviceState updateState = pushUpdate.deviceState;
209         String topic = pushUpdate.topic;
210         String deviceId = null;
211         String topicType = null;
212         if (topic != null) {
213             String parts[] = topic.split("/");
214             deviceId = parts[1];
215             topicType = parts[2];
216         }
217         // We can't use getThingByUID because we don't know the type of device and thus
218         // don't know the full uid (that is we cannot tell a fan from a fireplace, etc,
219         // from the contents of the update)
220         if (deviceId != null) {
221             if (topicType != null && "state".equals(topicType)) {
222                 synchronized (handlers) {
223                     for (BondDeviceHandler handler : handlers) {
224                         String handlerDeviceId = handler.getDeviceId();
225                         if (handlerDeviceId.equalsIgnoreCase(deviceId)) {
226                             handler.updateChannelsFromState(updateState);
227                             break;
228                         }
229                     }
230                 }
231             } else {
232                 logger.trace("could not read topic type from push update or type was not state.");
233             }
234         } else {
235             logger.warn("Can not read device Id from push update.");
236         }
237     }
238
239     /**
240      * Returns the Id of the bridge associated with the handler
241      */
242     public String getBridgeId() {
243         return config.serialNumber;
244     }
245
246     /**
247      * Returns the Ip Address of the bridge associated with the handler as a string
248      */
249     public String getBridgeIpAddress() {
250         return config.ipAddress;
251     }
252
253     /**
254      * Returns the local token of the bridge associated with the handler as a string
255      */
256     public String getBridgeToken() {
257         return config.localToken;
258     }
259
260     /**
261      * Returns the api instance
262      */
263     public BondHttpApi getBridgeAPI() {
264         return this.api;
265     }
266
267     /**
268      * Set the bridge status offline.
269      *
270      * Called by the dependents to set the bridge offline when repeated requests
271      * fail.
272      *
273      * NOTE: This does NOT stop the UDP listener job, which will keep pinging the
274      * bridge's IP once a minute. The listener job will set the bridge back online
275      * if it receives a proper response from the bridge.
276      */
277     public void setBridgeOffline(ThingStatusDetail detail, String description) {
278         updateStatus(ThingStatus.OFFLINE, detail, description);
279     }
280
281     /**
282      * Set the bridge status back online.
283      *
284      * Called by the UDP listener when it gets a proper response.
285      */
286     public void setBridgeOnline(String bridgeAddress) {
287         if (!config.isValid()) {
288             logger.warn("Configuration error, cannot set the bridghe online without configuration");
289             return;
290         } else if (!config.ipAddress.equals(bridgeAddress)) {
291             logger.debug("IP address of Bond {} has changed to {}", config.serialNumber, bridgeAddress);
292             Configuration c = editConfiguration();
293             c.put(CONFIG_IP_ADDRESS, bridgeAddress);
294             updateConfiguration(c);
295             updateBridgeProperties();
296             return;
297         }
298         // don't bother updating on every keepalive packet
299         if (getThing().getStatus() != ThingStatus.ONLINE) {
300             updateBridgeProperties();
301         }
302     }
303
304     private void updateProperty(Map<String, String> thingProperties, String key, @Nullable String value) {
305         if (value == null) {
306             return;
307         }
308         thingProperties.put(key, value);
309     }
310
311     private void updateBridgeProperties() {
312         BondSysVersion myVersion;
313         try {
314             myVersion = api.getBridgeVersion();
315         } catch (BondException e) {
316             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
317             return;
318         }
319         // Update all the thing properties based on the result
320         Map<String, String> thingProperties = editProperties();
321         updateProperty(thingProperties, PROPERTY_VENDOR, myVersion.make);
322         updateProperty(thingProperties, PROPERTY_MODEL_ID, myVersion.model);
323         updateProperty(thingProperties, PROPERTY_SERIAL_NUMBER, myVersion.bondid);
324         updateProperty(thingProperties, PROPERTY_FIRMWARE_VERSION, myVersion.firmwareVersion);
325         updateProperties(thingProperties);
326         updateStatus(ThingStatus.ONLINE);
327         BondDiscoveryService localDiscoveryService = discoveryService;
328         if (localDiscoveryService != null) {
329             localDiscoveryService.discoverNow();
330         }
331     }
332
333     @Override
334     public Collection<Class<? extends ThingHandlerService>> getServices() {
335         return Collections.singleton(BondDiscoveryService.class);
336     }
337
338     public void setDiscoveryService(BondDiscoveryService discoveryService) {
339         this.discoveryService = discoveryService;
340     }
341 }