/* * 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 #include #include #include #include #include #include #include #include #include // ThreadAPI_Sleep() #include // tickcounter_ms_t #include "hmi-debug.h" #include "utils.h" #include "ClientManager.h" #include 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 : "")); 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 : "")); 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 };