IndustrialDeviceController/Software/HighLevelApp/iot/iot.c (313 lines of code) (raw):

/* Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. */ #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <time.h> #include <unistd.h> #include <init/applibs_versions.h> #include <applibs/log.h> #include <applibs/powermanagement.h> #include <azureiot/azure_sphere_provisioning.h> #include <applibs/networking.h> #include <init/globals.h> #include <init/adapter.h> #include <iot/diag.h> #include <iot/iot.h> #include <iot/azure_iot_utilities.h> #include <utils/llog.h> #include <utils/timer.h> #include <utils/utils.h> #include <utils/network.h> #include <utils/property.h> #include <utils/event_loop_timer.h> #include <frozen/frozen.h> extern volatile bool g_app_running; typedef struct iot_t iot_t; struct iot_t { event_loop_timer_t *setup_timer; event_loop_timer_t *periodic_timer; event_loop_timer_t *reset_timer; struct timespec ts_last_online; struct timespec ts_last_offline; EventLoop *eloop; }; static iot_t s_iot; static event_code_t iot_connection_status2event(IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason) { event_code_t code; switch (reason) { case IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN: code = EVENT_IOT_EXPIRED_SAS_TOKEN; break; case IOTHUB_CLIENT_CONNECTION_DEVICE_DISABLED: code = EVENT_IOT_CONNECTION_DEVICE_DISABLED; break; case IOTHUB_CLIENT_CONNECTION_BAD_CREDENTIAL: code = EVENT_IOT_CONNECTION_BAD_CREDENTIAL; break; case IOTHUB_CLIENT_CONNECTION_RETRY_EXPIRED: code = EVENT_IOT_CONNECTION_RETRY_EXPIRED; break; case IOTHUB_CLIENT_CONNECTION_NO_NETWORK: code = EVENT_IOT_CONNECTION_NO_NETWORK; break; case IOTHUB_CLIENT_CONNECTION_COMMUNICATION_ERROR: code = EVENT_IOT_CONNECTION_COMMUNICATION_ERROR; break; case IOTHUB_CLIENT_CONNECTION_OK: code = EVENT_IOT_CONNECTION_OK; break; default: code = EVENT_IOT_DISCONNECTED; } return code; } static void force_system_reboot(void *context) { LOGI("System reboot ..."); PowerManagement_ForceSystemReboot(); } static void force_app_reset(void *context) { LOGI("App reset..."); g_app_running = false; } static void ota_system_reboot(void *context) { LOGI("OTA System reboot ..."); diag_log_event_to_file(EVENT_OTA); write_property("target_app_version", (char*)context); PowerManagement_ForceSystemReboot(); } static void process_c2d_reset(void) { // reboot after 10s to give app a chance to ack the C2D message LOGI("Force app reset in 10s"); struct timespec ts = {10, 0}; s_iot.reset_timer = event_loop_register_timer(s_iot.eloop, &ts, NULL, force_app_reset, NULL); } static void process_c2d_debug(const char *payload, size_t payload_size) { int debug_level = 0; json_scanf(payload, payload_size, "{data:%d}", &debug_level); if (debug_level > 0) { llog_config(LOG_ENDPOINT_IOTHUB, debug_level); LOGI("Remote debug on"); } else { llog_config(LOG_ENDPOINT_CONSOLE, LOG_LEVEL); LOGI("Remote debug off"); } } static void process_c2d_reboot(void) { LOGI("Force system reboot in 10s"); struct timespec ts = {10, 0}; s_iot.reset_timer = event_loop_register_timer(s_iot.eloop, &ts, NULL, force_system_reboot, NULL); } static void process_c2d_ota_reboot(const char *payload, size_t payload_size) { char *target_app_version = NULL; json_scanf(payload, payload_size, "{target_app_version:%Q}", &target_app_version); if (target_app_version && strcmp(target_app_version, app_version()) != 0) { LOGI("Schedule App update: %s", target_app_version); struct timespec ts = { 1, 0 }; s_iot.reset_timer = event_loop_register_timer(s_iot.eloop, &ts, NULL, ota_system_reboot, target_app_version); } } static void handle_c2d(const char *payload, size_t payload_size) { char *command = NULL; json_scanf(payload, payload_size, "{command:%Q}", &command); if (!command) { LOGE("Invalid c2d message"); return; } if (strcmp(command, IOT_COMMAND_RESET) == 0) { process_c2d_reset(); } else if (strcmp(command, IOT_COMMAND_DUMPSYS) == 0) { // not supported for now } else if (strcmp(command, IOT_COMMAND_DEBUG) == 0) { process_c2d_debug(payload, payload_size); } else if (strcmp(command, IOT_COMMAND_REBOOT) == 0) { process_c2d_reboot(); } else if (strcmp(command, IOT_COMMAND_OTA_REBOOT) == 0) { process_c2d_ota_reboot(payload, payload_size); } else { LOGE("Invalid C2D command:%s", command); } FREE(command); } static void message_received(const char *payload, size_t payload_size, const char *message_type, void *context) { if (!payload || !payload_size || !message_type) { LOGW("empty C2D message received"); return; } LOGI("Received %s message [size=%ld]", message_type, payload_size); if (strcmp(message_type, IOT_MESSAGE_TYPE_PROVISION) == 0) { adapter_provision(payload, payload_size, true); } else if (strcmp(message_type, IOT_MESSAGE_TYPE_CONTROL) == 0) { handle_c2d(payload, payload_size); } else { LOGW("Unsupport message type: %s", message_type); } } static void scan_desired_twin(const char *str, int len, void *user_data) { char *target_app_version = NULL; json_scanf(str, len, "{target_app_version:%Q}", &target_app_version); if (target_app_version && (strcmp(app_version(), target_app_version) != 0)) { LOGI("Schedule APP update: %s", target_app_version); ota_system_reboot(target_app_version); } } static void scan_reported_twin(const char *str, int len, void *user_data) { // do nothing } static void device_twin_update(const char *payload, size_t payload_size, void *context) { LOGI("DEVICE_TWIN_UPDATE: %.*s", payload_size, payload); int result = json_scanf(payload, payload_size, "{desired:%M, reported:%M}", scan_desired_twin, NULL, scan_reported_twin, NULL); // device twin update contain desired and reported for the first time // following update will only contain desired section if (result <= 0) { scan_desired_twin(payload, payload_size, context); } } static void connection_status_changed(bool connected, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void *context) { static bool last_connected = false; LOGI("iot hub %s", (connected ? "connected" : "disconnected")); if (!last_connected && connected) { clock_gettime(CLOCK_BOOTTIME, &s_iot.ts_last_online); diag_log_event(EVENT_IOT_CONNECTED); } else if (last_connected && !connected) { clock_gettime(CLOCK_BOOTTIME, &s_iot.ts_last_offline); diag_log_event(iot_connection_status2event(reason)); } last_connected = connected; } static void log_setup_error_event(AZURE_SPHERE_PROV_RESULT result) { switch (result) { case AZURE_SPHERE_PROV_RESULT_DEVICEAUTH_NOT_READY: diag_log_event(EVENT_IOT_SETUP_FAILED_DEVICEAUTH); break; case AZURE_SPHERE_PROV_RESULT_NETWORK_NOT_READY: diag_log_event(EVENT_IOT_SETUP_FAILED_NO_NETWORK); break; case AZURE_SPHERE_PROV_RESULT_PROV_DEVICE_ERROR: diag_log_event(EVENT_IOT_SETUP_FAILED_DEVICE_ERROR); break; default: diag_log_event(EVENT_IOT_SETUP_FAILED); } } static void iot_setup_task(void *context) { if (azure_iot_is_connected()) return; if (!network_is_connected()) { LOGI("iot setup client failed due to no network"); return; } // we seen scenario that callback not been invoked even azure_iot_setup_client return ok AZURE_SPHERE_PROV_RESULT result = azure_iot_setup_client(IOT_MAX_INFLIGHT_MESSAGE_SIZE, NULL); if (result == AZURE_SPHERE_PROV_RESULT_OK) { LOGI("iot setup client ok"); } else { log_setup_error_event(result); } } static void iot_periodic_task(void *context) { azure_iot_do_periodic_tasks(); } static int init_sdk(void) { if (!azure_iot_initialize()) return -1; // Set the Azure IoT hub related callbacks azure_iot_set_message_received_callback(&message_received); azure_iot_set_device_twin_update_callback(&device_twin_update); azure_iot_set_connection_status_callback(&connection_status_changed); return 0; } static int schedule_setup_task(void) { // can't use 0 as that means disarm timer struct timespec init_setup = MS2SPEC(30*1000); struct timespec ts_setup = MS2SPEC(IOT_SETUP_RETRY_MS); s_iot.setup_timer = event_loop_register_timer(s_iot.eloop, &init_setup, &ts_setup, iot_setup_task, NULL); return s_iot.setup_timer ? 0 : -1; } static int schedule_periodic_task(void) { struct timespec init_periodic = MS2SPEC(1000); struct timespec ts_periodic = MS2SPEC(IOT_PERIODIC_TASK_MS); s_iot.periodic_timer = event_loop_register_timer(s_iot.eloop, &init_periodic, &ts_periodic, iot_periodic_task, NULL); return s_iot.periodic_timer ? 0 : -1; } // ------------------------------ public interface ---------------------------- int iot_init(EventLoop *eloop) { LOGI("iot init"); s_iot.eloop = eloop; if (init_sdk() != 0) { LOGE("Failed to initialize Azure IoT Hub SDK"); return -1; } if (schedule_setup_task() != 0) { LOGE("Failed to schedule setup task"); return -1; } if (schedule_periodic_task() != 0) { LOGE("Failed to schedule periodic task"); return -1; } clock_gettime(CLOCK_BOOTTIME, &s_iot.ts_last_offline); return 0; } void iot_deinit() { event_loop_unregister_timer(s_iot.eloop, s_iot.setup_timer); event_loop_unregister_timer(s_iot.eloop, s_iot.periodic_timer); if (s_iot.reset_timer) { event_loop_unregister_timer(s_iot.eloop, s_iot.reset_timer); } azure_iot_destroy_client(); azure_iot_deinitialize(); } int iot_send_message_async(const char *iot_message, const char *iot_message_type, message_delivery_confirmation_func_t callback, void *context) { ASSERT(iot_message); ASSERT(iot_message_type); if (!network_is_connected()) { LOGW("Can't send message as network not connected"); return -1; } if (!azure_iot_is_connected()) { LOGW("Can't send message as iot hub not connected"); return -1; } return azure_iot_send_message_async(iot_message, iot_message_type, callback, context); } int iot_report_device_twin_async(const char *properties, device_twin_delivery_confirmation_func_t callback, void *context) { ASSERT(properties); if (!network_is_connected()) { LOGW("Can't report device twin as network not connected"); return -1; } if (!azure_iot_is_connected()) { LOGW("Can't report device twin as iot hub not connected"); return -1; } return azure_iot_twin_report_async(properties, callback, context); } bool iot_is_connected(void) { return azure_iot_is_connected(); } struct timespec iot_last_online(void) { return s_iot.ts_last_online; } struct timespec iot_last_offline(void) { return s_iot.ts_last_offline; }