diff --git a/components/esp_rainmaker/src/core/esp_rmaker_core.c b/components/esp_rainmaker/src/core/esp_rmaker_core.c index e8a517b4..5a1b029b 100644 --- a/components/esp_rainmaker/src/core/esp_rmaker_core.c +++ b/components/esp_rainmaker/src/core/esp_rmaker_core.c @@ -294,9 +294,32 @@ static esp_err_t esp_rmaker_deinit_priv_data(esp_rmaker_priv_data_t *rmaker_priv free(rmaker_priv_data); return ESP_OK; } +static void esp_rmaker_end() +{ + ESP_LOGW(TAG, "rmaker end was called.."); + esp_err_t err = ESP_FAIL; +#ifdef CONFIG_ESP_RMAKER_LOCAL_CTRL_ENABLE + err = esp_rmaker_local_ctrl_disable(); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Failed to disable local control service..."); + } +#endif /* CONFIG_ESP_RMAKER_LOCAL_CTRL_ENABLE */ + if (esp_rmaker_priv_data->mqtt_connected) { + err = esp_rmaker_mqtt_disconnect(); + } + esp_rmaker_mqtt_deinit(); + esp_rmaker_priv_data->state = ESP_RMAKER_STATE_INIT_DONE; + if (err == ESP_OK) { + ESP_LOGI(TAG, "esp_rmaker_end returned successfully"); + } + else { + ESP_LOGE(TAG, "mqtt_disconnect failed!"); + } +} esp_err_t esp_rmaker_node_deinit(const esp_rmaker_node_t *node) { + esp_rmaker_end(); if (!esp_rmaker_priv_data) { ESP_LOGE(TAG, "ESP RainMaker already de-initialized."); return ESP_ERR_INVALID_ARG; @@ -420,7 +443,18 @@ static void esp_rmaker_task(void *data) /* Check if already connected to Wi-Fi */ if (esp_wifi_sta_get_ap_info(&ap_info) != ESP_OK) { /* Wait for Wi-Fi connection */ - xEventGroupWaitBits(rmaker_core_event_group, WIFI_CONNECTED_EVENT, false, true, portMAX_DELAY); + do { + EventBits_t evt_bits = xEventGroupWaitBits( + rmaker_core_event_group, WIFI_CONNECTED_EVENT, false, true, pdMS_TO_TICKS(2*1000)); + if (evt_bits) { + break; /* We got connected */ + } + if (esp_rmaker_priv_data->state == ESP_RMAKER_STATE_STOP_REQUESTED) { + ESP_LOGI(TAG, "Rainmaker stop was requested. Aborting!"); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &esp_rmaker_event_handler); + goto rmaker_end; + } + } while (true); } esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &esp_rmaker_event_handler); #elif defined(CONFIG_ESP_RMAKER_NETWORK_OVER_THREAD) /* CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI */ @@ -494,7 +528,18 @@ static void esp_rmaker_task(void *data) goto rmaker_end; } ESP_LOGI(TAG, "Waiting for MQTT connection"); - xEventGroupWaitBits(rmaker_core_event_group, MQTT_CONNECTED_EVENT, false, true, portMAX_DELAY); + do { + EventBits_t evt_bits = xEventGroupWaitBits( + rmaker_core_event_group, MQTT_CONNECTED_EVENT, false, true, pdMS_TO_TICKS(2 * 1000)); + if (evt_bits) { + break; /* We got connected */ + } + if (esp_rmaker_priv_data->state == ESP_RMAKER_STATE_STOP_REQUESTED) { + ESP_LOGI(TAG, "Rainmaker stop was requested. Aborting!"); + esp_event_handler_unregister(RMAKER_COMMON_EVENT, RMAKER_MQTT_EVENT_CONNECTED, &esp_rmaker_event_handler); + goto rmaker_end; + } + } while (true); esp_event_handler_unregister(RMAKER_COMMON_EVENT, RMAKER_MQTT_EVENT_CONNECTED, &esp_rmaker_event_handler); esp_rmaker_priv_data->state = ESP_RMAKER_STATE_STARTED; err = esp_rmaker_report_node_config(); @@ -533,19 +578,13 @@ static void esp_rmaker_task(void *data) ESP_LOGI(TAG, "Waiting for User Node Association."); } err = ESP_OK; - -rmaker_end: if (rmaker_core_event_group) { vEventGroupDelete(rmaker_core_event_group); } rmaker_core_event_group = NULL; - if (err == ESP_OK) { - return; - } - if (esp_rmaker_priv_data->mqtt_connected) { - esp_rmaker_mqtt_disconnect(); - } - esp_rmaker_priv_data->state = ESP_RMAKER_STATE_INIT_DONE; + return; +rmaker_end: + esp_rmaker_end(); } @@ -709,6 +748,18 @@ esp_err_t esp_rmaker_start(void) esp_err_t esp_rmaker_stop() { ESP_RMAKER_CHECK_HANDLE(ESP_ERR_INVALID_STATE); + /* Since, rmaker_work_queue might be getting used out of rainmaker, we do not stop it. + * Just, remove the reset handler... + */ + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(RMAKER_COMMON_EVENT, ESP_EVENT_ANY_ID, &reset_event_handler)); + + if (esp_rmaker_priv_data->state == ESP_RMAKER_STATE_STARTED) { + esp_rmaker_end(); + ESP_LOGI(TAG, "RainMaker stopped"); + return ESP_OK; + } + /* rmaker_task is still executing. rely on that task for stop. */ + ESP_LOGI(TAG, "Rainmaker stop was requested, from state %d", esp_rmaker_priv_data->state); esp_rmaker_priv_data->state = ESP_RMAKER_STATE_STOP_REQUESTED; return ESP_OK; } diff --git a/components/esp_rainmaker/src/core/esp_rmaker_node.c b/components/esp_rainmaker/src/core/esp_rmaker_node.c index 04a51316..c98f4a9a 100644 --- a/components/esp_rainmaker/src/core/esp_rmaker_node.c +++ b/components/esp_rainmaker/src/core/esp_rmaker_node.c @@ -27,6 +27,7 @@ #include "esp_rmaker_internal.h" static const char *TAG = "esp_rmaker_node"; +static bool node_created; static void esp_rmaker_node_info_free(esp_rmaker_node_info_t *info) { @@ -84,6 +85,7 @@ esp_err_t esp_rmaker_node_delete(const esp_rmaker_node_t *node) _esp_rmaker_device_t *next_device = device->next; device->parent = NULL; esp_rmaker_device_delete((esp_rmaker_device_t *)device); + free(device); device = next_device; } /* Node ID is created in the context of esp_rmaker_init and just assigned @@ -95,6 +97,8 @@ esp_err_t esp_rmaker_node_delete(const esp_rmaker_node_t *node) if (_node->info) { esp_rmaker_node_info_free(_node->info); } + free(_node); + node_created = false; return ESP_OK; } return ESP_ERR_INVALID_ARG; @@ -102,7 +106,6 @@ esp_err_t esp_rmaker_node_delete(const esp_rmaker_node_t *node) esp_rmaker_node_t *esp_rmaker_node_create(const char *name, const char *type) { - static bool node_created; if (node_created) { ESP_LOGE(TAG, "Node has already been created. Cannot create another"); return NULL; diff --git a/components/esp_rainmaker/src/core/esp_rmaker_param.c b/components/esp_rainmaker/src/core/esp_rmaker_param.c index 34e42c6e..d291ac7d 100644 --- a/components/esp_rainmaker/src/core/esp_rmaker_param.c +++ b/components/esp_rainmaker/src/core/esp_rmaker_param.c @@ -533,6 +533,13 @@ esp_err_t esp_rmaker_param_delete(const esp_rmaker_param_t *param) if (_param->ui_type) { free(_param->ui_type); } + if (_param->bounds) { + free(_param->bounds); + } + if (_param->val.type == RMAKER_VAL_TYPE_STRING || _param->val.type == RMAKER_VAL_TYPE_OBJECT || + _param->val.type == RMAKER_VAL_TYPE_ARRAY) { + free(_param->val.val.s); + } free(_param); return ESP_OK; } diff --git a/components/esp_rainmaker/test/CMakeLists.txt b/components/esp_rainmaker/test/CMakeLists.txt new file mode 100644 index 00000000..32a48938 --- /dev/null +++ b/components/esp_rainmaker/test/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "test_rmaker.c" + INCLUDE_DIRS "." + PRIV_INCLUDE_DIRS . ${CMAKE_CURRENT_BINARY_DIR} + PRIV_REQUIRES unity nvs_flash esp_rainmaker app_network app_insights test_utils espressif__network_provisioning) diff --git a/components/esp_rainmaker/test/README.md b/components/esp_rainmaker/test/README.md new file mode 100644 index 00000000..9a652b5a --- /dev/null +++ b/components/esp_rainmaker/test/README.md @@ -0,0 +1,73 @@ +# esp_rainmaker unit tests + +Please take a look at how to build, flash, and run [esp-idf unit tests](https://github.com/espressif/esp-idf/tree/master/tools/unit-test-app#unit-test-app). + +Follow the steps mentioned below to unit test the esp_rainmaker + +* Change to the unit test app directory +``` +cd $IDF_PATH/tools/unit-test-app +``` + +* Set RMAKER_PATH to esp-rainmaker directory +* Note: This is cloned `espressif/esp-rainmaker` directory and not its internal component `esp_rainmaker` +``` +export RMAKER_PATH=/path/to/esp-rainmaker +``` + +* Add following line to partition table csv file +``` +fctry, data, nvs, , 0x6000 +``` + +* Copy following contents into `sdkconfig.defaults` +``` +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table_unit_test_app.csv" + +# mbedtls +CONFIG_MBEDTLS_DYNAMIC_BUFFER=y +CONFIG_MBEDTLS_DYNAMIC_FREE_PEER_CERT=y +CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y + +# For BLE Provisioning using NimBLE stack (Not applicable for ESP32-S2) +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BT_NIMBLE_ENABLED=y + +# Temporary Fix for Timer Overflows +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 + +# For additional security on reset to factory +CONFIG_ESP_RMAKER_USER_ID_CHECK=y + +# Secure Local Control +CONFIG_ESP_RMAKER_LOCAL_CTRL_AUTO_ENABLE=y +#CONFIG_ESP_RMAKER_LOCAL_CTRL_ENABLE is deprecated but will continue to work +CONFIG_ESP_RMAKER_LOCAL_CTRL_SECURITY_1=y + +# Application Rollback +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y + +# If ESP-Insights is enabled, we need MQTT transport selected +# Takes out manual efforts to enable this option +CONFIG_ESP_INSIGHTS_TRANSPORT_MQTT=y + +``` +* Append `/path/to/esp-rainmaker/components` and `/path/to/esp-rainmaker/examples/common` directory to `EXTRA_COMPONENT_DIRS` in `CMakeLists.txt` + +## Build, flash and run tests +``` +# Clean any previous configuration and builds +rm -r sdkconfig build + +# Set the target +idf.py set-target esp32s3 + +# Building the firmware +idf.py -T esp_rainmaker build + +# Flash and run the test cases +idf.py -p -T esp_rainmaker flash monitor +``` diff --git a/components/esp_rainmaker/test/test_rmaker.c b/components/esp_rainmaker/test/test_rmaker.c new file mode 100644 index 00000000..4660518e --- /dev/null +++ b/components/esp_rainmaker/test/test_rmaker.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include "memory_checks.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "app_network.h" +#include "app_insights.h" + +esp_rmaker_device_t *light_device; + +static const char * TAG = "test_rmaker"; +bool config_done = false; +static const int repeat = 1; + +#define DEFAULT_POWER true +#define DEFAULT_HUE 180 +#define DEFAULT_SATURATION 100 +#define DEFAULT_BRIGHTNESS 25 + +static void init_nvs_flash(void) +{ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_LOGI(TAG, "nvs_flash_init"); + TEST_ASSERT(err == ESP_OK); +} + +static void test_config(void) +{ + if (!config_done) { + init_nvs_flash(); + app_network_init(); + test_utils_record_free_mem(); + config_done = true; + } +} + +/* Callback to handle param updates received from the RainMaker cloud */ +static esp_err_t bulk_write_cb(const esp_rmaker_device_t *device, const esp_rmaker_param_write_req_t write_req[], + uint8_t count, void *priv_data, esp_rmaker_write_ctx_t *ctx) +{ + if (ctx) { + ESP_LOGI(TAG, "Received write request via : %s", esp_rmaker_device_cb_src_to_str(ctx->src)); + } + ESP_LOGI(TAG, "Light received %d params in write", count); + // We will return ESP_OK as testing callbacks is currently out of scope of this test + return ESP_OK; +} + +TEST_CASE("nvs init deinit", "[rmaker]") +{ + init_nvs_flash(); + nvs_flash_deinit(); +} + +TEST_CASE("node init", "[rmaker]") +{ + test_config(); + for (int i = 0; i < repeat; i++) { + esp_rmaker_config_t rainmaker_cfg = { + .enable_time_sync = false, + }; + esp_rmaker_node_t *node = esp_rmaker_node_init(&rainmaker_cfg, "ESP RainMaker Device", "Lightbulb"); + TEST_ASSERT(node != NULL); + + vTaskDelay(1000 / portTICK_PERIOD_MS); + + TEST_ASSERT(esp_rmaker_node_deinit(node) == ESP_OK); + } +} + +TEST_CASE("add device", "[rmaker]") +{ + test_config(); + for (int i = 0; i < repeat; i++) { + esp_rmaker_config_t rainmaker_cfg = { + .enable_time_sync = false, + }; + esp_rmaker_node_t *node = esp_rmaker_node_init(&rainmaker_cfg, "ESP RainMaker Device", "Lightbulb"); + TEST_ASSERT(node != NULL); + + light_device = esp_rmaker_lightbulb_device_create("Light", NULL, DEFAULT_POWER); + TEST_ASSERT(light_device != NULL); + + esp_rmaker_device_add_param(light_device, esp_rmaker_brightness_param_create(ESP_RMAKER_DEF_BRIGHTNESS_NAME, DEFAULT_BRIGHTNESS)); + + int ret = esp_rmaker_node_add_device(node, light_device); + + TEST_ASSERT(ret == ESP_OK); + + vTaskDelay(1000 / portTICK_PERIOD_MS); + + TEST_ASSERT(esp_rmaker_node_deinit(node) == ESP_OK); + } +} + +TEST_CASE("rmaker start stop", "[rmaker]") +{ + test_config(); + for (int i = 0; i < repeat; i++) { + esp_rmaker_config_t rainmaker_cfg = { + .enable_time_sync = false, + }; + esp_rmaker_node_t *node = esp_rmaker_node_init(&rainmaker_cfg, "ESP RainMaker Device", "Lightbulb"); + TEST_ASSERT(node != NULL); + + light_device = esp_rmaker_lightbulb_device_create("Light", NULL, DEFAULT_POWER); + TEST_ASSERT(light_device != NULL); + + TEST_ASSERT(esp_rmaker_device_add_bulk_cb(light_device, bulk_write_cb, NULL) == ESP_OK); + + esp_rmaker_device_add_param(light_device, esp_rmaker_brightness_param_create(ESP_RMAKER_DEF_BRIGHTNESS_NAME, DEFAULT_BRIGHTNESS)); + esp_rmaker_device_add_param(light_device, esp_rmaker_hue_param_create(ESP_RMAKER_DEF_HUE_NAME, DEFAULT_HUE)); + esp_rmaker_device_add_param(light_device, esp_rmaker_saturation_param_create(ESP_RMAKER_DEF_SATURATION_NAME, DEFAULT_SATURATION)); + + int ret = esp_rmaker_node_add_device(node, light_device); + TEST_ASSERT(ret == ESP_OK); + + TEST_ASSERT(esp_rmaker_start() == ESP_OK); + + TEST_ASSERT(app_network_start(POP_TYPE_RANDOM) == ESP_OK); + + ESP_LOGD(TAG, "Will wait for 1 seconds before calling esp_rmaker_stop"); + vTaskDelay(5*1000 / portTICK_PERIOD_MS); + + TEST_ASSERT(esp_rmaker_stop() == ESP_OK); + TEST_ASSERT(esp_rmaker_node_deinit(node) == ESP_OK); + } +}