/** * 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.urtsi.internal; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.openhab.binding.urtsi.UrtsiBindingProvider; import org.openhab.core.binding.AbstractBinding; import org.openhab.core.library.types.StopMoveType; import org.openhab.core.library.types.UpDownType; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.openhab.core.types.Type; import org.openhab.model.item.binding.BindingConfigParseException; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.Sets; /** * Main implementation of the Somfy URTSI II Binding. This binding is * responsible for delegating the received commands and updates to the * {@link UrtsiDevice}. * * @author Oliver Libutzki * @since 1.3.0 * */ public class UrtsiBinding extends AbstractBinding<UrtsiBindingProvider>implements ManagedService { private static final String GNU_IO_RXTX_SERIAL_PORTS = "gnu.io.rxtx.SerialPorts"; private final static Logger logger = LoggerFactory.getLogger(UrtsiBinding.class); private final static String COMMAND_UP = "U"; private final static String COMMAND_DOWN = "D"; private final static String COMMAND_STOP = "S"; private final static String CONFIG_PORT = "port"; private final static String CONFIG_INTERVAL = "interval"; private final static Pattern EXTRACT_URTSI_CONFIG_PATTERN = Pattern .compile("^(.*?)\\.(" + CONFIG_PORT + "|" + CONFIG_INTERVAL + ")$"); /** * Maps the device id to a URTSI device. This is needed if you use multiple * ports for multiple urtsi devices. */ private final Map<String, UrtsiDevice> idToDeviceMap = new HashMap<String, UrtsiDevice>(); /** * The method determines the appropriate * {@link org.openhab.core.binding.BindingProvider} and uses it to get the * corresponding URTSI device and channel. Bases on the given type a command * is send to the device. * * @param itemName * name of the item * @param type * Type of the command or status update * @return Returns true, if the command has been executed successfully. * Returns false otherwise. * @throws BindingConfigParseException */ private boolean sendToUrtsi(String itemName, Type type) { UrtsiBindingProvider provider = null; if (!providers.isEmpty()) { provider = providers.iterator().next(); } if (provider == null) { logger.error("doesn't find matching binding provider [itemName={}, type={}]", itemName, type); return false; } String urtsiDeviceId = provider.getDeviceId(itemName); UrtsiDevice urtsiDevice = idToDeviceMap.get(urtsiDeviceId); if (urtsiDevice == null) { logger.error("No serial port has been configured for urtsi device id '{}'", urtsiDeviceId); return false; } int channel = provider.getChannel(itemName); int address = provider.getAddress(itemName); logger.debug("Send to URTSI for item: {}; Type: {}", itemName, type); String actionKey = null; if (type instanceof UpDownType) { switch ((UpDownType) type) { case UP: actionKey = COMMAND_UP; break; case DOWN: actionKey = COMMAND_DOWN; break; } } else if (type instanceof StopMoveType) { switch ((StopMoveType) type) { case STOP: actionKey = COMMAND_STOP; break; default: break; } } logger.debug("Action key: {}", actionKey); if (actionKey != null) { String channelString = String.format("%02d", channel); String addressString = String.format("%02d", address); String command = addressString + channelString + actionKey; boolean executedSuccessfully = urtsiDevice.writeString(command); if (!executedSuccessfully) { logger.warn("Command has not been processed [itemName={}, command={}]", itemName, command); } return executedSuccessfully; } return false; } /** * The method delegates the received command to the URTSI device and updates * the item's state, if the command has been executed successfully. */ @Override protected void internalReceiveCommand(String itemName, Command command) { logger.debug("Received command for {}! Command: {}", itemName, command); boolean executedSuccessfully = sendToUrtsi(itemName, command); if (executedSuccessfully && command instanceof State) { eventPublisher.postUpdate(itemName, (State) command); } } /** * With openHAB 1.7.0 the state-update is not passed to the URTSI device anymore as this let to multiple actions in * the past. * */ @Override protected void internalReceiveUpdate(String itemName, State newState) { logger.debug("Received update for {}! New state: {}", itemName, newState); } protected void addBindingProvider(UrtsiBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(UrtsiBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } /** * Parses the global configuration file. * Expected values: * urtsi.<deviceid>.port=<serialport> * urtsi.<deviceid>.interval=<interval> (optional, default: 100) */ @Override public void updated(Dictionary<String, ?> config) throws ConfigurationException { if (config != null) { Map<String, String> errorMessages = new LinkedHashMap<String, String>(); Enumeration<String> keys = config.keys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); logger.debug("Processing key '{}'", key); // the config-key enumeration contains additional keys that we // don't want to process here ... if (!key.equals("service.pid") && !key.equals("component.name")) { Matcher matcher = EXTRACT_URTSI_CONFIG_PATTERN.matcher(key); if (!matcher.matches()) { logger.debug("given config key '{}' does not follow the expected pattern '<id>.port'", key); } else { matcher.reset(); matcher.find(); String deviceId = matcher.group(1); UrtsiDevice urtsiDevice = idToDeviceMap.get(deviceId); if (urtsiDevice == null) { urtsiDevice = new UrtsiDevice(); idToDeviceMap.put(deviceId, urtsiDevice); } String configKey = matcher.group(2); String value = (String) config.get(key); if (CONFIG_PORT.equals(configKey)) { urtsiDevice.setPort(value); } else if (CONFIG_INTERVAL.equals(configKey)) { urtsiDevice.setInterval(Integer.valueOf(value)); } else { errorMessages.put(configKey, "the given config key '" + configKey + "' is unknown"); } } } } for (Iterator<Entry<String, UrtsiDevice>> deviceIterator = idToDeviceMap.entrySet() .iterator(); deviceIterator.hasNext();) { Entry<String, UrtsiDevice> deviceEntry = deviceIterator.next(); UrtsiDevice urtsiDevice = deviceEntry.getValue(); try { String serialPortsProperty = System.getProperty(GNU_IO_RXTX_SERIAL_PORTS); Set<String> serialPorts = null; if (serialPortsProperty != null) { serialPorts = Sets.newHashSet(Splitter.on(":").split(serialPortsProperty)); } else { serialPorts = new HashSet<String>(); } if (serialPorts.add(urtsiDevice.getPort())) { logger.debug("Added {} to the {} system property.", urtsiDevice.getPort(), GNU_IO_RXTX_SERIAL_PORTS); } System.setProperty(GNU_IO_RXTX_SERIAL_PORTS, Joiner.on(":").join(serialPorts)); urtsiDevice.initialize(); } catch (Throwable e) { deviceIterator.remove(); errorMessages.put(deviceEntry.getKey(), e.getMessage()); } } if (!errorMessages.isEmpty()) { StringBuilder errorMessageStringBuilder = new StringBuilder("The following errors occurred:\r\n"); for (Iterator<Entry<String, String>> errorMessageIterator = errorMessages.entrySet() .iterator(); errorMessageIterator.hasNext();) { Entry<String, String> errorMessageEntry = errorMessageIterator.next(); errorMessageStringBuilder.append(errorMessageEntry.getKey()).append(": ") .append(errorMessageEntry.getValue()); if (errorMessageIterator.hasNext()) { errorMessageStringBuilder.append("\r\n"); } } logger.error(errorMessageStringBuilder.toString()); Entry<String, String> firstErrorMessageEntry = errorMessages.entrySet().iterator().next(); throw new ConfigurationException(firstErrorMessageEntry.getKey(), firstErrorMessageEntry.getValue()); } } } }