/** * 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.gpio.internal; import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.collections.BidiMap; import org.apache.commons.collections.bidimap.DualHashBidiMap; import org.openhab.binding.gpio.GPIOBindingProvider; import org.openhab.core.binding.AbstractBinding; import org.openhab.core.binding.BindingProvider; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.openhab.io.gpio.GPIO; import org.openhab.io.gpio.GPIOPin; import org.openhab.io.gpio.GPIOPinEventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Binds items with GPIO pins. Manages live for backend GPIO pin * objects and translates GPIO events to item events. * * @author Dancho Penev * @since 1.5.0 */ public class GPIOBinding extends AbstractBinding<GPIOBindingProvider>implements GPIOPinEventHandler { private static final Logger logger = LoggerFactory.getLogger(GPIOBinding.class); private static final long REGISTRYLOCK_TIMEOUT = 10; private static final TimeUnit REGISTRYLOCK_TIMEOUT_UNITS = TimeUnit.SECONDS; /** Read/write lock protecting item/pin object registry. */ private final ReentrantReadWriteLock registryLock = new ReentrantReadWriteLock(); /** * Bidirectional map for storing registered items and their * corresponding pin objects. */ private final BidiMap registry = new DualHashBidiMap(); /** GPIO IO service. */ private volatile GPIO gpio = null; @Override public void allBindingsChanged(BindingProvider provider) { if (gpio != null) { for (String itemName : provider.getItemNames()) { changeItem((GPIOBindingProvider) provider, itemName); } } super.allBindingsChanged(provider); } @Override public void bindingChanged(BindingProvider provider, String itemName) { if (gpio != null) { changeItem((GPIOBindingProvider) provider, itemName); } super.bindingChanged(provider, itemName); } protected void addBindingProvider(GPIOBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(GPIOBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } @Override protected void internalReceiveCommand(String itemName, Command command) { if (gpio != null) { try { if (registryLock.readLock().tryLock(REGISTRYLOCK_TIMEOUT, REGISTRYLOCK_TIMEOUT_UNITS)) { try { GPIOPin gpioPin = (GPIOPin) registry.get(itemName); if (gpioPin != null) { if (command == OnOffType.ON) { gpioPin.setValue(GPIOPin.VALUE_HIGH); logger.debug("Send command 1 to GPIO {}", gpioPin.getPinNumber()); } else { gpioPin.setValue(GPIOPin.VALUE_LOW); logger.debug("Send command 0 to GPIO {}", gpioPin.getPinNumber()); } } } catch (IOException e) { logger.error("Error occured while changing pin state for item " + itemName + ", exception: " + e.getMessage()); return; } finally { registryLock.readLock().unlock(); } } else { logger.error("Pin state for item " + itemName + " hasn't been changed, timeout expired while waiting for registry lock"); return; } } catch (InterruptedException e) { logger.error("Pin state for item " + itemName + " hasn't been changed, thread was interrupted while waiting for registry lock"); return; } } super.internalReceiveCommand(itemName, command); } /** * Called when GPIO OSGi service is available, creates and configure * backend objects for configured items. * * @param gpio GPIO OSGi service */ public void bindGPIO(GPIO gpio) { this.gpio = gpio; for (GPIOBindingProvider provider : providers) { for (String itemName : provider.getItemNames()) { if (provider.isItemConfigured(itemName)) { newItem(provider, itemName); } } } } /** * Called when GPIO OSGi service is stopped, deletes previously * created backend objects for configured items. * * @param gpio GPIO OSGi service */ public void unbindGPIO(GPIO gpio) { for (GPIOBindingProvider provider : providers) { for (String itemName : provider.getItemNames()) { deleteItem(itemName); } } this.gpio = null; } public void onEvent(GPIOPin pin, int value) { try { if (registryLock.readLock().tryLock(REGISTRYLOCK_TIMEOUT, REGISTRYLOCK_TIMEOUT_UNITS)) { String itemName = ""; try { itemName = (String) registry.getKey(pin); /* The item may be deleted while waiting to acquire read lock */ if (itemName != null) { if (value == GPIOPin.VALUE_HIGH) { eventPublisher.postUpdate(itemName, OpenClosedType.OPEN); logger.debug("Received command 1 from GPIO {}", pin.getPinNumber()); } else { eventPublisher.postUpdate(itemName, OpenClosedType.CLOSED); logger.debug("Received command 0 from GPIO {}", pin.getPinNumber()); } } } catch (IOException e) { logger.error("Error occured while receiving pin state for item " + itemName + ", exception: " + e.getMessage()); return; } finally { registryLock.readLock().unlock(); } } else { logger.error("Item state hasn't been changed, timeout expired while waiting for registry lock"); } } catch (InterruptedException e) { logger.error("Item state hasn't been changed, thread was interrupted while waiting for registry lock"); } } public void onError(GPIOPin pin, Exception exception) { String itemName = null; logger.error("Error occured in pin event processing, exception: " + exception.getMessage()); try { if (registryLock.readLock().tryLock(REGISTRYLOCK_TIMEOUT, REGISTRYLOCK_TIMEOUT_UNITS)) { try { itemName = (String) registry.getKey(pin); } finally { registryLock.readLock().unlock(); } } else { logger.error("Item name can't be determined, timeout expired while waiting for registry lock"); } } catch (InterruptedException e) { logger.error("Item name can't be determined, thread was interrupted while waiting for registry lock"); } /* The item may be deleted while waiting to acquire read lock */ if (itemName != null) { deleteItem(itemName); } } /** * Changes backend GPIO pin object for the item. * * @param provider binding provider * @param itemName the name of the item which to process */ private void changeItem(GPIOBindingProvider provider, String itemName) { if (provider.isItemConfigured(itemName)) { /* The item configuration was changed */ try { if (registryLock.writeLock().tryLock(REGISTRYLOCK_TIMEOUT, REGISTRYLOCK_TIMEOUT_UNITS)) { try { GPIOPin gpioPin = (GPIOPin) registry.get(itemName); /* Existing or new item */ if (gpioPin != null) { /* Pin number change requires deletion of old and creation of new backend object */ if (gpioPin.getPinNumber() != provider.getPinNumber(itemName)) { deleteItem(itemName); newItem(provider, itemName); } else { int newActiveLow = provider.getActiveLow(itemName); int currentDirection = gpioPin.getDirection(); int newDirection = provider.getDirection(itemName); if (newActiveLow != gpioPin.getActiveLow()) { gpioPin.setActiveLow(newActiveLow); } if (newDirection != currentDirection) { if (currentDirection == GPIOPin.DIRECTION_IN) { /* Tracking interrupts on output pins is meaningless */ gpioPin.removeEventHandler(this); } gpioPin.setDirection(newDirection); if (newDirection == GPIOPin.DIRECTION_IN) { gpioPin.setEdgeDetection(GPIOPin.EDGEDETECTION_BOTH); gpioPin.addEventHandler(this); } } /* Debouncing is valid only for input pins */ if (newDirection == GPIOPin.DIRECTION_IN) { long currentDebounceInterval = gpioPin.getDebounceInterval(); long defaultDebounceInterval = gpio.getDefaultDebounceInterval(); long newDebounceInterval = provider.getDebounceInterval(itemName); /* * If debounceInterval isn't configured its value is * GPIOBindingProvider.DEBOUNCEINTERVAL_UNDEFINED */ if (newDebounceInterval != GPIOBindingProvider.DEBOUNCEINTERVAL_UNDEFINED) { if (newDebounceInterval != currentDebounceInterval) { gpioPin.setDebounceInterval(newDebounceInterval); } } else { /* Revert back to default if was set before and after then deleted */ if (currentDebounceInterval != defaultDebounceInterval) { gpioPin.setDebounceInterval(defaultDebounceInterval); } } } } } else { newItem(provider, itemName); } } catch (Exception e) { logger.error("Error occured while changing backend object for item " + itemName + ", exception: " + e.getMessage()); } finally { registryLock.writeLock().unlock(); } } else { logger.error("Item " + itemName + " hasn't been changed, timeout expired while waiting for registry lock"); } } catch (InterruptedException e) { logger.error("Item " + itemName + " hasn't been changed, thread was interrupted while waiting for registry lock"); } } else { /* The item was deleted from configuration file or system is shut down */ deleteItem(itemName); } } /** * Setup new backend GPIO pin object and relate it with the item. * * @param provider binding provider * @param itemName the name of the item which to process */ private void newItem(GPIOBindingProvider provider, String itemName) { try { int direction; GPIOPin gpioPin = gpio.reservePin(provider.getPinNumber(itemName), provider.getPinForce(itemName)); gpioPin.setActiveLow(provider.getActiveLow(itemName)); direction = provider.getDirection(itemName); gpioPin.setDirection(direction); /* Edge detection and debouncing are valid only for input pins */ if (direction == GPIOPin.DIRECTION_IN) { long debounceInterval = provider.getDebounceInterval(itemName); gpioPin.setEdgeDetection(GPIOPin.EDGEDETECTION_BOTH); /* If debounceInterval isn't configured its value is GPIOBindingProvider.DEBOUNCEINTERVAL_UNDEFINED */ if (debounceInterval != GPIOBindingProvider.DEBOUNCEINTERVAL_UNDEFINED) { gpioPin.setDebounceInterval(debounceInterval); } } /* Register the pin */ try { if (registryLock.writeLock().tryLock(REGISTRYLOCK_TIMEOUT, REGISTRYLOCK_TIMEOUT_UNITS)) { try { registry.put(itemName, gpioPin); } finally { registryLock.writeLock().unlock(); } } else { logger.error("Item " + itemName + " hasn't been inserted into the registry, timeout expired while waiting for registry lock"); return; } } catch (InterruptedException e) { logger.error("Item " + itemName + " hasn't been inserted into the registry, thread was interrupted while waiting for registry lock"); return; } /* Set initial item state */ if (direction == GPIOPin.DIRECTION_IN) { /* Item type 'Contact' */ if (gpioPin.getValue() == GPIOPin.VALUE_HIGH) { eventPublisher.postUpdate(itemName, OpenClosedType.OPEN); } else { eventPublisher.postUpdate(itemName, OpenClosedType.CLOSED); } } else { /* Item type 'Switch' */ if (gpioPin.getValue() == GPIOPin.VALUE_HIGH) { eventPublisher.postUpdate(itemName, OnOffType.ON); } else { eventPublisher.postUpdate(itemName, OnOffType.OFF); } } /* The item is of type 'Contact', register for state change notifications */ if (direction == GPIOPin.DIRECTION_IN) { gpioPin.addEventHandler(this); } } catch (Exception e) { logger.error("Error occured while creating backend object for item " + itemName + ", exception: " + e.getMessage()); } } /** * Destroys backend GPIO pin object and clear relationship with the item. * * @param itemName the name of the item which to process */ private void deleteItem(String itemName) { try { if (registryLock.writeLock().tryLock(REGISTRYLOCK_TIMEOUT, REGISTRYLOCK_TIMEOUT_UNITS)) { try { /* Remove the item from registry */ GPIOPin gpioPin = (GPIOPin) registry.remove(itemName); /* Release the backend object */ if (gpioPin != null) { try { gpio.releasePin(gpioPin); } catch (Exception e) { logger.error("Error occured while deleting backend object for item " + itemName + ", exception: " + e.getMessage()); } } /* Change the item state to 'Undefined' */ eventPublisher.postUpdate(itemName, UnDefType.UNDEF); } finally { registryLock.writeLock().unlock(); } } else { logger.error( "Item " + itemName + " hasn't been deleted, timeout expired while waiting for registry lock"); } } catch (InterruptedException e) { logger.error("Item " + itemName + " hasn't been deleted, thread was interrupted while waiting for registry lock"); } } }