/** * 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.alarmdecoder.internal; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import org.openhab.binding.alarmdecoder.AlarmDecoderBindingProvider; import org.openhab.core.items.Item; import org.openhab.core.library.items.ContactItem; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.items.StringItem; import org.openhab.core.library.items.SwitchItem; import org.openhab.model.item.binding.AbstractGenericBindingProvider; import org.openhab.model.item.binding.BindingConfigParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class parses the binding configuration in the items file. * * @author Bernd Pfrommer * @since 1.6.0 */ public class AlarmDecoderGenericBindingProvider extends AbstractGenericBindingProvider implements AlarmDecoderBindingProvider { private static final Logger logger = LoggerFactory.getLogger(AlarmDecoderGenericBindingProvider.class); // m_itemMap allows quick retrieval of all items that reference a given message // type and address, thus facilitating the lookup when messages arrive private ArrayList<HashMap<String, ArrayList<AlarmDecoderBindingConfig>>> m_itemMap = new ArrayList<HashMap<String, ArrayList<AlarmDecoderBindingConfig>>>(); private HashMap<String, AlarmDecoderBindingConfig> m_itemsToConfig = new HashMap<String, AlarmDecoderBindingConfig>(); public AlarmDecoderGenericBindingProvider() { for (int i = 0; i < ADMsgType.NUMTYPES.getValue(); i++) { m_itemMap.add(new HashMap<String, ArrayList<AlarmDecoderBindingConfig>>()); } } @Override public AlarmDecoderBindingConfig getBindingConfig(String itemName) { return m_itemsToConfig.get(itemName); } @Override public ArrayList<AlarmDecoderBindingConfig> getConfigurations(ADMsgType mt, String addr, String feature) { HashMap<String, ArrayList<AlarmDecoderBindingConfig>> a2c = m_itemMap.get(mt.getValue()); if (addr != null) { ArrayList<AlarmDecoderBindingConfig> al = a2c.get(addr); return (al == null ? new ArrayList<AlarmDecoderBindingConfig>() : al); } else { ArrayList<AlarmDecoderBindingConfig> al = new ArrayList<AlarmDecoderBindingConfig>(); for (Entry<String, ArrayList<AlarmDecoderBindingConfig>> a : a2c.entrySet()) { if (feature == null) { al.addAll(a.getValue()); } else { for (AlarmDecoderBindingConfig cf : a.getValue()) { al.add(cf); } } } return (al); } } /** * {@inheritDoc} */ @Override public String getBindingType() { return "alarmdecoder"; } /** * {@inheritDoc} */ @Override public void validateItemType(Item item, String bindingConfig) throws BindingConfigParseException { // TODO: should parse, then do type checking based on binding config string! if ((item instanceof NumberItem) || (item instanceof ContactItem) || (item instanceof StringItem) || (item instanceof SwitchItem)) { return; } throw new BindingConfigParseException("item '" + item.getName() + "' is of type '" + item.getClass().getSimpleName() + "', only Number, Contact, String, or Switch item types are allowed. Check your *.items configuration"); } /** * {@inheritDoc} */ @Override public void processBindingConfiguration(String context, Item item, String bindingConfig) throws BindingConfigParseException { super.processBindingConfiguration(context, item, bindingConfig); HashMap<String, String> params = new HashMap<String, String>(); String[] parts = s_parseConfigString(item.getName(), bindingConfig, params); AlarmDecoderBindingConfig bc = null; if (parts[0].equals("SEND")) { // binding for sending commands if (!(parts.length == 2)) { throw new BindingConfigParseException("invalid SEND item config: " + bindingConfig); } bc = new AlarmDecoderBindingConfig(item, params); } else { // binding for receiving messages ADMsgType mt = ADMsgType.s_fromString(parts[0]); HashMap<String, ArrayList<AlarmDecoderBindingConfig>> addrToItemsMap = m_itemMap.get(mt.getValue()); ArrayList<AlarmDecoderBindingConfig> bcl = addrToItemsMap.get(parts[1]); if (bcl == null) { // don't have this address mapped to anything yet, start a new item list bcl = new ArrayList<AlarmDecoderBindingConfig>(); addrToItemsMap.put(parts[1], bcl); } else { // without this line a new binding configuration is entered whenever // the .items file is edited removeExisting(bcl, item); } bc = new AlarmDecoderBindingConfig(item, mt, parts[1], parts[2], params); bcl.add(bc); } addBindingConfig(item, bc); m_itemsToConfig.put(item.getName(), bc); logger.trace("processing item \"{}\" read from .items file with cfg string {}", item.getName(), bindingConfig); } /** * Removes existing item configurations * * @param bcl array list of binding configs to be checked * @param item item to be checked for */ private static void removeExisting(ArrayList<AlarmDecoderBindingConfig> bcl, Item item) { for (Iterator<AlarmDecoderBindingConfig> it = bcl.iterator(); it.hasNext();) { AlarmDecoderBindingConfig bc = it.next(); if (bc.getItemName().equals(item.getName())) { it.remove(); } } } /** * Parses binding configuration string * * @param bindingConfig * @return array with ["SEND", "TEXT"], or [type, address, feature + parameters] * @throws BindingConfigParseException if invalid binding string is found */ static private String[] s_parseConfigString(String itemName, String bindingConfig, HashMap<String, String> map) throws BindingConfigParseException { String shouldBe = "should be MSGType:ADDRESS#feature,param=foo or SEND#sendstring"; String[] segments = bindingConfig.split("#"); if (segments.length != 2) { throw new BindingConfigParseException("invalid item format: " + bindingConfig + ", " + shouldBe); } if (segments[0].equals("SEND")) { String[] params = segments[1].split(","); s_parseParameters(itemName, params, 0, map); return (segments); } else if (ADMsgType.s_containsValidMsgType(segments[0])) { return s_parseMsgConfigString(itemName, bindingConfig, map); } throw new BindingConfigParseException("invalid item format: " + bindingConfig + ", " + shouldBe); } static private String[] s_parseMsgConfigString(String itemName, String bindingConfig, HashMap<String, String> map) throws BindingConfigParseException { // String shouldBe = "should be MSGType:ADDRESS#feature,param=foo e.g. RFX:0923844#data,bit=5"; String[] segments = bindingConfig.split("#"); String[] dev = segments[0].split(":"); if (dev.length != 2) { throw new BindingConfigParseException("missing colon in item format: " + bindingConfig + ", " + shouldBe); } String type = dev[0]; String addr = dev[1]; ADMsgType mt = ADMsgType.s_fromString(type); if (!mt.isValid() || !s_isValidAddress(mt, addr)) { throw new BindingConfigParseException( "invalid device address for " + type + ": " + addr + " in items file."); } String[] params = segments[1].split(","); String feature = params[0]; s_parseParameters(itemName, params, 1, map); String[] retval = { type, addr, feature }; return retval; } static private void s_parseParameters(String itemName, String[] params, int offset, HashMap<String, String> map) { for (int i = offset; i < params.length; i++) { String[] kv = params[i].split("="); if (kv.length == 2) { map.put(kv[0], kv[1]); } else { logger.error("{} param {} does not have format a=b", itemName, params[i]); } } } /** * Address validator * * @param type the known msg type of the configuration * @param addr the address string of the configuration * @return true if valid address for given type */ static private boolean s_isValidAddress(ADMsgType type, String addr) { switch (type) { case LRR: case KPM: return (addr.matches("[0-9]+") || addr.equalsIgnoreCase("any")); case RFX: return (addr.matches("[0-9]+")); // e.g. 0923844 case EXP: case REL: return (addr.matches("[0-9][0-9],[0-9][0-9]")); // e.g 14,02 case INVALID: default: return (false); } } @Override public Boolean autoUpdate(String itemName) { return true; } }