3) {
try {
if(apiMessage.length() >= 8 && apiMessage.charAt(2) == ':' && apiMessage.charAt(5) == ':') {
+ timeStamp = apiMessage.substring(0,8);
apiMessage = apiMessage.substring(9, apiMessage.length() - 2);
}
else {
@@ -77,7 +80,9 @@ private void parseAPIMessage() {
apiCodeReceived = "000";
}
- if(APICode.getAPICodeValue(apiCodeReceived) != null) {
+ APICode apiCode = APICode.getAPICodeValue(apiCodeReceived);
+
+ if(apiCode != null) {
switch (APICode.getAPICodeValue(apiCodeReceived)) {
case CommandAcknowledge: /*500*/
@@ -91,6 +96,92 @@ private void parseAPIMessage() {
case SystemError: /*502*/
apiName = "System Error";
apiDescription = apiCodeReceived + ": An error has been detected.";
+ int systemErrorCode = 0;
+ systemErrorCode = Integer.parseInt(data);
+ switch(systemErrorCode) {
+ case 1:
+ error = "Receive Buffer Overrun";
+ break;
+ case 2:
+ error = "Receive Buffer Overflow";
+ break;
+ case 3:
+ error = "Transmit Buffer Overflow";
+ break;
+ case 10:
+ error = "Keybus Transmit Buffer Overrun";
+ break;
+ case 11:
+ error = "Keybus Transmit Time Timeout";
+ break;
+ case 12:
+ error = "Keybus Transmit Mode Timeout";
+ break;
+ case 13:
+ error = "Keybus Transmit Keystring Timeout";
+ break;
+ case 14:
+ error = "Keybus Interface Not Functioning";
+ break;
+ case 15:
+ error = "Keybus Busy - Attempting to Disarm or Arm with user code";
+ break;
+ case 16:
+ error = "Keybus Busy – Lockout";
+ break;
+ case 17:
+ error = "Keybus Busy – Installers Mode";
+ break;
+ case 18:
+ error = "Keybus Busy - General Busy";
+ break;
+ case 20:
+ error = "API Command Syntax Error";
+ break;
+ case 21:
+ error = "API Command Partition Error - Requested Partition is out of bounds";
+ break;
+ case 22:
+ error = "API Command Not Supported";
+ break;
+ case 23:
+ error = "API System Not Armed - Sent in response to a disarm command";
+ break;
+ case 24:
+ error = "API System Not Ready to Arm - System is either not-secure, in exit-delay, or already armed";
+ break;
+ case 25:
+ error = "API Command Invalid Length";
+ break;
+ case 26:
+ error = "API User Code not Required";
+ break;
+ case 27:
+ error = "API Invalid Characters in Command - No alpha characters are allowed except for checksum";
+ break;
+ case 28:
+ error = "API Virtual Keypad is Disabled";
+ break;
+ case 29:
+ error = "API Not Valid Parameter";
+ break;
+ case 30:
+ error = "API Keypad Does Not Come Out of Blank Mode";
+ break;
+ case 31:
+ error = "API IT-100 is Already in Thermostat Menu";
+ break;
+ case 32:
+ error = "API IT-100 is NOT in Thermostat Menu";
+ break;
+ case 33:
+ error = "API No Response From Thermostat or Escort Module";
+ break;
+ case 0:
+ default:
+ error = "No Error";
+ break;
+ }
break;
case LoginResponse: /*505*/
apiName = "Login Interaction";
@@ -109,7 +200,7 @@ private void parseAPIMessage() {
case TimeDateBroadcast: /*550*/
apiName = "Time-Date Broadcast";
apiDescription = apiCodeReceived + ": The current security system time.";
- data = apiMessage.substring(4);
+ data = apiMessage.substring(3);
break;
case RingDetected: /*560*/
apiName = "Ring Detected";
@@ -588,6 +679,11 @@ public String toString() {
sb.append(apiDescription);
sb.append("\"");
+ if (timeStamp != "") {
+ sb.append(", Time Stamp: ");
+ sb.append(timeStamp);
+ }
+
if (partition != 0) {
sb.append(", Partition: ");
sb.append(partition);
@@ -613,6 +709,11 @@ public String toString() {
sb.append(user);
}
+ if (error != "") {
+ sb.append(", error: ");
+ sb.append(error);
+ }
+
return sb.toString();
}
@@ -706,4 +807,22 @@ public String getMode() {
public String getUser() {
return user;
}
+
+ /**
+ * Returns the error string from the API message
+ *
+ * @return user
+ */
+ public String getError() {
+ return error;
+ }
+
+ /**
+ * Returns the time stamp if available
+ *
+ * @return timeStamp
+ */
+ public String getTimeStamp() {
+ return timeStamp;
+ }
}
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRBinding.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRBinding.java
index 26112685b93..8da811170bd 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRBinding.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRBinding.java
@@ -14,6 +14,8 @@
import org.openhab.binding.dsmr.DSMRBindingProvider;
import org.openhab.binding.dsmr.internal.cosem.CosemValue;
import org.openhab.binding.dsmr.internal.messages.OBISMessage;
+import org.openhab.binding.dsmr.internal.messages.OBISMsgFactory;
+import org.openhab.binding.dsmr.internal.p1telegram.P1TelegramParser;
import org.apache.commons.lang.StringUtils;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.types.State;
@@ -31,11 +33,12 @@
*
* At this moment the binding supports only a single Smart Meter.
*
- * The binding needs 2 necessary configuration parameters from openhab.cfg:
+ * The binding needs the following configuration parameters from openhab.cfg:
*
*
* - dsmr.port (serial port device)
- *
- dsmr.version {@link DSMRVersion}
+ *
- dsmr..chanel (M-Bus channel of the specified meter type gas,
+ * water, heating, cooling, generic, slaveelectricity)
*
*
* The implementation of the binding is based on the Dutch Smart Meter
@@ -56,8 +59,7 @@ public class DSMRBinding extends AbstractActiveBinding
/* Serial port (configurable via openhab.cfg) */
private String port = "";
- /* DSMR Version (configurable via openhab.cfg) */
- private DSMRVersion version = DSMRVersion.NONE;
+
/* Meter - channel mapping (configurable via openhab.cfg) */
private final List dsmrMeters = new ArrayList();
@@ -125,7 +127,7 @@ protected String getName() {
}
/**
- * @{inhearticDoc
+ * @{inheritDoc
*/
@Override
protected void execute() {
@@ -138,12 +140,15 @@ protected void execute() {
// Check if a valid DSMR port exists. Open a new one if necessary
if (dsmrPort == null || !dsmrPort.isOpen()) {
logger.debug("Creating DSMR Port:" + port);
- dsmrPort = new DSMRPort(port, version, dsmrMeters,
- DSMR_UPDATE_INTERVAL);
+
+ dsmrPort = new DSMRPort(port, new P1TelegramParser(
+ new OBISMsgFactory(dsmrMeters)), DSMR_UPDATE_INTERVAL / 2,
+ DSMR_UPDATE_INTERVAL * 2);
}
// Read the DSMRPort
List messages = dsmrPort.read();
+ logger.debug("Received " + messages.size() + " messages");
// Publish messages on the event bus
for (OBISMessage msg : messages) {
@@ -153,7 +158,8 @@ protected void execute() {
String dsmrItemId = provider.getDSMRItemID(itemName);
for (CosemValue extends State> openHABValue : msg
.getOpenHABValues()) {
- // DSMR items with an empty dsmrItemId are filtered automatically
+ // DSMR items with an empty dsmrItemId are filtered
+ // automatically
if (dsmrItemId.equals(openHABValue.getDsmrItemId())) {
logger.debug("Publish data(" + dsmrItemId + ") to "
+ itemName);
@@ -168,9 +174,7 @@ protected void execute() {
}
/**
- * Read the following properties: - dsmr:port. Serial port where DSMR can be
- * read (e.g. /dev/ttyUSB0) - dsmr:version. Version of the DSMR protocol of
- * the meter. See also {@link DSMRVersion}
+ * Read the dsmr:port and dsmr:.channel properties
*/
@Override
public void updated(Dictionary config)
@@ -186,15 +190,6 @@ public void updated(Dictionary config)
logger.warn("dsmr:port setting is empty");
}
- // Read version string
- String versionString = (String) config.get("version");
- logger.debug("dsmr:version=" + versionString);
- if (StringUtils.isNotBlank(versionString)) {
- version = DSMRVersion.getDSMRVersion(versionString);
- } else {
- logger.warn("dsmr:version setting is empty");
- }
-
/*
* Read the channel configuration
*/
@@ -217,8 +212,10 @@ public void updated(Dictionary config)
}
} else {
switch (meterType) {
- case NA: break; // Filter special DSMRMeterType
- case ELECTRICITY: break; // Always channel 0, configuration not needed
+ case NA:
+ break; // Filter special DSMRMeterType
+ case ELECTRICITY:
+ break; // Always channel 0, configuration not needed
default:
logger.info("dsmr:" + meterType.channelConfigKey
+ " setting is empty");
@@ -227,7 +224,7 @@ public void updated(Dictionary config)
}
// Validate minimal configuration
- if (version != DSMRVersion.NONE && port.length() > 0) {
+ if (port.length() > 0) {
logger.debug("Configuration succeeded");
setProperlyConfigured(true);
} else {
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRGenericBindingProvider.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRGenericBindingProvider.java
index 64ad923b7bb..c8ce32b4f0f 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRGenericBindingProvider.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRGenericBindingProvider.java
@@ -34,13 +34,14 @@ public String getBindingType() {
}
/**
- * @{inheritDoc}
+ * @{inheritDoc
*/
@Override
public void validateItemType(Item item, String bindingConfig)
throws BindingConfigParseException {
// TODO: More advanced checking based on bindingConfig is possible
- if (!(item instanceof NumberItem) && !(item instanceof StringItem) && !(item instanceof DateTimeItem)) {
+ if (!(item instanceof NumberItem) && !(item instanceof StringItem)
+ && !(item instanceof DateTimeItem)) {
throw new BindingConfigParseException(
"item '"
+ item.getName()
@@ -84,8 +85,7 @@ public String getDSMRItemID(String itemName) {
*
* The binding configuration consists only of the OBIS item.
*
- * Binding configuration for an openHAB Item looks like:
- * dsmr=""
+ * Binding configuration for an openHAB Item looks like: dsmr=""
*
* @author M. Volaart
* @since 1.7.0
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRMeter.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRMeter.java
index c98abeb10a8..e1ff2f87fe9 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRMeter.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRMeter.java
@@ -11,7 +11,7 @@
/**
* DSMR Meter represents a meter for this binding.
*
- * The main Electricity meter {@link DSMRMeterType}.ELECTRICTY is available
+ * The main Electricity meter {@link DSMRMeterType}.ELECTRICTY is available
* implicit and an instance of this class for this meter is not necessary.
*
* @author M. Volaart
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRMeterType.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRMeterType.java
index bcd6800cba7..5b7639d58c9 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRMeterType.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRMeterType.java
@@ -30,7 +30,7 @@ public enum DSMRMeterType {
/** Generic meter (generic.channel) */
GENERIC("generic.channel"),
/** Slave electricity meter (slaveelectricity.channel) */
- SLAVE_ELECTRICITY("slavelectricity.channel");
+ SLAVE_ELECTRICITY("slaveelectricity.channel");
/** Channel configuration key for openhab.cfg */
public final String channelConfigKey;
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRPort.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRPort.java
index 3172a78ad62..d44e4370a35 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRPort.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRPort.java
@@ -8,10 +8,9 @@
*/
package org.openhab.binding.dsmr.internal;
-import java.io.BufferedReader;
+import java.io.BufferedInputStream;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
@@ -21,7 +20,7 @@
import gnu.io.UnsupportedCommOperationException;
import org.openhab.binding.dsmr.internal.messages.OBISMessage;
-import org.openhab.binding.dsmr.internal.messages.OBISMsgFactory;
+import org.openhab.binding.dsmr.internal.p1telegram.P1TelegramParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -67,30 +66,35 @@
* @since 1.7.0
*/
public class DSMRPort {
- /* Internal state based on DSMR specification */
- private enum ReadState {
- WAIT_FOR_START, IDENTIFICATION, DATA, END
- };
-
/* logger */
private static final Logger logger = LoggerFactory
.getLogger(DSMRPort.class);
+ private enum PortState {
+ CLOSED, AUTO_DETECT, OPENED;
+ }
+
+ private enum PortSpeed {
+ LOW_SPEED, HIGH_SPEED
+ }
+
/* private object variables */
private final String portName;
- private final DSMRVersion version;
- private final int timeoutMSec;
+ private final int readTimeoutMSec;
+ private final int autoDetectTimeoutMSec;
+ private long autoDetectTS;
/* serial port resources */
private SerialPort serialPort;
- private BufferedReader reader;
+ private BufferedInputStream bis;
+ private byte[] buffer = new byte[1024]; // 1K
/* state variables */
- private ReadState readerState;
- private boolean isOpen = false;
+ private PortState portState;
+ private PortSpeed portSpeed;
/* helpers */
- private OBISMsgFactory factory;
+ private P1TelegramParser p1Parser;
/*
* The portLock is used for the shared data used when opening and closing
@@ -106,20 +110,23 @@ private enum ReadState {
*
* @param portName
* Device identifier of the post (e.g. /dev/ttyUSB0)
- * @param version
- * Version of the DSMR Specification. See {@link DSMRVersion}
- * @param dsmrMeters
- * List of available {@link DSMRMeter} in the binding
- * @param timeoutMSec
+ * @param p1Parser
+ * {@link P1TelegramParser}
+ * @param readTimeoutMSec
* communication timeout in milliseconds
+ * @param autoDetectTimeoutMSec
+ * timeout for auto detection in milliseconds (after this period
+ * the Serial Port speed will be changed)
*/
- public DSMRPort(String portName, DSMRVersion version,
- List dsmrMeters, int timeoutMSec) {
+ public DSMRPort(String portName, P1TelegramParser p1Parser,
+ int readTimeoutMSec, int autoDetectTimeoutMSec) {
this.portName = portName;
- this.version = version;
- this.timeoutMSec = timeoutMSec;
+ this.readTimeoutMSec = readTimeoutMSec;
+ this.autoDetectTimeoutMSec = autoDetectTimeoutMSec;
+ this.p1Parser = p1Parser;
- factory = new OBISMsgFactory(version, dsmrMeters);
+ portSpeed = PortSpeed.HIGH_SPEED;
+ portState = PortState.CLOSED;
}
/**
@@ -128,7 +135,7 @@ public DSMRPort(String portName, DSMRVersion version,
* @return true if the DSMRPort is open, false otherwise
*/
public boolean isOpen() {
- return isOpen;
+ return portState != PortState.CLOSED;
}
/**
@@ -138,11 +145,12 @@ public void close() {
synchronized (portLock) {
logger.info("Closing DSMR port");
- isOpen = false;
+ portState = PortState.CLOSED;
+
// Close resources
- if (reader != null) {
+ if (bis != null) {
try {
- reader.close();
+ bis.close();
} catch (IOException ioe) {
logger.debug("Failed to close reader", ioe);
}
@@ -152,7 +160,7 @@ public void close() {
}
// Release resources
- reader = null;
+ bis = null;
serialPort = null;
}
}
@@ -170,67 +178,41 @@ public void close() {
* @return List of {@link OBISMessage} with 0 or more entries
*/
public List read() {
- List messages = new ArrayList();
- long startTime = System.currentTimeMillis();
+ List receivedMessages = new LinkedList();
+
+ handlePortState();
// open port if it is not open
- if (!open()) {
+ if (portState == PortState.CLOSED) {
logger.warn("Could not open DSMRPort, no values will be read");
close();
- return messages;
+ return receivedMessages;
}
- // Initialize readerState
- readerState = ReadState.WAIT_FOR_START;
-
try {
- // wait till we reached the end of the telegram or a timeout
- while (readerState != ReadState.END
- && ((System.currentTimeMillis() - startTime) < (2 * timeoutMSec))) {
- String line = reader.readLine();
- logger.trace(line);
- logger.debug("Reader state: " + readerState);
-
- switch (readerState) {
- case WAIT_FOR_START:
- if (line.startsWith("/")) {
- readerState = ReadState.IDENTIFICATION;
- }
- break;
- case IDENTIFICATION:
- if (line.length() == 0) {
- readerState = ReadState.DATA;
- }
- break;
- case DATA:
- if (line.startsWith("!")) {
- readerState = ReadState.END;
- } else {
- OBISMessage msg = factory.getMessage(line);
- if (msg != null) {
- messages.add(msg);
- }
- }
- break;
- case END:
- break;
- default:
- logger.warn("Unsupported state:" + readerState);
- break;
+ // Read without block
+ int bytesAvailable = bis.available();
+ while (bytesAvailable > 0) {
+ int bytesRead = bis.read(buffer, 0,
+ Math.min(bytesAvailable, buffer.length));
+
+ if (bytesRead > 0) {
+ receivedMessages.addAll(p1Parser.parseData(buffer, 0,
+ bytesRead));
+ } else {
+ logger.debug("Expected bytes " + bytesAvailable
+ + " to read, but " + bytesRead + " bytes were read");
}
- }
- if (readerState != ReadState.END) {
- logger.error("Reading took too long and is aborted (readingtime: "
- + (System.currentTimeMillis() - startTime) + " ms)");
+ bytesAvailable = bis.available();
}
} catch (IOException ioe) {
/*
* Read is interrupted. This can be due to a broken connection or
* closing the port
*/
- if (!isOpen) {
+ if (portState == PortState.CLOSED) {
// Closing on purpose
logger.info("Read aborted: DSMRPort is closed");
} else {
@@ -242,7 +224,7 @@ public List read() {
close();
}
} catch (NullPointerException npe) {
- if (!isOpen) {
+ if (portState == PortState.CLOSED) {
// Port was closed
logger.info("Read aborted: DSMRPort is closed");
} else {
@@ -252,8 +234,56 @@ public List read() {
}
}
- // Return all received messages
- return messages;
+ if (portState == PortState.AUTO_DETECT && receivedMessages.size() > 0) {
+ portState = PortState.OPENED;
+ }
+ return receivedMessages;
+ }
+
+ /**
+ * Checks the current port state and initiate actions based on it.
+ *
+ * - CLOSED --> Port will be opened
+ *
- AUTO_DETECT --> Auto detect period will be evaluated
+ *
- OPENED --> Nothing has to be done
+ *
+ */
+ private void handlePortState() {
+ switch (portState) {
+ case CLOSED:
+ if (open()) {
+ portState = PortState.AUTO_DETECT;
+ autoDetectTS = System.currentTimeMillis();
+ }
+ break;
+ case AUTO_DETECT:
+ if ((System.currentTimeMillis() - autoDetectTS) > autoDetectTimeoutMSec) {
+ switchPortSpeed();
+ close();
+ if (open()) {
+ portState = PortState.AUTO_DETECT;
+ autoDetectTS = System.currentTimeMillis();
+ }
+ }
+ break;
+ case OPENED:
+ /* do nothing */
+ break;
+ }
+ }
+
+ /**
+ * Switch the Serial Port speed (LOW --> HIGH and vice versa).
+ */
+ private void switchPortSpeed() {
+ switch (portSpeed) {
+ case HIGH_SPEED:
+ portSpeed = PortSpeed.LOW_SPEED;
+ break;
+ case LOW_SPEED:
+ portSpeed = PortSpeed.HIGH_SPEED;
+ break;
+ }
}
/**
@@ -276,7 +306,7 @@ public List read() {
private boolean open() {
synchronized (portLock) {
// Sanity check
- if (isOpen) {
+ if (portState != PortState.CLOSED) {
return true;
}
@@ -287,34 +317,30 @@ private boolean open() {
.getPortIdentifier(portName);
logger.debug("Opening CommPortIdentifier");
CommPort commPort = portIdentifier.open(
- "org.openhab.binding.dsmr", 2000);
+ "org.openhab.binding.dsmr", readTimeoutMSec);
logger.debug("Configure serial port");
serialPort = (SerialPort) commPort;
serialPort.enableReceiveThreshold(1);
- serialPort.enableReceiveTimeout(timeoutMSec);
-
- // Configure Serial Port based on specified DSMR version
- logger.debug("Configure serial port based on version "
- + version);
- switch (version) {
- case V21:
- case V22:
- case V30:
+ serialPort.enableReceiveTimeout(readTimeoutMSec);
+
+ // Configure Serial Port based on specified port speed
+ logger.debug("Configure serial port speed " + portSpeed);
+ switch (portSpeed) {
+ case LOW_SPEED:
serialPort.setSerialPortParams(9600, SerialPort.DATABITS_7,
SerialPort.STOPBITS_1, SerialPort.PARITY_EVEN);
serialPort.setDTR(false);
serialPort.setRTS(true);
break;
- case V40:
- case V404:
+ case HIGH_SPEED:
serialPort.setSerialPortParams(115200,
SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
break;
default:
- logger.error("Invalid version, closing port");
+ logger.error("Invalid speed, closing port");
return false;
}
@@ -335,8 +361,7 @@ private boolean open() {
// SerialPort is ready, open the reader
logger.info("SerialPort opened successful");
try {
- reader = new BufferedReader(new InputStreamReader(
- serialPort.getInputStream()));
+ bis = new BufferedInputStream(serialPort.getInputStream());
} catch (IOException ioe) {
logger.error(
"Failed to get inputstream for serialPort. Closing port",
@@ -346,9 +371,7 @@ private boolean open() {
}
logger.info("DSMR Port opened successful");
- isOpen = true;
-
- return isOpen;
+ return true;
}
}
}
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRVersion.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRVersion.java
deleted file mode 100644
index 226de29024f..00000000000
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRVersion.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * Copyright (c) 2010-2015, 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.dsmr.internal;
-
-/**
- * The DSMRVersion specifies the DSMR specification version
- *
- * The following versions are special versions:
- *
- *
- * - NONE - This can be used for filtering purposes
- *
- ALL - This can be used to identify all versions
- *
- * This enumeration also has some convenience arrays for grouping versions
- *
- * @author M. Volaart
- * @since 1.7.0
- */
-public enum DSMRVersion {
- /** Special version for filtering purposes */
- NONE("None"),
- /** Special version that indicates all versions */
- ALL("All"),
- /** DSMR Specification v2.1 */
- V21("v2.1"),
- /** DSMR Specification v2.2 */
- V22("v2.2"),
- /** DSMR Specification v3.0 */
- V30("v3.0"),
- /** DSMR Specification v4.0 */
- V40("v4.0"),
- /** DSMR Specification v4.04 */
- V404("v4.04");
-
- /** String representation of the DSMR version */
- public final String version;
-
- /**
- * Construct a new Enum type
- *
- * @param version
- * String representation of the Enum
- */
- private DSMRVersion(String version) {
- this.version = version;
- }
-
- /** Compatibility for all versions */
- public static final DSMRVersion[] ALL_VERSIONS = new DSMRVersion[] { V21,
- V22, V30, V40, V404 };
- /** Compatibility for versions v2.1 and later */
- public static final DSMRVersion[] V21_UP = new DSMRVersion[] { V21, V22,
- V30, V40, V404 };
- /** Compatibility for versions v2.2 and later */
- public static final DSMRVersion[] V22_UP = new DSMRVersion[] { V22, V30,
- V40, V404 };
- /** Compatibility for versions v3.0 and later */
- public static final DSMRVersion[] V30_UP = new DSMRVersion[] { V30, V40,
- V404 };
- /** Compatibility for versions v4.0 and later */
- public static final DSMRVersion[] V40_UP = new DSMRVersion[] { V40 };
- /** Compatibility for versions v4.04 and later */
- public static final DSMRVersion[] V404_UP = new DSMRVersion[] { V404 };
- /** Compatibility for all v2.x versions */
- public static final DSMRVersion[] V2_VERSIONS = new DSMRVersion[] { V21,
- V22 };
- /** Compatibility for all v3.x versions */
- public static final DSMRVersion[] V3_VERSIONS = new DSMRVersion[] { V30 };
- /** Compatibility for all v4.x versions */
- public static final DSMRVersion[] V4_VERSIONS = new DSMRVersion[] { V40,
- V404 };
-
- /**
- * Returns the DSMRVersion object for the specified version. If the version
- * is not found the NONE version is returned
- *
- * @param version
- * String containing the version
- * @return corresponding {@link DMSRVersion} or NONE if a unknown version is
- * specified
- */
- public static DSMRVersion getDSMRVersion(String version) {
- for (DSMRVersion t : DSMRVersion.values()) {
- if (t.version.equalsIgnoreCase(version)) {
- return t;
- }
- }
- return NONE;
- }
-}
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemFloat.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemFloat.java
index 2143b393945..5d1d61200cf 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemFloat.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemFloat.java
@@ -46,7 +46,8 @@ protected DecimalType parse(String cosemValue) throws ParseException {
try {
return new DecimalType(Float.parseFloat(cosemValue));
} catch (NumberFormatException nfe) {
- throw new ParseException("Failed to parse value " + value + " as float", 0);
+ throw new ParseException("Failed to parse value " + value
+ + " as float", 0);
}
}
}
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemString.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemString.java
index 2fae2a3cd54..9bc00b588e6 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemString.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemString.java
@@ -32,7 +32,8 @@ public CosemString(String unit, String bindingSuffix) {
}
/**
- * Parses a String value (that represents an integer) to an openHAB StringType
+ * Parses a String value (that represents an integer) to an openHAB
+ * StringType
*
* @param cosemValue
* the value to parse
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemValueDescriptor.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemValueDescriptor.java
index 18120009718..c9d24a7e2bd 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemValueDescriptor.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/cosem/CosemValueDescriptor.java
@@ -47,6 +47,7 @@ public CosemValueDescriptor(
/**
* Returns the class of the CosemValue
+ *
* @return the class of the CosemValue
*/
public Class extends CosemValue extends State>> getCosemValueClass() {
@@ -55,6 +56,7 @@ public Class extends CosemValue extends State>> getCosemValueClass() {
/**
* Returns the unit
+ *
* @return the unit
*/
public String getUnit() {
@@ -62,7 +64,7 @@ public String getUnit() {
}
/**
- * Returns the DSMR item id
+ * Returns the DSMR item id
*
* @return the DSMR item id
*/
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISIdentifier.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISIdentifier.java
new file mode 100644
index 00000000000..2087b949c9e
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISIdentifier.java
@@ -0,0 +1,250 @@
+/**
+ * Copyright (c) 2010-2015, 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.dsmr.internal.messages;
+
+import java.text.ParseException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class representing an OBISIdentifier
+ *
+ * @author M. Volaart
+ * @since 1.7.0
+ */
+public class OBISIdentifier implements Comparable {
+ /* String representing a.b.c.d.e.f OBIS ID */
+ private static final String OBISID_REGEX = "((\\d+)\\-)?((\\d+):)?((\\d+)\\.)(\\d+)(\\.(\\d+))?(\\*(\\d+))?";
+
+ /* OBIS ID pattern */
+ private static final Pattern obisIdPattern = Pattern.compile(OBISID_REGEX);
+
+ /* the six individual group values of the OBIS ID */
+ private Integer groupA;
+ private Integer groupB;
+ private Integer groupC;
+ private Integer groupD;
+ private Integer groupE;
+ private Integer groupF;
+
+ /**
+ * Constructs a new OBIS Identifier (A.B.C.D.E.F)
+ *
+ * @param groupA
+ * A value
+ * @param groupB
+ * B value
+ * @param groupC
+ * C value
+ * @param groupD
+ * D value
+ * @param groupE
+ * E value
+ * @param groupF
+ * F value
+ */
+ public OBISIdentifier(Integer groupA, Integer groupB, Integer groupC,
+ Integer groupD, Integer groupE, Integer groupF) {
+ this.groupA = groupA;
+ this.groupB = groupB;
+ this.groupC = groupC;
+ this.groupD = groupD;
+ this.groupE = groupE;
+ this.groupF = groupF;
+ }
+
+ /**
+ * Creates a new OBISIdentifier of the specified String
+ *
+ * @param obisIDString
+ * the OBIS String ID
+ * @throws ParseException
+ * if obisIDString is not a valid OBIS Identifier
+ */
+ public OBISIdentifier(String obisIDString) throws ParseException {
+ Matcher m = obisIdPattern.matcher(obisIDString);
+
+ if (m.matches()) {
+ // Optional value A
+ if (m.group(2) != null) {
+ this.groupA = Integer.parseInt(m.group(2));
+ }
+
+ // Optional value B
+ if (m.group(4) != null) {
+ this.groupB = Integer.parseInt(m.group(4));
+ }
+
+ // Required value C & D
+ this.groupC = Integer.parseInt(m.group(6));
+ this.groupD = Integer.parseInt(m.group(7));
+
+ // Optional value E
+ if (m.group(9) != null) {
+ this.groupE = Integer.parseInt(m.group(9));
+ }
+
+ // Optional value F
+ if (m.group(11) != null) {
+ this.groupF = Integer.parseInt(m.group(11));
+ }
+ } else {
+ throw new ParseException("Invalis OBIS identifier:" + obisIDString,
+ 0);
+ }
+ }
+
+ /**
+ * @return the groupA
+ */
+ public Integer getGroupA() {
+ return groupA;
+ }
+
+ /**
+ * @return the groupB
+ */
+ public Integer getGroupB() {
+ return groupB;
+ }
+
+ /**
+ * @return the groupC
+ */
+ public Integer getGroupC() {
+ return groupC;
+ }
+
+ /**
+ * @return the groupD
+ */
+ public Integer getGroupD() {
+ return groupD;
+ }
+
+ /**
+ * @return the groupE
+ */
+ public Integer getGroupE() {
+ return groupE;
+ }
+
+ /**
+ * @return the groupF
+ */
+ public Integer getGroupF() {
+ return groupF;
+ }
+
+ @Override
+ public String toString() {
+ return (groupA != null ? (groupA + "-") : "")
+ + (groupB != null ? (groupB + ":") : "") + groupC + "."
+ + groupD + (groupE != null ? ("." + groupE) : "")
+ + (groupF != null ? ("*" + groupF) : "");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ OBISIdentifier o;
+ if (other != null && other instanceof OBISIdentifier) {
+ o = (OBISIdentifier) other;
+ } else {
+ return false;
+ }
+ boolean result = true;
+
+ if (groupA != null && o.groupA != null) {
+ result &= (groupA.equals(o.groupA));
+ }
+ if (groupB != null && o.groupB != null) {
+ result &= (groupB.equals(o.groupB));
+ }
+ result &= (groupC.equals(o.groupC));
+ result &= (groupD.equals(o.groupD));
+ if (groupE != null && o.groupE != null) {
+ result &= (groupE.equals(o.groupE));
+ }
+ if (groupF != null && o.groupF != null) {
+ result &= (groupF.equals(o.groupF));
+ }
+
+ return result;
+ }
+
+ @Override
+ public int compareTo(OBISIdentifier o) {
+ if (o == null) {
+ throw new NullPointerException("Compared OBISIdentifier is null");
+ }
+ if (groupA != o.groupA) {
+ if (groupA == null) {
+ return 1;
+ } else if (o.groupA == null) {
+ return -1;
+ } else {
+ return groupA.compareTo(o.groupA);
+ }
+ }
+ if (groupB != o.groupB) {
+ if (groupB == null) {
+ return 1;
+ } else if (o.groupB == null) {
+ return -1;
+ } else {
+ return groupB.compareTo(o.groupB);
+ }
+ }
+ if (groupC != o.groupC) {
+ return groupC.compareTo(o.groupC);
+ }
+ if (groupD != o.groupD) {
+ return groupD.compareTo(o.groupD);
+ }
+ if (groupE != o.groupE) {
+ if (groupE == null) {
+ return 1;
+ } else if (o.groupE == null) {
+ return -1;
+ } else {
+ return groupE.compareTo(o.groupE);
+ }
+ }
+ if (groupF != o.groupF) {
+ if (groupF == null) {
+ return 1;
+ } else if (o.groupF == null) {
+ return -1;
+ } else {
+ return groupF.compareTo(o.groupF);
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return ((groupA != null) ? groupA.hashCode() : 0)
+ + ((groupB != null) ? groupB.hashCode() : 0)
+ + groupC.hashCode() + groupD.hashCode()
+ + ((groupE != null) ? groupE.hashCode() : 0)
+ + ((groupF != null) ? groupF.hashCode() : 0);
+ }
+
+ /**
+ * Returns an reduced OBIS Identifier. This means group F is set to null
+ * (.i.e. not applicable)
+ *
+ * @return reduced OBIS Identifer
+ */
+ public OBISIdentifier getReducedOBISIdentifier() {
+ return new OBISIdentifier(groupA, groupB, groupC, groupD, groupE, null);
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMessage.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMessage.java
index f92af7a2756..2cc6a745bbd 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMessage.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMessage.java
@@ -34,6 +34,11 @@ public class OBISMessage {
private static final Logger logger = LoggerFactory
.getLogger(OBISMessage.class);
+ // Identifier of the first power failure date element
+ public static final int FIRST_POWER_FAILURE_DATE = 2;
+ // Identifier of the first power failure duration element
+ public static final int FIRST_POWER_FAILURE_DURATION = 3;
+
// OBIS Message Type
private final OBISMsgType msgType;
@@ -98,37 +103,44 @@ public void parseCosemValues(List cosemStringValues)
+ ", Needed items:" + msgType.cosemValueDescriptors.size());
/*
- * It is not necessarily a problem if 'Needed items' > 'Received items'.
- * Since some items have a dynamic number of values (e.g. Power Failure Log).
- *
- * Since the minority of the messages has such features, differences
- * between received and needed could indicate problems
+ * It is not necessarily a problem if 'Needed items' > 'Received items'.
+ * Since some items have a dynamic number of values (e.g. Power Failure
+ * Log).
+ *
+ * Since the minority of the messages has such features, differences
+ * between received and needed could indicate problems
*/
if (cosemStringValues.size() <= msgType.cosemValueDescriptors.size()) {
for (int i = 0; i < cosemStringValues.size(); i++) {
-
- CosemValue extends State> cosemValue = getCosemValue(msgType.cosemValueDescriptors.get(i));
+
+ CosemValue extends State> cosemValue = getCosemValue(msgType.cosemValueDescriptors
+ .get(i));
if (cosemValue != null) {
cosemValue.setValue(cosemStringValues.get(i));
cosemValues.add(cosemValue);
} else {
- logger.error("Failed to parse:" + cosemStringValues.get(i), " for OBISMsgType:" + msgType);
+ logger.error("Failed to parse:" + cosemStringValues.get(i),
+ " for OBISMsgType:" + msgType);
}
}
} else {
- throw new ParseException("Received items:" + cosemStringValues.size()
- + ", Needed items:" + msgType.cosemValueDescriptors.size(), 0);
+ throw new ParseException("Received items:"
+ + cosemStringValues.size() + ", Needed items:"
+ + msgType.cosemValueDescriptors.size(), 0);
}
-
+
/*
* Here we do a post processing on the values
*/
- switch(msgType) {
- case EMETER_POWER_FAILURE_LOG: postProcessKaifaE0003(); break;
- default: break;
+ switch (msgType) {
+ case EMETER_POWER_FAILURE_LOG:
+ postProcessKaifaE0003();
+ break;
+ default:
+ break;
}
}
-
+
/**
* Creates an empty CosemValue object
*
@@ -137,9 +149,10 @@ public void parseCosemValues(List cosemStringValues)
* @return the instantiated CosemValue based on the specified
* CosemValueDescriptor
*/
- private CosemValue extends State> getCosemValue(CosemValueDescriptor cosemValueDescriptor) {
- Class extends CosemValue extends State>> cosemValueClass =
- cosemValueDescriptor.getCosemValueClass();
+ private CosemValue extends State> getCosemValue(
+ CosemValueDescriptor cosemValueDescriptor) {
+ Class extends CosemValue extends State>> cosemValueClass = cosemValueDescriptor
+ .getCosemValueClass();
String unit = cosemValueDescriptor.getUnit();
String dsmrItemId = cosemValueDescriptor.getDsmrItemId();
@@ -163,18 +176,36 @@ private CosemValue extends State> getCosemValue(CosemValueDescriptor cosemValu
*/
private void postProcessKaifaE0003() {
logger.debug("postProcessKaifaE0003");
+
+ /*
+ * The list of cosemValues for this OBIS Message is:
+ * - [0] Number of entries in the list
+ * - [1] Cosem Identifier
+ * - [2] power failure date entry 1 [Optional]
+ * - [3] power failure duration entry 1 [Optional]
+ * - [entry n * 2] power failure date entry n
+ * - [entry n * 2 + 1] power failure duration entry n
+ */
- CosemDate powerFailureDate = (CosemDate)cosemValues.get(1);
- CosemInteger powerFailureDuration = (CosemInteger)cosemValues.get(2);
-
- Calendar epoch = Calendar.getInstance();
- epoch.setTime(new Date(0));
-
- if(powerFailureDate.getValue().getCalendar().before(epoch) &&
- powerFailureDuration.getValue().intValue() == Integer.MAX_VALUE) {
- logger.debug("Filter invalid power failure entry");
- cosemValues.remove(2); // powerFailureDuration
- cosemValues.remove(1); // powerFailureDate
- }
+ // First check of there is at least one entry of a power failure present
+ // (i.e. date at idx 2 and duration at idx 3)
+ if (cosemValues.size() > FIRST_POWER_FAILURE_DURATION) {
+ CosemDate powerFailureDate = (CosemDate) cosemValues
+ .get(FIRST_POWER_FAILURE_DATE);
+ CosemInteger powerFailureDuration = (CosemInteger) cosemValues
+ .get(FIRST_POWER_FAILURE_DURATION);
+
+ Calendar epoch = Calendar.getInstance();
+ epoch.setTime(new Date(0));
+
+ // Check if the first entry it as epoc and has a 2^32-1 value
+ // If so, filter this value, since it has no added value
+ if (powerFailureDate.getValue().getCalendar().before(epoch)
+ && powerFailureDuration.getValue().intValue() == Integer.MAX_VALUE) {
+ logger.debug("Filter invalid power failure entry");
+ cosemValues.remove(FIRST_POWER_FAILURE_DURATION);
+ cosemValues.remove(FIRST_POWER_FAILURE_DATE);
+ }
+ }
}
}
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMsgFactory.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMsgFactory.java
index 559850f4524..89b65861103 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMsgFactory.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMsgFactory.java
@@ -9,16 +9,12 @@
package org.openhab.binding.dsmr.internal.messages;
import java.text.ParseException;
-import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
import org.openhab.binding.dsmr.internal.DSMRMeter;
import org.openhab.binding.dsmr.internal.DSMRMeterType;
-import org.openhab.binding.dsmr.internal.DSMRVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,159 +29,129 @@ public class OBISMsgFactory {
private static final Logger logger = LoggerFactory
.getLogger(OBISMsgFactory.class);
- /* Regular expression for OBIS strings */
- private static final String OBIS_REGEX = "^(\\d{1,3}-\\d{1,3}:\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})(\\(.*\\))+$";
- private static final String OBIS_VALUE_REGEX = "\\(([^\\(\\)]*)\\)";
- private static final String OBIS_CONFIGURABLE_CHANNEL_PATTERN = "(\\d+)-(-1):(\\d+)\\.(\\d+)\\.(\\d+)";
-
- /* Pattern instances */
- private final Pattern obisPattern;
- private final Pattern obisValuePattern;
- private final Pattern obisChannelPattern;
-
/* internal lookup cache */
- private final HashMap obisLookupTable;
+ private final HashMap> obisLookupTable;
/**
- * Creates a new OBISMsgFactory for the specified DSMRVersion
+ * Creates a new OBISMsgFactory
*
- * @param version
- * {@link DMSRVersion} to use for handling input data
* @param dsmrMeters
* available DSMR meters (see {@link DSMRMeter}) in the binding
*/
- public OBISMsgFactory(DSMRVersion version, List dsmrMeters) {
- obisPattern = Pattern.compile(OBIS_REGEX);
- obisValuePattern = Pattern.compile(OBIS_VALUE_REGEX);
- obisChannelPattern = Pattern.compile(OBIS_CONFIGURABLE_CHANNEL_PATTERN);
-
+ public OBISMsgFactory(List dsmrMeters) {
/*
- * Fill a lookup table with OBIS messages belonging to the specified
- * DSMR version and channel - MeterType mapping
+ * Fill a lookup table with OBIS message types based on the specified
+ * channel - MeterType mapping
*/
- obisLookupTable = new HashMap();
+ obisLookupTable = new HashMap>();
// Create a convenience lookup table for a channel based on meter type
Map meterChannelMapping = new HashMap();
for (DSMRMeter meter : dsmrMeters) {
meterChannelMapping.put(meter.getMeterType(), meter.getChannel());
}
- fillLookupTable(version, meterChannelMapping);
+ fillLookupTable(meterChannelMapping);
}
/**
* Return OBISMessage from specified string or null if string couldn't be
* parsed correctly or no corresponding OBISMessage was found
*
- * @param obisStr
- * a single raw OBIS message string received from the DSMR meter
+ * @param obisIdString
+ * String containing the OBIS message identifier
+ * @param cosemStringValues
+ * LinkedList of String containing Cosem values
* @return OBISMessage or null if parsing failed
*/
- public OBISMessage getMessage(String obisStr) {
- OBISMessage msg = null;
-
- if (obisStr != null) {
- Matcher m = obisPattern.matcher(obisStr);
-
- if (m.matches()) {
- logger.debug("Received valid OBIS String:" + obisStr);
+ public OBISMessage getMessage(String obisIdString,
+ LinkedList cosemStringValues) {
+ OBISIdentifier obisId = null;
+ OBISIdentifier reducedObisId = null;
+
+ try {
+ obisId = new OBISIdentifier(obisIdString);
+ reducedObisId = obisId.getReducedOBISIdentifier();
+ } catch (ParseException pe) {
+ logger.error("Received invalid OBIS identifer:" + obisIdString);
+
+ return null;
+ }
- List cosemStringValues = new ArrayList();
+ logger.debug("Received obisIdString " + obisIdString + ", obisId:"
+ + obisId + ", values:" + cosemStringValues);
- // Get identifier and all the values as a single String
- OBISMsgType msgType = getOBISMsgType(m.group(1));
+ if (obisLookupTable.containsKey(reducedObisId)) {
+ List compatibleMsgTypes = obisLookupTable.get(reducedObisId);
- if (msgType != OBISMsgType.UNKNOWN) {
- // Get the individual COSEM String values
- String allCosemStringValues = m.group(2);
- Matcher valueMatcher = obisValuePattern
- .matcher(allCosemStringValues);
+ OBISMessage msg = null;
- while (valueMatcher.find()) {
- cosemStringValues.add(valueMatcher.group(1));
- }
+ logger.debug("Found " + compatibleMsgTypes.size()
+ + " compatible message type(s)");
+ for (OBISMsgType msgType : compatibleMsgTypes) {
+ msg = new OBISMessage(msgType);
- logger.debug("OBIS message type:" + msgType + ", values:"
- + cosemStringValues);
+ try {
+ logger.debug("Parse values for OBIS Message type:"
+ + msgType);
- msg = new OBISMessage(msgType);
+ msg.parseCosemValues(cosemStringValues);
- try {
- msg.parseCosemValues(cosemStringValues);
- } catch (ParseException pe) {
- logger.error("Failed to parse " + obisStr, pe);
- }
- } else {
- logger.warn("Received OBIS unknown message:" + obisStr);
+ return msg;
+ } catch (ParseException pe) {
+ logger.debug("Failed to parse OBIS identifer " + obisId
+ + ", values:" + cosemStringValues + " for type"
+ + msgType, pe);
}
}
- }
-
- logger.debug("Converted to:" + msg);
-
- return msg;
- }
-
- /**
- * Returns the OBIS message type (See {@link OBISMsgType}) for the specified
- * OBIS reduced identifier
- *
- * @param obisId
- * the OBIS reduced identifier
- * @return the {@link OBISMsgType} or UNKNOWN if the OBIS reduced identifier
- * is unknown
- */
- private OBISMsgType getOBISMsgType(String obisId) {
- if (obisLookupTable.containsKey(obisId)) {
- return obisLookupTable.get(obisId);
+ logger.error("Failed to parse OBIS identifier " + obisId
+ + ", values:" + cosemStringValues);
} else {
- return OBISMsgType.UNKNOWN;
+ logger.warn("Received OBIS unknown message:" + obisId);
}
+ return null;
}
/**
- * This method fills a lookup table in which the applicable OBIS message
- * types are stored based on the {@link DSMRVersion} and mapping channel -
- * {@link DSMRMeterType}
+ * This method fills a lookup table with the OBIS message types based on the
+ * mapping channel - {@link DSMRMeterType}
*
- * DSMR messages can be interpreted ambiguous if the version or mapping is
- * not known. The lookup table makes sure that messages are parsed without
- * ambiguity.
*
- * @param version
- * applicable DSMR version
* @param mapping
* DSMRMeterType - channel mapping
*/
- private void fillLookupTable(DSMRVersion version,
- Map mapping) {
+ private void fillLookupTable(Map mapping) {
for (OBISMsgType t : OBISMsgType.values()) {
- if (t.applicableVersions.contains(version)) {
- Matcher m = obisChannelPattern.matcher(t.obisId);
- if (m.matches()) {
- DSMRMeterType meterType = t.meterType;
- if (mapping.containsKey(meterType)) {
- /*
- * OBIS-identifier contains a variable channel Check if
- * the configuration contains the mapping for this meter
- * type and then make the OBIS-identifier specific
- */
-
- Integer channel = mapping.get(t.meterType);
- logger.debug("Change OBIS-identifier " + t.obisId
- + " for meter " + t.meterType + " on channel "
- + channel);
-
- String obisSpecificIdentifier = m.replaceFirst("$1-"
- + channel + ":$3.$4.$5");
- obisLookupTable.put(obisSpecificIdentifier, t);
- } else {
- logger.debug("Mapping does not contain a channel for "
- + meterType);
- }
+ OBISIdentifier obisId = t.obisId;
+
+ if (obisId.getGroupB() == null) {
+ DSMRMeterType meterType = t.meterType;
+ if (mapping.containsKey(meterType)) {
+ /*
+ * OBIS-identifier contains a variable channel Check if the
+ * configuration contains the mapping for this meter type
+ * and then make the OBIS-identifier specific
+ */
+
+ Integer channel = mapping.get(t.meterType);
+ logger.debug("Change OBIS-identifier " + t.obisId
+ + " for meter " + t.meterType + " on channel "
+ + channel);
+
+ obisId = new OBISIdentifier(obisId.getGroupA(), channel,
+ obisId.getGroupC(), obisId.getGroupD(),
+ obisId.getGroupE(), obisId.getGroupF());
} else {
- obisLookupTable.put(t.obisId, t);
+ logger.debug("Mapping does not contain a channel for "
+ + meterType);
+
+ obisId = null;
+ }
+ }
+ if (obisId != null) {
+ if (!obisLookupTable.containsKey(obisId)) {
+ obisLookupTable.put(obisId, new LinkedList());
}
+ obisLookupTable.get(obisId).add(t);
}
}
}
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMsgType.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMsgType.java
index fc199a0b19d..d7117be3449 100644
--- a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMsgType.java
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/messages/OBISMsgType.java
@@ -11,7 +11,6 @@
import java.util.Arrays;
import java.util.List;
import org.openhab.binding.dsmr.internal.DSMRMeterType;
-import org.openhab.binding.dsmr.internal.DSMRVersion;
import org.openhab.binding.dsmr.internal.cosem.CosemDate;
import org.openhab.binding.dsmr.internal.cosem.CosemFloat;
import org.openhab.binding.dsmr.internal.cosem.CosemInteger;
@@ -26,7 +25,6 @@
*
* - OBIS Identifier (reduced form)
*
- Applicable {@link DSMRMeterType}
- *
- Applicable {@link DSMRVersion}
*
- Description of the values (See {@link CosemValueDescriptor})
*
- Human readable description
*
@@ -35,111 +33,107 @@
* @since 1.7.0
*/
public enum OBISMsgType {
- UNKNOWN("",
+ UNKNOWN(new OBISIdentifier(null, null, -1, -1, null, null),
DSMRMeterType.NA,
- new CosemValueDescriptor(CosemString.class, "", ""),
- DSMRVersion.NONE,
+ new CosemValueDescriptor(CosemString.class, "", ""),
"Unknown OBIS message type"),
/* General messages (DSMR V4) */
- P1_VERSION_OUTPUT_V4("1-3:0.2.8",
+ P1_VERSION_OUTPUT_V4(new OBISIdentifier(1, 3, 0, 2, 8, null),
DSMRMeterType.NA,
new CosemValueDescriptor(CosemString.class, "", "P1VersionOutput"),
- DSMRVersion.V4_VERSIONS,
"Version information for P1 output"),
- P1_TIMESTAMP("0-0:1.0.0",
+ P1_TIMESTAMP(new OBISIdentifier(0, 0, 1, 0, 0, null),
DSMRMeterType.NA,
- new CosemValueDescriptor(CosemDate.class, "", "P1Timestamp"),
- DSMRVersion.V4_VERSIONS,
+ new CosemValueDescriptor(CosemDate.class, "", "P1Timestamp"),
"Timestamp of the P1 output"),
/* Electricity Meter */
- EMETER_EQUIPMENT_IDENTIFIER_V2_X("0-0:42.0.0",
+ EMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(0, 0, 42, 0, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemString.class, "", "eEquipmentId"),
- DSMRVersion.V2_VERSIONS,
"Equipment identifier DSMR v2.x"),
- EMETER_EQUIPMENT_IDENTIFIER("0-0:96.1.1",
+ EMETER_EQUIPMENT_IDENTIFIER_NTA8130(new OBISIdentifier(0, 0, 96, 1, 0, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemString.class, "", "eEquipmentId"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemString.class, "", "eEquipmentId"),
+ "Equipment identifier (NTA8130)"),
+ EMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, 0, 96, 1, 1, null),
+ DSMRMeterType.ELECTRICITY,
+ new CosemValueDescriptor(CosemString.class, "", "eEquipmentId"),
"Equipment identifier"),
- EMETER_DELIVERY_TARIFF1("1-0:1.8.1",
+ EMETER_DELIVERY_TARIFF0(new OBISIdentifier(1, 0, 1, 8, 0, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemFloat.class, "kWh", "eDeliveryTariff1"),
- DSMRVersion.ALL_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "kWh", "eDeliveryTariff0"),
+ "Total meter delivery tariff 0"),
+ EMETER_DELIVERY_TARIFF1(new OBISIdentifier(1, 0, 1, 8, 1, null),
+ DSMRMeterType.ELECTRICITY,
+ new CosemValueDescriptor(CosemFloat.class, "kWh", "eDeliveryTariff1"),
"Total meter delivery tariff 1"),
- EMETER_DELIVERY_TARIFF2("1-0:1.8.2",
+ EMETER_DELIVERY_TARIFF2(new OBISIdentifier(1, 0, 1, 8, 2, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "kWh", "eDeliveryTariff2"),
- DSMRVersion.ALL_VERSIONS,
"Total meter delivery tariff 2"),
- EMETER_PRODUCTION_TARIFF1("1-0:2.8.1",
+ EMETER_PRODUCTION_TARIFF0(new OBISIdentifier(1, 0, 2, 8, 0, null),
+ DSMRMeterType.ELECTRICITY,
+ new CosemValueDescriptor(CosemFloat.class, "kWh", "eProductionTariff0"),
+ "Total meter production tariff 0"),
+ EMETER_PRODUCTION_TARIFF1(new OBISIdentifier(1, 0, 2, 8, 1, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemFloat.class, "kWh", "eProductionTariff1"),
- DSMRVersion.ALL_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "kWh", "eProductionTariff1"),
"Total meter production tariff 1"),
- EMETER_PRODUCTION_TARIFF2("1-0:2.8.2",
+ EMETER_PRODUCTION_TARIFF2(new OBISIdentifier(1, 0, 2, 8, 2, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "kWh", "eProductionTariff2"),
- DSMRVersion.ALL_VERSIONS,
"Total meter production tariff 2"),
- EMETER_TARIFF_INDICATOR("0-0:96.14.0",
+ EMETER_TARIFF_INDICATOR(new OBISIdentifier(0, 0, 96, 14, 0, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemString.class, "", "eTariffIndicator"),
- DSMRVersion.ALL_VERSIONS,
+ new CosemValueDescriptor(CosemString.class, "", "eTariffIndicator"),
"Tariff indicator"),
- EMETER_ACTUAL_DELIVERY("1-0:1.7.0",
+ EMETER_ACTIVE_IMPORT_POWER(new OBISIdentifier(1, 0, 15, 7, 0, null),
+ DSMRMeterType.ELECTRICITY,
+ new CosemValueDescriptor(CosemFloat.class, "W", "eActualDelivery"),
+ "Aggregrate active import power"),
+ EMETER_ACTUAL_DELIVERY(new OBISIdentifier(1, 0, 1, 7, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "kW", "eActualDelivery"),
- DSMRVersion.ALL_VERSIONS,
"Actual power delivery"),
- EMETER_ACTUAL_PRODUCTION("1-0:2.7.0",
+ EMETER_ACTUAL_PRODUCTION(new OBISIdentifier(1, 0, 2, 7, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "kW", "eActualProduction"),
- DSMRVersion.ALL_VERSIONS,
"Actual power production"),
- EMETER_TRESHOLD_V2_1("1-0:17.0.0",
+ EMETER_TRESHOLD_V2_1(new OBISIdentifier(1, 0, 17, 0, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemInteger.class, "A", "eTreshold"),
- new DSMRVersion[] { DSMRVersion.V21 },
"The actual threshold Electricity in A (DSMR v2.1)"),
- EMETER_TRESHOLD("0-0:17.0.0",
+ EMETER_TRESHOLD(new OBISIdentifier(0, 0, 17, 0, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemInteger.class, "A", "eTreshold"),
- new DSMRVersion[] { DSMRVersion.V22, DSMRVersion.V30 },
"The actual threshold Electricity in A (DSMR v2.2 / v3)"),
- EMETER_TRESHOLD_V4("0-0:17.0.0",
+ EMETER_TRESHOLD_V4(new OBISIdentifier(0, 0, 17, 0, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "kW", "eTreshold"),
- DSMRVersion.V4_VERSIONS,
"The actual threshold Electricity in kW (DSMR v4)"),
- EMETER_SWITCH_POSITION_V2_1("1-0:96.3.10",
+ EMETER_SWITCH_POSITION_V2_1(new OBISIdentifier(1, 0, 96, 3, 10, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemInteger.class, "", "eSwitchPosition"),
- new DSMRVersion[] { DSMRVersion.V21 },
"Actual switch position Electricity DSMR v2.1"),
- EMETER_SWITCH_POSITION_V2_2("0-0:24.4.0",
+ EMETER_SWITCH_POSITION_V2_2(new OBISIdentifier(0, 0, 24, 4, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemInteger.class, "", "eSwitchPosition"),
- new DSMRVersion[] { DSMRVersion.V22 },
"Actual switch position Electricity DSMR v2.2"),
- EMETER_SWITCH_POSITION("0-0:96.3.10",
+ EMETER_SWITCH_POSITION(new OBISIdentifier(0, 0, 96, 3, 10, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemInteger.class, "", "eSwitchPosition"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemInteger.class, "", "eSwitchPosition"),
"Actual switch position Electricity"),
- EMETER_POWER_FAILURES("0-0:96.7.21",
+ EMETER_POWER_FAILURES(new OBISIdentifier(0, 0, 96, 7, 21, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "", "ePowerFailures"),
- DSMRVersion.V4_VERSIONS,
"Number of Power failures"),
- EMETER_LONG_POWER_FAILURES("0-0:96.7.9",
+ EMETER_LONG_POWER_FAILURES(new OBISIdentifier(0, 0, 96, 7, 9, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "", "eLongPowerFailures"),
- DSMRVersion.V4_VERSIONS,
"Number of Long Power failures"),
- EMETER_POWER_FAILURE_LOG("1-0:99.97.0",
+ EMETER_POWER_FAILURE_LOG(new OBISIdentifier(1, 0, 99, 97, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor[] {
new CosemValueDescriptor(CosemInteger.class, "", "eNumberOfLogEntries"),
@@ -164,121 +158,98 @@ public enum OBISMsgType {
new CosemValueDescriptor(CosemInteger.class, "s", "eDurationPowerFailure9"),
new CosemValueDescriptor(CosemDate.class, "", "eDatePowerFailure10"),
new CosemValueDescriptor(CosemInteger.class, "s", "eDurationPowerFailure10")},
- DSMRVersion.V4_VERSIONS,
"Power Failure event log"),
- EMETER_VOLTAGE_SAGS_L1("1-0:32.32.0",
+ EMETER_VOLTAGE_SAGS_L1(new OBISIdentifier(1, 0, 32, 32, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "", "eVoltageSagsL1"),
- DSMRVersion.V4_VERSIONS,
"Number of voltage sags L1"),
- EMETER_VOLTAGE_SAGS_L2("1-0:52.32.0",
+ EMETER_VOLTAGE_SAGS_L2(new OBISIdentifier(1, 0, 52, 32, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "", "eVoltageSagsL2"),
- DSMRVersion.V4_VERSIONS,
"Number of voltage sags L2"),
- EMETER_VOLTAGE_SAGS_L3("1-0:72.32.0",
+ EMETER_VOLTAGE_SAGS_L3(new OBISIdentifier(1, 0, 72, 32, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "", "eVoltageSagsL3"),
- DSMRVersion.V4_VERSIONS,
"Number of voltage sags L3"),
- EMETER_VOLTAGE_SWELLS_L1("1-0:32.36.0",
+ EMETER_VOLTAGE_SWELLS_L1(new OBISIdentifier(1, 0, 32, 36, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "", "eVoltageSwellsL1"),
- DSMRVersion.V4_VERSIONS,
"Number of voltage swells L1"),
- EMETER_VOLTAGE_SWELLS_L2("1-0:52.36.0",
+ EMETER_VOLTAGE_SWELLS_L2(new OBISIdentifier(1, 0, 52, 36, 0, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemFloat.class, "", "eVoltageSwellsL2"),
- DSMRVersion.V4_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "", "eVoltageSwellsL2"),
"Number of voltage swells L2"),
- EMETER_VOLTAGE_SWELLS_L3("1-0:72.36.0",
+ EMETER_VOLTAGE_SWELLS_L3(new OBISIdentifier(1, 0, 72, 36, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "", "eVoltageSwellsL3"),
- DSMRVersion.V4_VERSIONS,
"Number of voltage swells L3"),
- EMETER_TEXT_CODE("0-0:96.13.1",
+ EMETER_TEXT_CODE(new OBISIdentifier(0, 0, 96, 13, 1, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemString.class, "", "eTextCode"),
- DSMRVersion.ALL_VERSIONS,
+ new CosemValueDescriptor(CosemString.class, "", "eTextCode"),
"Text message code (8 digits)"),
- EMETER_TEXT_STRING("0-0:96.13.0",
+ EMETER_TEXT_STRING(new OBISIdentifier(0, 0, 96, 13, 0, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemString.class, "", "eTextMessage"),
- DSMRVersion.ALL_VERSIONS,
+ new CosemValueDescriptor(CosemString.class, "", "eTextMessage"),
"Text message"),
- EMETER_INSTANT_CURRENT_L1("1-0:31.7.0",
+ EMETER_INSTANT_CURRENT_L1(new OBISIdentifier(1, 0, 31, 7, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "A", "eInstantCurrentL1"),
- DSMRVersion.V404,
"Instantenous current L1"),
- EMETER_INSTANT_CURRENT_L2("1-0:51.7.0",
+ EMETER_INSTANT_CURRENT_L2(new OBISIdentifier(1, 0, 51, 7, 0, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemFloat.class, "A", "eInstantCurrentL2"),
- DSMRVersion.V404,
+ new CosemValueDescriptor(CosemFloat.class, "A", "eInstantCurrentL2"),
"Instantenous current L2"),
- EMETER_INSTANT_CURRENT_L3("1-0:71.7.0",
+ EMETER_INSTANT_CURRENT_L3(new OBISIdentifier(1, 0, 71, 7, 0, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemFloat.class, "A", "eInstantCurrentL3"),
- DSMRVersion.V404,
+ new CosemValueDescriptor(CosemFloat.class, "A", "eInstantCurrentL3"),
"Instantenous current L3"),
- EMETER_INSTANT_POWER_DELIVERY_L1("1-0:21.7.0",
+ EMETER_INSTANT_POWER_DELIVERY_L1(new OBISIdentifier(1, 0, 21, 7, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "kW", "eInstantPowerDeliveryL1"),
- DSMRVersion.V404,
"Instantenous active power delivery L1"),
- EMETER_INSTANT_POWER_DELIVERY_L2("1-0:41.7.0",
+ EMETER_INSTANT_POWER_DELIVERY_L2(new OBISIdentifier(1, 0, 41, 7, 0, null),
DSMRMeterType.ELECTRICITY,
- new CosemValueDescriptor(CosemFloat.class, "kW", "eInstantPowerDeliveryL2"),
- DSMRVersion.V404,
+ new CosemValueDescriptor(CosemFloat.class, "kW", "eInstantPowerDeliveryL2"),
"Instantenous active power delivery L2"),
- EMETER_INSTANT_POWER_DELIVERY_L3("1-0:61.7.0",
+ EMETER_INSTANT_POWER_DELIVERY_L3(new OBISIdentifier(1, 0, 61, 7, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "kW", "eInstantPowerDeliveryL3"),
- DSMRVersion.V404,
"Instantenous active power delivery L3"),
- EMETER_INSTANT_POWER_PRODUCTION_L1("1-0:22.7.0",
+ EMETER_INSTANT_POWER_PRODUCTION_L1(new OBISIdentifier(1, 0, 22, 7, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "kW", "eInstantPowerProductionL1"),
- DSMRVersion.V404,
"Instantenous active power production L1"),
- EMETER_INSTANT_POWER_PRODUCTION_L2("1-0:42.7.0",
+ EMETER_INSTANT_POWER_PRODUCTION_L2(new OBISIdentifier(1, 0, 42, 7, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "kW", "eInstantPowerProductionL2"),
- DSMRVersion.V404,
"Instantenous active power production L2"),
- EMETER_INSTANT_POWER_PRODUCTION_L3("1-0:62.7.0",
+ EMETER_INSTANT_POWER_PRODUCTION_L3(new OBISIdentifier(1, 0, 62, 7, 0, null),
DSMRMeterType.ELECTRICITY,
new CosemValueDescriptor(CosemFloat.class, "kW", "eInstantPowerProductionL3"),
- DSMRVersion.V404,
"Instantenous active power production L3"),
/* Gas Meter */
- GMETER_DEVICE_TYPE("0--1:24.1.0",
+ GMETER_DEVICE_TYPE(new OBISIdentifier(0, null, 24, 1, 0, null),
DSMRMeterType.GAS,
- new CosemValueDescriptor(CosemString.class, "", "gDeviceType"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemString.class, "", "gDeviceType"),
"Device Type"),
- GMETER_EQUIPMENT_IDENTIFIER_V2_X("7-0:0.0.0",
+ GMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(7, 0, 0, 0, 0, null),
DSMRMeterType.GAS,
new CosemValueDescriptor(CosemString.class, "", "gEquipmentId"),
- DSMRVersion.V2_VERSIONS,
"Equipment identifier"),
- GMETER_EQUIPMENT_IDENTIFIER("0--1:96.1.0",
+ GMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 0, null),
DSMRMeterType.GAS,
- new CosemValueDescriptor(CosemString.class, "", "gEquipmentId"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemString.class, "", "gEquipmentId"),
"Equipment identifier"),
- GMETER_24H_DELIVERY_V2_X("7-0:23.1.0",
+ GMETER_24H_DELIVERY_V2_X(new OBISIdentifier(7, 0, 23, 1, 0, null),
DSMRMeterType.GAS,
new CosemValueDescriptor(CosemString.class, "m3", "gValue"),
- DSMRVersion.V2_VERSIONS,
"Delivery of the past hour(v3.0 and up) or 24 hours (v2.1 / v2.2)"),
- GMETER_24H_DELIVERY_COMPENSATED_V2_X("7-0:23.2.0",
+ GMETER_24H_DELIVERY_COMPENSATED_V2_X(new OBISIdentifier(7, 0, 23, 2, 0, null),
DSMRMeterType.GAS,
new CosemValueDescriptor(CosemString.class, "m3", "gValueCompensated"),
- DSMRVersion.V2_VERSIONS,
"Temperature compensated delivery of the past 24 hours"),
- GMETER_VALUE_V3("0--1:24.3.0",
+ GMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null),
DSMRMeterType.GAS,
new CosemValueDescriptor[]{
new CosemValueDescriptor(CosemDate.class, "", "gValueTS"),
@@ -287,268 +258,178 @@ public enum OBISMsgType {
new CosemValueDescriptor(CosemInteger.class, "", "gNumberOfValues"),
new CosemValueDescriptor(CosemString.class, "", ""),
new CosemValueDescriptor(CosemString.class, "", "gUnit"),
- new CosemValueDescriptor(CosemInteger.class, "", "gValue"),
- new CosemValueDescriptor(CosemInteger.class, "", "gValue2"),
- new CosemValueDescriptor(CosemInteger.class, "", "gValue3"),
- new CosemValueDescriptor(CosemInteger.class, "", "gValue4"),
- new CosemValueDescriptor(CosemInteger.class, "", "gValue5"),
- new CosemValueDescriptor(CosemInteger.class, "", "gValue6"),
- new CosemValueDescriptor(CosemInteger.class, "", "gValue7"),
- new CosemValueDescriptor(CosemInteger.class, "", "gValue8"),
- new CosemValueDescriptor(CosemInteger.class, "", "gValue9"),
- new CosemValueDescriptor(CosemInteger.class, "", "gValue10")},
- DSMRVersion.V3_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "", "gValue"),
+ new CosemValueDescriptor(CosemFloat.class, "", "gValue2"),
+ new CosemValueDescriptor(CosemFloat.class, "", "gValue3"),
+ new CosemValueDescriptor(CosemFloat.class, "", "gValue4"),
+ new CosemValueDescriptor(CosemFloat.class, "", "gValue5"),
+ new CosemValueDescriptor(CosemFloat.class, "", "gValue6"),
+ new CosemValueDescriptor(CosemFloat.class, "", "gValue7"),
+ new CosemValueDescriptor(CosemFloat.class, "", "gValue8"),
+ new CosemValueDescriptor(CosemFloat.class, "", "gValue9"),
+ new CosemValueDescriptor(CosemFloat.class, "", "gValue10")},
"Delivery of the past 24 hours"),
- GMETER_VALUE_V4("0--1:24.2.1",
+ GMETER_VALUE_V4(new OBISIdentifier(0, null, 24, 2, 1, null),
DSMRMeterType.GAS,
new CosemValueDescriptor[]{
new CosemValueDescriptor(CosemDate.class, "", "gValueTS"),
new CosemValueDescriptor(CosemFloat.class, "m3", "gValue")},
- DSMRVersion.V4_VERSIONS,
"Delivery of the past 24 hours"),
- GMETER_VALVE_POSITION_V2_1("7-0:96.3.10",
+ GMETER_VALVE_POSITION_V2_1(new OBISIdentifier(7, 0, 96, 3, 10, null),
DSMRMeterType.GAS,
new CosemValueDescriptor(CosemInteger.class, "", "gValvePosition"),
- DSMRVersion.V2_VERSIONS,
"Valve position"),
- GMETER_VALVE_POSITION_V2_2("7-0:24.4.0",
+ GMETER_VALVE_POSITION_V2_2(new OBISIdentifier(7, 0, 24, 4, 0, null),
DSMRMeterType.GAS,
- new CosemValueDescriptor(CosemInteger.class, "", "gValvePosition"),
- DSMRVersion.V2_VERSIONS,
+ new CosemValueDescriptor(CosemInteger.class, "", "gValvePosition"),
"Valve position"),
- GMETER_VALVE_POSITION("0--1:24.4.0",
+ GMETER_VALVE_POSITION(new OBISIdentifier(0, null, 24, 4, 0, null),
DSMRMeterType.GAS,
- new CosemValueDescriptor(CosemInteger.class, "", "gValvePosition"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemInteger.class, "", "gValvePosition"),
"Valve position"),
/* Heating Meter */
- HMETER_DEVICE_TYPE("0--1:24.1.0",
+ HMETER_DEVICE_TYPE(new OBISIdentifier(0, null, 24, 1, 0, null),
DSMRMeterType.HEATING,
- new CosemValueDescriptor(CosemString.class, "", "hDeviceType"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemString.class, "", "hDeviceType"),
"Device Type"),
- HMETER_EQUIPMENT_IDENTIFIER_V2_X("5-0:0.0.0",
+ HMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(5, 0, 0, 0, 0, null),
DSMRMeterType.HEATING,
- new CosemValueDescriptor(CosemString.class, "", "hEquipmentId"),
- DSMRVersion.V2_VERSIONS,
+ new CosemValueDescriptor(CosemString.class, "", "hEquipmentId"),
"Equipment identifier"),
- HMETER_EQUIPMENT_IDENTIFIER("0--1:96.1.0",
+ HMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 0, null),
DSMRMeterType.HEATING,
- new CosemValueDescriptor(CosemString.class, "", "hEquipmentId"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemString.class, "", "hEquipmentId"),
"Equipment identifier"),
- HMETER_VALUE_V2_X("5-0:1.0.0",
+ HMETER_VALUE_V2_X(new OBISIdentifier(5, 0, 1, 0, 0, null),
DSMRMeterType.HEATING,
- new CosemValueDescriptor(CosemFloat.class, "GJ", "hValue"),
- DSMRVersion.V2_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "GJ", "hValue"),
"Last hour delivery"),
- HMETER_VALUE_HEAT_V3("0--1:24.3.0",
+ HMETER_VALUE_HEAT_V3(new OBISIdentifier(0, null, 24, 3, 0, null),
DSMRMeterType.HEATING,
- new CosemValueDescriptor(CosemFloat.class, "GJ", "hValue"),
- DSMRVersion.V3_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "GJ", "hValue"),
"Last hour delivery"),
- HMETER_VALUE_HEAT_V4("0--1:24.2.1",
+ HMETER_VALUE_HEAT_V4(new OBISIdentifier(0, null, 24, 2, 1, null),
DSMRMeterType.HEATING,
new CosemValueDescriptor[] {
new CosemValueDescriptor(CosemDate.class, "", "hValueTS"),
- new CosemValueDescriptor(CosemFloat.class, "GJ", "hValue")},
- DSMRVersion.V4_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "GJ", "hValue")},
"Last hour delivery"),
- HMETER_VALVE_POSITION("0--1:24.4.0",
+ HMETER_VALVE_POSITION(new OBISIdentifier(0, null, 24, 4, 0, null),
DSMRMeterType.HEATING,
- new CosemValueDescriptor(CosemInteger.class, "", "hValvePosition"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemInteger.class, "", "hValvePosition"),
"Valve position"),
/* Cooling Meter */
- CMETER_DEVICE_TYPE("0--1:24.1.0",
+ CMETER_DEVICE_TYPE(new OBISIdentifier(0, null, 24, 1, 0, null),
DSMRMeterType.COOLING,
- new CosemValueDescriptor(CosemString.class, "", "cDeviceType"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemString.class, "", "cDeviceType"),
"Device Type"),
- CMETER_EQUIPMENT_IDENTIFIER_V2_X("6-0:0.0.0",
+ CMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(6, 0, 0, 0, 0, null),
DSMRMeterType.COOLING,
- new CosemValueDescriptor(CosemString.class, "", "cEquipmentId"),
- DSMRVersion.V2_VERSIONS,
+ new CosemValueDescriptor(CosemString.class, "", "cEquipmentId"),
"Equipment identifier"),
- CMETER_EQUIPMENT_IDENTIFIER("0--1:96.1.0",
+ CMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 0, null),
DSMRMeterType.COOLING,
- new CosemValueDescriptor(CosemString.class, "", "cEquipmentId"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemString.class, "", "cEquipmentId"),
"Equipment identifier"),
- CMETER_VALUE_V2_X("6-0:1.0.0",
+ CMETER_VALUE_V2_X(new OBISIdentifier(6, 0, 1, 0, 0, null),
DSMRMeterType.COOLING,
- new CosemValueDescriptor(CosemFloat.class, "GJ", "cValue"),
- DSMRVersion.V2_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "GJ", "cValue"),
"Value"),
- CMETER_VALUE_COLD_V3("0--1:24.3.1",
+ CMETER_VALUE_COLD_V3(new OBISIdentifier(0, null, 24, 3, 1, null),
DSMRMeterType.COOLING,
- new CosemValueDescriptor(CosemFloat.class, "GJ", "cValue"),
- DSMRVersion.V3_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "GJ", "cValue"),
"Last hour delivery"),
- CMETER_VALUE_COLD_V4("0--1:24.2.1",
+ CMETER_VALUE_COLD_V4(new OBISIdentifier(0, null, 24, 2, 1, null),
DSMRMeterType.COOLING,
new CosemValueDescriptor[]{
new CosemValueDescriptor(CosemDate.class, "", "cValueTS"),
- new CosemValueDescriptor(CosemFloat.class, "GJ", "cValue")},
- DSMRVersion.V4_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "GJ", "cValue")},
"Last hour delivery"),
- CMETER_VALVE_POSITION("0--1:24.4.0",
+ CMETER_VALVE_POSITION(new OBISIdentifier(0, null, 24, 4, 0, null),
DSMRMeterType.COOLING,
- new CosemValueDescriptor(CosemInteger.class, "", "cValvePosition"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemInteger.class, "", "cValvePosition"),
"Valve position"),
/* Water Meter */
- WMETER_DEVICE_TYPE("0--1:24.1.0",
+ WMETER_DEVICE_TYPE(new OBISIdentifier(0, null, 24, 1, 0, null),
DSMRMeterType.WATER,
- new CosemValueDescriptor(CosemString.class, "", "wDeviceType"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemString.class, "", "wDeviceType"),
"Device Type"),
- WMETER_EQUIPMENT_IDENTIFIER_V2_X("8-0:0.0.0",
+ WMETER_EQUIPMENT_IDENTIFIER_V2_X(new OBISIdentifier(8, 0, 0, 0, 0, null),
DSMRMeterType.WATER,
- new CosemValueDescriptor(CosemString.class, "", "wEquipmentId"),
- DSMRVersion.V2_VERSIONS,
+ new CosemValueDescriptor(CosemString.class, "", "wEquipmentId"),
"Equipment identifier"),
- WMETER_EQUIPMENT_IDENTIFIER("0--1:96.1.0",
+ WMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 0, null),
DSMRMeterType.WATER,
- new CosemValueDescriptor(CosemString.class, "", "wEquipmentId"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemString.class, "", "wEquipmentId"),
"Equipment identifier"),
- WMETER_VALUE_V2_X("8-0:1.0.0",
+ WMETER_VALUE_V2_X(new OBISIdentifier(8, 0, 1, 0, 0, null),
DSMRMeterType.WATER,
- new CosemValueDescriptor(CosemFloat.class, "m3", "wValue"),
- DSMRVersion.V2_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "m3", "wValue"),
"Last hour delivery"),
- WMETER_VALUE_V3("0--1:24.3.0",
+ WMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null),
DSMRMeterType.WATER,
new CosemValueDescriptor(CosemFloat.class, "m3", "wValue"),
- DSMRVersion.V3_VERSIONS, "Last hourly meter reading"),
- WMETER_VALUE_V4("0--1:24.2.1",
+ "Last hourly meter reading"),
+ WMETER_VALUE_V4(new OBISIdentifier(0, null, 24, 2, 1, null),
DSMRMeterType.WATER,
new CosemValueDescriptor[]{
new CosemValueDescriptor(CosemDate.class, "", "wValueTS"),
- new CosemValueDescriptor(CosemFloat.class, "m3", "wValue")},
- DSMRVersion.V4_VERSIONS,
+ new CosemValueDescriptor(CosemFloat.class, "m3", "wValue")},
"Last hourly meter reading"),
- WMETER_VALVE_POSITION("0--1:24.4.0",
+ WMETER_VALVE_POSITION(new OBISIdentifier(0, null, 24, 4, 0, null),
DSMRMeterType.WATER,
- new CosemValueDescriptor(CosemInteger.class, "", "wValvePosition"),
- DSMRVersion.V30_UP,
+ new CosemValueDescriptor(CosemInteger.class, "", "wValvePosition"),
"Water valve position"),
/* Generic Meter (DSMR v3 only) */
- GENMETER_DEVICE_TYPE("0--1:24.1.0",
+ GENMETER_DEVICE_TYPE(new OBISIdentifier(0, null, 24, 1, 0, null),
DSMRMeterType.GENERIC,
- new CosemValueDescriptor(CosemString.class, "", "genericDeviceType"),
- DSMRVersion.V3_VERSIONS,
+ new CosemValueDescriptor(CosemString.class, "", "genericDeviceType"),
"Device Type"),
- GENMETER_EQUIPMENT_IDENTIFIER("0--1:96.1.0",
+ GENMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 0, null),
DSMRMeterType.GENERIC,
new CosemValueDescriptor(CosemString.class, "", "genericEquipmentId"),
- DSMRVersion.V3_VERSIONS,
"Equipment identifier"),
- GENMETER_VALUE_V3("0--1:24.3.0",
+ GENMETER_VALUE_V3(new OBISIdentifier(0, null, 24, 3, 0, null),
DSMRMeterType.GENERIC,
new CosemValueDescriptor(CosemFloat.class, "", "genericValue"),
- DSMRVersion.V3_VERSIONS,
"Last hourly meter reading"),
/* Slave E Meter (DSMR v4) */
- SEMETER_DEVICE_TYPE("0--1:24.1.0",
+ SEMETER_DEVICE_TYPE(new OBISIdentifier(0, null, 24, 1, 0, null),
DSMRMeterType.SLAVE_ELECTRICITY,
new CosemValueDescriptor(CosemString.class, "", "seDeviceType"),
- DSMRVersion.V4_VERSIONS,
"Slave Electricity Device Type"),
- SEMETER_EQUIPMENT_IDENTIFIER("0--1:96.1.0",
+ SEMETER_EQUIPMENT_IDENTIFIER(new OBISIdentifier(0, null, 96, 1, 0, null),
DSMRMeterType.SLAVE_ELECTRICITY,
new CosemValueDescriptor(CosemString.class, "", "seEquipmentId"),
- DSMRVersion.V4_VERSIONS,
"Slave Electricity Equipment identifier"),
- SEMETER_VALUE_V4("0--1:24.2.1",
+ SEMETER_VALUE_V4(new OBISIdentifier(0, null, 24, 2, 1, null),
DSMRMeterType.SLAVE_ELECTRICITY,
new CosemValueDescriptor[]{
new CosemValueDescriptor(CosemDate.class, "", "seValueTS"),
new CosemValueDescriptor(CosemFloat.class, "kWh", "seValue")},
- DSMRVersion.V4_VERSIONS,
"Slave Electricity last hourly meter reading"),
- SEMETER_SWITCH_POSITION("0--1:24.4.0",
+ SEMETER_SWITCH_POSITION(new OBISIdentifier(0, null, 24, 4, 0, null),
DSMRMeterType.SLAVE_ELECTRICITY,
- new CosemValueDescriptor(CosemInteger.class, "", "seSwitchPosition"),
- DSMRVersion.V4_VERSIONS,
+ new CosemValueDescriptor(CosemInteger.class, "", "seSwitchPosition"),
"Slave Electricity switch position");
/** OBIS reduced identifier */
- public String obisId;
+ public OBISIdentifier obisId;
/** COSEM value descriptors */
public final List cosemValueDescriptors;
/** Description of this message type */
public final String description;
-
- /** Applicable DSMR versions for this message type */
- public final List applicableVersions;
-
+
/** Applicable meter type for this message type*/
public final DSMRMeterType meterType;
- /**
- * Constructor
- *
- * @param obisId
- * OBIS Identifier for the OBIS message
- * @param meterType
- * the {@link DSMRMeterType} that this message is applicable for
- * @param cosemValueDescriptors
- * array of {@link CosemValueDescriptor} that describe the values
- * of the message
- * @param applicableVersions
- * array of {@link DSMRVersion} that this message is applicable
- * for
- * @param description
- * human readable description of the OBIS message
- */
- private OBISMsgType(
- String obisId,
- DSMRMeterType meterType,
- CosemValueDescriptor[] cosemValueDescriptors,
- DSMRVersion[] applicableVersions,
- String description) {
- this.obisId = obisId;
- this.meterType = meterType;
- this.cosemValueDescriptors = Arrays.asList(cosemValueDescriptors);
- this.applicableVersions = Arrays.asList(applicableVersions);
- this.description = description;
- }
-
- /**
- * Constructor
- *
- * @param obisId
- * OBIS Identifier for the OBIS message
- * @param meterType
- * the {@link DSMRMeterType} that this message is applicable for
- * @param cosemValueDescriptor
- * {@link CosemValueDescriptor} that describes the value of the
- * message
- * @param applicableVersion
- * {@link DSMRVersion} that this message is applicable for
- * @param description
- * human readable description of the OBIS message
- */
- private OBISMsgType(
- String obisId,
- DSMRMeterType meterType,
- CosemValueDescriptor cosemValueDescriptor,
- DSMRVersion applicableVersion,
- String description) {
- this(obisId, meterType, new CosemValueDescriptor[] { cosemValueDescriptor },
- new DSMRVersion[] { applicableVersion },
- description);
- }
-
/**
* Constructor
*
@@ -559,20 +440,15 @@ private OBISMsgType(
* @param cosemValueDescriptor
* {@link CosemValueDescriptor} that describes the value
* of the message
- * @param applicableVersions
- * array of {@link DSMRVersion} that this message is applicable
- * for
* @param description
* human readable description of the OBIS message
*/
private OBISMsgType(
- String obisId,
+ OBISIdentifier obisId,
DSMRMeterType meterType,
CosemValueDescriptor cosemValueDescriptor,
- DSMRVersion[] applicableVersions,
String description) {
this(obisId, meterType, new CosemValueDescriptor[] { cosemValueDescriptor },
- applicableVersions,
description);
}
@@ -586,20 +462,17 @@ private OBISMsgType(
* @param cosemValueDescriptors
* array of {@link CosemValueDescriptor} that describe the values
* of the message
- * @param applicableVersions
- * {@link DSMRVersion} that this message is applicable
- * for
* @param description
* human readable description of the OBIS message
*/
private OBISMsgType(
- String obisId,
+ OBISIdentifier obisId,
DSMRMeterType meterType,
CosemValueDescriptor[] cosemValueDescriptors,
- DSMRVersion applicableVersion,
String description) {
- this(obisId, meterType, cosemValueDescriptors,
- new DSMRVersion[] { applicableVersion },
- description);
+ this.obisId = obisId;
+ this.meterType = meterType;
+ this.cosemValueDescriptors = Arrays.asList(cosemValueDescriptors);
+ this.description = description;
}
}
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/p1telegram/P1TelegramParser.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/p1telegram/P1TelegramParser.java
new file mode 100644
index 00000000000..428c85dabcc
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/p1telegram/P1TelegramParser.java
@@ -0,0 +1,295 @@
+/**
+ * Copyright (c) 2010-2015, 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.dsmr.internal.p1telegram;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.openhab.binding.dsmr.internal.messages.OBISMessage;
+import org.openhab.binding.dsmr.internal.messages.OBISMsgFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The P1TelegramParser is a class that will read P1-port data create a full P1
+ * telegram.
+ *
+ * This class parses data via the public method parseData. It will parse the
+ * received data. If a complete P1 telegram is received the OBIS messages are
+ * returned. Otherwise it will continue parsing with the next call op parseData.
+ *
+ * @author mvolaart
+ * @since 1.7.0
+ */
+public class P1TelegramParser {
+ // Logger
+ public static final Logger logger = LoggerFactory
+ .getLogger(P1TelegramParser.class);
+
+ // Helper classers
+ private OBISMsgFactory factory;
+
+ // Reader state
+ private P1TelegramParserState parserState;
+
+ // Current P1Telegram values
+ private LinkedList obisDataLines;
+ private OBISDataLine currentLine = null;
+
+ /**
+ * Creates a new P1TelegramParser
+ *
+ * @param factory
+ * OBISMsgFactory object
+ */
+ public P1TelegramParser(OBISMsgFactory factory) {
+ this.factory = factory;
+
+ obisDataLines = new LinkedList();
+ parserState = new P1TelegramParserState();
+ }
+
+ /**
+ * Parses data. If parsing is not ready yet nothing will be returned. If
+ * parsing fails completely nothing will be returned. If parsing succeeds
+ * (partial) the received OBIS messages will be returned.
+ *
+ * @param data
+ * byte data
+ * @param offset
+ * offset tot start in the data buffer
+ * @param length
+ * number of bytes to parse
+ * @return List of {@link OBISMessage} if a full P1 telegram is received,
+ * empty list if parsing is not ready or failed completely.
+ */
+ public List parseData(byte[] data, int offset, int length) {
+ List receivedMessages = new LinkedList();
+
+ logger.trace("Data:"
+ + Arrays.toString(Arrays.copyOfRange(data, offset, length))
+ + ", state before parsing:" + parserState);
+ for (int i = offset; i < (offset + length); i++) {
+ char c = (char) data[i];
+
+ switch (parserState.getState()) {
+ case WAIT_FOR_START:
+ if (c == '/') {
+ parserState.setState(P1TelegramParserState.State.STARTED);
+
+ handleNewP1Telegram();
+ }
+ break;
+ case STARTED:
+ parserState.setState(P1TelegramParserState.State.HEADER);
+ break;
+
+ case HEADER:
+ if (c == '\r') {
+ parserState.setState(P1TelegramParserState.State.CRLF);
+ }
+ break;
+ case CRLF:
+ if (Character.isWhitespace(c)) {
+ // do nothing
+ } else if (Character.isDigit(c)) {
+ parserState
+ .setState(P1TelegramParserState.State.DATA_OBIS_ID);
+
+ finishObisLine();
+ } else {
+ handleUnexpectedCharacter(c);
+
+ parserState
+ .setState(P1TelegramParserState.State.WAIT_FOR_START);
+ }
+ break;
+ case DATA_OBIS_ID:
+ if (Character.isWhitespace(c)) {
+ // ignore
+ } else if (Character.isDigit(c) || c == ':' || c == '-'
+ || c == '.' || c == '*') {
+ // do nothing
+ } else if (c == '(') {
+ parserState
+ .setState(P1TelegramParserState.State.DATA_OBIS_VALUE_START);
+ } else if (c == '!') {
+ logger.warn("Unexpected character '!'. Going to state:"
+ + P1TelegramParserState.State.CRC_VALUE);
+
+ parserState.setState(P1TelegramParserState.State.CRC_VALUE);
+ } else {
+ handleUnexpectedCharacter(c);
+
+ parserState
+ .setState(P1TelegramParserState.State.WAIT_FOR_START);
+ }
+ break;
+ case DATA_OBIS_VALUE_START:
+ if (c == ')') {
+ handleObisValueReady();
+
+ parserState
+ .setState(P1TelegramParserState.State.DATA_OBIS_VALUE_END);
+ } else {
+ parserState
+ .setState(P1TelegramParserState.State.DATA_OBIS_VALUE);
+ }
+ break;
+
+ case DATA_OBIS_VALUE:
+ if (c == ')') {
+ handleObisValueReady();
+
+ parserState
+ .setState(P1TelegramParserState.State.DATA_OBIS_VALUE_END);
+ }
+ break;
+ case DATA_OBIS_VALUE_END:
+ if (Character.isWhitespace(c)) {
+ // ignore
+ } else if (Character.isDigit(c)) {
+ finishObisLine();
+
+ parserState
+ .setState(P1TelegramParserState.State.DATA_OBIS_ID);
+ } else if (c == '(') {
+ parserState
+ .setState(P1TelegramParserState.State.DATA_OBIS_VALUE_START);
+ } else if (c == '!') {
+ parserState.setState(P1TelegramParserState.State.CRC_VALUE);
+ } else {
+ handleUnexpectedCharacter(c);
+
+ parserState
+ .setState(P1TelegramParserState.State.WAIT_FOR_START);
+ }
+ break;
+
+ case CRC_VALUE:
+ if (c == '\r') {
+ if (parserState.getCrcValue().length() > 0) {
+ // TODO: Handle CRC here
+ }
+
+ receivedMessages.addAll(handleEndP1Telegram());
+
+ parserState
+ .setState(P1TelegramParserState.State.WAIT_FOR_START);
+ }
+ break;
+ }
+ parserState.handleCharacter(c);
+ }
+ logger.trace("State after parsing:" + parserState);
+
+ return receivedMessages;
+ }
+
+ /**
+ * Handles the start of a new P1 telegram This method will clear internal
+ * state
+ */
+ private void handleNewP1Telegram() {
+ obisDataLines.clear();
+ currentLine = null;
+ }
+
+ /**
+ * Handles the end of a P1 telegram. This method will parse the raw data to
+ * a list of {@link OBISMessage}
+ */
+ private List handleEndP1Telegram() {
+ finishObisLine();
+
+ List obisMessages = new LinkedList();
+
+ for (OBISDataLine obisDataLine : obisDataLines) {
+ OBISMessage obisMessage = factory.getMessage(obisDataLine.obisId,
+ obisDataLine.obisStringValues);
+ logger.debug("Parsed:" + obisDataLine + ", to:" + obisMessage);
+ if (obisMessage != null) {
+ obisMessages.add(obisMessage);
+ } else {
+ logger.warn("Failed to parse:" + obisDataLine);
+ }
+ }
+
+ return obisMessages;
+ }
+
+ /**
+ * Handles an unexpected character. The character will be logged and the P1
+ * telegram will be finished.
+ *
+ * @param c
+ * the unexpected character
+ */
+ private void handleUnexpectedCharacter(char c) {
+ logger.warn("Unexpected character '" + c + "' in state:" + parserState
+ + ". Publishing partial P1Telegram and wait for new P1Telegram");
+
+ handleEndP1Telegram();
+ }
+
+ /**
+ * Handle if a full OBIS value is parsed. This method will store the OBIS
+ * value in the current OBISDataLine
+ */
+ private void handleObisValueReady() {
+ if (currentLine == null) {
+ currentLine = new OBISDataLine(parserState.getObisId());
+ }
+ currentLine.obisStringValues.add(parserState.getObisValue());
+ }
+
+ /**
+ * Handle the end of a OBIS Line (i.e. all values are parsed)
+ */
+ private void finishObisLine() {
+ if (currentLine != null) {
+ obisDataLines.add(currentLine);
+
+ currentLine = null;
+ }
+ }
+
+ /**
+ * Class representing a OBISDataLine, e.g. '0-0:96.3.10(1)'
+ *
+ * @author mvolaart
+ * @since 1.7.0
+ */
+ class OBISDataLine {
+ // OBIS identifier
+ final String obisId;
+
+ // List of OBIS values
+ final LinkedList obisStringValues;
+
+ /**
+ * Creates a new OBISDataLine
+ *
+ * @param obisId
+ * OBISIdentifer
+ */
+ OBISDataLine(String obisId) {
+ this.obisId = obisId;
+
+ obisStringValues = new LinkedList();
+ }
+
+ @Override
+ public String toString() {
+ return "OBISDataLine [OBIS id:" + obisId + ", obis values:"
+ + obisStringValues;
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/p1telegram/P1TelegramParserState.java b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/p1telegram/P1TelegramParserState.java
new file mode 100644
index 00000000000..03d78964fb3
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/p1telegram/P1TelegramParserState.java
@@ -0,0 +1,205 @@
+/**
+ * Copyright (c) 2010-2015, 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.dsmr.internal.p1telegram;
+
+/**
+ * P1TelegramParserState stores the state of the P1TelegramParser The state
+ * consists of the following attributes:
+ *
+ * - receivedData (all parsed data of a single P1 telegram)
+ *
- header (the header string of a single P1 telegram)
+ *
- obisId (the last OBIS Identifier seen or parsing)
+ *
- obisValue (the last OBIS value seen or parsing)
+ *
- crcValue (the last crc value seen or parsing)
+ *
- state (state in the P1 telegram)
+ *
+ *
+ * @author mvolaart
+ * @since 1.7.0
+ */
+class P1TelegramParserState {
+ static enum State {
+ // Wait for the '/' character
+ WAIT_FOR_START,
+ // '/' character seen
+ STARTED,
+ // Parsing data after the '/' character
+ HEADER,
+ // Waiting for the header to end with a CR & LF
+ CRLF,
+ // Handling OBIS Identifier
+ DATA_OBIS_ID,
+ // OBIS value start seen '('
+ DATA_OBIS_VALUE_START,
+ // Parsing OBIS value
+ DATA_OBIS_VALUE,
+ // OBIS value end seen ')'
+ DATA_OBIS_VALUE_END,
+ // Parsing CRC value following '!'
+ CRC_VALUE
+ };
+
+ /* internal state variables */
+ private String receivedData = "";
+ private String header = "";
+ private String obisId = "";
+ private String obisValue = "";
+ private String crcValue = "";
+ private State state = State.WAIT_FOR_START;
+
+ /**
+ * Creates a new P1TelegramParser object
+ */
+ P1TelegramParserState() {
+ }
+
+ /**
+ * Stores a single character
+ *
+ * @param c
+ * the character to store
+ */
+ void handleCharacter(char c) {
+
+ switch (state) {
+ case WAIT_FOR_START:
+ // ignore the data
+ break;
+ case STARTED:
+ header += c;
+ receivedData += c;
+ break;
+ case HEADER:
+ header += c;
+ receivedData += c;
+ break;
+ case CRLF:
+ receivedData += c;
+ break;
+ case DATA_OBIS_ID:
+ obisId += c;
+ receivedData += c;
+ break;
+ case DATA_OBIS_VALUE_START:
+ receivedData += c;
+ break;
+ case DATA_OBIS_VALUE:
+ obisValue += c;
+ receivedData += c;
+ break;
+ case DATA_OBIS_VALUE_END:
+ receivedData += c;
+ break;
+ case CRC_VALUE:
+ crcValue += c;
+ // CRC data is not part of received data
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Clear internal state
+ */
+ private void clearInternalData() {
+ header = "";
+ obisId = "";
+ obisValue = "";
+ receivedData = "";
+ crcValue = "";
+ }
+
+ /**
+ * Clears the received OBIS Identifer
+ */
+ private void clearObisId() {
+ obisId = "";
+ }
+
+ /**
+ * Clears the current OBIS value
+ */
+ private void clearObisValue() {
+ obisValue = "";
+ }
+
+ /**
+ * @return the state
+ */
+ State getState() {
+ return state;
+ }
+
+ /**
+ * @param state
+ * the state to set
+ */
+ void setState(State newState) {
+ switch (newState) {
+ case WAIT_FOR_START:
+ clearInternalData();
+ break;
+ case DATA_OBIS_ID:
+ clearObisId();
+ clearObisValue();
+ break;
+ case DATA_OBIS_VALUE_START:
+ break;
+ case DATA_OBIS_VALUE:
+ clearObisValue();
+ break;
+ default:
+ break;
+ }
+
+ state = newState;
+ }
+
+ /**
+ * @return the meterId
+ */
+ String getHeader() {
+ return header;
+ }
+
+ /**
+ * @return the obisId
+ */
+ String getObisId() {
+ return obisId;
+ }
+
+ /**
+ * @return the obisValue
+ */
+ String getObisValue() {
+ return obisValue;
+ }
+
+ /**
+ * @return the crcValue
+ */
+ String getCrcValue() {
+ return crcValue;
+ }
+
+ /**
+ * @return the receivedData
+ */
+ String getReceivedData() {
+ return receivedData;
+ }
+
+ @Override
+ public String toString() {
+ return state + ", header = " + header + ", obisId = " + obisId
+ + ", obisValue = " + obisValue;
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/.classpath b/bundles/binding/org.openhab.binding.ecobee/.classpath
new file mode 100644
index 00000000000..577bac87a26
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bundles/library/org.openhab.library.tel/bin/.project b/bundles/binding/org.openhab.binding.ecobee/.project
similarity index 84%
rename from bundles/library/org.openhab.library.tel/bin/.project
rename to bundles/binding/org.openhab.binding.ecobee/.project
index 2cfeb09c548..69237326c6d 100644
--- a/bundles/library/org.openhab.library.tel/bin/.project
+++ b/bundles/binding/org.openhab.binding.ecobee/.project
@@ -1,7 +1,7 @@
- org.openhab.library.tel
- This is the runtime component of the open Home Automation Bus (openHAB)
+ org.openhab.binding.ecobee
+ This is the Ecobee binding of the open Home Automation Bus (openHAB)
diff --git a/bundles/binding/org.openhab.binding.ecobee/.settings/org.eclipse.jdt.core.prefs b/bundles/binding/org.openhab.binding.ecobee/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000000..f42de363afa
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/bundles/binding/org.openhab.binding.ecobee/.settings/org.eclipse.jdt.ui.prefs b/bundles/binding/org.openhab.binding.ecobee/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 00000000000..243459222cb
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.ui.javadoc=false
+org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n@JsonProperty("${bare_field_name}")\n *//**\n * @param ${param} the ${bare_field_name} to set\n@JsonProperty("${bare_field_name}")\n *//**\n * ${tags}\n *//**\n * \n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * \n *//**\n * ${tags}\n *//* (non-Javadoc)\n * ${see_to_overridden}\n *//**\n * ${tags}\n * ${see_to_target}\n */${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\n${body_statement}${body_statement}\n// ${todo} Auto-generated constructor stubreturn this.${field};${field} \= ${param};
diff --git a/bundles/binding/org.openhab.binding.ecobee/META-INF/MANIFEST.MF b/bundles/binding/org.openhab.binding.ecobee/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..256fc1768ac
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/META-INF/MANIFEST.MF
@@ -0,0 +1,43 @@
+Manifest-Version: 1.0
+Private-Package: org.openhab.binding.ecobee.internal
+Ignore-Package: org.openhab.binding.ecobee.internal
+Bundle-License: http://www.eclipse.org/legal/epl-v10.html
+Bundle-Name: openHAB Ecobee Binding
+Bundle-SymbolicName: org.openhab.binding.ecobee
+Bundle-Vendor: openHAB.org
+Bundle-Version: 1.7.0.qualifier
+Bundle-Activator: org.openhab.binding.ecobee.internal.EcobeeActivator
+Bundle-ManifestVersion: 2
+Bundle-Description: This is the Ecobee binding of the open Home Aut
+ omation Bus (openHAB)
+Import-Package: com.google.common.base,
+ com.google.common.io,
+ org.apache.commons.httpclient,
+ org.apache.commons.httpclient.util,
+ org.apache.commons.lang,
+ org.apache.commons.lang.builder,
+ org.apache.commons.logging,
+ org.codehaus.jackson,
+ org.codehaus.jackson.annotate,
+ org.codehaus.jackson.map,
+ org.openhab.binding.ecobee,
+ org.openhab.core.binding,
+ org.openhab.core.events,
+ org.openhab.core.items,
+ org.openhab.core.library.items,
+ org.openhab.core.library.types,
+ org.openhab.core.types,
+ org.openhab.io.net.http,
+ org.openhab.model.item.binding,
+ org.osgi.framework,
+ org.osgi.service.cm,
+ org.osgi.service.component,
+ org.osgi.service.event,
+ org.slf4j
+Export-Package: org.openhab.binding.ecobee
+Bundle-DocURL: http://www.openhab.org
+Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Service-Component: OSGI-INF/binding.xml, OSGI-INF/genericbindingprovider.xml
+Bundle-ClassPath: lib/commons-beanutils-1.8.3.jar,
+ .
+Require-Bundle: org.openhab.io.rest.lib
diff --git a/bundles/binding/org.openhab.binding.ecobee/OSGI-INF/binding.xml b/bundles/binding/org.openhab.binding.ecobee/OSGI-INF/binding.xml
new file mode 100644
index 00000000000..18c43772a58
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/OSGI-INF/binding.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/binding/org.openhab.binding.ecobee/OSGI-INF/genericbindingprovider.xml b/bundles/binding/org.openhab.binding.ecobee/OSGI-INF/genericbindingprovider.xml
new file mode 100644
index 00000000000..4044a8f4ac8
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/OSGI-INF/genericbindingprovider.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/binding/org.openhab.binding.ecobee/README.md b/bundles/binding/org.openhab.binding.ecobee/README.md
new file mode 100644
index 00000000000..8a0b03853b2
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/README.md
@@ -0,0 +1,89 @@
+org.openhab.binding.ecobee
+==========================
+
+openHAB Ecobee Binding
+
+Installation
+------------
+
+* Add an appkey and scope to openhab.cfg
+
+```
+ecobee:refresh=60000
+ecobee:appkey=ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHI
+ecobee:scope=smartWrite
+```
+
+* Login to your Ecobee portal and navigate to My Apps
+
+* Install the binding bundle by copying the appropriate JAR file to your addons directory.
+
+* Monitor the openhab.log for INFO messages from this binding that instruct you to enter
+ the supplied four-letter PIN into the My Apps, and follow those instructions.
+
+* Configure items and rules. Examples:
+
+```
+Number Ecobee_Name "Room [%s]" {ecobee="<[123456789#name]"
+```
+Return the name of the thermostat whose ID is 123456789 using the default Ecobee app instance (configured in openhab.cfg).
+
+```
+Number Dining_Humidity "Humidity [%d %%]" {ecobee="<[123456789#runtime.actualHumidity]"}
+```
+Example item for retrieving the relative humidity.
+
+```
+Number Condo_Temperature "Condo Temperature [%d ºF]" {ecobee="<[condo.987654321#runtime.actualTemperature]"}
+```
+Return the current temperature read by the thermostat using the condo account at ecobee.com. If you want to instead have Celsius temperatures reported, add the setting `ecobee:tempscale=C` to your openhab.cfg.
+
+```
+Number Dining_FanMinOnTime "Fan Min On Time [%d min/hour]" {ecobee="=[543212345#settings.fanMinOnTime]"}
+```
+Set the minimum number of minutes per hour the fan will run on thermostat ID 543212345.
+
+```
+String All_HVAC_Modes "[%s]" {ecobee=">[*#settings.hvacMode]"}
+```
+Change the HVAC mode to one of `"auto"`, `"auxHeatOnly"`, `"cool"`, `"heat"`, or
+`"off"` on all thermostats registered in the default app instance.
+
+```
+Number Lakehouse_Backlights "[%d]" {ecobee=">[lakehouse.*#settings.backlightSleepIntensity]"}
+```
+Changes the backlight sleep intensity on all thermostats at the lake house (meaning, all thermostats registered to the lakehouse Ecobee account).
+
+Supported are a long list of runtime and configuration values (see below).
+
+Example rule to send a mail if humidity reaches a certain threshold:
+```
+var boolean humHighWarning = false
+var boolean humVeryHighWarning = false
+
+rule "Monitor humidity level"
+ when
+ Item Dining_Humidity changed
+ then
+ if(Dining_Humidity.state > 60) {
+ if(humHighWarning == false) {
+ sendMail("example@example.com",
+ "High humidity level!",
+ "Humidity level is " + Dining_Humidity.state + " %%.")
+ humHighWarning = true
+ }
+ } else if(Dining_Humidity.state > 75) {
+ if(humVeryHighWarning == false) {
+ sendMail("example@example.com",
+ "Very high humidity level!",
+ "Humidity level is " + Dining_Humidity.state + " %%.")
+ humVeryHighWarning = true
+ }
+ } else {
+ humHighWarning = false
+ humVeryHighWarning = false
+ }
+end
+```
+
+TBD
diff --git a/bundles/binding/org.openhab.binding.ecobee/build.properties b/bundles/binding/org.openhab.binding.ecobee/build.properties
new file mode 100644
index 00000000000..008e33bdc67
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/build.properties
@@ -0,0 +1,7 @@
+source.. = src/main/java/,\
+ src/main/resources/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/,\
+ lib/commons-beanutils-1.8.3.jar
+output.. = target/classes/
diff --git a/bundles/binding/org.openhab.binding.ecobee/lib/commons-beanutils-1.8.3.jar b/bundles/binding/org.openhab.binding.ecobee/lib/commons-beanutils-1.8.3.jar
new file mode 100644
index 00000000000..218510bc5d6
Binary files /dev/null and b/bundles/binding/org.openhab.binding.ecobee/lib/commons-beanutils-1.8.3.jar differ
diff --git a/bundles/binding/org.openhab.binding.ecobee/pom.xml b/bundles/binding/org.openhab.binding.ecobee/pom.xml
new file mode 100644
index 00000000000..5394100b3f4
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/pom.xml
@@ -0,0 +1,35 @@
+
+
+
+
+ org.openhab.bundles
+ binding
+ 1.7.0-SNAPSHOT
+
+
+
+ org.openhab.binding.ecobee
+ org.openhab.binding.ecobee
+ openhab-addon-binding-Ecobee
+ openhab addon binding Ecobee
+
+
+ 4.0.0
+ org.openhab.binding
+ org.openhab.binding.ecobee
+
+ openHAB Ecobee Binding
+
+ eclipse-plugin
+
+
+
+
+ org.vafer
+ jdeb
+
+
+
+
+
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/EcobeeBindingProvider.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/EcobeeBindingProvider.java
new file mode 100644
index 00000000000..c2ca31c0ac0
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/EcobeeBindingProvider.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee;
+
+import org.openhab.core.binding.BindingProvider;
+
+/**
+ * This interface is implemented by classes that can provide mapping information between openHAB items and Ecobee items.
+ *
+ * Implementing classes should register themselves as a service in order to be taken into account.
+ *
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public interface EcobeeBindingProvider extends BindingProvider {
+
+ /**
+ * Returns an Id of the user the settings refer to.
+ *
+ * @param itemName
+ * the itemName to query
+ * @return the ID of the user the settings refer to.
+ */
+ String getUserid(String itemName);
+
+ /**
+ * Queries the Ecobee thermostat identifier of the given {@code itemName}.
+ *
+ * @param itemName
+ * the itemName to query
+ * @return the Ecobee thermostat identifier of the Item identified by {@code itemName} if it has an Ecobee binding,
+ * null
otherwise
+ */
+ String getThermostatIdentifier(String itemName);
+
+ /**
+ * Queries the Ecobee property of the given {@code itemName}.
+ *
+ * @param itemName
+ * the itemName to query
+ * @return the Ecobee property of the Item identified by {@code itemName} if it has an Ecobee binding,
+ * null
otherwise
+ */
+ String getProperty(String itemName);
+
+ /**
+ * Queries whether this item can be read from the Ecobee API, for the given {@code itemName}.
+ *
+ * @param itemName
+ * the itemName to query
+ * @return true
if this property can be read from the Ecobee API.
+ */
+ boolean isInBound(String itemName);
+
+ /**
+ * Queries whether this item can be written to the Ecobee API, for the given {@code itemName}.
+ *
+ * @param itemName
+ * the itemName to query
+ * @return true
if this property can be written to the Ecobee API.
+ */
+ boolean isOutBound(String itemName);
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeActivator.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeActivator.java
new file mode 100644
index 00000000000..c209c4adf86
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeActivator.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extension of the default OSGi bundle activator
+ *
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public final class EcobeeActivator implements BundleActivator {
+
+ private static Logger logger = LoggerFactory.getLogger(EcobeeActivator.class);
+
+ private static BundleContext context;
+
+ /**
+ * Called whenever the OSGi framework starts our bundle
+ */
+ public void start(BundleContext bc) throws Exception {
+ context = bc;
+ logger.debug("Ecobee binding has been started.");
+ }
+
+ /**
+ * Called whenever the OSGi framework stops our bundle
+ */
+ public void stop(BundleContext bc) throws Exception {
+ context = null;
+ logger.debug("Ecobee binding has been stopped.");
+ }
+
+ /**
+ * Returns the bundle context of this bundle
+ *
+ * @return the bundle context
+ */
+ public static BundleContext getContext() {
+ return context;
+ }
+
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeBinding.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeBinding.java
new file mode 100644
index 00000000000..aa49a52ff66
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeBinding.java
@@ -0,0 +1,1009 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.prefs.Preferences;
+
+import org.apache.commons.beanutils.ConvertUtils;
+import org.apache.commons.beanutils.Converter;
+import org.openhab.binding.ecobee.EcobeeBindingProvider;
+import org.openhab.binding.ecobee.internal.messages.AbstractFunction;
+import org.openhab.binding.ecobee.internal.messages.ApiResponse;
+import org.openhab.binding.ecobee.internal.messages.AuthorizeRequest;
+import org.openhab.binding.ecobee.internal.messages.AuthorizeResponse;
+import org.openhab.binding.ecobee.internal.messages.Request;
+import org.openhab.binding.ecobee.internal.messages.Selection;
+import org.openhab.binding.ecobee.internal.messages.Selection.SelectionType;
+import org.openhab.binding.ecobee.internal.messages.Status;
+import org.openhab.binding.ecobee.internal.messages.Temperature;
+import org.openhab.binding.ecobee.internal.messages.Thermostat;
+import org.openhab.binding.ecobee.internal.messages.Thermostat.HvacMode;
+import org.openhab.binding.ecobee.internal.messages.Thermostat.VentilatorMode;
+import org.openhab.binding.ecobee.internal.messages.ThermostatRequest;
+import org.openhab.binding.ecobee.internal.messages.ThermostatResponse;
+import org.openhab.binding.ecobee.internal.messages.ThermostatSummaryRequest;
+import org.openhab.binding.ecobee.internal.messages.ThermostatSummaryResponse;
+import org.openhab.binding.ecobee.internal.messages.ThermostatSummaryResponse.Revision;
+import org.openhab.binding.ecobee.internal.messages.RefreshTokenRequest;
+import org.openhab.binding.ecobee.internal.messages.TokenRequest;
+import org.openhab.binding.ecobee.internal.messages.TokenResponse;
+import org.openhab.binding.ecobee.internal.messages.UpdateThermostatRequest;
+
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+
+import org.openhab.core.binding.AbstractActiveBinding;
+import org.openhab.core.binding.BindingProvider;
+import org.openhab.core.items.ItemRegistry;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Binding that retrieves information about thermostats we're interested in every few minutes, and sends updates and
+ * commands to Ecobee as they are made. Reviewed lots of other binding implementations, particularly Netatmo and XBMC.
+ *
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public class EcobeeBinding extends AbstractActiveBinding implements ManagedService {
+
+ private static final String DEFAULT_USER_ID = "DEFAULT_USER";
+
+ private static final Logger logger = LoggerFactory.getLogger(EcobeeBinding.class);
+
+ protected static final String CONFIG_REFRESH = "refresh";
+ protected static final String CONFIG_APP_KEY = "appkey";
+ protected static final String CONFIG_SCOPE = "scope";
+ protected static final String CONFIG_TEMP_SCALE = "tempscale";
+
+ static {
+ // Register bean type converters
+ ConvertUtils.register(new Converter() {
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof DecimalType) {
+ return Temperature.fromLocalTemperature(((DecimalType) value).toBigDecimal());
+ } else {
+ return null;
+ }
+ }
+ }, Temperature.class);
+ ConvertUtils.register(new Converter() {
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof StringType) {
+ return HvacMode.forValue(value.toString());
+ } else {
+ return null;
+ }
+ }
+ }, HvacMode.class);
+ ConvertUtils.register(new Converter() {
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof DecimalType) {
+ return ((DecimalType) value).intValue();
+ } else {
+ return null;
+ }
+ }
+ }, Integer.class);
+ ConvertUtils.register(new Converter() {
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof StringType) {
+ return VentilatorMode.forValue(value.toString());
+ } else {
+ return null;
+ }
+ }
+ }, VentilatorMode.class);
+ ConvertUtils.register(new Converter() {
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ if (value instanceof OnOffType) {
+ return ((OnOffType) value) == OnOffType.ON;
+ } else {
+ return null;
+ }
+ }
+ }, Boolean.class);
+ ConvertUtils.register(new Converter() {
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object convert(Class type, Object value) {
+ return value.toString();
+ }
+ }, String.class);
+ }
+
+ /**
+ * the refresh interval which is used to poll values from the Ecobee server (optional, defaults to 60000ms)
+ */
+ private long refreshInterval = 60000;
+
+ /**
+ * A map of userids from the openhab.cfg file to OAuth credentials used to communicate with each app instance.
+ */
+ private Map credentialsCache = new HashMap();
+
+ /**
+ * used to store events that we have sent ourselves; we need to remember them for not reacting to them
+ */
+ private List ignoreEventList = Collections.synchronizedList(new ArrayList());
+
+ /**
+ * The most recently received list of revisions, or an empty Map if none have been retrieved yet.
+ */
+ private Map lastRevisionMap = new HashMap();
+
+ // Injected by the OSGi Container through the setItemRegistry and
+ // unsetItemRegistry methods.
+ private ItemRegistry itemRegistry;
+
+ public EcobeeBinding() {
+ }
+
+ /**
+ * Invoked by the OSGi Framework.
+ *
+ * This method is invoked by OSGi during the initialization of the EcobeeBinding, so we have subsequent access to
+ * the ItemRegistry (needed to get values from Items in openHAB)
+ */
+ public void setItemRegistry(ItemRegistry itemRegistry) {
+ this.itemRegistry = itemRegistry;
+ }
+
+ /**
+ * Invoked by the OSGi Framework.
+ *
+ * This method is invoked by OSGi during the initialization of the EcobeeBinding, so we have subsequent access to
+ * the ItemRegistry (needed to get values from Items in openHAB)
+ */
+ public void unsetItemRegistry(ItemRegistry itemRegistry) {
+ this.itemRegistry = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void activate() {
+ super.activate();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void deactivate() {
+ // deallocate resources here that are no longer needed and
+ // should be reset when activating this binding again
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected long getRefreshInterval() {
+ return refreshInterval;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String getName() {
+ return "Ecobee Refresh Service";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void execute() {
+ logger.trace("Querying Ecobee API");
+
+ try {
+ for (String userid : credentialsCache.keySet()) {
+ OAuthCredentials oauthCredentials = getOAuthCredentials(userid);
+
+ Selection selection = createSelection(oauthCredentials);
+ if (selection == null) {
+ logger.debug("Nothing to retrieve for '{}'; skipping thermostat retrieval.",
+ oauthCredentials.userid);
+ continue;
+ }
+
+ if (oauthCredentials.noAccessToken()) {
+ if (!oauthCredentials.refreshTokens()) {
+ logger.warn("Periodic poll skipped for '{}'.", oauthCredentials.userid);
+ continue;
+ }
+ }
+
+ readEcobee(oauthCredentials, selection);
+ }
+ } catch (Exception e) {
+ logger.error("Error reading from Ecobee:", e);
+ }
+ }
+
+ /**
+ * Given the credentials to use and what to select from the Ecobee API, read any changed information from Ecobee and
+ * update the affected items.
+ *
+ * @param oauthCredentials
+ * the credentials to use
+ * @param selection
+ * the selection of data to retrieve
+ */
+ private void readEcobee(OAuthCredentials oauthCredentials, Selection selection) throws Exception {
+
+ logger.debug("Requesting summaries for {}", selection);
+
+ ThermostatSummaryRequest request = new ThermostatSummaryRequest(oauthCredentials.accessToken, selection);
+ ThermostatSummaryResponse response = request.execute();
+ if (response.isError()) {
+ final Status status = response.getStatus();
+
+ if (status.isAccessTokenExpired()) {
+ logger.debug("Access token has expired: {}", status);
+ if (oauthCredentials.refreshTokens())
+ readEcobee(oauthCredentials, selection);
+ } else {
+ logger.error(status.getMessage());
+ }
+
+ return; // abort processing
+ }
+
+ logger.debug("Retrieved summaries for {} thermostat(s).", response.getRevisionList().size());
+
+ // Identify which thermostats have changed since the last fetch
+
+ Map newRevisionMap = new HashMap();
+ for (Revision r : response.getRevisionList()) {
+ newRevisionMap.put(r.getThermostatIdentifier(), r);
+ }
+
+ // Accumulate the thermostat IDs for thermostats that have updated
+ // since the last fetch.
+ Set thermostatIdentifiers = new HashSet();
+
+ for (Revision newRevision : newRevisionMap.values()) {
+ Revision lastRevision = this.lastRevisionMap.get(newRevision.getThermostatIdentifier());
+
+ // If this thermostat's values have changed,
+ // add it to the list for full retrieval
+
+ /*
+ * NOTE: The following tests may be more eager than they should be, because we may have a settings binding
+ * for one thermostat and not another, and a runtime binding for another thermostat but not this one, but we
+ * will now retrieve both thermostats. A small sin. If the Ecobee binding is only working with a single
+ * thermostat, these tests will be perfectly accurate.
+ */
+
+ boolean changed = false;
+
+ changed = changed
+ || (newRevision.hasRuntimeChanged(lastRevision) && (selection.includeRuntime() || selection
+ .includeExtendedRuntime()));
+ changed = changed
+ || (newRevision.hasThermostatChanged(lastRevision) && (selection.includeSettings() || selection
+ .includeProgram()));
+
+ if (changed) {
+ thermostatIdentifiers.add(newRevision.getThermostatIdentifier());
+ }
+ }
+
+ // Remember the new revisions for the next execute() call.
+ this.lastRevisionMap = newRevisionMap;
+
+ if (0 == thermostatIdentifiers.size()) {
+ logger.debug("No changes detected.");
+ return;
+ }
+
+ logger.debug("Requesting full retrieval for {} thermostat(s).", thermostatIdentifiers.size());
+
+ // Potentially decrease the number of thermostats for the full
+ // retrieval.
+
+ selection.setSelectionMatch(thermostatIdentifiers);
+
+ // TODO loop through possibly multiple pages (@watou)
+ ThermostatRequest treq = new ThermostatRequest(oauthCredentials.accessToken, selection, null);
+ ThermostatResponse tres = treq.execute();
+
+ if (tres.isError()) {
+ logger.error("Error retrieving thermostats: {}", tres.getStatus());
+ return;
+ }
+
+ // Create a ID-based map of the thermostats we retrieved.
+ Map thermostats = new HashMap();
+
+ for (Thermostat t : tres.getThermostatList()) {
+ thermostats.put(t.getIdentifier(), t);
+ }
+
+ // Iterate through bindings and update all inbound values.
+ for (final EcobeeBindingProvider provider : this.providers) {
+ for (final String itemName : provider.getItemNames()) {
+ if (provider.isInBound(itemName)) {
+ final State newState = getState(provider, thermostats, itemName);
+ State oldState = (itemRegistry == null) ? null : itemRegistry.getItem(itemName).getState();
+
+ if ((oldState == null && newState != null)
+ || (UnDefType.UNDEF.equals(oldState) && !UnDefType.UNDEF.equals(newState))
+ || !oldState.equals(newState)) {
+ logger.debug("readEcobee: Updating itemName '{}' with newState '{}', oldState '{}'", itemName,
+ newState, oldState);
+
+ /*
+ * we need to make sure that we won't send out this event to Ecobee again, when receiving it on
+ * the openHAB bus
+ */
+ ignoreEventList.add(itemName + newState.toString());
+ logger.trace("Added event (item='{}', newState='{}') to the ignore event list", itemName,
+ newState);
+ this.eventPublisher.postUpdate(itemName, newState);
+ } else {
+ logger.trace("readEcobee: Ignoring item='{}' with newState='{}', oldState='{}'", itemName,
+ newState, oldState);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Give a binding provider, a map of thermostats, and an item name, return the corresponding state object.
+ *
+ * @param provider
+ * the Ecobee binding provider
+ * @param thermostats
+ * a map of thermostat identifiers to {@link Thermostat} objects
+ * @param itemName
+ * the item name from the items file.
+ * @return the State object for the named item
+ */
+ private State getState(EcobeeBindingProvider provider, Map thermostats, String itemName) {
+
+ final String thermostatIdentifier = provider.getThermostatIdentifier(itemName);
+ final String property = provider.getProperty(itemName);
+ final Thermostat thermostat = thermostats.get(thermostatIdentifier);
+
+ if (thermostat == null) {
+ logger.error("Did not receive thermostat '{}' for item '{}'; skipping.", thermostatIdentifier, itemName);
+ } else {
+ try {
+ return createState(thermostat.getProperty(property));
+ } catch (Exception e) {
+ logger.error("Unable to get state from thermostat", e);
+ }
+ }
+ return UnDefType.NULL;
+ }
+
+ /**
+ * Creates an openHAB {@link State} in accordance to the class of the given {@code propertyValue}. Currently
+ * {@link Date}, {@link BigDecimal}, {@link Temperature} and {@link Boolean} are handled explicitly. All other
+ * {@code dataTypes} are mapped to {@link StringType}.
+ *
+ * If {@code propertyValue} is {@code null}, {@link UnDefType#NULL} will be returned.
+ *
+ * Copied/adapted from the Koubachi binding.
+ *
+ * @param propertyValue
+ *
+ * @return the new {@link State} in accordance with {@code dataType}. Will never be {@code null}.
+ */
+ private State createState(Object propertyValue) {
+ if (propertyValue == null) {
+ return UnDefType.NULL;
+ }
+
+ Class> dataType = propertyValue.getClass();
+
+ if (Date.class.isAssignableFrom(dataType)) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime((Date) propertyValue);
+ return new DateTimeType(calendar);
+ } else if (Integer.class.isAssignableFrom(dataType)) {
+ return new DecimalType((Integer) propertyValue);
+ } else if (BigDecimal.class.isAssignableFrom(dataType)) {
+ return new DecimalType((BigDecimal) propertyValue);
+ } else if (Boolean.class.isAssignableFrom(dataType)) {
+ if ((Boolean) propertyValue) {
+ return OnOffType.ON;
+ } else {
+ return OnOffType.OFF;
+ }
+ } else if (Temperature.class.isAssignableFrom(dataType)) {
+ return new DecimalType(((Temperature) propertyValue).toLocalTemperature());
+ } else {
+ return new StringType(propertyValue.toString());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void internalReceiveCommand(String itemName, Command command) {
+ logger.trace("internalReceiveCommand(item='{}', command='{}')", itemName, command);
+ commandEcobee(itemName, command);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void internalReceiveUpdate(final String itemName, final State newState) {
+ logger.trace("Received update (item='{}', state='{}')", itemName, newState.toString());
+ if (!isEcho(itemName, newState)) {
+ updateEcobee(itemName, newState);
+ }
+ }
+
+ /**
+ * Perform the given {@code command} against all targets referenced in {@code itemName}.
+ *
+ * @param command
+ * the command to execute
+ * @param the
+ * target(s) against which to execute this command
+ */
+ private void commandEcobee(final String itemName, final Command command) {
+ if (command instanceof State) {
+ updateEcobee(itemName, (State) command);
+ }
+ }
+
+ private boolean isEcho(String itemName, State state) {
+ String ignoreEventListKey = itemName + state.toString();
+ if (ignoreEventList.remove(ignoreEventListKey)) {
+ logger.trace(
+ "We received this event (item='{}', state='{}') from Ecobee, so we don't send it back again -> ignore!",
+ itemName, state.toString());
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Send the {@code newState} for the given {@code itemName} to Ecobee.
+ *
+ * @param itemName
+ * @param newState
+ */
+ private void updateEcobee(final String itemName, final State newState) {
+
+ // Find the first binding provider for this itemName.
+ EcobeeBindingProvider provider = null;
+ String selectionMatch = null;
+ for (EcobeeBindingProvider p : this.providers) {
+ selectionMatch = p.getThermostatIdentifier(itemName);
+ if (selectionMatch != null) {
+ provider = p;
+ break;
+ }
+ }
+
+ if (provider == null) {
+ logger.warn("no matching binding provider found [itemName={}, newState={}]", itemName, newState);
+ return;
+ } else {
+ final Selection selection = new Selection(selectionMatch);
+ List functions = null;
+ logger.debug("Selection for update: {}", selection);
+
+ String property = provider.getProperty(itemName);
+
+ try {
+ final Thermostat thermostat = new Thermostat(null);
+
+ logger.debug("About to set property '{}' to '{}'", property, newState);
+
+ thermostat.setProperty(property, newState);
+
+ logger.debug("Thermostat for update: {}", thermostat);
+
+ OAuthCredentials oauthCredentials = getOAuthCredentials(provider.getUserid(itemName));
+
+ if (oauthCredentials == null) {
+ logger.warn("Unable to locate credentials for item {}; aborting update.", itemName);
+ return;
+ }
+
+ if (oauthCredentials.noAccessToken()) {
+ if (!oauthCredentials.refreshTokens()) {
+ logger.warn("Sending update skipped.");
+ return;
+ }
+ }
+
+ UpdateThermostatRequest request = new UpdateThermostatRequest(oauthCredentials.accessToken, selection,
+ functions, thermostat);
+ ApiResponse response = request.execute();
+ if (response.isError()) {
+ final Status status = response.getStatus();
+ if (status.isAccessTokenExpired()) {
+ if (oauthCredentials.refreshTokens()) {
+ updateEcobee(itemName, newState);
+ }
+ } else {
+ logger.error("Error updating thermostat(s): {}", response);
+ }
+ }
+ } catch (Exception e) {
+ logger.error("Unable to update thermostat(s)", e);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void bindingChanged(BindingProvider provider, String itemName) {
+
+ // Forget prior revisions because we may be concerned with
+ // different thermostats or properties than before.
+ if (provider instanceof EcobeeBindingProvider) {
+ this.lastRevisionMap.clear();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void allBindingsChanged(BindingProvider provider) {
+
+ // Forget prior revisions because we may be concerned with
+ // different thermostats or properties than before.
+ if (provider instanceof EcobeeBindingProvider) {
+ this.lastRevisionMap.clear();
+ }
+ }
+
+ /**
+ * Returns the cached {@link OAuthCredentials} for the given {@code userid}. If their is no such cached
+ * {@link OAuthCredentials} element, the cache is searched with the {@code DEFAULT_USER}. If there is still no
+ * cached element found {@code NULL} is returned.
+ *
+ * @param userid
+ * the userid to find the {@link OAuthCredentials}
+ * @return the cached {@link OAuthCredentials} or {@code NULL}
+ */
+ private OAuthCredentials getOAuthCredentials(String userid) {
+ if (credentialsCache.containsKey(userid)) {
+ return credentialsCache.get(userid);
+ } else {
+ return credentialsCache.get(DEFAULT_USER_ID);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void updated(Dictionary config) throws ConfigurationException {
+ if (config != null) {
+
+ // to override the default refresh interval one has to add a
+ // parameter to openhab.cfg like ecobee:refresh=120000
+ String refreshIntervalString = (String) config.get(CONFIG_REFRESH);
+ if (isNotBlank(refreshIntervalString)) {
+ refreshInterval = Long.parseLong(refreshIntervalString);
+ }
+ // to override the default usage of Fahrenheit one has to add a
+ // parameter to openhab.cfg, as in ecobee:tempscale=C
+ String tempScaleString = (String) config.get(CONFIG_TEMP_SCALE);
+ if (isNotBlank(tempScaleString)) {
+ try {
+ Temperature.setLocalScale(Temperature.Scale.forValue(tempScaleString));
+ } catch (IllegalArgumentException iae) {
+ throw new ConfigurationException(CONFIG_TEMP_SCALE, "Unsupported temperature scale '"
+ + tempScaleString + "'.");
+ }
+ }
+
+ Enumeration configKeys = config.keys();
+ while (configKeys.hasMoreElements()) {
+ String configKey = (String) configKeys.nextElement();
+
+ // the config-key enumeration contains additional keys that we
+ // don't want to process here ...
+ if (CONFIG_REFRESH.equals(configKey) || CONFIG_TEMP_SCALE.equals(configKey)
+ || "service.pid".equals(configKey)) {
+ continue;
+ }
+
+ String userid;
+ String configKeyTail;
+
+ if (configKey.contains(".")) {
+ String[] keyElements = configKey.split("\\.");
+ userid = keyElements[0];
+ configKeyTail = keyElements[1];
+
+ } else {
+ userid = DEFAULT_USER_ID;
+ configKeyTail = configKey;
+ }
+
+ OAuthCredentials credentials = credentialsCache.get(userid);
+ if (credentials == null) {
+ credentials = new OAuthCredentials(userid);
+ credentialsCache.put(userid, credentials);
+ }
+
+ String value = (String) config.get(configKey);
+
+ if (CONFIG_APP_KEY.equals(configKeyTail)) {
+ credentials.appKey = value;
+ } else if (CONFIG_SCOPE.equals(configKeyTail)) {
+ credentials.scope = value;
+ } else {
+ throw new ConfigurationException(configKey, "the given configKey '" + configKey + "' is unknown");
+ }
+ }
+
+ // Verify the completeness of each OAuthCredentials entry
+ // to make sure we can get started.
+
+ boolean properlyConfigured = true;
+
+ for (String userid : credentialsCache.keySet()) {
+ OAuthCredentials oauthCredentials = getOAuthCredentials(userid);
+ String userString = (DEFAULT_USER_ID.equals(userid)) ? "" : (userid + ".");
+ if (oauthCredentials.appKey == null) {
+ logger.error("Required ecobee:{}{} is missing.", userString, CONFIG_APP_KEY);
+ properlyConfigured = false;
+ break;
+ }
+ if (oauthCredentials.scope == null) {
+ logger.error("Required ecobee:{}{} is missing.", userString, CONFIG_SCOPE);
+ properlyConfigured = false;
+ break;
+ }
+ }
+
+ setProperlyConfigured(properlyConfigured);
+ }
+ }
+
+ /**
+ * Creates the necessary {@link Selection} object to request all information required from the Ecobee API for all
+ * thermostats and sub-objects that have a binding, per set of credentials configured in openhab.cfg. One
+ * {@link ThermostatRequest} can then query all information in one go.
+ *
+ * @param oauthCredentials
+ * constrain the resulting Selection object to only select the thermostats which the configuration
+ * indicates can be reached using these credentials.
+ * @returns the Selection object, or null
if only an unsuitable Selection is possible.
+ */
+ private Selection createSelection(OAuthCredentials oauthCredentials) {
+ final Selection selection = new Selection(SelectionType.THERMOSTATS, null);
+ final Set thermostatIdentifiers = new HashSet();
+
+ for (final EcobeeBindingProvider provider : this.providers) {
+ for (final String itemName : provider.getItemNames()) {
+
+ final String thermostatIdentifier = provider.getThermostatIdentifier(itemName);
+ final String property = provider.getProperty(itemName);
+
+ /*
+ * We are only concerned with inbound items, so there would be no point to including the criteria for
+ * this item.
+ *
+ * We are also only concerned with items that can be reached by the given credentials.
+ */
+
+ if (!provider.isInBound(itemName)
+ || oauthCredentials != getOAuthCredentials(provider.getUserid(itemName))) {
+ continue;
+ }
+
+ thermostatIdentifiers.add(thermostatIdentifier);
+
+ if (property.startsWith("settings")) {
+ selection.setIncludeSettings(true);
+ } else if (property.startsWith("runtime")) {
+ selection.setIncludeRuntime(true);
+ } else if (property.startsWith("extendedRuntime")) {
+ selection.setIncludeExtendedRuntime(true);
+ } else if (property.startsWith("electricity")) {
+ selection.setIncludeElectricity(true);
+ } else if (property.startsWith("devices")) {
+ selection.setIncludeDevice(true);
+ } else if (property.startsWith("electricity")) {
+ selection.setIncludeElectricity(true);
+ } else if (property.startsWith("location")) {
+ selection.setIncludeLocation(true);
+ } else if (property.startsWith("technician")) {
+ selection.setIncludeTechnician(true);
+ } else if (property.startsWith("utility")) {
+ selection.setIncludeUtility(true);
+ } else if (property.startsWith("management")) {
+ selection.setIncludeManagement(true);
+ } else if (property.startsWith("weather")) {
+ selection.setIncludeWeather(true);
+ } else if (property.startsWith("events")) {
+ selection.setIncludeEvents(true);
+ } else if (property.startsWith("program")) {
+ selection.setIncludeProgram(true);
+ } else if (property.startsWith("houseDetails")) {
+ selection.setIncludeHouseDetails(true);
+ } else if (property.startsWith("oemCfg")) {
+ selection.setIncludeOemCfg(true);
+ } else if (property.startsWith("equipmentStatus")) {
+ selection.setIncludeEquipmentStatus(true);
+ } else if (property.startsWith("notificationSettings")) {
+ selection.setIncludeNotificationSettings(true);
+ } else if (property.startsWith("privacy")) {
+ selection.setIncludePrivacy(true);
+ } else if (property.startsWith("version")) {
+ selection.setIncludeVersion(true);
+ }
+ }
+ }
+
+ if (thermostatIdentifiers.isEmpty()) {
+ logger.info("No Ecobee in-bindings have been found for selection.");
+ return null;
+ }
+
+ // include all the thermostats we found in the bindings
+ selection.setSelectionMatch(thermostatIdentifiers);
+
+ return selection;
+ }
+
+ /**
+ * This internal class holds the different credentials necessary for the OAuth2 flow to work. It also provides basic
+ * methods to refresh the tokens.
+ *
+ *
+ * OAuth States
+ *
+ *
+ *
+ * authToken |
+ * refreshToken |
+ * accessToken |
+ * State |
+ *
+ *
+ *
+ * null |
+ * |
+ * |
+ * authorize |
+ *
+ *
+ * non-null |
+ * null |
+ * |
+ * request tokens |
+ *
+ *
+ * non-null |
+ * non-null |
+ * null |
+ * refresh tokens |
+ *
+ *
+ * non-null |
+ * non-null |
+ * non-null |
+ * if expired, refresh if any error, authorize |
+ *
+ *
+ *
+ *
+ * @author John Cocula
+ * @since 1.7.0
+ */
+ static class OAuthCredentials {
+
+ private static final String AUTH_TOKEN = "authToken";
+ private static final String REFRESH_TOKEN = "refreshToken";
+ private static final String ACCESS_TOKEN = "accessToken";
+
+ private String userid;
+
+ /**
+ * The private app key needed in order to interact with the Ecobee API. This must be provided in the
+ * openhab.cfg
file.
+ */
+ private String appKey;
+
+ /**
+ * The scope needed when authorizing this client to the Ecobee API.
+ *
+ * @see AuthorizeRequest
+ */
+ private String scope;
+
+ /**
+ * The authorization token needed to request the refresh and access tokens. Obtained and persisted when
+ * {@code authorize()} is called.
+ *
+ * @see AuthorizeRequest
+ * @see #authorize()
+ */
+ private String authToken;
+
+ /**
+ * The refresh token to access the Ecobee API. Initial token is received using the authToken
,
+ * periodically refreshed using the previous refreshToken, and saved in persistent storage so it can be used
+ * across activations.
+ *
+ * @see TokenRequest
+ * @see RefreshTokenRequest
+ */
+ private String refreshToken;
+
+ /**
+ * The access token to access the Ecobee API. Automatically renewed from the API using the refresh token and
+ * persisted for use across activations.
+ *
+ * @see #refreshTokens()
+ */
+ private String accessToken;
+
+ public OAuthCredentials(String userid) {
+
+ try {
+ this.userid = userid;
+ load();
+ } catch (Exception e) {
+ throw new EcobeeException("Cannot create OAuthCredentials.", e);
+ }
+ }
+
+ private Preferences getPrefsNode() {
+ return Preferences.userRoot().node("org.openhab.ecobee." + userid);
+ }
+
+ private void load() {
+ Preferences prefs = getPrefsNode();
+ this.authToken = prefs.get(AUTH_TOKEN, null);
+ this.refreshToken = prefs.get(REFRESH_TOKEN, null);
+ this.accessToken = prefs.get(ACCESS_TOKEN, null);
+ }
+
+ private void save() {
+ Preferences prefs = getPrefsNode();
+ if (this.authToken != null) {
+ prefs.put(AUTH_TOKEN, this.authToken);
+ } else {
+ prefs.remove(AUTH_TOKEN);
+ }
+
+ if (this.refreshToken != null) {
+ prefs.put(REFRESH_TOKEN, this.refreshToken);
+ } else {
+ prefs.remove(REFRESH_TOKEN);
+ }
+
+ if (this.accessToken != null) {
+ prefs.put(ACCESS_TOKEN, this.accessToken);
+ } else {
+ prefs.remove(ACCESS_TOKEN);
+ }
+ }
+
+ public boolean noAccessToken() {
+ return this.accessToken == null;
+ }
+
+ public void authorize() {
+ logger.trace("Authorizing this binding with the Ecobee API.");
+
+ final AuthorizeRequest request = new AuthorizeRequest(this.appKey, this.scope);
+ logger.trace("Request: {}", request);
+
+ final AuthorizeResponse response = request.execute();
+ logger.trace("Response: {}", response);
+
+ this.authToken = response.getAuthToken();
+ this.refreshToken = null;
+ this.accessToken = null;
+ save();
+
+ logger.info("#########################################################################################");
+ logger.info("# Ecobee-Integration: U S E R I N T E R A C T I O N R E Q U I R E D !!");
+ logger.info("# 1. Login to www.ecobee.com using your '{}' account", this.userid);
+ logger.info("# 2. Enter the PIN '{}' in My Apps within the next {} minutes.", response.getEcobeePin(),
+ response.getExpiresIn());
+ logger.info("# NOTE: Any API attempts will fail in the meantime.");
+ logger.info("#########################################################################################");
+ }
+
+ /**
+ * This method attempts to advance the authorization process by retrieving the tokens needed to use the API. It
+ * returns true
if there is reason to believe that an immediately subsequent API call would
+ * succeed.
+ *
+ * This method requests access and refresh tokens to use the Ecobee API. If there is a refreshToken
+ * , it will be used to obtain the tokens, but if there is only an authToken
, that will be used
+ * instead.
+ *
+ * @return true
if there is reason to believe that an immediately subsequent API call would
+ * succeed.
+ */
+ public boolean refreshTokens() {
+ if (this.authToken == null) {
+ authorize();
+ return false;
+ } else {
+ logger.trace("Refreshing tokens.");
+
+ Request request;
+
+ if (this.refreshToken == null) {
+ request = new TokenRequest(this.authToken, this.appKey);
+ } else {
+ request = new RefreshTokenRequest(this.refreshToken, this.appKey);
+ }
+ logger.trace("Request: {}", request);
+
+ final TokenResponse response = (TokenResponse) request.execute();
+ logger.trace("Response: {}", response);
+
+ if (response.isError()) {
+ logger.error("Error retrieving tokens: {}", response.getError());
+ return false;
+ } else {
+ this.refreshToken = response.getRefreshToken();
+ this.accessToken = response.getAccessToken();
+ save();
+ return true;
+ }
+ }
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeException.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeException.java
new file mode 100644
index 00000000000..6561ab829ba
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeException.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal;
+
+/**
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public class EcobeeException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public EcobeeException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public EcobeeException(final Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeGenericBindingProvider.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeGenericBindingProvider.java
new file mode 100644
index 00000000000..011b80c1493
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/EcobeeGenericBindingProvider.java
@@ -0,0 +1,268 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.openhab.binding.ecobee.EcobeeBindingProvider;
+import org.openhab.binding.ecobee.internal.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.
+ *
+ *
+ * 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.
+ *
+ *
+ * The first character is then followed by a section between square brackets ([ and ] characters):
+ *
+ *
+ * [<thermostat>#<property>]
+ *
+ *
+ * Where thermostat
is a decimal thermostat identifier for in, out and bidirectional bindings.
+ *
+ *
+ * For out bindings only, thermostat
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 *
character).
+ *
+ *
+ * In the case of out bindings for EMS or Utility accounts, the thermostat
criteria can be a path to a
+ * management set (for example, /Toronto/Campus/BuildingA
).
+ *
+ *
+ * The thermostat
specification can be optionally prepended with a specific app instance name as specified
+ * in openhab.cfg
, as in condo.123456789
when you have specified
+ * ecobee:condo.scope
and ecobee:condo.appkey
properties in openhab.cfg
.
+ *
+ *
+ * property
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.
+ *
+ *
+ *
+ *
+ * Property |
+ * In |
+ * Out
+ * |
+ *
+ *
+ * name |
+ * X |
+ * X |
+ *
+ *
+ * runtime.actualTemperature |
+ * X |
+ * |
+ *
+ *
+ * runtime.actualHumidity |
+ * X |
+ * |
+ *
+ *
+ * settings.hvacMode |
+ * X |
+ * X |
+ *
+ *
+ *
+ *
+ *
+ * Example bindings:
+ *
+ * { ecobee="<[123456789#name]" }
+ *
+ * Return the name of the thermostat whose ID is 123456789 using the default Ecobee app instance (configured in
+ * openhab.cfg).
+ * { ecobee="<[condo.987654321#runtime.actualTemperature]" }
+ *
+ * Return the current temperature read by the thermostat using the condo account at ecobee.com.
+ * { ecobee=">[543212345#settings.fanMinOnTime]" }
+ *
+ * Set the minimum number of minutes per hour the fan will run on thermostat ID 543212345.
+ * { ecobee=">[*#settings.hvacMode]" }
+ *
+ * Change the HVAC mode to one of "auto"
, "auxHeatOnly"
, "cool"
,
+ * "heat"
, or "off"
on all thermostats registered in the default app instance.
+ * -
+ *
{ ecobee=">[lakehouse.*#settings.backlightSleepIntensity]" }
+ *
+ * Changes the backlight sleep intensity on all thermostats at the lake house (meaning, all thermostats registered to
+ * the lakehouse Ecobee account).
+ *
+ *
+ * @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}
+ */
+ 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);
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractAuthResponse.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractAuthResponse.java
new file mode 100644
index 00000000000..42acf6b3556
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractAuthResponse.java
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * Base class for all Ecobee authorization responses. The members of this abstract class will de-serialize to
+ * null
s if there was no error, but the concrete subclasses will instead be filled in.
+ *
+ * @see Authorization
+ * Requests and Errors
+ * @author John Cocula
+ * @since 1.7.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public abstract class AbstractAuthResponse extends AbstractMessage implements Response {
+
+ @JsonProperty("error")
+ private String error;
+ @JsonProperty("error_description")
+ private String errorDescription;
+ @JsonProperty("error_uri")
+ private String errorURI;
+
+ /**
+ * @return the error type
+ */
+ @JsonProperty("error")
+ public String getError() {
+ return this.error;
+ }
+
+ /**
+ * @return the error description
+ */
+ @JsonProperty("error_description")
+ public String getErrorDescription() {
+ return this.errorDescription;
+ }
+
+ /**
+ * @return the error URI, explaining why this error may have occurred.
+ */
+ @JsonProperty("error_uri")
+ public String getErrorURI() {
+ return this.errorURI;
+ }
+
+ @Override
+ public String getResponseMessage() {
+ return this.error;
+ }
+
+ @Override
+ public boolean isError() {
+ return this.error != null;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ if (this.error != null) {
+ builder.append("error", this.error);
+ }
+ if (this.errorDescription != null) {
+ builder.append("errorDescription", this.errorDescription);
+ }
+ if (this.errorURI != null) {
+ builder.append("errorURI", this.errorURI);
+ }
+
+ return builder.toString();
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractFunction.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractFunction.java
new file mode 100644
index 00000000000..b78ef4d604b
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractFunction.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * The function object is defined by its functionType and one or more additional properties. The property list is
+ * variable depending on the type of function.
+ *
+ *
+ * Functions are used to perform more complex operations on a thermostat or user which are too complex with simple
+ * property modifications. Functions are used to modify read-only objects where appropriate.
+ *
+ *
+ * Each function takes different parameters.
+ *
+ * @see Function
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public abstract class AbstractFunction extends AbstractMessagePart {
+
+ // TODO needs to be in specific thermostat's local timezone (@watou)
+ protected final static DateFormat ymd = new SimpleDateFormat("yyyy-MM-dd");
+ protected final static DateFormat hms = new SimpleDateFormat("HH:mm:ss");
+
+ private String type;
+ private Map params;
+
+ /**
+ * Construct a function of given type with zero params.
+ *
+ * @param type
+ * the function type name
+ */
+ protected AbstractFunction(String type) {
+ this.type = type;
+ }
+
+ protected final Map makeParams() {
+ if (params == null)
+ params = new HashMap();
+ return params;
+ }
+
+ /**
+ * @return the function type name See the type name in the function documentation
+ */
+ @JsonProperty("type")
+ public String getType() {
+ return this.type;
+ }
+
+ /**
+ * @return a map of key=value
pairs as the parameters to the function. See individual function
+ * documentation for the properties.
+ */
+ @JsonProperty("params")
+ public Map getParams() {
+ return this.params;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("type", this.type);
+ builder.append("params", this.params);
+
+ return builder.toString();
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractMessage.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractMessage.java
new file mode 100644
index 00000000000..1eb384aa752
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractMessage.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import static org.apache.commons.lang.builder.ToStringStyle.SHORT_PREFIX_STYLE;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+/**
+ * Base class for all messages.
+ *
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public class AbstractMessage {
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+
+ return builder.toString();
+ }
+
+ protected final ToStringBuilder createToStringBuilder() {
+ return new ToStringBuilder(this, SHORT_PREFIX_STYLE);
+ }
+
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractMessagePart.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractMessagePart.java
new file mode 100644
index 00000000000..d4ffb6d88b6
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractMessagePart.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import static org.apache.commons.lang.builder.ToStringStyle.SHORT_PREFIX_STYLE;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+/**
+ * Base class for all message parts; i.e., objects within a response.
+ *
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public class AbstractMessagePart {
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+
+ return builder.toString();
+ }
+
+ protected final ToStringBuilder createToStringBuilder() {
+ return new ToStringBuilder(this, SHORT_PREFIX_STYLE);
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractRequest.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractRequest.java
new file mode 100644
index 00000000000..335b087d8cd
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AbstractRequest.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import java.text.SimpleDateFormat;
+import java.util.Properties;
+
+import org.codehaus.jackson.map.JsonMappingException;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
+import org.openhab.binding.ecobee.internal.EcobeeException;
+
+/**
+ * Base class for all Ecobee API requests.
+ *
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public abstract class AbstractRequest extends AbstractMessage implements Request {
+
+ protected static final String HTTP_GET = "GET";
+
+ protected static final String HTTP_POST = "POST";
+
+ protected static final String API_BASE_URL = "https://api.ecobee.com/";
+
+ protected static final Properties HTTP_HEADERS;
+
+ protected static final int HTTP_REQUEST_TIMEOUT = 10000;
+
+ protected static final ObjectMapper JSON = new ObjectMapper();
+
+ static {
+ HTTP_HEADERS = new Properties();
+ HTTP_HEADERS.put("Content-Type", "application/json;charset=UTF-8");
+ HTTP_HEADERS.put("User-Agent", "ecobee-openhab-api/1.0");
+
+ // do not serialize null values
+ JSON.setSerializationInclusion(Inclusion.NON_NULL);
+
+ // *most* dates in the JSON are in this format
+ JSON.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
+ }
+
+ protected final RuntimeException newException(final String message, final Exception cause, final String url,
+ final String json) {
+ if (cause instanceof JsonMappingException) {
+ return new EcobeeException("Could not parse JSON from URL '" + url + "': " + json, cause);
+ }
+
+ return new EcobeeException(message, cause);
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AcknowledgeFunction.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AcknowledgeFunction.java
new file mode 100644
index 00000000000..93f9511f6e0
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AcknowledgeFunction.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import org.codehaus.jackson.annotate.JsonValue;
+
+/**
+ * The acknowledge function allows an alert to be acknowledged.
+ *
+ * @see Acknowledge
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public final class AcknowledgeFunction extends AbstractFunction {
+
+ /**
+ * The type of acknowledgment.
+ */
+ public static enum AckType {
+ ACCEPT("accept"), DECLINE("decline"), DEFER("defer"), UNACKNOWLEDGED("unacknowledged");
+
+ private final String type;
+
+ private AckType(String type) {
+ this.type = type;
+ }
+
+ @Override
+ @JsonValue
+ public String toString() {
+ return this.type;
+ }
+ }
+
+ public AcknowledgeFunction(String thermostatIdentifier, String ackRef, AckType ackType, Boolean remindMeLater) {
+ super("acknowledge");
+
+ if (thermostatIdentifier == null || ackRef == null || ackType == null) {
+ throw new IllegalArgumentException("thermostatIdentifier, ackRef and ackType are required.");
+ }
+
+ makeParams().put("thermostatIdentifier", thermostatIdentifier);
+ makeParams().put("ackRef", ackRef);
+ makeParams().put("ackType", ackType);
+ if (remindMeLater != null) {
+ makeParams().put("remindMeLater", remindMeLater);
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/ApiResponse.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/ApiResponse.java
new file mode 100644
index 00000000000..06c48e6ddaa
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/ApiResponse.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * Base class for all Ecobee API responses.
+ *
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public class ApiResponse extends AbstractMessage implements Response {
+
+ private Status status;
+
+ @JsonProperty("status")
+ public Status getStatus() {
+ return this.status;
+ }
+
+ @Override
+ public String getResponseMessage() {
+ return status.toString();
+ }
+
+ @Override
+ public boolean isError() {
+ return status.getCode() != 0;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ if (this.status != null) {
+ builder.append("status", this.status);
+ }
+
+ return builder.toString();
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AuthorizeRequest.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AuthorizeRequest.java
new file mode 100644
index 00000000000..44c8f9fe74d
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AuthorizeRequest.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import static org.openhab.io.net.http.HttpUtil.executeUrl;
+
+import org.apache.commons.httpclient.util.URIUtil;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.openhab.binding.ecobee.internal.EcobeeException;
+
+/**
+ * The ecobee PIN authorization method is designed to support any 3rd party device, be it a mobile phone, tablet,
+ * desktop widget or remote server. This authorization method allows a 3rd party application to obtain an authorization
+ * code and a 4 byte alphabetic string which can be displayed to the user. The user then logs into the ecobee Portal and
+ * registers the application using the PIN provided. Once this step is completed, the 3rd party application is able to
+ * request the access and refresh tokens.
+ *
+ * @see AuthorizeResponse
+ * @see PIN
+ * Authorization Strategy
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public class AuthorizeRequest extends AbstractRequest {
+
+ private static final String RESOURCE_URL = API_BASE_URL + "authorize";
+
+ private String appKey;
+ private String scope;
+
+ /**
+ * Construct an authorization request.
+ *
+ * @param appKey
+ * the application key for your application (this binding)
+ * @param scope
+ * the scope the application requests from the user
+ */
+ public AuthorizeRequest(final String appKey, final String scope) {
+ assert appKey != null : "appKey must not be null!";
+ assert scope != null : "scope must not be null!";
+
+ this.appKey = appKey;
+ this.scope = scope;
+ }
+
+ @Override
+ public AuthorizeResponse execute() {
+ final String url = buildQueryString();
+ String json = null;
+
+ try {
+ json = executeQuery(url);
+
+ final AuthorizeResponse response = JSON.readValue(json, AuthorizeResponse.class);
+
+ return response;
+ } catch (final Exception e) {
+ throw newException("Could not get authorization.", e, url, json);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("appKey", this.appKey);
+ builder.append("scope", this.scope);
+ return builder.toString();
+ }
+
+ protected String executeQuery(final String url) {
+ return executeUrl(HTTP_GET, url, HTTP_HEADERS, null, null, HTTP_REQUEST_TIMEOUT);
+ }
+
+ private String buildQueryString() {
+ final StringBuilder urlBuilder = new StringBuilder(RESOURCE_URL);
+
+ try {
+ urlBuilder.append("?response_type=ecobeePin");
+ urlBuilder.append("&client_id=");
+ urlBuilder.append(appKey);
+ urlBuilder.append("&scope=");
+ urlBuilder.append(scope);
+ return URIUtil.encodeQuery(urlBuilder.toString());
+ } catch (final Exception e) {
+ throw new EcobeeException(e);
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AuthorizeResponse.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AuthorizeResponse.java
new file mode 100644
index 00000000000..6e20b4ec744
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/AuthorizeResponse.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * This class represents the response to the /authorize
endpoint. Upon success, the PIN, authorization
+ * token, scope, the PIN expiration and minimum polling interval for PINs is returned.
+ *
+ *
+ * At this point, the application should display the PIN to the user and request that they log into their web portal and
+ * register the application using the PIN in their My Apps widget.
+ *
+ * @see AuthorizeRequest
+ * @see PIN
+ * Authorization Strategy
+ * @author John Cocula
+ * @since 1.7.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AuthorizeResponse extends AbstractAuthResponse {
+
+ @JsonProperty("ecobeePin")
+ private String ecobeePin;
+ @JsonProperty("code")
+ private String authToken;
+ @JsonProperty("scope")
+ private String scope;
+ @JsonProperty("expires_in")
+ private Integer expiresIn;
+ @JsonProperty("interval")
+ private Integer interval;
+
+ /**
+ * @return the PIN a user enters in the web portal
+ */
+ @JsonProperty("ecobeePin")
+ public String getEcobeePin() {
+ return this.ecobeePin;
+ }
+
+ /**
+ * @return the authorization token needed to request the access and refresh tokens
+ */
+ @JsonProperty("code")
+ public String getAuthToken() {
+ return this.authToken;
+ }
+
+ /**
+ * @return the requested Scope from the original request. This must match the original request.
+ */
+ @JsonProperty("scope")
+ public String getScope() {
+ return this.scope;
+ }
+
+ /**
+ * @return the number of minutes until the PIN expires. Ensure you inform the user how much time they have.
+ */
+ @JsonProperty("expires_in")
+ public Integer getExpiresIn() {
+ return this.expiresIn;
+ }
+
+ /**
+ * @return the minimum amount of seconds which must pass between polling attempts for a token.
+ */
+ @JsonProperty("interval")
+ public Integer getInterval() {
+ return this.interval;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("ecobeePin", this.ecobeePin);
+ builder.append("authToken", this.authToken);
+ builder.append("scope", this.scope);
+ builder.append("expiresIn", this.expiresIn);
+ builder.append("interval", this.interval);
+
+ return builder.toString();
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/ControlPlugFunction.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/ControlPlugFunction.java
new file mode 100644
index 00000000000..ab45110dd0a
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/ControlPlugFunction.java
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import java.util.Date;
+
+import org.codehaus.jackson.annotate.JsonValue;
+
+/**
+ * Control the on/off state of a plug by setting a hold on the plug. Creates a hold for the on or off state of the plug
+ * for the specified duration. Note that an event is created regardless of whether the program is in the same state as
+ * the requested state.
+ *
+ * @see ControlPlug
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public final class ControlPlugFunction extends AbstractFunction {
+
+ /**
+ * The state to put the plug into. Valid values: on, off, resume.
+ */
+ public static enum PlugState {
+
+ /**
+ * Sets the plug into the on state for the start/end period specified. Creates a plug hold in the on state.
+ */
+ ON("on"),
+
+ /**
+ * Sets the plug into the off state for the start/end period specified. Creates a plug hold in the off state.
+ */
+ OFF("off"),
+
+ /**
+ * Causes the plug to resume its regular program and to follow it. Removes the currently active plug hold, if no
+ * hold is currently running, nothing happens. No other optional properties are used.
+ */
+ RESUME("resume");
+
+ private final String state;
+
+ private PlugState(String state) {
+ this.state = state;
+ }
+
+ @Override
+ @JsonValue
+ public String toString() {
+ return this.state;
+ }
+ }
+
+ /**
+ * Construct a ControlPlug function.
+ *
+ * @param plugName
+ * the name of the plug. Ensure each plug has a unique name. Required.
+ * @param plugState
+ * the state to put the plug into. Valid values: on, off, resume. Required.
+ * @param startDateTime
+ * the start date and time in thermostat time
+ * @param endDateTime
+ * the end date and time in thermostat time
+ * @param holdType
+ * the hold duration type
+ * @param holdHours
+ * the number of hours to hold for, used and required if holdType='holdHours'
+ * @throws IllegalArgumentException
+ * if the parameters are incorrect.
+ */
+ public ControlPlugFunction(final String plugName, final PlugState plugState, final Date startDateTime,
+ final Date endDateTime, final HoldType holdType, final Integer holdHours) {
+ super("controlPlug");
+
+ if (plugName == null || plugState == null) {
+ throw new IllegalArgumentException("plugName and plugState arguments are required.");
+ }
+ if (holdType == HoldType.DATE_TIME && endDateTime == null) {
+ throw new IllegalArgumentException("End date/time is required for dateTime hold type.");
+ }
+ if (holdType == HoldType.HOLD_HOURS && holdHours == null) {
+ throw new IllegalArgumentException("holdHours must be specified when using holdHours hold type.");
+ }
+
+ makeParams().put("plugName", plugName);
+ makeParams().put("plugState", plugState);
+ if (startDateTime != null) {
+ makeParams().put("startDate", ymd.format(startDateTime));
+ makeParams().put("startTime", hms.format(startDateTime));
+ }
+ if (endDateTime != null) {
+ makeParams().put("endDate", ymd.format(endDateTime));
+ makeParams().put("endTime", hms.format(endDateTime));
+ }
+ if (holdType != null) {
+ makeParams().put("holdType", holdType);
+ }
+ if (holdHours != null) {
+ makeParams().put("holdHours", holdHours);
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/CreateVacationFunction.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/CreateVacationFunction.java
new file mode 100644
index 00000000000..db0b00a90f5
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/CreateVacationFunction.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import java.util.Date;
+
+/**
+ * The create vacation function creates a vacation event on the thermostat. If the start/end date/times are not provided
+ * for the vacation event, the vacation event will begin immediately and last 14 days.
+ *
+ *
+ * If both the coolHoldTemp
and heatHoldTemp
parameters provided to this function have the
+ * same value, and the {@link Thermostat} is in auto mode, then the two values will be adjusted during processing to be
+ * separated by the value stored in {@link Thermostat.Settings#heatCoolMinDelta}.
+ *
+ * @see CreateVacation
+ * @author John Cocula
+ * @author Ecobee
+ * @since 1.7.0
+ */
+public final class CreateVacationFunction extends AbstractFunction {
+
+ public CreateVacationFunction(final String name, final Temperature coolHoldTemp, final Temperature heatHoldTemp,
+ final Date startDateTime, final Date endDateTime, final FanMode fan, final Integer fanMinOnTime) {
+ // TODO doc says String not Integer for fanMinOnTime parameter (@watou)
+ super("createVacation");
+
+ if (name == null || coolHoldTemp == null || heatHoldTemp == null) {
+ throw new IllegalArgumentException("name, coolHoldTemp and heatHoldTemp arguments are required.");
+ }
+
+ makeParams().put("name", name);
+ makeParams().put("coolHoldTemp", coolHoldTemp);
+ makeParams().put("heatHoldTemp", heatHoldTemp);
+ if (startDateTime != null) {
+ makeParams().put("startDate", ymd.format(startDateTime));
+ makeParams().put("startTime", hms.format(startDateTime));
+ }
+ if (endDateTime != null) {
+ makeParams().put("endDate", ymd.format(endDateTime));
+ makeParams().put("endTime", hms.format(endDateTime));
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/DeleteVacationFunction.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/DeleteVacationFunction.java
new file mode 100644
index 00000000000..cf57936e976
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/DeleteVacationFunction.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+/**
+ * The delete vacation function deletes a vacation event from a thermostat. This is the only way to cancel a vacation
+ * event. This method is able to remove vacation events not yet started and scheduled in the future.
+ *
+ * @see DeleteVacation
+ * @author John Cocula
+ * @author Ecobee
+ * @since 1.7.0
+ */
+public class DeleteVacationFunction extends AbstractFunction {
+
+ /**
+ * @param name
+ * the vacation event name to delete
+ */
+ public DeleteVacationFunction(String name) {
+ super("deleteVacation");
+ if (name == null) {
+ throw new IllegalArgumentException("name argument is required.");
+ }
+
+ makeParams().put("name", name);
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/FanMode.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/FanMode.java
new file mode 100644
index 00000000000..ea44f42c95f
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/FanMode.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonValue;
+
+/**
+ * Possible values for fan mode
+ *
+ * @see Event
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public enum FanMode {
+ AUTO("auto"), ON("on");
+
+ private final String mode;
+
+ private FanMode(final String mode) {
+ this.mode = mode;
+ }
+
+ @JsonValue
+ public String value() {
+ return mode;
+ }
+
+ @JsonCreator
+ public static FanMode forValue(String v) {
+ for (FanMode fm : FanMode.values()) {
+ if (fm.mode.equals(v)) {
+ return fm;
+ }
+ }
+ throw new IllegalArgumentException("Invalid fan mode: " + v);
+ }
+
+ @Override
+ public String toString() {
+ return this.mode;
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Group.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Group.java
new file mode 100644
index 00000000000..ff0b1767895
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Group.java
@@ -0,0 +1,353 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import java.util.List;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * A Group object defines the group (and the related Group settings) which a thermostat may belong to. There could be a
+ * number of Groups and also a number of thermostats in each Group. The Group object allows the caller to define the
+ * Group name, which user preferences are shared across all thermostats in that Group, and indeed which Thermostats are
+ * part of that Group.
+ *
+ *
+ * The result is that if you modify the Group settings, for example set the synchronizeAlerts
flag to true,
+ * any {@link Thermostat.Alert} changes made to any thermostat in that group will be shared with the remaining
+ * thermostats in the same group.
+ *
+ *
+ * The Grouping algorithm uses a "first group wins" strategy when a {@link Thermostat} is referenced in multiple groups.
+ * What this means in practice is that when the API request is processed and a Thermostat is referenced in more than one
+ * group, that Thermostat will only be added to the first Group (at head of array) and not to the others.
+ *
+ *
+ * If any of the synchronizeXXX
fields are not supplied they will default to false. So to set all to false
+ * where previously some were set to true the caller can either pass all the synchronizeXXX
fields
+ * explicitly, or pass none and the default will be set for each.
+ *
+ *
+ * The Group object may be modified. However, it is important to note that if the groupRef is not sent by the caller it
+ * is assumed that this is a new group, even if the groupName has not changed, and a new groupRef will be generated and
+ * returned. Therefore when updating a Group the groupRef must always be sent.
+ *
+ *
+ * Also note that if the thermostats list is not sent, or an empty list is sent, the Group will effectively be deleted
+ * as it will no longer contain any thermostats and any group information will be lost.
+ *
+ * @see Group
+ * @author John Cocula
+ * @since 1.7.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Group extends AbstractMessagePart {
+ private String groupRef;
+ private String groupName;
+ private Boolean synchronizeAlerts;
+ private Boolean synchronizeSystemMode;
+ private Boolean synchronizeSchedule;
+ private Boolean synchronizeQuickSave;
+ private Boolean synchronizeReminders;
+ private Boolean synchronizeContractorInfo;
+ private Boolean synchronizeUserPreferences;
+ private Boolean synchronizeUtilityInfo;
+ private Boolean synchronizeLocation;
+ private Boolean synchronizeReset;
+ private Boolean synchronizeVacation;
+ private List thermostats;
+
+ /**
+ * Construct a Group.
+ *
+ * @param groupName
+ * the name for the Group
+ */
+ public Group(@JsonProperty("groupName") final String groupName) {
+ this.groupName = groupName;
+ }
+
+ /**
+ * @return the unique reference Id for the Group.
+ */
+ @JsonProperty("groupRef")
+ public String getGroupRef() {
+ return this.groupRef;
+ }
+
+ /**
+ * @param groupRef
+ * the unique reference Id for the Group. If not supplied in the POST call, and new groupRef will be
+ * generated.
+ */
+ @JsonProperty("groupRef")
+ public void setGroupRef(final String groupRef) {
+ this.groupRef = groupRef;
+ }
+
+ /**
+ * @return the name for the Group
+ */
+ @JsonProperty("groupName")
+ public String getGroupName() {
+ return this.groupName;
+ }
+
+ /**
+ * @return flag for whether to synchronize {@link Thermostat.Alert}s with all other {@link Thermostat}s in the
+ * Group. Default is false.
+ */
+ @JsonProperty("synchronizeAlerts")
+ public Boolean getSynchronizeAlerts() {
+ return this.synchronizeAlerts;
+ }
+
+ /**
+ * @param synchronizeAlerts
+ * flag for whether to synchronize {@link Thermostat.Alert}s with all other {@link Thermostat}s in the
+ * Group. Default is false.
+ */
+ @JsonProperty("synchronizeAlerts")
+ public void setSynchronizeAlerts(final Boolean synchronizeAlerts) {
+ this.synchronizeAlerts = synchronizeAlerts;
+ }
+
+ /**
+ * @return flag for whether to synchronize the Thermostat mode with all other Thermostats in the Group. Default is
+ * false.
+ */
+ @JsonProperty("synchronizeSystemMode")
+ public Boolean getSynchronizeSystemMode() {
+ return this.synchronizeSystemMode;
+ }
+
+ /**
+ * @param synchronizeSystemMode
+ * flag for whether to synchronize the Thermostat mode with all other Thermostats in the Group. Default
+ * is false.
+ */
+ @JsonProperty("synchronizeSystemMode")
+ public void setSynchronizeSystemMode(final Boolean synchronizeSystemMode) {
+ this.synchronizeSystemMode = synchronizeSystemMode;
+ }
+
+ /**
+ * @return flag for whether to synchronize the Thermostat schedule/Program details with all other Thermostats in the
+ * Group. Default is false.
+ */
+ @JsonProperty("synchronizeSchedule")
+ public Boolean getSynchronizeSchedule() {
+ return this.synchronizeSchedule;
+ }
+
+ /**
+ * @param synchronizeSchedule
+ * flag for whether to synchronize the Thermostat schedule/Program details with all other Thermostats in
+ * the Group. Default is false.
+ */
+ @JsonProperty("synchronizeSchedule")
+ public void setSynchronizeSchedule(final Boolean synchronizeSchedule) {
+ this.synchronizeSchedule = synchronizeSchedule;
+ }
+
+ /**
+ * @return flag for whether to synchronize the Thermostat quick save settings with all other Thermostats in the
+ * Group. Default is false.
+ */
+ @JsonProperty("synchronizeQuickSave")
+ public Boolean getSynchronizeQuickSave() {
+ return this.synchronizeQuickSave;
+ }
+
+ /**
+ * @param synchronizeQuickSave
+ * flag for whether to synchronize the Thermostat quick save settings with all other Thermostats in the
+ * Group. Default is false.
+ */
+ @JsonProperty("synchronizeQuickSave")
+ public void setSynchronizeQuickSave(final Boolean synchronizeQuickSave) {
+ this.synchronizeQuickSave = synchronizeQuickSave;
+ }
+
+ /**
+ * @return flag for whether to synchronize the Thermostat reminders with all other Thermostats in the Group. Default
+ * is false.
+ */
+ @JsonProperty("synchronizeReminders")
+ public Boolean getSynchronizeReminders() {
+ return this.synchronizeReminders;
+ }
+
+ /**
+ * @param synchronizeReminders
+ * flag for whether to synchronize the Thermostat reminders with all other Thermostats in the Group.
+ * Default is false.
+ */
+ @JsonProperty("synchronizeReminders")
+ public void setSynchronizeReminders(final Boolean synchronizeReminders) {
+ this.synchronizeReminders = synchronizeReminders;
+ }
+
+ /**
+ * @return flag for whether to synchronize the Thermostat Technician/Contractor Information with all other
+ * Thermostats in the Group. Default is false.
+ */
+ @JsonProperty("synchronizeContractorInfo")
+ public Boolean getSynchronizeContractorInfo() {
+ return this.synchronizeContractorInfo;
+ }
+
+ /**
+ * @param synchronizeContractorInfo
+ * flag for whether to synchronize the Thermostat Technician/Contractor Information with all other
+ * Thermostats in the Group. Default is false.
+ */
+ @JsonProperty("synchronizeContractorInfo")
+ public void setSynchronizeContractorInfo(final Boolean synchronizeContractorInfo) {
+ this.synchronizeContractorInfo = synchronizeContractorInfo;
+ }
+
+ /**
+ * @return flag for whether to synchronize the Thermostat user preferences with all other Thermostats in the Group.
+ * Default is false.
+ */
+ @JsonProperty("synchronizeUserPreferences")
+ public Boolean getSynchronizeUserPreferences() {
+ return this.synchronizeUserPreferences;
+ }
+
+ /**
+ * @param synchronizeUserPreferences
+ * tflag for whether to synchronize the Thermostat user preferences with all other Thermostats in the
+ * Group. Default is false.
+ */
+ @JsonProperty("synchronizeUserPreferences")
+ public void setSynchronizeUserPreferences(final Boolean synchronizeUserPreferences) {
+ this.synchronizeUserPreferences = synchronizeUserPreferences;
+ }
+
+ /**
+ * @return flag for whether to synchronize the Thermostat utility information with all other Thermostats in the
+ * Group. Default is false.
+ */
+ @JsonProperty("synchronizeUtilityInfo")
+ public Boolean getSynchronizeUtilityInfo() {
+ return this.synchronizeUtilityInfo;
+ }
+
+ /**
+ * @param synchronizeUtilityInfo
+ * flag for whether to synchronize the Thermostat utility information with all other Thermostats in the
+ * Group. Default is false.
+ */
+ @JsonProperty("synchronizeUtilityInfo")
+ public void setSynchronizeUtilityInfo(final Boolean synchronizeUtilityInfo) {
+ this.synchronizeUtilityInfo = synchronizeUtilityInfo;
+ }
+
+ /**
+ * @return flag for whether to synchronize the Thermostat Location with all other Thermostats in the Group. Default
+ * is false.
+ */
+ @JsonProperty("synchronizeLocation")
+ public Boolean getSynchronizeLocation() {
+ return this.synchronizeLocation;
+ }
+
+ /**
+ * @param synchronizeLocation
+ * flag for whether to synchronize the Thermostat Location with all other Thermostats in the Group.
+ * Default is false.
+ */
+ @JsonProperty("synchronizeLocation")
+ public void setSynchronizeLocation(final Boolean synchronizeLocation) {
+ this.synchronizeLocation = synchronizeLocation;
+ }
+
+ /**
+ * @return flag for whether to synchronize the Thermostat reset with all other Thermostats in the Group. Default is
+ * false.
+ */
+ @JsonProperty("synchronizeReset")
+ public Boolean getSynchronizeReset() {
+ return this.synchronizeReset;
+ }
+
+ /**
+ * @param synchronizeReset
+ * flag for whether to synchronize the Thermostat reset with all other Thermostats in the Group. Default
+ * is false.
+ */
+ @JsonProperty("synchronizeReset")
+ public void setSynchronizeReset(final Boolean synchronizeReset) {
+ this.synchronizeReset = synchronizeReset;
+ }
+
+ /**
+ * @return flag for whether to synchronize the Thermostat vacation Program with all other Thermostats in the Group.
+ * Default is false.
+ */
+ @JsonProperty("synchronizeVacation")
+ public Boolean getSynchronizeVacation() {
+ return this.synchronizeVacation;
+ }
+
+ /**
+ * @param synchronizeVacation
+ * flag for whether to synchronize the Thermostat vacation Program with all other Thermostats in the
+ * Group. Default is false.
+ */
+ @JsonProperty("synchronizeVacation")
+ public void setSynchronizeVacation(final Boolean synchronizeVacation) {
+ this.synchronizeVacation = synchronizeVacation;
+ }
+
+ /**
+ * @return the list of Thermostat identifiers which belong to the group. If an empty list is sent the Group will be
+ * deleted.
+ */
+ @JsonProperty("thermostats")
+ public List getThermostats() {
+ return this.thermostats;
+ }
+
+ /**
+ * @param thermostats
+ * the list of Thermostat identifiers which belong to the group. If an empty list is sent the Group will
+ * be deleted.
+ */
+ @JsonProperty("thermostats")
+ public void setThermostats(final List thermostats) {
+ this.thermostats = thermostats;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("groupRef", this.groupRef);
+ builder.append("groupName", this.groupName);
+ builder.append("synchronizeAlerts", this.synchronizeAlerts);
+ builder.append("synchronizeSystemMode", this.synchronizeSystemMode);
+ builder.append("synchronizeSchedule", this.synchronizeSchedule);
+ builder.append("synchronizeQuickSave", this.synchronizeQuickSave);
+ builder.append("synchronizeReminders", this.synchronizeReminders);
+ builder.append("synchronizeContractorInfo", this.synchronizeContractorInfo);
+ builder.append("synchronizeUserPreferences", this.synchronizeUserPreferences);
+ builder.append("synchronizeUtilityInfo", this.synchronizeUtilityInfo);
+ builder.append("synchronizeLocation", this.synchronizeLocation);
+ builder.append("synchronizeReset", this.synchronizeReset);
+ builder.append("synchronizeVacation", this.synchronizeVacation);
+ builder.append("thermostats", this.thermostats);
+
+ return builder.toString();
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/HoldType.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/HoldType.java
new file mode 100644
index 00000000000..4dbf0113818
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/HoldType.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import org.codehaus.jackson.annotate.JsonValue;
+
+/**
+ * Hold types ease the calculation of end times for holds. You are always free to provide the startDate
and
+ * startTime
for the hold and it will be honoured, except where documented. The endDate
and
+ * endTime
depend on the hold type chosen. Default is indefinite
. Valid values:
+ * dateTime
, nextTransition
, indefinite
, holdHours
.
+ *
+ * @see SetHold
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public enum HoldType {
+
+ /**
+ * Use the provided startDate
, startTime
, endDate
and endTime
+ * for the event. If start date/time is not provided, it will be assumed to be right now. End date/time is required.
+ */
+ DATE_TIME("dateTime"),
+
+ /**
+ * The end date/time will be set to the next climate transition in the program.
+ */
+ NEXT_TRANSITION("nextTransition"),
+
+ /**
+ * The hold will not end and require to be cancelled explicitly.
+ */
+ INDEFINITE("indefinite"),
+
+ /**
+ * Use the value in the holdHours
parameter to set the end date/time for the event.
+ */
+ HOLD_HOURS("holdHours");
+
+ private final String type;
+
+ private HoldType(final String type) {
+ this.type = type;
+ }
+
+ @Override
+ @JsonValue
+ public String toString() {
+ return this.type;
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Page.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Page.java
new file mode 100644
index 00000000000..4af960409e6
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Page.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * The Page object contains the response page information.
+ *
+ * @see Page
+ * @author John Cocula
+ * @since 1.7.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Page extends AbstractMessagePart {
+ private Integer page;
+ private Integer totalPages;
+ private Integer pageSize;
+ private Integer total;
+
+ /**
+ * Construct a Page.
+ *
+ * @param page
+ * the specific page requested
+ */
+ public Page(@JsonProperty("page") final Integer page) {
+ this.page = page;
+ }
+
+ /**
+ * @return the page retrieved or, in the case of a request parameter, the specific page requested
+ */
+ @JsonProperty("page")
+ public Integer getPage() {
+ return this.page;
+ }
+
+ /**
+ * @return the total pages available
+ */
+ @JsonProperty("totalPages")
+ public Integer getTotalPages() {
+ return this.totalPages;
+ }
+
+ /**
+ * @return the number of objects on this page
+ */
+ @JsonProperty("pageSize")
+ public Integer getPageSize() {
+ return this.pageSize;
+ }
+
+ /**
+ * @return the total number of objects available
+ */
+ @JsonProperty("total")
+ public Integer getTotal() {
+ return this.total;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("page", this.page);
+ builder.append("totalPages", this.totalPages);
+ builder.append("pageSize", this.pageSize);
+ builder.append("total", this.total);
+
+ return builder.toString();
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/RefreshTokenRequest.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/RefreshTokenRequest.java
new file mode 100644
index 00000000000..72a3a3a6551
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/RefreshTokenRequest.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import static org.openhab.io.net.http.HttpUtil.executeUrl;
+
+import org.apache.commons.httpclient.util.URIUtil;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.openhab.binding.ecobee.internal.EcobeeException;
+
+/**
+ * All access tokens must be refreshed periodically. Token refresh reduces the potential and benefit of token theft.
+ * Since all tokens expire, stolen tokens may only be used for a limited time. A token refresh immediately expires the
+ * previously issued access and refresh tokens and issues brand new tokens.
+ *
+ * @see TokenResponse
+ * @see Refreshing Your
+ * Tokens
+ * @author John Cocula
+ * @author Ecobee
+ * @since 1.7.0
+ */
+public class RefreshTokenRequest extends AbstractRequest {
+
+ private static final String RESOURCE_URL = API_BASE_URL + "token";
+
+ private String refreshToken;
+ private String appKey;
+
+ /**
+ * Construct a refresh token request.
+ *
+ * @param refreshToken
+ * the refresh token you were issued
+ * @param appKey
+ * the application key for your application (this binding)
+ */
+ public RefreshTokenRequest(final String refreshToken, final String appKey) {
+ assert refreshToken != null : "refreshToken must not be null!";
+ assert appKey != null : "appKey must not be null!";
+
+ this.refreshToken = refreshToken;
+ this.appKey = appKey;
+ }
+
+ @Override
+ public TokenResponse execute() {
+ final String url = buildQueryString();
+ String json = null;
+
+ try {
+ json = executeQuery(url);
+
+ final TokenResponse response = JSON.readValue(json, TokenResponse.class);
+
+ return response;
+ } catch (final Exception e) {
+ throw newException("Could not get refresh token.", e, url, json);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("refreshToken", this.refreshToken);
+ builder.append("appKey", this.appKey);
+ return builder.toString();
+ }
+
+ protected String executeQuery(final String url) {
+ return executeUrl(HTTP_POST, url, HTTP_HEADERS, null, null, HTTP_REQUEST_TIMEOUT);
+ }
+
+ private String buildQueryString() {
+ final StringBuilder urlBuilder = new StringBuilder(RESOURCE_URL);
+
+ try {
+ urlBuilder.append("?grant_type=refresh_token");
+ urlBuilder.append("&code=");
+ urlBuilder.append(refreshToken);
+ urlBuilder.append("&client_id=");
+ urlBuilder.append(appKey);
+ return URIUtil.encodeQuery(urlBuilder.toString());
+ } catch (final Exception e) {
+ throw new EcobeeException(e);
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Request.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Request.java
new file mode 100644
index 00000000000..c0cc14a88ae
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Request.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+/**
+ * Base interface for all Ecobee API requests.
+ *
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public interface Request {
+
+ /**
+ * Send this request to the Ecobee API. Implementations specify a more concrete {@link Request} class.
+ *
+ * @return a {@link Response} containing the requested data or an error
+ */
+ Response execute();
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Response.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Response.java
new file mode 100644
index 00000000000..a0413eda1fa
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Response.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+/**
+ * Base interface for all Ecobee API responses.
+ *
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public interface Response {
+
+ /**
+ * Return the response message, or null
if there is none.
+ *
+ * @return the response message
+ *
+ * @see #isError()
+ */
+ String getResponseMessage();
+
+ /**
+ * Checks if this response contained an error.
+ *
+ * @return true
if the response contained an error instead of actual data.
+ */
+ boolean isError();
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/ResumeProgramFunction.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/ResumeProgramFunction.java
new file mode 100644
index 00000000000..54b3e17c9ab
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/ResumeProgramFunction.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+/**
+ * The resume program function removes the currently running event providing the event is not a mandatory demand
+ * response event. The top active event is removed from the stack and the thermostat resumes its program, or enters the
+ * next event in the stack if one exists. This function may need to be called multiple times in order to resume all
+ * events. Sending 3 resume functions in a row will resume the thermostat to its program in all instances.
+ *
+ *
+ * Note that vacation events cannot be resumed, you must delete the vacation event using the
+ * {@link DeleteVacationFunction}.
+ *
+ * @see ResumeProgram
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public final class ResumeProgramFunction extends AbstractFunction {
+
+ public ResumeProgramFunction() {
+ super("resumeProgram");
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Selection.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Selection.java
new file mode 100644
index 00000000000..d774a2cedc2
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Selection.java
@@ -0,0 +1,771 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import java.util.Set;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import static org.apache.commons.lang.StringUtils.join;
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.annotate.JsonValue;
+
+/**
+ * The selection object defines the resources and information to return as part of a response. The selection is required
+ * in all requests however meaning of some selection fields is only meaningful to certain types of requests.
+ *
+ *
+ * The selectionType
parameter defines the type of selection to perform. The selectionMatch
+ * specifies the matching criteria for the type specified.
+ *
+ *
+ *
+ * Selection Type |
+ * Account Type |
+ * Selection Match Example |
+ * Description |
+ *
+ *
+ * registered |
+ * Smart only. |
+ * match is not used. |
+ * When this is set the thermostats registered to the current user will be returned. This is only usable with Smart
+ * thermostats registered to a user. It does not work on EMS thermostats and may not be used by a Utility who is not the
+ * owner of thermostats. |
+ *
+ *
+ * thermostats |
+ * All accounts |
+ * identifier1,identifier2,etc... |
+ * Select only those thermostats listed in the CSV match criteria. No spaces in the CSV string. There is a limit of
+ * 25 identifiers per request. |
+ *
+ *
+ * managementSet |
+ * EMS/Utility only. |
+ * /Toronto/Campus/BuildingA |
+ * Selects all thermostats for a given management set defined by the Management/Utility account. This is only
+ * available to Management/Utility accounts. "/" is the root, represented by the "My Sets" set. |
+ *
+ *
+ *
+ * @see Selection
+ * @author John Cocula
+ * @author Ecobee
+ * @since 1.7.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Selection extends AbstractMessagePart {
+ public static final String REGISTERED_WILDCARD = "*";
+ public static final String MANAGEMENT_SET_DELIMITER = "/";
+
+ private SelectionType selectionType;
+ private String selectionMatch;
+ private Boolean includeRuntime;
+ private Boolean includeExtendedRuntime;
+ private Boolean includeElectricity;
+ private Boolean includeSettings;
+ private Boolean includeLocation;
+ private Boolean includeProgram;
+ private Boolean includeEvents;
+ private Boolean includeDevice;
+ private Boolean includeTechnician;
+ private Boolean includeUtility;
+ private Boolean includeManagement;
+ private Boolean includeAlerts;
+ private Boolean includeWeather;
+ private Boolean includeHouseDetails;
+ private Boolean includeOemCfg;
+ private Boolean includeEquipmentStatus;
+ private Boolean includeNotificationSettings;
+ private Boolean includePrivacy;
+ private Boolean includeVersion;
+
+ /**
+ * The SelectionType
defines the type of selection to perform.
+ *
+ * @see Selection
+ * Object
+ * @author John Cocula
+ * @author Ecobee
+ */
+ public static enum SelectionType {
+
+ /**
+ * No doc found.
+ */
+ NONE("none"),
+
+ /**
+ * All accounts.
+ *
+ *
+ * Select only those thermostats listed in the CSV match criteria. No spaces in the CSV string. There is a limit
+ * of 25 identifiers per request.
+ */
+ THERMOSTATS("thermostats"),
+
+ /**
+ * Smart only.
+ *
+ *
+ * When this is set the thermostats registered to the current user will be returned. This is only usable with
+ * Smart thermostats registered to a user. It does not work on EMS thermostats and may not be used by a Utility
+ * who is not the owner of thermostats.
+ */
+ REGISTERED("registered"),
+
+ /**
+ * No doc found.
+ */
+ USER("user"),
+
+ /**
+ * EMS/Utility only.
+ *
+ *
+ * Selects all thermostats for a given management set defined by the Management/Utility account. This is only
+ * available to Management/Utility accounts. "/" is the root, represented by the "My Sets" set.
+ */
+ MANAGEMENT_SET("managementSet");
+
+ private final String type;
+
+ private SelectionType(final String type) {
+ this.type = type;
+ }
+
+ @JsonCreator
+ public static SelectionType forValue(String v) {
+ for (SelectionType st : SelectionType.values()) {
+ if (st.type.equals(v)) {
+ return st;
+ }
+ }
+ throw new IllegalArgumentException("Invalid selection type: " + v);
+ }
+
+ @Override
+ @JsonValue
+ public String toString() {
+ return this.type;
+ }
+ }
+
+ /**
+ * Give a selectionMatch string, infer what kind of selectionType should be used.
+ *
+ * @param selectionMatch
+ * the match string to inspect
+ * @returns the selectionType to be used with the selectionMatch
+ */
+ private SelectionType inferSelectionType(String selectionMatch) {
+ SelectionType selectionType;
+
+ if (REGISTERED_WILDCARD.equals(selectionMatch)) {
+ selectionType = SelectionType.REGISTERED;
+ } else if (selectionMatch.startsWith(MANAGEMENT_SET_DELIMITER)) {
+ selectionType = SelectionType.MANAGEMENT_SET;
+ } else {
+ selectionType = SelectionType.THERMOSTATS;
+ }
+ return selectionType;
+ }
+
+ /**
+ * Return true
if the given string matches the known format for thermostat identifiers.
+ *
+ * @param thermostatIdentifier
+ * the string to test
+ * @return true
if the given string matches the known format for thermostat identifiers.
+ */
+ public static boolean isThermostatIdentifier(String thermostatIdentifier) {
+ return thermostatIdentifier.matches("[0-9]+");
+ }
+
+ /**
+ * Construct a Selection object using a single selectionMatch
, and inferring the
+ * selectionType
from the selectionMatch
.
+ *
+ * @param selectionMatch
+ * based on the syntax, infer the selectionType
+ */
+ public Selection(@JsonProperty("selectionMatch") final String selectionMatch) {
+ this.selectionType = inferSelectionType(selectionMatch);
+ this.selectionMatch = selectionMatch;
+ }
+
+ /**
+ * Construct a Selection object.
+ *
+ * @param selectionType
+ * the type of match data supplied.
+ * @param selectionMatch
+ */
+ public Selection(@JsonProperty("selectionType") final SelectionType selectionType,
+ @JsonProperty("selectionMatch") final String selectionMatch) {
+ this.selectionType = selectionType;
+ this.selectionMatch = selectionMatch;
+ }
+
+ /**
+ * @return the type of match data supplied.
+ */
+ @JsonProperty("selectionType")
+ public SelectionType getSelectionType() {
+ return this.selectionType;
+ }
+
+ /**
+ * @param selectionType
+ * the type of match data supplied.
+ */
+ @JsonProperty("selectionType")
+ public void setSelectionType(final SelectionType selectionType) {
+ this.selectionType = selectionType;
+ }
+
+ /**
+ * @return the match data based on selectionType (e.g. a list of thermostat identifiers in the case of a
+ * selectionType of thermostats)
+ */
+ @JsonProperty("selectionMatch")
+ public String getSelectionMatch() {
+ return this.selectionMatch;
+ }
+
+ /**
+ * @param selectionMatch
+ * the match data based on selectionType (e.g. a list of thermostat identifiers in the case of a
+ * selectionType
of "thermostats"
)
+ */
+ @JsonProperty("selectionMatch")
+ public void setSelectionMatch(final String selectionMatch) {
+ this.selectionMatch = selectionMatch;
+ }
+
+ /**
+ * @param thermostatIdentifiers
+ * set a list of thermostat identifiers as the selectionMatch
and set the
+ * selectionType
to {@code SelectionType.THERMOSTATS}.
+ */
+ public void setSelectionMatch(final Set thermostatIdentifiers) {
+ this.selectionType = SelectionType.THERMOSTATS;
+ this.selectionMatch = join(thermostatIdentifiers, ',');
+ }
+
+ /**
+ * @return include the {@link Thermostat.Runtime} object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeRuntime")
+ public Boolean getIncludeRuntime() {
+ return this.includeRuntime;
+ }
+
+ /**
+ * @return true
if we would like runtime values returned from this Selection.
+ */
+ public boolean includeRuntime() {
+ return (this.includeRuntime == null) ? false : this.includeRuntime;
+ }
+
+ /**
+ * @param includeRuntime
+ * include the thermostat {@link Thermostat.Runtime} object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeRuntime")
+ public void setIncludeRuntime(final Boolean includeRuntime) {
+ this.includeRuntime = includeRuntime;
+ }
+
+ /**
+ * @return include the extended thermostat runtime object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeExtendedRuntime")
+ public Boolean getIncludeExtendedRuntime() {
+ return this.includeExtendedRuntime;
+ }
+
+ /**
+ * @return true
if we would like extended runtime values returned from this Selection.
+ */
+ public boolean includeExtendedRuntime() {
+ return (this.includeExtendedRuntime == null) ? false : this.includeExtendedRuntime;
+ }
+
+ /**
+ * @param includeExtendedRuntime
+ * include the @{link Thermostat.ExtendedRuntime} object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeExtendedRuntime")
+ public void setIncludeExtendedRuntime(final Boolean includeExtendedRuntime) {
+ this.includeExtendedRuntime = includeExtendedRuntime;
+ }
+
+ /**
+ * @return include the {@link Thermostat.Electricity} readings object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeElectricity")
+ public Boolean getIncludeElectricity() {
+ return this.includeElectricity;
+ }
+
+ /**
+ * @return true
if we would like electricity values returned from this Selection.
+ */
+ public boolean includeElectricity() {
+ return (this.includeElectricity == null) ? false : this.includeElectricity;
+ }
+
+ /**
+ * @param includeElectricity
+ * include the {@link Thermostat.Electricity} readings object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeElectricity")
+ public void setIncludeElectricity(final Boolean includeElectricity) {
+ this.includeElectricity = includeElectricity;
+ }
+
+ /**
+ * @return include the {@link Thermostat.Settings} object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeSettings")
+ public Boolean getIncludeSettings() {
+ return this.includeSettings;
+ }
+
+ /**
+ * @return true
if we would like settings values returned from this Selection.
+ */
+ public boolean includeSettings() {
+ return (this.includeSettings == null) ? false : this.includeSettings;
+ }
+
+ /**
+ * @param includeSettings
+ * include the {@link Thermostat.Settings} object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeSettings")
+ public void setIncludeSettings(final Boolean includeSettings) {
+ this.includeSettings = includeSettings;
+ }
+
+ /**
+ * @return include the {@link Thermostat.Location} object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeLocation")
+ public Boolean getIncludeLocation() {
+ return this.includeLocation;
+ }
+
+ /**
+ * @return true
if we would like location values returned from this Selection.
+ */
+ public boolean includeLocation() {
+ return (this.includeLocation == null) ? false : this.includeLocation;
+ }
+
+ /**
+ * @param includeLocation
+ * include the {@link Thermostat.Location} object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeLocation")
+ public void setIncludeLocation(final Boolean includeLocation) {
+ this.includeLocation = includeLocation;
+ }
+
+ /**
+ * @return include the {@link Thermostat.Program} object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeProgram")
+ public Boolean getIncludeProgram() {
+ return this.includeProgram;
+ }
+
+ /**
+ * @return true
if we would like program values returned from this Selection.
+ */
+ public boolean includeProgram() {
+ return (this.includeProgram == null) ? false : this.includeProgram;
+ }
+
+ /**
+ * @param includeProgram
+ * include the {@link Thermostat.Program} object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeProgram")
+ public void setIncludeProgram(final Boolean includeProgram) {
+ this.includeProgram = includeProgram;
+ }
+
+ /**
+ * @return include the {@link Thermostat.Event}s calendar objects. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeEvents")
+ public Boolean getIncludeEvents() {
+ return this.includeEvents;
+ }
+
+ /**
+ * @return true
if we would like events returned from this Selection.
+ */
+ public boolean includeEvents() {
+ return (this.includeEvents == null) ? false : this.includeEvents;
+ }
+
+ /**
+ * @param includeEvents
+ * include the {@link Thermostat.Event}s calendar objects. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeEvents")
+ public void setIncludeEvents(final Boolean includeEvents) {
+ this.includeEvents = includeEvents;
+ }
+
+ /**
+ * @return include the {@link Thermostat.Device} configuration objects. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeDevice")
+ public Boolean getIncludeDevice() {
+ return this.includeDevice;
+ }
+
+ /**
+ * @return true
if we would like device values returned from this Selection.
+ */
+ public boolean includeDevice() {
+ return (this.includeDevice == null) ? false : this.includeDevice;
+ }
+
+ /**
+ * @param includeDevice
+ * include the {@link Thermostat.Device} configuration objects. If not specified, defaults to false.
+ */
+ @JsonProperty("includeDevice")
+ public void setIncludeDevice(final Boolean includeDevice) {
+ this.includeDevice = includeDevice;
+ }
+
+ /**
+ * @return include the {@link Thermostat.Technician} object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeTechnician")
+ public Boolean getIncludeTechnician() {
+ return this.includeTechnician;
+ }
+
+ /**
+ * @return true
if we would like technician values returned from this Selection.
+ */
+ public boolean includeTechnician() {
+ return (this.includeTechnician == null) ? false : this.includeTechnician;
+ }
+
+ /**
+ * @param includeTechnician
+ * include the {@link Thermostat.Technician} object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeTechnician")
+ public void setIncludeTechnician(final Boolean includeTechnician) {
+ this.includeTechnician = includeTechnician;
+ }
+
+ /**
+ * @return include the {@link Thermostat.Utility} company object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeUtility")
+ public Boolean getIncludeUtility() {
+ return this.includeUtility;
+ }
+
+ /**
+ * @return true
if we would like utility values returned from this Selection.
+ */
+ public boolean includeUtility() {
+ return (this.includeUtility == null) ? false : this.includeUtility;
+ }
+
+ /**
+ * @param includeUtility
+ * include the {@link Thermostat.Utility} company object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeUtility")
+ public void setIncludeUtility(final Boolean includeUtility) {
+ this.includeUtility = includeUtility;
+ }
+
+ /**
+ * @return include the {@link Thermostat.Management} company object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeManagement")
+ public Boolean getIncludeManagement() {
+ return this.includeManagement;
+ }
+
+ /**
+ * @return true
if we would like management values returned from this Selection.
+ */
+ public boolean includeManagement() {
+ return (this.includeManagement == null) ? false : this.includeManagement;
+ }
+
+ /**
+ * @param includeManagement
+ * include the {@link Thermostat.Management} company object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeManagement")
+ public void setIncludeManagement(final Boolean includeManagement) {
+ this.includeManagement = includeManagement;
+ }
+
+ /**
+ * @return include the unacknowledged {@link Thermostat.Alert} objects. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeAlerts")
+ public Boolean getIncludeAlerts() {
+ return this.includeAlerts;
+ }
+
+ /**
+ * @return true
if we would like alerts returned from this Selection.
+ */
+ public boolean includeAlerts() {
+ return (this.includeAlerts == null) ? false : this.includeAlerts;
+ }
+
+ /**
+ * @param includeAlerts
+ * include the unacknowledged {@link Thermostat.Alert} objects. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeAlerts")
+ public void setIncludeAlerts(final Boolean includeAlerts) {
+ this.includeAlerts = includeAlerts;
+ }
+
+ /**
+ * @return include the current {@link Thermostat.Weather} forecast object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeWeather")
+ public Boolean getIncludeWeather() {
+ return this.includeWeather;
+ }
+
+ /**
+ * @return true
if we would like weather returned from this Selection.
+ */
+ public boolean includeWeather() {
+ return (this.includeWeather == null) ? false : this.includeWeather;
+ }
+
+ /**
+ * @param includeWeather
+ * include the current {@link Thermostat.Weather} forecast object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeWeather")
+ public void setIncludeWeather(final Boolean includeWeather) {
+ this.includeWeather = includeWeather;
+ }
+
+ /**
+ * @return include the current {@link Thermostat.HouseDetails} object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeHouseDetails")
+ public Boolean getIncludeHouseDetails() {
+ return this.includeHouseDetails;
+ }
+
+ /**
+ * @return true
if we would like house details returned from this Selection.
+ */
+ public boolean includeHouseDetails() {
+ return (this.includeHouseDetails == null) ? false : this.includeHouseDetails;
+ }
+
+ /**
+ * @param includeHouseDetails
+ * include the current {@link Thermostat.HouseDetails} object. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeHouseDetails")
+ public void setIncludeHouseDetails(final Boolean includeHouseDetails) {
+ this.includeHouseDetails = includeHouseDetails;
+ }
+
+ /**
+ * @return include the current thermostat OemCfg object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeOemCfg")
+ public Boolean getIncludeOemCfg() {
+ return this.includeOemCfg;
+ }
+
+ /**
+ * @return true
if we would like OemCfg values returned from this Selection.
+ */
+ public boolean includeOemCfg() {
+ return (this.includeOemCfg == null) ? false : this.includeOemCfg;
+ }
+
+ /**
+ * @param includeOemCfg
+ * include the current thermostat OemCfg object. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeOemCfg")
+ public void setIncludeOemCfg(final Boolean includeOemCfg) {
+ this.includeOemCfg = includeOemCfg;
+ }
+
+ /**
+ * @return include the current thermostat equipment status information. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeEquipmentStatus")
+ public Boolean getIncludeEquipmentStatus() {
+ return this.includeEquipmentStatus;
+ }
+
+ /**
+ * @return true
if we would like equipment status returned from this Selection.
+ */
+ public boolean includeEquipmentStatus() {
+ return (this.includeEquipmentStatus == null) ? false : this.includeEquipmentStatus;
+ }
+
+ /**
+ * @param includeEquipmentStatus
+ * include the current thermostat equipment status information. If not specified, defaults to
+ * false
.
+ */
+ @JsonProperty("includeEquipmentStatus")
+ public void setIncludeEquipmentStatus(final Boolean includeEquipmentStatus) {
+ this.includeEquipmentStatus = includeEquipmentStatus;
+ }
+
+ /**
+ * @return include the current alert and reminders {@link Thermostat.NotificationSettings}. If not specified,
+ * defaults to false
.
+ */
+ @JsonProperty("includeNotificationSettings")
+ public Boolean getIncludeNotificationSettings() {
+ return this.includeNotificationSettings;
+ }
+
+ /**
+ * @return true
if we would like notification settings returned from this Selection.
+ */
+ public boolean includeNotificationSettings() {
+ return (this.includeNotificationSettings == null) ? false : this.includeNotificationSettings;
+ }
+
+ /**
+ * @param includeNotificationSettings
+ * include the current alert and reminders {@link Thermostat.NotificationSettings}. If not specified,
+ * defaults to false
.
+ */
+ @JsonProperty("includeNotificationSettings")
+ public void setIncludeNotificationSettings(final Boolean includeNotificationSettings) {
+ this.includeNotificationSettings = includeNotificationSettings;
+ }
+
+ /**
+ * @return include the current thermostat privacy settings. Note: access to this object is restricted to callers
+ * with implicit authentication, setting this value to true without proper credentials will result in an
+ * authentication exception.
+ */
+ @JsonProperty("includePrivacy")
+ public Boolean getIncludePrivacy() {
+ return this.includePrivacy;
+ }
+
+ /**
+ * @return true
if we would like privacy settings returned from this Selection.
+ */
+ public boolean includePrivacy() {
+ return (this.includePrivacy == null) ? false : this.includePrivacy;
+ }
+
+ /**
+ * @param includePrivacy
+ * include the current thermostat privacy settings. Note: access to this object is restricted to callers
+ * with implicit authentication, setting this value to true without proper credentials will result in an
+ * authentication exception.
+ */
+ @JsonProperty("includePrivacy")
+ public void setIncludePrivacy(final Boolean includePrivacy) {
+ this.includePrivacy = includePrivacy;
+ }
+
+ /**
+ * @return include the {@link Thermostat.Version}. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeVersion")
+ public Boolean getIncludeVersion() {
+ return this.includeVersion;
+ }
+
+ /**
+ * @return true
if we would like version information returned from this Selection.
+ */
+ public boolean includeVersion() {
+ return (this.includeVersion == null) ? false : this.includeVersion;
+ }
+
+ /**
+ * @param includeVersion
+ * include the {@link Thermostat.Version}. If not specified, defaults to false
.
+ */
+ @JsonProperty("includeVersion")
+ public void setIncludeVersion(final Boolean includeVersion) {
+ this.includeVersion = includeVersion;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("selectionType", this.selectionType);
+ builder.append("selectionMatch", this.selectionMatch);
+ builder.append("includeRuntime", this.includeRuntime);
+ builder.append("includeExtendedRuntime", this.includeExtendedRuntime);
+ builder.append("includeElectricity", this.includeElectricity);
+ builder.append("includeSettings", this.includeSettings);
+ builder.append("includeLocation", this.includeLocation);
+ builder.append("includeProgram", this.includeProgram);
+ builder.append("includeEvents", this.includeEvents);
+ builder.append("includeDevice", this.includeDevice);
+ builder.append("includeTechnician", this.includeTechnician);
+ builder.append("includeUtility", this.includeUtility);
+ builder.append("includeManagement", this.includeManagement);
+ builder.append("includeAlerts", this.includeAlerts);
+ builder.append("includeWeather", this.includeWeather);
+ builder.append("includeHouseDetails", this.includeHouseDetails);
+ builder.append("includeOemCfg", this.includeOemCfg);
+ builder.append("includeEquipmentStatus", this.includeEquipmentStatus);
+ builder.append("includeNotificationSettings", this.includeNotificationSettings);
+ builder.append("includePrivacy", this.includePrivacy);
+ builder.append("includeVersion", this.includeVersion);
+
+ return builder.toString();
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/SendMessageFunction.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/SendMessageFunction.java
new file mode 100644
index 00000000000..77821f65a4f
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/SendMessageFunction.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+/**
+ * The send message function allows an alert message to be sent to the thermostat. The message properties are same
+ * as those of the {@link Thermostat.Alert} object.
+ *
+ * @see SendMessage
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public final class SendMessageFunction extends AbstractFunction {
+
+ /**
+ * @param text
+ * the message text to send. Text will be truncated to 500 characters if longer.
+ * @throws IllegalArgumentException
+ * is text is null
.
+ */
+ public SendMessageFunction(String text) {
+ super("sendMessage");
+ if (text == null) {
+ throw new IllegalArgumentException("text is required.");
+ }
+
+ makeParams().put("text", text);
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/SetHoldFunction.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/SetHoldFunction.java
new file mode 100644
index 00000000000..938c6d9d3fe
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/SetHoldFunction.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import java.util.Date;
+
+/**
+ * The set hold function sets the thermostat into a hold with the specified temperature. Creates a hold for the
+ * specified duration. Note that an event is created regardless of whether the program is in the same state as the
+ * requested state.
+ *
+ *
+ * There is also support for creating a hold by passing a holdClimateRef
request parameter/value pair to
+ * this function (See {@link Thermostat.Event}). When an existing and valid {@link Thermostat.Climate#climateRef} value
+ * is passed to this function, the coolHoldTemp
, heatHoldTemp
and fan
mode from
+ * that {@link Thermostat.Climate} are used in the creation of the hold event. The values from that Climate will take
+ * precedence over any coolHoldTemp
, heatHoldTemp
and fan
mode parameters passed
+ * into this function separately.
+ *
+ *
+ * To resume from a hold and return to the program, use the {@link ResumeProgramFunction}.
+ *
+ * @see SetHold
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public final class SetHoldFunction extends AbstractFunction {
+
+ /**
+ * Construct a SetHoldFunction.
+ *
+ * @param coolHoldTemp
+ * the temperature to set the cool hold at
+ * @param heatHoldTemp
+ * the temperature to set the heat hold at
+ * @param holdClimateRef
+ * the Climate to use as reference for setting the coolHoldTemp, heatHoldTemp and fan settings for this
+ * hold. If this value is passed, the coolHoldTemp and heatHoldTemp are not required.
+ * @param startDateTime
+ * the start date and time in thermostat time
+ * @param endDateTime
+ * the end date and time in thermostat time
+ * @param holdType
+ * the hold duration type
+ * @param holdHours
+ * the number of hours to hold for, used and required if holdType='holdHours'
+ */
+ public SetHoldFunction(Temperature coolHoldTemp, Temperature heatHoldTemp, String holdClimateRef,
+ Date startDateTime, Date endDateTime, HoldType holdType, Integer holdHours) {
+ super("setHold");
+
+ if (holdClimateRef == null && (coolHoldTemp == null || heatHoldTemp == null)) {
+ throw new IllegalArgumentException(
+ "coolHoldTemp and heatHoldTemp are required when holdClimateRef is not supplied.");
+ }
+ if (holdType == HoldType.HOLD_HOURS && holdHours == null) {
+ throw new IllegalArgumentException("holdHours must be specified when holdType='holdHours'");
+ }
+ if (holdType == HoldType.DATE_TIME && endDateTime == null) {
+ throw new IllegalArgumentException("endDateTime must be specific when holdType='dateTime'");
+ }
+
+ if (coolHoldTemp != null) {
+ makeParams().put("coolHoldTemp", coolHoldTemp);
+ }
+ if (heatHoldTemp != null) {
+ makeParams().put("heatHoldTemp", heatHoldTemp);
+ }
+ if (holdClimateRef != null) {
+ makeParams().put("holdClimateRef", holdClimateRef);
+ }
+ if (startDateTime != null) {
+ makeParams().put("startDate", ymd.format(startDateTime));
+ makeParams().put("startTime", hms.format(startDateTime));
+ }
+ if (endDateTime != null) {
+ makeParams().put("endDate", ymd.format(endDateTime));
+ makeParams().put("endTime", hms.format(endDateTime));
+ }
+ if (holdType != null) {
+ makeParams().put("holdType", holdType);
+ }
+ if (holdHours != null) {
+ makeParams().put("holdHours", holdHours);
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/SetOccupiedFunction.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/SetOccupiedFunction.java
new file mode 100644
index 00000000000..176129efb8e
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/SetOccupiedFunction.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import java.util.Date;
+
+/**
+ * The set occupied function may only be used by EMS thermostats.
+ *
+ *
+ * The function switches a thermostat from occupied mode to unoccupied, or vice versa. If used on a Smart thermostat,
+ * the function will throw an error. Switch occupancy events are treated as Holds. There may only be one Switch
+ * Occupancy at one time, and the new event will replace any previous event.
+ *
+ *
+ * Note that an occupancy event is created regardless what the program on the thermostat is set to. For example, if the
+ * program is currently unoccupied and you set occupied=false
, an occupancy event will be created using the
+ * heat/cool settings of the unoccupied program climate. If your intent is to go back to the program and remove the
+ * occupancy event, use {@link ResumeProgramFunction} instead.
+ *
+ * @see SetOccupied
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public final class SetOccupiedFunction extends AbstractFunction {
+
+ /**
+ * Construct a SetOccupiedFunction.
+ *
+ * @param occupied
+ * the climate to use for the temperature, occupied (true) or unoccupied (false)
+ * @param startDateTime
+ * the start date and time in thermostat time
+ * @param endDateTime
+ * the end date and time in thermostat time
+ * @param holdType
+ * the hold duration type
+ * @param holdHours
+ * the number of hours to hold for, used and required if holdType='holdHours'
+ */
+ public SetOccupiedFunction(Boolean occupied, Date startDateTime, Date endDateTime, HoldType holdType,
+ Integer holdHours) {
+ super("setOccupied"); // not in doc; assuming
+
+ if (occupied == null) {
+ throw new IllegalArgumentException("occupied state is required.");
+ }
+ if (holdType == HoldType.HOLD_HOURS && holdHours == null) {
+ throw new IllegalArgumentException("holdHours must be specified when holdType='holdHours'");
+ }
+ if (holdType == HoldType.DATE_TIME && endDateTime == null) {
+ throw new IllegalArgumentException("endDateTime must be specific when holdType='dateTime'");
+ }
+
+ makeParams().put("occupied", occupied);
+ if (startDateTime != null) {
+ makeParams().put("startDate", ymd.format(startDateTime));
+ makeParams().put("startTime", hms.format(startDateTime));
+ }
+ if (endDateTime != null) {
+ makeParams().put("endDate", ymd.format(endDateTime));
+ makeParams().put("endTime", hms.format(endDateTime));
+ }
+ if (holdType != null) {
+ makeParams().put("holdType", holdType);
+ }
+ if (holdHours != null) {
+ makeParams().put("holdHours", holdHours);
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Status.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Status.java
new file mode 100644
index 00000000000..ace95d5e050
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Status.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * The response status object contains the processing status of the request. It will contain any relevant error
+ * information should an error occur. The status object is returned with every response regardless of success or failure
+ * status. It is suitable for logging request failures.
+ *
+ * @see Status
+ * @author John Cocula
+ * @since 1.7.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Status extends AbstractMessagePart {
+ private Integer code;
+ private String message;
+
+ /**
+ * @return the status code for this status
+ */
+ @JsonProperty("code")
+ public Integer getCode() {
+ return this.code;
+ }
+
+ /**
+ * @return the detailed message for this status
+ */
+ @JsonProperty("message")
+ public String getMessage() {
+ return this.message;
+ }
+
+ /**
+ * @return true
if we know this error indicates that the access token has expired.
+ */
+ public boolean isAccessTokenExpired() {
+ return this.code != null && this.code == 14;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("code", this.code);
+ builder.append("message", this.message);
+
+ return builder.toString();
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Temperature.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Temperature.java
new file mode 100644
index 00000000000..9138231b91e
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Temperature.java
@@ -0,0 +1,169 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import java.math.BigDecimal;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonValue;
+
+/**
+ * Temperature values are expressed as degrees Fahrenheit, multiplied by 10. For example, a temperature of 72F would be
+ * expressed as the value 720. If the user preferences indicate the use of Celsius values, it is the responsibility of
+ * the caller to convert values to and from Celsius.
+ *
+ *
+ * Where specified explicitly Degrees F indicates that the temperature will be returned in degrees Fahrenheit accurate
+ * to one decimal place (i.e. 18.5F).
+ *
+ *
+ * Methods are included to construct Temperature objects from Celsius, Fahrenheit, or whichever the local temperature
+ * scale is.
+ *
+ * @see Values
+ * and Representation
+ * @author John Cocula
+ * @since 1.7.0
+ */
+public class Temperature {
+
+ /**
+ * Enum for representing the temperature scale that will be reported.
+ */
+ public static enum Scale {
+ CELSIUS("C"), FAHRENHEIT("F");
+
+ private final String scale;
+
+ private Scale(final String scale) {
+ this.scale = scale;
+ }
+
+ public String value() {
+ return scale;
+ }
+
+ public static Scale forValue(String v) {
+ for (Scale s : Scale.values()) {
+ if (s.scale.equals(v)) {
+ return s;
+ }
+ }
+ throw new IllegalArgumentException("Invalid temperature scale: " + v);
+ }
+ }
+
+ /**
+ * The local temperature scale used throughout this JVM. Defaults to Fahrenheit.
+ */
+ private static Scale localScale = Scale.FAHRENHEIT;
+
+ public static void setLocalScale(final Scale localScale) {
+ Temperature.localScale = localScale;
+ }
+
+ private BigDecimal temp;
+
+ @JsonValue
+ public int value() {
+ return temp.intValue();
+ }
+
+ /**
+ * Construct a Temperature from the Ecobee-style temperature value (Fahrenheit times 10).
+ *
+ * @param temp
+ * Ecobee-style temperature value (Fahrenheit times 10)
+ */
+ @JsonCreator
+ public Temperature(int temp) {
+ this.temp = new BigDecimal(temp);
+ }
+
+ /**
+ * Construct a Temperature from the Ecobee-style temperature value (Fahrenheit times 10).
+ *
+ * @param temp
+ * Ecobee-style temperature value (Fahrenheit times 10)
+ */
+ public Temperature(BigDecimal temp) {
+ this.temp = temp;
+ }
+
+ /**
+ * Factory method to construct a Temperature from the local temperature scale.
+ *
+ * @param localTemp
+ * the temperature in the local temperature scale.
+ * @return a new Temperature object
+ */
+ public static Temperature fromLocalTemperature(BigDecimal localTemp) {
+ if (localScale.equals(Scale.CELSIUS)) {
+ return fromCelsius(localTemp);
+ } else {
+ return fromFahrenheit(localTemp);
+ }
+ }
+
+ /**
+ * Factory method to construct a Temperature from Fahrenheit.
+ *
+ * @param fahrenheit
+ * the Fahrenheit temperature
+ */
+ public static Temperature fromFahrenheit(BigDecimal fahrenheit) {
+ return new Temperature(fahrenheit.movePointRight(1));
+ }
+
+ private static BigDecimal NINE = new BigDecimal("9");
+ private static BigDecimal FIVE = new BigDecimal("5");
+ private static BigDecimal THIRTY_TWO = new BigDecimal("32");
+
+ /**
+ * Factory method to construct a Temperature from Celsius.
+ *
+ * @param celsius
+ * the Celsius temperature
+ */
+ public static Temperature fromCelsius(BigDecimal celsius) {
+ return new Temperature(celsius.multiply(NINE).divide(FIVE).add(THIRTY_TWO).movePointRight(1));
+ }
+
+ /**
+ * Convert this temperature to a temperature in the local temperature scale.
+ *
+ * @return temperarure in the local temperature scale
+ */
+ public final BigDecimal toLocalTemperature() {
+ return localScale == Scale.CELSIUS ? toCelsius() : toFahrenheit();
+ }
+
+ /**
+ * Convert this Temperature to Fahrenheit.
+ *
+ * @return temperature in Fahrenheit
+ */
+ public final BigDecimal toFahrenheit() {
+ return temp.movePointLeft(1);
+ }
+
+ /**
+ * Convert this Temperature to Celsius.
+ *
+ * @return temperature in Celsius
+ */
+ public final BigDecimal toCelsius() {
+ return temp.movePointLeft(1).subtract(THIRTY_TWO).divide(NINE.divide(FIVE));
+ }
+
+ @Override
+ public String toString() {
+ return temp.toString();
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Thermostat.java b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Thermostat.java
new file mode 100644
index 00000000000..292f1984d4d
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.ecobee/src/main/java/org/openhab/binding/ecobee/internal/messages/Thermostat.java
@@ -0,0 +1,6016 @@
+/**
+ * Copyright (c) 2010-2015, 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.ecobee.internal.messages;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.annotate.JsonValue;
+
+/**
+ * The Thermostat Java Bean is the central piece of the ecobee API. All objects relate in one way or another to a real
+ * thermostat. The Thermostat class and its component classes define the real thermostat device.
+ *
+ * @see Thermostat
+ * @author John Cocula
+ * @since 1.7.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Thermostat extends AbstractMessagePart {
+
+ /**
+ * Numeric values whose value is not known are expressed as -5002. This is the numeric equivalent to a null value.
+ * The value of -5002 had been chosen as an unknown value because the representation of -500.2F is below absolute
+ * zero when representing temperatures.
+ *
+ * @see Core Concepts
+ */
+ public static final int UNKNOWN_VALUE = -5002;
+
+ /**
+ * There is a concept of dates "before time began" which is equivalent to a NULL time and "end of time" which
+ * represents infinite durations (i.e. events). The API represents these as:
+ *
+ * Before Time Began Date: 2008-01-02
+ *
+ * End of Time Date: 2035-01-01
+ *
+ * @see Core Concepts
+ */
+ public static final Date BEFORE_TIME_BEGAN = new GregorianCalendar(2008, 1, 2).getTime();
+ public static final Date END_OF_TIME = new GregorianCalendar(2035, 1, 1).getTime();
+
+ private String identifier;
+ private String name;
+ private String thermostatRev;
+ private Boolean isRegistered;
+ private String modelNumber;
+ private Date lastModified;
+ private Date thermostatTime;
+ private Date utcTime;
+ private List alerts;
+ private Settings settings;
+ private Runtime runtime;
+ private ExtendedRuntime extendedRuntime;
+ private Electricity electricity;
+ private List devices;
+ private Location location;
+ private Technician technician;
+ private Utility utility;
+ private Management management;
+ private Weather weather;
+ private List events;
+ private Program program;
+ private HouseDetails houseDetails;
+ private ThermostatOemCfg oemCfg;
+ private String equipmentStatus;
+ private NotificationSettings notificationSettings;
+ private ThermostatPrivacy privacy;
+ private Version version;
+
+ public Thermostat(@JsonProperty("identifier") String identifier) {
+ this.identifier = identifier;
+ }
+
+ /**
+ * Return a JavaBean property by name.
+ *
+ * @param name
+ * the named property to return
+ * @return the named property's value
+ * @see BeanUtils#getProperty()
+ * @throws IllegalAccessException
+ * if the caller does not have access to the property accessor method
+ * @throws InvocationTargetException
+ * if the property accessor method throws an exception
+ * @throws NoSuchMethodException
+ * if the accessor method for this property cannot be found
+ */
+ public Object getProperty(String name) throws IllegalAccessException, InvocationTargetException,
+ NoSuchMethodException {
+
+ return PropertyUtils.getProperty(this, name);
+ }
+
+ /**
+ * Set the specified property value, performing type conversions as required to conform to the type of the
+ * destination property. Nest beans are created if they are currently null
.
+ *
+ * @param name
+ * property name (can be nested/indexed/mapped/combo)
+ * @param value
+ * value to be set
+ * @throws IllegalAccessException
+ * if the caller does not have access to the property accessor method
+ * @throws InvocationTargetException
+ * if the property accessor method throws an exception
+ */
+ public void setProperty(String name, Object value) throws IllegalAccessException, InvocationTargetException {
+
+ if (name.startsWith("settings") && this.settings == null) {
+ this.settings = new Settings();
+ } else if (name.startsWith("location") && this.location == null) {
+ this.location = new Location();
+ } else if (name.startsWith("program") && this.program == null) {
+ this.program = new Program();
+ } else if (name.startsWith("houseDetails") && this.houseDetails == null) {
+ this.houseDetails = new HouseDetails();
+ } else if (name.startsWith("notificationSettings") && this.notificationSettings == null) {
+ this.notificationSettings = new NotificationSettings();
+ }
+
+ BeanUtils.setProperty(this, name, value);
+ }
+
+ /**
+ * @return the unique thermostat serial number.
+ */
+ @JsonProperty("identifier")
+ public String getIdentifier() {
+ return this.identifier;
+ }
+
+ /**
+ * @return the user defined name for a thermostat
+ */
+ @JsonProperty("name")
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * @param name
+ * the user defined name for a thermostat
+ */
+ @JsonProperty("name")
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return current thermostat configuration revision
+ */
+ @JsonProperty("thermostatRev")
+ public String getThermostatRev() {
+ return this.thermostatRev;
+ }
+
+ /**
+ * @return whether the user registered the thermostat
+ */
+ @JsonProperty("isRegistered")
+ public Boolean getIsRegistered() {
+ return this.isRegistered;
+ }
+
+ /**
+ * @return the thermostat model number. Values: idtSmart, idtEms, siSmart, siEms
+ */
+ @JsonProperty("modelNumber")
+ public String getModelNumber() {
+ return this.modelNumber;
+ }
+
+ /**
+ * @return the last modified date time for the thermostat configuration.
+ */
+ @JsonProperty("lastModified")
+ public Date getLastModified() {
+ return this.lastModified;
+ }
+
+ /**
+ * @return the current time in the thermostat's time zone TODO time zone adjust (@watou)
+ * http://wiki.fasterxml.com/JacksonFAQDateHandling
+ */
+ @JsonProperty("thermostatTime")
+ public Date getThermostatTime() {
+ return this.thermostatTime;
+ }
+
+ /**
+ * @return the current time in UTC
+ */
+ @JsonProperty("utcTime")
+ public Date getUtcTime() {
+ return this.utcTime;
+ }
+
+ /**
+ * @return the list of Alert objects tied to the thermostat
+ */
+ @JsonProperty("alerts")
+ public List getAlerts() {
+ return this.alerts;
+ }
+
+ /**
+ * @return the thermostat Setting object linked to the thermostat
+ */
+ @JsonProperty("settings")
+ public Settings getSettings() {
+ return this.settings;
+ }
+
+ /**
+ * @param settings
+ * the thermostat Setting object linked to the thermostat
+ */
+ @JsonProperty("settings")
+ public void setSettings(Settings settings) {
+ this.settings = settings;
+ }
+
+ /**
+ * @return the Runtime state object for the thermostat
+ */
+ @JsonProperty("runtime")
+ public Runtime getRuntime() {
+ return this.runtime;
+ }
+
+ /**
+ * @return the ExtendedRuntime object for the thermostat
+ */
+ @JsonProperty("extendedRuntime")
+ public ExtendedRuntime getExtendedRuntime() {
+ return this.extendedRuntime;
+ }
+
+ /**
+ * @return the Electricity object for the thermostat
+ */
+ @JsonProperty("electricity")
+ public Electricity getElectricity() {
+ return this.electricity;
+ }
+
+ /**
+ * @return the list of Device objects linked to the thermostat
+ */
+ @JsonProperty("devices")
+ public List getDevices() {
+ return this.devices;
+ }
+
+ /**
+ * @return the Location object for the thermostat
+ */
+ @JsonProperty("location")
+ public Location getLocation() {
+ return this.location;
+ }
+
+ /**
+ * @param location
+ * the Location object for the thermostat
+ */
+ @JsonProperty("location")
+ public void setLocation(Location location) {
+ this.location = location;
+ }
+
+ /**
+ * @return the Technician object associated with the thermostat containing the technician contact information
+ */
+ @JsonProperty("technician")
+ public Technician getTechnician() {
+ return this.technician;
+ }
+
+ /**
+ * @return the Utility object associated with the thermostat containing the utility company information
+ */
+ @JsonProperty("utility")
+ public Utility getUtility() {
+ return this.utility;
+ }
+
+ /**
+ * @return the Management object associated with the thermostat containing the management company information
+ */
+ @JsonProperty("management")
+ public Management getManagement() {
+ return this.management;
+ }
+
+ /**
+ * @return the Weather object linked to the thermostat representing the current weather on the thermostat
+ */
+ @JsonProperty("weather")
+ public Weather getWeather() {
+ return this.weather;
+ }
+
+ /**
+ * @return the list of Event objects linked to the thermostat representing any events that are active or scheduled.
+ */
+ @JsonProperty("events")
+ public List getEvents() {
+ return this.events;
+ }
+
+ /**
+ * @return the Program object for the thermostat
+ */
+ @JsonProperty("program")
+ public Program getProgram() {
+ return this.program;
+ }
+
+ /**
+ * @param program
+ * the Program object for the thermostat
+ */
+ @JsonProperty("program")
+ public void setProgram(Program program) {
+ this.program = program;
+ }
+
+ /**
+ * @return the HouseDetails object that contains the information about the house the thermostat is installed in
+ */
+ @JsonProperty("houseDetails")
+ public HouseDetails getHouseDetails() {
+ return this.houseDetails;
+ }
+
+ /**
+ * @param houseDetails
+ * the HouseDetails object that contains the information about the house the thermostat is installed in
+ */
+ @JsonProperty("houseDetails")
+ public void setHouseDetails(HouseDetails houseDetails) {
+ this.houseDetails = houseDetails;
+ }
+
+ /**
+ * @return the ThermostatOemCfg object that contains information about the OEM specific thermostat
+ */
+ @JsonProperty("oemCfg")
+ public ThermostatOemCfg getOemCfg() {
+ return this.oemCfg;
+ }
+
+ /**
+ * @param oemCfg
+ * the ThermostatOemCfg object that contains information about the OEM specific thermostat
+ */
+ @JsonProperty("oemCfg")
+ public void setOemCfg(ThermostatOemCfg oemCfg) {
+ this.oemCfg = oemCfg;
+ }
+
+ /**
+ * The status of all equipment controlled by this Thermostat. Only running equipment is listed in the CSV String. If
+ * no equipment is currently running an empty string is returned.
+ *
+ * Values: heatPump, heatPump2, heatPump3, compCool1, compCool2, auxHeat1, auxHeat2, auxHeat3, fan, humidifier,
+ * dehumidifier, ventilator, economizer, compHotWater, auxHotWater.
+ *
+ * @return the equipmentStatus
+ */
+ @JsonProperty("equipmentStatus")
+ public String getEquipmentStatus() {
+ return this.equipmentStatus;
+ }
+
+ /**
+ * @return the NotificationSettings object containing the configuration for Alert and Reminders for the Thermostat
+ */
+ @JsonProperty("notificationSettings")
+ public NotificationSettings getNotificationSettings() {
+ return this.notificationSettings;
+ }
+
+ /**
+ * @param notificationSettings
+ * the NotificationSettings object containing the configuration for Alert and Reminders for the
+ * Thermostat
+ */
+ @JsonProperty("notificationSettings")
+ public void setNotificationSettings(NotificationSettings notificationSettings) {
+ this.notificationSettings = notificationSettings;
+ }
+
+ /**
+ * @return the Privacy object containing the privacy settings for the Thermostat. Note: access to this object is
+ * restricted to callers with implicit authentication.
+ */
+ @JsonProperty("privacy")
+ public ThermostatPrivacy getPrivacy() {
+ return this.privacy;
+ }
+
+ /**
+ * @param privacy
+ * the Privacy object containing the privacy settings for the Thermostat. Note: access to this object is
+ * restricted to callers with implicit authentication.
+ */
+ @JsonProperty("privacy")
+ public void setPrivacy(ThermostatPrivacy privacy) {
+ this.privacy = privacy;
+ }
+
+ /**
+ * @return the version information about the thermostat
+ */
+ @JsonProperty("version")
+ public Version getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("identifier", this.identifier);
+ builder.append("name", this.name);
+ builder.append("thermostatRev", this.thermostatRev);
+ builder.append("isRegistered", this.isRegistered);
+ builder.append("modelNumber", this.modelNumber);
+ builder.append("lastModified", this.lastModified);
+ builder.append("thermostatTime", this.thermostatTime);
+ builder.append("utcTime", this.utcTime);
+ builder.append("alerts", this.alerts);
+ builder.append("settings", this.settings);
+ builder.append("runtime", this.runtime);
+ builder.append("extendedRuntime", this.extendedRuntime);
+ builder.append("electricity", this.electricity);
+ builder.append("devices", this.devices);
+ builder.append("location", this.location);
+ builder.append("technician", this.technician);
+ builder.append("utility", this.utility);
+ builder.append("management", this.management);
+ builder.append("weather", this.weather);
+ builder.append("events", this.events);
+ builder.append("program", this.program);
+ builder.append("houseDetails", this.houseDetails);
+ builder.append("oemCfg", this.oemCfg);
+ builder.append("equipmentStatus", this.equipmentStatus);
+ builder.append("notificationSettings", this.notificationSettings);
+ builder.append("privacy", this.privacy);
+ builder.append("version", this.version);
+
+ return builder.toString();
+ }
+
+ /**
+ * The Alert object represents an alert generated either by a thermostat or user which requires user attention. It
+ * may be an error, or a reminder for a filter change. Alerts may not be modified directly but rather they must be
+ * acknowledged using the Acknowledge Function.
+ *
+ * @see Alert
+ * @author John Cocula
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Alert extends AbstractMessagePart {
+ private String acknowledgeRef;
+ private String date;
+ private String time;
+ private String severity;
+ private String text;
+ private Integer alertNumber;
+ private String alertType;
+ @JsonProperty("isOperatorAlert")
+ private Boolean _isOperatorAlert;
+ private String reminder;
+ @JsonProperty("showIdt")
+ private Boolean _showIdt;
+ @JsonProperty("showWeb")
+ private Boolean _showWeb;
+ @JsonProperty("sendEmail")
+ private Boolean _sendEmail;
+ private String acknowledgement;
+ @JsonProperty("remindMeLater")
+ private Boolean _remindMeLater;
+ private String thermostatIdentifier;
+ private String notificationType;
+
+ /**
+ * @return the acknowledgeRef
+ */
+ @JsonProperty("acknowledgeRef")
+ public String getAcknowledgeRef() {
+ return this.acknowledgeRef;
+ }
+
+ /**
+ * @return the date
+ */
+ @JsonProperty("date")
+ public String getDate() {
+ return this.date;
+ }
+
+ /**
+ * @return the time
+ */
+ @JsonProperty("time")
+ public String getTime() {
+ return this.time;
+ }
+
+ /**
+ * @return the severity
+ */
+ @JsonProperty("severity")
+ public String getSeverity() {
+ return this.severity;
+ }
+
+ /**
+ * @return the text
+ */
+ @JsonProperty("text")
+ public String getText() {
+ return this.text;
+ }
+
+ /**
+ * @return the alertNumber
+ */
+ @JsonProperty("alertNumber")
+ public Integer getAlertNumber() {
+ return this.alertNumber;
+ }
+
+ /**
+ * @return the alertType
+ */
+ @JsonProperty("alertType")
+ public String getAlertType() {
+ return this.alertType;
+ }
+
+ /**
+ * @return the isOperatorAlert
+ */
+ @JsonProperty("isOperatorAlert")
+ public Boolean isOperatorAlert() {
+ return this._isOperatorAlert;
+ }
+
+ /**
+ * @return the reminder
+ */
+ @JsonProperty("reminder")
+ public String getReminder() {
+ return this.reminder;
+ }
+
+ /**
+ * @return the showIdt
+ */
+ @JsonProperty("showIdt")
+ public Boolean showIdt() {
+ return this._showIdt;
+ }
+
+ /**
+ * @return the showWeb
+ */
+ @JsonProperty("showWeb")
+ public Boolean showWeb() {
+ return this._showWeb;
+ }
+
+ /**
+ * @return the sendEmail
+ */
+ @JsonProperty("sendEmail")
+ public Boolean sendEmail() {
+ return this._sendEmail;
+ }
+
+ /**
+ * @return the acknowledgement
+ */
+ @JsonProperty("acknowledgement")
+ public String getAcknowledgement() {
+ return this.acknowledgement;
+ }
+
+ /**
+ * @return the remindMeLater
+ */
+ @JsonProperty("remindMeLater")
+ public Boolean remindMeLater() {
+ return this._remindMeLater;
+ }
+
+ /**
+ * @return the thermostatIdentifier
+ */
+ @JsonProperty("thermostatIdentifier")
+ public String getThermostatIdentifier() {
+ return this.thermostatIdentifier;
+ }
+
+ /**
+ * @return the notificationType
+ */
+ @JsonProperty("notificationType")
+ public String getNotificationType() {
+ return this.notificationType;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+
+ builder.append("acknowledgeRef", this.acknowledgeRef);
+ builder.append("date", this.date);
+ builder.append("time", this.time);
+ builder.append("severity", this.severity);
+ builder.append("text", this.text);
+ builder.append("alertNumber", this.alertNumber);
+ builder.append("alertType", this.alertType);
+ builder.append("isOperatorAlert", this._isOperatorAlert);
+ builder.append("reminder", this.reminder);
+ builder.append("showIdt", this._showIdt);
+ builder.append("showWeb", this._showWeb);
+ builder.append("sendEmail", this._sendEmail);
+ builder.append("acknowledgement", this.acknowledgement);
+ builder.append("remindMeLater", this._remindMeLater);
+ builder.append("thermostatIdentifier", this.thermostatIdentifier);
+ builder.append("notificationType", this.notificationType);
+
+ return builder.toString();
+ }
+ }
+
+ /**
+ * The Settings Java Bean contains all the configuration properties of the thermostat in which it is contained.
+ *
+ * @see Settings
+ * @author John Cocula
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Settings extends AbstractMessagePart {
+ private HvacMode hvacMode;
+ private String lastServiceDate; // TODO Jackson 1.9 dates (@watou)
+ private Boolean serviceRemindMe;
+ private Integer monthsBetweenService;
+ private String remindMeDate; // TODO Jackson 1.9 dates (@watou)
+ private VentilatorMode vent;
+ private Integer ventilatorMinOnTime;
+ private Boolean serviceRemindTechnician;
+ private String eiLocation;
+ private Temperature coldTempAlert;
+ private Boolean coldTempAlertEnabled;
+ private Temperature hotTempAlert;
+ private Boolean hotTempAlertEnabled;
+ private Integer coolStages;
+ private Integer heatStages;
+ private Temperature maxSetBack;
+ private Temperature maxSetForward;
+ private Temperature quickSaveSetBack;
+ private Temperature quickSaveSetForward;
+ private Boolean hasHeatPump;
+ private Boolean hasForcedAir;
+ private Boolean hasBoiler;
+ private Boolean hasHumidifier;
+ private Boolean hasErv;
+ private Boolean hasHrv;
+ private Boolean condensationAvoid;
+ private Boolean useCelsius;
+ private Boolean useTimeFormat12;
+ private String locale;
+ private String humidity;
+ private String humidifierMode;
+ private Integer backlightOnIntensity;
+ private Integer backlightSleepIntensity;
+ private Integer backlightOffTime;
+ private Integer soundTickVolume;
+ private Integer soundAlertVolume;
+ private Integer compressorProtectionMinTime;
+ private Temperature compressorProtectionMinTemp;
+ private Temperature stage1HeatingDifferentialTemp;
+ private Temperature stage1CoolingDifferentialTemp;
+ private Integer stage1HeatingDissipationTime;
+ private Integer stage1CoolingDissipationTime;
+ private Boolean heatPumpReversalOnCool;
+ private Boolean fanControlRequired;
+ private Integer fanMinOnTime;
+ private Temperature heatCoolMinDelta;
+ private Temperature tempCorrection;
+ private String holdAction;
+ private Boolean heatPumpGroundWater;
+ private Boolean hasElectric;
+ private Boolean hasDehumidifier;
+ private String dehumidifierMode;
+ private Integer dehumidifierLevel;
+ private Boolean dehumidifyWithAC;
+ private Integer dehumidifyOvercoolOffset;
+ private Boolean autoHeatCoolFeatureEnabled;
+ private Boolean wifiOfflineAlert;
+ private Temperature heatMinTemp;
+ private Temperature heatMaxTemp;
+ private Temperature coolMinTemp;
+ private Temperature coolMaxTemp;
+ private Temperature heatRangeHigh;
+ private Temperature heatRangeLow;
+ private Temperature coolRangeHigh;
+ private Temperature coolRangeLow;
+ private String userAccessCode;
+ private Integer userAccessSetting;
+ private Temperature auxRuntimeAlert;
+ private Temperature auxOutdoorTempAlert;
+ private Temperature auxMaxOutdoorTemp;
+ private Boolean auxRuntimeAlertNotify;
+ private Boolean auxOutdoorTempAlertNotify;
+ private Boolean auxRuntimeAlertNotifyTechnician;
+ private Boolean auxOutdoorTempAlertNotifyTechnician;
+ private Boolean disablePreHeating;
+ private Boolean disablePreCooling;
+ private Boolean installerCodeRequired;
+ private String drAccept;
+ private Boolean isRentalProperty;
+ private Boolean useZoneController;
+ private Integer randomStartDelayCool;
+ private Integer randomStartDelayHeat;
+ private Integer humidityHighAlert;
+ private Integer humidityLowAlert;
+ private Boolean disableHeatPumpAlerts;
+ private Boolean disableAlertsOnIdt;
+ private Boolean humidityAlertNotify;
+ private Boolean humidityAlertNotifyTechnician;
+ private Boolean tempAlertNotify;
+ private Boolean tempAlertNotifyTechnician;
+ private Integer monthlyElectricityBillLimit;
+ private Boolean enableElectricityBillAlert;
+ private Boolean enableProjectedElectricityBillAlert;
+ private Integer electricityBillingDayOfMonth;
+ private Integer electricityBillCycleMonths;
+ private Integer electricityBillStartMonth;
+ private Integer ventilatorMinOnTimeHome;
+ private Integer ventilatorMinOnTimeAway;
+ private Boolean backlightOffDuringSleep;
+ private Boolean autoAway;
+ private Boolean smartCirculation;
+ private Boolean followMeComfort;
+ private String ventilatorType;
+ private Boolean isVentilatorTimerOn;
+ private Date ventilatorOffDateTime;
+ private Boolean hasUVFilter;
+ private Boolean coolingLockout;
+ private Boolean ventilatorFreeCooling;
+ private Boolean dehumidifyWhenHeating;
+ private String groupRef;
+ private String groupName;
+ private Integer groupSetting;
+
+ /**
+ * @return the current HVAC mode the thermostat is in. Values: auto, auxHeatOnly, cool, heat, off.
+ */
+ @JsonProperty("hvacMode")
+ public HvacMode getHvacMode() {
+ return this.hvacMode;
+ }
+
+ /**
+ * @param hvacMode
+ * the current HVAC mode the thermostat is in. Values: auto, auxHeatOnly, cool, heat, off.
+ */
+ @JsonProperty("hvacMode")
+ public void setHvacMode(HvacMode hvacMode) {
+ this.hvacMode = hvacMode;
+ }
+
+ /**
+ * @return the last service date of the HVAC equipment
+ */
+ @JsonProperty("lastServiceDate")
+ public String getLastServiceDate() {
+ return this.lastServiceDate;
+ }
+
+ /**
+ * @param lastServiceDate
+ * the last service date of the HVAC equipment
+ */
+ @JsonProperty("lastServiceDate")
+ public void setLastServiceDate(String lastServiceDate) {
+ this.lastServiceDate = lastServiceDate;
+ }
+
+ /**
+ * @return whether to send an alert when service is required again
+ */
+ @JsonProperty("serviceRemindMe")
+ public Boolean getServiceRemindMe() {
+ return this.serviceRemindMe;
+ }
+
+ /**
+ * @param serviceRemindMe
+ * whether to send an alert when service is required again
+ */
+ @JsonProperty("serviceRemindMe")
+ public void setServiceRemindMe(Boolean serviceRemindMe) {
+ this.serviceRemindMe = serviceRemindMe;
+ }
+
+ /**
+ * @return the user configured monthly interval between HVAC service reminders
+ */
+ @JsonProperty("monthsBetweenService")
+ public Integer getMonthsBetweenService() {
+ return this.monthsBetweenService;
+ }
+
+ /**
+ * @param monthsBetweenService
+ * the user configured monthly interval between HVAC service reminders
+ */
+ @JsonProperty("monthsBetweenService")
+ public void setMonthsBetweenService(Integer monthsBetweenService) {
+ this.monthsBetweenService = monthsBetweenService;
+ }
+
+ /**
+ * @return the date to be reminded about the next HVAC service date
+ */
+ @JsonProperty("remindMeDate")
+ public String getRemindMeDate() {
+ return this.remindMeDate;
+ }
+
+ /**
+ * @param remindMeDate
+ * the date to be reminded about the next HVAC service date
+ */
+ @JsonProperty("remindMeDate")
+ public void setRemindMeDate(String remindMeDate) {
+ this.remindMeDate = remindMeDate;
+ }
+
+ /**
+ * @return the ventilator mode. Values: auto, minontime, on, off
+ */
+ @JsonProperty("vent")
+ public VentilatorMode getVent() {
+ return this.vent;
+ }
+
+ /**
+ * @param vent
+ * the ventilator mode. Values: auto, minontime, on, off
+ */
+ @JsonProperty("vent")
+ public void setVent(VentilatorMode vent) {
+ this.vent = vent;
+ }
+
+ /**
+ * @return the minimum time in minutes the ventilator is configured to run. The thermostat will always guarantee
+ * that the ventilator runs for this minimum duration whenever engaged.
+ */
+ @JsonProperty("ventilatorMinOnTime")
+ public Integer getVentilatorMinOnTime() {
+ return this.ventilatorMinOnTime;
+ }
+
+ /**
+ * @param ventilatorMinOnTime
+ * the minimum time in minutes the ventilator is configured to run. The thermostat will always
+ * guarantee that the ventilator runs for this minimum duration whenever engaged.
+ */
+ @JsonProperty("ventilatorMinOnTime")
+ public void setVentilatorMinOnTime(Integer ventilatorMinOnTime) {
+ this.ventilatorMinOnTime = ventilatorMinOnTime;
+ }
+
+ /**
+ * @return whether the technician associated with this thermostat should receive the HVAC service reminders as
+ * well
+ */
+ @JsonProperty("serviceRemindTechnician")
+ public Boolean getServiceRemindTechnician() {
+ return this.serviceRemindTechnician;
+ }
+
+ /**
+ * @param serviceRemindTechnician
+ * whether the technician associated with this thermostat should receive the HVAC service reminders
+ * as well
+ */
+ @JsonProperty("serviceRemindTechnician")
+ public void setServiceRemindTechnician(Boolean serviceRemindTechnician) {
+ this.serviceRemindTechnician = serviceRemindTechnician;
+ }
+
+ /**
+ * @return a note about the physical location where the SMART or EMS Equipment Interface module is located
+ */
+ @JsonProperty("eiLocation")
+ public String getEiLocation() {
+ return this.eiLocation;
+ }
+
+ /**
+ * @param eiLocation
+ * a note about the physical location where the SMART or EMS Equipment Interface module is located
+ */
+ @JsonProperty("eiLocation")
+ public void setEiLocation(String eiLocation) {
+ this.eiLocation = eiLocation;
+ }
+
+ /**
+ * @return the temperature at which a cold temp alert is triggered
+ */
+ @JsonProperty("coldTempAlert")
+ public Temperature getColdTempAlert() {
+ return this.coldTempAlert;
+ }
+
+ /**
+ * @param coldTempAlert
+ * the temperature at which a cold temp alert is triggered
+ */
+ @JsonProperty("coldTempAlert")
+ public void setColdTempAlert(Temperature coldTempAlert) {
+ this.coldTempAlert = coldTempAlert;
+ }
+
+ /**
+ * @return whether cold temperature alerts are enabled
+ */
+ @JsonProperty("coldTempAlertEnabled")
+ public Boolean getColdTempAlertEnabled() {
+ return this.coldTempAlertEnabled;
+ }
+
+ /**
+ * @param coldTempAlertEnabled
+ * whether cold temperature alerts are enabled
+ */
+ @JsonProperty("coldTempAlertEnabled")
+ public void setColdTempAlertEnabled(Boolean coldTempAlertEnabled) {
+ this.coldTempAlertEnabled = coldTempAlertEnabled;
+ }
+
+ /**
+ * @return the temperature at which a hot temp alert is triggered
+ */
+ @JsonProperty("hotTempAlert")
+ public Temperature getHotTempAlert() {
+ return this.hotTempAlert;
+ }
+
+ /**
+ * @param hotTempAlert
+ * the temperature at which a hot temp alert is triggered
+ */
+ @JsonProperty("hotTempAlert")
+ public void setHotTempAlert(Temperature hotTempAlert) {
+ this.hotTempAlert = hotTempAlert;
+ }
+
+ /**
+ * @return whether hot temperature alerts are enabled
+ */
+ @JsonProperty("hotTempAlertEnabled")
+ public Boolean getHotTempAlertEnabled() {
+ return this.hotTempAlertEnabled;
+ }
+
+ /**
+ * @param hotTempAlertEnabled
+ * whether hot temperature alerts are enabled
+ */
+ @JsonProperty("hotTempAlertEnabled")
+ public void setHotTempAlertEnabled(Boolean hotTempAlertEnabled) {
+ this.hotTempAlertEnabled = hotTempAlertEnabled;
+ }
+
+ /**
+ * @return the number of cool stages the connected HVAC equipment supports
+ */
+ @JsonProperty("coolStages")
+ public Integer getCoolStages() {
+ return this.coolStages;
+ }
+
+ /**
+ * @return the number of heat stages the connected HVAC equipment supports
+ */
+ @JsonProperty("heatStages")
+ public Integer getHeatStages() {
+ return this.heatStages;
+ }
+
+ /**
+ * @return the maximum automated set point set back offset allowed in degrees
+ */
+ @JsonProperty("maxSetBack")
+ public Temperature getMaxSetBack() {
+ return this.maxSetBack;
+ }
+
+ /**
+ * @param maxSetBack
+ * the maximum automated set point set back offset allowed in degrees
+ */
+ @JsonProperty("maxSetBack")
+ public void setMaxSetBack(Temperature maxSetBack) {
+ this.maxSetBack = maxSetBack;
+ }
+
+ /**
+ * @return the maximum automated set point set forward offset allowed in degrees
+ */
+ @JsonProperty("maxSetForward")
+ public Temperature getMaxSetForward() {
+ return this.maxSetForward;
+ }
+
+ /**
+ * @param maxSetForward
+ * the maximum automated set point set forward offset allowed in degrees
+ */
+ @JsonProperty("maxSetForward")
+ public void setMaxSetForward(Temperature maxSetForward) {
+ this.maxSetForward = maxSetForward;
+ }
+
+ /**
+ * @return the set point set back offset, in degrees, configured for a quick save event
+ */
+ @JsonProperty("quickSaveSetBack")
+ public Temperature getQuickSaveSetBack() {
+ return this.quickSaveSetBack;
+ }
+
+ /**
+ * @param quickSaveSetBack
+ * the set point set back offset, in degrees, configured for a quick save event
+ */
+ @JsonProperty("quickSaveSetBack")
+ public void setQuickSaveSetBack(Temperature quickSaveSetBack) {
+ this.quickSaveSetBack = quickSaveSetBack;
+ }
+
+ /**
+ * @return the set point set forward offset, in degrees, configured for a quick save event
+ */
+ @JsonProperty("quickSaveSetForward")
+ public Temperature getQuickSaveSetForward() {
+ return this.quickSaveSetForward;
+ }
+
+ /**
+ * @param quickSaveSetForward
+ * the set point set forward offset, in degrees, configured for a quick save event
+ */
+ @JsonProperty("quickSaveSetForward")
+ public void setQuickSaveSetForward(Temperature quickSaveSetForward) {
+ this.quickSaveSetForward = quickSaveSetForward;
+ }
+
+ /**
+ * @return whether the thermostat is controlling a heat pump
+ */
+ @JsonProperty("hasHeatPump")
+ public Boolean getHasHeatPump() {
+ return this.hasHeatPump;
+ }
+
+ /**
+ * @return whether the thermostat is controlling a forced air furnace
+ */
+ @JsonProperty("hasForcedAir")
+ public Boolean getHasForcedAir() {
+ return this.hasForcedAir;
+ }
+
+ /**
+ * @return whether the thermostat is controlling a boiler
+ */
+ @JsonProperty("hasBoiler")
+ public Boolean getHasBoiler() {
+ return this.hasBoiler;
+ }
+
+ /**
+ * @return whether the thermostat is controlling a humidifier
+ */
+ @JsonProperty("hasHumidifier")
+ public Boolean getHasHumidifier() {
+ return this.hasHumidifier;
+ }
+
+ /**
+ * @return whether the thermostat is controlling an energy recovery ventilator
+ */
+ @JsonProperty("hasErv")
+ public Boolean getHasErv() {
+ return this.hasErv;
+ }
+
+ /**
+ * @return whether the thermostat is controlling a heat recovery ventilator
+ */
+ @JsonProperty("hasHrv")
+ public Boolean getHasHrv() {
+ return this.hasHrv;
+ }
+
+ /**
+ * @return whether the thermostat is in frost control mode
+ */
+ @JsonProperty("condensationAvoid")
+ public Boolean getCondensationAvoid() {
+ return this.condensationAvoid;
+ }
+
+ /**
+ * @param condensationAvoid
+ * whether the thermostat is in frost control mode
+ */
+ @JsonProperty("condensationAvoid")
+ public void setCondensationAvoid(Boolean condensationAvoid) {
+ this.condensationAvoid = condensationAvoid;
+ }
+
+ /**
+ * @return whether the thermostat is configured to report in degrees Celsius
+ */
+ @JsonProperty("useCelsius")
+ public Boolean getUseCelsius() {
+ return this.useCelsius;
+ }
+
+ /**
+ * @param useCelsius
+ * whether the thermostat is configured to report in degrees Celsius
+ */
+ @JsonProperty("useCelsius")
+ public void setUseCelsius(Boolean useCelsius) {
+ this.useCelsius = useCelsius;
+ }
+
+ /**
+ * @return whether the thermostat is using 12hr time format
+ */
+ @JsonProperty("useTimeFormat12")
+ public Boolean getUseTimeFormat12() {
+ return this.useTimeFormat12;
+ }
+
+ /**
+ * @param useTimeFormat12
+ * whether the thermostat is using 12hr time format
+ */
+ @JsonProperty("useTimeFormat12")
+ public void setUseTimeFormat12(Boolean useTimeFormat12) {
+ this.useTimeFormat12 = useTimeFormat12;
+ }
+
+ /**
+ * @return the locale
+ */
+ @JsonProperty("locale")
+ public String getLocale() {
+ return this.locale;
+ }
+
+ /**
+ * @param locale
+ * the locale to set
+ */
+ @JsonProperty("locale")
+ public void setLocale(String locale) {
+ this.locale = locale;
+ }
+
+ /**
+ * @return the minimum humidity level (in percent) set point for the humidifier
+ */
+ @JsonProperty("humidity")
+ public String getHumidity() {
+ return this.humidity;
+ }
+
+ /**
+ * @param humidity
+ * the minimum humidity level (in percent) set point for the humidifier
+ */
+ @JsonProperty("humidity")
+ public void setHumidity(String humidity) {
+ this.humidity = humidity;
+ }
+
+ /**
+ * @return the humidifier mode. Values: auto, manual, off
+ */
+ @JsonProperty("humidifierMode")
+ public String getHumidifierMode() {
+ return this.humidifierMode;
+ }
+
+ /**
+ * @param humidifierMode
+ * the humidifier mode. Values: auto, manual, off
+ */
+ @JsonProperty("humidifierMode")
+ public void setHumidifierMode(String humidifierMode) {
+ this.humidifierMode = humidifierMode;
+ }
+
+ /**
+ * @return the thermostat backlight intensity when on. A value between 1 and 10.
+ */
+ @JsonProperty("backlightOnIntensity")
+ public Integer getBacklightOnIntensity() {
+ return this.backlightOnIntensity;
+ }
+
+ /**
+ * @param backlightOnIntensity
+ * the thermostat backlight intensity when on. A value between 1 and 10.
+ */
+ @JsonProperty("backlightOnIntensity")
+ public void setBacklightOnIntensity(Integer backlightOnIntensity) {
+ this.backlightOnIntensity = backlightOnIntensity;
+ }
+
+ /**
+ * @return the thermostat backlight intensity when asleep. A value between 1 and 10.
+ */
+ @JsonProperty("backlightSleepIntensity")
+ public Integer getBacklightSleepIntensity() {
+ return this.backlightSleepIntensity;
+ }
+
+ /**
+ * @param backlightSleepIntensity
+ * the thermostat backlight intensity when asleep. A value between 1 and 10.
+ */
+ @JsonProperty("backlightSleepIntensity")
+ public void setBacklightSleepIntensity(Integer backlightSleepIntensity) {
+ this.backlightSleepIntensity = backlightSleepIntensity;
+ }
+
+ /**
+ * @return the time in seconds before the thermostat screen goes into sleep mode
+ */
+ @JsonProperty("backlightOffTime")
+ public Integer getBacklightOffTime() {
+ return this.backlightOffTime;
+ }
+
+ /**
+ * @param backlightOffTime
+ * the time in seconds before the thermostat screen goes into sleep mode
+ */
+ @JsonProperty("backlightOffTime")
+ public void setBacklightOffTime(Integer backlightOffTime) {
+ this.backlightOffTime = backlightOffTime;
+ }
+
+ /**
+ * @return the volume level for key presses on the thermostat. A value between 1 and 10.
+ */
+ @JsonProperty("soundTickVolume")
+ public Integer getSoundTickVolume() {
+ return this.soundTickVolume;
+ }
+
+ /**
+ * @param soundTickVolume
+ * the volume level for key presses on the thermostat. A value between 1 and 10.
+ */
+ @JsonProperty("soundTickVolume")
+ public void setSoundTickVolume(Integer soundTickVolume) {
+ this.soundTickVolume = soundTickVolume;
+ }
+
+ /**
+ * @return the volume level for alerts on the thermostat. A value between 1 and 10.
+ */
+ @JsonProperty("soundAlertVolume")
+ public Integer getSoundAlertVolume() {
+ return this.soundAlertVolume;
+ }
+
+ /**
+ * @param soundAlertVolume
+ * the volume level for alerts on the thermostat. A value between 1 and 10.
+ */
+ @JsonProperty("soundAlertVolume")
+ public void setSoundAlertVolume(Integer soundAlertVolume) {
+ this.soundAlertVolume = soundAlertVolume;
+ }
+
+ /**
+ * @return the compressorProtectionMinTime
+ */
+ @JsonProperty("compressorProtectionMinTime")
+ public Integer getCompressorProtectionMinTime() {
+ return this.compressorProtectionMinTime;
+ }
+
+ /**
+ * @param compressorProtectionMinTime
+ * the compressorProtectionMinTime to set
+ */
+ @JsonProperty("compressorProtectionMinTime")
+ public void setCompressorProtectionMinTime(Integer compressorProtectionMinTime) {
+ this.compressorProtectionMinTime = compressorProtectionMinTime;
+ }
+
+ /**
+ * @return the compressorProtectionMinTemp
+ */
+ @JsonProperty("compressorProtectionMinTemp")
+ public Temperature getCompressorProtectionMinTemp() {
+ return this.compressorProtectionMinTemp;
+ }
+
+ /**
+ * @param compressorProtectionMinTemp
+ * the compressorProtectionMinTemp to set
+ */
+ @JsonProperty("compressorProtectionMinTemp")
+ public void setCompressorProtectionMinTemp(Temperature compressorProtectionMinTemp) {
+ this.compressorProtectionMinTemp = compressorProtectionMinTemp;
+ }
+
+ /**
+ * @return the stage1HeatingDifferentialTemp
+ */
+ @JsonProperty("stage1HeatingDifferentialTemp")
+ public Temperature getStage1HeatingDifferentialTemp() {
+ return this.stage1HeatingDifferentialTemp;
+ }
+
+ /**
+ * @param stage1HeatingDifferentialTemp
+ * the stage1HeatingDifferentialTemp to set
+ */
+ @JsonProperty("stage1HeatingDifferentialTemp")
+ public void setStage1HeatingDifferentialTemp(Temperature stage1HeatingDifferentialTemp) {
+ this.stage1HeatingDifferentialTemp = stage1HeatingDifferentialTemp;
+ }
+
+ /**
+ * @return the stage1CoolingDifferentialTemp
+ */
+ @JsonProperty("stage1CoolingDifferentialTemp")
+ public Temperature getStage1CoolingDifferentialTemp() {
+ return this.stage1CoolingDifferentialTemp;
+ }
+
+ /**
+ * @param stage1CoolingDifferentialTemp
+ * the stage1CoolingDifferentialTemp to set
+ */
+ @JsonProperty("stage1CoolingDifferentialTemp")
+ public void setStage1CoolingDifferentialTemp(Temperature stage1CoolingDifferentialTemp) {
+ this.stage1CoolingDifferentialTemp = stage1CoolingDifferentialTemp;
+ }
+
+ /**
+ * @return the stage1HeatingDissipationTime
+ */
+ @JsonProperty("stage1HeatingDissipationTime")
+ public Integer getStage1HeatingDissipationTime() {
+ return this.stage1HeatingDissipationTime;
+ }
+
+ /**
+ * @param stage1HeatingDissipationTime
+ * the stage1HeatingDissipationTime to set
+ */
+ @JsonProperty("stage1HeatingDissipationTime")
+ public void setStage1HeatingDissipationTime(Integer stage1HeatingDissipationTime) {
+ this.stage1HeatingDissipationTime = stage1HeatingDissipationTime;
+ }
+
+ /**
+ * @return the stage1CoolingDissipationTime
+ */
+ @JsonProperty("stage1CoolingDissipationTime")
+ public Integer getStage1CoolingDissipationTime() {
+ return this.stage1CoolingDissipationTime;
+ }
+
+ /**
+ * @param stage1CoolingDissipationTime
+ * the stage1CoolingDissipationTime to set
+ */
+ @JsonProperty("stage1CoolingDissipationTime")
+ public void setStage1CoolingDissipationTime(Integer stage1CoolingDissipationTime) {
+ this.stage1CoolingDissipationTime = stage1CoolingDissipationTime;
+ }
+
+ /**
+ * @return the heatPumpReversalOnCool
+ */
+ @JsonProperty("heatPumpReversalOnCool")
+ public Boolean getHeatPumpReversalOnCool() {
+ return this.heatPumpReversalOnCool;
+ }
+
+ /**
+ * @param heatPumpReversalOnCool
+ * the heatPumpReversalOnCool to set
+ */
+ @JsonProperty("heatPumpReversalOnCool")
+ public void setHeatPumpReversalOnCool(Boolean heatPumpReversalOnCool) {
+ this.heatPumpReversalOnCool = heatPumpReversalOnCool;
+ }
+
+ /**
+ * @return the fanControlRequired
+ */
+ @JsonProperty("fanControlRequired")
+ public Boolean getFanControlRequired() {
+ return this.fanControlRequired;
+ }
+
+ /**
+ * @param fanControlRequired
+ * the fanControlRequired to set
+ */
+ @JsonProperty("fanControlRequired")
+ public void setFanControlRequired(Boolean fanControlRequired) {
+ this.fanControlRequired = fanControlRequired;
+ }
+
+ /**
+ * @return the minimum time, in minutes, to run the fan each hour. Value from 1 to 60.
+ */
+ @JsonProperty("fanMinOnTime")
+ public Integer getFanMinOnTime() {
+ return this.fanMinOnTime;
+ }
+
+ /**
+ * @param fanMinOnTime
+ * the minimum time, in minutes, to run the fan each hour. Value from 1 to 60.
+ */
+ @JsonProperty("fanMinOnTime")
+ public void setFanMinOnTime(Integer fanMinOnTime) {
+ this.fanMinOnTime = fanMinOnTime;
+ }
+
+ /**
+ * @return the minimum temperature difference between the heat and cool values. Used to ensure that when
+ * thermostat is in auto mode, the heat and cool values are separated by at least this value.
+ */
+ @JsonProperty("heatCoolMinDelta")
+ public Temperature getHeatCoolMinDelta() {
+ return this.heatCoolMinDelta;
+ }
+
+ /**
+ * @param heatCoolMinDelta
+ * the minimum temperature difference between the heat and cool values. Used to ensure that when
+ * thermostat is in auto mode, the heat and cool values are separated by at least this value.
+ */
+ @JsonProperty("heatCoolMinDelta")
+ public void setHeatCoolMinDelta(Temperature heatCoolMinDelta) {
+ this.heatCoolMinDelta = heatCoolMinDelta;
+ }
+
+ /**
+ * @return the tempCorrection
+ */
+ @JsonProperty("tempCorrection")
+ public Temperature getTempCorrection() {
+ return this.tempCorrection;
+ }
+
+ /**
+ * @param tempCorrection
+ * the tempCorrection to set
+ */
+ @JsonProperty("tempCorrection")
+ public void setTempCorrection(Temperature tempCorrection) {
+ this.tempCorrection = tempCorrection;
+ }
+
+ /**
+ * @return the default end time setting the thermostat applies to user temperature holds. Values
+ * useEndTime4hour, useEndTime2hour (EMS Only), nextPeriod, indefinite, askMe
+ */
+ @JsonProperty("holdAction")
+ public String getHoldAction() {
+ return this.holdAction;
+ }
+
+ /**
+ * @param holdAction
+ * the default end time setting the thermostat applies to user temperature holds. Values
+ * useEndTime4hour, useEndTime2hour (EMS Only), nextPeriod, indefinite, askMe
+ */
+ @JsonProperty("holdAction")
+ public void setHoldAction(String holdAction) {
+ this.holdAction = holdAction;
+ }
+
+ /**
+ * @return the heatPumpGroundWater
+ */
+ @JsonProperty("heatPumpGroundWater")
+ public Boolean getHeatPumpGroundWater() {
+ return this.heatPumpGroundWater;
+ }
+
+ /**
+ * @return whether the thermostat is connected to an electric HVAC system
+ */
+ @JsonProperty("hasElectric")
+ public Boolean getHasElectric() {
+ return this.hasElectric;
+ }
+
+ /**
+ * @return whether the thermostat is connected to a dehumidifier
+ */
+ @JsonProperty("hasDehumidifier")
+ public Boolean getHasDehumidifier() {
+ return this.hasDehumidifier;
+ }
+
+ /**
+ * @return the dehumidifier mode. Values: on, off.
+ */
+ @JsonProperty("dehumidifierMode")
+ public String getDehumidifierMode() {
+ return this.dehumidifierMode;
+ }
+
+ /**
+ * @param dehumidifierMode
+ * the dehumidifier mode. Values: on, off.
+ */
+ @JsonProperty("dehumidifierMode")
+ public void setDehumidifierMode(String dehumidifierMode) {
+ this.dehumidifierMode = dehumidifierMode;
+ }
+
+ /**
+ * @return the dehumidification set point in percentage
+ */
+ @JsonProperty("dehumidifierLevel")
+ public Integer getDehumidifierLevel() {
+ return this.dehumidifierLevel;
+ }
+
+ /**
+ * @param dehumidifierLevel
+ * the dehumidification set point in percentage
+ */
+ @JsonProperty("dehumidifierLevel")
+ public void setDehumidifierLevel(Integer dehumidifierLevel) {
+ this.dehumidifierLevel = dehumidifierLevel;
+ }
+
+ /**
+ * @return whether the thermostat should use AC overcool to dehumidify.
+ */
+ @JsonProperty("dehumidifyWithAC")
+ public Boolean getDehumidifyWithAC() {
+ return this.dehumidifyWithAC;
+ }
+
+ /**
+ * @param dehumidifyWithAC
+ * whether the thermostat should use AC overcool to dehumidify. When set to true a positive integer
+ * value must be supplied for dehumidifyOvercoolOffset otherwise an API validation exception will be
+ * thrown. TODO implement constraint here (@watou).
+ */
+ @JsonProperty("dehumidifyWithAC")
+ public void setDehumidifyWithAC(Boolean dehumidifyWithAC) {
+ this.dehumidifyWithAC = dehumidifyWithAC;
+ }
+
+ /**
+ * @return whether the thermostat should use AC overcool to dehumidify and what that temperature offset should
+ * be. A value of 0 means this feature is disabled and dehumidifyWithAC will be set to false. Value
+ * represents the value in F to subtract from the current set point.
+ */
+ @JsonProperty("dehumidifyOvercoolOffset")
+ public Integer getDehumidifyOvercoolOffset() {
+ return this.dehumidifyOvercoolOffset;
+ }
+
+ /**
+ * @param dehumidifyOvercoolOffset
+ * whether the thermostat should use AC overcool to dehumidify and what that temperature offset
+ * should be. A value of 0 means this feature is disabled and dehumidifyWithAC will be set to false.
+ * Value represents the value in F to subtract from the current set point. Values should be in the
+ * range 0 - 50 and be divisible by 5.
+ */
+ @JsonProperty("dehumidifyOvercoolOffset")
+ public void setDehumidifyOvercoolOffset(Integer dehumidifyOvercoolOffset) {
+ this.dehumidifyOvercoolOffset = dehumidifyOvercoolOffset;
+ }
+
+ /**
+ * @return the autoHeatCoolFeatureEnabled
+ */
+ @JsonProperty("autoHeatCoolFeatureEnabled")
+ public Boolean getAutoHeatCoolFeatureEnabled() {
+ return this.autoHeatCoolFeatureEnabled;
+ }
+
+ /**
+ * @param autoHeatCoolFeatureEnabled
+ * the autoHeatCoolFeatureEnabled to set
+ */
+ @JsonProperty("autoHeatCoolFeatureEnabled")
+ public void setAutoHeatCoolFeatureEnabled(Boolean autoHeatCoolFeatureEnabled) {
+ this.autoHeatCoolFeatureEnabled = autoHeatCoolFeatureEnabled;
+ }
+
+ /**
+ * @return the wifiOfflineAlert
+ */
+ @JsonProperty("wifiOfflineAlert")
+ public Boolean getWifiOfflineAlert() {
+ return this.wifiOfflineAlert;
+ }
+
+ /**
+ * @param wifiOfflineAlert
+ * the wifiOfflineAlert to set
+ */
+ @JsonProperty("wifiOfflineAlert")
+ public void setWifiOfflineAlert(Boolean wifiOfflineAlert) {
+ this.wifiOfflineAlert = wifiOfflineAlert;
+ }
+
+ /**
+ * @return the minimum heat set point allowed by the thermostat firmware
+ */
+ @JsonProperty("heatMinTemp")
+ public Temperature getHeatMinTemp() {
+ return this.heatMinTemp;
+ }
+
+ /**
+ * @return the maximum heat set point allowed by the thermostat firmware
+ */
+ @JsonProperty("heatMaxTemp")
+ public Temperature getHeatMaxTemp() {
+ return this.heatMaxTemp;
+ }
+
+ /**
+ * @return the minimum cool set point allowed by the thermostat firmware
+ */
+ @JsonProperty("coolMinTemp")
+ public Temperature getCoolMinTemp() {
+ return this.coolMinTemp;
+ }
+
+ /**
+ * @return the maximum cool set point allowed by the thermostat firmware
+ */
+ @JsonProperty("coolMaxTemp")
+ public Temperature getCoolMaxTemp() {
+ return this.coolMaxTemp;
+ }
+
+ /**
+ * @return the maximum heat set point configured by the user's preferences
+ */
+ @JsonProperty("heatRangeHigh")
+ public Temperature getHeatRangeHigh() {
+ return this.heatRangeHigh;
+ }
+
+ /**
+ * @param heatRangeHigh
+ * the maximum heat set point configured by the user's preferences
+ */
+ @JsonProperty("heatRangeHigh")
+ public void setHeatRangeHigh(Temperature heatRangeHigh) {
+ this.heatRangeHigh = heatRangeHigh;
+ }
+
+ /**
+ * @return the minimum heat set point configured by the user's preferences
+ */
+ @JsonProperty("heatRangeLow")
+ public Temperature getHeatRangeLow() {
+ return this.heatRangeLow;
+ }
+
+ /**
+ * @param heatRangeLow
+ * the minimum heat set point configured by the user's preferences
+ */
+ @JsonProperty("heatRangeLow")
+ public void setHeatRangeLow(Temperature heatRangeLow) {
+ this.heatRangeLow = heatRangeLow;
+ }
+
+ /**
+ * @return the maximum cool set point configured by the user's preferences
+ */
+ @JsonProperty("coolRangeHigh")
+ public Temperature getCoolRangeHigh() {
+ return this.coolRangeHigh;
+ }
+
+ /**
+ * @param coolRangeHigh
+ * the maximum cool set point configured by the user's preferences
+ */
+ @JsonProperty("coolRangeHigh")
+ public void setCoolRangeHigh(Temperature coolRangeHigh) {
+ this.coolRangeHigh = coolRangeHigh;
+ }
+
+ /**
+ * @return the minimum heat set point configured by the user's preferences
+ */
+ @JsonProperty("coolRangeLow")
+ public Temperature getCoolRangeLow() {
+ return this.coolRangeLow;
+ }
+
+ /**
+ * @param coolRangeLow
+ * the minimum heat set point configured by the user's preferences
+ */
+ @JsonProperty("coolRangeLow")
+ public void setCoolRangeLow(Temperature coolRangeLow) {
+ this.coolRangeLow = coolRangeLow;
+ }
+
+ /**
+ * @return the userAccessCode
+ */
+ @JsonProperty("userAccessCode")
+ public String getUserAccessCode() {
+ return this.userAccessCode;
+ }
+
+ /**
+ * @param userAccessCode
+ * the userAccessCode to set
+ */
+ @JsonProperty("userAccessCode")
+ public void setUserAccessCode(String userAccessCode) {
+ this.userAccessCode = userAccessCode;
+ }
+
+ /**
+ * @return the userAccessSetting
+ */
+ @JsonProperty("userAccessSetting")
+ public Integer getUserAccessSetting() {
+ return this.userAccessSetting;
+ }
+
+ /**
+ * @param userAccessSetting
+ * the userAccessSetting to set
+ */
+ @JsonProperty("userAccessSetting")
+ public void setUserAccessSetting(Integer userAccessSetting) {
+ this.userAccessSetting = userAccessSetting;
+ }
+
+ /**
+ * @return the auxRuntimeAlert
+ */
+ @JsonProperty("auxRuntimeAlert")
+ public Temperature getAuxRuntimeAlert() {
+ return this.auxRuntimeAlert;
+ }
+
+ /**
+ * @param auxRuntimeAlert
+ * the auxRuntimeAlert to set
+ */
+ @JsonProperty("auxRuntimeAlert")
+ public void setAuxRuntimeAlert(Temperature auxRuntimeAlert) {
+ this.auxRuntimeAlert = auxRuntimeAlert;
+ }
+
+ /**
+ * @return the auxOutdoorTempAlert
+ */
+ @JsonProperty("auxOutdoorTempAlert")
+ public Temperature getAuxOutdoorTempAlert() {
+ return this.auxOutdoorTempAlert;
+ }
+
+ /**
+ * @param auxOutdoorTempAlert
+ * the auxOutdoorTempAlert to set
+ */
+ @JsonProperty("auxOutdoorTempAlert")
+ public void setAuxOutdoorTempAlert(Temperature auxOutdoorTempAlert) {
+ this.auxOutdoorTempAlert = auxOutdoorTempAlert;
+ }
+
+ /**
+ * @return the auxMaxOutdoorTemp
+ */
+ @JsonProperty("auxMaxOutdoorTemp")
+ public Temperature getAuxMaxOutdoorTemp() {
+ return this.auxMaxOutdoorTemp;
+ }
+
+ /**
+ * @param auxMaxOutdoorTemp
+ * the auxMaxOutdoorTemp to set
+ */
+ @JsonProperty("auxMaxOutdoorTemp")
+ public void setAuxMaxOutdoorTemp(Temperature auxMaxOutdoorTemp) {
+ this.auxMaxOutdoorTemp = auxMaxOutdoorTemp;
+ }
+
+ /**
+ * @return the auxRuntimeAlertNotify
+ */
+ @JsonProperty("auxRuntimeAlertNotify")
+ public Boolean getAuxRuntimeAlertNotify() {
+ return this.auxRuntimeAlertNotify;
+ }
+
+ /**
+ * @param auxRuntimeAlertNotify
+ * the auxRuntimeAlertNotify to set
+ */
+ @JsonProperty("auxRuntimeAlertNotify")
+ public void setAuxRuntimeAlertNotify(Boolean auxRuntimeAlertNotify) {
+ this.auxRuntimeAlertNotify = auxRuntimeAlertNotify;
+ }
+
+ /**
+ * @return the auxOutdoorTempAlertNotify
+ */
+ @JsonProperty("auxOutdoorTempAlertNotify")
+ public Boolean getAuxOutdoorTempAlertNotify() {
+ return this.auxOutdoorTempAlertNotify;
+ }
+
+ /**
+ * @param auxOutdoorTempAlertNotify
+ * the auxOutdoorTempAlertNotify to set
+ */
+ @JsonProperty("auxOutdoorTempAlertNotify")
+ public void setAuxOutdoorTempAlertNotify(Boolean auxOutdoorTempAlertNotify) {
+ this.auxOutdoorTempAlertNotify = auxOutdoorTempAlertNotify;
+ }
+
+ /**
+ * @return the auxRuntimeAlertNotifyTechnician
+ */
+ @JsonProperty("auxRuntimeAlertNotifyTechnician")
+ public Boolean getAuxRuntimeAlertNotifyTechnician() {
+ return this.auxRuntimeAlertNotifyTechnician;
+ }
+
+ /**
+ * @param auxRuntimeAlertNotifyTechnician
+ * the auxRuntimeAlertNotifyTechnician to set
+ */
+ @JsonProperty("auxRuntimeAlertNotifyTechnician")
+ public void setAuxRuntimeAlertNotifyTechnician(Boolean auxRuntimeAlertNotifyTechnician) {
+ this.auxRuntimeAlertNotifyTechnician = auxRuntimeAlertNotifyTechnician;
+ }
+
+ /**
+ * @return the auxOutdoorTempAlertNotifyTechnician
+ */
+ @JsonProperty("auxOutdoorTempAlertNotifyTechnician")
+ public Boolean getAuxOutdoorTempAlertNotifyTechnician() {
+ return this.auxOutdoorTempAlertNotifyTechnician;
+ }
+
+ /**
+ * @param auxOutdoorTempAlertNotifyTechnician
+ * the auxOutdoorTempAlertNotifyTechnician to set
+ */
+ @JsonProperty("auxOutdoorTempAlertNotifyTechnician")
+ public void setAuxOutdoorTempAlertNotifyTechnician(Boolean auxOutdoorTempAlertNotifyTechnician) {
+ this.auxOutdoorTempAlertNotifyTechnician = auxOutdoorTempAlertNotifyTechnician;
+ }
+
+ /**
+ * @return whether the thermostat should use pre heating to reach the set point on time
+ */
+ @JsonProperty("disablePreHeating")
+ public Boolean getDisablePreHeating() {
+ return this.disablePreHeating;
+ }
+
+ /**
+ * @param disablePreHeating
+ * whether the thermostat should use pre heating to reach the set point on time
+ */
+ @JsonProperty("disablePreHeating")
+ public void setDisablePreHeating(Boolean disablePreHeating) {
+ this.disablePreHeating = disablePreHeating;
+ }
+
+ /**
+ * @return whether the thermostat should use pre cooling to reach the set point on time
+ */
+ @JsonProperty("disablePreCooling")
+ public Boolean getDisablePreCooling() {
+ return this.disablePreCooling;
+ }
+
+ /**
+ * @param disablePreCooling
+ * whether the thermostat should use pre cooling to reach the set point on time
+ */
+ @JsonProperty("disablePreCooling")
+ public void setDisablePreCooling(Boolean disablePreCooling) {
+ this.disablePreCooling = disablePreCooling;
+ }
+
+ /**
+ * @return whether an installer code is required
+ */
+ @JsonProperty("installerCodeRequired")
+ public Boolean getInstallerCodeRequired() {
+ return this.installerCodeRequired;
+ }
+
+ /**
+ * @param installerCodeRequired
+ * whether an installer code is required
+ */
+ @JsonProperty("installerCodeRequired")
+ public void setInstallerCodeRequired(Boolean installerCodeRequired) {
+ this.installerCodeRequired = installerCodeRequired;
+ }
+
+ /**
+ * @return whether Demand Response requests are accepted by this thermostat. Possible values are: always, askMe,
+ * customerSelect, defaultAccept, defaultDecline, never.
+ */
+ @JsonProperty("drAccept")
+ public String getDrAccept() {
+ return this.drAccept;
+ }
+
+ /**
+ * @param drAccept
+ * whether Demand Response requests are accepted by this thermostat. Possible values are: always,
+ * askMe, customerSelect, defaultAccept, defaultDecline, never.
+ */
+ @JsonProperty("drAccept")
+ public void setDrAccept(String drAccept) {
+ this.drAccept = drAccept;
+ }
+
+ /**
+ * @return whether the property is a rental or not
+ */
+ @JsonProperty("isRentalProperty")
+ public Boolean getIsRentalProperty() {
+ return this.isRentalProperty;
+ }
+
+ /**
+ * @param isRentalProperty
+ * whether the property is a rental or not
+ */
+ @JsonProperty("isRentalProperty")
+ public void setIsRentalProperty(Boolean isRentalProperty) {
+ this.isRentalProperty = isRentalProperty;
+ }
+
+ /**
+ * @return whether to use a zone controller or not
+ */
+ @JsonProperty("useZoneController")
+ public Boolean getUseZoneController() {
+ return this.useZoneController;
+ }
+
+ /**
+ * @param useZoneController
+ * whether to use a zone controller or not
+ */
+ @JsonProperty("useZoneController")
+ public void setUseZoneController(Boolean useZoneController) {
+ this.useZoneController = useZoneController;
+ }
+
+ /**
+ * @return whether random start delay is enabled for cooling
+ */
+ @JsonProperty("randomStartDelayCool")
+ public Integer getRandomStartDelayCool() {
+ return this.randomStartDelayCool;
+ }
+
+ /**
+ * @param randomStartDelayCool
+ * whether random start delay is enabled for cooling
+ */
+ @JsonProperty("randomStartDelayCool")
+ public void setRandomStartDelayCool(Integer randomStartDelayCool) {
+ this.randomStartDelayCool = randomStartDelayCool;
+ }
+
+ /**
+ * @return whether random start delay is enabled for heating
+ */
+ @JsonProperty("randomStartDelayHeat")
+ public Integer getRandomStartDelayHeat() {
+ return this.randomStartDelayHeat;
+ }
+
+ /**
+ * @param randomStartDelayHeat
+ * whether random start delay is enabled for heating
+ */
+ @JsonProperty("randomStartDelayHeat")
+ public void setRandomStartDelayHeat(Integer randomStartDelayHeat) {
+ this.randomStartDelayHeat = randomStartDelayHeat;
+ }
+
+ /**
+ * @return the humidity level to trigger a high humidity alert
+ */
+ @JsonProperty("humidityHighAlert")
+ public Integer getHumidityHighAlert() {
+ return this.humidityHighAlert;
+ }
+
+ /**
+ * @param humidityHighAlert
+ * the humidity level to trigger a high humidity alert
+ */
+ @JsonProperty("humidityHighAlert")
+ public void setHumidityHighAlert(Integer humidityHighAlert) {
+ this.humidityHighAlert = humidityHighAlert;
+ }
+
+ /**
+ * @return the humidity level to trigger a low humidity alert
+ */
+ @JsonProperty("humidityLowAlert")
+ public Integer getHumidityLowAlert() {
+ return this.humidityLowAlert;
+ }
+
+ /**
+ * @param humidityLowAlert
+ * the humidity level to trigger a low humidity alert
+ */
+ @JsonProperty("humidityLowAlert")
+ public void setHumidityLowAlert(Integer humidityLowAlert) {
+ this.humidityLowAlert = humidityLowAlert;
+ }
+
+ /**
+ * @return whether heat pump alerts are disabled
+ */
+ @JsonProperty("disableHeatPumpAlerts")
+ public Boolean getDisableHeatPumpAlerts() {
+ return this.disableHeatPumpAlerts;
+ }
+
+ /**
+ * @param disableHeatPumpAlerts
+ * whether heat pump alerts are disabled
+ */
+ @JsonProperty("disableHeatPumpAlerts")
+ public void setDisableHeatPumpAlerts(Boolean disableHeatPumpAlerts) {
+ this.disableHeatPumpAlerts = disableHeatPumpAlerts;
+ }
+
+ /**
+ * @return whether alerts are disabled from showing on the thermostat
+ */
+ @JsonProperty("disableAlertsOnIdt")
+ public Boolean getDisableAlertsOnIdt() {
+ return this.disableAlertsOnIdt;
+ }
+
+ /**
+ * @param disableAlertsOnIdt
+ * whether alerts are disabled from showing on the thermostat
+ */
+ @JsonProperty("disableAlertsOnIdt")
+ public void setDisableAlertsOnIdt(Boolean disableAlertsOnIdt) {
+ this.disableAlertsOnIdt = disableAlertsOnIdt;
+ }
+
+ /**
+ * @return whether humidification alerts are enabled to the thermostat owner
+ */
+ @JsonProperty("humidityAlertNotify")
+ public Boolean getHumidityAlertNotify() {
+ return this.humidityAlertNotify;
+ }
+
+ /**
+ * @param humidityAlertNotify
+ * whether humidification alerts are enabled to the thermostat owner
+ */
+ @JsonProperty("humidityAlertNotify")
+ public void setHumidityAlertNotify(Boolean humidityAlertNotify) {
+ this.humidityAlertNotify = humidityAlertNotify;
+ }
+
+ /**
+ * @return whether humidification alerts are enabled to the technician associated with the thermostat
+ */
+ @JsonProperty("humidityAlertNotifyTechnician")
+ public Boolean getHumidityAlertNotifyTechnician() {
+ return this.humidityAlertNotifyTechnician;
+ }
+
+ /**
+ * @param humidityAlertNotifyTechnician
+ * whether humidification alerts are enabled to the technician associated with the thermostat
+ */
+ @JsonProperty("humidityAlertNotifyTechnician")
+ public void setHumidityAlertNotifyTechnician(Boolean humidityAlertNotifyTechnician) {
+ this.humidityAlertNotifyTechnician = humidityAlertNotifyTechnician;
+ }
+
+ /**
+ * @return whether temperature alerts are enabled to the thermostat owner
+ */
+ @JsonProperty("tempAlertNotify")
+ public Boolean getTempAlertNotify() {
+ return this.tempAlertNotify;
+ }
+
+ /**
+ * @param tempAlertNotify
+ * whether temperature alerts are enabled to the thermostat owner
+ */
+ @JsonProperty("tempAlertNotify")
+ public void setTempAlertNotify(Boolean tempAlertNotify) {
+ this.tempAlertNotify = tempAlertNotify;
+ }
+
+ /**
+ * @return hether temperature alerts are enabled to the technician associated with the thermostat
+ */
+ @JsonProperty("tempAlertNotifyTechnician")
+ public Boolean getTempAlertNotifyTechnician() {
+ return this.tempAlertNotifyTechnician;
+ }
+
+ /**
+ * @param tempAlertNotifyTechnician
+ * hether temperature alerts are enabled to the technician associated with the thermostat
+ */
+ @JsonProperty("tempAlertNotifyTechnician")
+ public void setTempAlertNotifyTechnician(Boolean tempAlertNotifyTechnician) {
+ this.tempAlertNotifyTechnician = tempAlertNotifyTechnician;
+ }
+
+ /**
+ * @return the dollar amount the owner specifies for their desired maximum electricity bill
+ */
+ @JsonProperty("monthlyElectricityBillLimit")
+ public Integer getMonthlyElectricityBillLimit() {
+ return this.monthlyElectricityBillLimit;
+ }
+
+ /**
+ * @param monthlyElectricityBillLimit
+ * the dollar amount the owner specifies for their desired maximum electricity bill
+ */
+ @JsonProperty("monthlyElectricityBillLimit")
+ public void setMonthlyElectricityBillLimit(Integer monthlyElectricityBillLimit) {
+ this.monthlyElectricityBillLimit = monthlyElectricityBillLimit;
+ }
+
+ /**
+ * @return whether electricity bill alerts are enabled
+ */
+ @JsonProperty("enableElectricityBillAlert")
+ public Boolean getEnableElectricityBillAlert() {
+ return this.enableElectricityBillAlert;
+ }
+
+ /**
+ * @param enableElectricityBillAlert
+ * whether electricity bill alerts are enabled
+ */
+ @JsonProperty("enableElectricityBillAlert")
+ public void setEnableElectricityBillAlert(Boolean enableElectricityBillAlert) {
+ this.enableElectricityBillAlert = enableElectricityBillAlert;
+ }
+
+ /**
+ * @return whether electricity bill projection alerts are enabled
+ */
+ @JsonProperty("enableProjectedElectricityBillAlert")
+ public Boolean getEnableProjectedElectricityBillAlert() {
+ return this.enableProjectedElectricityBillAlert;
+ }
+
+ /**
+ * @param enableProjectedElectricityBillAlert
+ * whether electricity bill projection alerts are enabled
+ */
+ @JsonProperty("enableProjectedElectricityBillAlert")
+ public void setEnableProjectedElectricityBillAlert(Boolean enableProjectedElectricityBillAlert) {
+ this.enableProjectedElectricityBillAlert = enableProjectedElectricityBillAlert;
+ }
+
+ /**
+ * @return the electricityBillingDayOfMonth
+ */
+ @JsonProperty("electricityBillingDayOfMonth")
+ public Integer getElectricityBillingDayOfMonth() {
+ return this.electricityBillingDayOfMonth;
+ }
+
+ /**
+ * @param electricityBillingDayOfMonth
+ * the electricityBillingDayOfMonth to set
+ */
+ @JsonProperty("electricityBillingDayOfMonth")
+ public void setElectricityBillingDayOfMonth(Integer electricityBillingDayOfMonth) {
+ this.electricityBillingDayOfMonth = electricityBillingDayOfMonth;
+ }
+
+ /**
+ * @return the owner's billing cycle duration in months
+ */
+ @JsonProperty("electricityBillCycleMonths")
+ public Integer getElectricityBillCycleMonths() {
+ return this.electricityBillCycleMonths;
+ }
+
+ /**
+ * @param electricityBillCycleMonths
+ * the owner's billing cycle duration in months
+ */
+ @JsonProperty("electricityBillCycleMonths")
+ public void setElectricityBillCycleMonths(Integer electricityBillCycleMonths) {
+ this.electricityBillCycleMonths = electricityBillCycleMonths;
+ }
+
+ /**
+ * @return the annual start month of the owner's billing cycle
+ */
+ @JsonProperty("electricityBillStartMonth")
+ public Integer getElectricityBillStartMonth() {
+ return this.electricityBillStartMonth;
+ }
+
+ /**
+ * @param electricityBillStartMonth
+ * the annual start month of the owner's billing cycle
+ */
+ @JsonProperty("electricityBillStartMonth")
+ public void setElectricityBillStartMonth(Integer electricityBillStartMonth) {
+ this.electricityBillStartMonth = electricityBillStartMonth;
+ }
+
+ /**
+ * @return the number of minutes to run ventilator per hour when home
+ */
+ @JsonProperty("ventilatorMinOnTimeHome")
+ public Integer getVentilatorMinOnTimeHome() {
+ return this.ventilatorMinOnTimeHome;
+ }
+
+ /**
+ * @param ventilatorMinOnTimeHome
+ * the number of minutes to run ventilator per hour when home
+ */
+ @JsonProperty("ventilatorMinOnTimeHome")
+ public void setVentilatorMinOnTimeHome(Integer ventilatorMinOnTimeHome) {
+ this.ventilatorMinOnTimeHome = ventilatorMinOnTimeHome;
+ }
+
+ /**
+ * @return the number of minutes to run ventilator per hour when away
+ */
+ @JsonProperty("ventilatorMinOnTimeAway")
+ public Integer getVentilatorMinOnTimeAway() {
+ return this.ventilatorMinOnTimeAway;
+ }
+
+ /**
+ * @param ventilatorMinOnTimeAway
+ * the number of minutes to run ventilator per hour when away
+ */
+ @JsonProperty("ventilatorMinOnTimeAway")
+ public void setVentilatorMinOnTimeAway(Integer ventilatorMinOnTimeAway) {
+ this.ventilatorMinOnTimeAway = ventilatorMinOnTimeAway;
+ }
+
+ /**
+ * @return whether or not to turn the backlight off during sleep
+ */
+ @JsonProperty("backlightOffDuringSleep")
+ public Boolean getBacklightOffDuringSleep() {
+ return this.backlightOffDuringSleep;
+ }
+
+ /**
+ * @param backlightOffDuringSleep
+ * whether or not to turn the backlight off during sleep
+ */
+ @JsonProperty("backlightOffDuringSleep")
+ public void setBacklightOffDuringSleep(Boolean backlightOffDuringSleep) {
+ this.backlightOffDuringSleep = backlightOffDuringSleep;
+ }
+
+ /**
+ * @return when set to true if no occupancy motion detected thermostat will go into indefinite away hold, until
+ * either the user presses resume schedule or motion is detected.
+ */
+ @JsonProperty("autoAway")
+ public Boolean getAutoAway() {
+ return this.autoAway;
+ }
+
+ /**
+ * @return when set to true if a larger than normal delta is found between sensors the fan will be engaged for
+ * 15min/hour.
+ */
+ @JsonProperty("smartCirculation")
+ public Boolean getSmartCirculation() {
+ return this.smartCirculation;
+ }
+
+ /**
+ * @param smartCirculation
+ * when set to true if a larger than normal delta is found between sensors the fan will be engaged
+ * for 15min/hour.
+ */
+ @JsonProperty("smartCirculation")
+ public void setSmartCirculation(Boolean smartCirculation) {
+ this.smartCirculation = smartCirculation;
+ }
+
+ /**
+ * @return true if a sensor has detected presence for more than 10 minutes then include that sensor in temp
+ * average. If no activity has been seen on a sensor for more than 1 hour then remove this sensor from
+ * temperature average.
+ */
+ @JsonProperty("followMeComfort")
+ public Boolean getFollowMeComfort() {
+ return this.followMeComfort;
+ }
+
+ /**
+ * @param followMeComfort
+ * set to true if a sensor has detected presence for more than 10 minutes then include that sensor in
+ * temp average. If no activity has been seen on a sensor for more than 1 hour then remove this
+ * sensor from temperature average.
+ */
+ @JsonProperty("followMeComfort")
+ public void setFollowMeComfort(Boolean followMeComfort) {
+ this.followMeComfort = followMeComfort;
+ }
+
+ /**
+ * @return the type of ventilator present for the Thermostat. The possible values are none, ventilator, hrv, and
+ * erv.
+ */
+ @JsonProperty("ventilatorType")
+ public String getVentilatorType() {
+ return this.ventilatorType;
+ }
+
+ /**
+ * @return whether the ventilator timer is on or off. The default value is false. If set to true the
+ * ventilatorOffDateTime is set to now() + 20 minutes. If set to false the ventilatorOffDateTime is set
+ * to its default value.
+ */
+ @JsonProperty("isVentilatorTimerOn")
+ public Boolean getIsVentilatorTimerOn() {
+ return this.isVentilatorTimerOn;
+ }
+
+ /**
+ * @param isVentilatorTimerOn
+ * whether the ventilator timer is on or off. The default value is false. If set to true the
+ * ventilatorOffDateTime is set to now() + 20 minutes. If set to false the ventilatorOffDateTime is
+ * set to its default value.
+ */
+ @JsonProperty("isVentilatorTimerOn")
+ public void setIsVentilatorTimerOn(Boolean isVentilatorTimerOn) {
+ this.isVentilatorTimerOn = isVentilatorTimerOn;
+ }
+
+ /**
+ * @return the date and time the ventilator will run until. The default value is 2014-01-01 00:00:00.
+ */
+ @JsonProperty("ventilatorOffDateTime")
+ public Date getVentilatorOffDateTime() {
+ return this.ventilatorOffDateTime;
+ }
+
+ /**
+ * @return whether the HVAC system has a UV filter. The default value is true.
+ */
+ @JsonProperty("hasUVFilter")
+ public Boolean getHasUVFilter() {
+ return this.hasUVFilter;
+ }
+
+ /**
+ * @param hasUVFilter
+ * whether the HVAC system has a UV filter. The default value is true.
+ */
+ @JsonProperty("hasUVFilter")
+ public void setHasUVFilter(Boolean hasUVFilter) {
+ this.hasUVFilter = hasUVFilter;
+ }
+
+ /**
+ * @return whether to permit the cooling to operate when the outdoor temperature is under a specific threshold,
+ * currently 55F. The default value is false.
+ */
+ @JsonProperty("coolingLockout")
+ public Boolean getCoolingLockout() {
+ return this.coolingLockout;
+ }
+
+ /**
+ * @param coolingLockout
+ * whether to permit the cooling to operate when the outdoor temperature is under a specific
+ * threshold, currently 55F. The default value is false.
+ */
+ @JsonProperty("coolingLockout")
+ public void setCoolingLockout(Boolean coolingLockout) {
+ this.coolingLockout = coolingLockout;
+ }
+
+ /**
+ * @return the ventilatorFreeCooling
+ */
+ @JsonProperty("ventilatorFreeCooling")
+ public Boolean getVentilatorFreeCooling() {
+ return this.ventilatorFreeCooling;
+ }
+
+ /**
+ * @param ventilatorFreeCooling
+ * the ventilatorFreeCooling to set
+ */
+ @JsonProperty("ventilatorFreeCooling")
+ public void setVentilatorFreeCooling(Boolean ventilatorFreeCooling) {
+ this.ventilatorFreeCooling = ventilatorFreeCooling;
+ }
+
+ /**
+ * @return whether to permit dehumidifier to operate when the heating is running. The default value is false.
+ */
+ @JsonProperty("dehumidifyWhenHeating")
+ public Boolean getDehumidifyWhenHeating() {
+ return this.dehumidifyWhenHeating;
+ }
+
+ /**
+ * @param dehumidifyWhenHeating
+ * whether to permit dehumidifier to operate when the heating is running. The default value is false.
+ */
+ @JsonProperty("dehumidifyWhenHeating")
+ public void setDehumidifyWhenHeating(Boolean dehumidifyWhenHeating) {
+ this.dehumidifyWhenHeating = dehumidifyWhenHeating;
+ }
+
+ /**
+ * @return the unique reference to the group this thermostat belongs to, if any. See GET Group request and POST
+ * Group request for more information.
+ */
+ @JsonProperty("groupRef")
+ public String getGroupRef() {
+ return this.groupRef;
+ }
+
+ /**
+ * @param groupRef
+ * the unique reference to the group this thermostat belongs to, if any. See GET Group request and
+ * POST Group request for more information.
+ */
+ @JsonProperty("groupRef")
+ public void setGroupRef(String groupRef) {
+ this.groupRef = groupRef;
+ }
+
+ /**
+ * @return the name of the the group this thermostat belongs to, if any. See GET Group request and POST Group
+ * request for more information.
+ */
+ @JsonProperty("groupName")
+ public String getGroupName() {
+ return this.groupName;
+ }
+
+ /**
+ * @param groupName
+ * the name of the the group this thermostat belongs to, if any. See GET Group request and POST Group
+ * request for more information.
+ */
+ @JsonProperty("groupName")
+ public void setGroupName(String groupName) {
+ this.groupName = groupName;
+ }
+
+ /**
+ * @return the setting value for the group this thermostat belongs to, if any. See GET Group request and POST
+ * Group request for more information.
+ */
+ @JsonProperty("groupSetting")
+ public Integer getGroupSetting() {
+ return this.groupSetting;
+ }
+
+ /**
+ * @param groupSetting
+ * the setting value for the group this thermostat belongs to, if any. See GET Group request and POST
+ * Group request for more information.
+ */
+ @JsonProperty("groupSetting")
+ public void setGroupSetting(Integer groupSetting) {
+ this.groupSetting = groupSetting;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("hvacMode", this.hvacMode);
+ builder.append("lastServiceDate", this.lastServiceDate);
+ builder.append("serviceRemindMe", this.serviceRemindMe);
+ builder.append("monthsBetweenService", this.monthsBetweenService);
+ builder.append("remindMeDate", this.remindMeDate);
+ builder.append("vent", this.vent);
+ builder.append("ventilatorMinOnTime", this.ventilatorMinOnTime);
+ builder.append("serviceRemindTechnician", this.serviceRemindTechnician);
+ builder.append("eiLocation", this.eiLocation);
+ builder.append("coldTempAlert", this.coldTempAlert);
+ builder.append("coldTempAlertEnabled", this.coldTempAlertEnabled);
+ builder.append("hotTempAlert", this.hotTempAlert);
+ builder.append("hotTempAlertEnabled", this.hotTempAlertEnabled);
+ builder.append("coolStages", this.coolStages);
+ builder.append("heatStages", this.heatStages);
+ builder.append("maxSetBack", this.maxSetBack);
+ builder.append("maxSetForward", this.maxSetForward);
+ builder.append("quickSaveSetBack", this.quickSaveSetBack);
+ builder.append("quickSaveSetForward", this.quickSaveSetForward);
+ builder.append("hasHeatPump", this.hasHeatPump);
+ builder.append("hasForcedAir", this.hasForcedAir);
+ builder.append("hasBoiler", this.hasBoiler);
+ builder.append("hasHumidifier", this.hasHumidifier);
+ builder.append("hasErv", this.hasErv);
+ builder.append("hasHrv", this.hasHrv);
+ builder.append("condensationAvoid", this.condensationAvoid);
+ builder.append("useCelsius", this.useCelsius);
+ builder.append("useTimeFormat12", this.useTimeFormat12);
+ builder.append("locale", this.locale);
+ builder.append("humidity", this.humidity);
+ builder.append("humidifierMode", this.humidifierMode);
+ builder.append("backlightOnIntensity", this.backlightOnIntensity);
+ builder.append("backlightSleepIntensity", this.backlightSleepIntensity);
+ builder.append("backlightOffTime", this.backlightOffTime);
+ builder.append("soundTickVolume", this.soundTickVolume);
+ builder.append("soundAlertVolume", this.soundAlertVolume);
+ builder.append("compressorProtectionMinTime", this.compressorProtectionMinTime);
+ builder.append("compressorProtectionMinTemp", this.compressorProtectionMinTemp);
+ builder.append("stage1HeatingDifferentialTemp", this.stage1HeatingDifferentialTemp);
+ builder.append("stage1CoolingDifferentialTemp", this.stage1CoolingDifferentialTemp);
+ builder.append("stage1HeatingDissipationTime", this.stage1HeatingDissipationTime);
+ builder.append("stage1CoolingDissipationTime", this.stage1CoolingDissipationTime);
+ builder.append("heatPumpReversalOnCool", this.heatPumpReversalOnCool);
+ builder.append("fanControlRequired", this.fanControlRequired);
+ builder.append("fanMinOnTime", this.fanMinOnTime);
+ builder.append("heatCoolMinDelta", this.heatCoolMinDelta);
+ builder.append("tempCorrection", this.tempCorrection);
+ builder.append("holdAction", this.holdAction);
+ builder.append("heatPumpGroundWater", this.heatPumpGroundWater);
+ builder.append("hasElectric", this.hasElectric);
+ builder.append("hasDehumidifier", this.hasDehumidifier);
+ builder.append("humidifierMode", this.humidifierMode);
+ builder.append("dehumidifierLevel", this.dehumidifierLevel);
+ builder.append("dehumidifyWithAC", this.dehumidifyWithAC);
+ builder.append("dehumidifyOvercoolOffset", this.dehumidifyOvercoolOffset);
+ builder.append("autoHeatCoolFeatureEnabled", this.autoHeatCoolFeatureEnabled);
+ builder.append("wifiOfflineAlert", this.wifiOfflineAlert);
+ builder.append("heatMinTemp", this.heatMinTemp);
+ builder.append("heatMaxTemp", this.heatMaxTemp);
+ builder.append("coolMinTemp", this.coolMinTemp);
+ builder.append("coolMaxTemp", this.coolMaxTemp);
+ builder.append("heatRangeHigh", this.heatRangeHigh);
+ builder.append("heatRangeLow", this.heatRangeLow);
+ builder.append("coolRangeHigh", this.coolRangeHigh);
+ builder.append("coolRangeLow", this.coolRangeLow);
+ builder.append("userAccessCode", this.userAccessCode);
+ builder.append("userAccessSetting", this.userAccessSetting);
+ builder.append("auxRuntimeAlert", this.auxRuntimeAlert);
+ builder.append("auxOutdoorTempAlert", this.auxOutdoorTempAlert);
+ builder.append("auxMaxOutdoorTemp", this.auxMaxOutdoorTemp);
+ builder.append("auxRuntimeAlertNotify", this.auxRuntimeAlertNotify);
+ builder.append("auxOutdoorTempAlertNotify", this.auxOutdoorTempAlertNotify);
+ builder.append("auxRuntimeAlertNotifyTechnician", this.auxRuntimeAlertNotifyTechnician);
+ builder.append("auxOutdoorTempAlertNotifyTechnician", this.auxOutdoorTempAlertNotifyTechnician);
+ builder.append("disablePreHeating", this.disablePreHeating);
+ builder.append("disablePreCooling", this.disablePreCooling);
+ builder.append("installerCodeRequired", this.installerCodeRequired);
+ builder.append("drAccept", this.drAccept);
+ builder.append("isRentalProperty", this.isRentalProperty);
+ builder.append("useZoneController", this.useZoneController);
+ builder.append("randomStartDelayCool", this.randomStartDelayCool);
+ builder.append("randomStartDelayHeat", this.randomStartDelayHeat);
+ builder.append("humidityHighAlert", this.humidityHighAlert);
+ builder.append("humidityLowAlert", this.humidityLowAlert);
+ builder.append("disableHeatPumpAlerts", this.disableHeatPumpAlerts);
+ builder.append("disableAlertsOnIdt", this.disableAlertsOnIdt);
+ builder.append("humidityAlertNotify", this.humidityAlertNotify);
+ builder.append("humidityAlertNotifyTechnician", this.humidityAlertNotifyTechnician);
+ builder.append("tempAlertNotify", this.tempAlertNotify);
+ builder.append("tempAlertNotifyTechnician", this.tempAlertNotifyTechnician);
+ builder.append("monthlyElectricityBillLimit", this.monthlyElectricityBillLimit);
+ builder.append("enableElectricityBillAlert", this.enableElectricityBillAlert);
+ builder.append("enableProjectedElectricityBillAlert", this.enableProjectedElectricityBillAlert);
+ builder.append("electricityBillingDayOfMonth", this.electricityBillingDayOfMonth);
+ builder.append("electricityBillCycleMonths", this.electricityBillCycleMonths);
+ builder.append("electricityBillStartMonth", this.electricityBillStartMonth);
+ builder.append("ventilatorMinOnTimeHome", this.ventilatorMinOnTimeHome);
+ builder.append("ventilatorMinOnTimeAway", this.ventilatorMinOnTimeAway);
+ builder.append("backlightOffDuringSleep", this.backlightOffDuringSleep);
+ builder.append("autoAway", this.autoAway);
+ builder.append("smartCirculation", this.smartCirculation);
+ builder.append("followMeComfort", this.followMeComfort);
+ builder.append("ventilatorType", this.ventilatorType);
+ builder.append("isVentilatorTimerOn", this.isVentilatorTimerOn);
+ builder.append("ventilatorOffDateTime", this.ventilatorOffDateTime);
+ builder.append("hasUVFilter", this.hasUVFilter);
+ builder.append("coolingLockout", this.coolingLockout);
+ builder.append("ventilatorFreeCooling", this.ventilatorFreeCooling);
+ builder.append("dehumidifyWhenHeating", this.dehumidifyWhenHeating);
+ builder.append("groupRef", this.groupRef);
+ builder.append("groupName", this.groupName);
+ builder.append("groupSetting", this.groupSetting);
+
+ return builder.toString();
+ }
+ }
+
+ /**
+ * Possible values for hvacMode
+ */
+ public static enum HvacMode {
+ AUTO("auto"), AUX_HEAT_ONLY("auxHeatOnly"), COOL("cool"), HEAT("heat"), OFF("off");
+
+ private final String mode;
+
+ private HvacMode(String mode) {
+ this.mode = mode;
+ }
+
+ @JsonValue
+ public String value() {
+ return mode;
+ }
+
+ @JsonCreator
+ public static HvacMode forValue(String v) {
+ for (HvacMode hm : HvacMode.values()) {
+ if (hm.mode.equals(v)) {
+ return hm;
+ }
+ }
+ throw new IllegalArgumentException("Invalid hvacMode: " + v);
+ }
+
+ @Override
+ public String toString() {
+ return this.mode;
+ }
+ }
+
+ /**
+ * Possible values for vent
+ */
+ public static enum VentilatorMode {
+ AUTO("auto"), MIN_ON_TIME("minontime"), ON("on"), OFF("off");
+
+ private final String mode;
+
+ private VentilatorMode(String mode) {
+ this.mode = mode;
+ }
+
+ @JsonValue
+ public String value() {
+ return mode;
+ }
+
+ @JsonCreator
+ public static VentilatorMode forValue(String v) {
+ for (VentilatorMode vm : VentilatorMode.values()) {
+ if (vm.mode.equals(v)) {
+ return vm;
+ }
+ }
+ throw new IllegalArgumentException("Invalid vent: " + v);
+ }
+
+ @Override
+ public String toString() {
+ return this.mode;
+ }
+ }
+
+ /**
+ * The Runtime Java Bean represents the last known thermostat running state. This state is composed from the last
+ * interval status message received from a thermostat. It is also updated each time the thermostat posts
+ * configuration changes to the server.
+ *
+ *
+ * The runtime object contains the last 5 minute interval value sent by the thermostat for the past 15 minutes of
+ * runtime. The thermostat updates the server every 15 minutes with the last three 5 minute readings.
+ *
+ *
+ * The actual temperature and humidity will also be updated when the equipment state changes by the thermostat, this
+ * may occur at a frequency of 3 minutes, however it is only transmitted when there is an equipment state change on
+ * the thermostat.
+ *
+ *
+ * See Thermostat Interval Report Data for additional information about the interval readings.
+ *
+ *
+ * The Runtime class is read-only.
+ *
+ * @see Runtime
+ * @see Thermostat
+ * Interval Report Data
+ * @author John Cocula
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Runtime extends AbstractMessagePart {
+ private String runtimeRev;
+ private Boolean connected;
+ private Date firstConnected;
+ private Date connectDateTime;
+ private String disconnectDateTime; // TODO: Jackson 1.9 can't handle
+ // date only (no time) (@watou)
+ private Date lastModified;
+ private Date lastStatusModified;
+ private String runtimeDate; // TODO: Jackson 1.9 can't handle date only
+ // (no time) (@watou)
+ private Integer runtimeInterval;
+ private Temperature actualTemperature;
+ private Integer actualHumidity;
+ private Temperature desiredHeat;
+ private Temperature desiredCool;
+ private Integer desiredHumidity;
+ private Integer desiredDehumidity;
+ private String desiredFanMode;
+
+ /**
+ * @return the current runtime revision. Equivalent in meaning to the runtime revision number in the thermostat
+ * summary call.
+ */
+ @JsonProperty("runtimeRev")
+ public String getRuntimeRev() {
+ return this.runtimeRev;
+ }
+
+ /**
+ * @return whether the thermostat is currently connected to the server
+ */
+ @JsonProperty("connected")
+ public Boolean getConnected() {
+ return this.connected;
+ }
+
+ /**
+ * @return the UTC date/time stamp of when the thermostat first connected to the ecobee server
+ */
+ @JsonProperty("firstConnected")
+ public Date getFirstConnected() {
+ return this.firstConnected;
+ }
+
+ /**
+ * @return the last recorded connection date and time
+ */
+ @JsonProperty("connectDateTime")
+ public Date getConnectDateTime() {
+ return this.connectDateTime;
+ }
+
+ /**
+ * @return the last recorded disconnection date and time
+ */
+ @JsonProperty("disconnectDateTime")
+ public String getDisconnectDateTime() {
+ return this.disconnectDateTime;
+ }
+
+ /**
+ * @return the UTC date/time stamp of when the thermostat was updated. Format: YYYY-MM-DD HH:MM:SS
+ */
+ @JsonProperty("lastModified")
+ public Date getLastModified() {
+ return this.lastModified;
+ }
+
+ /**
+ * @return the UTC date/time stamp of when the thermostat last posted its runtime information. Format:
+ * YYYY-MM-DD HH:MM:SS
+ */
+ @JsonProperty("lastStatusModified")
+ public Date getLastStatusModified() {
+ return this.lastStatusModified;
+ }
+
+ /**
+ * @return the UTC date of the last runtime reading. Format: YYYY-MM-DD
+ */
+ @JsonProperty("runtimeDate")
+ public String getRuntimeDate() {
+ return this.runtimeDate;
+ }
+
+ /**
+ * @return the last 5 minute interval which was updated by the thermostat telemetry update. Subtract 2 from this
+ * interval to obtain the beginning interval for the last 3 readings. Multiply by 5 mins to obtain the
+ * minutes of the day. Range: 0-287
+ */
+ @JsonProperty("runtimeInterval")
+ public Integer getRuntimeInterval() {
+ return this.runtimeInterval;
+ }
+
+ /**
+ * @return the current temperature displayed on the thermostat
+ */
+ @JsonProperty("actualTemperature")
+ public Temperature getActualTemperature() {
+ return this.actualTemperature;
+ }
+
+ /**
+ * @return the current humidity % shown on the thermostat
+ */
+ @JsonProperty("actualHumidity")
+ public Integer getActualHumidity() {
+ return this.actualHumidity;
+ }
+
+ /**
+ * @return the desired heat temperature as per the current running program or active event
+ */
+ @JsonProperty("desiredHeat")
+ public Temperature getDesiredHeat() {
+ return this.desiredHeat;
+ }
+
+ /**
+ * @return the desired cool temperature as per the current running program or active event.
+ */
+ @JsonProperty("desiredCool")
+ public Temperature getDesiredCool() {
+ return this.desiredCool;
+ }
+
+ /**
+ * @return the desired humidity set point
+ */
+ @JsonProperty("desiredHumidity")
+ public Integer getDesiredHumidity() {
+ return this.desiredHumidity;
+ }
+
+ /**
+ * @return the desired dehumidification set point
+ */
+ @JsonProperty("desiredDehumidity")
+ public Integer getDesiredDehumidity() {
+ return this.desiredDehumidity;
+ }
+
+ /**
+ * @return the desired fan mode. Values: auto, on or null if the HVAC system is off. TODO handle null as valid
+ * value (@watou).
+ */
+ @JsonProperty("desiredFanMode")
+ public String getDesiredFanMode() {
+ return this.desiredFanMode;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("runtimeRev", this.runtimeRev);
+ builder.append("connected", this.connected);
+ builder.append("firstConnected", this.firstConnected);
+ builder.append("connectDateTime", this.connectDateTime);
+ builder.append("disconnectDateTime", this.disconnectDateTime);
+ builder.append("lastModified", this.lastModified);
+ builder.append("lastStatusModified", this.lastStatusModified);
+ builder.append("runtimeDate", this.runtimeDate);
+ builder.append("runtimeInterval", this.runtimeInterval);
+ builder.append("actualTemperature", this.actualTemperature);
+ builder.append("actualHumidity", this.actualHumidity);
+ builder.append("desiredHeat", this.desiredHeat);
+ builder.append("desiredCool", this.desiredCool);
+ builder.append("desiredHumidity", this.desiredHumidity);
+ builder.append("desiredDehumidity", this.desiredDehumidity);
+ builder.append("desiredFanMode", this.desiredFanMode);
+
+ return builder.toString();
+ }
+ }
+
+ /**
+ * The ExtendedRuntime Java Bean contains the last three 5 minute interval values sent by the thermostat for the
+ * past 15 minutes of runtime. The interval values are valuable when you are interested in analyzing the runtime
+ * data in a more granular fashion, at 5 minute increments rather than the more general 15 minute value from the
+ * Runtime Object.
+ *
+ *
+ * For the runtime values (i.e. heatPump, auxHeat, cool, etc.) refer to the {@link Thermostat#settings} values (
+ * {@link Settings#hasHeatPump}, {@link Settings#heatStages}, {@link Settings#coolStages}) to determine whether a
+ * heat pump exists and how many stages the thermostat supports.
+ *
+ *
+ * The actual temperature and humidity will also be updated when the equipment state changes by the thermostat, this
+ * may occur at a frequency of 3 minutes, however it is only transmitted when there is an equipment state change on
+ * the thermostat.
+ *
+ *
+ * The extended runtime object is read-only.
+ *
+ * @see ExtendedRuntime
+ * @see Thermostat
+ * Interval Report Data
+ * @author John Cocula
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ExtendedRuntime extends AbstractMessagePart {
+ private Date lastReadingTimestamp;
+ private String runtimeDate; // TODO Jackson 1.9 can't also deal with date-only dates (@watou)
+ private Integer runtimeInterval;
+ private List actualTemperature;
+ private List actualHumidity;
+ private List desiredHeat;
+ private List desiredCool;
+ private List desiredHumidity;
+ private List desiredDehumidity;
+ private List dmOffset;
+ private List hvacMode;
+ private List heatPump1;
+ private List heatPump2;
+ private List auxHeat1;
+ private List auxHeat2;
+ private List auxHeat3;
+ private List cool1;
+ private List cool2;
+ private List fan;
+ private List humidifier;
+ private List dehumidifier;
+ private List economizer;
+ private List ventilator;
+ private Integer currentElectricityBill;
+ private Integer projectedElectricityBill;
+
+ /**
+ * @return the UTC timestamp of the last value read. This timestamp is updated at a 15 min interval by the
+ * thermostat. For the 1st value, it is timestamp - 10 mins, for the 2nd value it is timestamp - 5 mins.
+ * Consider day boundaries being straddled when using these values.
+ */
+ @JsonProperty("lastReadingTimestamp")
+ public Date getLastReadingTimestamp() {
+ return this.lastReadingTimestamp;
+ }
+
+ /**
+ * @return the UTC date of the last runtime reading. Format: YYYY-MM-DD
+ */
+ @JsonProperty("runtimeDate")
+ public String getRuntimeDate() {
+ return this.runtimeDate;
+ }
+
+ /**
+ * @return the last 5 minute interval which was updated by the thermostat telemetry update. Subtract 2 from this
+ * interval to obtain the beginning interval for the last 3 readings. Multiply by 5 mins to obtain the
+ * minutes of the day. Range: 0-287
+ */
+ @JsonProperty("runtimeInterval")
+ public Integer getRuntimeInterval() {
+ return this.runtimeInterval;
+ }
+
+ /**
+ * @return the last three 5 minute actual temperature readings
+ */
+ @JsonProperty("actualTemperature")
+ public List getActualTemperature() {
+ return this.actualTemperature;
+ }
+
+ /**
+ * @return the last three 5 minute actual humidity readings
+ */
+ @JsonProperty("actualHumidity")
+ public List getActualHumidity() {
+ return this.actualHumidity;
+ }
+
+ /**
+ * @return the last three 5 minute desired heat temperature readings
+ */
+ @JsonProperty("desiredHeat")
+ public List getDesiredHeat() {
+ return this.desiredHeat;
+ }
+
+ /**
+ * @return the last three 5 minute desired cool temperature readings
+ */
+ @JsonProperty("desiredCool")
+ public List getDesiredCool() {
+ return this.desiredCool;
+ }
+
+ /**
+ * @return the last three 5 minute desired humidity readings
+ */
+ @JsonProperty("desiredHumidity")
+ public List getDesiredHumidity() {
+ return this.desiredHumidity;
+ }
+
+ /**
+ * @return the last three 5 minute desired dehumidification readings
+ */
+ @JsonProperty("desiredDehumidity")
+ public List getDesiredDehumidity() {
+ return this.desiredDehumidity;
+ }
+
+ /**
+ * @return the last three 5 minute desired Demand Management temperature offsets. This value is Demand
+ * Management adjustment value which was applied by the thermostat. If the thermostat decided not to
+ * honor the adjustment, it will send 0 for the interval. Compare these values with the values sent in
+ * the DM message to determine whether the thermostat applied the adjustment.
+ */
+ @JsonProperty("dmOffset")
+ public List getDmOffset() {
+ return this.dmOffset;
+ }
+
+ /**
+ * @return the last three 5 minute HVAC Mode reading. These values indicate which stage was energized in the 5
+ * minute interval. Values: heatStage1On, heatStage2On, heatStage3On, heatOff, compressorCoolStage1On,
+ * compressorCoolStage2On, compressorCoolOff, compressorHeatStage1On, compressorHeatStage2On,
+ * compressorHeatOff, economyCycle.
+ */
+ @JsonProperty("hvacMode")
+ public List getHvacMode() {
+ return this.hvacMode;
+ }
+
+ /**
+ * @return the last three 5 minute HVAC Runtime values in seconds (0-300 seconds) per interval. This value
+ * corresponds to the heat pump stage 1 runtime.
+ */
+ @JsonProperty("heatPump1")
+ public List getHeatPump1() {
+ return this.heatPump1;
+ }
+
+ /**
+ * @return the last three 5 minute HVAC Runtime values in seconds (0-300 seconds) per interval. This value
+ * corresponds to the heat pump stage 2 runtime.
+ */
+ @JsonProperty("heatPump2")
+ public List getHeatPump2() {
+ return this.heatPump2;
+ }
+
+ /**
+ * @return the last three 5 minute HVAC Runtime values in seconds (0-300 seconds) per interval. This value
+ * corresponds to the auxiliary heat stage 1. If the thermostat does not have a heat pump, this is heat
+ * stage 1.
+ */
+ @JsonProperty("auxHeat1")
+ public List getAuxHeat1() {
+ return this.auxHeat1;
+ }
+
+ /**
+ * @return the last three 5 minute HVAC Runtime values in seconds (0-300 seconds) per interval. This value
+ * corresponds to the auxiliary heat stage 2. If the thermostat does not have a heat pump, this is heat
+ * stage 2.
+ */
+ @JsonProperty("auxHeat2")
+ public List getAuxHeat2() {
+ return this.auxHeat2;
+ }
+
+ /**
+ * @return the last three 5 minute HVAC Runtime values in seconds (0-300 seconds) per interval. This value
+ * corresponds to the heat stage 3 if the thermostat does not have a heat pump. Auxiliary stage 3 is not
+ * supported.
+ */
+ @JsonProperty("auxHeat3")
+ public List getAuxHeat3() {
+ return this.auxHeat3;
+ }
+
+ /**
+ * @return the last three 5 minute HVAC Runtime values in seconds (0-300 seconds) per interval. This value
+ * corresponds to the cooling stage 1.
+ */
+ @JsonProperty("cool1")
+ public List getCool1() {
+ return this.cool1;
+ }
+
+ /**
+ * @return the last three 5 minute HVAC Runtime values in seconds (0-300 seconds) per interval. This value
+ * corresponds to the cooling stage 2.
+ */
+ @JsonProperty("cool2")
+ public List getCool2() {
+ return this.cool2;
+ }
+
+ /**
+ * @return the last three 5 minute fan Runtime values in seconds (0-300 seconds) per interval.
+ */
+ @JsonProperty("fan")
+ public List getFan() {
+ return this.fan;
+ }
+
+ /**
+ * @return the last three 5 minute humidifier Runtime values in seconds (0-300 seconds) per interval.
+ */
+ @JsonProperty("humidifier")
+ public List getHumidifier() {
+ return this.humidifier;
+ }
+
+ /**
+ * @return the last three 5 minute dehumidifier Runtime values in seconds (0-300 seconds) per interval.
+ */
+ @JsonProperty("dehumidifier")
+ public List getDehumidifier() {
+ return this.dehumidifier;
+ }
+
+ /**
+ * @return the last three 5 minute economizer Runtime values in seconds (0-300 seconds) per interval.
+ */
+ @JsonProperty("economizer")
+ public List getEconomizer() {
+ return this.economizer;
+ }
+
+ /**
+ * @return the last three 5 minute ventilator Runtime values in seconds (0-300 seconds) per interval.
+ */
+ @JsonProperty("ventilator")
+ public List getVentilator() {
+ return this.ventilator;
+ }
+
+ /**
+ * @return the latest value of the current electricity bill as interpolated from the thermostat's readings from
+ * a paired electricity meter.
+ */
+ @JsonProperty("currentElectricityBill")
+ public Integer getCurrentElectricityBill() {
+ return this.currentElectricityBill;
+ }
+
+ /**
+ * @return the latest estimate of the projected electricity bill as interpolated from the thermostat's readings
+ * from a paired electricity meter.
+ */
+ @JsonProperty("projectedElectricityBill")
+ public Integer getProjectedElectricityBill() {
+ return this.projectedElectricityBill;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("lastReadingTimestamp", this.lastReadingTimestamp);
+ builder.append("runtimeDate", this.runtimeDate);
+ builder.append("runtimeInterval", this.runtimeInterval);
+ builder.append("actualTemperature", this.actualTemperature);
+ builder.append("actualHumidity", this.actualHumidity);
+ builder.append("desiredHeat", this.desiredHeat);
+ builder.append("desiredCool", this.desiredCool);
+ builder.append("desiredHumidity", this.desiredHumidity);
+ builder.append("desiredDehumidity", this.desiredDehumidity);
+ builder.append("dmOffset", this.dmOffset);
+ builder.append("hvacMode", this.hvacMode);
+ builder.append("heatPump1", this.heatPump1);
+ builder.append("heatPump2", this.heatPump2);
+ builder.append("auxHeat1", this.auxHeat1);
+ builder.append("auxHeat2", this.auxHeat2);
+ builder.append("auxHeat3", this.auxHeat3);
+ builder.append("cool1", this.cool1);
+ builder.append("cool2", this.cool2);
+ builder.append("fan", this.fan);
+ builder.append("humidifier", this.humidifier);
+ builder.append("dehumidifier", this.dehumidifier);
+ builder.append("economizer", this.economizer);
+ builder.append("ventilator", this.ventilator);
+ builder.append("currentElectricityBill", this.currentElectricityBill);
+ builder.append("projectedElectricityBill", this.projectedElectricityBill);
+
+ return builder.toString();
+ }
+ }
+
+ /**
+ * The Electricity class contains the last collected electricity usage measurements for the thermostat. An
+ * electricity object is composed of ElectricityDevices, each of which contains readings from an ElectricityTier.
+ *
+ * @see Electricity
+ * @author John Cocula
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Electricity extends AbstractMessagePart {
+ private List devices;
+
+ Electricity(@JsonProperty("devices") List devices) {
+ this.devices = devices;
+ }
+
+ /**
+ * @return the list of ElectricityDevice objects associated with the thermostat, each representing a device such
+ * as an electric meter or remote load control.
+ */
+ @JsonProperty("devices")
+ public List getDevices() {
+ return this.devices;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("devices", this.devices);
+
+ return builder.toString();
+ }
+ }
+
+ /**
+ * An ElectricityDevice represents an energy recording device. At this time, only meters are supported by the API.
+ *
+ * @see ElectricityDevice
+ * @author John Cocula
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ElectricityDevice extends AbstractMessagePart {
+ private List tiers;
+ private List lastUpdate;
+ private List cost;
+ private List consumption;
+
+ /**
+ * @return the list of ElectricityTiers containing the break down of daily electricity consumption of the device
+ * for the day, broken down per pricing tier
+ */
+ @JsonProperty("tiers")
+ public List getTiers() {
+ return this.tiers;
+ }
+
+ /**
+ * @return the last date/time the reading was updated in UTC time
+ */
+ @JsonProperty("lastUpdate")
+ public List getLastUpdate() {
+ return this.lastUpdate;
+ }
+
+ /**
+ * @return the last three daily electricity cost reads from the device in cents with a three decimal place
+ * precision.
+ */
+ @JsonProperty("cost")
+ public List getCost() {
+ return this.cost;
+ }
+
+ /**
+ * @return the last three daily electricity consumption reads from the device in KWh with a three decimal place
+ * precision.
+ */
+ @JsonProperty("consumption")
+ public List getConsumption() {
+ return this.consumption;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("tiers", this.tiers);
+ builder.append("lastUpdate", this.lastUpdate);
+ builder.append("cost", this.cost);
+ builder.append("consumption", this.consumption);
+
+ return builder.toString();
+ }
+ }
+
+ /**
+ * An ElectricityTier object represents the last reading from a given pricing tier if the utility provides such
+ * information. If there are no pricing tiers defined, than an unnamed tier will represent the total reading. The
+ * values represented here are a daily cumulative total in kWh. The cost is likewise a cumulative total in cents.
+ *
+ * @see ElectricityTier
+ * @author John Cocula
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ElectricityTier extends AbstractMessagePart {
+ private String name;
+ private String consumption;
+ private String cost;
+
+ /**
+ * @return the tier name as defined by the {@link Utility}. May be an empty string if the tier is undefined or
+ * the usage falls outside the defined tiers.
+ */
+ @JsonProperty("name")
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * @return the last daily consumption reading collected. The reading format and precision is to three decimal
+ * places in kWh.
+ */
+ @JsonProperty("consumption")
+ public String getConsumption() {
+ return this.consumption;
+ }
+
+ /**
+ * @return the daily cumulative tier cost in dollars if defined by the Utility. May be an empty string if
+ * undefined.
+ */
+ @JsonProperty("cost")
+ public String getCost() {
+ return this.cost;
+ }
+
+ @Override
+ public String toString() {
+ final ToStringBuilder builder = createToStringBuilder();
+ builder.appendSuper(super.toString());
+ builder.append("name", this.name);
+ builder.append("consumption", this.consumption);
+ builder.append("cost", this.cost);
+
+ return builder.toString();
+ }
+ }
+
+ /**
+ * Represents a device attached to the thermostat. Devices may not be modified remotely; all changes must occur on
+ * the thermostat.
+ *
+ * @see Device
+ * @author John Cocula
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Device extends AbstractMessagePart {
+ private Integer deviceId;
+ private String name;
+ private List sensors;
+ private List