]> git.basschouten.com Git - openhab-addons.git/blob
dd69e1b5e7cb4262fe39333bdc480f2f057dc494
[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.homematic.internal.handler;
14
15 import static org.openhab.binding.homematic.internal.HomematicBindingConstants.CHANNEL_TYPE_DUTY_CYCLE_RATIO;
16 import static org.openhab.core.thing.Thing.*;
17
18 import java.io.IOException;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.Map;
22 import java.util.concurrent.Future;
23 import java.util.concurrent.TimeUnit;
24
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.openhab.binding.homematic.internal.common.HomematicConfig;
28 import org.openhab.binding.homematic.internal.communicator.HomematicGateway;
29 import org.openhab.binding.homematic.internal.communicator.HomematicGatewayAdapter;
30 import org.openhab.binding.homematic.internal.communicator.HomematicGatewayFactory;
31 import org.openhab.binding.homematic.internal.discovery.HomematicDeviceDiscoveryService;
32 import org.openhab.binding.homematic.internal.misc.HomematicClientException;
33 import org.openhab.binding.homematic.internal.model.HmDatapoint;
34 import org.openhab.binding.homematic.internal.model.HmDatapointConfig;
35 import org.openhab.binding.homematic.internal.model.HmDevice;
36 import org.openhab.binding.homematic.internal.model.HmGatewayInfo;
37 import org.openhab.binding.homematic.internal.type.HomematicTypeGenerator;
38 import org.openhab.binding.homematic.internal.type.UidUtils;
39 import org.openhab.core.library.types.DecimalType;
40 import org.openhab.core.thing.Bridge;
41 import org.openhab.core.thing.Channel;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.binding.BaseBridgeHandler;
47 import org.openhab.core.thing.binding.ThingHandler;
48 import org.openhab.core.thing.binding.ThingHandlerService;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.types.RefreshType;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * {@link HomematicBridgeHandler} is the handler for a Homematic gateway and connects it to the framework.
56  *
57  * @author Gerhard Riegler - Initial contribution
58  */
59 public class HomematicBridgeHandler extends BaseBridgeHandler implements HomematicGatewayAdapter {
60     private final Logger logger = LoggerFactory.getLogger(HomematicBridgeHandler.class);
61     private static final long REINITIALIZE_DELAY_SECONDS = 10;
62     private static final int DUTY_CYCLE_RATIO_LIMIT = 99;
63     private static final int DUTY_CYCLE_DISCONNECTED = -1;
64     private static SimplePortPool portPool = new SimplePortPool();
65
66     private final Object dutyCycleRatioUpdateLock = new Object();
67     private final Object initDisposeLock = new Object();
68
69     private Future<?> initializeFuture;
70     private boolean isDisposed;
71
72     private HomematicConfig config;
73     private HomematicGateway gateway;
74     private final HomematicTypeGenerator typeGenerator;
75     private final HttpClient httpClient;
76
77     private HomematicDeviceDiscoveryService discoveryService;
78
79     private final String ipv4Address;
80     private boolean isInDutyCycle = false;
81     private int dutyCycleRatio = 0;
82
83     public HomematicBridgeHandler(@NonNull Bridge bridge, HomematicTypeGenerator typeGenerator, String ipv4Address,
84             HttpClient httpClient) {
85         super(bridge);
86         this.typeGenerator = typeGenerator;
87         this.ipv4Address = ipv4Address;
88         this.httpClient = httpClient;
89     }
90
91     @Override
92     public void initialize() {
93         synchronized (initDisposeLock) {
94             isDisposed = false;
95             initializeFuture = scheduler.submit(this::initializeInternal);
96         }
97     }
98
99     public void setDiscoveryService(HomematicDeviceDiscoveryService discoveryService) {
100         this.discoveryService = discoveryService;
101     }
102
103     private void initializeInternal() {
104         synchronized (initDisposeLock) {
105             config = createHomematicConfig();
106
107             try {
108                 String id = getThing().getUID().getId();
109                 gateway = HomematicGatewayFactory.createGateway(id, config, this, httpClient);
110                 configureThingProperties();
111                 gateway.initialize();
112
113                 // scan for already known devices (new devices will not be discovered,
114                 // since installMode==true is only achieved if the bridge is online
115                 discoveryService.startScan(null);
116                 discoveryService.waitForScanFinishing();
117
118                 updateStatus(ThingStatus.ONLINE);
119                 if (!config.getGatewayInfo().isHomegear()) {
120                     try {
121                         gateway.loadRssiValues();
122                     } catch (IOException ex) {
123                         logger.warn("Unable to load RSSI values from bridge '{}'", getThing().getUID().getId());
124                         logger.error("{}", ex.getMessage(), ex);
125                     }
126                 }
127                 gateway.startWatchdogs();
128             } catch (IOException ex) {
129                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
130                 logger.debug(
131                         "Homematic bridge was set to OFFLINE-COMMUNICATION_ERROR due to the following exception: {}",
132                         ex.getMessage(), ex);
133                 disposeInternal();
134                 scheduleReinitialize();
135             }
136         }
137     }
138
139     private void configureThingProperties() {
140         final HmGatewayInfo info = config.getGatewayInfo();
141         final Map<String, String> properties = getThing().getProperties();
142
143         if (!properties.containsKey(PROPERTY_FIRMWARE_VERSION)) {
144             getThing().setProperty(PROPERTY_FIRMWARE_VERSION, info.getFirmware());
145         }
146         if (!properties.containsKey(PROPERTY_SERIAL_NUMBER)) {
147             getThing().setProperty(PROPERTY_SERIAL_NUMBER, info.getAddress());
148         }
149         if (!properties.containsKey(PROPERTY_MODEL_ID)) {
150             getThing().setProperty(PROPERTY_MODEL_ID, info.getType());
151         }
152     }
153
154     /**
155      * Schedules a reinitialization, if the Homematic gateway is not reachable at bridge startup.
156      */
157     private void scheduleReinitialize() {
158         if (!isDisposed) {
159             initializeFuture = scheduler.schedule(this::initializeInternal, REINITIALIZE_DELAY_SECONDS,
160                     TimeUnit.SECONDS);
161         }
162     }
163
164     @Override
165     public void dispose() {
166         synchronized (initDisposeLock) {
167             super.dispose();
168
169             if (initializeFuture != null) {
170                 initializeFuture.cancel(true);
171             }
172
173             disposeInternal();
174             isDisposed = true;
175         }
176     }
177
178     private void disposeInternal() {
179         logger.debug("Disposing bridge '{}'", getThing().getUID().getId());
180         if (discoveryService != null) {
181             discoveryService.stopScan();
182         }
183         if (gateway != null) {
184             gateway.dispose();
185         }
186         if (config != null) {
187             portPool.release(config.getXmlCallbackPort());
188             portPool.release(config.getBinCallbackPort());
189         }
190     }
191
192     /**
193      * Sets the OFFLINE status for all things of this bridge that has been removed from the gateway.
194      */
195     @SuppressWarnings("null")
196     public void setOfflineStatus() {
197         for (Thing hmThing : getThing().getThings()) {
198             try {
199                 gateway.getDevice(UidUtils.getHomematicAddress(hmThing));
200             } catch (HomematicClientException e) {
201                 if (hmThing.getHandler() != null) {
202                     ((HomematicThingHandler) hmThing.getHandler()).handleRemoval();
203                 }
204             }
205         }
206     }
207
208     /**
209      * Creates the configuration for the HomematicGateway.
210      */
211     private HomematicConfig createHomematicConfig() {
212         HomematicConfig homematicConfig = getThing().getConfiguration().as(HomematicConfig.class);
213         if (homematicConfig.getCallbackHost() == null) {
214             homematicConfig.setCallbackHost(this.ipv4Address);
215         }
216         if (homematicConfig.getBindAddress() == null) {
217             homematicConfig.setBindAddress(homematicConfig.getCallbackHost());
218         }
219         if (homematicConfig.getXmlCallbackPort() == 0) {
220             homematicConfig.setXmlCallbackPort(portPool.getNextPort());
221         } else {
222             portPool.setInUse(homematicConfig.getXmlCallbackPort());
223         }
224         if (homematicConfig.getBinCallbackPort() == 0) {
225             homematicConfig.setBinCallbackPort(portPool.getNextPort());
226         } else {
227             portPool.setInUse(homematicConfig.getBinCallbackPort());
228         }
229         logger.debug("{}", homematicConfig);
230         return homematicConfig;
231     }
232
233     @Override
234     public Collection<Class<? extends ThingHandlerService>> getServices() {
235         return Collections.singleton(HomematicDeviceDiscoveryService.class);
236     }
237
238     @Override
239     public void handleCommand(ChannelUID channelUID, Command command) {
240         if (RefreshType.REFRESH == command) {
241             logger.debug("Refreshing bridge '{}'", getThing().getUID().getId());
242             reloadAllDeviceValues();
243         }
244     }
245
246     /**
247      * Returns the TypeGenerator.
248      */
249     public HomematicTypeGenerator getTypeGenerator() {
250         return typeGenerator;
251     }
252
253     /**
254      * Returns the HomematicGateway.
255      */
256     public HomematicGateway getGateway() {
257         return gateway;
258     }
259
260     /**
261      * Updates the thing for the given Homematic device.
262      */
263     private void updateThing(HmDevice device) {
264         Thing hmThing = getThing().getThing(UidUtils.generateThingUID(device, getThing()));
265         if (hmThing != null) {
266             HomematicThingHandler thingHandler = (HomematicThingHandler) hmThing.getHandler();
267             if (thingHandler != null) {
268                 thingHandler.thingUpdated(hmThing);
269                 for (Channel channel : hmThing.getChannels()) {
270                     thingHandler.handleRefresh(channel.getUID());
271                 }
272             }
273         }
274     }
275
276     @Override
277     public void onStateUpdated(HmDatapoint dp) {
278         Thing hmThing = getThing().getThing(UidUtils.generateThingUID(dp.getChannel().getDevice(), getThing()));
279         if (hmThing != null) {
280             final ThingStatus status = hmThing.getStatus();
281             if (status == ThingStatus.ONLINE || status == ThingStatus.OFFLINE) {
282                 HomematicThingHandler thingHandler = (HomematicThingHandler) hmThing.getHandler();
283                 if (thingHandler != null) {
284                     thingHandler.updateDatapointState(dp);
285                 }
286             }
287         }
288     }
289
290     @Override
291     public HmDatapointConfig getDatapointConfig(HmDatapoint dp) {
292         Thing hmThing = getThing().getThing(UidUtils.generateThingUID(dp.getChannel().getDevice(), getThing()));
293         if (hmThing != null) {
294             HomematicThingHandler thingHandler = (HomematicThingHandler) hmThing.getHandler();
295             if (thingHandler != null) {
296                 return thingHandler.getChannelConfig(dp);
297             }
298         }
299         return new HmDatapointConfig();
300     }
301
302     @Override
303     public void onNewDevice(HmDevice device) {
304         onDeviceLoaded(device);
305         updateThing(device);
306     }
307
308     @SuppressWarnings("null")
309     @Override
310     public void onDeviceDeleted(HmDevice device) {
311         discoveryService.deviceRemoved(device);
312         updateThing(device);
313
314         Thing hmThing = getThing().getThing(UidUtils.generateThingUID(device, getThing()));
315         if (hmThing != null && hmThing.getHandler() != null) {
316             ((HomematicThingHandler) hmThing.getHandler()).deviceRemoved();
317         }
318     }
319
320     @Override
321     public void onConnectionLost() {
322         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connection lost");
323     }
324
325     @Override
326     public void onConnectionResumed() {
327         updateStatus(ThingStatus.ONLINE);
328         reloadAllDeviceValues();
329     }
330
331     @Override
332     public void onDeviceLoaded(HmDevice device) {
333         typeGenerator.generate(device);
334         if (discoveryService != null) {
335             discoveryService.deviceDiscovered(device);
336         }
337
338         Thing hmThing = getThing().getThing(UidUtils.generateThingUID(device, getThing()));
339         if (hmThing != null) {
340             HomematicThingHandler thingHandler = (HomematicThingHandler) hmThing.getHandler();
341             if (thingHandler != null) {
342                 thingHandler.deviceLoaded(device);
343             }
344         }
345     }
346
347     @Override
348     public void onDutyCycleRatioUpdate(int dutyCycleRatio) {
349         synchronized (dutyCycleRatioUpdateLock) {
350             this.dutyCycleRatio = dutyCycleRatio;
351             Channel dutyCycleRatioChannel = thing.getChannel(CHANNEL_TYPE_DUTY_CYCLE_RATIO);
352             if (dutyCycleRatioChannel != null) {
353                 this.updateState(dutyCycleRatioChannel.getUID(),
354                         new DecimalType(dutyCycleRatio < 0 ? 0 : dutyCycleRatio));
355             }
356
357             if (!isInDutyCycle) {
358                 if (dutyCycleRatio >= DUTY_CYCLE_RATIO_LIMIT) {
359                     logger.info("Duty cycle threshold exceeded by homematic bridge {}, it will go OFFLINE.",
360                             thing.getUID());
361                     isInDutyCycle = true;
362                     this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DUTY_CYCLE);
363                 } else if (dutyCycleRatio == DUTY_CYCLE_DISCONNECTED) {
364                     logger.info(
365                             "Duty cycle indicates a communication problem by homematic bridge {}, it will go OFFLINE.",
366                             thing.getUID());
367                     isInDutyCycle = true;
368                     this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
369                 }
370             } else {
371                 if (dutyCycleRatio < DUTY_CYCLE_RATIO_LIMIT && dutyCycleRatio != DUTY_CYCLE_DISCONNECTED) {
372                     logger.info("Homematic bridge {}: duty cycle back to normal and bridge will come ONLINE again.",
373                             thing.getUID());
374                     isInDutyCycle = false;
375                     this.updateStatus(ThingStatus.ONLINE);
376                 }
377             }
378         }
379     }
380
381     /**
382      * Returns the last value for the duty cycle ratio that was retrieved from the homematic gateway.
383      *
384      * @return The duty cycle ratio of the gateway
385      */
386     public int getDutyCycleRatio() {
387         return dutyCycleRatio;
388     }
389
390     @Override
391     public void reloadDeviceValues(HmDevice device) {
392         updateThing(device);
393         if (device.isGatewayExtras()) {
394             typeGenerator.generate(device);
395         }
396     }
397
398     @Override
399     public void reloadAllDeviceValues() {
400         for (Thing hmThing : getThing().getThings()) {
401             try {
402                 HmDevice device = gateway.getDevice(UidUtils.getHomematicAddress(hmThing));
403                 gateway.triggerDeviceValuesReload(device);
404             } catch (HomematicClientException ex) {
405                 logger.warn("{}", ex.getMessage());
406             }
407         }
408     }
409
410     @Override
411     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
412         if (((HomematicThingHandler) childHandler).isDeletionPending()) {
413             deleteFromGateway(UidUtils.getHomematicAddress(childThing), false, true, false);
414         }
415     }
416
417     /**
418      * Updates the {@link HmDatapoint} by reloading the value from the homematic gateway.
419      *
420      * @param dp The HmDatapoint that shall be updated
421      * @throws IOException If there is a problem while communicating to the gateway
422      */
423     public void updateDatapoint(HmDatapoint dp) throws IOException {
424         getGateway().loadDatapointValue(dp);
425     }
426
427     /**
428      * Deletes a device from the gateway.
429      *
430      * @param address The address of the device to be deleted
431      * @param reset <i>true</i> will perform a factory reset on the device before deleting it.
432      * @param force <i>true</i> will delete the device even if it is not reachable.
433      * @param defer <i>true</i> will delete the device once it becomes available.
434      */
435     public void deleteFromGateway(String address, boolean reset, boolean force, boolean defer) {
436         scheduler.submit(() -> {
437             logger.debug("Deleting the device '{}' from gateway '{}'", address, getBridge());
438             getGateway().deleteDevice(address, reset, force, defer);
439         });
440     }
441 }