/**
* 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.k8055.internal;
import java.util.Dictionary;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.k8055.k8055BindingProvider;
import org.openhab.binding.k8055.internal.k8055GenericBindingProvider.k8055BindingConfig;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jna.Library;
import com.sun.jna.Native;
/**
* K8055 Binding that polls the USB Hardware
*
* @author Anthony Green
* @since 1.5.0
*/
public class k8055Binding extends AbstractActiveBinding<k8055BindingProvider>implements ManagedService {
private static final Logger logger = LoggerFactory.getLogger(k8055Binding.class);
/**
* the refresh interval which is used to poll values from the k8055 server
* (optional, defaults to 1000ms)
*/
private long refreshInterval = 1000;
static LibK8055 sysLibrary;
int boardNo = 0;
/**
* Previously read digital input state
*/
long lastDigitalInputs = -1;
/**
* Are we currently connected to the hardware?
*/
boolean connected;
public k8055Binding() {
}
protected boolean connect() {
try {
if (sysLibrary == null) {
logger.debug("Loading native code library...");
sysLibrary = (LibK8055) Native
.synchronizedLibrary((Library) Native.loadLibrary("k8055", LibK8055.class));
logger.debug("Done loading native code library");
}
if (!connected) {
if (sysLibrary.OpenDevice(boardNo) == boardNo) {
connected = true;
// We don't really know the existing state - so this results
// in the state of all inputs being republished
lastDigitalInputs = -1;
logger.info("K8055: Connect to board: " + boardNo + " succeeeded.");
} else {
logger.error("K8055: Connect to board: " + boardNo + " failed.");
}
}
;
} catch (Exception e) {
logger.error(
"Failed to load K8055 native library. Please check the libk8055 and jna native libraries are in the Java library path. ",
e);
}
return connected;
}
@Override
public void activate() {
logger.debug("activate() method is called!");
connected = false;
logger.debug("activate() method completed!");
}
@Override
public void deactivate() {
logger.debug("deactivate() method is called!");
if (sysLibrary != null) {
try {
sysLibrary.CloseDevice();
} catch (Exception e) {
logger.warn("Failed to close connection to hardware", e);
}
}
sysLibrary = null;
}
/**
* @{inheritDoc
*/
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
/**
* @{inheritDoc
*/
@Override
protected String getName() {
return "k8055 Refresh Service";
}
/**
* @{inheritDoc
*/
@Override
protected void execute() {
logger.debug("execute() method is called!");
try {
if (!connect()) {
logger.error("Not connected to hardware. Skipping attempt to read inputs.");
return;
}
// Read all of the digital inputs in one go
long inputs = sysLibrary.ReadAllDigital();
if (inputs < 0) {
throw new Exception("Failed to read digital inputs from hardware");
}
for (k8055BindingProvider provider : this.providers) {
for (String itemName : provider.getItemNames()) {
k8055BindingConfig config = provider.getItemConfig(itemName);
if (IOType.DIGITAL_IN.equals(config.ioType)) {
boolean newstate = (inputs & (1 << (config.ioNumber - 1))) != 0;
boolean oldstate = (lastDigitalInputs & (1 << (config.ioNumber - 1))) != 0;
// For quick response times a very short poll interval
// is required. To avoid flooding
// masses of events to the event bus, just post updates
// when changes occur (or when first connecting to
// hardware)
if (lastDigitalInputs < 0 || newstate != oldstate) {
if (newstate) {
eventPublisher.postUpdate(itemName, OpenClosedType.CLOSED);
} else {
eventPublisher.postUpdate(itemName, OpenClosedType.OPEN);
}
}
} else if (IOType.ANALOG_IN.equals(config.ioType)) {
int state = sysLibrary.ReadAnalogChannel(config.ioNumber);
if (inputs < 0) {
throw new Exception("Failed to read analog channel " + config.ioNumber + " from hardware");
}
eventPublisher.postUpdate(itemName, new DecimalType(state));
}
}
}
lastDigitalInputs = inputs;
} catch (Exception e) {
// Connection failure
logger.error("Failed to read from hardware ", e);
connected = false;
sysLibrary.CloseDevice();
}
}
/**
* @{inheritDoc
*/
@Override
protected void internalReceiveCommand(String itemName, Command command) {
logger.debug("Received command for: " + itemName + " - " + command);
k8055BindingConfig itemConfig = getConfigForItemName(itemName);
try {
if (itemConfig.ioType == IOType.DIGITAL_OUT) {
logger.debug("Updating hardware Digital output: " + itemConfig.ioNumber);
if (connect()) {
if (OnOffType.ON.equals(command)) {
if (sysLibrary.SetDigitalChannel(itemConfig.ioNumber) < 0) {
throw new Exception("Failed to set digital channel: " + itemConfig.ioNumber);
}
} else if (OnOffType.OFF.equals(command)) {
if (sysLibrary.ClearDigitalChannel(itemConfig.ioNumber) < 0) {
throw new Exception("Failed to set digital channel: " + itemConfig.ioNumber);
}
;
} else {
logger.error("Received unknown command: " + command + " for item: " + itemName);
}
} else {
logger.error(
"Not connected to hardware. Command: " + command + " for item: " + itemName + " ignored");
}
} else if (itemConfig.ioType == IOType.ANALOG_OUT) {
logger.debug("Updating hardware Analog output: " + itemConfig.ioNumber);
// TODO: Implement Increase/decrease commands
if (connect()) {
if (OnOffType.ON.equals(command)) {
if (sysLibrary.SetAnalogChannel(itemConfig.ioNumber) < 0) {
throw new Exception("Failed to set analog channel: " + itemConfig.ioNumber);
}
;
} else if (OnOffType.OFF.equals(command)) {
if (sysLibrary.ClearAnalogChannel(itemConfig.ioNumber) < 0) {
throw new Exception("Failed to clear analog channel: " + itemConfig.ioNumber);
}
;
} else if (command instanceof PercentType) {
// Convert 0-100% to 0-255
int value = Math.round((((PercentType) command).shortValue() * 255) / 100);
if (sysLibrary.OutputAnalogChannel(itemConfig.ioNumber, value) < 0) {
throw new Exception("Failed to output analog channel: " + itemConfig.ioNumber);
}
;
} else if (command instanceof DecimalType) {
// Force number into 0-255 range.
if (sysLibrary.OutputAnalogChannel(itemConfig.ioNumber,
Math.min(Math.max(((DecimalType) command).intValue(), 0), 255)) < 0) {
throw new Exception("Failed to output analog channel: " + itemConfig.ioNumber);
}
;
} else {
logger.error("Received unknown command: " + command + " for item: " + itemName);
}
} else {
logger.error(
"Not connected to hardware. Command: " + command + " for item: " + itemName + " ignored");
}
}
} catch (Exception e) {
// Connection failure
logger.error("Failed to read from hardware ", e);
connected = false;
sysLibrary.CloseDevice();
}
// 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!");
}
/**
* @{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!");
}
protected void addBindingProvider(k8055BindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(k8055BindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
logger.debug("updated() called");
setProperlyConfigured(false);
if (config != null) {
// to override the default refresh interval one has to add a
// parameter to openhab.cfg like
// <bindingName>:refresh=<intervalInMs>
String refreshIntervalString = (String) config.get("refresh");
if (StringUtils.isNotBlank(refreshIntervalString)) {
refreshInterval = Long.parseLong(refreshIntervalString);
}
String boardNum = (String) config.get("boardno");
if (StringUtils.isNotBlank(boardNum)) {
try {
boardNo = Integer.parseInt(boardNum);
setProperlyConfigured(true);
} catch (NumberFormatException e) {
logger.error("Invalid board number: " + boardNum);
throw new ConfigurationException("boardno", boardNum + " is not a valid board number.");
}
}
} else {
logger.info("No config supplied - using default values");
}
if (isProperlyConfigured()) {
// Connect to hardware
connect();
}
}
/**
* Lookup of the configuration of the named item.
*
* @param itemName
* The name of the item.
* @return The configuration, null otherwise.
*/
private k8055BindingConfig getConfigForItemName(String itemName) {
for (k8055BindingProvider provider : this.providers) {
if (provider.getItemConfig(itemName) != null) {
return provider.getItemConfig(itemName);
}
}
return null;
}
/**
* Describes the interface of the K8055 C Library. (Used by JNA)
*
* @author anthony
*
*/
public interface LibK8055 extends Library {
/* prototypes */
int OpenDevice(int board_address);
int CloseDevice();
int ReadAnalogChannel(int Channelno);
int ReadAllAnalog(int[] data1, int[] data2);
int OutputAnalogChannel(int channel, int data);
int OutputAllAnalog(int data1, int data2);
int ClearAllAnalog();
int ClearAnalogChannel(int channel);
int SetAnalogChannel(int channel);
int SetAllAnalog();
int WriteAllDigital(int data);
int ClearDigitalChannel(int channel);
int ClearAllDigital();
int SetDigitalChannel(int channel);
int SetAllDigital();
int ReadDigitalChannel(int channel);
long ReadAllDigital();
int ResetCounter(int counternr);
long ReadCounter(int counterno);
int SetCounterDebounceTime(int counterno, int debouncetime);
}
}