]> git.basschouten.com Git - openhab-addons.git/blob
93e852d0198c302c4638feb777838526e884ed0e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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 @Nullable BondBridgeConfiguration config;
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         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;
107             return;
108         }
109         if (localConfig.ipAddress == null) {
110             try {
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;
123                 return;
124             }
125         } else {
126             try {
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;
132                 return;
133             }
134         }
135
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;
141     }
142
143     @Override
144     public void dispose() {
145         // The listener should already have been stopped when the last child was
146         // disposed,
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);
152         }
153     }
154
155     private synchronized void startUDPListenerJob() {
156         if (udpListener.isRunning()) {
157             return;
158         }
159         logger.debug("Started listener job");
160         udpListener.start(bondScheduler);
161     }
162
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");
168         }
169     }
170
171     @Override
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);
181                 }
182             }
183         }
184     }
185
186     @Override
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();
195                 }
196             }
197         }
198         super.childHandlerDisposed(childHandler, childThing);
199     }
200
201     /**
202      * Forwards a push update to a device
203      *
204      * @param the {@link BPUPUpdate object}
205      */
206     public void forwardUpdateToThing(BPUPUpdate pushUpdate) {
207
208         updateStatus(ThingStatus.ONLINE);
209
210         BondDeviceState updateState = pushUpdate.deviceState;
211         String topic = pushUpdate.topic;
212         String deviceId = null;
213         String topicType = null;
214         if (topic != null) {
215             String parts[] = topic.split("/");
216             deviceId = parts[1];
217             topicType = parts[2];
218         }
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);
229                             break;
230                         }
231                     }
232                 }
233             } else {
234                 logger.trace("could not read topic type from push update or type was not state.");
235             }
236         } else {
237             logger.warn("Can not read device Id from push update.");
238         }
239     }
240
241     /**
242      * Returns the Id of the bridge associated with the handler
243      */
244     public String getBridgeId() {
245         String serialNumber = config.serialNumber;
246         return serialNumber == null ? "" : serialNumber;
247     }
248
249     /**
250      * Returns the Ip Address of the bridge associated with the handler as a string
251      */
252     public @Nullable String getBridgeIpAddress() {
253         return config.ipAddress;
254     }
255
256     /**
257      * Returns the local token of the bridge associated with the handler as a string
258      */
259     public String getBridgeToken() {
260         String localToken = config.localToken;
261         return localToken == null ? "" : localToken;
262     }
263
264     /**
265      * Returns the api instance
266      */
267     public BondHttpApi getBridgeAPI() {
268         return this.api;
269     }
270
271     /**
272      * Set the bridge status offline.
273      *
274      * Called by the dependents to set the bridge offline when repeated requests
275      * fail.
276      *
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.
280      */
281     public void setBridgeOffline(ThingStatusDetail detail, String description) {
282         updateStatus(ThingStatus.OFFLINE, detail, description);
283     }
284
285     /**
286      * Set the bridge status back online.
287      *
288      * Called by the UDP listener when it gets a proper response.
289      */
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();
298             return;
299         }
300         // don't bother updating on every keepalive packet
301         if (getThing().getStatus() != ThingStatus.ONLINE) {
302             updateBridgeProperties();
303         }
304     }
305
306     private void updateProperty(Map<String, String> thingProperties, String key, @Nullable String value) {
307         if (value == null) {
308             return;
309         }
310         thingProperties.put(key, value);
311     }
312
313     private void updateBridgeProperties() {
314         BondSysVersion myVersion;
315         try {
316             myVersion = api.getBridgeVersion();
317         } catch (BondException e) {
318             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
319             return;
320         }
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();
332         }
333     }
334
335     @Override
336     public Collection<Class<? extends ThingHandlerService>> getServices() {
337         return Collections.singleton(BondDiscoveryService.class);
338     }
339
340     public void setDiscoveryService(BondDiscoveryService discoveryService) {
341         this.discoveryService = discoveryService;
342     }
343 }