/** * 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.mcp23017.internal; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.openhab.binding.mcp23017.MCP23017BindingProvider; import org.openhab.core.binding.AbstractActiveBinding; 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.State; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.pi4j.gpio.extension.mcp.MCP23017GpioProvider; import com.pi4j.io.gpio.GpioController; import com.pi4j.io.gpio.GpioFactory; import com.pi4j.io.gpio.GpioPin; import com.pi4j.io.gpio.GpioPinDigitalInput; import com.pi4j.io.gpio.GpioPinDigitalOutput; import com.pi4j.io.gpio.Pin; import com.pi4j.io.gpio.PinMode; import com.pi4j.io.gpio.PinPullResistance; import com.pi4j.io.gpio.PinState; import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent; import com.pi4j.io.gpio.event.GpioPinListenerDigital; import com.pi4j.io.i2c.I2CBus; import com.pi4j.io.i2c.I2CFactory.UnsupportedBusNumberException; import com.pi4j.wiringpi.GpioUtil; /** * Implement this class if you are going create an actively polling service like * querying a Website/Device. * * @author Diego A. Fliess * @author Alexander Falkenstern * @since 1.9.0 */ public class MCP23017Binding extends AbstractActiveBinding<MCP23017BindingProvider> implements GpioPinListenerDigital { private static final Logger logger = LoggerFactory.getLogger(MCP23017Binding.class); private final GpioController gpio; private Map<String, GpioPin> gpioPins = new HashMap<String, GpioPin>(); private static final Map<Integer, MCP23017GpioProvider> mcpProviders = new HashMap<Integer, MCP23017GpioProvider>(); /** * The BundleContext. This is only valid when the bundle is ACTIVE. It is * set in the activate() method and must not be accessed anymore once the * deactivate() method was called or before activate() was called. */ @SuppressWarnings("unused") private BundleContext bundleContext; /** * the refresh interval which is used to poll values from the mcp23017 server * (optional, defaults to 60000ms) */ private long refreshInterval = 60000; /** * the polling interval mcp23071 check interrupt register (optional, defaults to 50ms) */ private int pollingInterval = 50; public MCP23017Binding() { // ask for non privileged access (run without root) GpioUtil.enableNonPrivilegedAccess(); // now create a controller gpio = GpioFactory.getInstance(); } /** * 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) { this.bundleContext = bundleContext; // to override the default refresh interval one has to add a // parameter to openhab.cfg like <bindingName>:refresh=<intervalInMs> String refreshIntervalString = (String) configuration.get("refresh"); if (StringUtils.isNotBlank(refreshIntervalString)) { refreshInterval = Long.parseLong(refreshIntervalString); } // to override the default polling interval one has to add a // parameter to openhab.cfg like <bindingName>:polling=<intervalInMs> String pollingIntervalString = (String) configuration.get("polling"); if (StringUtils.isNotBlank(pollingIntervalString)) { pollingInterval = Integer.parseInt(pollingIntervalString); } setProperlyConfigured(true); logger.debug("mcp23017 activated and properly configured {}", this.hashCode()); } /** * 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 logger.debug("mcp23017 modified"); } /** * 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) { this.bundleContext = null; // deallocate resources here that are no longer needed and // should be reset when activating this binding again logger.debug("mcp23017 deactivated"); mcpProviders.clear(); gpio.shutdown(); } /** * @{inheritDoc} */ @Override protected long getRefreshInterval() { return refreshInterval; } /** * @{inheritDoc} */ @Override protected String getName() { return "mcp23017 Refresh Service"; } /** * @{inheritDoc} */ @Override protected void execute() { } @Override public void addBindingProvider(BindingProvider provider) { super.addBindingProvider(provider); /* first call contains all, better use activate ? if you have providers */ logger.debug("addBindingProvider: {}", Arrays.toString(provider.getItemNames().toArray())); for (String itemName : provider.getItemNames()) { bindGpioPin((MCP23017BindingProvider) provider, itemName); } } @Override public void removeBindingProvider(BindingProvider provider) { super.removeBindingProvider(provider); /* shutdown call contains all better use deactivate */ logger.debug("removeBindingProvider: {}", Arrays.toString(provider.getItemNames().toArray())); for (String itemName : provider.getItemNames()) { unBindGpioPin((MCP23017BindingProvider) provider, itemName); } } @Override public void bindingChanged(BindingProvider provider, String itemName) { if (provider instanceof MCP23017BindingProvider) { String allItemNames = Arrays.toString(provider.getItemNames().toArray()); if (provider.getItemNames().contains(itemName)) { bindGpioPin((MCP23017BindingProvider) provider, itemName); logger.debug("bindingChanged item bound {} - {}", itemName, allItemNames); } else { unBindGpioPin((MCP23017BindingProvider) provider, itemName); logger.debug("bindingChanged item unbound {} - {}", itemName, allItemNames); } } super.bindingChanged(provider, itemName); } private void bindGpioPin(MCP23017BindingProvider provider, String itemName) { try { int address = provider.getBusAddress(itemName); MCP23017GpioProvider mcp = mcpProviders.get(address); if (mcp == null) { try { mcp = new MCP23017GpioProvider(I2CBus.BUS_1, address); mcp.setPollingTime(pollingInterval); } catch (UnsupportedBusNumberException ex) { throw new IllegalArgumentException("Tried to access not available I2C bus"); } mcpProviders.put(address, mcp); } Pin pin = provider.getPin(itemName); PinMode mode = provider.getPinMode(itemName); if (mode.equals(PinMode.DIGITAL_OUTPUT)) { GpioPinDigitalOutput output = gpio.provisionDigitalOutputPin(mcp, pin, itemName, provider.getDefaultState(itemName)); gpioPins.put(itemName, output); logger.debug("Provisioned digital output for {}", itemName); } else if (mode.equals(PinMode.DIGITAL_INPUT)) { GpioPinDigitalInput input = gpio.provisionDigitalInputPin(mcp, pin, itemName, PinPullResistance.OFF); input.setDebounce(20); input.addListener(this); gpioPins.put(itemName, input); logger.debug("Provisioned digital input for {}", itemName); } else { throw new IllegalArgumentException("Invalid Pin Mode in config " + mode.name()); } } catch (IOException exception) { logger.error("I/O error {}", exception.getMessage()); } } private void unBindGpioPin(MCP23017BindingProvider provider, String itemName) { GpioPin pin = gpioPins.remove(itemName); gpio.unprovisionPin(pin); logger.debug("Unbound item {}", itemName); } /** * @{inheritDoc} */ @Override protected void internalReceiveCommand(String itemName, Command command) { // the code being executed when a command was sent on the openHAB // event bus goes here. This method is only called if one of the // BindingProviders provide a binding for the given 'itemName'. logger.debug("internalReceiveCommand({},{}) is called!", itemName, command); // the configuration is guaranteed not to be null, because the component // definition has the configuration-policy set to require. If set to // 'optional' then the configuration may be null if (command instanceof OnOffType) { final OnOffType switchCommand = (OnOffType) command; for (MCP23017BindingProvider provider : this.providers) { if (provider.providesBindingFor(itemName)) { GpioPin pin = gpioPins.get(itemName); if (switchCommand == OnOffType.OFF) { gpio.setState(PinState.LOW, (GpioPinDigitalOutput) pin); } else if (switchCommand == OnOffType.ON) { gpio.setState(PinState.HIGH, (GpioPinDigitalOutput) pin); } } } } } /** * @{inheritDoc} */ @Override public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) { GpioPin pin = event.getPin(); OpenClosedType state = OpenClosedType.CLOSED; // Assume we are high... if (event.getState() == PinState.LOW) { // To err is human... state = OpenClosedType.OPEN; } this.eventPublisher.postUpdate(pin.getName(), state); logger.debug("GPIO pin state change: {} = {}", pin, state); } /** * @{inheritDoc} */ @Override protected void internalReceiveUpdate(String itemName, State newState) { // the code being executed when a state was sent on the openHAB // event bus goes here. This method is only called if one of the // BindingProviders provide a binding for the given 'itemName'. logger.debug("internalReceiveUpdate({},{}) is called", itemName, newState); } }