]> git.basschouten.com Git - openhab-addons.git/blob
35cd1806c2c0fcaa414f1f45ca18367555dff4e2
[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 handler) {
174             synchronized (handlers) {
175                 // Start the BPUP update service after the first child device is added
176                 startUDPListenerJob();
177                 if (!handlers.contains(handler)) {
178                     handlers.add(handler);
179                 }
180             }
181         }
182     }
183
184     @Override
185     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
186         if (childHandler instanceof BondDeviceHandler handler) {
187             synchronized (handlers) {
188                 handlers.remove(handler);
189                 if (handlers.isEmpty()) {
190                     // Stop the update service when the last child is removed
191                     stopUDPListenerJob();
192                 }
193             }
194         }
195         super.childHandlerDisposed(childHandler, childThing);
196     }
197
198     /**
199      * Forwards a push update to a device
200      *
201      * @param pushUpdate the {@link BPUPUpdate object}
202      */
203     public void forwardUpdateToThing(BPUPUpdate pushUpdate) {
204         updateStatus(ThingStatus.ONLINE);
205
206         BondDeviceState updateState = pushUpdate.deviceState;
207         String topic = pushUpdate.topic;
208         String deviceId = null;
209         String topicType = null;
210         if (topic != null) {
211             String[] parts = topic.split("/");
212             deviceId = parts[1];
213             topicType = parts[2];
214         }
215         // We can't use getThingByUID because we don't know the type of device and thus
216         // don't know the full uid (that is we cannot tell a fan from a fireplace, etc,
217         // from the contents of the update)
218         if (deviceId != null) {
219             if (topicType != null && "state".equals(topicType)) {
220                 synchronized (handlers) {
221                     for (BondDeviceHandler handler : handlers) {
222                         String handlerDeviceId = handler.getDeviceId();
223                         if (handlerDeviceId.equalsIgnoreCase(deviceId)) {
224                             handler.updateChannelsFromState(updateState);
225                             break;
226                         }
227                     }
228                 }
229             } else {
230                 logger.trace("could not read topic type from push update or type was not state.");
231             }
232         } else {
233             logger.warn("Can not read device Id from push update.");
234         }
235     }
236
237     /**
238      * Returns the Id of the bridge associated with the handler
239      */
240     public String getBridgeId() {
241         return config.serialNumber;
242     }
243
244     /**
245      * Returns the Ip Address of the bridge associated with the handler as a string
246      */
247     public String getBridgeIpAddress() {
248         return config.ipAddress;
249     }
250
251     /**
252      * Returns the local token of the bridge associated with the handler as a string
253      */
254     public String getBridgeToken() {
255         return config.localToken;
256     }
257
258     /**
259      * Returns the api instance
260      */
261     public BondHttpApi getBridgeAPI() {
262         return this.api;
263     }
264
265     /**
266      * Set the bridge status offline.
267      *
268      * Called by the dependents to set the bridge offline when repeated requests
269      * fail.
270      *
271      * NOTE: This does NOT stop the UDP listener job, which will keep pinging the
272      * bridge's IP once a minute. The listener job will set the bridge back online
273      * if it receives a proper response from the bridge.
274      */
275     public void setBridgeOffline(ThingStatusDetail detail, String description) {
276         updateStatus(ThingStatus.OFFLINE, detail, description);
277     }
278
279     /**
280      * Set the bridge status back online.
281      *
282      * Called by the UDP listener when it gets a proper response.
283      */
284     public void setBridgeOnline(String bridgeAddress) {
285         if (!config.isValid()) {
286             logger.warn("Configuration error, cannot set the bridghe online without configuration");
287             return;
288         } else if (!config.ipAddress.equals(bridgeAddress)) {
289             logger.debug("IP address of Bond {} has changed to {}", config.serialNumber, bridgeAddress);
290             Configuration c = editConfiguration();
291             c.put(CONFIG_IP_ADDRESS, bridgeAddress);
292             updateConfiguration(c);
293             updateBridgeProperties();
294             return;
295         }
296         // don't bother updating on every keepalive packet
297         if (getThing().getStatus() != ThingStatus.ONLINE) {
298             updateBridgeProperties();
299         }
300     }
301
302     private void updateProperty(Map<String, String> thingProperties, String key, @Nullable String value) {
303         if (value == null) {
304             return;
305         }
306         thingProperties.put(key, value);
307     }
308
309     private void updateBridgeProperties() {
310         BondSysVersion myVersion;
311         try {
312             myVersion = api.getBridgeVersion();
313         } catch (BondException e) {
314             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
315             return;
316         }
317         // Update all the thing properties based on the result
318         Map<String, String> thingProperties = editProperties();
319         updateProperty(thingProperties, PROPERTY_VENDOR, myVersion.make);
320         updateProperty(thingProperties, PROPERTY_MODEL_ID, myVersion.model);
321         updateProperty(thingProperties, PROPERTY_SERIAL_NUMBER, myVersion.bondid);
322         updateProperty(thingProperties, PROPERTY_FIRMWARE_VERSION, myVersion.firmwareVersion);
323         updateProperties(thingProperties);
324         updateStatus(ThingStatus.ONLINE);
325         BondDiscoveryService localDiscoveryService = discoveryService;
326         if (localDiscoveryService != null) {
327             localDiscoveryService.discoverNow();
328         }
329     }
330
331     @Override
332     public Collection<Class<? extends ThingHandlerService>> getServices() {
333         return Set.of(BondDiscoveryService.class);
334     }
335
336     public void setDiscoveryService(BondDiscoveryService discoveryService) {
337         this.discoveryService = discoveryService;
338     }
339 }