diff options
Diffstat (limited to 'src/AzureClient.cpp')
-rw-r--r-- | src/AzureClient.cpp | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/src/AzureClient.cpp b/src/AzureClient.cpp new file mode 100644 index 0000000..e381922 --- /dev/null +++ b/src/AzureClient.cpp @@ -0,0 +1,296 @@ +/* + * 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 "AzureClient.h" + +#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 "utils.h" +#include "CloudType.h" +#include "ClientManager.h" + +#include <glib.h> +#include <afb/afb-binding.h> // for AFB_* logger + +namespace +{ + +void connection_status_callback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void* user_context) +{ + AFB_NOTICE("%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) + { + AFB_NOTICE("The device client is connected to iothub"); + } + else + { + AFB_NOTICE("The device client has been disconnected"); + } +} + +IOTHUBMESSAGE_DISPOSITION_RESULT receive_msg_callback(IOTHUB_MESSAGE_HANDLE message, void* user_context) +{ + AFB_NOTICE("%s called", __FUNCTION__); + (void)user_context; + + 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) + { + AFB_ERROR("Failure retrieving byte array message"); + } + else + { + AFB_NOTICE("Received Binary message, size %d, data '%.*s'", (int)buff_len, (int)buff_len, buff_msg); + } + + const char* app_id = IoTHubMessage_GetProperty(message, "application_id"); + AFB_NOTICE("Received property 'application_id': %s", (app_id ? app_id : "<unavailable>")); + + if (app_id && app_id[0]) + ClientManager::instance().emitReceivedMessage(app_id, CloudType::Azure, std::string((const char*)buff_msg, buff_len)); + else + AFB_ERROR("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) + { + AFB_NOTICE("Failure retrieving String message"); + } + else + { + AFB_NOTICE("Received String message, size %lu, data '%s'", strlen(string_msg), string_msg); + } + + const char* app_id = IoTHubMessage_GetProperty(message, "application_id"); + AFB_NOTICE("Received property 'application_id': %s", (app_id ? app_id : "<unavailable>")); + + if (app_id && app_id[0]) + ClientManager::instance().emitReceivedMessage(app_id, CloudType::Azure, string_msg); + else + AFB_ERROR("Can't emit SendMessageConfirmation: appid is not valid"); + } + else + { + AFB_ERROR("Unsupported message content type"); + } + + return IOTHUBMESSAGE_ACCEPTED; +} + + +int device_method_callback(const char* method_name, const unsigned char* payload, size_t size, unsigned char** response, size_t* resp_size, void* userContextCallback) +{ + AFB_NOTICE("%s called, method_name %s", __FUNCTION__, method_name); + + const char* device_id = (const char*)userContextCallback; + + int status = 501; + const char* RESPONSE_STRING = "{ \"Response\": \"Unknown method requested.\" }"; + + AFB_NOTICE("Device Method called for device %s", device_id); + AFB_NOTICE("Device Method name: %s", method_name); + AFB_NOTICE("Device Method payload: %.*s", (int)size, (const char*)payload); + + + AFB_NOTICE("Response status: %d", status); + AFB_NOTICE("Response payload: %s", RESPONSE_STRING); + + *resp_size = strlen(RESPONSE_STRING); + if ((*response = (unsigned char*)malloc(*resp_size)) == nullptr) + { + status = -1; + } + else + { + memcpy(*response, RESPONSE_STRING, *resp_size); + } + + return status; +} + + +void send_confirm_callback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* userContextCallback) +{ + AFB_NOTICE("%s called, result %d", __FUNCTION__, result); + (void)userContextCallback; + // When a message is sent this callback will get invoked + + AFB_NOTICE("Confirmation callback result %s", MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONFIRMATION_RESULT, result)); + + const char* appid = (const char*)userContextCallback; + if (!appid || !appid[0]) + { + AFB_ERROR("Confirmation callback: appid is not set"); + + if (userContextCallback) + free(userContextCallback); + + return; + } + + ClientManager::instance().emitSendMessageConfirmation(appid, CloudType::Azure, result == IOTHUB_CLIENT_CONFIRMATION_OK); + free(userContextCallback); +} + +} //end namespace + +AzureClient::AzureClient() = default; + +AzureClient::~AzureClient() +{ + if (m_iot_inited) + { + if (m_azure_client && *m_azure_client) + IoTHubDeviceClient_Destroy(*m_azure_client); + + IoTHub_Deinit(); + } +} + +bool AzureClient::createConnection() +{ + AFB_NOTICE("%s called", __FUNCTION__); + + if (m_iot_inited) + { + AFB_ERROR("Azure IoT already initalized"); + return false; + } + + // Init Azure API: + { + int res = IoTHub_Init(); + m_iot_inited = true; + + if (res) + { + AFB_ERROR("Azure IoTHub_Init() failed: %d", res); + return false; + } + } + + if (m_azure_client) + { + AFB_ERROR("connection already created"); + return false; + } + + IOTHUB_DEVICE_CLIENT_HANDLE device_handle = IoTHubDeviceClient_CreateFromConnectionString(m_conf.device_connection_string.c_str(), MQTT_Protocol); + if (!device_handle) + { + AFB_ERROR("Failure creating Azure IoTHubDeviceClient device"); + return false; + } + + bool traceOn = false; + IoTHubDeviceClient_SetOption(device_handle, OPTION_LOG_TRACE, &traceOn); + IoTHubDeviceClient_SetConnectionStatusCallback(device_handle, connection_status_callback, nullptr); + IoTHubDeviceClient_SetMessageCallback(device_handle, receive_msg_callback, nullptr); + IoTHubDeviceClient_SetDeviceMethodCallback(device_handle, device_method_callback, nullptr); + + tickcounter_ms_t ms_delay = 10; + IoTHubDeviceClient_SetOption(device_handle, OPTION_DO_WORK_FREQUENCY_IN_MS, &ms_delay); // DoWork multithread + + m_azure_client.reset(new IOTHUB_DEVICE_CLIENT_HANDLE{device_handle}); + + return true; +} + + +bool AzureClient::sendMessage(const std::string& appid, const std::string& data) +{ + if (!m_azure_client) + { + AFB_ERROR("AzureClient is not ready for message sending"); + return false; + } + + 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) + { + AFB_ERROR("Can't create IoTHubMessage message"); + return false; + } + + IoTHubMessage_SetProperty(message_handle, "application_id", appid.c_str()); + + if (IoTHubDeviceClient_SendEventAsync(*m_azure_client, message_handle, send_confirm_callback, strdup(appid.c_str()))) + { + AFB_ERROR("Can't send IoTHubMessage message"); + return false; + } + + return true; +} + +bool AzureClient::enabled() const +{ + return m_conf.enabled; +} + +bool AzureClient::connected() const +{ + return (m_azure_client && *m_azure_client); +} + +bool AzureClient::loadConf(GKeyFile* conf_file) +{ + g_autoptr(GError) error = nullptr; + + // Azure parameters: + m_conf.enabled = g_key_file_get_boolean(conf_file, "AzureCloudConnection", "Enabled", &error); + + g_autofree gchar *value = g_key_file_get_string(conf_file, "AzureCloudConnection", "DeviceConnectionString", &error); + if (value == nullptr) + { + AFB_ERROR("can't read AzureCloudConnection/DeviceConnectionString from config"); + return false; + } + + m_conf.device_connection_string = value; + if (m_conf.device_connection_string.empty()) + { + AFB_ERROR("AzureCloudConnection/DeviceConnectionString is empty"); + return false; + } + + return true; +} |