/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.homematic.internal.communicator; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.openhab.binding.homematic.internal.common.HomematicContext; import org.openhab.binding.homematic.internal.communicator.ProviderItemIterator.ProviderItemIteratorCallback; import org.openhab.binding.homematic.internal.communicator.client.BaseHomematicClient.HmValueItemIteratorCallback; import org.openhab.binding.homematic.internal.communicator.client.HomematicClientException; import org.openhab.binding.homematic.internal.config.binding.DatapointConfig; import org.openhab.binding.homematic.internal.config.binding.HomematicBindingConfig; import org.openhab.binding.homematic.internal.converter.state.Converter; import org.openhab.binding.homematic.internal.model.HmRssiInfo; import org.openhab.binding.homematic.internal.model.HmValueItem; import org.openhab.core.items.Item; import org.openhab.core.types.State; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class which (re-)loads and holds the cached values from a Homematic server. * * @author Gerhard Riegler * @since 1.5.0 */ public class StateHolder { private static final Logger logger = LoggerFactory.getLogger(StateHolder.class); private HomematicContext context; private ExecutorService reloadExecutorPool; private boolean datapointReloadInProgress = false; private Map<HomematicBindingConfig, Object> refreshCache = new HashMap<HomematicBindingConfig, Object>(); private Map<HomematicBindingConfig, HmValueItem> datapoints = new HashMap<HomematicBindingConfig, HmValueItem>(); private Map<HomematicBindingConfig, HmValueItem> variables = new HashMap<HomematicBindingConfig, HmValueItem>(); public StateHolder(HomematicContext context) { this.context = context; } /** * If a datapoint reload is currently running, this returns true. */ public boolean isDatapointReloadInProgress() { return datapointReloadInProgress; } /** * A datapoint reload takes some seconds, this method is called when a event * receives from the Homematic server during the reload. */ public void addToRefreshCache(HomematicBindingConfig bindingConfig, Object value) { refreshCache.put(bindingConfig, value); } /** * Returns the cached HmValueItem. */ public HmValueItem getState(HomematicBindingConfig bindingConfig) { HmValueItem hmValueItem = datapoints.get(bindingConfig); if (hmValueItem == null) { hmValueItem = variables.get(bindingConfig); } return hmValueItem; } /** * Loads all datapoints from the Homematic server, only executed at startup. */ public void loadDatapoints() throws HomematicClientException { logger.info("Loading Homematic datapoints"); context.getHomematicClient().iterateAllDatapoints(new HmValueItemIteratorCallback() { @Override public void iterate(HomematicBindingConfig bindingConfig, HmValueItem hmValueItem) { datapoints.put(bindingConfig, hmValueItem); } }); logger.info("Finished loading {} Homematic datapoints", datapoints.size()); } /** * Reloads all datapoints from the Homematic server and publishes only * changed values to the openHAB bus. */ public void reloadDatapoints() { reloadExecutorPool.execute(new Runnable() { @Override public void run() { try { logger.debug("Reloading Homematic server datapoints"); datapointReloadInProgress = true; context.getHomematicClient().iterateAllDatapoints(new HmValueItemIteratorCallback() { @Override public void iterate(HomematicBindingConfig bindingConfig, HmValueItem hmValueItem) { if (!datapoints.containsKey(bindingConfig)) { logger.info("Adding new {}", bindingConfig); datapoints.put(bindingConfig, hmValueItem); } else { Object cachedValue = refreshCache.get(bindingConfig); if (cachedValue != null) { logger.debug("Value changed while refreshing from '{}' to '{}' for binding {}", hmValueItem.getValue(), cachedValue, bindingConfig); hmValueItem.setValue(cachedValue); } if (hasChanged(bindingConfig, datapoints.get(bindingConfig), hmValueItem)) { datapoints.put(bindingConfig, hmValueItem); publish(bindingConfig, hmValueItem); } } } }); logger.debug("Finished reloading {} Homematic server datapoints", datapoints.size()); } catch (HomematicClientException ex) { logger.error(ex.getMessage(), ex); } finally { datapointReloadInProgress = false; refreshCache.clear(); } } }); } /** * Reloads all RSSI values from the Homematic server and publishes only * changed values to the openHAB bus. */ public void reloadRssi() { reloadExecutorPool.execute(new Runnable() { @Override public void run() { try { logger.debug("Reloading Homematic server RSSI values"); Map<String, HmRssiInfo> rssiList = context.getHomematicClient().getRssiInfo(); for (String address : rssiList.keySet()) { HmRssiInfo rssiInfo = rssiList.get(address); updateRssiInfo(new DatapointConfig(address, "0", "RSSI_DEVICE"), rssiInfo.getDevice()); updateRssiInfo(new DatapointConfig(address, "0", "RSSI_PEER"), rssiInfo.getPeer()); } logger.debug("Finished reloading {} Homematic server RSSI values", rssiList.size()); } catch (HomematicClientException ex) { logger.error(ex.getMessage(), ex); } } }); } /** * Upates the RSSI datapoint if available. */ private void updateRssiInfo(DatapointConfig bindingConfig, Integer rssiValue) { HmValueItem valueItem = datapoints.get(bindingConfig); if (valueItem != null) { if (!valueItem.getValue().equals(rssiValue)) { logger.debug("Value changed from '{}' to '{}' for binding {}", valueItem == null ? "null" : valueItem.getValue(), rssiValue, bindingConfig); valueItem.setValue(rssiValue); publish(bindingConfig, valueItem); } } else { logger.debug("Rssi info not found: {}", bindingConfig); } } /** * Loads all variables from the Homematic server, only executed at startup. */ public void loadVariables() throws HomematicClientException { if (context.getHomematicClient().supportsVariables()) { logger.info("Loading Homematic Server variables"); context.getHomematicClient().iterateAllVariables(new HmValueItemIteratorCallback() { @Override public void iterate(HomematicBindingConfig bindingConfig, HmValueItem variable) { variables.put(bindingConfig, variable); } }); logger.info("Finished loading {} Homematic server variables", variables.size()); } } /** * Reloads all variables from the Homematic server and publishes only * changed values to the openHAB bus. */ public void reloadVariables() { if (context.getHomematicClient().supportsVariables()) { reloadExecutorPool.execute(new Runnable() { @Override public void run() { logger.debug("Reloading Homematic server variables"); try { context.getHomematicClient().iterateAllVariables(new HmValueItemIteratorCallback() { @Override public void iterate(HomematicBindingConfig bindingConfig, HmValueItem variable) { if (hasChanged(bindingConfig, variables.get(bindingConfig), variable)) { variables.put(bindingConfig, variable); publish(bindingConfig, variable); } } }); logger.debug("Finished reloading {} Homematic server variables", variables.size()); } catch (HomematicClientException ex) { logger.error(ex.getMessage(), ex); } } }); } } /** * Initializes the stateholder by creating a new executor pool. */ public void init() { reloadExecutorPool = Executors.newCachedThreadPool(); } /** * Destroys the cache. */ public void destroy() { datapointReloadInProgress = false; if (reloadExecutorPool != null) { reloadExecutorPool.shutdownNow(); reloadExecutorPool = null; } datapoints.clear(); variables.clear(); } /** * Returns true if the cached value is not up to date. */ protected boolean hasChanged(HomematicBindingConfig bindingConfig, HmValueItem parentHmValueItem, HmValueItem hmValueItem) { if (parentHmValueItem == null || (parentHmValueItem != null && !parentHmValueItem.getValue().equals(hmValueItem.getValue()))) { logger.debug("Value changed from '{}' to '{}' for binding {}", parentHmValueItem == null ? "null" : parentHmValueItem.getValue(), hmValueItem.getValue(), bindingConfig); return true; } return false; } /** * Iterate through all items and publishes the state. */ protected void publish(final HomematicBindingConfig bindingConfig, final HmValueItem hmValueItem) { new ProviderItemIterator().iterate(bindingConfig, new ProviderItemIteratorCallback() { @Override public void next(HomematicBindingConfig providerBindingConfig, Item item, Converter<?> converter) { State state = converter.convertFromBinding(hmValueItem); context.getEventPublisher().postUpdate(item.getName(), state); } }); } }