/** * 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.ecobee.internal; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.openhab.binding.ecobee.EcobeeBindingProvider; import org.openhab.binding.ecobee.messages.Selection; import org.openhab.core.binding.BindingConfig; import org.openhab.core.items.Item; import org.openhab.model.item.binding.AbstractGenericBindingProvider; import org.openhab.model.item.binding.BindingConfigParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is responsible for parsing the binding configuration. * * <p> * Ecobee bindings start with a <, > or =, to indicate if the item receives values from the API (in binding), * sends values to the API (out binding), or both (bidirectional binding), respectively. * * <p> * The first character is then followed by a section between square brackets ([ and ] characters): * * <p> * <code>[<thermostat>#<property>]</code> * * <p> * Where <code>thermostat</code> is a decimal thermostat identifier for in, out and bidirectional bindings. * * <p> * For out bindings only, <code>thermostat</code> can instead be selection criteria that specify which thermostats to * change. You can use either a comma-separated list of thermostat identifiers, or, for non-EMS thermostats only, a * wildcard (the <code>*</code> character). * * <p> * In the case of out bindings for EMS or Utility accounts, the <code>thermostat</code> criteria can be a path to a * management set (for example, <code>/Toronto/Campus/BuildingA</code>). * * <p> * The <code>thermostat</code> specification can be optionally prepended with a specific app instance name as specified * in <code>openhab.cfg</code>, as in <code>condo.123456789</code> when you have specified * <code>ecobee:condo.scope</code> and <code>ecobee:condo.appkey</code> properties in <code>openhab.cfg</code>. * * <p> * <code>property</code> is one of a long list of thermostat properties than you can read and optionally change. See the * list below, and peruse this binding's JavaDoc for all specifics as to their meanings. * * <table> * <thead> * <tr> * <th>Property</th> * <th>In</th> * <th>Out</ht> * </tr> * </thead> <tbody> * <tr> * <td>name</td> * <td>X</td> * <td>X</td> * </tr> * <tr> * <td>runtime.actualTemperature</td> * <td>X</td> * <td></td> * </tr> * <tr> * <td>runtime.actualHumidity</td> * <td>X</td> * <td></td> * </tr> * <tr> * <td>settings.hvacMode</td> * <td>X</td> * <td>X</td> * </tr> * </tbody> * </table> * * <p> * Example bindings: * <ul> * <li><code>{ ecobee="<[123456789#name]" }</code> * <p> * Return the name of the thermostat whose ID is 123456789 using the default Ecobee app instance (configured in * openhab.cfg).</li> * <li><code>{ ecobee="<[condo.987654321#runtime.actualTemperature]" }</code> * <p> * Return the current temperature read by the thermostat using the condo account at ecobee.com.</li> * <li><code>{ ecobee=">[543212345#settings.fanMinOnTime]" }</code> * <p> * Set the minimum number of minutes per hour the fan will run on thermostat ID 543212345.</li> * <li><code>{ ecobee=">[*#settings.hvacMode]" }</code> * <p> * Change the HVAC mode to one of <code>"auto"</code>, <code>"auxHeatOnly"</code>, <code>"cool"</code>, * <code>"heat"</code>, or <code>"off"</code> on all thermostats registered in the default app instance.</li> * <li> * <code>{ ecobee=">[lakehouse.*#settings.backlightSleepIntensity]" }</code> * <p> * Changes the backlight sleep intensity on all thermostats at the lake house (meaning, all thermostats registered to * the lakehouse Ecobee account).</li> * </ul> * * @author John Cocula * @since 1.7.0 */ public class EcobeeGenericBindingProvider extends AbstractGenericBindingProvider implements EcobeeBindingProvider { private static class EcobeeBindingConfig implements BindingConfig { String userid; String thermostatIdentifier; String property; boolean inBound = false; boolean outBound = false; public EcobeeBindingConfig(final String userid, final String thermostatIdentifier, final String property, final boolean inBound, final boolean outBound) { this.userid = userid; this.thermostatIdentifier = thermostatIdentifier; this.property = property; this.inBound = inBound; this.outBound = outBound; } @Override public String toString() { return "EcobeeBindingConfig [userid=" + this.userid + "thermostatIdentifier=" + this.thermostatIdentifier + ", property=" + this.property + ", inBound=" + this.inBound + ", outBound=" + this.outBound + "]"; } } private static Logger logger = LoggerFactory.getLogger(EcobeeGenericBindingProvider.class); private static final Pattern CONFIG_PATTERN = Pattern.compile(".\\[(.*)#(.*)\\]"); // the first character in the above pattern private static final String IN_BOUND = "<"; private static final String OUT_BOUND = ">"; private static final String BIDIRECTIONAL = "="; /** * {@inheritDoc} */ @Override public String getBindingType() { return "ecobee"; } /** * {@inheritDoc} */ @Override public String getUserid(final String itemName) { final EcobeeBindingConfig config = (EcobeeBindingConfig) this.bindingConfigs.get(itemName); return config != null ? config.userid : null; } /** * {@inheritDoc} */ @Override public String getThermostatIdentifier(final String itemName) { EcobeeBindingConfig config = (EcobeeBindingConfig) this.bindingConfigs.get(itemName); return config != null ? config.thermostatIdentifier : null; } /** * {@inheritDoc} */ @Override public String getProperty(final String itemName) { EcobeeBindingConfig config = (EcobeeBindingConfig) this.bindingConfigs.get(itemName); return config != null ? config.property : null; } /** * {@inheritDoc} */ @Override public boolean isInBound(final String itemName) { EcobeeBindingConfig config = (EcobeeBindingConfig) this.bindingConfigs.get(itemName); return config != null ? config.inBound : false; } /** * {@inheritDoc} */ @Override public boolean isOutBound(final String itemName) { EcobeeBindingConfig config = (EcobeeBindingConfig) this.bindingConfigs.get(itemName); return config != null ? config.outBound : false; } /** * {@inheritDoc} */ @Override public void processBindingConfiguration(final String context, final Item item, final String bindingConfig) throws BindingConfigParseException { logger.debug("Processing binding configuration: '{}'", bindingConfig); super.processBindingConfiguration(context, item, bindingConfig); boolean inBound = false; boolean outBound = false; if (bindingConfig.startsWith(IN_BOUND)) { inBound = true; } else if (bindingConfig.startsWith(OUT_BOUND)) { outBound = true; } else if (bindingConfig.startsWith(BIDIRECTIONAL)) { inBound = true; outBound = true; } else { throw new BindingConfigParseException("Item \"" + item.getName() + "\" does not start with " + IN_BOUND + ", " + OUT_BOUND + " or " + BIDIRECTIONAL + "."); } Matcher matcher = CONFIG_PATTERN.matcher(bindingConfig); if (!matcher.matches() || matcher.groupCount() != 2) { throw new BindingConfigParseException("Config for item '" + item.getName() + "' could not be parsed."); } String userid = null; String thermostatIdentifier = matcher.group(1); if (thermostatIdentifier.contains(".")) { String[] parts = thermostatIdentifier.split("\\."); userid = parts[0]; thermostatIdentifier = parts[1]; } if (inBound && !Selection.isThermostatIdentifier(thermostatIdentifier)) { throw new BindingConfigParseException( "Only a single thermostat identifier is permitted in an in binding or bidirectional binding."); } String property = matcher.group(2); EcobeeBindingConfig config = new EcobeeBindingConfig(userid, thermostatIdentifier, property, inBound, outBound); addBindingConfig(item, config); } /** * {@inheritDoc} */ @Override public void validateItemType(Item item, String bindingConfig) throws BindingConfigParseException { logger.trace("validateItemType called with bindingConfig={}", bindingConfig); Matcher matcher = CONFIG_PATTERN.matcher(bindingConfig); if (!matcher.matches() || matcher.groupCount() != 2) { throw new BindingConfigParseException("Config for item '" + item.getName() + "' could not be parsed."); } String property = matcher.group(2); logger.trace("validateItemType called with property={}", property); } }