/** * 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.expire.internal; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.openhab.binding.expire.ExpireBindingProvider; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This binding monitors state changes and sets the state to "Undefined" (or any other configured expired state) * if not state change occurs within the configured time * * @author Michael Wyraz * @author John Cocula - minor refactoring * @since 1.9.0 */ public class ExpireBinding extends AbstractActiveBinding<ExpireBindingProvider> { private static final Logger logger = LoggerFactory.getLogger(ExpireBinding.class); /** * The refresh interval is used to check if any of the bound items is expired. * One second should be fine for all use cases, so it's final and cannot be configured. */ private static final long refreshInterval = 1000; /** * Mapping of item names to future timestamps (in milliseconds) to expire them. */ private Map<String, Long> itemExpireMap = new ConcurrentHashMap<String, Long>(); public ExpireBinding() { } protected void addBindingProvider(ExpireBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(ExpireBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } /** * Called by the SCR to activate the component with its configuration read from CAS * * @param bundleContext BundleContext of the Bundle that defines this component * @param configuration Configuration properties for this component obtained from the ConfigAdmin service */ public void activate(final BundleContext bundleContext, final Map<String, Object> configuration) { setProperlyConfigured(true); } /** * Called by the SCR when the configuration of a binding has been changed through the ConfigAdmin service. * * @param configuration Updated configuration properties */ public void modified(final Map<String, Object> configuration) { // update the internal configuration accordingly } /** * Called by the SCR to deactivate the component when either the configuration is removed or * mandatory references are no longer satisfied or the component has simply been stopped. * * @param reason Reason code for the deactivation:<br> * <ul> * <li>0 – Unspecified * <li>1 – The component was disabled * <li>2 – A reference became unsatisfied * <li>3 – A configuration was changed * <li>4 – A configuration was deleted * <li>5 – The component was disposed * <li>6 – The bundle was stopped * </ul> */ public void deactivate(final int reason) { // deallocate resources here that are no longer needed and // should be reset when activating this binding again itemExpireMap.clear(); } /** * {@inheritDoc} */ @Override protected long getRefreshInterval() { return refreshInterval; } /** * {@inheritDoc} */ @Override protected String getName() { return "Expire Refresh Service"; } private boolean isReadyToExpire(String itemName) { Long nextExpireTs = itemExpireMap.get(itemName); return (nextExpireTs != null) && (nextExpireTs <= System.currentTimeMillis()); } private void expire(String itemName, ExpireBindingProvider provider) { itemExpireMap.remove(itemName); // disable expire trigger until next update or command Command expireCommand = provider.getExpireCommand(itemName); State expireState = provider.getExpireState(itemName); if (expireCommand != null) { logger.debug("Item {} received no command or update for {} - posting command '{}'", itemName, provider.getDurationString(itemName), expireCommand); eventPublisher.postCommand(itemName, expireCommand); } else if (expireState != null) { logger.debug("Item {} received no command or update for {} - posting state '{}'", itemName, provider.getDurationString(itemName), expireState); eventPublisher.postUpdate(itemName, expireState); } } /** * {@inheritDoc} */ @Override protected void execute() { logger.trace("Executing..."); // only bother to iterate if there is any possible work to do if (!itemExpireMap.isEmpty()) { for (ExpireBindingProvider provider : providers) { for (String itemName : provider.getItemNames()) { if (isReadyToExpire(itemName)) { expire(itemName, provider); } } } } } /** * {@inheritDoc} */ @Override protected void internalReceiveCommand(final String itemName, final Command newCommand) { logger.trace("Received command '{}' for item {}", newCommand, itemName); for (ExpireBindingProvider provider : providers) { if (provider.providesBindingFor(itemName)) { Command expireCommand = provider.getExpireCommand(itemName); State expireState = provider.getExpireState(itemName); if ((expireCommand != null && expireCommand.equals(newCommand)) || (expireState != null && expireState.equals(newCommand))) { // New command is expired command or state -> no further action needed itemExpireMap.remove(itemName); // remove expire trigger until next update or command logger.debug("Item {} received command '{}'; stopping any future expiration.", itemName, newCommand); } else { // New command is not the expired command or state, so add the trigger to the map long duration = provider.getDuration(itemName); itemExpireMap.put(itemName, System.currentTimeMillis() + duration); logger.debug("Item {} will expire (with '{}' {}) in {} ms", itemName, expireCommand == null ? expireState : expireCommand, expireCommand == null ? "state" : "command", duration); } break; } } } /** * {@inheritDoc} */ @Override protected void internalReceiveUpdate(final String itemName, final State newState) { logger.trace("Received update '{}' for item {}", newState, itemName); for (ExpireBindingProvider provider : providers) { if (provider.providesBindingFor(itemName)) { Command expireCommand = provider.getExpireCommand(itemName); State expireState = provider.getExpireState(itemName); if ((expireCommand != null && expireCommand.equals(newState)) || (expireState != null && expireState.equals(newState))) { // New state is expired command or state -> no further action needed itemExpireMap.remove(itemName); // remove expire trigger until next update or command logger.debug("Item {} received update '{}'; stopping any future expiration.", itemName, newState); } else { // New state is not the expired command or state, so add the trigger to the map long duration = provider.getDuration(itemName); itemExpireMap.put(itemName, System.currentTimeMillis() + duration); logger.debug("Item {} will expire (with '{}' {}) in {} ms", itemName, expireCommand == null ? expireState : expireCommand, expireCommand == null ? "state" : "command", duration); } break; } } } }