/**
* Copyright (c) 2010-2016, openHAB.org and others.
*
* 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.mcp3424.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.mcp3424.MCP3424BindingProvider;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.library.items.DimmerItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PercentType;
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.MCP3424GpioProvider;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPin;
import com.pi4j.io.gpio.GpioPinAnalogInput;
import com.pi4j.io.gpio.Pin;
import com.pi4j.io.gpio.event.GpioPinAnalogValueChangeEvent;
import com.pi4j.io.gpio.event.GpioPinListenerAnalog;
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 Alexander Falkenstern
* @since 1.9.0
*/
public class MCP3424Binding extends AbstractActiveBinding<MCP3424BindingProvider> implements GpioPinListenerAnalog {
private static final Logger logger = LoggerFactory.getLogger(MCP3424Binding.class);
private final GpioController gpio;
private Map<String, GpioPin> gpioPins = new HashMap<String, GpioPin>();
private static final Map<Integer, MCP3424GpioProvider> mcpProviders = new HashMap<Integer, MCP3424GpioProvider>();
/**
* 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 mcp3424 server
* (optional, defaults to 60000ms)
*/
private long refreshInterval = 60000;
/**
* the polling interval mcp3424 check interrupt register (optional, defaults to 50ms)
*/
private int pollingInterval = 50;
public MCP3424Binding() {
// 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);
}
// read further config parameters here ...
setProperlyConfigured(true);
logger.debug("mcp3424 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("mcp3424 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("mcp3424 deactivated");
mcpProviders.clear();
gpio.shutdown();
}
/**
* @{inheritDoc}
*/
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
/**
* @{inheritDoc}
*/
@Override
protected String getName() {
return "mcp3424 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((MCP3424BindingProvider) 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((MCP3424BindingProvider) provider, itemName);
}
}
@Override
public void bindingChanged(BindingProvider provider, String itemName) {
if (provider instanceof MCP3424BindingProvider) {
String allItemNames = Arrays.toString(provider.getItemNames().toArray());
if (provider.getItemNames().contains(itemName)) {
bindGpioPin((MCP3424BindingProvider) provider, itemName);
logger.debug("bindingChanged item bound {} - {}", itemName, allItemNames);
} else {
unBindGpioPin((MCP3424BindingProvider) provider, itemName);
logger.debug("bindingChanged item unbound {} - {}", itemName, allItemNames);
}
}
super.bindingChanged(provider, itemName);
}
private void bindGpioPin(MCP3424BindingProvider provider, String itemName) {
try {
int address = provider.getBusAddress(itemName);
MCP3424GpioProvider mcp = mcpProviders.get(address);
if (mcp == null) {
try {
int gain = provider.getGain(itemName);
int resolution = provider.getResolution(itemName);
mcp = new MCP3424GpioProvider(I2CBus.BUS_1, address, resolution, gain);
mcp.setMonitorInterval(pollingInterval);
} catch (UnsupportedBusNumberException ex) {
throw new IllegalArgumentException("Tried to access not available I2C bus");
}
mcpProviders.put(address, mcp);
}
mcp.setMonitorEnabled(false);
Pin pin = provider.getPin(itemName);
GpioPinAnalogInput input = gpio.provisionAnalogInputPin(mcp, pin, itemName);
input.addListener(this);
input.setTag(provider.getItem(itemName));
gpioPins.put(itemName, input);
mcp.setEventThreshold(0, input);
mcp.setMonitorEnabled(true);
logger.debug("Provisioned analog input for {}", itemName);
} catch (IOException exception) {
logger.error("I/O error {}", exception.getMessage());
}
}
private void unBindGpioPin(MCP3424BindingProvider 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) {
super.internalReceiveCommand(itemName, 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);
}
/**
* Function will be called by Pi4J, if new conversion result is available.
* On this time we can notify openhab, that something was changed.
**/
@Override
public void handleGpioPinAnalogValueChangeEvent(GpioPinAnalogValueChangeEvent event) {
GpioPin pin = event.getPin();
MCP3424GpioProvider provider = (MCP3424GpioProvider) pin.getProvider();
if (pin.getTag() instanceof NumberItem) {
double value = provider.getAnalogValue(pin.getPin());
try {
this.eventPublisher.postUpdate(pin.getName(), new DecimalType(value));
} catch (NumberFormatException exception) {
logger.warn("Unable to convert '{}' for item {} to number", String.valueOf(value), pin.getName());
}
logger.debug("GPIO channel change: {} = {}", pin, value);
} else if (pin.getTag() instanceof DimmerItem) {
double value = provider.getPercentValue(pin.getPin());
try {
this.eventPublisher.postUpdate(pin.getName(), new PercentType((int) value));
} catch (NumberFormatException e) {
logger.warn("Unable to convert '{}' for item {} to number", String.valueOf(value), pin.getName());
}
logger.debug("GPIO channel change: {} = {}", pin, value);
}
}
/**
* @{inheritDoc}
*/
@Override
protected void internalReceiveUpdate(String itemName, State newState) {
super.internalReceiveUpdate(itemName, 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);
}
}