/** * 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.sonos.internal; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.openhab.binding.sonos.SonosBindingProvider; import org.openhab.core.binding.BindingConfig; import org.openhab.core.items.Item; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.StringType; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.openhab.core.types.TypeParser; import org.openhab.model.item.binding.AbstractGenericBindingProvider; import org.openhab.model.item.binding.BindingConfigParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * sonos commands will be limited to the simple commands that take only up to one input parameter. unpnp actions * requiring input variables could potentially take their inputs from elsewhere in the binding, e.g. config parameters * or other * * sonos="[ON:office:play], [OFF:office:stop]" - switch items for ordinary sonos commands * using openhab command : player name : sonos command as format * * sonos="[office:getcurrenttrack]" - string and number items for UPNP service variable updates using * using player_name : somecommand, where somecommand takes a simple input/output value from/to the string * * @author Karel Goderis * @author Pauli Anttila * @since 1.1.0 */ public class SonosGenericBindingProvider extends AbstractGenericBindingProvider implements SonosBindingProvider { static final Logger logger = LoggerFactory.getLogger(SonosGenericBindingProvider.class); /** {@link Pattern} which matches a binding configuration part */ private static final Pattern ACTION_CONFIG_PATTERN = Pattern.compile("\\[(.*):(.*):(.*)\\]"); private static final Pattern STATUS_CONFIG_PATTERN = Pattern.compile("\\[(.*):(.*)\\]"); static int counter = 0; @Override public String getBindingType() { return "sonos"; } @Override public void validateItemType(Item item, String bindingConfig) throws BindingConfigParseException { // All Item Types are accepted by SonosGenericBindingProvider } /** * {@inheritDoc} */ @Override public void processBindingConfiguration(String context, Item item, String bindingConfig) throws BindingConfigParseException { super.processBindingConfiguration(context, item, bindingConfig); if (bindingConfig != null) { parseAndAddBindingConfig(item, bindingConfig); } else { logger.warn("bindingConfig is NULL (item=" + item + ") -> processing bindingConfig aborted!"); } } private void parseAndAddBindingConfig(Item item, String bindingConfigs) throws BindingConfigParseException { String bindingConfig = StringUtils.substringBefore(bindingConfigs, ","); String bindingConfigTail = StringUtils.substringAfter(bindingConfigs, ","); SonosBindingConfig newConfig = new SonosBindingConfig(); parseBindingConfig(newConfig, item, bindingConfig); addBindingConfig(item, newConfig); while (StringUtils.isNotBlank(bindingConfigTail)) { bindingConfig = StringUtils.substringBefore(bindingConfigTail, ","); bindingConfig = StringUtils.strip(bindingConfig); bindingConfigTail = StringUtils.substringAfter(bindingConfigTail, ","); parseBindingConfig(newConfig, item, bindingConfig); addBindingConfig(item, newConfig); } } private void parseBindingConfig(SonosBindingConfig config, Item item, String bindingConfig) throws BindingConfigParseException { String sonosID = null; String commandAsString = null; String sonosCommand = null; if (bindingConfig != null) { Matcher actionMatcher = ACTION_CONFIG_PATTERN.matcher(bindingConfig); Matcher statusMatcher = STATUS_CONFIG_PATTERN.matcher(bindingConfig); if (!actionMatcher.matches() && !statusMatcher.matches()) { throw new BindingConfigParseException("Sonos binding configuration must consist of either two [config=" + statusMatcher + "] or three parts [config=" + actionMatcher + "]"); } else { if (actionMatcher.matches()) { commandAsString = actionMatcher.group(1); sonosID = actionMatcher.group(2); sonosCommand = actionMatcher.group(3); } else if (statusMatcher.matches()) { commandAsString = null; sonosID = statusMatcher.group(1); sonosCommand = statusMatcher.group(2); } SonosBindingConfigElement newElement = new SonosBindingConfigElement(sonosCommand, sonosID, item.getAcceptedDataTypes()); Command command = null; if (commandAsString == null) { command = createCommandFromString(null, Integer.toString(counter)); counter++; config.put(command, newElement); } else { command = createCommandFromString(item, commandAsString); config.put(command, newElement); } } } else { return; } } /** * Creates a {@link Command} out of the given <code>commandAsString</code> * incorporating the {@link TypeParser}. * * @param item, or null if the Command has to be of the StringType type * @param commandAsString * * @return an appropriate Command (see {@link TypeParser} for more * information * * @throws BindingConfigParseException if the {@link TypeParser} couldn't * create a command appropriately * * @see {@link TypeParser} */ private Command createCommandFromString(Item item, String commandAsString) throws BindingConfigParseException { List<Class<? extends Command>> acceptedTypes = new ArrayList<Class<? extends Command>>(); if (item != null) { acceptedTypes = item.getAcceptedCommandTypes(); } else { acceptedTypes.add(StringType.class); } Command command = TypeParser.parseCommand(acceptedTypes, commandAsString); if (command == null) { throw new BindingConfigParseException("couldn't create Command from '" + commandAsString + "' "); } return command; } /** * This is an internal data structure to map commands to * {@link SonosBindingConfigElement }. There will be map like * <code>ON->SonosBindingConfigElement</code> */ static class SonosBindingConfig extends HashMap<Command, SonosBindingConfigElement>implements BindingConfig { private static final long serialVersionUID = 1943053272317438930L; } static class SonosBindingConfigElement implements BindingConfig { final private String sonosCommand; final private String id; final private List<Class<? extends State>> acceptedDataTypes; public SonosBindingConfigElement(String sonosCommand, String sonosID, List<Class<? extends State>> acceptedDataTypes) { this.sonosCommand = sonosCommand; this.id = sonosID; this.acceptedDataTypes = acceptedDataTypes; } @Override public String toString() { return "SonosBindingConfigElement [Direction=" + sonosCommand + ", id=" + id + ", type=" + "]"; } /** * @return the id */ public String getSonosID() { return id; } /** * @return the command */ public String getSonosCommand() { return sonosCommand; } public List<Class<? extends State>> getAcceptedDataTypes() { return acceptedDataTypes; } } @Override public List<String> getSonosID(String itemName) { List<String> ids = new ArrayList<String>(); SonosBindingConfig aConfig = (SonosBindingConfig) bindingConfigs.get(itemName); for (Command aCommand : aConfig.keySet()) { ids.add(aConfig.get(aCommand).getSonosID()); } return ids; } @Override public String getSonosID(String itemName, Command aCommand) { SonosBindingConfig aConfig = (SonosBindingConfig) bindingConfigs.get(itemName); return aConfig != null && aConfig.get(aCommand) != null ? aConfig.get(aCommand).getSonosID() : null; } @Override public List<Class<? extends State>> getAcceptedDataTypes(String itemName) { SonosBindingConfig aConfig = (SonosBindingConfig) bindingConfigs.get(itemName); if (aConfig != null) { if (aConfig.size() != 0) { Iterator<SonosBindingConfigElement> it = aConfig.values().iterator(); while (it.hasNext()) { SonosBindingConfigElement anElement = it.next(); return anElement.getAcceptedDataTypes(); } } } return null; } @Override public String getSonosCommand(String itemName, Command aCommand) { SonosBindingConfig aBindingConfig = (SonosBindingConfig) bindingConfigs.get(itemName); if (aBindingConfig != null) { for (Command command : aBindingConfig.keySet()) { SonosBindingConfigElement anElement = aBindingConfig.get(command); if (command == aCommand) { return anElement.getSonosCommand(); } } } return null; } @Override public List<String> getItemNames(String sonosID, String sonosCommand) { List<String> result = new ArrayList<String>(); Collection<String> items = getItemNames(); for (String anItem : items) { SonosBindingConfig aBindingConfig = (SonosBindingConfig) bindingConfigs.get(anItem); for (Command command : aBindingConfig.keySet()) { SonosBindingConfigElement anElement = aBindingConfig.get(command); if (anElement.getSonosCommand().equals(sonosCommand) && anElement.getSonosID().equals(sonosID) && !result.contains(anItem)) { result.add(anItem); } } } return result; } @Override public List<Command> getCommands(String anItem, String sonosCommand) { List<Command> commands = new ArrayList<Command>(); SonosBindingConfig aConfig = (SonosBindingConfig) bindingConfigs.get(anItem); for (Command aCommand : aConfig.keySet()) { SonosBindingConfigElement anElement = aConfig.get(aCommand); if (anElement.getSonosCommand().equals(sonosCommand)) { commands.add(aCommand); } } return commands; } @Override public List<Command> getVariableCommands(String anItem) { List<Command> commands = new ArrayList<Command>(); SonosBindingConfig aConfig = (SonosBindingConfig) bindingConfigs.get(anItem); for (Command aCommand : aConfig.keySet()) { if (aCommand instanceof StringType || aCommand instanceof DecimalType) { commands.add(aCommand); } } return commands; } @Override public List<String> getItemNames(String sonosCommand) { List<String> result = new ArrayList<String>(); Collection<String> items = getItemNames(); for (String anItem : items) { SonosBindingConfig aBindingConfig = (SonosBindingConfig) bindingConfigs.get(anItem); for (Command command : aBindingConfig.keySet()) { SonosBindingConfigElement anElement = aBindingConfig.get(command); if (anElement.getSonosCommand().equals(sonosCommand) && !result.contains(anItem)) { result.add(anItem); } } } return result; } }