aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authormaratsabitov <marat.sabitov@mera.com>2020-05-08 18:03:57 +0300
committerMarat Sabitov <marat.sabitov@mera.com>2020-07-09 09:38:01 +0300
commit0cecffd4565b52bd8d200f6bc9f4144f244a6515 (patch)
treec0969bb192bb711c0bd2e2444a66c0060b2e13af /src
parentf496bfe75ef0a1ec2658d3fec51866d4c7551282 (diff)
Cloudproxy service allows applications to communicate with clouds. The commit includes the test applications as sample for real client imlementation. Bug-AGL: SPEC-3370 Change-Id: I82d8122e93d01451f4471cc20c706e75c16f1c29 Signed-off-by: maratsabitov <marat.sabitov@mera.com>
Diffstat (limited to 'src')
-rwxr-xr-xsrc/CMakeLists.txt77
-rwxr-xr-xsrc/ClientManager.cpp215
-rwxr-xr-xsrc/ClientManager.h59
-rwxr-xr-xsrc/cloudproxy-bindings.cpp441
-rw-r--r--src/config_sample.ini3
-rwxr-xr-xsrc/export.map1
-rwxr-xr-xsrc/hmi-debug.h82
-rwxr-xr-xsrc/utils.h54
8 files changed, 932 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100755
index 0000000..fc397f1
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,77 @@
+###########################################################################
+# Copyright (C) 2020 MERA
+#
+# 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.
+###########################################################################
+
+cmake_minimum_required(VERSION 2.8)
+
+set(TARGETS_ARBINDER cloudproxy-service)
+
+INCLUDE(FindThreads)
+FIND_PACKAGE(Threads)
+
+pkg_check_modules(cp_binding_depends afb-daemon glib-2.0 gio-2.0 gio-unix-2.0 json-c)
+set(binding_cp_sources
+ cloudproxy-bindings.cpp
+ utils.h
+ hmi-debug.h
+ ClientManager.h
+ ClientManager.cpp
+)
+
+link_libraries(-Wl,--as-needed -Wl,--gc-sections -Wl,--no-undefined)
+
+set(AZURE_DEP_LIBS iothub_client)
+
+add_library(${TARGETS_ARBINDER} MODULE ${binding_cp_sources})
+
+include_directories(${CMAKE_SYSROOT}/usr/include/azureiot)
+
+target_compile_options(${TARGETS_ARBINDER} PRIVATE ${cp_binding_depends_CFLAGS})
+if(DEFINED DEBUGMODE)
+ target_compile_options(${TARGETS_ARBINDER} PRIVATE -g -O0)
+else(DEFINED DEBUGMODE)
+ target_compile_options(${TARGETS_ARBINDER} PRIVATE -g -O2)
+endif(DEFINED DEBUGMODE)
+
+target_link_libraries(${TARGETS_ARBINDER} ${CMAKE_THREAD_LIBS_INIT} ${link_libraries} ${cp_binding_depends_LIBRARIES} ${AZURE_DEP_LIBS})
+
+
+set_target_properties(${TARGETS_ARBINDER} PROPERTIES
+ PREFIX ""
+ LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/export.map"
+ )
+
+target_compile_definitions(${TARGETS_ARBINDER}
+ PRIVATE
+ AFB_BINDING_VERSION=3
+ _GNU_SOURCE)
+
+
+if(NOT EXISTS ${PROJECT_BINARY_DIR}/package)
+ add_custom_command(TARGET ${TARGETS_ARBINDER} POST_BUILD
+ COMMAND cp -rf ${PROJECT_SOURCE_DIR}/package ${PROJECT_BINARY_DIR}
+ )
+endif()
+
+add_custom_command(TARGET ${TARGETS_ARBINDER} POST_BUILD
+ COMMAND mkdir -p ${PROJECT_BINARY_DIR}/package/root/lib
+ COMMAND cp -rf ${PROJECT_BINARY_DIR}/src/${TARGETS_ARBINDER}.so ${PROJECT_BINARY_DIR}/package/root/lib
+ COMMAND mkdir -p ${PROJECT_BINARY_DIR}/package/root/etc
+ COMMAND cp -f ${PROJECT_SOURCE_DIR}/src/config_sample.ini ${PROJECT_BINARY_DIR}/package/root/etc/config.ini
+)
+
+add_custom_target(widget DEPENDS ${PROJECT_BINARY_DIR}/package/root
+ COMMAND wgtpkg-pack -f -o ${PROJECT_BINARY_DIR}/${TARGETS_ARBINDER}.wgt ${PROJECT_BINARY_DIR}/package/root
+)
diff --git a/src/ClientManager.cpp b/src/ClientManager.cpp
new file mode 100755
index 0000000..74d672d
--- /dev/null
+++ b/src/ClientManager.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2020 MERA
+ *
+ * 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 "ClientManager.h"
+#include "hmi-debug.h"
+
+#include <set>
+#include <json-c/json.h>
+
+// static
+void ClientManager::cbRemoveClientCtx(void *data)
+{
+ ClientManager::instance().removeClient((ClientManager::ClientCtx*)data);
+}
+
+
+// static
+ClientManager& ClientManager::instance()
+{
+ static ClientManager instance;
+ return instance;
+}
+
+
+ClientManager::~ClientManager()
+{
+ std::lock_guard<std::mutex> lock(this->m_mutex);
+ for (auto cl : m_clients)
+ delete cl.second;
+}
+
+
+ClientManager::ClientCtx* ClientManager::addClient(afb_req_t req, const std::string& appid)
+{
+ ClientCtx* ctx = (ClientCtx*)afb_req_context_get(req);
+ if (!ctx)
+ {
+ HMI_NOTICE("cloudproxy-service", "create new session for %s", appid.c_str());
+ ctx = new ClientCtx{appid, afb_api_make_event(req->api, appid.c_str())};
+ afb_req_session_set_LOA(req, 1);
+ afb_req_context_set(req, ctx, cbRemoveClientCtx);
+ }
+
+ m_clients[appid] = ctx;
+ return ctx;
+}
+
+
+void ClientManager::removeClient(ClientCtx* ctx)
+{
+ if(!ctx)
+ {
+ HMI_ERROR("cloudproxy-service", "data is nullptr");
+ return;
+ }
+
+ HMI_NOTICE("cloudproxy-service", "remove app %s", ctx->appid.c_str());
+ std::lock_guard<std::mutex> lock(this->m_mutex);
+
+ auto it = m_clients.find(ctx->appid);
+ if (it != m_clients.end())
+ {
+ delete it->second;
+ m_clients.erase(it);
+ }
+}
+
+
+bool ClientManager::handleRequest(afb_req_t request, const std::string& verb, const std::string& appid)
+{
+ HMI_NOTICE("cloudproxy-service", "handleRequest: verb='%s', appid='%s'", verb.c_str(), appid.c_str());
+ std::lock_guard<std::mutex> lock(this->m_mutex);
+
+ if (appid.empty())
+ {
+ HMI_ERROR("cloudproxy-service", "appid is empty");
+ return false;
+ }
+
+ auto client_it = m_clients.find(appid);
+ if (verb != "subscribe" && client_it == m_clients.end())
+ {
+ HMI_NOTICE("cloudproxy-service", "client with appid '%s' is not registered", appid.c_str());
+ return false;
+ }
+
+ if (verb == "subscribe")
+ {
+ const char *value = afb_req_value(request, "event");
+ if(!value)
+ {
+ HMI_ERROR("cloudproxy-service", "Can't subscribe: event name is not defined");
+ return false;
+ }
+ std::string req_event{value};
+ HMI_NOTICE("cloudproxy-service", "subscribe req: appid '%s', event '%s'", appid.c_str(), req_event.c_str());
+
+ if (!isSupportedEvent(req_event))
+ {
+ HMI_ERROR("cloudproxy-service", "event '%s' is not supported", req_event.c_str());
+ return false;
+ }
+
+ ClientCtx* ctx = addClient(request, appid);
+ ctx->subs_events.insert(req_event);
+ if(!ctx->subscription)
+ {
+ if(afb_req_subscribe(request, ctx->event) == 0)
+ {
+ ctx->subscription = true;
+ }
+ else
+ {
+ HMI_ERROR("cloudproxy-service", "API error in afb_req_subscribe");
+ return false;
+ }
+ }
+
+ return true;
+ }
+ else if (verb == "unsubscribe")
+ {
+ const char *value = afb_req_value(request, "event");
+ if(!value)
+ {
+ HMI_ERROR("cloudproxy-service", "Can't unsubscribe: event name is not defined");
+ return false;
+ }
+ std::string req_event{value};
+ HMI_NOTICE("cloudproxy-service", "unsubscribe req: appid '%s', event '%s'", appid.c_str(), req_event.c_str());
+
+ ClientCtx* ctx{client_it->second};
+ ctx->subs_events.erase(req_event);
+
+ if(ctx->subs_events.empty())
+ {
+ if (afb_req_unsubscribe(request, ctx->event) != 0)
+ HMI_ERROR("cloudproxy-service", "API error in afb_req_unsubscribe");
+
+ ctx->subscription = false;
+ }
+
+ return true;
+ }
+
+ HMI_NOTICE("cloudproxy-service", "Unsupported verb '%s'", verb.c_str());
+ return false;
+}
+
+
+bool ClientManager::isSupportedEvent(const std::string& event)
+{
+ const std::set<std::string> event_list{
+ "sendMessageConfirmation",
+ "receivedMessage"
+ };
+
+ return (event_list.end() != event_list.find(event));
+}
+
+
+bool ClientManager::emitReceivedMessage(const std::string& appid, const std::string& data)
+{
+ std::lock_guard<std::mutex> lock(this->m_mutex);
+
+ auto it = m_clients.find(appid);
+ if (it == m_clients.end())
+ {
+ HMI_WARNING("cloudproxy-service", "Client with appid '%s' is not present in list", appid.c_str());
+ // print app list
+ for (const auto& i : m_clients)
+ HMI_DEBUG("cloudproxy-service", "Client list: appid '%s' - '%s'", i.first.c_str(), i.second->appid.c_str());
+
+ return false;
+ }
+
+ json_object* push_obj = json_object_new_object();
+ json_object_object_add(push_obj, "type", json_object_new_string("receivedMessage"));
+ json_object_object_add(push_obj, "data", json_object_new_string(data.c_str()));
+ return (0 == afb_event_push(it->second->event, push_obj));
+}
+
+bool ClientManager::emitSendMessageConfirmation(const std::string& appid, bool result)
+{
+ std::lock_guard<std::mutex> lock(this->m_mutex);
+
+ auto it = m_clients.find(appid);
+ if (it == m_clients.end())
+ {
+ HMI_WARNING("cloudproxy-service", "Client with appid '%s' is not present in list", appid.c_str());
+ // print app list
+ for (const auto& i : m_clients)
+ HMI_DEBUG("cloudproxy-service", "Client list: appid '%s' - '%s'", i.first.c_str(), i.second->appid.c_str());
+
+ return false;
+ }
+
+ json_object* push_obj = json_object_new_object();
+ json_object_object_add(push_obj, "type", json_object_new_string("sendMessageConfirmation"));
+ json_object_object_add(push_obj, "result", json_object_new_boolean(result));
+ return (0 == afb_event_push(it->second->event, push_obj));
+}
diff --git a/src/ClientManager.h b/src/ClientManager.h
new file mode 100755
index 0000000..a1038e1
--- /dev/null
+++ b/src/ClientManager.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 MERA
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <afb/afb-binding.h>
+
+#include <map>
+#include <unordered_set>
+#include <mutex>
+#include <string>
+
+class ClientManager
+{
+public:
+ struct ClientCtx
+ {
+ std::string appid;
+ afb_event_t event;
+ std::unordered_set<std::string> subs_events;
+ bool subscription{false};
+ };
+
+ ClientManager() = default;
+ ~ClientManager();
+ ClientManager(const ClientManager&) = delete;
+ void operator=(const ClientManager&) = delete;
+
+ bool handleRequest(afb_req_t request, const std::string& verb, const std::string& appid);
+ bool emitReceivedMessage(const std::string& appid, const std::string& data);
+ bool emitSendMessageConfirmation(const std::string& appid, bool result);
+
+ static ClientManager& instance();
+
+ // don't use it directly
+ static void cbRemoveClientCtx(void *data);
+
+protected:
+ ClientCtx* addClient(afb_req_t req, const std::string& appid);
+ void removeClient(ClientCtx* ctx);
+ bool isSupportedEvent(const std::string& event);
+
+protected:
+ std::mutex m_mutex;
+ std::map<std::string, ClientCtx*> m_clients;
+};
diff --git a/src/cloudproxy-bindings.cpp b/src/cloudproxy-bindings.cpp
new file mode 100755
index 0000000..1a40730
--- /dev/null
+++ b/src/cloudproxy-bindings.cpp
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2020 MERA
+ *
+ * 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 <afb/afb-binding.h>
+#include <json-c/json.h>
+
+#include <memory>
+#include <algorithm>
+
+#include <iothub.h>
+#include <iothub_device_client.h>
+#include <iothub_client_options.h>
+#include <iothub_message.h>
+#include <iothubtransportmqtt.h>
+#include <azure_c_shared_utility/threadapi.h> // ThreadAPI_Sleep()
+#include <azure_c_shared_utility/tickcounter.h> // tickcounter_ms_t
+
+#include "hmi-debug.h"
+#include "utils.h"
+#include "ClientManager.h"
+
+#include <glib.h>
+
+static const char* API_name{"cloudproxy"};
+
+static std::string g_connectionString;
+static IOTHUB_DEVICE_CLIENT_HANDLE g_device_handle{nullptr};
+static bool g_iot_inited{false};
+
+
+
+static utils::scope_exit g_destroy([]{
+ if (g_iot_inited)
+ {
+ if (g_device_handle)
+ IoTHubDeviceClient_Destroy(g_device_handle);
+
+ IoTHub_Deinit();
+ }
+});
+
+
+static bool loadConf()
+{
+ const char* CONF_FILE_PATH{"/etc/config.ini"};
+ const char* DEFAULT_ETC_PATH{"/etc/cloudproxy-service"};
+ const char *p = getenv("AFM_APP_INSTALL_DIR");
+ if(!p)
+ {
+ HMI_ERROR("cloudproxy-service", "AFM_APP_INSTALL_DIR is not set, try to find conf in %s", DEFAULT_ETC_PATH);
+ p = DEFAULT_ETC_PATH;
+ }
+
+ std::string conf_path = p;
+ conf_path += CONF_FILE_PATH;
+
+ g_autoptr(GKeyFile) conf_file = g_key_file_new();
+ g_autoptr(GError) error = nullptr;
+
+ if (!conf_file || !g_key_file_load_from_file(conf_file, conf_path.c_str(), G_KEY_FILE_NONE, &error))
+ {
+ HMI_ERROR("cloudproxy-service", "can't load file %s", conf_path.c_str());
+ return false;
+ }
+
+ g_autofree gchar *value = g_key_file_get_string(conf_file, "AzureCloudConnection", "DeviceConnectionString", &error);
+ if (value == nullptr)
+ {
+ HMI_ERROR("cloudproxy-service", "can't read DeviceConnectionString from config %d", conf_path.c_str());
+ return false;
+ }
+
+ g_connectionString = value;
+ if (g_connectionString.empty())
+ {
+ HMI_ERROR("cloudproxy-service", "DeviceConnectionString is empty");
+ return false;
+ }
+
+ return true;
+}
+
+
+//-------------- Iot callbacks
+static void connection_status_callback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void* user_context)
+{
+ HMI_NOTICE("cloudproxy-service", "%s called: result %d, reason %d", __FUNCTION__, result, reason);
+
+ (void)reason;
+ (void)user_context;
+ // This sample DOES NOT take into consideration network outages.
+ if (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED && reason == IOTHUB_CLIENT_CONNECTION_OK)
+ {
+ HMI_NOTICE("cloudproxy-service", "The device client is connected to iothub");
+ }
+ else
+ {
+ HMI_NOTICE("cloudproxy-service", "The device client has been disconnected");
+ }
+}
+
+static IOTHUBMESSAGE_DISPOSITION_RESULT receive_msg_callback(IOTHUB_MESSAGE_HANDLE message, void* user_context)
+{
+ HMI_NOTICE("cloudproxy-service", "%s called", __FUNCTION__);
+ (void)user_context;
+ const char* messageId;
+ const char* correlationId;
+
+ IOTHUBMESSAGE_CONTENT_TYPE content_type = IoTHubMessage_GetContentType(message);
+
+ if (content_type == IOTHUBMESSAGE_BYTEARRAY)
+ {
+ const unsigned char* buff_msg;
+ size_t buff_len;
+
+ if (IoTHubMessage_GetByteArray(message, &buff_msg, &buff_len) != IOTHUB_MESSAGE_OK)
+ {
+ HMI_ERROR("cloudproxy-service", "Failure retrieving byte array message");
+ }
+ else
+ {
+ HMI_NOTICE("cloudproxy-service", "Received Binary message, size %d, data '%.*s'", (int)buff_len, (int)buff_len, buff_msg);
+ }
+
+ const char* app_id = IoTHubMessage_GetProperty(message, "application_id");
+ HMI_NOTICE("cloudproxy-service", "Received property 'application_id': %s", (app_id ? app_id : "<unavailable>"));
+
+ if (app_id && app_id[0])
+ ClientManager::instance().emitReceivedMessage(app_id, std::string((const char*)buff_msg, buff_len));
+ else
+ HMI_ERROR("cloudproxy-service", "Can't emit SendMessageConfirmation: appid is not valid");
+ }
+ else if (content_type == IOTHUBMESSAGE_STRING)
+ {
+ const char* string_msg = IoTHubMessage_GetString(message);
+ if (string_msg == nullptr)
+ {
+ HMI_NOTICE("cloudproxy-service", "Failure retrieving String message");
+ }
+ else
+ {
+ HMI_NOTICE("cloudproxy-service", "Received String message, size %d, data '%s'", strlen(string_msg), string_msg);
+ }
+
+ const char* app_id = IoTHubMessage_GetProperty(message, "application_id");
+ HMI_NOTICE("cloudproxy-service", "Received property 'application_id': %s", (app_id ? app_id : "<unavailable>"));
+
+ if (app_id && app_id[0])
+ ClientManager::instance().emitReceivedMessage(app_id, string_msg);
+ else
+ HMI_ERROR("cloudproxy-service", "Can't emit SendMessageConfirmation: appid is not valid");
+ }
+ else
+ {
+ HMI_ERROR("cloudproxy-service", "Unsupported message content type");
+ }
+
+ return IOTHUBMESSAGE_ACCEPTED;
+}
+
+
+static int device_method_callback(const char* method_name, const unsigned char* payload, size_t size, unsigned char** response, size_t* resp_size, void* userContextCallback)
+{
+ HMI_NOTICE("cloudproxy-service", "%s called, method_name %s", __FUNCTION__, method_name);
+
+ const char* device_id = (const char*)userContextCallback;
+ char* end = nullptr;
+ int newInterval;
+
+ int status = 501;
+ const char* RESPONSE_STRING = "{ \"Response\": \"Unknown method requested.\" }";
+
+ HMI_NOTICE("cloudproxy-service", "Device Method called for device %s", device_id);
+ HMI_NOTICE("cloudproxy-service", "Device Method name: %s", method_name);
+ HMI_NOTICE("cloudproxy-service", "Device Method payload: %.*s", (int)size, (const char*)payload);
+
+ HMI_NOTICE("cloudproxy-service", "Response status: %d", status);
+ HMI_NOTICE("cloudproxy-service", "Response payload: %s", RESPONSE_STRING);
+
+ *resp_size = strlen(RESPONSE_STRING);
+ if ((*response = (unsigned char*)malloc(*resp_size)) == NULL)
+ {
+ status = -1;
+ }
+ else
+ {
+ memcpy(*response, RESPONSE_STRING, *resp_size);
+ }
+
+ return status;
+}
+
+
+static void send_confirm_callback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* userContextCallback)
+{
+ HMI_NOTICE("cloudproxy-service", "%s called, result %d", __FUNCTION__, result);
+ (void)userContextCallback;
+ // When a message is sent this callback will get invoked
+
+ HMI_NOTICE("cloudproxy-service", "Confirmation callback result %s", MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONFIRMATION_RESULT, result));
+
+ const char* appid = (const char*)userContextCallback;
+ if (!appid || !appid[0])
+ {
+ HMI_ERROR("cloudproxy-service", "Confirmation callback: appid is not set");
+
+ if (userContextCallback)
+ free(userContextCallback);
+
+ return;
+ }
+
+ ClientManager::instance().emitSendMessageConfirmation(appid, result == IOTHUB_CLIENT_CONFIRMATION_OK);
+ free(userContextCallback);
+}
+//--------------
+
+//-------------- help functions
+static bool createConnection()
+{
+ HMI_NOTICE("cloudproxy-service", "%s called", __FUNCTION__);
+
+ if (g_device_handle)
+ {
+ HMI_WARNING("cloudproxy-service", "connection already created");
+ return true;
+ }
+
+ g_device_handle = IoTHubDeviceClient_CreateFromConnectionString(g_connectionString.c_str(), MQTT_Protocol);
+ if (!g_device_handle)
+ {
+ HMI_ERROR("cloudproxy-service", "Failure creating IoTHubDeviceClient device");
+ return false;
+ }
+
+ bool traceOn = false;
+ IoTHubDeviceClient_SetOption(g_device_handle, OPTION_LOG_TRACE, &traceOn);
+ IoTHubDeviceClient_SetConnectionStatusCallback(g_device_handle, connection_status_callback, nullptr);
+ IoTHubDeviceClient_SetMessageCallback(g_device_handle, receive_msg_callback, nullptr);
+ IoTHubDeviceClient_SetDeviceMethodCallback(g_device_handle, device_method_callback, nullptr);
+
+ tickcounter_ms_t ms_delay = 10;
+ IoTHubDeviceClient_SetOption(g_device_handle, OPTION_DO_WORK_FREQUENCY_IN_MS, &ms_delay); // DoWork multithread
+
+ return true;
+}
+
+//--------------
+
+
+static void pingSample(afb_req_t request)
+{
+ static int pingcount = 0;
+ afb_req_success_f(request, json_object_new_int(pingcount), "Ping count = %d", pingcount);
+ HMI_NOTICE("cloudproxy-service", "Verbosity macro at level notice invoked at ping invocation count = %d", pingcount);
+ pingcount++;
+}
+
+
+static bool initAzureSdk()
+{
+ //Allow program to try to establish connection several times
+ if (!g_iot_inited)
+ {
+ if(IoTHub_Init())
+ {
+ HMI_ERROR("cloudproxy-service","Azure IoTHub_Init() failed");
+ }
+ else
+ {
+ g_iot_inited = true;
+ }
+ }
+
+ return g_iot_inited;
+}
+
+static void sendMessage(afb_req_t request)
+{
+ HMI_NOTICE("cloudproxy-service", "%s called", __FUNCTION__);
+
+ json_object* object = afb_req_json(request);
+ if (!object)
+ {
+ HMI_ERROR("cloudproxy-service", "Can't parse request");
+ afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
+ return;
+ }
+
+ const std::string appid{utils::get_application_id(request)};
+ std::string data;
+ json_object *obj_data;
+ if(!json_object_object_get_ex(object, "data", &obj_data))
+ {
+ HMI_ERROR("cloudproxy-service", "can't obtain application_id or data from request");
+ return;
+ }
+ data = json_object_get_string(obj_data);
+
+ if (!g_device_handle && !createConnection())
+ {
+ HMI_ERROR("cloudproxy-service", "Can't create connection to cloud");
+ afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
+ return;
+ }
+
+ IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(data.c_str());
+
+ utils::scope_exit message_handle_destroy([&message_handle](){
+ // The message is copied to the sdk, so the we can destroy it
+ if (message_handle)
+ IoTHubMessage_Destroy(message_handle);
+ });
+
+ if (!message_handle)
+ {
+ HMI_ERROR("cloudproxy-service", "Can't create IoTHubMessage message");
+ afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
+ return;
+ }
+
+ IoTHubMessage_SetProperty(message_handle, "application_id", appid.c_str());
+
+ if (IoTHubDeviceClient_SendEventAsync(g_device_handle, message_handle, send_confirm_callback, strdup(appid.c_str())))
+ {
+ HMI_ERROR("cloudproxy-service", "Can't send IoTHubMessage message");
+ afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
+ return;
+ }
+
+ afb_req_success(request, json_object_new_object(), __FUNCTION__);
+}
+
+
+static void subscribe(afb_req_t request)
+{
+ HMI_NOTICE("cloudproxy-service", "%s called", __FUNCTION__);
+
+ std::string req_appid{utils::get_application_id(request)};
+ if(req_appid.empty())
+ {
+ HMI_ERROR("cloudproxy-service", "Can't subscribe: empty appid");
+ afb_req_fail_f(request, "%s failed: application_id is not defined in request", __FUNCTION__);
+ return;
+ }
+
+ if (!ClientManager::instance().handleRequest(request, __FUNCTION__, req_appid))
+ {
+ HMI_ERROR("cloudproxy-service", "%s failed in handleRequest", __FUNCTION__);
+ afb_req_fail_f(request, "%s failed", __FUNCTION__);
+ }
+ else
+ {
+ afb_req_success(request, json_object_new_object(), __FUNCTION__);
+ }
+}
+
+static void unsubscribe(afb_req_t request)
+{
+ HMI_NOTICE("cloudproxy-service", "%s called", __FUNCTION__);
+
+ std::string req_appid{utils::get_application_id(request)};
+ if(req_appid.empty())
+ {
+ HMI_ERROR("cloudproxy-service", "Can't unsubscribe: empty appid");
+ afb_req_fail_f(request, "%s failed: application_id is not defined in request", __FUNCTION__);
+ return;
+ }
+
+ if (!ClientManager::instance().handleRequest(request, __FUNCTION__, req_appid))
+ {
+ HMI_ERROR("cloudproxy-service", "%s failedin handleRequest", __FUNCTION__);
+ afb_req_fail_f(request, "%s failed", __FUNCTION__);
+ }
+ else
+ {
+ afb_req_success(request, json_object_new_object(), __FUNCTION__);
+ }
+}
+
+/*
+ * array of the verbs exported to afb-daemon
+ */
+static const afb_verb_t verbs[]= {
+ /* VERB'S NAME FUNCTION TO CALL */
+ { .verb="ping", .callback=pingSample },
+ { .verb="sendMessage", .callback=sendMessage },
+ { .verb="subscribe", .callback=subscribe },
+ { .verb="unsubscribe", .callback=unsubscribe },
+ {nullptr } /* marker for end of the array */
+};
+
+
+static int preinit(afb_api_t api)
+{
+ HMI_NOTICE("cloudproxy-service", "binding preinit (was register)");
+
+ if (!loadConf())
+ {
+ HMI_ERROR("cloudproxy-service", "Can't load configuration file or configuration is wrong");
+ return -1;
+ }
+
+ if (!initAzureSdk())
+ {
+ HMI_ERROR("cloudproxy-service", "Can't initialize Azure SDK");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int init(afb_api_t api)
+{
+ HMI_NOTICE("cloudproxy-service","binding init");
+ return (g_iot_inited ? 0 : -1);
+}
+
+const afb_binding_t afbBindingExport = {
+ .api = "cloudproxy",
+ .specification = nullptr,
+ .info = nullptr,
+ .verbs = verbs,
+ .preinit = preinit,
+ .init = init,
+ .onevent = nullptr
+};
diff --git a/src/config_sample.ini b/src/config_sample.ini
new file mode 100644
index 0000000..fa97cd7
--- /dev/null
+++ b/src/config_sample.ini
@@ -0,0 +1,3 @@
+[AzureCloudConnection]
+DeviceConnectionString=My=Azure=Device=Connection=String
+
diff --git a/src/export.map b/src/export.map
new file mode 100755
index 0000000..f3961c0
--- /dev/null
+++ b/src/export.map
@@ -0,0 +1 @@
+{ global: afbBindingV*; local: *; }; \ No newline at end of file
diff --git a/src/hmi-debug.h b/src/hmi-debug.h
new file mode 100755
index 0000000..c8e8638
--- /dev/null
+++ b/src/hmi-debug.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2017 TOYOTA MOTOR CORPORATION
+ *
+ * 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 __HMI_DEBUG_H__
+#define __HMI_DEBUG_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <time.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/types.h> // gettid()
+#include <unistd.h>
+#include <sys/syscall.h>
+
+ enum LOG_LEVEL{
+ LOG_LEVEL_NONE = 0,
+ LOG_LEVEL_ERROR,
+ LOG_LEVEL_WARNING,
+ LOG_LEVEL_NOTICE,
+ LOG_LEVEL_INFO,
+ LOG_LEVEL_DEBUG,
+ LOG_LEVEL_MAX = LOG_LEVEL_DEBUG
+ };
+
+#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
+
+#define HMI_ERROR(prefix, args,...) _HMI_LOG(LOG_LEVEL_ERROR, __FILENAME__, __FUNCTION__, __LINE__, prefix, args, ##__VA_ARGS__)
+#define HMI_WARNING(prefix, args,...) _HMI_LOG(LOG_LEVEL_WARNING, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__)
+#define HMI_NOTICE(prefix, args,...) _HMI_LOG(LOG_LEVEL_NOTICE, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__)
+#define HMI_INFO(prefix, args,...) _HMI_LOG(LOG_LEVEL_INFO, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__)
+#define HMI_DEBUG(prefix, args,...) _HMI_LOG(LOG_LEVEL_DEBUG, __FILENAME__, __FUNCTION__,__LINE__, prefix, args,##__VA_ARGS__)
+
+ static char ERROR_FLAG[6][20] = {"NONE", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG"};
+
+ static void _HMI_LOG(enum LOG_LEVEL level, const char* file, const char* func, const int line, const char* prefix, const char* log, ...)
+ {
+ //SA: WARN: const int log_level = (getenv("USE_HMI_DEBUG") == NULL)?LOG_LEVEL_ERROR:atoi(getenv("USE_HMI_DEBUG"));
+ const int log_level = LOG_LEVEL_MAX;
+ if(log_level < level)
+ {
+ return;
+ }
+
+ char *message;
+ struct timespec tp;
+ unsigned int time;
+
+ clock_gettime(CLOCK_REALTIME, &tp);
+ time = (tp.tv_sec * 1000000L) + (tp.tv_nsec / 1000);
+
+ va_list args;
+ va_start(args, log);
+ if (log == NULL || vasprintf(&message, log, args) < 0)
+ message = NULL;
+ fprintf(stderr, "[%10.3f] [%s %s] [%s, %s(), Line:%d] [thread %llx]>>> %s \n", time / 1000.0, prefix, ERROR_FLAG[level], file, func, line, (unsigned long long)syscall(__NR_gettid), message);
+ va_end(args);
+ free(message);
+ }
+
+#ifdef __cplusplus
+}
+#endif
+#endif //__HMI_DEBUG_H__
diff --git a/src/utils.h b/src/utils.h
new file mode 100755
index 0000000..9069c4a
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 MERA
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <functional>
+#include <string>
+
+#include <afb/afb-binding.h>
+
+namespace utils
+{
+ class scope_exit
+ {
+ public:
+ explicit scope_exit(std::function<void()> func)
+ : m_f(func)
+ {}
+ ~scope_exit()
+ {
+ if (!!m_f)
+ m_f();
+ }
+
+ private:
+ std::function<void()> m_f;
+ };
+
+ std::string get_application_id(const afb_req_t request)
+ {
+ char *app_id = afb_req_get_application_id(request);
+ std::string appid;
+ if(app_id)
+ {
+ appid = std::string(app_id);
+ free(app_id);
+ }
+
+ return appid;
+ }
+}