# SConscript(os.path.join('plugins', 'nest_plugin', 'SConscript'))
-# SConscript(os.path.join('plugins', 'lyric_plugin', 'SConscript'))
+ SConscript(os.path.join('plugins', 'lyric_plugin', 'SConscript'))
--- /dev/null
+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.
--- /dev/null
+#******************************************************************
+#
+# 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')
+
+
--- /dev/null
+//******************************************************************
+//
+// 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);
+}
+
--- /dev/null
+//******************************************************************
+//
+// 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__
--- /dev/null
+//******************************************************************
+//
+// 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);
+}
+
--- /dev/null
+//******************************************************************
+//
+// 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;
+}
+
--- /dev/null
+//******************************************************************
+//
+// 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__ */
--- /dev/null
+//******************************************************************
+//
+// 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__ */
--- /dev/null
+//******************************************************************
+//
+// 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__ */
--- /dev/null
+//******************************************************************
+//
+// 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;
+}
--- /dev/null
+//******************************************************************
+//
+// 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__ */
--- /dev/null
+//******************************************************************
+//
+// 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;
+}
--- /dev/null
+//******************************************************************
+//
+// 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__ */