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.communicator;
15 import static org.openhab.binding.homematic.internal.misc.HomematicConstants.*;
17 import java.io.IOException;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
25 import java.util.Map.Entry;
27 import java.util.TreeMap;
28 import java.util.concurrent.ScheduledExecutorService;
29 import java.util.concurrent.ScheduledFuture;
30 import java.util.concurrent.TimeUnit;
32 import org.apache.commons.lang.StringUtils;
33 import org.eclipse.jetty.client.HttpClient;
34 import org.openhab.binding.homematic.internal.common.HomematicConfig;
35 import org.openhab.binding.homematic.internal.communicator.client.BinRpcClient;
36 import org.openhab.binding.homematic.internal.communicator.client.RpcClient;
37 import org.openhab.binding.homematic.internal.communicator.client.TransferMode;
38 import org.openhab.binding.homematic.internal.communicator.client.UnknownParameterSetException;
39 import org.openhab.binding.homematic.internal.communicator.client.XmlRpcClient;
40 import org.openhab.binding.homematic.internal.communicator.parser.ListBidcosInterfacesParser;
41 import org.openhab.binding.homematic.internal.communicator.server.BinRpcServer;
42 import org.openhab.binding.homematic.internal.communicator.server.RpcEventListener;
43 import org.openhab.binding.homematic.internal.communicator.server.RpcServer;
44 import org.openhab.binding.homematic.internal.communicator.server.XmlRpcServer;
45 import org.openhab.binding.homematic.internal.communicator.virtual.BatteryTypeVirtualDatapointHandler;
46 import org.openhab.binding.homematic.internal.communicator.virtual.ButtonVirtualDatapointHandler;
47 import org.openhab.binding.homematic.internal.communicator.virtual.DeleteDeviceModeVirtualDatapointHandler;
48 import org.openhab.binding.homematic.internal.communicator.virtual.DeleteDeviceVirtualDatapointHandler;
49 import org.openhab.binding.homematic.internal.communicator.virtual.DisplayOptionsVirtualDatapointHandler;
50 import org.openhab.binding.homematic.internal.communicator.virtual.DisplayTextVirtualDatapoint;
51 import org.openhab.binding.homematic.internal.communicator.virtual.FirmwareVirtualDatapointHandler;
52 import org.openhab.binding.homematic.internal.communicator.virtual.HmwIoModuleVirtualDatapointHandler;
53 import org.openhab.binding.homematic.internal.communicator.virtual.InstallModeDurationVirtualDatapoint;
54 import org.openhab.binding.homematic.internal.communicator.virtual.InstallModeVirtualDatapoint;
55 import org.openhab.binding.homematic.internal.communicator.virtual.OnTimeAutomaticVirtualDatapointHandler;
56 import org.openhab.binding.homematic.internal.communicator.virtual.ReloadAllFromGatewayVirtualDatapointHandler;
57 import org.openhab.binding.homematic.internal.communicator.virtual.ReloadFromGatewayVirtualDatapointHandler;
58 import org.openhab.binding.homematic.internal.communicator.virtual.ReloadRssiVirtualDatapointHandler;
59 import org.openhab.binding.homematic.internal.communicator.virtual.RssiVirtualDatapointHandler;
60 import org.openhab.binding.homematic.internal.communicator.virtual.SignalStrengthVirtualDatapointHandler;
61 import org.openhab.binding.homematic.internal.communicator.virtual.StateContactVirtualDatapointHandler;
62 import org.openhab.binding.homematic.internal.communicator.virtual.VirtualDatapointHandler;
63 import org.openhab.binding.homematic.internal.communicator.virtual.VirtualGateway;
64 import org.openhab.binding.homematic.internal.misc.DelayedExecuter;
65 import org.openhab.binding.homematic.internal.misc.DelayedExecuter.DelayedExecuterCallback;
66 import org.openhab.binding.homematic.internal.misc.HomematicClientException;
67 import org.openhab.binding.homematic.internal.misc.HomematicConstants;
68 import org.openhab.binding.homematic.internal.misc.MiscUtils;
69 import org.openhab.binding.homematic.internal.model.HmChannel;
70 import org.openhab.binding.homematic.internal.model.HmDatapoint;
71 import org.openhab.binding.homematic.internal.model.HmDatapointConfig;
72 import org.openhab.binding.homematic.internal.model.HmDatapointInfo;
73 import org.openhab.binding.homematic.internal.model.HmDevice;
74 import org.openhab.binding.homematic.internal.model.HmGatewayInfo;
75 import org.openhab.binding.homematic.internal.model.HmInterface;
76 import org.openhab.binding.homematic.internal.model.HmParamsetType;
77 import org.openhab.binding.homematic.internal.model.HmRssiInfo;
78 import org.openhab.binding.homematic.internal.model.HmValueType;
79 import org.openhab.core.common.ThreadPoolManager;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
84 * The {@link AbstractHomematicGateway} is the main class for the communication with a Homematic gateway.
86 * @author Gerhard Riegler - Initial contribution
88 public abstract class AbstractHomematicGateway implements RpcEventListener, HomematicGateway, VirtualGateway {
89 private final Logger logger = LoggerFactory.getLogger(AbstractHomematicGateway.class);
90 public static final double DEFAULT_DISABLE_DELAY = 2.0;
91 private static final long CONNECTION_TRACKER_INTERVAL_SECONDS = 15;
92 private static final String GATEWAY_POOL_NAME = "homematicGateway";
94 private final Map<TransferMode, RpcClient<?>> rpcClients = new HashMap<>();
95 private final Map<TransferMode, RpcServer> rpcServers = new HashMap<>();
97 protected HomematicConfig config;
98 protected HttpClient httpClient;
99 private final String id;
100 private final HomematicGatewayAdapter gatewayAdapter;
101 private final DelayedExecuter sendDelayedExecutor = new DelayedExecuter();
102 private final DelayedExecuter receiveDelayedExecutor = new DelayedExecuter();
103 private final Set<HmDatapointInfo> echoEvents = Collections.synchronizedSet(new HashSet<>());
104 private ScheduledFuture<?> connectionTrackerFuture;
105 private ConnectionTrackerThread connectionTrackerThread;
106 private final Map<String, HmDevice> devices = Collections.synchronizedMap(new HashMap<>());
107 private final Map<HmInterface, TransferMode> availableInterfaces = new TreeMap<>();
108 private static List<VirtualDatapointHandler> virtualDatapointHandlers = new ArrayList<>();
109 private boolean cancelLoadAllMetadata;
110 private boolean initialized;
111 private boolean newDeviceEventsEnabled;
112 private ScheduledFuture<?> enableNewDeviceFuture;
113 private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(GATEWAY_POOL_NAME);
116 // loads all virtual datapoints
117 virtualDatapointHandlers.add(new BatteryTypeVirtualDatapointHandler());
118 virtualDatapointHandlers.add(new FirmwareVirtualDatapointHandler());
119 virtualDatapointHandlers.add(new DisplayOptionsVirtualDatapointHandler());
120 virtualDatapointHandlers.add(new ReloadFromGatewayVirtualDatapointHandler());
121 virtualDatapointHandlers.add(new ReloadAllFromGatewayVirtualDatapointHandler());
122 virtualDatapointHandlers.add(new OnTimeAutomaticVirtualDatapointHandler());
123 virtualDatapointHandlers.add(new InstallModeVirtualDatapoint());
124 virtualDatapointHandlers.add(new InstallModeDurationVirtualDatapoint());
125 virtualDatapointHandlers.add(new DeleteDeviceModeVirtualDatapointHandler());
126 virtualDatapointHandlers.add(new DeleteDeviceVirtualDatapointHandler());
127 virtualDatapointHandlers.add(new RssiVirtualDatapointHandler());
128 virtualDatapointHandlers.add(new ReloadRssiVirtualDatapointHandler());
129 virtualDatapointHandlers.add(new StateContactVirtualDatapointHandler());
130 virtualDatapointHandlers.add(new SignalStrengthVirtualDatapointHandler());
131 virtualDatapointHandlers.add(new DisplayTextVirtualDatapoint());
132 virtualDatapointHandlers.add(new HmwIoModuleVirtualDatapointHandler());
133 virtualDatapointHandlers.add(new ButtonVirtualDatapointHandler());
136 public AbstractHomematicGateway(String id, HomematicConfig config, HomematicGatewayAdapter gatewayAdapter,
137 HttpClient httpClient) {
139 this.config = config;
140 this.gatewayAdapter = gatewayAdapter;
141 this.httpClient = httpClient;
145 public void initialize() throws IOException {
146 logger.debug("Initializing gateway with id '{}'", id);
148 HmGatewayInfo gatewayInfo = config.getGatewayInfo();
149 if (gatewayInfo.isHomegear()) {
151 availableInterfaces.put(HmInterface.RF, TransferMode.BIN_RPC);
152 } else if (gatewayInfo.isCCU()) {
154 if (gatewayInfo.isRfInterface()) {
155 availableInterfaces.put(HmInterface.RF, TransferMode.XML_RPC);
157 if (gatewayInfo.isWiredInterface()) {
158 availableInterfaces.put(HmInterface.WIRED, TransferMode.XML_RPC);
160 if (gatewayInfo.isHmipInterface()) {
161 availableInterfaces.put(HmInterface.HMIP, TransferMode.XML_RPC);
163 if (gatewayInfo.isCuxdInterface()) {
164 availableInterfaces.put(HmInterface.CUXD, TransferMode.BIN_RPC);
166 if (gatewayInfo.isGroupInterface()) {
167 availableInterfaces.put(HmInterface.GROUP, TransferMode.XML_RPC);
171 if (gatewayInfo.isRfInterface()) {
172 availableInterfaces.put(HmInterface.RF, TransferMode.XML_RPC);
174 if (gatewayInfo.isWiredInterface()) {
175 availableInterfaces.put(HmInterface.WIRED, TransferMode.XML_RPC);
177 if (gatewayInfo.isHmipInterface()) {
178 availableInterfaces.put(HmInterface.HMIP, TransferMode.XML_RPC);
182 logger.info("{}", config.getGatewayInfo());
183 StringBuilder sb = new StringBuilder();
184 for (Entry<HmInterface, TransferMode> entry : availableInterfaces.entrySet()) {
185 sb.append(entry.getKey()).append(":").append(entry.getValue()).append(", ");
187 if (sb.length() > 2) {
188 sb.setLength(sb.length() - 2);
190 logger.debug("Used Homematic transfer modes: {}", sb.toString());
194 if (!config.getGatewayInfo().isHomegear()) {
195 // delay the newDevice event handling at startup, reduces some API calls
196 long delay = config.getGatewayInfo().isCCU1() ? 10 : 3;
197 enableNewDeviceFuture = scheduler.schedule(() -> {
198 newDeviceEventsEnabled = true;
199 }, delay, TimeUnit.MINUTES);
201 newDeviceEventsEnabled = true;
206 public void dispose() {
208 if (enableNewDeviceFuture != null) {
209 enableNewDeviceFuture.cancel(true);
211 newDeviceEventsEnabled = false;
213 sendDelayedExecutor.stop();
214 receiveDelayedExecutor.stop();
219 availableInterfaces.clear();
220 config.setGatewayInfo(null);
224 * Starts the Homematic gateway client.
226 protected synchronized void startClients() throws IOException {
227 for (TransferMode mode : availableInterfaces.values()) {
228 if (!rpcClients.containsKey(mode)) {
230 mode == TransferMode.XML_RPC ? new XmlRpcClient(config, httpClient) : new BinRpcClient(config));
236 * Stops the Homematic gateway client.
238 protected synchronized void stopClients() {
239 for (RpcClient<?> rpcClient : rpcClients.values()) {
246 * Starts the Homematic RPC server.
248 private synchronized void startServers() throws IOException {
249 for (TransferMode mode : availableInterfaces.values()) {
250 if (!rpcServers.containsKey(mode)) {
251 RpcServer rpcServer = mode == TransferMode.XML_RPC ? new XmlRpcServer(this, config)
252 : new BinRpcServer(this, config);
253 rpcServers.put(mode, rpcServer);
257 for (HmInterface hmInterface : availableInterfaces.keySet()) {
258 getRpcClient(hmInterface).init(hmInterface, hmInterface.toString() + "-" + id);
263 * Stops the Homematic RPC server.
265 private synchronized void stopServers() {
266 for (HmInterface hmInterface : availableInterfaces.keySet()) {
268 getRpcClient(hmInterface).release(hmInterface);
269 } catch (IOException ex) {
270 // recoverable exception, therefore only debug
271 logger.debug("Unable to release the connection to the gateway with id '{}': {}", id, ex.getMessage(),
276 for (TransferMode mode : rpcServers.keySet()) {
277 rpcServers.get(mode).shutdown();
283 public void startWatchdogs() {
284 logger.debug("Starting connection tracker for gateway with id '{}'", id);
285 connectionTrackerThread = new ConnectionTrackerThread();
286 connectionTrackerFuture = scheduler.scheduleWithFixedDelay(connectionTrackerThread, 30,
287 CONNECTION_TRACKER_INTERVAL_SECONDS, TimeUnit.SECONDS);
290 private void stopWatchdogs() {
291 if (connectionTrackerFuture != null) {
292 connectionTrackerFuture.cancel(true);
294 connectionTrackerThread = null;
298 * Returns the default interface to communicate with the Homematic gateway.
300 protected HmInterface getDefaultInterface() {
301 return availableInterfaces.containsKey(HmInterface.RF) ? HmInterface.RF : HmInterface.HMIP;
305 public RpcClient<?> getRpcClient(HmInterface hmInterface) throws IOException {
306 RpcClient<?> rpcClient = rpcClients.get(availableInterfaces.get(hmInterface));
307 if (rpcClient == null) {
308 throw new IOException("RPC client for interface " + hmInterface + " not available");
314 * Loads all gateway variables into the given device.
316 protected abstract void loadVariables(HmChannel channel) throws IOException;
319 * Loads all gateway scripts into the given device.
321 protected abstract void loadScripts(HmChannel channel) throws IOException;
324 * Loads all names of the devices.
326 protected abstract void loadDeviceNames(Collection<HmDevice> devices) throws IOException;
329 * Sets a variable on the Homematic gateway.
331 protected abstract void setVariable(HmDatapoint dp, Object value) throws IOException;
334 * Execute a script on the Homematic gateway.
336 protected abstract void executeScript(HmDatapoint dp) throws IOException;
339 public HmDatapoint getDatapoint(HmDatapointInfo dpInfo) throws HomematicClientException {
340 HmDevice device = getDevice(dpInfo.getAddress());
341 HmChannel channel = device.getChannel(dpInfo.getChannel());
342 if (channel == null) {
343 throw new HomematicClientException(String.format("Channel %s in device '%s' not found on gateway '%s'",
344 dpInfo.getChannel(), dpInfo.getAddress(), id));
346 HmDatapoint dp = channel.getDatapoint(dpInfo);
348 throw new HomematicClientException(String.format("Datapoint '%s' not found on gateway '%s'", dpInfo, id));
354 public HmDevice getDevice(String address) throws HomematicClientException {
355 HmDevice device = devices.get(address);
356 if (device == null) {
357 throw new HomematicClientException(
358 String.format("Device with address '%s' not found on gateway '%s'", address, id));
364 public void cancelLoadAllDeviceMetadata() {
365 cancelLoadAllMetadata = true;
369 public void loadAllDeviceMetadata() throws IOException {
370 cancelLoadAllMetadata = false;
371 // load all device descriptions
372 List<HmDevice> deviceDescriptions = getDeviceDescriptions();
374 // loading datapoints for all channels
375 Set<String> loadedDevices = new HashSet<>();
376 Map<String, Collection<HmDatapoint>> datapointsByChannelIdCache = new HashMap<>();
377 for (HmDevice device : deviceDescriptions) {
378 if (!cancelLoadAllMetadata) {
380 logger.trace("Loading metadata for device '{}' of type '{}'", device.getAddress(),
382 if (device.isGatewayExtras()) {
383 loadChannelValues(device.getChannel(HmChannel.CHANNEL_NUMBER_VARIABLE));
384 loadChannelValues(device.getChannel(HmChannel.CHANNEL_NUMBER_SCRIPT));
386 for (HmChannel channel : device.getChannels()) {
387 logger.trace(" Loading channel {}", channel);
388 // speed up metadata generation a little bit for equal channels in the gateway devices
389 if ((DEVICE_TYPE_VIRTUAL.equals(device.getType())
390 || DEVICE_TYPE_VIRTUAL_WIRED.equals(device.getType())) && channel.getNumber() > 1) {
391 HmChannel previousChannel = device.getChannel(channel.getNumber() - 1);
392 cloneAllDatapointsIntoChannel(channel, previousChannel.getDatapoints());
394 String channelId = String.format("%s:%s:%s", channel.getDevice().getType(),
395 channel.getDevice().getFirmware(), channel.getNumber());
396 Collection<HmDatapoint> cachedDatapoints = datapointsByChannelIdCache.get(channelId);
397 if (cachedDatapoints != null) {
398 // clone all datapoints
399 cloneAllDatapointsIntoChannel(channel, cachedDatapoints);
401 logger.trace(" Loading datapoints into channel {}", channel);
402 addChannelDatapoints(channel, HmParamsetType.MASTER);
403 addChannelDatapoints(channel, HmParamsetType.VALUES);
405 // Make sure to only cache non-reconfigurable channels. For reconfigurable channels,
406 // the data point set might change depending on the selected mode.
407 if (!channel.isReconfigurable()) {
408 datapointsByChannelIdCache.put(channelId, channel.getDatapoints());
414 prepareDevice(device);
415 loadedDevices.add(device.getAddress());
416 gatewayAdapter.onDeviceLoaded(device);
417 } catch (IOException ex) {
418 logger.warn("Can't load device with address '{}' from gateway '{}': {}", device.getAddress(), id,
423 if (!cancelLoadAllMetadata) {
424 devices.keySet().retainAll(loadedDevices);
430 * Loads all datapoints from the gateway.
432 protected void addChannelDatapoints(HmChannel channel, HmParamsetType paramsetType) throws IOException {
434 getRpcClient(channel.getDevice().getHmInterface()).addChannelDatapoints(channel, paramsetType);
435 } catch (UnknownParameterSetException ex) {
437 "Can not load metadata for device: {}, channel: {}, paramset: {}, maybe there are no channels available",
438 channel.getDevice().getAddress(), channel.getNumber(), paramsetType);
443 * Loads all device descriptions from the gateway.
445 private List<HmDevice> getDeviceDescriptions() throws IOException {
446 List<HmDevice> deviceDescriptions = new ArrayList<>();
447 for (HmInterface hmInterface : availableInterfaces.keySet()) {
448 deviceDescriptions.addAll(getRpcClient(hmInterface).listDevices(hmInterface));
450 if (!cancelLoadAllMetadata) {
451 deviceDescriptions.add(createGatewayDevice());
452 loadDeviceNames(deviceDescriptions);
454 return deviceDescriptions;
458 * Clones all datapoints into the given channel.
460 private void cloneAllDatapointsIntoChannel(HmChannel channel, Collection<HmDatapoint> datapoints) {
461 logger.trace(" Cloning {} datapoints into channel {}", datapoints.size(), channel);
462 for (HmDatapoint dp : datapoints) {
463 if (!dp.isVirtual()) {
464 HmDatapoint clonedDp = dp.clone();
465 clonedDp.setValue(null);
466 channel.addDatapoint(clonedDp);
472 public void loadChannelValues(HmChannel channel) throws IOException {
473 if (channel.getDevice().isGatewayExtras()) {
474 if (channel.getNumber() != HmChannel.CHANNEL_NUMBER_EXTRAS) {
475 List<HmDatapoint> datapoints = channel.getDatapoints();
477 if (channel.getNumber() == HmChannel.CHANNEL_NUMBER_VARIABLE) {
478 loadVariables(channel);
479 logger.debug("Loaded {} gateway variable(s)", datapoints.size());
480 } else if (channel.getNumber() == HmChannel.CHANNEL_NUMBER_SCRIPT) {
481 loadScripts(channel);
482 logger.debug("Loaded {} gateway script(s)", datapoints.size());
486 logger.debug("Loading values for channel {} of device '{}'", channel, channel.getDevice().getAddress());
487 setChannelDatapointValues(channel, HmParamsetType.MASTER);
488 setChannelDatapointValues(channel, HmParamsetType.VALUES);
491 for (HmDatapoint dp : channel.getDatapoints()) {
492 handleVirtualDatapointEvent(dp, false);
495 channel.setInitialized(true);
499 public void updateChannelValueDatapoints(HmChannel channel) throws IOException {
500 logger.debug("Updating value datapoints for channel {} of device '{}', has {} datapoints before", channel,
501 channel.getDevice().getAddress(), channel.getDatapoints().size());
503 channel.removeValueDatapoints();
504 addChannelDatapoints(channel, HmParamsetType.VALUES);
505 setChannelDatapointValues(channel, HmParamsetType.VALUES);
507 logger.debug("Updated value datapoints for channel {} of device '{}' (function {}), now has {} datapoints",
508 channel, channel.getDevice().getAddress(), channel.getCurrentFunction(),
509 channel.getDatapoints().size());
513 * Sets all datapoint values for the given channel.
515 protected void setChannelDatapointValues(HmChannel channel, HmParamsetType paramsetType) throws IOException {
517 getRpcClient(channel.getDevice().getHmInterface()).setChannelDatapointValues(channel, paramsetType);
518 } catch (UnknownParameterSetException ex) {
520 "Can not load values for device: {}, channel: {}, paramset: {}, maybe there are no values available",
521 channel.getDevice().getAddress(), channel.getNumber(), paramsetType);
526 public void loadDatapointValue(HmDatapoint dp) throws IOException {
527 getRpcClient(dp.getChannel().getDevice().getHmInterface()).getDatapointValue(dp);
531 public void loadRssiValues() throws IOException {
532 for (HmInterface hmInterface : availableInterfaces.keySet()) {
533 if (hmInterface == HmInterface.RF) {
534 List<HmRssiInfo> rssiInfos = getRpcClient(hmInterface).loadRssiInfo(hmInterface);
535 for (HmRssiInfo hmRssiInfo : rssiInfos) {
536 updateRssiInfo(hmRssiInfo.getAddress(), DATAPOINT_NAME_RSSI_DEVICE, hmRssiInfo.getDevice());
537 updateRssiInfo(hmRssiInfo.getAddress(), DATAPOINT_NAME_RSSI_PEER, hmRssiInfo.getPeer());
544 public void setInstallMode(boolean enable, int seconds) throws IOException {
545 HmDevice gwExtrasHm = devices.get(HmDevice.ADDRESS_GATEWAY_EXTRAS);
547 if (gwExtrasHm != null) {
548 // since the homematic virtual device exist: try setting install mode via its dataPoints
549 HmDatapoint installModeDataPoint = null;
550 HmDatapoint installModeDurationDataPoint = null;
552 // collect virtual datapoints to be accessed
553 HmChannel hmChannel = gwExtrasHm.getChannel(HmChannel.CHANNEL_NUMBER_EXTRAS);
554 HmDatapointInfo installModeDurationDataPointInfo = new HmDatapointInfo(HmParamsetType.VALUES, hmChannel,
555 HomematicConstants.VIRTUAL_DATAPOINT_NAME_INSTALL_MODE_DURATION);
557 installModeDurationDataPoint = hmChannel.getDatapoint(installModeDurationDataPointInfo);
560 HmDatapointInfo installModeDataPointInfo = new HmDatapointInfo(HmParamsetType.VALUES, hmChannel,
561 HomematicConstants.VIRTUAL_DATAPOINT_NAME_INSTALL_MODE);
563 installModeDataPoint = hmChannel.getDatapoint(installModeDataPointInfo);
565 // first set duration on the datapoint
566 if (installModeDurationDataPoint != null) {
568 VirtualDatapointHandler handler = getVirtualDatapointHandler(installModeDurationDataPoint, null);
569 handler.handleCommand(this, installModeDurationDataPoint, new HmDatapointConfig(), seconds);
571 // notify thing if exists
572 gatewayAdapter.onStateUpdated(installModeDurationDataPoint);
573 } catch (HomematicClientException ex) {
574 logger.warn("Failed to send datapoint {}", installModeDurationDataPoint, ex);
578 // now that the duration is set, we can enable / disable
579 if (installModeDataPoint != null) {
581 VirtualDatapointHandler handler = getVirtualDatapointHandler(installModeDataPoint, null);
582 handler.handleCommand(this, installModeDataPoint, new HmDatapointConfig(), enable);
584 // notify thing if exists
585 gatewayAdapter.onStateUpdated(installModeDataPoint);
588 } catch (HomematicClientException ex) {
589 logger.warn("Failed to send datapoint {}", installModeDataPoint, ex);
594 // no gwExtrasHm available (or previous approach failed), therefore use rpc client directly
595 for (HmInterface hmInterface : availableInterfaces.keySet()) {
596 if (hmInterface == HmInterface.RF || hmInterface == HmInterface.CUXD) {
597 getRpcClient(hmInterface).setInstallMode(hmInterface, enable, seconds);
603 public int getInstallMode() throws IOException {
604 for (HmInterface hmInterface : availableInterfaces.keySet()) {
605 if (hmInterface == HmInterface.RF || hmInterface == HmInterface.CUXD) {
606 return getRpcClient(hmInterface).getInstallMode(hmInterface);
610 throw new IllegalStateException("Could not determine install mode because no suitable interface exists");
613 private void updateRssiInfo(String address, String datapointName, Integer value) {
614 HmDatapointInfo dpInfo = new HmDatapointInfo(address, HmParamsetType.VALUES, 0, datapointName);
617 channel = getDevice(dpInfo.getAddress()).getChannel(0);
618 if (channel != null) {
619 eventReceived(dpInfo, value);
621 } catch (HomematicClientException e) {
627 public void triggerDeviceValuesReload(HmDevice device) {
628 logger.debug("Triggering values reload for device '{}'", device.getAddress());
629 for (HmChannel channel : device.getChannels()) {
630 channel.setInitialized(false);
632 gatewayAdapter.reloadDeviceValues(device);
636 public void sendDatapointIgnoreVirtual(HmDatapoint dp, HmDatapointConfig dpConfig, Object newValue)
637 throws IOException, HomematicClientException {
638 sendDatapoint(dp, dpConfig, newValue, null, true);
642 public void sendDatapoint(HmDatapoint dp, HmDatapointConfig dpConfig, Object newValue, String rxMode)
643 throws IOException, HomematicClientException {
644 sendDatapoint(dp, dpConfig, newValue, rxMode, false);
648 * Main method for sending datapoints to the gateway. It handles scripts, variables, virtual datapoints, delayed
649 * executions and auto disabling.
651 private void sendDatapoint(final HmDatapoint dp, final HmDatapointConfig dpConfig, final Object newValue,
652 final String rxMode, final boolean ignoreVirtualDatapoints) throws IOException, HomematicClientException {
653 final HmDatapointInfo dpInfo = new HmDatapointInfo(dp);
654 if (dp.isPressDatapoint() || (config.getGatewayInfo().isHomegear() && dp.isVariable())) {
655 echoEvents.add(dpInfo);
657 if (dp.isReadOnly()) {
658 logger.warn("Datapoint is readOnly, it is not published to the gateway with id '{}': '{}'", id, dpInfo);
659 } else if (HmValueType.ACTION == dp.getType() && MiscUtils.isFalseValue(newValue)) {
661 "Datapoint of type ACTION cannot be set to false, it is not published to the gateway with id '{}': '{}'",
664 final VirtualGateway gateway = this;
665 sendDelayedExecutor.start(dpInfo, dpConfig.getDelay(), new DelayedExecuterCallback() {
668 public void execute() throws IOException, HomematicClientException {
669 VirtualDatapointHandler virtualDatapointHandler = ignoreVirtualDatapoints ? null
670 : getVirtualDatapointHandler(dp, newValue);
671 if (virtualDatapointHandler != null) {
672 logger.debug("Handling virtual datapoint '{}' on gateway with id '{}'", dp.getName(), id);
673 virtualDatapointHandler.handleCommand(gateway, dp, dpConfig, newValue);
674 } else if (dp.isScript()) {
675 if (MiscUtils.isTrueValue(newValue)) {
676 logger.debug("Executing script '{}' on gateway with id '{}'", dp.getInfo(), id);
679 } else if (dp.isVariable()) {
680 logger.debug("Sending variable '{}' with value '{}' to gateway with id '{}'", dp.getInfo(),
682 setVariable(dp, newValue);
684 logger.debug("Sending datapoint '{}' with value '{}' to gateway with id '{}' using rxMode '{}'",
685 dpInfo, newValue, id, rxMode == null ? "DEFAULT" : rxMode);
686 getRpcClient(dp.getChannel().getDevice().getHmInterface()).setDatapointValue(dp, newValue,
689 dp.setValue(newValue);
691 if (MiscUtils.isTrueValue(newValue)
692 && (dp.isPressDatapoint() || dp.isScript() || dp.isActionType())) {
693 disableDatapoint(dp, DEFAULT_DISABLE_DELAY);
701 * Returns a VirtualDatapointHandler for the given datapoint if available.
703 private VirtualDatapointHandler getVirtualDatapointHandler(HmDatapoint dp, Object value) {
704 for (VirtualDatapointHandler vdph : virtualDatapointHandlers) {
705 if (vdph.canHandleCommand(dp, value)) {
712 private void handleVirtualDatapointEvent(HmDatapoint dp, boolean publishToGateway) {
713 for (VirtualDatapointHandler vdph : virtualDatapointHandlers) {
714 if (vdph.canHandleEvent(dp)) {
715 vdph.handleEvent(this, dp);
716 if (publishToGateway) {
717 gatewayAdapter.onStateUpdated(vdph.getVirtualDatapoint(dp.getChannel()));
724 public void eventReceived(HmDatapointInfo dpInfo, Object newValue) {
725 String className = newValue == null ? "Unknown" : newValue.getClass().getSimpleName();
726 logger.debug("Received new ({}) value '{}' for '{}' from gateway with id '{}'", className, newValue, dpInfo,
729 if (echoEvents.remove(dpInfo)) {
730 logger.debug("Echo event detected, ignoring '{}'", dpInfo);
733 if (connectionTrackerThread != null && dpInfo.isPong() && id.equals(newValue)) {
734 connectionTrackerThread.pongReceived();
737 final HmDatapoint dp = getDatapoint(dpInfo);
738 HmDatapointConfig config = gatewayAdapter.getDatapointConfig(dp);
739 receiveDelayedExecutor.start(dpInfo, config.getReceiveDelay(), () -> {
740 dp.setValue(newValue);
742 gatewayAdapter.onStateUpdated(dp);
743 handleVirtualDatapointEvent(dp, true);
744 if (dp.isPressDatapoint() && MiscUtils.isTrueValue(dp.getValue())) {
745 disableDatapoint(dp, DEFAULT_DISABLE_DELAY);
749 } catch (HomematicClientException | IOException ex) {
756 public void newDevices(List<String> adresses) {
757 if (initialized && newDeviceEventsEnabled) {
758 for (String address : adresses) {
760 logger.debug("New device '{}' detected on gateway with id '{}'", address, id);
761 List<HmDevice> deviceDescriptions = getDeviceDescriptions();
762 for (HmDevice device : deviceDescriptions) {
763 if (device.getAddress().equals(address)) {
764 for (HmChannel channel : device.getChannels()) {
765 addChannelDatapoints(channel, HmParamsetType.MASTER);
766 addChannelDatapoints(channel, HmParamsetType.VALUES);
768 prepareDevice(device);
769 gatewayAdapter.onNewDevice(device);
772 } catch (Exception ex) {
773 logger.error("{}", ex.getMessage(), ex);
780 public void deleteDevices(List<String> addresses) {
782 for (String address : addresses) {
783 logger.debug("Device '{}' removed from gateway with id '{}'", address, id);
784 HmDevice device = devices.remove(address);
785 if (device != null) {
786 gatewayAdapter.onDeviceDeleted(device);
793 public String getId() {
798 public HomematicGatewayAdapter getGatewayAdapter() {
799 return gatewayAdapter;
803 * Creates a virtual device for handling variables, scripts and other special gateway functions.
805 private HmDevice createGatewayDevice() {
806 String type = String.format("%s-%s", HmDevice.TYPE_GATEWAY_EXTRAS, StringUtils.upperCase(id));
807 HmDevice device = new HmDevice(HmDevice.ADDRESS_GATEWAY_EXTRAS, getDefaultInterface(), type,
808 config.getGatewayInfo().getId(), null, null);
809 device.setName(HmDevice.TYPE_GATEWAY_EXTRAS);
811 device.addChannel(new HmChannel(HmChannel.TYPE_GATEWAY_EXTRAS, HmChannel.CHANNEL_NUMBER_EXTRAS));
812 device.addChannel(new HmChannel(HmChannel.TYPE_GATEWAY_VARIABLE, HmChannel.CHANNEL_NUMBER_VARIABLE));
813 device.addChannel(new HmChannel(HmChannel.TYPE_GATEWAY_SCRIPT, HmChannel.CHANNEL_NUMBER_SCRIPT));
819 * Adds virtual datapoints to the device.
821 private void prepareDevice(HmDevice device) {
822 for (VirtualDatapointHandler vdph : virtualDatapointHandlers) {
823 vdph.initialize(device);
826 devices.put(device.getAddress(), device);
827 logger.debug("Loaded device '{}' ({}) with {} datapoints", device.getAddress(), device.getType(),
828 device.getDatapointCount());
830 if (logger.isTraceEnabled()) {
831 logger.trace("{}", device);
832 for (HmChannel channel : device.getChannels()) {
833 logger.trace(" {}", channel);
834 for (HmDatapoint dp : channel.getDatapoints()) {
835 logger.trace(" {}", dp);
842 public void disableDatapoint(final HmDatapoint dp, double delay) {
844 sendDelayedExecutor.start(new HmDatapointInfo(dp), delay, new DelayedExecuterCallback() {
847 public void execute() throws IOException {
848 if (MiscUtils.isTrueValue(dp.getValue())) {
849 dp.setValue(Boolean.FALSE);
850 gatewayAdapter.onStateUpdated(dp);
851 handleVirtualDatapointEvent(dp, true);
852 } else if (dp.getType() == HmValueType.ENUM && dp.getValue() != null && !dp.getValue().equals(0)) {
853 dp.setValue(dp.getMinValue());
854 gatewayAdapter.onStateUpdated(dp);
855 handleVirtualDatapointEvent(dp, true);
859 } catch (IOException | HomematicClientException ex) {
860 logger.error("{}", ex.getMessage(), ex);
865 public void deleteDevice(String address, boolean reset, boolean force, boolean defer) {
866 for (RpcClient<?> rpcClient : rpcClients.values()) {
868 rpcClient.deleteDevice(getDevice(address), translateFlags(reset, force, defer));
869 } catch (HomematicClientException e) {
870 // thrown by getDevice(address) if no device for the given address is paired on the gateway
871 logger.info("Device deletion not possible: {}", e.getMessage());
872 } catch (IOException e) {
873 logger.warn("Device deletion failed: {}", e.getMessage(), e);
878 private int translateFlags(boolean reset, boolean force, boolean defer) {
879 final int resetFlag = 0b001;
880 final int forceFlag = 0b010;
881 final int deferFlag = 0b100;
885 resultFlag += resetFlag;
888 resultFlag += forceFlag;
891 resultFlag += deferFlag;
898 * Thread which validates the connection to the gateway and restarts the RPC client if necessary.
899 * It also polls for the current duty cycle ratio of the gateway after every successful connection validation.
901 private class ConnectionTrackerThread implements Runnable {
902 private boolean connectionLost;
903 private boolean ping;
904 private boolean pong;
910 handleInvalidConnection("No Pong received!");
914 if (config.getGatewayInfo().isCCU1()) {
915 // the CCU1 does not support the ping command, we need a workaround
916 getRpcClient(getDefaultInterface()).listBidcosInterfaces(getDefaultInterface());
917 // if there is no exception, connection is valid
920 getRpcClient(getDefaultInterface()).ping(getDefaultInterface(), id);
925 updateDutyCycleRatio();
926 } catch (IOException e) {
927 logger.debug("Could not read the duty cycle ratio: {}", e.getMessage());
929 } catch (IOException ex) {
931 handleInvalidConnection("IOException " + ex.getMessage());
932 } catch (IOException ex2) {
938 public void pongReceived() {
940 connectionConfirmed();
943 private void updateDutyCycleRatio() throws IOException {
944 ListBidcosInterfacesParser parser = getRpcClient(getDefaultInterface())
945 .listBidcosInterfaces(getDefaultInterface());
946 Integer dutyCycleRatio = parser.getDutyCycleRatio();
948 if (dutyCycleRatio != null) {
949 gatewayAdapter.onDutyCycleRatioUpdate(dutyCycleRatio);
953 private void connectionConfirmed() {
954 if (connectionLost) {
955 connectionLost = false;
956 logger.info("Connection resumed on gateway '{}'", id);
957 gatewayAdapter.onConnectionResumed();
961 private void handleInvalidConnection(String cause) throws IOException {
963 if (!connectionLost) {
964 connectionLost = true;
965 logger.warn("Connection lost on gateway '{}', cause: \"{}\"", id, cause);
966 gatewayAdapter.onConnectionLost();