/**
* 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.ekey.internal;
import java.util.Dictionary;
import java.util.Objects;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.ekey.EKeyBindingProvider;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import at.fhooe.mc.schlgtwt.parser.UniformPacket;
/**
* This binding will start a thread that listens to incoming UDP traffic to
* receive packet from the eKey-UDP-Converter. It is an unidirectional
* conversation between the converter and the binding. No data is sent. This
* binding supports the following protocol-modes:
* <ul>
* <li><b>RARE</b> This is set by default on the ekey-UDP-converter.<br>
* It provides the following information:
* <ul>
* <li><b>Action</b> - open or deny</li>
* <li><b>TerminalID</b> - final 8 digits of serial</li>
* <li><b>RelayID</b> - ID of Relay that switched</li>
* <li><b>UserID</b> - ID of User according to Controller</li>
* <li><b>FingerID</b> - which finger was used</li>
* </ul>
* </li>
* <li><b>HOME</b> This provides about the same information as the RARE type. <br>
* But in contrast this is sent as a String message over UDP and not as byte message
* <ul>
* <li><b>Action</b> - open/deny or digital input</li>
* <li><b>TerminalID</b> - full serial of the terminal used</li>
* <li><b>RelayID</b> - ID of Relay that switched</li>
* <li><b>UserID</b> - ID of User according to controller</li>
* <li><b>FingerID</b> - which finger was used</li>
* </ul>
* </li>
* <li><b>MULTI</b> This is the most powerful mode. It provides the most information.<br>
* But it is only available on eKey-Multi devices
* <ul>
* <li><b>Action</b> - open/deny (various forms of denial)</li>
* <li><b>TerminalID</b> - full serial of the terminal used</li>
* <li><b>TerminalName</b> - short name for the terminal according to the controller</li>
* <li><b>KeyID</b> - ID of the Key that was specified for that finger on the controller</li>
* <li><b>UserID</b> - ID of User according to Controller</li>
* <li><b>UserName</b> - short name for the user according to the controller</li>
* <li><b>UserStatus</b> - Status of the user (active/inactive)</li>
* <li><b>FingerID</b> - which finger was used</li>
* <li><b>InputID</b> - ID of the digital input that was triggered</li>
* <li>but no RelayID</li>
* </ul>
* </li>
* </ul>
* You can decide between those modes depending on your hardware. Use the software that is delivered with the
* UDP-converter to change the mode.
*
* @author Paul Schlagitweit
* @since 1.5.0
*/
public class EKeyBinding extends AbstractBinding<EKeyBindingProvider> implements ManagedService, IEKeyListener {
private static final Logger logger = LoggerFactory.getLogger(EKeyBinding.class);
private static final String[] MODES = { "RARE", "HOME", "MULTI" };
// pattern for checking ip addresses
private static final String IP_PATTERN = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
// members
private int port = 0;
private String ip = null;
private String deliminator = null;
private int mode = UniformPacket.tRARE;
private EKeyPacketReceiver packetlistener;
public EKeyBinding() {
packetlistener = new EKeyPacketReceiver(this);
}
@Override
public void activate() {
}
@Override
public void deactivate() {
logger.debug("Stopping eKey listener...");
if (packetlistener != null) {
packetlistener.stopListener();
}
}
protected void addBindingProvider(EKeyBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(EKeyBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
if (config != null) {
// get ip from config file
ip = Objects.toString(config.get("ip"), null);
if (!Pattern.compile(IP_PATTERN).matcher(ip).matches()) { // check valid ip
logger.warn(
"ip configuration parameter not specified or incorrect format. It is recommended to specify a static ip.");
ip = null;
}
// get port from configuration
String portstring = Objects.toString(config.get("port"), null);
if (StringUtils.isNotBlank(portstring)) {
port = Integer.parseInt(portstring);
} else {
throw new ConfigurationException("port", "No port specified."
+ " Please specify a port as you did during the UDP-Converter configuration.");
}
// get mode from configuration
String modestring = Objects.toString(config.get("mode"), null);
if (StringUtils.isNotBlank(modestring)) {
modestring = modestring.toUpperCase().trim();
if (modestring.equals(MODES[0])) {
mode = UniformPacket.tRARE;
} else if (modestring.equals(MODES[1])) {
mode = UniformPacket.tHOME;
} else if (modestring.equals(MODES[2])) {
mode = UniformPacket.tMULTI;
} else {
throw new ConfigurationException("mode",
"Unknown mode '" + modestring + "'. Valid values are RARE, MULTI or HOME.");
}
} else { // no mode specified -> use default
logger.warn("mode was not declared in the configuration, so mode RARE is used as default.");
mode = UniformPacket.tRARE;
}
// get delimiter from the configuration
String delstring = Objects.toString(config.get("delimiter"), null);
if (StringUtils.isBlank(delstring)) {
// a blank (" ") will be defined as default delimiter
deliminator = " ";
} else {
deliminator = delstring;
}
// make sure that there is no listener running
packetlistener.stopListener();
// send the parsed information to the listener
packetlistener.initializeReceiver(mode, ip, port, deliminator);
// start the listener
new Thread(packetlistener).start();
}
}
@Override
public void publishUpdate(UniformPacket ekeyRecord) {
for (EKeyBindingProvider provider : providers) {
for (String itemName : provider.getItemNames()) {
// get the interesting value from the parsed ekey packet
Object value = EKeyBindingConfig.getValueOfInterest(ekeyRecord, provider.getItemInterest(itemName));
if (value == null) {
// is wanted
logger.error("The interest '" + provider.getItemInterest(itemName).toString()
+ "' that you specified for the item '" + itemName + "' is not provided by the packet mode "
+ "which is defined in the openhab.cfg.\nPlease change the mode on your eKey controller"
+ "and your config file or avoid using this type of interest");
}
// check what type of item wants to know the value
Class<? extends Item> itemType = provider.getItemType(itemName);
if (itemType.isAssignableFrom(NumberItem.class)) { // NumberItem
if (value instanceof Integer) {
eventPublisher.postUpdate(itemName, DecimalType.valueOf(value.toString()));
} else {
logger.error("Your NumberItem cannot take values of type String!");
}
} else if (itemType.isAssignableFrom(StringItem.class)) {
// allow both - return of number or integer
if (value != null && (value instanceof String || value instanceof Integer)) {
eventPublisher.postUpdate(itemName, StringType.valueOf(value.toString()));
}
}
}
}
if (ekeyRecord != null) {
logger.debug("new packet arrived {} ", ekeyRecord);
}
}
}