Initial plugin "Lyric Plugin" for bridging project. 65/16865/26
authorGaganpreet Kaur <gaganpreetx.kaur@intel.com>
Wed, 15 Feb 2017 10:41:15 +0000 (16:11 +0530)
committerTodd Malsbary <todd.malsbary@intel.com>
Fri, 24 Feb 2017 00:15:57 +0000 (00:15 +0000)
Change-Id: I3bc2ef0bd0d86f0e307a5648a3a252513c694a5d
Signed-off-by: Joseph Morrow <joseph.l.morrow@intel.com>
Signed-off-by: Gaganpreet Kaur <gaganpreetx.kaur@intel.com>
Reviewed-on: https://gerrit.iotivity.org/gerrit/16865
Tested-by: jenkins-iotivity <jenkins@iotivity.org>
Reviewed-by: Todd Malsbary <todd.malsbary@intel.com>
14 files changed:
bridging/SConscript
bridging/plugins/lyric_plugin/README [new file with mode: 0644]
bridging/plugins/lyric_plugin/SConscript [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywellHelpers.cpp [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywellHelpers.h [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywellResource.cpp [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywell_objects/honeywell.cpp [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywell_objects/honeywell.h [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywell_objects/honeywellDefs.h [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywell_objects/honeywellDefsLyric.h [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywell_objects/honeywellLyric.cpp [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywell_objects/honeywellLyric.h [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywell_objects/honeywellThermostat.cpp [new file with mode: 0644]
bridging/plugins/lyric_plugin/honeywell_objects/honeywellThermostat.h [new file with mode: 0644]

index eac2c98..18056b1 100644 (file)
@@ -46,4 +46,4 @@ if target_os not in ['android', 'arduino', 'darwin', 'ios', 'tizen', 'msys_nt',
 
 #    SConscript(os.path.join('plugins', 'nest_plugin', 'SConscript'))
 
-#    SConscript(os.path.join('plugins', 'lyric_plugin', 'SConscript'))
+    SConscript(os.path.join('plugins', 'lyric_plugin', 'SConscript'))
diff --git a/bridging/plugins/lyric_plugin/README b/bridging/plugins/lyric_plugin/README
new file mode 100644 (file)
index 0000000..015f5f2
--- /dev/null
@@ -0,0 +1,87 @@
+General:
+To use this plugin, a config file "lyric.cnf" needs to be populated and placed in
+the same directory as the mpm client (i.e. "mpm_sample_client")
+generated by IoTivity's build system.
+
+Note: A Lyric thermostat with a non-proxied internet connection is required to use
+this plugin. All tests/verifications have been with a "Honeywll Lyric Round Thermostat".
+This plugin will NOT provision your thermostat for you. You will need to perform
+the set-up steps through the Lyric app (this can be found in either the iOS or
+Android app store) and your own Lyric user account.
+
+What should this file look like?
+
+    See sample "lyric.cnf" file with contents as shown
+    below(without spaces, tabs or quotes). You will
+    need to create this. See next questions to know
+    how you can obtain your own tokens and ids.
+    Example:
+
+    "
+        Rtgfeja4637nnvls90nkasvwnklre054
+        f02yhajhKDSL64QMWMTRhgsdfgmklbmk
+        cdf1519ec9998380fff7abcaf03d404401063cec7a2972dc68d4eef3e6f328e2:
+    "
+
+What is contained in this file?
+
+    It consists of three values:
+    1. First line of this file represents refresh token value.
+    2. Second line represents the API KEY/CLIENT_ID value
+    3. Third line represents the CLIENT ID AND SECRET value
+
+Where to put this file?
+
+    The placement of the lyric.cnf file should be where
+    your mpm client is also:
+
+    <iotivity>/out/<TARGET_OS>/<TARGET_ARCH>/<BUILD>/bridging/src/mpm_client
+
+    Example: <iotivity>/out/linux/x86_64/release/bridging/src/mpm_client
+        Depending on your build configuration, the path may
+        look mildly different.
+
+What is this API KEY/CLIENT ID or token?
+
+    The Lyric mapping requires usage of the Lyric
+    Developer's API. The usage of this API requires
+    that every user/developer has their own refresh Token,
+    API KEY/CLIENT ID, CLIENT SECRET. These allow
+    liblyricplugin.so to perform actions within your
+    Lyric cloud account on your behalf.
+
+Where can I obtain the API KEY/CLIENT ID and SECRET as shown in the above example?
+
+    Please go through the instructions on the below link to
+    generate the API KEY/CLIENT ID:
+
+    https://developer.honeywell.com/content/getting-started-guide
+
+Where can I obtain the refresh token as shown in the above example?
+
+    Use the below procedure to generate the refresh token as:
+    1. Go to below Link:
+       https://api.honeywell.com/oauth2/app/login?apikey=<API KEY>
+       &redirect_uri=<redirect uri same as your Lyric app>
+    2. Login using your credentials
+    3. It will ask for your consent and then it will show the
+       accesstoken, refresh token and expiry time in the following
+       format as below:
+       {"access_token":"f02yhajhKDSL64QMWMTRhgsdfgmklbmk",
+        "refresh_token":"Rtgfeja4637nnvls90nkasvwnklre054",
+        "expires_in":"599"}
+    4. Copy refresh token and put it in lyric.cnf file to be used
+       by plugin
+
+How can I start this plugin?
+
+    Use either binary executable 'mpm_sample_client' to load and
+    control this plugin.
+
+    More information on these clients can be found at
+    <iotivity>/bridging/src/mpm_client/README.
+
+For proper documentation of this plugin, Mini Plugin
+Manager, the client applications, and other plugins, please
+perform a query on the "Bridging" or "Bridging Project" at
+wiki.iotivity.org.
diff --git a/bridging/plugins/lyric_plugin/SConscript b/bridging/plugins/lyric_plugin/SConscript
new file mode 100644 (file)
index 0000000..e76a4c5
--- /dev/null
@@ -0,0 +1,104 @@
+#******************************************************************
+#
+# Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+#
+#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+##
+# Lyric Plugin build script
+##
+
+import os
+import os.path
+
+Import('env')
+
+target_os = env.get('TARGET_OS')
+src_dir = env.get('SRC_DIR')
+bridging_path = os.path.join(src_dir, 'bridging')
+
+lyric_env = env.Clone()
+
+print "Reading Lyric Plugin script"
+
+######################################################################
+# Build flags
+######################################################################
+
+def maskFlags(flags):
+    flags = [flags.replace('-Wl,--no-undefined', '' ) for flags in flags]
+    return flags
+
+lyric_env.PrependUnique(CPPPATH = [ os.path.join(src_dir, 'resource', 'c_common', 'oic_malloc', 'include'),
+                              os.path.join(src_dir, 'resource', 'c_common', 'oic_string', 'include'),
+                              os.path.join(src_dir, 'resource', 'c_common'),
+                              os.path.join(src_dir, 'resource', 'oc_logger', 'include'),
+                              os.path.join(src_dir, 'resource', 'csdk', 'logger', 'include'),
+                              os.path.join(src_dir, 'resource', 'csdk', 'include'),
+                              os.path.join(src_dir, 'resource', 'csdk', 'stack', 'include'),
+                              os.path.join(src_dir, 'resource', 'include'),
+                              os.path.join(src_dir, 'extlibs', 'cjson'),
+                              os.path.join(src_dir, 'extlibs', 'tinycbor', 'src'),
+                              os.path.join(src_dir, 'extlibs', 'rapidjson', 'rapidjson', 'include', 'rapidjson')
+                              ])
+lyric_env.AppendUnique(CPPPATH = [ os.path.join(bridging_path, 'include'),
+                             os.path.join(bridging_path, 'plugins', 'lyric_plugin'),
+                             os.path.join(bridging_path, 'plugins', 'lyric_plugin', 'honeywell_objects')
+                             ])
+
+if target_os not in ['arduino', 'windows']:
+    lyric_env.AppendUnique(CPPDEFINES = ['WITH_POSIX'])
+
+if target_os in ['darwin','ios']:
+    lyric_env.AppendUnique(CPPDEFINES = ['_DARWIN_C_SOURCE'])
+
+if 'g++' in lyric_env.get('CXX'):
+    lyric_env.AppendUnique(CXXFLAGS = ['-std=c++0x', '-Wall', '-Wextra', '-Werror'])
+
+lyric_env.AppendUnique(RPATH = [lyric_env.get('BUILD_DIR')])
+lyric_env.AppendUnique(LIBPATH = [lyric_env.get('BUILD_DIR')])
+
+if lyric_env.get('LOGGING'):
+    lyric_env.AppendUnique(CPPDEFINES = ['TB_LOG'])
+
+lyric_env['LINKFLAGS'] = maskFlags(env['LINKFLAGS'])
+lyric_env.AppendUnique(LINKFLAGS = ['-Wl,--allow-shlib-undefined'])
+lyric_env.AppendUnique(LINKFLAGS = ['-Wl,--whole-archive', lyric_env.get('BUILD_DIR') +'libmpmcommon.a','-Wl,-no-whole-archive'])
+
+lyric_env.AppendUnique(LIBS = ['m',
+                               'octbstack',
+                               'ocsrm',
+                               'connectivity_abstraction',
+                               'coap',
+                               'curl' ])
+
+#####################################################################
+# Source files and Target(s)
+######################################################################
+lyric_src = [
+         os.path.join(bridging_path, 'plugins', 'lyric_plugin', 'honeywellResource.cpp'),
+         os.path.join(bridging_path, 'plugins', 'lyric_plugin', 'honeywellHelpers.cpp'),
+         os.path.join(bridging_path, 'plugins', 'lyric_plugin', 'honeywell_objects', 'honeywellLyric.cpp'),
+         os.path.join(bridging_path, 'plugins', 'lyric_plugin', 'honeywell_objects', 'honeywell.cpp'),
+         os.path.join(bridging_path, 'plugins', 'lyric_plugin', 'honeywell_objects', 'honeywellThermostat.cpp'),
+         ]
+
+lyric_env.AppendUnique(LYRIC_SRC = lyric_src)
+lyriclib = lyric_env.SharedLibrary('lyricplugin', lyric_env.get('LYRIC_SRC'))
+lyric_env.InstallTarget(lyriclib, 'lyricplugin')
+lyric_env.UserInstallTargetLib(lyriclib, 'lyricplugin')
+
+
diff --git a/bridging/plugins/lyric_plugin/honeywellHelpers.cpp b/bridging/plugins/lyric_plugin/honeywellHelpers.cpp
new file mode 100644 (file)
index 0000000..cb3e214
--- /dev/null
@@ -0,0 +1,213 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+#include "honeywellHelpers.h"
+#include "logger.h"
+#include <stdlib.h> // realloc
+#include <string.h> // memcpy
+#include <sstream> // ostringstream
+#include <fstream> // ifstream
+#include "honeywellDefsLyric.h"
+
+#include <stdio.h> // sprintf
+
+#ifndef LOG_TAG
+#define LOG_TAG "HONEYWELL_HELPERS"
+#endif
+
+MPMResult LoadFileIntoString(const char *filePath, std::string &fileContents)
+{
+    MPMResult result = MPM_RESULT_OK;
+    if (NULL == filePath)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "filePath is NULL.");
+        result = MPM_RESULT_INVALID_PARAMETER;
+    }
+
+    if (MPM_RESULT_OK == result)
+    {
+        try
+        {
+            std::ostringstream buffer;
+            std::ifstream inputFile(filePath);
+            if (!inputFile)
+            {
+                OIC_LOG_V(ERROR, LOG_TAG, "Couldn't open file %s", filePath);
+                result = MPM_RESULT_FILE_NOT_OPEN;
+            }
+            else
+            {
+                buffer << inputFile.rdbuf();
+                fileContents = buffer.str();
+                OIC_LOG_V(INFO, LOG_TAG, "Read %lu bytes from file", (unsigned long) fileContents.size());
+            }
+        }
+        catch (...)
+        {
+            OIC_LOG(ERROR, LOG_TAG, "caught exception.");
+            result = MPM_RESULT_INTERNAL_ERROR;
+        }
+    }
+
+    return result;
+}
+
+MPMResult SaveStringIntoFile(const char *stringData, const char *filePath)
+{
+    MPMResult result = MPM_RESULT_OK;
+
+    if ((NULL == stringData) || (NULL == filePath))
+    {
+        OIC_LOG(ERROR, LOG_TAG, "stringData or filePath are NULL");
+        result = MPM_RESULT_INVALID_PARAMETER;
+        goto cleanUp;
+    }
+
+    try
+    {
+        std::ofstream outFile(filePath, std::ofstream::out);
+        if (!outFile)
+        {
+            OIC_LOG_V(ERROR, LOG_TAG, "Failed to open file %s for output", filePath);
+            result = MPM_RESULT_FILE_NOT_OPEN;
+            goto cleanUp;
+        }
+        outFile << stringData;
+    }
+    catch (...)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Caught exception.");
+        result = MPM_RESULT_INTERNAL_ERROR;
+        goto cleanUp;
+    }
+
+    cleanUp:
+
+    return result;
+}
+
+MPMResult CopyFile(const char *sourceFilePath, const char *destFilePath, bool binaryFile)
+{
+    MPMResult result = MPM_RESULT_OK;
+
+    std::ofstream::openmode outMode = std::ofstream::out;
+    std::ifstream::openmode inMode = std::ifstream::in;
+
+    if (binaryFile)
+    {
+        outMode |= std::ofstream::binary;
+        inMode |= std::ifstream::binary;
+    }
+
+    if ((NULL == sourceFilePath) || (NULL == destFilePath))
+    {
+        OIC_LOG(ERROR, LOG_TAG, "sourceFilePath or destFilePath are NULL");
+        result = MPM_RESULT_INVALID_PARAMETER;
+        goto cleanUp;
+    }
+
+    try
+    {
+        std::ofstream outFile(destFilePath, outMode);
+        if (!outFile)
+        {
+            OIC_LOG_V(ERROR, LOG_TAG, "Failed to open file %s for output", destFilePath);
+            result = MPM_RESULT_FILE_NOT_OPEN;
+            goto cleanUp;
+        }
+
+        std::ifstream inFile(sourceFilePath, inMode);
+        if (!inFile)
+        {
+            OIC_LOG_V(ERROR, LOG_TAG, "Failed to open file %s for input", sourceFilePath);
+            result = MPM_RESULT_FILE_NOT_OPEN;
+            goto cleanUp;
+        }
+
+        outFile << inFile.rdbuf();
+    }
+    catch (...)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Caught exception.");
+        result = MPM_RESULT_INTERNAL_ERROR;
+        goto cleanUp;
+    }
+
+    cleanUp:
+
+    return result;
+}
+
+std::string GetTokenPath(const char *fileName)
+{
+    char *tokenPathVar = NULL;
+    std::string tokenPath = "/";
+    size_t pathLen = 0;
+
+    tokenPathVar = getenv("TOKEN_DIR");
+    if (NULL != tokenPathVar)
+    {
+        // replace default tokenPath with environment variable contents
+        tokenPath = tokenPathVar;
+    }
+
+    if (NULL != fileName)
+    {
+        pathLen = tokenPath.length();
+        if (0 != pathLen)
+        {
+            // path has at least one char; make sure it ends with a slash.
+            if (tokenPath.at(pathLen - 1) != '/')
+            {
+                tokenPath += "/";
+            }
+        }
+
+        // filename should not begin with a slash
+        if (fileName[0] == '/')
+        {
+            fileName++;
+        }
+        // append passed filename
+        tokenPath += fileName;
+    }
+
+    OIC_LOG_V(INFO, LOG_TAG, "Token file path: %s", tokenPath.c_str());
+
+    return tokenPath;
+}
+
+void computeSetpoints(double targetTemp, double &heatSetpoint, double &coolSetpoint)
+{
+    // this creates a temperature range around a desired target temperature. Lyric requires
+    // at least a 3 degree difference between hot and cool setpoints.
+    heatSetpoint = targetTemp - HONEYWELL_SETPOINT_BUFFER;
+    coolSetpoint = targetTemp + HONEYWELL_SETPOINT_BUFFER;
+    return;
+}
+
+double computeTargetTemp(double heatSetpoint, double coolSetpoint)
+{
+    // we divine a target temperature from honeywell's setpoints by averaging the hot and cool
+    // setpoints. the target temperature is right between them.
+    return ((heatSetpoint + coolSetpoint) / 2);
+}
+
diff --git a/bridging/plugins/lyric_plugin/honeywellHelpers.h b/bridging/plugins/lyric_plugin/honeywellHelpers.h
new file mode 100644 (file)
index 0000000..985cb70
--- /dev/null
@@ -0,0 +1,83 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+#ifndef __HONEYWELLHELPERS_H__
+#define __HONEYWELLHELPERS_H__
+
+#include <string>
+#include "mpmErrorCode.h"
+
+// HELPER FUNCS
+
+/// Loads a text file into a string.
+///
+/// @param filePath - (input) Path/filename to load.
+/// @param fileContents - (output) Returned file contents.
+///
+/// @return MPM_RESULT_OK on success, error code otherwise.
+MPMResult LoadFileIntoString(const char *filePath, std::string &fileContents);
+
+/// Saves a string into a text file.
+///
+/// @param stringData - (input) String to save to file.
+/// @param filePath - (input) Path/filename to load.
+///
+/// @return MPM_RESULT_OK on success, error code otherwise.
+MPMResult SaveStringIntoFile(const char *stringData, const char *filePath);
+
+/// Copies a file.
+///
+/// @param sourceFilePath - (input) source file/path to copy
+/// @param destFilePath - (input) target file/path
+/// @param binaryFile - (input) true if source is binary file, false if text file.
+///
+/// @return MPM_RESULT_OK on success, error code otherwise.
+MPMResult CopyFile(const char *sourceFilePath, const char *destFilePath, bool binaryFile);
+
+/// builds a path string to a given token file of given filename.
+/// Uses the contents of the TOKEN_DIR environment variable as the
+/// directory, if set, otherwise uses "/" (which is the "IntelCE/root" folder
+/// on Ubuntu host in tftpboot scenario). If you don't specify a valid filename
+/// this function returns the currently selected token directory.
+///
+/// @param fileName - (input) filename only
+///
+/// @return Full path/filename of token file.
+std::string GetTokenPath(const char *fileName);
+
+/// Calculates setpoints to achieve a desired target temperature. Honeywell thermostats don't have
+/// a single target temp like Nest. Instead you need to set an upper and lower threshold using
+/// setpoints. This computes a setpoint range around the desired target temperature.
+///
+/// @param targetTemp - (input) desired target temperature
+/// @param heatSetpoint - (output) computed heat threshold
+/// @param coolSetpoint - (output) computed cool threshold
+void computeSetpoints(double targetTemp, double &heatSetpoint, double &coolSetpoint);
+
+/// Calculates an average target temperature based on current theshold temperatures.
+///
+/// @param heatSetpoint - (input) current heat threshold
+/// @param coolSetpoint - (input) current cool threshold
+///
+/// @return computed target temperature
+double computeTargetTemp(double heatSetpoint, double coolSetpoint);
+
+#endif // __HONEYWELLHELPERS_H__
diff --git a/bridging/plugins/lyric_plugin/honeywellResource.cpp b/bridging/plugins/lyric_plugin/honeywellResource.cpp
new file mode 100644 (file)
index 0000000..249ca78
--- /dev/null
@@ -0,0 +1,916 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+/* This file contains plugin specific code that adapts the native resource model
+ * of native devices into the resource model of OCF.  The file is divided into two
+ * sections; first plugin specific entry points are implemented followed by the
+ * implementation of the resource entity handler required by the IoTivity implementation
+ * for each resource.
+ *
+ * NOTE: This file is plumbed ready for dynamic resource additions.  There is a
+ * thread provided to manage the devices.  When a resource is found it is added
+ * to a work queue which is serviced by the plugin process function.  The plugin
+ * process function is a thread safe place for the plugin specific code to call
+ * OIC APIs.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string>
+#include <unistd.h>
+#include <signal.h>
+#include <pthread.h>
+#include <fstream>
+#include <map>
+#include <iostream>
+#include "honeywell.h"
+#include "mpmErrorCode.h"
+#include "pluginServer.h"
+#include "rapidjson.h"
+#include "document.h"
+#include "stringbuffer.h"
+
+#include "oic_malloc.h"
+#include "oic_string.h"
+#include "honeywellLyric.h"
+#include "logger.h"
+#include "ConcurrentIotivityUtils.h"
+#include "octypes.h"
+#include "ocstack.h"
+#include "ocpayload.h"
+#include "messageHandler.h"
+
+#include "honeywellThermostat.h"
+#include "honeywellHelpers.h"
+
+#define LOG_TAG "HONEYWELL_RESOURCE"
+
+#define MANUFACTURER_NAME "HONEYWELL"
+#define DEVICE_NAME "Honeywell Lyric Translator"
+#define DEVICE_TYPE "oic.d.thermostat"
+#define MAX_RESOURCES 3
+#define MAX_DEVICE_ID_LEN 32
+#define BM 3
+#define MAX_CHANGEABLEVALUES_LEN 103
+
+using namespace OC::Bridging;
+
+/*******************************************************************************
+ * Pound defines and structure declarations go here
+ ******************************************************************************/
+typedef struct {
+        int locationId;
+        char deviceIdStr[MAX_DEVICE_ID_LEN];
+        char uniqueId[MPM_MAX_UNIQUE_ID_LEN];
+        double ambientTempF;
+        char changeableValues[MAX_CHANGEABLEVALUES_LEN];
+} ThermostatDetails;
+
+/* Resources can come and go based on having a IOT device added or removed from
+ * the physical infrastructure.  Also the Iotivity entity handler does not pass
+ * or present a resource handle in the entity handler callback.  Therefore, in
+ * the plugin specific code an associative structure needs to be kept around in
+ * global space to allow the implementer to make the association between URI and
+ * other attributes corresponding with the IOT device in the data model.
+ *
+ * NOTE: Iotivity guarantees that one can not represent two devices with exactly
+ *       the same URI, so keying off of the URI is a successful strategy.
+ */
+
+/*******************************************************************************
+ * global data goes here
+ ******************************************************************************/
+
+std::map<std::string, LyricThermostatSharedPtr> uriToLyricThermostatMap;
+std::map<std::string, LyricThermostatSharedPtr> addedThermostats;
+
+HoneywellLyric g_honeywell;
+Honeywell::ACCESS_TOKEN m_token;
+bool g_isAuthorized = false;
+Honeywell::CLIENT_ID_SECRET m_clientId_secret;
+
+/*******************************************************************************
+ * prototypes go here
+ ******************************************************************************/
+MPMResult loadAccessToken(const char *filename, Honeywell::ACCESS_TOKEN &token);
+
+OCEntityHandlerResult resourceEntityHandlerCb(OCEntityHandlerFlag,
+                                                 OCEntityHandlerRequest *entityHandlerRequest,
+                                                 void *);
+
+OCEntityHandlerResult processPutRequest(OCRepPayload * payload, LyricThermostatSharedPtr targetThermostat, const std::string uri);
+
+OCRepPayload *getPayload(const char *uri, const THERMOSTAT &data);
+
+void *accessTokenMonitorThread(void *pointer);
+
+static const char CRED_FILE[] = "./oic_svr_db_lyric.dat";
+
+FILE *honeywellFopen(const char *, const char *mode)
+{
+    return fopen(CRED_FILE, mode);
+}
+
+MPMPluginCtx *g_pluginCtx = NULL;
+
+MPMResult pluginCreate(MPMPluginCtx **pluginSpecificCtx)
+{
+    MPMResult result = MPM_RESULT_OK;
+
+    if (g_pluginCtx != NULL)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Plugin is already created");
+        return MPM_RESULT_ALREADY_CREATED;
+    }
+
+    /* allocate a context structure for the plugin */
+    MPMPluginCtx *ctx = (MPMPluginCtx *) OICCalloc(1, sizeof(MPMPluginCtx));
+
+    /* initialize the plugin context */
+    if (ctx == NULL)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Unable to allocate plugin specific context.");
+        return MPM_RESULT_INTERNAL_ERROR;
+    }
+
+    *pluginSpecificCtx = ctx;
+    g_pluginCtx = ctx;
+
+    ctx->device_name = DEVICE_NAME;
+    ctx->resource_type = DEVICE_TYPE;
+    ctx->open = honeywellFopen;
+
+    FILE *fp = fopen("./lyric.cnf", "r");
+
+    if (NULL == fp)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "error loading lyric.cnf file.");
+        return MPM_RESULT_INTERNAL_ERROR;
+    }
+    char code[HONEYWELL_REFRESH_TOKEN_BUFSIZE];
+    char str[1024];
+    size_t size = sizeof(str);
+    if (fgets(str, size, fp) == NULL)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Failed to read ./lyric.cnf");
+        fclose(fp);
+        return MPM_RESULT_INTERNAL_ERROR;
+    }
+
+    str[strlen(str) - 1] = '\0';
+    OICStrcpy(code, HONEYWELL_REFRESH_TOKEN_BUFSIZE, str);
+
+    if (fgets(str, size, fp) == NULL)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Failed to read ./lyric.cnf");
+        fclose(fp);
+        return MPM_RESULT_INTERNAL_ERROR;
+    }
+
+    str[strlen(str) - 1] = '\0';
+    OICStrcpy(m_clientId_secret.honeywell_clientId, HONEYWELL_CLIENT_ID_BUFFSIZE, str);
+    if (fgets(str, size, fp) == NULL)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Failed to read ./lyric.cnf");
+        fclose(fp);
+        return MPM_RESULT_INTERNAL_ERROR;
+    }
+
+    str[strlen(str) - 1] = '\0';
+    OICStrcpy(m_clientId_secret.honeywell_client_secret, HONEYWELL_CLIENT_AND_SECRET_64_BUFFSIZE, str);
+    fclose(fp);
+
+    g_honeywell.setClientIdAndSecret(m_clientId_secret);
+
+    std::string acode;
+    acode.assign(code);
+    result = (MPMResult) g_honeywell.getAccessToken(acode, m_token);
+    if (MPM_RESULT_OK != result)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "getAccessToken failed with %d", result);
+        // TODO - what to do in case of failure? free resources? reset auth flag?
+        g_isAuthorized = false;
+        return MPM_RESULT_INTERNAL_ERROR;
+    }
+    else
+    {
+        OIC_LOG(DEBUG, LOG_TAG, "getAccessToken is successful");
+        g_isAuthorized = true;
+        g_honeywell.setAccessToken(m_token);
+    }
+
+    OIC_LOG_V(INFO, LOG_TAG, "Plugin create return value: %d.", result);
+
+    return result;
+}
+
+MPMResult pluginStart(MPMPluginCtx *pluginSpecificCtx)
+{
+    MPMResult result = MPM_RESULT_INTERNAL_ERROR;
+    int error = -1;
+
+    if (pluginSpecificCtx != NULL)
+    {
+        // set global plugin context
+        g_pluginCtx = pluginSpecificCtx;
+
+        /* create house keeping thread */
+        error = pthread_create(&(pluginSpecificCtx->thread_handle), NULL, accessTokenMonitorThread, pluginSpecificCtx);
+        if (error == 0)
+        {
+            pluginSpecificCtx->stay_in_process_loop = true;
+            pluginSpecificCtx->started = true;
+            result = MPM_RESULT_OK;
+        }
+        else
+        {
+            pluginSpecificCtx->stay_in_process_loop = false;
+            pluginSpecificCtx->started = false;
+            OIC_LOG_V(ERROR, LOG_TAG, "Can't create plugin specific thread :[%s]", strerror(error));
+            result = MPM_RESULT_STARTED_FAILED;
+        }
+    }
+
+    OIC_LOG_V(INFO, LOG_TAG, "Plugin start return value: %d.", result);
+
+    return result;
+}
+
+MPMResult pluginStop(MPMPluginCtx *pluginSpecificCtx)
+{
+    MPMResult result = MPM_RESULT_INTERNAL_ERROR;
+
+    if (NULL != pluginSpecificCtx)
+    {
+        result = MPM_RESULT_OK;
+
+        if (pluginSpecificCtx->started == true)
+        {
+            pluginSpecificCtx->stay_in_process_loop = false;
+            pthread_join(pluginSpecificCtx->thread_handle, NULL);
+            pluginSpecificCtx->started = false;
+        }
+    }
+
+    OIC_LOG_V(INFO, LOG_TAG, "Plugin stop's return value:%d", result);
+
+    return result;
+}
+
+MPMResult pluginDestroy(MPMPluginCtx *pluginSpecificCtx)
+{
+    MPMResult result = MPM_RESULT_INTERNAL_ERROR;
+
+    if (pluginSpecificCtx != NULL)
+    {
+
+        if (pluginSpecificCtx->started == true)
+        {
+            pluginStop(pluginSpecificCtx);
+        }
+
+        // freeing the resource allocated in create
+        OICFree(pluginSpecificCtx);
+        pluginSpecificCtx = NULL;
+        result = MPM_RESULT_OK;
+    }
+
+    OIC_LOG_V(INFO, LOG_TAG, "Plugin destroy's return value:%d", result);
+
+    return result;
+}
+
+/**
+ * This method checks if the request is for an actuator(oic.if.a) resource or a
+ * sensor(oic.if.s) resource. Sensors allow only GET. Actuators allow GET PUT
+ * POST. Sensors resources end with /current. Actuators have uris ending with
+ * /heater or /cooler.
+ *
+ * @param[in] uri           Resource Uri
+ * @param[in] operation     Operation to be checked.
+ */
+OCEntityHandlerResult checkIfOperationIsAllowed(std::string uri, OCMethod operation)
+{
+    if (operation == OC_REST_GET)
+    {
+        return OC_EH_OK;
+    }
+    if (operation == OC_REST_DELETE)
+    {
+        return OC_EH_FORBIDDEN;
+    }
+
+    std::string sensor_suffix = "/current";
+
+    // uri is smaller than suffix.. which is weird.. but not a failure in this function.
+    if (uri.length() < sensor_suffix.length())
+    {
+        return OC_EH_OK;
+    }
+
+    // Code here means operation is not a GET. Disallow operation if it is a sensor.
+    if (std::equal(sensor_suffix.rbegin(), sensor_suffix.rend(), uri.rbegin()))
+    {
+        OIC_LOG(INFO, LOG_TAG, "PUT/POST not allowed on sensors");
+        return OC_EH_FORBIDDEN;
+    }
+
+    return OC_EH_OK;
+}
+
+/**
+ * This method creates payload for response to GET/PUT/POST request.
+ *
+ * @param[in] uri           Resource Uri
+ * @param[in] data          Thermostat detials to be sent in response.
+ */
+OCRepPayload *getPayload(const char *uri, const THERMOSTAT &data)
+{
+    bool result = true;
+    std::string modeString;
+    OCRepPayload *payload = OCRepPayloadCreate();
+    if (NULL == payload)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Failed to allocate Payload");
+        result = false;
+    }
+
+    OIC_LOG_V(INFO, LOG_TAG,
+          "coolSP: %f, heatSP: %f, ambient: %f, desired: %f",
+          data.coolSetpointF,
+          data.heatSetpointF,
+          data.ambientTempF,
+          computeTargetTemp(data.heatSetpointF, data.coolSetpointF));
+
+    if (result)
+    {
+        result = OCRepPayloadSetUri(payload, uri);
+        if (false == result)
+        {
+            OIC_LOG(ERROR, LOG_TAG, "OCRepPayloadSetUri failed");
+        }
+    }
+
+    if (result)
+    {
+        result = OCRepPayloadAddResourceType(payload, HONEYWELL_THERMOSTAT_RT);
+        if (false == result)
+        {
+            OIC_LOG(ERROR, LOG_TAG, "OCRepPayloadAddResourceType failed");
+        }
+    }
+
+    if (result)
+    {
+        // high target temp is cool setpoint (hottest it gets before cooling kicks on)
+        result = OCRepPayloadSetPropDouble(payload, REP_NAME_TARGET_TEMP_HIGH, data.coolSetpointF);
+        if (false == result)
+        {
+            OIC_LOG(ERROR, LOG_TAG, "OCRepPayloadSetPropDouble REP_NAME_TARGET_TEMP_HIGH failed");
+        }
+    }
+    if (result)
+    {
+        // low target temp is heat setpoint (coolest it gets before heating kicks on)
+        result = OCRepPayloadSetPropDouble(payload, REP_NAME_TARGET_TEMP_LOW, data.heatSetpointF);
+        if (false == result)
+        {
+            OIC_LOG(ERROR, LOG_TAG, "OCRepPayloadSetPropDouble REP_NAME_TARGET_TEMP_LOW failed");
+        }
+    }
+    if (result)
+    {
+        // Return ambient/indoor temperature (if available) in optional property
+        result = OCRepPayloadSetPropDouble(payload, REP_NAME_INDOOR_TEMP, data.ambientTempF);
+        if (false == result)
+        {
+            OIC_LOG(ERROR, LOG_TAG, "OCRepPayloadSetPropDouble REP_NAME_INDOOR_TEMP failed");
+        }
+        // Return desired/target temperature for both gets and sets
+        double temperature = computeTargetTemp(data.heatSetpointF, data.coolSetpointF);
+        OIC_LOG_V(INFO, LOG_TAG, "Setting temperature in payload as %f", temperature);
+
+        result = OCRepPayloadSetPropDouble(
+                payload, REP_NAME_TEMPERATURE, temperature);
+        if (false == result)
+        {
+            OIC_LOG(ERROR, LOG_TAG, "OCRepPayloadSetPropDouble REP_NAME_TEMPERATURE failed");
+        }
+    }
+    if (result)
+    {
+        if (data.hvacMode == HVAC_COOL)
+        {
+            modeString = REP_VALUE_COOL;
+        }
+        else if (data.hvacMode == HVAC_HEAT)
+        {
+            modeString = REP_VALUE_HEAT;
+        }
+        else
+        {
+            modeString = REP_VALUE_OFF;
+        }
+
+        result = OCRepPayloadSetPropString(payload, REP_NAME_MODE, modeString.c_str());
+        if (false == result)
+        {
+            OIC_LOG(ERROR, LOG_TAG, "OCRepPayloadSetPropString REP_NAME_MODE failed");
+        }
+    }
+
+    if (false == result)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Failed to set payload value(s)");
+
+        // if we are in an error state, we need to free the payload.
+        if (NULL != payload)
+        {
+            OCRepPayloadDestroy(payload);
+            payload = NULL;
+        }
+    }
+
+    return payload;
+}
+
+OCEntityHandlerResult
+resourceEntityHandlerCb(OCEntityHandlerFlag, OCEntityHandlerRequest *request, void *)
+{
+    OCEntityHandlerResult result = OC_EH_OK;
+
+    try
+    {
+        std::string resourceUri;
+        std::size_t found = 0;
+        ConcurrentIotivityUtils::getUriFromHandle(request->resource, resourceUri);
+        found = resourceUri.find_last_of("/");
+        std::string uri = resourceUri.substr(0, found);  //device uri
+        LyricThermostatSharedPtr targetThermostat = addedThermostats[uri];
+        THERMOSTAT data;
+
+        result = checkIfOperationIsAllowed(resourceUri, request->method);
+        if (result != OC_EH_OK)
+        {
+           OIC_LOG_V(INFO, LOG_TAG, "Operation not allowed on %s", resourceUri.c_str());
+           return result;
+        }
+
+        switch (request->method)
+        {
+            case OC_REST_GET:
+                // Empty GET case as actual request will be processed after the switch case.
+                OIC_LOG_V(INFO, LOG_TAG, "GET on %s", resourceUri.c_str());
+                break;
+
+            case OC_REST_PUT:
+            case OC_REST_POST:
+
+                OIC_LOG_V(INFO, LOG_TAG, "UPDATE on %s", resourceUri.c_str());
+                result = processPutRequest((OCRepPayload*) request->payload, targetThermostat, resourceUri);
+                if (result != OC_EH_OK)
+                {
+                    OIC_LOG_V(ERROR, LOG_TAG, "process_put_request returned Error = %d", result);
+                }
+
+                break;
+
+            default:
+                OIC_LOG_V(INFO, LOG_TAG,"Unsupported method (%d) received", request->method);
+                ConcurrentIotivityUtils::respondToRequestWithError(request, "Unsupported method received",
+                    OC_EH_METHOD_NOT_ALLOWED);
+                return OC_EH_OK;
+        }
+
+        targetThermostat->get(data);
+        OCRepPayload *payload = getPayload(uri.c_str(), data);
+
+        ConcurrentIotivityUtils::respondToRequest(request, payload, result);
+        OCRepPayloadDestroy(payload);
+    }
+
+    catch (std::string errorMessage)
+    {
+        ConcurrentIotivityUtils::respondToRequestWithError(request, errorMessage.c_str(), OC_EH_ERROR);
+        return OC_EH_OK;
+    }
+
+    return OC_EH_OK;
+}
+
+
+OCEntityHandlerResult processPutRequest(OCRepPayload * payload, LyricThermostatSharedPtr targetThermostat, const std::string uri)
+{
+    OCEntityHandlerResult ehResult = OC_EH_OK;
+    OIC_LOG_V(INFO, LOG_TAG, "Put request for %s", uri.c_str());
+    THERMOSTAT localData;
+    int result = MPM_RESULT_OK;
+    if (!payload)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Entity handler request payload is empty while processing PUT request");
+        return OC_EH_ERROR;
+    }
+
+    // Get pointer to query
+    if (!OCRepPayloadGetPropDouble(payload, REP_NAME_TEMPERATURE, &(localData.targetTempF)))
+    {
+        OIC_LOG(ERROR, LOG_TAG, "REP_NAME_TEMPERATURE not found in payload (data).");
+        return OC_EH_ERROR;
+    }
+
+    OIC_LOG_V(INFO, LOG_TAG, "localData.targetTempF %f", localData.targetTempF);
+    // compute cool and hot setpoints based on desired temperature
+    // NOTE: low = heatSetpoint, high = coolSetpoint (they are the temperatures those
+    //       modes try to achieve.)
+    computeSetpoints(localData.targetTempF, localData.heatSetpointF, localData.coolSetpointF);
+
+    OIC_LOG_V(INFO, LOG_TAG, "localData.heatSetpointF %f", localData.heatSetpointF);
+    OIC_LOG_V(INFO, LOG_TAG, "localData.coolSetpointF %f", localData.coolSetpointF);
+
+    result = g_honeywell.setTemperature(targetThermostat, localData, uri);
+
+    OIC_LOG_V(INFO, LOG_TAG, "setTemperature returned result = %d", result);
+    if (result != MPM_RESULT_OK)
+    {
+        throw "Error setting temperature for PUT request";
+    }
+
+    return ehResult;
+}
+
+MPMResult pluginScan(MPMPluginCtx *, MPMPipeMessage *)
+{
+    OIC_LOG(INFO, LOG_TAG, "Inside plugin_scan");
+    std::vector<LyricThermostatSharedPtr> thermostatsScanned;
+
+    MPMResult result = MPM_RESULT_INTERNAL_ERROR;
+
+    result = (MPMResult) g_honeywell.getThermostats(thermostatsScanned);
+    if (MPM_RESULT_OK == result)
+    {
+        for (uint32_t i = 0; i < thermostatsScanned.size(); ++i)
+        {
+            LyricThermostatSharedPtr thermostat = thermostatsScanned[i];
+
+            std::string uri = "/honeywell/" + thermostat->getDeviceUniqueId();
+            if(addedThermostats.find(uri) != addedThermostats.end())
+            {
+                OIC_LOG_V(INFO, LOG_TAG, "Already Added %s. Ignoring", uri.c_str());
+                continue;
+            }
+
+            uriToLyricThermostatMap[uri] = thermostat;
+
+            MPMSendResponse(uri.c_str(), uri.size(), MPM_SCAN);
+        }
+    }
+    else
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "getThermostats failed with %d", result);
+    }
+    OIC_LOG(INFO, LOG_TAG, "Leaving plugin specific thread handler.");
+    return result;
+}
+
+bool createSecureResources()
+{
+    char *non_secure_env = getenv("NONSECURE");
+
+    if (non_secure_env != NULL && strcmp(non_secure_env, "true") == 0)
+    {
+        OIC_LOG(INFO, LOG_TAG, "Creating NON SECURE resources");
+        return false;
+    }
+    OIC_LOG(INFO, LOG_TAG, "Creating SECURE resources");
+    return true;
+}
+
+void createPayloadForMetadata(MPMResourceList **list , const char *uri, const char * interface)
+{
+    MPMResourceList *tempPtr;
+    tempPtr = (MPMResourceList *) OICCalloc(1, sizeof(MPMResourceList));
+
+    if (tempPtr == NULL)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Memory Allocation failed");
+        return;
+    }
+    OICStrcpy(tempPtr->rt, MPM_MAX_LENGTH_64, HONEYWELL_THERMOSTAT_RT);
+    OICStrcpy(tempPtr->href, MPM_MAX_URI_LEN, uri);
+    OICStrcpy(tempPtr->interfaces, MPM_MAX_LENGTH_64, interface);
+    tempPtr->bitmap = BM;
+    tempPtr->next = *list;
+    *list  = tempPtr;
+}
+
+void updatePluginSpecificData(THERMOSTAT thermostat, ThermostatDetails *thermostatDetails)
+{
+    OICStrcpy(thermostatDetails->deviceIdStr, MAX_DEVICE_ID_LEN, thermostat.devInfo.deviceIdStr.c_str());
+    OICStrcpy(thermostatDetails->uniqueId, MPM_MAX_UNIQUE_ID_LEN, thermostat.devInfo.uniqueId.c_str());
+    thermostatDetails->locationId = thermostat.devInfo.locationId;
+    thermostatDetails->ambientTempF = thermostat.ambientTempF;
+}
+
+MPMResult pluginAdd(MPMPluginCtx *, MPMPipeMessage * message)
+{
+    uint8_t resourceProperties = (OC_OBSERVABLE | OC_DISCOVERABLE);
+    if (createSecureResources()) resourceProperties |= OC_SECURE;
+
+    std::string uri = reinterpret_cast<const char *>(message->payload);
+    if(addedThermostats.find(uri) != addedThermostats.end())
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "%s already added", uri.c_str());
+        return MPM_RESULT_ALREADY_CREATED;
+    }
+    if(uriToLyricThermostatMap.find(uri) == uriToLyricThermostatMap.end())
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "%s was NOT discovered in a scan", uri.c_str());
+        return MPM_RESULT_INTERNAL_ERROR;
+    }
+
+    MPMResourceList *list = NULL;
+
+    /* Heater Resource */
+    std::string honeywellDeviceActuatorHeaterUri = uri + "/heater";
+    ConcurrentIotivityUtils::queueCreateResource(honeywellDeviceActuatorHeaterUri, HONEYWELL_THERMOSTAT_RT, HONEYWELL_THERMOSTAT_ACTUATOR_IF, resourceEntityHandlerCb, NULL, resourceProperties);
+    createPayloadForMetadata(&list, honeywellDeviceActuatorHeaterUri.c_str(), HONEYWELL_THERMOSTAT_ACTUATOR_IF);
+
+    /* Cooler Resource */
+    std::string honeywellDeviceActuatorCoolerUri = uri + "/cooler";
+    ConcurrentIotivityUtils::queueCreateResource(honeywellDeviceActuatorCoolerUri, HONEYWELL_THERMOSTAT_RT, HONEYWELL_THERMOSTAT_ACTUATOR_IF, resourceEntityHandlerCb, NULL, resourceProperties);
+    createPayloadForMetadata(&list, honeywellDeviceActuatorCoolerUri.c_str(), HONEYWELL_THERMOSTAT_ACTUATOR_IF);
+
+    /* Sensor Resource */
+    std::string honeywellDeviceSensorUri = uri + "/current";
+    ConcurrentIotivityUtils::queueCreateResource(honeywellDeviceSensorUri, HONEYWELL_THERMOSTAT_RT, HONEYWELL_THERMOSTAT_SENSOR_IF, resourceEntityHandlerCb, NULL, resourceProperties);
+    createPayloadForMetadata(&list, honeywellDeviceSensorUri.c_str(), HONEYWELL_THERMOSTAT_SENSOR_IF);
+
+    addedThermostats[uri] = uriToLyricThermostatMap[uri];
+
+    uint8_t * buff = (uint8_t *) OICCalloc(1, MPM_MAX_METADATA_LEN);
+    ThermostatDetails thermostatDetails;
+    MPMDeviceSpecificData deviceConfiguration;
+    THERMOSTAT thermostat;
+    std::string changeableValues;
+
+    addedThermostats[uri]->get(thermostat);
+    changeableValues = addedThermostats[uri]->getChangeableValues();
+    memset(&thermostatDetails, 0, sizeof(ThermostatDetails));
+    memset(&deviceConfiguration, 0, sizeof(MPMDeviceSpecificData));
+
+    OICStrcpy(thermostatDetails.changeableValues, MAX_CHANGEABLEVALUES_LEN, changeableValues.c_str());
+    updatePluginSpecificData(thermostat, &thermostatDetails);
+    OICStrcpy(deviceConfiguration.devName, MPM_MAX_LENGTH_64, DEVICE_NAME);
+    OICStrcpy(deviceConfiguration.devType, MPM_MAX_LENGTH_64, DEVICE_TYPE);
+    OICStrcpy(deviceConfiguration.manufacturerName, MPM_MAX_LENGTH_256, MANUFACTURER_NAME);
+    MPMFormMetaData(list, &deviceConfiguration, buff, MPM_MAX_METADATA_LEN, (void *)&thermostatDetails, sizeof(ThermostatDetails));
+
+    MPMAddResponse addResponse;
+    memset(&addResponse, 0, sizeof(MPMAddResponse));
+    OICStrcpy(addResponse.uri, MPM_MAX_URI_LEN, uri.c_str());
+    memcpy(addResponse.metadata, buff, MPM_MAX_METADATA_LEN);
+    MPMSendResponse(&addResponse, sizeof(MPMAddResponse), MPM_ADD);
+    OICFree(buff);
+    return MPM_RESULT_OK;
+
+}
+
+MPMResult pluginRemove(MPMPluginCtx *, MPMPipeMessage * message)
+{
+    std::string uri = reinterpret_cast<const char *>(message->payload);
+
+    /* Heater Resource */
+    std::string honeywellDeviceActuatorHeaterUri = uri + "/heater";
+    ConcurrentIotivityUtils::queueDeleteResource(honeywellDeviceActuatorHeaterUri);
+
+    /* Cooler Resource */
+    std::string honeywellDeviceActuatorCoolerUri = uri + "/cooler";
+    ConcurrentIotivityUtils::queueDeleteResource(honeywellDeviceActuatorCoolerUri);
+
+    /* Sensor Resource */
+    std::string honeywellDeviceSensorUri = uri + "/current";
+    ConcurrentIotivityUtils::queueDeleteResource(honeywellDeviceSensorUri);
+
+    addedThermostats.erase(uri);
+    uriToLyricThermostatMap.erase(uri);
+
+    MPMSendResponse(uri.c_str(), uri.size(), MPM_REMOVE);
+
+    return MPM_RESULT_OK;
+}
+
+MPMResult pluginReconnect(MPMPluginCtx *, MPMPipeMessage * message)
+{
+    MPMResourceList *list = NULL, *temp = NULL;
+    char *buff = NULL;
+    THERMOSTAT thermostat;
+    std::vector<LyricThermostatSharedPtr> thermostatsReconnected;
+    void *details = NULL;
+    MPMResult result = MPM_RESULT_INTERNAL_ERROR;
+    uint8_t resourceProperties = (OC_OBSERVABLE | OC_DISCOVERABLE);
+    std::shared_ptr<HoneywellThermostat> sharedThermostat;
+    std::string thermostatMode;
+    std::string uri;
+
+    if(message->payloadSize > 0 &&message->payloadSize < SIZE_MAX)
+    {
+        buff = (char *) OICCalloc(1, message->payloadSize);
+
+        if (buff ==NULL)
+        {
+            OIC_LOG(ERROR, LOG_TAG, "OICCalloc Failed");
+            return MPM_RESULT_INTERNAL_ERROR;
+        }
+    }
+    else
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Payload size is out of bound");
+        return MPM_RESULT_INTERNAL_ERROR;
+    }
+
+    memcpy(buff, message->payload, message->payloadSize);
+    MPMParseMetaData((uint8_t*)buff, MPM_MAX_METADATA_LEN, &list, &details);
+
+    ThermostatDetails *thermostatDetails = (ThermostatDetails *)details;
+    HoneywellThermostat honeywellThermostat;
+
+    thermostat.devInfo.locationId = thermostatDetails->locationId;
+    thermostat.devInfo.deviceIdStr.assign(thermostatDetails->deviceIdStr);
+    thermostat.devInfo.uniqueId.assign(thermostatDetails->uniqueId);
+    thermostat.ambientTempF = thermostatDetails->ambientTempF;
+    honeywellThermostat.setDeviceUniqueId(thermostat.devInfo.uniqueId.c_str());
+
+    honeywellThermostat.setChangeableValues(thermostatDetails->changeableValues);
+
+    rapidjson:: Document values;
+    values.SetObject();
+    if (values.Parse(thermostatDetails->changeableValues).HasParseError())
+    {
+        OIC_LOG(ERROR, LOG_TAG,"Parse error in set changeableValues");
+        result = MPM_RESULT_JSON_ERROR;
+        goto CLEANUP;
+    }
+
+    if(values.HasMember(JSON_MODE))
+    {
+        thermostatMode = values[JSON_MODE].GetString();
+    }
+
+    if(values.HasMember(HONEYWELL_HEAT_SETPOINT))
+    {
+       thermostat.heatSetpointF = values[HONEYWELL_HEAT_SETPOINT].GetDouble();
+    }
+
+    if(values.HasMember(HONEYWELL_COOL_SETPOINT))
+    {
+       thermostat.coolSetpointF = values[HONEYWELL_COOL_SETPOINT].GetDouble();
+    }
+
+    if (0 == strncmp(thermostatMode.c_str(), THERMOSTAT_MODE_COOL, sizeof(THERMOSTAT_MODE_COOL)))
+    {
+        thermostat.hvacMode = HVAC_COOL;
+    }
+    else if (0 == strncmp(thermostatMode.c_str(), THERMOSTAT_MODE_HEAT, sizeof(THERMOSTAT_MODE_HEAT)))
+    {
+        thermostat.hvacMode = HVAC_HEAT;
+    }
+    else
+    {
+        thermostat.hvacMode = HVAC_OFF;
+    }
+
+    thermostat.targetTempF = computeTargetTemp(thermostat.heatSetpointF, thermostat.coolSetpointF);
+    dump_details(thermostat, "thermostatData");
+    honeywellThermostat.set(thermostat);
+
+    sharedThermostat = std::make_shared<HoneywellThermostat>(honeywellThermostat);
+
+    uri = "/honeywell/" + sharedThermostat->getDeviceUniqueId();
+    if(uriToLyricThermostatMap.find(uri) != uriToLyricThermostatMap.end())
+    {
+        OIC_LOG_V(INFO, LOG_TAG, "Already found %s. Ignoring", uri.c_str());
+    }
+    else
+    {
+        OIC_LOG_V(INFO, LOG_TAG, "Adding %s to uriToLyricThermostatMap", uri.c_str());
+        uriToLyricThermostatMap[uri] = sharedThermostat;
+    }
+
+    if(addedThermostats.find(uri) != addedThermostats.end())
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "%s already added", uri.c_str());
+        result = MPM_RESULT_ALREADY_CREATED;
+        goto CLEANUP;
+    }
+    if(uriToLyricThermostatMap.find(uri) == uriToLyricThermostatMap.end())
+    {
+        result = MPM_RESULT_INTERNAL_ERROR;
+        goto CLEANUP;
+    }
+
+    if (createSecureResources()) resourceProperties |= OC_SECURE;
+
+    while(list)
+    {
+        temp = list;
+        std::string resourceUri(list->href);
+        OIC_LOG_V(INFO, LOG_TAG, "resource uri = %s", resourceUri.c_str());
+        ConcurrentIotivityUtils::queueCreateResource(resourceUri, list->rt, list->interfaces, resourceEntityHandlerCb, NULL, resourceProperties);
+        list = list->next;
+        OICFree(temp);
+    }
+
+    addedThermostats[uri] = uriToLyricThermostatMap[uri];
+    result = MPM_RESULT_OK;
+
+    CLEANUP:
+    if (buff != NULL)
+    {
+        OICFree(buff);
+    }
+    if (thermostatDetails !=NULL)
+    {
+        OICFree(thermostatDetails);
+        details = NULL;
+    }
+    return result;
+}
+
+/**
+ * The Lyric Access Token process loop.
+ * - manages honeywell device resources in response to changes in
+ *   authentication status
+ * - performs re-authentication with Honeywell servers to keep a fresh
+ *   access token (Lyric access tokens only last for 10 minutes)
+ *
+ * @param[in] pointer     plugin specific context
+ */
+void *accessTokenMonitorThread(void *pointer)
+{
+    OIC_LOG(DEBUG, LOG_TAG, "Entered accessTokenMonitorThread");
+    MPMPluginCtx *ctx = (MPMPluginCtx *) pointer;
+    int reauthCountdown = HW_QUERY_INTERVAL_SECONDS; // period to wait before attempting re-auth
+    bool lastAuthorized = false;
+    std::string emptycode;
+    MPMResult result = MPM_RESULT_INTERNAL_ERROR;
+
+    if (ctx != NULL)
+    {
+        while (ctx->stay_in_process_loop)
+        {
+            // check if there has been a change in authorization status
+            if (lastAuthorized != g_isAuthorized)
+            {
+                // make sure we don't get into an unproductive cycle
+                lastAuthorized = g_isAuthorized;
+            }
+
+            // Only do Lyric re-auth logic if we are already authenticated.
+            if (lastAuthorized && (reauthCountdown <= 0))
+            {
+                result = (MPMResult) g_honeywell.getAccessToken(emptycode, m_token);
+                if (MPM_RESULT_OK != result)
+                {
+                    OIC_LOG_V(ERROR, LOG_TAG, "getAccessToken failed with %d", result);
+                    // TODO - what to do in case of failure? free resources? reset auth flag?
+                    g_isAuthorized = false;
+                }
+                else
+                {
+                    // reset the counter
+                    reauthCountdown = (HW_AUTH_LOOP_MINUTES * 60);
+                    OIC_LOG(DEBUG, LOG_TAG, "getAccessToken is successful");
+                    g_isAuthorized = true;
+                    g_honeywell.setAccessToken(m_token);
+                }
+            }
+            else
+            {
+                // not time to refresh token yet, just decrement
+                reauthCountdown--;
+            }
+            sleep(MPM_THREAD_PROCESS_SLEEPTIME);
+        }
+        OIC_LOG(INFO, LOG_TAG,"Leaving LYRIC monitor thread");
+    }
+    pthread_exit(NULL);
+}
+
diff --git a/bridging/plugins/lyric_plugin/honeywell_objects/honeywell.cpp b/bridging/plugins/lyric_plugin/honeywell_objects/honeywell.cpp
new file mode 100644 (file)
index 0000000..3fc5bce
--- /dev/null
@@ -0,0 +1,195 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+#include <time.h>
+#include <stdio.h>
+#include <iostream>
+#include "honeywell.h"
+#include "curlClient.h"
+
+#include "honeywellDefsLyric.h"
+#include "oic_string.h"
+
+#include "rapidjson.h"
+#include "document.h"
+#include "stringbuffer.h"
+#include "writer.h"
+#include "logger.h"
+#include <math.h>  // for fmin
+#include <sstream> // for stringstream
+#include <map>
+using namespace rapidjson;
+
+#define LOG_TAG "HONEYWELL"
+
+// EXTERNS
+extern std::map<std::string, bool> g_deviceChangedMap;
+
+// MEMBER FUNCTIONS
+
+Honeywell::Honeywell(const ACCESS_TOKEN &accessToken, const CLIENT_ID_SECRET &clientIdSecret)
+        : m_accessToken(accessToken), m_clientIdAndSecret(clientIdSecret), m_isAuthorized(false)
+{
+    // for curl support
+    manageMutexes(true);
+    m_getInProgress = false;
+}
+
+Honeywell::Honeywell() : m_isAuthorized(false)
+{
+    initializeAccessToken();
+    initializeClientIdSecret();
+    // for curl support
+    manageMutexes(true);
+    m_getInProgress = false;
+
+    return;
+}
+
+Honeywell::~Honeywell()
+{
+    manageMutexes(false);
+}
+
+void Honeywell::initializeAccessToken()
+{
+        ::memset(&m_accessToken.accessToken, 0, HONEYWELL_ACCESS_TOKEN_BUFSIZE);
+        ::memset(&m_accessToken.expires, 0, HONEYWELL_ACCESS_TOKEN_EXPIRY);
+        m_accessToken.acquiredTime = 0;
+        m_accessToken.grantTime = 0;
+        m_accessToken.userId = 0;
+}
+
+void Honeywell::initializeClientIdSecret()
+{
+        ::memset(&m_clientIdAndSecret.honeywell_clientId, 0, HONEYWELL_CLIENT_ID_BUFFSIZE);
+        ::memset(&m_clientIdAndSecret.honeywell_client_secret, 0, HONEYWELL_CLIENT_AND_SECRET_64_BUFFSIZE);
+}
+
+MPMResult Honeywell::manageMutexes(bool initialize)
+{
+    MPMResult result = MPM_RESULT_OK;
+    m_accessTokenMutexInitialized = false;
+    m_deviceChangeMutexInitialized = false;
+    int intResult = 0;
+
+    if (initialize)
+    {
+        // caller wants to init
+        intResult = pthread_mutex_init(&m_cloudAccessMutex, NULL);
+        if (0 != intResult)
+        {
+            OIC_LOG_V(ERROR, LOG_TAG, "pthread_mutex_init m_cloudAccessMutex failed with error %d", intResult);
+            result = MPM_RESULT_INTERNAL_ERROR;
+        }
+        else
+        {
+            m_accessTokenMutexInitialized = true;
+        }
+    }
+    else
+    {
+        // caller wants to destroy
+        if (m_accessTokenMutexInitialized)
+        {
+            intResult = pthread_mutex_destroy(&m_cloudAccessMutex);
+            if (0 != intResult)
+            {
+                OIC_LOG_V(ERROR, LOG_TAG, "pthread_mutex_destroy m_cloudAccessMutex failed with error %d", intResult);
+                result = MPM_RESULT_INTERNAL_ERROR;
+            }
+            // always set to false
+            m_accessTokenMutexInitialized = false;
+        }
+        // don't do anything if m_accessTokenMutexInitialized already false
+    }
+
+    return result;
+}
+
+void Honeywell::dumpResponseString(const char *stringData, const char *fileName)
+{
+    char logBuffer[MAX_LOG_STRING + 1];
+
+    if ((NULL == stringData) || (0 == strlen(stringData)))
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "stringData is NULL or zero len");
+        return;
+    }
+
+    OICStrcpy(logBuffer, sizeof(logBuffer), stringData);
+    // display LENGTH of full response string, but only output the safe/truncated string
+    if ((NULL != fileName) && (0 != strlen(fileName)))
+    {
+        if (MPM_RESULT_OK != SaveStringIntoFile(stringData, fileName))
+        {
+            OIC_LOG_V(ERROR, LOG_TAG, "Error saving file %s", fileName);
+        }
+    }
+}
+
+void Honeywell::setAccessToken(const ACCESS_TOKEN &token)
+{
+    m_accessToken = token;
+}
+
+void Honeywell::setClientIdAndSecret(const CLIENT_ID_SECRET &clientIdAndSecret)
+{
+    m_clientIdAndSecret = clientIdAndSecret;
+}
+
+bool Honeywell::lockCloudAccess()
+{
+    bool mutexLocked = false;
+    int intResult = pthread_mutex_lock(&m_cloudAccessMutex);
+    if (0 != intResult)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "pthread_mutex_lock failed with %d", intResult);
+    }
+    else
+    {
+        mutexLocked = true;
+    }
+    return mutexLocked;
+}
+
+bool Honeywell::unlockCloudAccess()
+{
+    bool success = true;
+    int intResult = pthread_mutex_unlock(&m_cloudAccessMutex);
+    if (0 != intResult)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "pthread_mutex_unlock failed with %d", intResult);
+        success = false;
+    }
+    return success;
+}
+
+int Honeywell::deauthorizateToken()
+{
+    int result = (int) MPM_RESULT_OK;
+    initializeAccessToken();
+    initializeClientIdSecret();
+    m_isAuthorized = false;
+
+    return result;
+}
+
diff --git a/bridging/plugins/lyric_plugin/honeywell_objects/honeywell.h b/bridging/plugins/lyric_plugin/honeywell_objects/honeywell.h
new file mode 100644 (file)
index 0000000..6d5537f
--- /dev/null
@@ -0,0 +1,186 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+#ifndef __HONEYWELL_H__
+#define __HONEYWELL_H__
+
+#include <time.h>
+#include <stdio.h>
+#include <vector>
+#include <stdint.h>
+#include <typeinfo>
+#include "honeywellDefsLyric.h"
+
+#include "honeywellThermostat.h"
+#include "../honeywellHelpers.h"
+#include <pthread.h> // pthread_mutex_t
+
+using namespace std;
+
+///
+/// @brief This class encapsulates basic Honeywell functionality and
+/// the ability to enumerate and retrieve instances of Honeywell
+/// devices.
+///
+class Honeywell
+{
+public:
+///
+/// @brief Holds an access token.
+///
+#pragma pack(push, 1)
+    typedef struct _ACCESS_TOKEN
+    {
+        char accessToken[HONEYWELL_ACCESS_TOKEN_BUFSIZE]; // the access token used
+        char refreshToken[HONEYWELL_REFRESH_TOKEN_BUFSIZE];
+        // in all REST calls
+        char expires[HONEYWELL_ACCESS_TOKEN_EXPIRY];  // UTC time when token expires
+        time_t acquiredTime;                          // the time the token was aquired
+        // (Epoch time-January,1,1970)
+        uint32_t grantTime;                           // the time the token is valid for
+        // (seconds)
+        int userId;                                   // ID issued by honeywell after token exchange
+    } ACCESS_TOKEN;
+#pragma pack(pop)
+
+#pragma pack(push, 1)
+    typedef struct _CLIENT_ID_SECRET
+    {
+        char honeywell_clientId[HONEYWELL_CLIENT_ID_BUFFSIZE];
+        char honeywell_client_secret[HONEYWELL_CLIENT_AND_SECRET_64_BUFFSIZE];
+    } CLIENT_ID_SECRET;
+#pragma pack(pop)
+    typedef std::vector<std::string> curlHeadersV;
+
+    /// Initialize object with valid access token.
+    Honeywell(const ACCESS_TOKEN &accessToken, const CLIENT_ID_SECRET &clientIdSecret);
+
+    /// Basic object initialization.
+    Honeywell();
+
+    virtual ~Honeywell();
+
+    ///
+    /// Attempts to obtain an access token given the authorization code. The application
+    /// should have retrieved the authorization code OOB by following the OAuth flow
+    /// after loading the URI returned from getAuthoriizationUrl() method.
+    ///
+    /// @param authorizationCode is a reference to a code retrieved from
+    ///
+    /// @param accessCode is a reference to an ACCESS_TOKEN structure returned
+    /// upon success.
+    ///
+    /// OAuth 2.0 authorization with the Honeywell API backend.
+    ///
+    virtual int getAccessToken(std::string &authorizationCode, ACCESS_TOKEN &accessToken) = 0;
+
+    /// deauthrorize a token
+    int deauthorizateToken();
+
+    ///
+    /// Sets a previously acquired access token structure to the Honeywell client.
+    ///
+    /// @param token is a reference to a valid access token.
+    ///
+    void setAccessToken(const ACCESS_TOKEN &token);
+
+    ///
+    /// Sets the Client Id and Client secret of the user read from the .cnf file.
+    ///
+    /// @param token is a reference to a valid client id and client secret.
+    ///
+    void setClientIdAndSecret(const CLIENT_ID_SECRET &clientIdAndSecret);
+
+    ///
+    /// Returns the authorization state of the client.
+    ///
+    /// @returns true if the client is authorized to use the devices, otherwise false.
+    ///
+    /// @note use the getAuthorizationUrl to get the required authorizationURL to get
+    /// user approval and an authorizationCode that can be used to exchange for a long
+    /// term access token.
+    ///
+    virtual bool isAuthorized() = 0;
+
+    ///
+    /// Returns the device list of thermostats.
+    ///
+    /// @return a SMART_DEV_OK on success, or another SMART_DEV_ on error. On
+    /// success the devices vector will contain the authorized/found thermostats.
+    ///
+    virtual int getThermostats(std::vector<LyricThermostatSharedPtr> &thermostats) = 0;
+
+    /// Safely logs a string to OIC_LOG output.
+    ///
+    /// @param stringData - (input) String buffer to print.
+    /// @param filename - (input optional) If non-null, specifies an output file to store the passed string.
+    ///        File output is not truncated like OIC_LOG output is. Use this to capture JSON responses from cloud.
+    void dumpResponseString(const char *stringData, const char *fileName);
+
+    /// Sets temperature of selected honeywell thermostat.
+    ///
+    /// @param - Vector of honeywell thermostat devices.
+    /// @data - (input) Contains target temperature data to set.
+    /// @uri - (input optional) Resource URI of thermostat to target. (If not specified, first discovered
+    ///        device is used.)
+    ///
+    /// @return MPM_RESULT_OK on success, error otherwise.
+    virtual MPMResult setTemperature(LyricThermostatSharedPtr thermostat, const THERMOSTAT data,
+                                     const std::string uri) = 0;
+
+    /// Returns index of current "preferred" device. (In cases where we force a specific device to be used
+    /// in tests or demos.
+    ///
+    /// @return index of preferred device in device array.
+    int getPreferredDeviceIndex()
+    { return m_preferredDeviceIndex; }
+
+    /// Returns status of the mutex that protects the list of discovered devices.
+    bool isDeviceChangeMutexInitialized()
+    { return m_deviceChangeMutexInitialized; }
+
+protected:
+
+    /// Locks mutex that protects cloud-based activity.
+    ///
+    /// @return - true on success, false on failure.
+    bool lockCloudAccess();
+
+    /// Unlocks mutex that protects cloud-based activity.
+    ///
+    /// @return - true on success, false on failure.
+    bool unlockCloudAccess();
+
+    ACCESS_TOKEN m_accessToken; /// access token of current user
+    CLIENT_ID_SECRET m_clientIdAndSecret;
+    bool m_isAuthorized; /// whether user is authorized to access honeywell devices
+    int m_preferredDeviceIndex; /// cloud array index of preferred device
+    pthread_mutex_t m_cloudAccessMutex; /// mutex that serializes honeywell cloud transactions
+    bool m_accessTokenMutexInitialized; // status of mutex that protects the current access token
+    MPMResult manageMutexes(bool initialize);
+
+    void initializeAccessToken();
+    void initializeClientIdSecret();
+    bool m_deviceChangeMutexInitialized; // status of mutex that protects device array
+    bool m_getInProgress; // value indicating whether a GET operation is underway
+};
+
+#endif /* __HONEYWELL_H__ */
diff --git a/bridging/plugins/lyric_plugin/honeywell_objects/honeywellDefs.h b/bridging/plugins/lyric_plugin/honeywell_objects/honeywellDefs.h
new file mode 100644 (file)
index 0000000..041acfc
--- /dev/null
@@ -0,0 +1,90 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+#ifndef __HONEYWELLDEFS_H__
+#define __HONEYWELLDEFS_H__
+
+#include <string>
+
+// *** THIS CONTAINS CONSTANTS COMMON TO ALL HONEYWELL PLUGINS ***
+
+// various constants
+#define MESSAGE_STRING_SIZE         1024
+#define MAX_LOG_STRING              768
+#define HONEYWELL_HEAT_SETPOINT     "heatSetpoint"
+#define HONEYWELL_COOL_SETPOINT     "coolSetpoint"
+#define HONEYWELL_VALUE             "value"
+#define HONEYWELL_SETPOINT_BUFFER   2
+#define HONEYWELL_THERMOSTAT_ACTUATOR_IF     "oic.if.a"
+#define HONEYWELL_THERMOSTAT_SENSOR_IF       "oic.if.s"
+#define HONEYWELL_THERMOSTAT_BASELINE_IF     "oic.if.baseline"
+#define HONEYWELL_THERMOSTAT_RT     "oic.r.temperature"
+#define INVALID_DEVICE_INDEX        (-1)
+#define DEVICE_INDEX_START          1
+#define FIRST_ARRAY_ELEMENT         0
+#define HW_MANUFACTURER_NAME        "Honeywell"
+#define HW_AUTH_LOOP_MINUTES        9
+#define HW_QUERY_INTERVAL_SECONDS   60
+#define HONEYWELL_TEMP_SCALE_C      "C"
+#define HONEYWELL_TEMP_SCALE_F      "F"
+
+// representation value names
+#define REP_NAME_TEMPERATURE        "temperature"
+#define REP_NAME_MODE               "x.intel.com.mode"
+#define REP_NAME_TARGET_TEMP_HIGH   "x.intel.com.targetTempHigh"
+#define REP_NAME_TARGET_TEMP_LOW    "x.intel.com.targetTempLow"
+#define REP_NAME_INDOOR_TEMP        "x.intel.com.indoorTemp"
+
+// representation values
+#define REP_VALUE_COOL              "cool"
+#define REP_VALUE_HEAT              "heat"
+#define REP_VALUE_OFF               "off"
+
+// HONEYWELL THERMOSTAT MODES
+// See ThermostatMode enumeration documentation at TCC Web API Help site (more modes there)
+#define THERMOSTAT_MODE_FORMAT         "\"%s\""
+#define THERMOSTAT_MODE_OFF            "Off"
+#define THERMOSTAT_MODE_COOL           "Cool"
+#define THERMOSTAT_MODE_HEAT           "Heat"
+
+///
+/// json tag definitions
+///
+#define JSON_REFRESH_TOKEN              "refresh_token"
+#define JSON_ACCESS_TOKEN               "access_token"
+#define JSON_ERROR                      "error"
+#define JSON_USER_ID                    "userID"
+#define JSON_DEVICE_ID                  "deviceID"
+#define JSON_UNIQUE_ID                  "macID"
+#define JSON_ACCESS_CONFIRMED           "accessConfirmed"
+#define JSON_MODE                       "mode"
+#define JSON_EXPIRES_IN                 "expires_in"
+#define JSON_DEVICE_NAME                "deviceName"
+#define JSON_RESPONSE_CODE              "code"
+#define JSON_RESPONSE_MESSAGE           "message"
+#define JSON_RESPONSE_UNAUTHORIZED      "unauthorized"
+#define JSON_LOCATION_ID                "locationID"
+#define JSON_USERS_ARRAY                "users"
+#define JSON_DEVICES_ARRAY              "devices"
+#define JSON_THERMOSTAT                 "thermostat"
+#define JSON_CHANGEABLE_VALUES          "changeableValues"
+
+#endif /* __HONEYWELLDEFS_H__ */
diff --git a/bridging/plugins/lyric_plugin/honeywell_objects/honeywellDefsLyric.h b/bridging/plugins/lyric_plugin/honeywell_objects/honeywellDefsLyric.h
new file mode 100644 (file)
index 0000000..82a8c19
--- /dev/null
@@ -0,0 +1,89 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+#ifndef __HONEYWELLDEFSLYRIC_H__
+#define __HONEYWELLDEFSLYRIC_H__
+
+#include "honeywellDefs.h"
+
+// various constants
+#define HONEYWELL_REFRESH_TOKEN_LENGTH  32
+#define HONEYWELL_ACCESS_TOKEN_LENGTH   28
+#define HONEYWELL_TOKEN_FILE            "lyricToken.json"
+#define HONEYWELL_TOKEN_BACKUP          "lyricToken.json.bak"
+#define HW_PRODUCT_NAME                 "Lyric Thermostat"
+#define HW_AUTH_SERVER_INFO             "Honeywell Lyric Server"
+#define HONEYWELL_ACCESS_TOKEN_BUFSIZE  (HONEYWELL_ACCESS_TOKEN_LENGTH + 1)
+#define HONEYWELL_REFRESH_TOKEN_BUFSIZE (HONEYWELL_REFRESH_TOKEN_LENGTH + 1)
+#define HONEYWELL_ACCESS_TOKEN_EXPIRY   599 // assuming this is seconds?
+#define HONEYWELL_BASE_URL "https://api.honeywell.com"
+#define HONEYWELL_CLIENT_ID_LENGTH      32
+#define HONEYWELL_CLIENT_ID_BUFFSIZE    (HONEYWELL_CLIENT_ID_LENGTH + 1)
+#define HONEYWELL_CLIENT_AND_SECRET_64_LENGTH 68
+#define HONEYWELL_CLIENT_AND_SECRET_64_BUFFSIZE (HONEYWELL_CLIENT_AND_SECRET_64_LENGTH + 1)
+
+// Example: "https://api.honeywell.com/oauth2/authorize?client_id=0pKks3fVy8JqwsfTPW3TdWNwypdGpmIq&
+//           response_type=code&redirect_uri=https://therm-api-wrapper.herokuapp.com/intelLyric"
+// Format with AUTHORIZATION_FORMAT, AUTHORIZATION_URL, HONEYWELL_CLIENT_ID, HONEYWELL_REDIRECT_URI
+#define AUTH_FORMAT_SIZE 512
+#define AUTHORIZATION_FORMAT "%s?client_id=%s&response_type=code&redirect_uri=%s"
+#define AUTHORIZATION_URL (HONEYWELL_BASE_URL "/oauth2/authorize")
+#define HONEYWELL_REDIRECT_URI "https://therm-api-wrapper.herokuapp.com/intelLyric"
+
+// REFRESHING AN ACCESS TOKEN
+// POST TO URL: ACCESS_TOKEN_URL
+// HEADER: AUTH_HEADER_FORMAT + HONEYWELL_CLIENT_AND_SECRET_64
+// BODY: AUTH_REFRESH_BODY_FORMAT + current refresh token
+#define ACCESS_TOKEN_URL (HONEYWELL_BASE_URL "/oauth2/token")
+#define AUTH_HEADER_FORMAT "Authorization: Basic %s"
+#define AUTH_REFRESH_BODY_FORMAT    "grant_type=refresh_token&refresh_token=%s"
+// below is for exchanging auth code instead of refresh token
+#define AUTH_AUTHCODE_BODY_FORMAT   "grant_type=authorization_code&code=%d&redirect_uri=%s"
+
+// REQUESTING ACCOUNT INFO
+// GET URL: ACCOUNT_INFO_URL
+// HEADER: ACCT_HEADER_FORMAT + current access token (from access token response)
+// BODY: N/A
+#define ACCOUNT_INFO_URL (HONEYWELL_BASE_URL "/v1/locations")
+#define ACCT_HEADER_FORMAT "Authorization: Bearer %s"
+#define ACCOUNT_INFO_FORMAT "%s?apikey=%s"
+
+// CONFIRMING DEVICE ACCESS
+// PUT URL: ACCESS_LIST_FORMAT + ACCESS_LIST_URL + userId
+// HEADERS: ACCT_HEADER_FORMAT, CONTENT_TYPE_JSON
+// BODY: JSON array with deviceId and accessConfirmed fields in element.
+#define CONTENT_TYPE_JSON "Content-Type: application/json"
+#define DEVICE_ACCESS_SUCCESS 204
+
+// GETTING DEVICE DETAILS
+// GET URL: CHANGEABLE_VALUES_FORMAT + deviceId + HONEYWELL_CLIENT_ID + locationId
+// HEADERS: ACCT_HEADER_FORMAT, CONTENT_TYPE_JSON
+// BODY: N/A
+#define CHANGEABLE_VALUES_FORMAT \
+    (HONEYWELL_BASE_URL "/v1/devices/thermostats/%s?apikey=%s&locationId=%i")
+
+// GETTING THERMOSTAT CURRENT STATE/STATUS
+#define JSON_INDOOR_TEMPERATURE "indoorTemperature"
+
+// CHANGING THERMOSTAT MODE
+#define CHANGEABLE_VALUES_PUT_SUCCESS   200
+
+#endif /* __HONEYWELLDEFSLYRIC_H__ */
diff --git a/bridging/plugins/lyric_plugin/honeywell_objects/honeywellLyric.cpp b/bridging/plugins/lyric_plugin/honeywell_objects/honeywellLyric.cpp
new file mode 100644 (file)
index 0000000..3c9d1dd
--- /dev/null
@@ -0,0 +1,575 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+#include <time.h>
+#include <stdio.h>
+#include <iostream>
+#include <string>
+#include "honeywellDefsLyric.h"
+#include "honeywellLyric.h"
+#include "curlClient.h"
+#include "rapidjson.h"
+#include "document.h"
+#include "stringbuffer.h"
+#include "writer.h"
+#include "logger.h"
+#include <math.h>  // for fmin
+#include <sstream> // for stringstream
+#include <map>
+#include <algorithm> // std::transform
+#include "oic_string.h"
+#include <fstream>      // std::ifstream
+
+// *** THIS IS THE LYRIC VERSION OF THE FILE ***
+
+using namespace rapidjson;
+using namespace OC::Bridging;
+
+#define LOG_TAG "HONEYWELL_LYRIC"
+
+bool HoneywellLyric::isAuthorized()
+{
+    bool result = false;
+
+    // NOTE: Unlike with Nest, Honeywell authorization is complete by the time
+    //       anyone calls this function. In this plugin, you are "authorized"
+    //       when you have received a valid access token and user ID from
+    //       Honeywell's TCC server, and are recognized as a valid user account
+    //       by Honeywell. It DOESN'T mean that you necessarily have access to
+    //       any devices. This is determined later in the getThermostats function.
+
+    // do some extra checks to make sure all values are set.
+    if (m_isAuthorized)
+    {
+        if (0 == strlen(m_accessToken.accessToken))
+        {
+            OIC_LOG(ERROR, LOG_TAG, "m_isAuthorized is true but accessToken is empty!");
+        }
+        else
+        {
+            OIC_LOG(INFO, LOG_TAG, "Valid user is authorized to access Honeywell account.");
+            result = true;
+        }
+    }
+    return result;
+}
+
+// passed authorizationCode is expected to be a 32-character refresh_token string.
+// passed accessToken is an empty ACCESS_TOKEN struct. Struct will be populated on
+// success and also stored in member m_accessToken.
+int HoneywellLyric::getAccessToken(std::string &authorizationCode, ACCESS_TOKEN &accessToken)
+{
+    OIC_LOG_V(INFO, LOG_TAG, "Honeywell::getAccessToken (Lyric) - Entered...");
+    int result = MPM_RESULT_INTERNAL_ERROR;
+    char messageHeader[MESSAGE_STRING_SIZE];
+    char messageBody[MESSAGE_STRING_SIZE];
+    std::string accessTokenLocal;
+    std::string errorMessage;
+    std::string refreshTokenFile = GetTokenPath(HONEYWELL_TOKEN_FILE);
+    std::string oldRefreshTokenFile = GetTokenPath(HONEYWELL_TOKEN_BACKUP);
+    std::string expiresIn; // access token ttl in seconds (string for lyric)
+    std::string curlResponse;
+    std::string fileContents; // contents of loaded token file
+    std::vector<std::string> outHeaders;
+    CurlClient cc = CurlClient(CurlClient::CurlMethod::POST, ACCESS_TOKEN_URL);
+    std::string msgBody;
+    // lock mutex
+    bool mutexLocked = lockCloudAccess();
+    rapidjson::Document accessTokenJsonResponse;
+
+    std::stringstream expiresStream;
+    int expiresInt = 0;
+    std::ifstream fileExists (refreshTokenFile.c_str());
+
+    if(mutexLocked == false)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Cloud is already locked by other get request in progress!");
+        result = MPM_RESULT_ALREADY_STARTED;
+        return result;
+    }
+
+    // form url for initial POST:
+    // REFRESHING AN ACCESS TOKEN
+    // POST TO URL: ACCESS_TOKEN_URL
+    // HEADER: AUTH_HEADER_FORMAT + HONEYWELL_CLIENT_AND_SECRET_64
+    sprintf(messageHeader, AUTH_HEADER_FORMAT, m_clientIdAndSecret.honeywell_client_secret);
+
+    if (strlen(messageHeader) >= MAX_LOG_STRING)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "messageHeader is too long to display (%lu)", (unsigned long) strlen(messageHeader));
+    }
+
+    // check for expected refresh token length
+    if (HONEYWELL_REFRESH_TOKEN_LENGTH != authorizationCode.length())
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "authorizationCode length: %lu", (unsigned long) authorizationCode.length());
+        if (strncmp(m_accessToken.refreshToken, "", HONEYWELL_REFRESH_TOKEN_LENGTH) != 0)
+        {
+            authorizationCode = m_accessToken.refreshToken;
+            OIC_LOG_V(INFO, LOG_TAG, "authorizationCode = %s", authorizationCode.c_str());
+        }
+        else if (0 == authorizationCode.length())
+        {
+            OIC_LOG(INFO, LOG_TAG, "Attempting to load refresh_token from file...");
+
+            // no code passed; load the code from the stored file, if available
+            result = LoadFileIntoString(refreshTokenFile.c_str(), fileContents);
+            if (MPM_RESULT_OK != result)
+            {
+                OIC_LOG_V(ERROR, LOG_TAG,
+                      "LoadFileIntoString failed with %d attempting to open %s",
+                      result,
+                      refreshTokenFile.c_str());
+                goto CLEANUP;
+            }
+
+            // get the refresh code from the file:
+            rapidjson:: Document fileValues;
+            fileValues.SetObject();
+            if (fileValues.Parse(fileContents.c_str()).HasParseError())
+            {
+                OIC_LOG(ERROR, LOG_TAG,"Parse error in fileContents");
+                result = MPM_RESULT_JSON_ERROR;
+                goto CLEANUP;
+            }
+            if (fileValues.HasMember(JSON_REFRESH_TOKEN))
+            {
+                authorizationCode = fileValues[JSON_REFRESH_TOKEN].GetString();
+                result = MPM_RESULT_OK;
+            }
+            else
+            {
+                OIC_LOG_V(ERROR, LOG_TAG, "GetJsonString failed attempting to get %s", JSON_REFRESH_TOKEN);
+                result = MPM_RESULT_JSON_ERROR;
+                goto CLEANUP;
+            }
+        }
+    }
+
+    // BODY: AUTH_REFRESH_BODY_FORMAT + current refresh token
+    sprintf(messageBody, AUTH_REFRESH_BODY_FORMAT, authorizationCode.c_str());
+    if (strlen(messageBody) >= MAX_LOG_STRING)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "messageBody is too long to display (%lu)", (unsigned long) strlen(messageBody));
+    }
+
+    // don't have an access token yet, use default headers
+    msgBody.assign(messageBody);
+    cc = CurlClient(CurlClient::CurlMethod::POST, ACCESS_TOKEN_URL)
+                   .addRequestHeader(messageHeader)
+                   .setUserName(authorizationCode)
+                   .setRequestBody(msgBody);
+
+    result = (MPMResult) cc.send();
+
+    if (MPM_RESULT_OK != result)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "doPostRequest failed with %lu", (unsigned long) result);
+        goto CLEANUP;
+    }
+
+    curlResponse = cc.getResponseBody();
+
+    // see if we got an access token back. print error info if honeywell server returned an error.
+    dumpResponseString(curlResponse.c_str(), "postAccessTokenResponse.json");
+    accessTokenJsonResponse.SetObject();
+
+    if (accessTokenJsonResponse.Parse(curlResponse.c_str()).HasParseError())
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Parse error");
+        result = MPM_RESULT_JSON_ERROR;
+        goto CLEANUP;
+    }
+
+    if (accessTokenJsonResponse.HasMember("fault"))
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "Curl Response is error string = \n %s", curlResponse.c_str());
+        result = MPM_RESULT_JSON_ERROR;
+        goto CLEANUP;
+    }
+
+    if (accessTokenJsonResponse.HasMember(JSON_ACCESS_TOKEN))
+    {
+        accessTokenLocal = accessTokenJsonResponse[JSON_ACCESS_TOKEN].GetString();
+    }
+
+    if (accessTokenJsonResponse.HasMember(JSON_EXPIRES_IN))
+    {
+        expiresIn = accessTokenJsonResponse[JSON_EXPIRES_IN].GetString();
+        OIC_LOG_V(INFO, LOG_TAG, "expires_in: %s seconds (string)", expiresIn.c_str());
+        expiresStream.str(expiresIn);
+        expiresStream >> expiresInt;
+        accessToken.grantTime = (uint32_t) expiresInt;
+        OIC_LOG_V(INFO, LOG_TAG, "expires_in: %d seconds (int)", accessToken.grantTime);
+    }
+    else
+    {
+        accessToken.grantTime = (uint32_t) HONEYWELL_ACCESS_TOKEN_EXPIRY;
+    }
+
+    // we got an access token, so first back up the old token file (if exists) to get ready for new one
+    if (fileExists)
+    {
+        result = CopyFile(refreshTokenFile.c_str(), oldRefreshTokenFile.c_str(), false);
+        if (MPM_RESULT_OK != result)
+        {
+            OIC_LOG_V(ERROR, LOG_TAG, "Failed to copy %s to %s", refreshTokenFile.c_str(), oldRefreshTokenFile.c_str());
+            goto CLEANUP;
+        }
+    }
+
+    // store token in accessToken param
+    OIC_LOG_V(INFO, LOG_TAG, "Access token length: %lu chars", (unsigned long) accessTokenLocal.length());
+    OICStrcpy(accessToken.accessToken, HONEYWELL_ACCESS_TOKEN_BUFSIZE, accessTokenLocal.c_str());
+    OICStrcpy(accessToken.refreshToken, HONEYWELL_REFRESH_TOKEN_BUFSIZE, authorizationCode.c_str());
+
+    // next save the new tokens in the old file's place
+    result = SaveStringIntoFile(curlResponse.c_str(), refreshTokenFile.c_str());
+    if (MPM_RESULT_OK != result)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "Failed to save tokens into %s", refreshTokenFile.c_str());
+        goto CLEANUP;
+    }
+
+    // if we're here we're successful. store token info and return successfully.
+    m_accessToken = accessToken;
+    m_isAuthorized = true;
+    result = MPM_RESULT_OK;
+
+    CLEANUP:
+
+    // unlock mutex
+    if (mutexLocked)
+    {
+        unlockCloudAccess();
+    }
+
+    return result;
+}
+
+MPMResult HoneywellLyric::setTemperature(LyricThermostatSharedPtr thermostat, const THERMOSTAT data,
+                                         const std::string uri)
+{
+    MPMResult result = MPM_RESULT_OK;
+    std::string newThermostatMode; // new mode to choose
+    char messageUrl[MESSAGE_STRING_SIZE];
+    char messageHeader[MESSAGE_STRING_SIZE];
+    THERMOSTAT devicesThermostat;
+    std::string mode;
+    std::string curlResponse;
+    long lastResponse = 0;
+    bool changeMode = false;
+    // lock mutex
+    bool mutexLocked = lockCloudAccess();
+    std::string changeableValues;
+    rapidjson::StringBuffer sb;
+    rapidjson::Writer<rapidjson::StringBuffer> writer( sb );
+
+    if (uri.length() <= 0)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "uri is NULL");
+    }
+
+    OIC_LOG_V(INFO, LOG_TAG, "uri = %s", uri.c_str());
+    // format uri with device ID and required setpoints
+    // PUT URL: CHANGEABLE_VALUES_FORMAT + deviceId
+    // HEADERS: ACCT_HEADER_FORMAT, CONTENT_TYPE_JSON
+    // TODO - Come up with a better way of handling the device ID- necessary for multi thermostat support.
+    thermostat->get(devicesThermostat);
+
+    // DEBUG: Dump device info data
+    dump_details(data, "data"); // caller data (cool and heat setpoint only)
+    dump_details(devicesThermostat, "devicesThermostat"); // device data
+
+    sprintf(messageUrl,
+            CHANGEABLE_VALUES_FORMAT,
+            devicesThermostat.devInfo.deviceIdStr.c_str(),
+            m_clientIdAndSecret.honeywell_clientId,
+            devicesThermostat.devInfo.locationId);
+    OIC_LOG_V(INFO, LOG_TAG, "changeableValues messageUrl: %s", messageUrl);
+
+    snprintf(messageHeader, MESSAGE_STRING_SIZE, ACCT_HEADER_FORMAT, m_accessToken.accessToken);
+
+    CurlClient cc = CurlClient(CurlClient::CurlMethod::POST, messageUrl);
+
+    // change the thermostat mode if indoor temp exceeds thresholds. otherwise, leave mode alone.
+    if (devicesThermostat.ambientTempF > data.coolSetpointF)
+    {
+        mode.assign(THERMOSTAT_MODE_COOL);
+        changeMode = true;
+    }
+    else if (devicesThermostat.ambientTempF < data.heatSetpointF)
+    {
+        mode.assign(THERMOSTAT_MODE_HEAT);
+        changeMode = true;
+    }
+
+    changeableValues = thermostat->getChangeableValues();
+
+    rapidjson:: Document values;
+    values.SetObject();
+    if (values.Parse(changeableValues.c_str()).HasParseError())
+    {
+        OIC_LOG(ERROR, LOG_TAG,"Parse error in set temperature");
+        result = MPM_RESULT_JSON_ERROR;
+        goto CLEANUP;
+    }
+
+    if (changeMode)
+    {
+        if(values.HasMember(JSON_MODE))
+        {
+            values[JSON_MODE].SetString(mode.c_str(), mode.length());
+        }
+    }
+
+    if(values.HasMember(HONEYWELL_HEAT_SETPOINT))
+    {
+       values[HONEYWELL_HEAT_SETPOINT].SetDouble(data.heatSetpointF);
+    }
+
+    if(values.HasMember(HONEYWELL_COOL_SETPOINT))
+    {
+       values[HONEYWELL_COOL_SETPOINT].SetDouble(data.coolSetpointF);
+    }
+
+    values.Accept( writer );
+    changeableValues = sb.GetString();
+    OIC_LOG_V(INFO, LOG_TAG, "New Value = %s", changeableValues.c_str());
+
+    cc = CurlClient(CurlClient::CurlMethod::POST, messageUrl)
+                   .addRequestHeader(messageHeader)
+                   .addRequestHeader(CONTENT_TYPE_JSON)
+                   .setUserName(m_accessToken.accessToken)
+                   .setRequestBody(changeableValues);
+
+    result = (MPMResult) cc.send();
+    if (MPM_RESULT_OK != result)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "Post Request (account info) failed with %lu", (unsigned long) result);
+        result = MPM_RESULT_INTERNAL_ERROR;
+        goto CLEANUP;
+    }
+
+    curlResponse = cc.getResponseBody();
+
+    // check result
+    lastResponse = cc.getLastResponseCode();
+    if (lastResponse != CHANGEABLE_VALUES_PUT_SUCCESS)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG,
+              "Response (%d) didn't match expected response (%d).",
+              (int) lastResponse,
+              (int) CHANGEABLE_VALUES_PUT_SUCCESS);
+        goto CLEANUP;
+    }
+    else
+    {
+        OIC_LOG(INFO, LOG_TAG, "changeableValues change succeeded.");
+    }
+
+    // update current device item
+    thermostat->setTemperature(data);
+    thermostat->setChangeableValues(changeableValues);
+
+    CLEANUP:
+    // unlock mutex
+    if (mutexLocked)
+    {
+        unlockCloudAccess();
+    }
+
+    return result;
+}
+
+int HoneywellLyric::getThermostats(std::vector<LyricThermostatSharedPtr> &thermostats)
+{
+    OIC_LOG(DEBUG, LOG_TAG, "Entered getThermostats.");
+
+    int result = MPM_RESULT_OK;
+    std::string accessConfirmed; // json blob indicating which devices to confirm
+    std::string thermostatMode;  // current thermostat mode
+    THERMOSTAT thermostatData;
+    std::string curlResponse;
+    char formattedUrl[128];
+
+    // get list of authorized user locations (ACCOUNT_INFO_URL = locations)
+    // Unlike Honeywell TCC, this command seems to require the auth and json header,
+    // and additionally the API key needs to be included in the query.
+    sprintf(formattedUrl, ACCOUNT_INFO_FORMAT, ACCOUNT_INFO_URL, m_clientIdAndSecret.honeywell_clientId);
+
+    CurlClient cc = CurlClient(CurlClient::CurlMethod::GET, formattedUrl);
+    std::string thermostatElement; // thermostat element from device data
+    char messageHeader[MESSAGE_STRING_SIZE];
+    rapidjson::Document thermostatsJsonResponse;
+    int i = 0;
+
+    // NOTE: Can't do memsets or memcpys with THERMOSTAT struct because it contains
+    //       objects. Doing those things will result in corrupt objects. Added a
+    //       constructor that initializes itself instead.
+    HoneywellThermostat honeywellThermostat;
+
+    // lock cloud mutex
+    bool cloudMutexLocked = lockCloudAccess();
+
+    if(cloudMutexLocked == false)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Cloud is already locked by other get Request in progress!");
+        return MPM_RESULT_ALREADY_STARTED;
+    }
+
+    if (!strlen(m_accessToken.accessToken))
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Not authorized");
+        result = MPM_RESULT_NOT_AUTHORIZED;
+        goto CLEANUP;
+    }
+
+    if (m_getInProgress)
+    {
+        OIC_LOG(ERROR, LOG_TAG, "There is already a getThermostats in progress!");
+        result = MPM_RESULT_ALREADY_STARTED;
+        goto CLEANUP;
+    }
+
+    // block other attempts while one is already in progress
+    m_getInProgress = true;
+
+    snprintf(messageHeader, MESSAGE_STRING_SIZE, ACCT_HEADER_FORMAT, m_accessToken.accessToken);
+    if (strlen(messageHeader) >= MAX_LOG_STRING)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "messageHeader is too long to display (%lu)", (unsigned long) strlen(messageHeader));
+    }
+    cc = CurlClient(CurlClient::CurlMethod::GET, formattedUrl)
+                   .addRequestHeader(messageHeader)
+                   .setUserName(m_accessToken.accessToken);
+
+    result = (MPMResult) cc.send();
+    if (MPM_RESULT_OK != result)
+    {
+        OIC_LOG_V(ERROR, LOG_TAG, "doGetRequest (account info) failed with %lu", (unsigned long) result);
+        goto CLEANUP;
+    }
+
+    curlResponse = cc.getResponseBody();
+    dumpResponseString(curlResponse.c_str(), "getAccountInfoResponse.json");
+
+    thermostatsJsonResponse.SetObject();
+
+    if (thermostatsJsonResponse.Parse<0>(curlResponse.c_str()).HasParseError())
+    {
+        OIC_LOG_V(ERROR, LOG_TAG,"Error parsing JSON:\n%s", curlResponse.c_str());
+        result = MPM_RESULT_JSON_ERROR;
+        goto CLEANUP;
+    }
+
+    if (!thermostatsJsonResponse.IsArray())
+    {
+        OIC_LOG_V(ERROR, LOG_TAG,"Response is not an array.\n%s", curlResponse.c_str());
+        result = MPM_RESULT_JSON_ERROR;
+        goto CLEANUP;
+    }
+    if (thermostatsJsonResponse.Empty())
+    {
+        OIC_LOG(ERROR, LOG_TAG, "Response is empty");
+        result = MPM_RESULT_JSON_ERROR;
+        goto CLEANUP;
+    }
+
+    for (rapidjson::Value::ConstValueIterator itr = thermostatsJsonResponse.Begin(); itr != thermostatsJsonResponse.End(); ++itr)
+    {
+        if (thermostatsJsonResponse[i].IsObject())
+        {
+            thermostatData.devInfo.locationId = thermostatsJsonResponse[i][JSON_LOCATION_ID].GetInt();
+
+            OIC_LOG_V(DEBUG, LOG_TAG, "locationId = %d", thermostatData.devInfo.locationId);
+            if (thermostatsJsonResponse[i].HasMember(JSON_DEVICES_ARRAY))
+            {
+                const rapidjson::Value& devices = thermostatsJsonResponse[i][JSON_DEVICES_ARRAY];
+
+                for (rapidjson::SizeType itr = 0; itr < devices.Size(); itr++)
+                {
+                    if (devices[itr].HasMember(JSON_DEVICE_ID))
+                    {
+                        thermostatData.devInfo.deviceIdStr = devices[itr][JSON_DEVICE_ID].GetString();
+                    }
+                    if (devices[itr].HasMember(JSON_UNIQUE_ID))
+                    {
+                        // --- get and store macID (string)
+                        thermostatData.devInfo.uniqueId = devices[itr][JSON_UNIQUE_ID].GetString();
+                        honeywellThermostat.setDeviceUniqueId(thermostatData.devInfo.uniqueId.c_str());
+                    }
+                    if (devices[itr].HasMember(JSON_THERMOSTAT))
+                    {
+                        const rapidjson::Value& thrmS = devices[itr][JSON_THERMOSTAT];
+                        thermostatData.ambientTempF =  thrmS[JSON_INDOOR_TEMPERATURE].GetDouble();
+                        if (thrmS.HasMember(JSON_CHANGEABLE_VALUES))
+                        {
+                            const rapidjson::Value& changeValue = thrmS[JSON_CHANGEABLE_VALUES];
+                            rapidjson::StringBuffer sb;
+                            rapidjson::Writer<rapidjson::StringBuffer> writer( sb );
+                            thrmS[JSON_CHANGEABLE_VALUES].Accept( writer );
+                            std::string changeableValues = sb.GetString();
+                            honeywellThermostat.setChangeableValues(changeableValues);
+                            thermostatMode = changeValue[JSON_MODE].GetString();
+                            thermostatData.heatSetpointF = changeValue[HONEYWELL_HEAT_SETPOINT].GetDouble();
+                            thermostatData.coolSetpointF = changeValue[HONEYWELL_COOL_SETPOINT].GetDouble();
+                            if (0 == strcasecmp(thermostatMode.c_str(), THERMOSTAT_MODE_COOL))
+                            {
+                                thermostatData.hvacMode = HVAC_COOL;
+                            }
+                            else if (0 == strcasecmp(thermostatMode.c_str(), THERMOSTAT_MODE_HEAT))
+                            {
+                                thermostatData.hvacMode = HVAC_HEAT;
+                            }
+                            else
+                            {
+                                thermostatData.hvacMode = HVAC_OFF;
+                            }
+
+                           // always compute target temp based on hot and cool threshold
+                           thermostatData.targetTempF =
+                                          computeTargetTemp(thermostatData.heatSetpointF, thermostatData.coolSetpointF);
+                           dump_details(thermostatData, "thermostatData");
+                           honeywellThermostat.set(thermostatData);
+
+                           std::shared_ptr<HoneywellThermostat> thermostat = std::make_shared<HoneywellThermostat>(honeywellThermostat);
+                           thermostats.push_back(thermostat);
+                        }
+                    }
+                }
+            }
+        }
+        i++;
+    }
+
+    CLEANUP:
+
+    m_getInProgress = false;
+
+    if (cloudMutexLocked)
+    {
+        unlockCloudAccess();
+    }
+
+    OIC_LOG_V(INFO, LOG_TAG, "Exiting getThermostats with code %d", result);
+    return result;
+}
diff --git a/bridging/plugins/lyric_plugin/honeywell_objects/honeywellLyric.h b/bridging/plugins/lyric_plugin/honeywell_objects/honeywellLyric.h
new file mode 100644 (file)
index 0000000..f7bdf3d
--- /dev/null
@@ -0,0 +1,81 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+#ifndef __HONEYWELLLYRIC_H__
+#define __HONEYWELLLYRIC_H__
+
+#include "honeywell.h"
+#include <vector>
+
+using namespace std;
+
+///
+/// @brief This class encapsulates basic Honeywell functionality and
+/// the ability to enumerate and retrieve instances of Honeywell
+/// devices.
+///
+class HoneywellLyric : public Honeywell
+{
+public:
+    ///
+    /// Attempts to obtain an access token given the authorization code.
+    ///
+    /// @param authorizationCode is a reference to a code retrieved from
+    ///
+    /// @param accessCode is a reference to an ACCESS_TOKEN structure returned
+    /// upon success.
+    ///
+    /// OAuth 2.0 authorization with the Honeywell API backend.
+    ///
+    virtual int getAccessToken(std::string &authorizationCode, ACCESS_TOKEN &accessToken);
+
+    ///
+    /// Returns the authorization state of the client.
+    ///
+    /// @returns true if the client is authorized to use the devices, otherwise false.
+    ///
+    /// @note use the getAuthorizationUrl to get the required authorizationURL to get
+    /// user approval and an authorizationCode that can be used to exchange for a long
+    /// term access token.
+    ///
+    virtual bool isAuthorized();
+
+    ///
+    /// Returns the device list of thermostats.
+    ///
+    /// @return a SMART_DEV_OK on success, or another SMART_DEV_ on error. On
+    /// success the devices vector will contain the authorized/found thermostats.
+    ///
+    virtual int getThermostats(std::vector<LyricThermostatSharedPtr> &thermostats);
+
+    /// Sets temperature of selected honeywell thermostat.
+    ///
+    /// @param - Vector of honeywell thermostat devices.
+    /// @data - (input) Contains target temperature data to set.
+    /// @uri - (input optional) Resource URI of thermostat to target. (If not specified, first discovered
+    ///        device is used.)
+    ///
+    /// @return MPM_RESULT_OK on success, error otherwise.
+    virtual MPMResult setTemperature(LyricThermostatSharedPtr thermostat, const THERMOSTAT data,
+                                     const std::string uri);
+};
+
+#endif /* __HONEYWELLLYRIC_H__ */
diff --git a/bridging/plugins/lyric_plugin/honeywell_objects/honeywellThermostat.cpp b/bridging/plugins/lyric_plugin/honeywell_objects/honeywellThermostat.cpp
new file mode 100644 (file)
index 0000000..b990f3d
--- /dev/null
@@ -0,0 +1,238 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+#include <stdio.h>
+#include <string>
+#include <sstream> // ostringstream
+#include "honeywellThermostat.h"
+
+#include "honeywellDefsLyric.h"
+
+#include "rapidjson.h"
+#include "document.h"
+#include "writer.h"
+#include "stringbuffer.h"
+#include "curlClient.h"
+#include "logger.h"
+#include "../honeywellHelpers.h"
+
+#define LOG_TAG "HONEYWELL_THERMOSTAT"
+
+HoneywellThermostat::HoneywellThermostat(const std::string &token, const std::string &thermostat) : m_token(token)
+{
+    buildThermostat(thermostat);
+}
+
+HoneywellThermostat::HoneywellThermostat(const THERMOSTAT data)
+{
+    m_thermostat = data;
+}
+
+HoneywellThermostat::HoneywellThermostat()
+{
+    m_valid = false;
+}
+
+std::string hvacModeToString(const HVAC_MODE &hvacMode)
+{
+    std::string result = THERMOSTAT_MODE_OFF;
+    switch (hvacMode)
+    {
+        case HVAC_COOL :
+            result = THERMOSTAT_MODE_COOL;
+            break;
+        case HVAC_HEAT :
+            result = THERMOSTAT_MODE_HEAT;
+            break;
+        case HVAC_OFF :
+            // do nothing, already 'off'
+            break;
+        default:
+            OIC_LOG(ERROR, LOG_TAG, "Unexpected hvacMode; defaulting to Off");
+            break;
+    }
+    return result;
+}
+
+void HoneywellThermostat::buildDeviceUri(const char *baseUri)
+{
+    (void)baseUri;
+    std::string hw_tag = "/honeywell/";
+    std::ostringstream uriStream;
+
+    uriStream << hw_tag.c_str() << m_deviceUniqueId.c_str();
+    m_deviceUri = uriStream.str();
+    OIC_LOG_V(INFO, LOG_TAG, "m_deviceUri: %s", m_deviceUri.c_str());
+}
+
+HVAC_MODE HoneywellThermostat::getHVACmode(const std::string &hvacMode)
+{
+    HVAC_MODE result = HVAC_UNDEFINED;
+    if (hvacMode == REP_VALUE_HEAT)
+    {
+        result = HVAC_HEAT;
+    }
+    else if (hvacMode == REP_VALUE_COOL)
+    {
+        result = HVAC_COOL;
+    }
+    else if (hvacMode == REP_VALUE_OFF)
+    {
+        result = HVAC_OFF;
+    }
+    return result;
+}
+
+TEMPERATURE_SCALE HoneywellThermostat::getTemperatureScale(const std::string &tempScale)
+{
+    TEMPERATURE_SCALE result = TEMP_UNDEFINED;
+    if (tempScale == HONEYWELL_TEMP_SCALE_C)
+    {
+        result = TEMP_CELSIUS;
+    }
+    else if (tempScale == HONEYWELL_TEMP_SCALE_F)
+    {
+        result = TEMP_FAHRENHEIT;
+    }
+    return result;
+}
+
+int HoneywellThermostat::buildThermostat(const std::string &thermostat)
+{
+    if (thermostat.empty())
+    {
+        return MPM_RESULT_INVALID_DATA;
+    }
+
+    OIC_LOG(INFO, LOG_TAG, "This version of the function not currently used.");
+
+    return MPM_RESULT_NOT_IMPLEMENTED;
+}
+
+MPMResult HoneywellThermostat::setTemperature(const THERMOSTAT data)
+{
+    MPMResult result = MPM_RESULT_OK;
+
+    // the important values here are the coolSetpoint and heatSetpoint (honeywell doesn't
+    // have a single desired temperature). we can also set the mode if the current room temp
+    // is beyond the limits. (not necessary if thermostat is in auto mode, though- they
+    // will automatically kick on)
+
+    // note: the global Honeywell object now takes care of communicating with the cloud.
+    //       just copy relevant values here.
+    m_thermostat.targetTempF = data.targetTempF;
+    m_thermostat.heatSetpointF = data.heatSetpointF;
+    m_thermostat.coolSetpointF = data.coolSetpointF;
+    m_thermostat.hvacMode = data.hvacMode;
+
+    return result;
+}
+
+THERMOSTAT::THERMOSTAT(const THERMOSTAT &data)
+{
+    devInfo.deviceId = data.devInfo.deviceId; // tcc device id
+    devInfo.id = data.devInfo.id;
+    devInfo.lastConnection = data.devInfo.lastConnection;
+    devInfo.locale = data.devInfo.locale;
+    devInfo.name = data.devInfo.name;
+    devInfo.nameLong = data.devInfo.nameLong;
+    devInfo.structId = data.devInfo.structId;
+    devInfo.version = data.devInfo.version;
+    devInfo.deviceIdStr = data.devInfo.deviceIdStr; // lyric device id
+    devInfo.locationId = data.devInfo.locationId; // lyric location id
+    devInfo.uniqueId = data.devInfo.uniqueId;
+
+    isOnline = data.isOnline;
+    canCool = data.canCool;
+    canHeat = data.canHeat;
+    usingEmergencyHeat = data.usingEmergencyHeat;
+    hasFan = data.hasFan;
+    fanTimerActive = data.fanTimerActive;
+    fanTimerTimeout = data.fanTimerTimeout;
+    hasLeaf = data.hasLeaf;
+    temperature = data.temperature;
+    targetTempC = data.targetTempC;
+    targetTempF = data.targetTempF;
+    heatSetpointC = data.heatSetpointC;
+    heatSetpointF = data.heatSetpointF;
+    coolSetpointC = data.coolSetpointC;
+    coolSetpointF = data.coolSetpointF;
+    awayTempHighF = data.awayTempHighF;
+    awayTempHighC = data.awayTempHighC;
+    awayTempLowF = data.awayTempLowF;
+    awayTempLowC = data.awayTempLowC;
+    hvacMode = data.hvacMode;
+    ambientTempF = data.ambientTempF;
+    ambientTempC = data.ambientTempC;
+    humidity = data.humidity;
+    preferredDevice = data.preferredDevice;
+    cloudIndex = data.cloudIndex;
+
+    return;
+}
+
+THERMOSTAT::THERMOSTAT()
+{
+    devInfo.deviceId = 0;
+    isOnline = false;
+    canCool = false;
+    canHeat = false;
+    usingEmergencyHeat = false;
+    hasFan = false;
+    fanTimerActive = false;
+    fanTimerTimeout = false;
+    hasLeaf = false;
+    temperature = TEMP_FAHRENHEIT;
+    targetTempC = 0.0;
+    targetTempF = 0.0;
+    heatSetpointC = 0.0;
+    heatSetpointF = 0;
+    coolSetpointC = 0.0;
+    coolSetpointF = 0.0;
+    awayTempHighC = 0.0;
+    awayTempHighF = 0.0;
+    awayTempLowC = 0.0;
+    awayTempLowF = 0.0;
+    hvacMode = HVAC_OFF;
+    ambientTempC = 0.0;
+    ambientTempF = 0.0;
+    humidity = 0.0;
+    preferredDevice = false;
+    cloudIndex = 0;
+}
+
+void dump_details(const THERMOSTAT &thermostat, const char *description)
+{
+    if (NULL == description)
+    {
+        OIC_LOG_V(INFO, LOG_TAG, "deviceId: %d, heatSetpoint: %f, coolSetpoint: %f, targetTemp: %f",
+              thermostat.devInfo.deviceId, thermostat.heatSetpointF, thermostat.coolSetpointF,
+              thermostat.targetTempF);
+    }
+    else
+    {
+        OIC_LOG_V(INFO, LOG_TAG, "%s - deviceId: %d, heatSetpoint: %f, coolSetpoint: %f, targetTemp: %f",
+              description, thermostat.devInfo.deviceId, thermostat.heatSetpointF,
+              thermostat.coolSetpointF, thermostat.targetTempF);
+    }
+    (void)thermostat;
+    return;
+}
diff --git a/bridging/plugins/lyric_plugin/honeywell_objects/honeywellThermostat.h b/bridging/plugins/lyric_plugin/honeywell_objects/honeywellThermostat.h
new file mode 100644 (file)
index 0000000..4a5c5e7
--- /dev/null
@@ -0,0 +1,184 @@
+//******************************************************************
+//
+// Copyright 2017 Intel Mobile Communications GmbH All Rights Reserved.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+
+#ifndef __HONEYWELLTHERMOSTAT_H__
+#define __HONEYWELLTHERMOSTAT_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <vector>
+#include <string>
+#include "mpmErrorCode.h"
+#include <typeinfo>
+#include <memory>
+
+enum TEMPERATURE_SCALE
+{
+    TEMP_UNDEFINED = 0,
+    TEMP_CELSIUS = 1,
+    TEMP_FAHRENHEIT = 2,
+    TEMP_MAX
+};
+
+enum HVAC_MODE
+{
+    HVAC_UNDEFINED = 0,
+    HVAC_HEAT = 1,
+    HVAC_COOL = 2,
+    HVAC_MIXED = 3,
+    HVAC_OFF = 4,
+    HVAC_MAX
+};
+
+typedef struct _DEVICE_INFO
+{
+    int deviceId;               // honeywell device id (only used for Honeywell TCC)
+    std::string id;             // the unique ID for this instance
+    std::string version;        // software version
+    std::string structId;       // structure ID
+    std::string name;           // Friendly name of the thermostat
+    std::string nameLong;       // Long friendly name
+    std::string lastConnection; // Last connection time
+    std::string locale;         // Locale
+    std::string deviceIdStr;    // lyric device id (only for Lyric)
+    std::string uniqueId;       // unique ID
+    std::string uri;            // device uri
+    int locationId;             // lyric location id (required for device access)
+} DEVICE_INFO;
+
+struct THERMOSTAT
+{
+    THERMOSTAT(); // default constructor
+    THERMOSTAT(const THERMOSTAT &data); // copy constructor
+
+    DEVICE_INFO devInfo;           // Device info
+    bool isOnline;                 // Is thermostat online now?
+    bool canCool;                  // AC connected
+    bool canHeat;                  // Heater connected
+    bool usingEmergencyHeat;       // True if emergency heat is in use
+    bool hasFan;                   // Is a fan installed
+    bool fanTimerActive;           // Is the fan timer currently active
+    bool fanTimerTimeout;          // Fan timer timeout (absolut timee)
+    bool hasLeaf;                  // Is leaf (savings) displayed
+    TEMPERATURE_SCALE temperature; // Temperature scale (C or F))
+    double targetTempC;            // Target temperature C
+    double targetTempF;            // Target temperature F
+    double awayTempHighF;          // Away high tempereature F
+    double awayTempHighC;          // Away high temperature C
+    double awayTempLowF;           // Away low temperature F
+    double awayTempLowC;           // Away low temperature C
+    HVAC_MODE hvacMode;            // Current HVAC mode
+    double ambientTempF;           // Ambient temperature F
+    double ambientTempC;           // Ambient temperature C
+    double humidity;               // Current humidity (0 - 100)
+    double heatSetpointF;           // heat threshold F
+    double coolSetpointF;           // cool threshold F
+    double heatSetpointC;           // heat threshold C
+    double coolSetpointC;           // cool threshold C
+    bool preferredDevice;           // is this device mentioned in PREFERRED_THERMOSTAT?
+    int cloudIndex;                 // index of device in array returned by server
+};
+
+void dump_details(const THERMOSTAT &thermostat, const char *description);
+
+std::string hvacModeToString(const HVAC_MODE &hvacMode);
+
+class HoneywellThermostat
+{
+public:
+    typedef std::vector<HoneywellThermostat>::iterator it;
+    typedef std::vector<std::string> curlHeaders;
+
+    bool operator==(const HoneywellThermostat &ht)
+    {
+        return m_changeableValues == ht.m_changeableValues;
+    }
+
+    HoneywellThermostat(const std::string &token, const std::string &jsonThermostat);
+
+    HoneywellThermostat(const THERMOSTAT data);
+
+    HoneywellThermostat();
+
+    virtual ~HoneywellThermostat()
+    {
+    }
+
+    int get(THERMOSTAT &data)
+    {
+        data = m_thermostat;
+        return MPM_RESULT_OK;
+    }
+
+    void set(const THERMOSTAT &data)
+    {
+        m_thermostat = data;
+    }
+
+    MPMResult setTemperature(const THERMOSTAT data);
+
+    HVAC_MODE getHVACmode(const std::string &hvacMode);
+
+    void setChangeableValues(const std::string &changeableValues)
+    {
+        m_changeableValues = changeableValues;
+    }
+
+    std::string getChangeableValues()
+    {
+        return m_changeableValues;
+    }
+
+    void buildDeviceUri(const char *baseUri = NULL);
+
+    std::string getDeviceUri()
+    {
+        return m_deviceUri;
+    }
+
+    void setDeviceUniqueId(const std::string &uniqueId)
+    {
+        m_deviceUniqueId = uniqueId;
+    }
+
+    std::string getDeviceUniqueId()
+    {
+        return m_deviceUniqueId;
+    }
+
+private:
+    int buildThermostat(const std::string &json);
+
+    TEMPERATURE_SCALE getTemperatureScale(const std::string &tempScale);
+
+    bool m_valid;
+    THERMOSTAT m_thermostat;
+    std::string m_token;
+    curlHeaders m_inHeaders;
+    std::string m_changeableValues;
+    std::string m_deviceUri;
+    std::string m_deviceUniqueId;
+};
+
+typedef std::shared_ptr<HoneywellThermostat> LyricThermostatSharedPtr;
+
+#endif /* __HONEYWELL_THERMOSTAT_H__ */