2 * Copyright (c) 2010-2022 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.getXmlCallbackPort() == 0) {
217 homematicConfig.setXmlCallbackPort(portPool.getNextPort());
219 portPool.setInUse(homematicConfig.getXmlCallbackPort());
221 if (homematicConfig.getBinCallbackPort() == 0) {
222 homematicConfig.setBinCallbackPort(portPool.getNextPort());
224 portPool.setInUse(homematicConfig.getBinCallbackPort());
226 logger.debug("{}", homematicConfig);
227 return homematicConfig;
231 public Collection<Class<? extends ThingHandlerService>> getServices() {
232 return Collections.singleton(HomematicDeviceDiscoveryService.class);
236 public void handleCommand(ChannelUID channelUID, Command command) {
237 if (RefreshType.REFRESH == command) {
238 logger.debug("Refreshing bridge '{}'", getThing().getUID().getId());
239 reloadAllDeviceValues();
244 * Returns the TypeGenerator.
246 public HomematicTypeGenerator getTypeGenerator() {
247 return typeGenerator;
251 * Returns the HomematicGateway.
253 public HomematicGateway getGateway() {
258 * Updates the thing for the given Homematic device.
260 private void updateThing(HmDevice device) {
261 Thing hmThing = getThing().getThing(UidUtils.generateThingUID(device, getThing()));
262 if (hmThing != null) {
263 HomematicThingHandler thingHandler = (HomematicThingHandler) hmThing.getHandler();
264 if (thingHandler != null) {
265 thingHandler.thingUpdated(hmThing);
266 for (Channel channel : hmThing.getChannels()) {
267 thingHandler.handleRefresh(channel.getUID());
274 public void onStateUpdated(HmDatapoint dp) {
275 Thing hmThing = getThing().getThing(UidUtils.generateThingUID(dp.getChannel().getDevice(), getThing()));
276 if (hmThing != null) {
277 final ThingStatus status = hmThing.getStatus();
278 if (status == ThingStatus.ONLINE || status == ThingStatus.OFFLINE) {
279 HomematicThingHandler thingHandler = (HomematicThingHandler) hmThing.getHandler();
280 if (thingHandler != null) {
281 thingHandler.updateDatapointState(dp);
288 public HmDatapointConfig getDatapointConfig(HmDatapoint dp) {
289 Thing hmThing = getThing().getThing(UidUtils.generateThingUID(dp.getChannel().getDevice(), getThing()));
290 if (hmThing != null) {
291 HomematicThingHandler thingHandler = (HomematicThingHandler) hmThing.getHandler();
292 if (thingHandler != null) {
293 return thingHandler.getChannelConfig(dp);
296 return new HmDatapointConfig();
300 public void onNewDevice(HmDevice device) {
301 onDeviceLoaded(device);
305 @SuppressWarnings("null")
307 public void onDeviceDeleted(HmDevice device) {
308 discoveryService.deviceRemoved(device);
311 Thing hmThing = getThing().getThing(UidUtils.generateThingUID(device, getThing()));
312 if (hmThing != null && hmThing.getHandler() != null) {
313 ((HomematicThingHandler) hmThing.getHandler()).deviceRemoved();
318 public void onConnectionLost() {
319 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connection lost");
323 public void onConnectionResumed() {
324 updateStatus(ThingStatus.ONLINE);
325 reloadAllDeviceValues();
329 public void onDeviceLoaded(HmDevice device) {
330 typeGenerator.generate(device);
331 if (discoveryService != null) {
332 discoveryService.deviceDiscovered(device);
335 Thing hmThing = getThing().getThing(UidUtils.generateThingUID(device, getThing()));
336 if (hmThing != null) {
337 HomematicThingHandler thingHandler = (HomematicThingHandler) hmThing.getHandler();
338 if (thingHandler != null) {
339 thingHandler.deviceLoaded(device);
345 public void onDutyCycleRatioUpdate(int dutyCycleRatio) {
346 synchronized (dutyCycleRatioUpdateLock) {
347 this.dutyCycleRatio = dutyCycleRatio;
348 Channel dutyCycleRatioChannel = thing.getChannel(CHANNEL_TYPE_DUTY_CYCLE_RATIO);
349 if (dutyCycleRatioChannel != null) {
350 this.updateState(dutyCycleRatioChannel.getUID(),
351 new DecimalType(dutyCycleRatio < 0 ? 0 : dutyCycleRatio));
354 if (!isInDutyCycle) {
355 if (dutyCycleRatio >= DUTY_CYCLE_RATIO_LIMIT) {
356 logger.info("Duty cycle threshold exceeded by homematic bridge {}, it will go OFFLINE.",
358 isInDutyCycle = true;
359 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DUTY_CYCLE);
360 } else if (dutyCycleRatio == DUTY_CYCLE_DISCONNECTED) {
362 "Duty cycle indicates a communication problem by homematic bridge {}, it will go OFFLINE.",
364 isInDutyCycle = true;
365 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
368 if (dutyCycleRatio < DUTY_CYCLE_RATIO_LIMIT && dutyCycleRatio != DUTY_CYCLE_DISCONNECTED) {
369 logger.info("Homematic bridge {}: duty cycle back to normal and bridge will come ONLINE again.",
371 isInDutyCycle = false;
372 this.updateStatus(ThingStatus.ONLINE);
379 * Returns the last value for the duty cycle ratio that was retrieved from the homematic gateway.
381 * @return The duty cycle ratio of the gateway
383 public int getDutyCycleRatio() {
384 return dutyCycleRatio;
388 public void reloadDeviceValues(HmDevice device) {
390 if (device.isGatewayExtras()) {
391 typeGenerator.generate(device);
396 public void reloadAllDeviceValues() {
397 for (Thing hmThing : getThing().getThings()) {
399 HmDevice device = gateway.getDevice(UidUtils.getHomematicAddress(hmThing));
400 gateway.triggerDeviceValuesReload(device);
401 } catch (HomematicClientException ex) {
402 logger.warn("{}", ex.getMessage());
408 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
409 if (((HomematicThingHandler) childHandler).isDeletionPending()) {
410 deleteFromGateway(UidUtils.getHomematicAddress(childThing), false, true, false);
415 * Updates the {@link HmDatapoint} by reloading the value from the homematic gateway.
417 * @param dp The HmDatapoint that shall be updated
418 * @throws IOException If there is a problem while communicating to the gateway
420 public void updateDatapoint(HmDatapoint dp) throws IOException {
421 getGateway().loadDatapointValue(dp);
425 * Deletes a device from the gateway.
427 * @param address The address of the device to be deleted
428 * @param reset <i>true</i> will perform a factory reset on the device before deleting it.
429 * @param force <i>true</i> will delete the device even if it is not reachable.
430 * @param defer <i>true</i> will delete the device once it becomes available.
432 public void deleteFromGateway(String address, boolean reset, boolean force, boolean defer) {
433 scheduler.submit(() -> {
434 logger.debug("Deleting the device '{}' from gateway '{}'", address, getBridge());
435 getGateway().deleteDevice(address, reset, force, defer);