2 * Copyright (c) 2010-2020 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.homematic.internal.handler;
15 import static org.openhab.binding.homematic.internal.HomematicBindingConstants.CHANNEL_TYPE_DUTY_CYCLE_RATIO;
16 import static org.openhab.core.thing.Thing.*;
18 import java.io.IOException;
19 import java.util.Collection;
20 import java.util.Collections;
22 import java.util.concurrent.Future;
23 import java.util.concurrent.TimeUnit;
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;
55 * {@link HomematicBridgeHandler} is the handler for a Homematic gateway and connects it to the framework.
57 * @author Gerhard Riegler - Initial contribution
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();
66 private final Object dutyCycleRatioUpdateLock = new Object();
67 private final Object initDisposeLock = new Object();
69 private Future<?> initializeFuture;
70 private boolean isDisposed;
72 private HomematicConfig config;
73 private HomematicGateway gateway;
74 private final HomematicTypeGenerator typeGenerator;
75 private final HttpClient httpClient;
77 private HomematicDeviceDiscoveryService discoveryService;
79 private final String ipv4Address;
80 private boolean isInDutyCycle = false;
81 private int dutyCycleRatio = 0;
83 public HomematicBridgeHandler(@NonNull Bridge bridge, HomematicTypeGenerator typeGenerator, String ipv4Address,
84 HttpClient httpClient) {
86 this.typeGenerator = typeGenerator;
87 this.ipv4Address = ipv4Address;
88 this.httpClient = httpClient;
92 public void initialize() {
93 synchronized (initDisposeLock) {
95 initializeFuture = scheduler.submit(this::initializeInternal);
99 public void setDiscoveryService(HomematicDeviceDiscoveryService discoveryService) {
100 this.discoveryService = discoveryService;
103 private void initializeInternal() {
104 synchronized (initDisposeLock) {
105 config = createHomematicConfig();
108 String id = getThing().getUID().getId();
109 gateway = HomematicGatewayFactory.createGateway(id, config, this, httpClient);
110 configureThingProperties();
111 gateway.initialize();
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();
118 updateStatus(ThingStatus.ONLINE);
119 if (!config.getGatewayInfo().isHomegear()) {
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);
127 gateway.startWatchdogs();
128 } catch (IOException ex) {
129 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
131 "Homematic bridge was set to OFFLINE-COMMUNICATION_ERROR due to the following exception: {}",
132 ex.getMessage(), ex);
134 scheduleReinitialize();
139 private void configureThingProperties() {
140 final HmGatewayInfo info = config.getGatewayInfo();
141 final Map<String, String> properties = getThing().getProperties();
143 if (!properties.containsKey(PROPERTY_FIRMWARE_VERSION)) {
144 getThing().setProperty(PROPERTY_FIRMWARE_VERSION, info.getFirmware());
146 if (!properties.containsKey(PROPERTY_SERIAL_NUMBER)) {
147 getThing().setProperty(PROPERTY_SERIAL_NUMBER, info.getAddress());
149 if (!properties.containsKey(PROPERTY_MODEL_ID)) {
150 getThing().setProperty(PROPERTY_MODEL_ID, info.getType());
155 * Schedules a reinitialization, if the Homematic gateway is not reachable at bridge startup.
157 private void scheduleReinitialize() {
159 initializeFuture = scheduler.schedule(this::initializeInternal, REINITIALIZE_DELAY_SECONDS,
165 public void dispose() {
166 synchronized (initDisposeLock) {
169 if (initializeFuture != null) {
170 initializeFuture.cancel(true);
178 private void disposeInternal() {
179 logger.debug("Disposing bridge '{}'", getThing().getUID().getId());
180 if (discoveryService != null) {
181 discoveryService.stopScan();
183 if (gateway != null) {
186 if (config != null) {
187 portPool.release(config.getXmlCallbackPort());
188 portPool.release(config.getBinCallbackPort());
193 * Sets the OFFLINE status for all things of this bridge that has been removed from the gateway.
195 @SuppressWarnings("null")
196 public void setOfflineStatus() {
197 for (Thing hmThing : getThing().getThings()) {
199 gateway.getDevice(UidUtils.getHomematicAddress(hmThing));
200 } catch (HomematicClientException e) {
201 if (hmThing.getHandler() != null) {
202 ((HomematicThingHandler) hmThing.getHandler()).handleRemoval();
209 * Creates the configuration for the HomematicGateway.
211 private HomematicConfig createHomematicConfig() {
212 HomematicConfig homematicConfig = getThing().getConfiguration().as(HomematicConfig.class);
213 if (homematicConfig.getCallbackHost() == null) {
214 homematicConfig.setCallbackHost(this.ipv4Address);
216 if (homematicConfig.getBindAddress() == null) {
217 homematicConfig.setBindAddress(homematicConfig.getCallbackHost());
219 if (homematicConfig.getXmlCallbackPort() == 0) {
220 homematicConfig.setXmlCallbackPort(portPool.getNextPort());
222 portPool.setInUse(homematicConfig.getXmlCallbackPort());
224 if (homematicConfig.getBinCallbackPort() == 0) {
225 homematicConfig.setBinCallbackPort(portPool.getNextPort());
227 portPool.setInUse(homematicConfig.getBinCallbackPort());
229 logger.debug("{}", homematicConfig);
230 return homematicConfig;
234 public Collection<Class<? extends ThingHandlerService>> getServices() {
235 return Collections.singleton(HomematicDeviceDiscoveryService.class);
239 public void handleCommand(ChannelUID channelUID, Command command) {
240 if (RefreshType.REFRESH == command) {
241 logger.debug("Refreshing bridge '{}'", getThing().getUID().getId());
242 reloadAllDeviceValues();
247 * Returns the TypeGenerator.
249 public HomematicTypeGenerator getTypeGenerator() {
250 return typeGenerator;
254 * Returns the HomematicGateway.
256 public HomematicGateway getGateway() {
261 * Updates the thing for the given Homematic device.
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());
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);
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);
299 return new HmDatapointConfig();
303 public void onNewDevice(HmDevice device) {
304 onDeviceLoaded(device);
308 @SuppressWarnings("null")
310 public void onDeviceDeleted(HmDevice device) {
311 discoveryService.deviceRemoved(device);
314 Thing hmThing = getThing().getThing(UidUtils.generateThingUID(device, getThing()));
315 if (hmThing != null && hmThing.getHandler() != null) {
316 ((HomematicThingHandler) hmThing.getHandler()).deviceRemoved();
321 public void onConnectionLost() {
322 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connection lost");
326 public void onConnectionResumed() {
327 updateStatus(ThingStatus.ONLINE);
328 reloadAllDeviceValues();
332 public void onDeviceLoaded(HmDevice device) {
333 typeGenerator.generate(device);
334 if (discoveryService != null) {
335 discoveryService.deviceDiscovered(device);
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);
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));
357 if (!isInDutyCycle) {
358 if (dutyCycleRatio >= DUTY_CYCLE_RATIO_LIMIT) {
359 logger.info("Duty cycle threshold exceeded by homematic bridge {}, it will go OFFLINE.",
361 isInDutyCycle = true;
362 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DUTY_CYCLE);
363 } else if (dutyCycleRatio == DUTY_CYCLE_DISCONNECTED) {
365 "Duty cycle indicates a communication problem by homematic bridge {}, it will go OFFLINE.",
367 isInDutyCycle = true;
368 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
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.",
374 isInDutyCycle = false;
375 this.updateStatus(ThingStatus.ONLINE);
382 * Returns the last value for the duty cycle ratio that was retrieved from the homematic gateway.
384 * @return The duty cycle ratio of the gateway
386 public int getDutyCycleRatio() {
387 return dutyCycleRatio;
391 public void reloadDeviceValues(HmDevice device) {
393 if (device.isGatewayExtras()) {
394 typeGenerator.generate(device);
399 public void reloadAllDeviceValues() {
400 for (Thing hmThing : getThing().getThings()) {
402 HmDevice device = gateway.getDevice(UidUtils.getHomematicAddress(hmThing));
403 gateway.triggerDeviceValuesReload(device);
404 } catch (HomematicClientException ex) {
405 logger.warn("{}", ex.getMessage());
411 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
412 if (((HomematicThingHandler) childHandler).isDeletionPending()) {
413 deleteFromGateway(UidUtils.getHomematicAddress(childThing), false, true, false);
418 * Updates the {@link HmDatapoint} by reloading the value from the homematic gateway.
420 * @param dp The HmDatapoint that shall be updated
421 * @throws IOException If there is a problem while communicating to the gateway
423 public void updateDatapoint(HmDatapoint dp) throws IOException {
424 getGateway().loadDatapointValue(dp);
428 * Deletes a device from the gateway.
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.
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);