/** * 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.powerdoglocalapi.internal; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.openhab.binding.powerdoglocalapi.PowerDogLocalApiBindingProvider; import org.openhab.core.binding.BindingConfig; import org.openhab.core.items.Item; import org.openhab.core.library.items.ContactItem; import org.openhab.core.library.items.DimmerItem; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.items.StringItem; import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.StringType; import org.openhab.core.types.Command; import org.openhab.model.item.binding.AbstractGenericBindingProvider; import org.openhab.model.item.binding.BindingConfigParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p> * This class parses the EcoData PowerDog LocalAPI item binding data. It * registers as a {@link PowerDogLocalAPIGenericBindingProvider} service as * well. * </p> * * <p> * Here are some examples for valid binding configuration strings: * <ul> * <li> * <code>{ powerdoglocalapi="<serverId:arithmetic_1234567890:300000" }</code></li> * <li><code>{ powerdoglocalapi="<powerdog:pv_global_1234567890:300000" }</code> * </li> * <li> * <code>{ powerdoglocalapi="<powerdog:pv_global_1234567890:300000:Current_Value" }</code> * </li> * <li> * <code>{ powerdoglocalapi="<powerdog:impulsecounter_1234567890:300000:Unit_1000000" }</code> * </li> * <li><code>{ powerdoglocalapi=">powerdog:powerapi_1234567890:300000" }</code></li> * </ul> * * The 'serverId' referenced in the binding string is configured in the * openhab.cfg file -: powerdoglocalapi:serverId.host = powerdog * * 'serverId' can be any alphanumeric string as long as it is the same in the * binding and configuration file. <b>NOTE</b>: The parameter is case sensitive! * * @author wuellueb * @since 1.9.0 */ public class PowerDogLocalApiGenericBindingProvider extends AbstractGenericBindingProvider implements PowerDogLocalApiBindingProvider { static final Logger logger = LoggerFactory.getLogger(PowerDogLocalApiGenericBindingProvider.class); /** {@link Pattern} which matches a binding configuration part */ private static final Pattern BASE_CONFIG_PATTERN = Pattern.compile("(<|>)([0-9._a-zA-Z]+:[0-9._a-zA-Z:]+)"); /** {@link Pattern} which matches an In-Binding */ private static final Pattern INNER_BINDING_PATTERN = Pattern .compile("([0-9._a-zA-Z]+):([0-9._a-zA-Z]+):([0-9]+)(:?[0-9._a-zA-Z]*)"); /** * Artificial command for the PowerDog configuration */ protected static final Command IN_BINDING_KEY = StringType.valueOf("IN_BINDING"); protected static final Command OUT_BINDING_KEY = StringType.valueOf("OUT_BINDING"); /** * {@inheritDoc} */ public String getBindingType() { return "powerdoglocalapi"; } /** * @{inheritDoc */ @Override public void validateItemType(Item item, String bindingConfig) throws BindingConfigParseException { if (!(item instanceof SwitchItem || item instanceof DimmerItem || item instanceof ContactItem || item instanceof NumberItem || item instanceof StringItem)) { throw new BindingConfigParseException("item '" + item.getName() + "' is of type '" + item.getClass().getSimpleName() + "', only Switch-, Dimmer-, Contact-, Number- and String-Items are allowed - please check your *.items configuration"); } logger.debug("PowerDogLocalApi:validateItemType called"); } /** * {@inheritDoc} */ @Override public void processBindingConfiguration(String context, Item item, String bindingConfig) throws BindingConfigParseException { logger.debug("PowerDogLocalApi:processBindingConfiguration called"); super.processBindingConfiguration(context, item, bindingConfig); // parse and accept binding configuration if (bindingConfig != null) { PowerDogLocalApiBindingConfig config = parseBindingConfig(item, bindingConfig); logger.debug("bindingConfig added (config={})", config.toString()); addBindingConfig(item, config); } else { logger.warn("bindingConfig is NULL (item={}) -> process bindingConfig aborted!", item); } } /** * Delegates parsing the <code>bindingConfig</code> with respect to the * first character (<code><</code> or <code>></code>) to the * specialized parsing methods * * @param item * @param bindingConfig * * @throws BindingConfigParseException */ protected PowerDogLocalApiBindingConfig parseBindingConfig(Item item, String bindingConfig) throws BindingConfigParseException { logger.debug("PowerDogLocalAPI:parseBindingConfig called"); // create binding configuration PowerDogLocalApiBindingConfig config = new PowerDogLocalApiBindingConfig(); config.itemType = item.getClass(); // set regular expression Matcher matcher = BASE_CONFIG_PATTERN.matcher(bindingConfig); if (!matcher.matches()) { throw new BindingConfigParseException("PowerDogLocalAPI:bindingConfig '" + bindingConfig + "' doesn't contain a valid binding configuration"); } matcher.reset(); // check for high level regular expression, if in- or out-binding is // asked while (matcher.find()) { String direction = matcher.group(1); String bindingConfigPart = matcher.group(2); if (direction.equals("<")) { // in-binding config line found config = parseInnerBindingConfig(item, bindingConfigPart, config, IN_BINDING_KEY); } // else if (direction.equals(">")) { // out-binding config line found config = parseInnerBindingConfig(item, bindingConfigPart, config, OUT_BINDING_KEY); } else { throw new BindingConfigParseException( "Unknown command given! Configuration must start with '<' or '>' "); } } return config; } /** * Parses a PowerDog LocalAPI in configuration by using the regular * expression {@link INNER_BINDING_PATTERN}. Where the groups should contain * the following content: * <ul> * <li>1 - Server ID</li> * <li>2 - PowerDog Value ID</li> * <li>3 - Refresh Interval</li> * <li>4 - Variable name (optional, defaults to Current_Value)</li> * </ul> * * @param item * @param bindingConfig * the config string to parse * @param config * * @return the filled {@link PowerDogLocalAPIBindingConfig} * @throws BindingConfigParseException * if the regular expression doesn't match the given * <code>bindingConfig</code> */ protected PowerDogLocalApiBindingConfig parseInnerBindingConfig(Item item, String bindingConfig, PowerDogLocalApiBindingConfig config, Command key) throws BindingConfigParseException { logger.debug("PowerDogLocalAPI:parseInnerBindingConfig called"); PowerDogLocalApiBindingConfigElement configElement; // Check if regex for in-binding matches Matcher matcher = INNER_BINDING_PATTERN.matcher(bindingConfig); if (!matcher.matches()) { throw new BindingConfigParseException("bindingConfig '" + bindingConfig + "' doesn't represent a valid binding-configuration. A valid configuration is matched by the RegExp '" + INNER_BINDING_PATTERN + "'"); } matcher.reset(); // parse regex and extract configuration data while (matcher.find()) { configElement = new PowerDogLocalApiBindingConfigElement(); configElement.serverId = matcher.group(1); configElement.valueId = matcher.group(2); configElement.refreshInterval = Integer.valueOf(matcher.group(3)); if (matcher.group(4).isEmpty()) { // group 4 is optional configElement.name = "Current_Value"; } else { configElement.name = matcher.group(4).substring(1); } logger.debug("PowerDogLocalAPI: {}", configElement); config.put(key, configElement); } return config; } /** * @{inheritDoc */ @Override public Class<? extends Item> getItemType(String itemName) { PowerDogLocalApiBindingConfig config = (PowerDogLocalApiBindingConfig) bindingConfigs.get(itemName); return config != null ? config.itemType : null; } /** * {@inheritDoc} */ public String getServerId(String itemName) { PowerDogLocalApiBindingConfig config = (PowerDogLocalApiBindingConfig) bindingConfigs.get(itemName); String returnValue = null; if (config != null) { if (config.get(IN_BINDING_KEY) != null) { returnValue = config.get(IN_BINDING_KEY).serverId; } else if (config.get(OUT_BINDING_KEY) != null) { returnValue = config.get(OUT_BINDING_KEY).serverId; } } return returnValue; } /** * {@inheritDoc} */ public String getValueId(String itemName) { PowerDogLocalApiBindingConfig config = (PowerDogLocalApiBindingConfig) bindingConfigs.get(itemName); String returnValue = null; if (config != null) { if (config.get(IN_BINDING_KEY) != null) { returnValue = config.get(IN_BINDING_KEY).valueId; } else if (config.get(OUT_BINDING_KEY) != null) { returnValue = config.get(OUT_BINDING_KEY).valueId; } } return returnValue; } /** * {@inheritDoc} */ public String getName(String itemName) { PowerDogLocalApiBindingConfig config = (PowerDogLocalApiBindingConfig) bindingConfigs.get(itemName); return config != null && config.get(IN_BINDING_KEY) != null ? config.get(IN_BINDING_KEY).name : null; } /** * {@inheritDoc} */ public int getRefreshInterval(String itemName) { PowerDogLocalApiBindingConfig config = (PowerDogLocalApiBindingConfig) bindingConfigs.get(itemName); int returnValue = 0; if (config != null) { if (config.get(IN_BINDING_KEY) != null) { returnValue = config.get(IN_BINDING_KEY).refreshInterval; } else if (config.get(OUT_BINDING_KEY) != null) { returnValue = config.get(OUT_BINDING_KEY).refreshInterval; } } return returnValue; } /** * {@inheritDoc} */ public List<String> getInBindingItemNames() { List<String> inBindings = new ArrayList<String>(); for (String itemName : bindingConfigs.keySet()) { PowerDogLocalApiBindingConfig pdConfig = (PowerDogLocalApiBindingConfig) bindingConfigs.get(itemName); if (pdConfig.containsKey(IN_BINDING_KEY)) { inBindings.add(itemName); } } return inBindings; } /** * {@inheritDoc} */ public List<String> getOutBindingItemNames() { List<String> outBindings = new ArrayList<String>(); for (String itemName : bindingConfigs.keySet()) { PowerDogLocalApiBindingConfig pdConfig = (PowerDogLocalApiBindingConfig) bindingConfigs.get(itemName); if (pdConfig.containsKey(OUT_BINDING_KEY)) { outBindings.add(itemName); } } return outBindings; } /** * This is a helper class holding binding specific configuration details to * map commands to {@link PowerDogLocalAPIBindingConfigElement }. There will * be a map like <code>ON->PowerDogLocalAPIBindingConfigElement</code> * * @author wuellueb * @since 1.9.0 */ class PowerDogLocalApiBindingConfig extends HashMap<Command, PowerDogLocalApiBindingConfigElement> implements BindingConfig { private static final long serialVersionUID = -3746900828632519633L; Class<? extends Item> itemType; } /** * This is an internal data structure to store information from the binding * config strings and use it to answer the requests to the binding provider. */ static class PowerDogLocalApiBindingConfigElement implements BindingConfig { public String serverId; // as used in the openhab configuration file public String valueId; // PowerDog value ID, e.g. // 'impulsecounter_1234567890' public String name; // Parameter name, e.g. 'Current_Value' (not valid // for out-binding) public int refreshInterval; // Refresh rate for the specific item, will // not be queried faster than set for the // corresponding PowerDog @Override public String toString() { return "PowerDogLocalAPIBindingConfigElement [serverId=" + serverId + ", valueId=" + valueId + ", name=" + name + ", refreshInterval=" + refreshInterval + "]"; } } }