#include #include #include #include "sdkconfig.h" #if CONFIG_SSCMA_ENABLE_DEBUG_LOG // The local log level must be defined before including esp_log.h // Set the maximum log level for this source file #define LOG_SSCMA_LEVEL ESP_LOG_DEBUG #endif #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/list.h" #include "cJSON.h" #include "mbedtls/base64.h" #include "esp_log.h" #include "esp_check.h" #include "esp_timer.h" #include "sscma_client_types.h" #include "sscma_client_io.h" #include "sscma_client_flasher.h" #include "sscma_client_commands.h" #include "sscma_client_ops.h" static const char *TAG = "sscma_client"; const int error_map[] = { ESP_OK, ESP_ERR_NOT_FINISHED, ESP_FAIL, ESP_ERR_TIMEOUT, ESP_ERR_INVALID_RESPONSE, ESP_ERR_INVALID_ARG, ESP_ERR_NO_MEM, ESP_ERR_INVALID_STATE, ESP_ERR_NOT_SUPPORTED, ESP_FAIL, }; #define SSCMA_CLIENT_CMD_ERROR_CODE(err) (error_map[(err & 0x0F) > (CMD_EUNKNOWN - 1) ? (CMD_EUNKNOWN - 1) : (err & 0x0F)]) static inline void *__malloc(size_t sz) { #ifdef CONFIG_SSCMA_ALLOC_SMALL_SHORTTERM_MEM_EXTERNALLY return heap_caps_calloc(1, sz, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); #else return malloc(sz); #endif } static char *__strdup(const char *s) { #ifdef CONFIG_SSCMA_ALLOC_SMALL_SHORTTERM_MEM_EXTERNALLY size_t len = strlen(s) + 1; void *new = heap_caps_calloc(1, len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); if (new == NULL) return NULL; return (char *)memcpy(new, s, len); #else return strdup(s); #endif } static inline void fetch_string_common(cJSON *object, cJSON *field, char **target) { if (field == NULL || !cJSON_IsString(field)) { *target = NULL; return; } if (*target != NULL) { free(*target); *target = NULL; } *target = __strdup(field->valuestring); } static inline void fetch_string_from_object(cJSON *object, const char *field_name, char **target) { if (object == NULL || !cJSON_IsObject(object)) { *target = NULL; return; } cJSON *field = cJSON_GetObjectItem(object, field_name); fetch_string_common(object, field, target); } static inline void fetch_string_from_array(cJSON *object, int index, char **target) { if (object == NULL || !cJSON_IsArray(object)) { *target = NULL; return; } cJSON *field = cJSON_GetArrayItem(object, index); fetch_string_common(object, field, target); } static inline int get_int_from_object(cJSON *object, const char *field_name) { if (object == NULL || !cJSON_IsObject(object)) { return INT_MIN; } cJSON *field = cJSON_GetObjectItem(object, field_name); if (field == NULL || !cJSON_IsNumber(field)) { return INT_MIN; } return field->valueint; } static inline int get_int_from_array(cJSON *object, int index) { if (object == NULL || !cJSON_IsArray(object)) { return INT_MIN; } cJSON *field = cJSON_GetArrayItem(object, index); if (field == NULL || !cJSON_IsNumber(field)) { return INT_MIN; } return field->valueint; } void sscma_client_reply_clear(sscma_client_reply_t *reply) { if (reply->payload) { cJSON_Delete(reply->payload); reply->payload = NULL; } if (reply->data) { free(reply->data); reply->data = NULL; } reply->len = 0; } static void sscma_client_monitor(void *arg) { sscma_client_handle_t client = (sscma_client_handle_t)arg; sscma_client_reply_t reply; while (true) { xQueueReceive(client->reply_queue, &reply, portMAX_DELAY); cJSON *type = cJSON_GetObjectItem(reply.payload, "type"); if (type == NULL) { sscma_client_reply_clear(&reply); continue; } if (client->on_connect) { cJSON *name = cJSON_GetObjectItem(reply.payload, "name"); if (name != NULL && strnstr(name->valuestring, EVENT_INIT, strlen(name->valuestring)) != NULL) { client->on_connect(client, &reply, client->user_ctx); sscma_client_reply_clear(&reply); continue; } } if (type->valueint == CMD_TYPE_EVENT) { if (client->on_event) { client->on_event(client, &reply, client->user_ctx); } } else if (type->valueint == CMD_TYPE_LOG) { if (client->on_log) { client->on_log(client, &reply, client->user_ctx); } } else { if (client->on_response) { client->on_response(client, &reply, client->user_ctx); } } sscma_client_reply_clear(&reply); } } static void sscma_client_process(void *arg) { size_t rlen = 0; char *suffix = NULL; char *prefix = NULL; sscma_client_handle_t client = (sscma_client_handle_t)arg; sscma_client_reply_t reply; while (true) { vTaskDelay(10 / portTICK_PERIOD_MS); if (client->inited == false) { continue; } if (sscma_client_available(client, &rlen) == ESP_OK && rlen) { if (rlen + client->rx_buffer.pos > client->rx_buffer.len) { rlen = client->rx_buffer.len - client->rx_buffer.pos; if (rlen <= 0) { ESP_LOGW(TAG, "rx buffer is full"); client->rx_buffer.pos = 0; continue; } } sscma_client_read(client, client->rx_buffer.data + client->rx_buffer.pos, rlen); client->rx_buffer.pos += rlen; int new_pos = 0; for (int i = 0; i < client->rx_buffer.pos; i++) { if (client->rx_buffer.data[i] != '\0') { client->rx_buffer.data[new_pos++] = client->rx_buffer.data[i]; } } client->rx_buffer.pos = new_pos; client->rx_buffer.data[client->rx_buffer.pos] = 0; while ((suffix = strnstr(client->rx_buffer.data, RESPONSE_SUFFIX, client->rx_buffer.pos)) != NULL) { if ((prefix = strnstr(client->rx_buffer.data, RESPONSE_PREFIX, suffix - client->rx_buffer.data)) != NULL) { int len = suffix - prefix + RESPONSE_SUFFIX_LEN; reply.data = (char *)__malloc(len + 1); if (reply.data != NULL) { reply.len = len; memcpy(reply.data, prefix, len); // delete this reply from rx buffer memmove(client->rx_buffer.data, suffix + RESPONSE_SUFFIX_LEN, client->rx_buffer.pos - (suffix - client->rx_buffer.data) - RESPONSE_PREFIX_LEN); client->rx_buffer.pos -= len; reply.data[len] = 0; reply.payload = cJSON_Parse(reply.data); if (reply.payload != NULL) { cJSON *type = cJSON_GetObjectItem(reply.payload, "type"); cJSON *name = cJSON_GetObjectItem(reply.payload, "name"); if (type == NULL || name == NULL) { ESP_LOGW(TAG, "invalid reply: %s", reply.data); sscma_client_reply_clear(&reply); continue; } if (client->on_connect) { if (name != NULL && strnstr(name->valuestring, EVENT_INIT, strlen(name->valuestring)) != NULL) { xQueueReset(client->reply_queue); // reset reply queue if (xQueueSend(client->reply_queue, &reply, 0) != pdTRUE) { sscma_client_reply_clear(&reply); } continue; } } if (type->valueint == CMD_TYPE_RESPONSE) { sscma_client_request_t *first_req, *next_req = NULL; bool found = false; if (listCURRENT_LIST_LENGTH(client->request_list) > (UBaseType_t)0) { listGET_OWNER_OF_NEXT_ENTRY(first_req, client->request_list); do { listGET_OWNER_OF_NEXT_ENTRY(next_req, client->request_list); if (strncmp(next_req->cmd, name->valuestring, sizeof(next_req->cmd)) == 0) { if (next_req->reply) { found = true; if (xQueueSend(next_req->reply, &reply, 0) != pdTRUE) { sscma_client_reply_clear(&reply); // discard this reply } break; } } } while (next_req != first_req); } if (!found) { ESP_LOGW(TAG, "request not found: %s", name->valuestring); if (client->on_response == NULL || xQueueSend(client->reply_queue, &reply, 0) != pdTRUE) { sscma_client_reply_clear(&reply); // discard this reply } } } else if (type->valueint == CMD_TYPE_LOG) { cJSON *code = cJSON_GetObjectItem(reply.payload, "code"); if (code == NULL) { ESP_LOGW(TAG, "invalid log: %s", reply.data); sscma_client_reply_clear(&reply); continue; } if (code->valueint == CMD_EINVAL) { // unkown command cJSON *data = cJSON_GetObjectItem(reply.payload, "data"); if (data == NULL) { ESP_LOGW(TAG, "invalid log: %s", reply.data); sscma_client_reply_clear(&reply); continue; } sscma_client_request_t *first_req, *next_req = NULL; bool found = false; if (listCURRENT_LIST_LENGTH(client->request_list) > (UBaseType_t)0) { listGET_OWNER_OF_NEXT_ENTRY(first_req, client->request_list); do { listGET_OWNER_OF_NEXT_ENTRY(next_req, client->request_list); if (strnstr(data->valuestring, next_req->cmd, strlen(data->valuestring)) != NULL) { if (next_req->reply) { found = true; if (xQueueSend(next_req->reply, &reply, 0) != pdTRUE) { sscma_client_reply_clear(&reply); // discard this reply } break; } } } while (next_req != first_req); } if (!found) { ESP_LOGW(TAG, "request not found: %s", name->valuestring); if (client->on_log == NULL || xQueueSend(client->reply_queue, &reply, 0) != pdTRUE) { sscma_client_reply_clear(&reply); // discard this reply } } } else { if (client->on_log == NULL || xQueueSend(client->reply_queue, &reply, 0) != pdTRUE) { sscma_client_reply_clear(&reply); // discard this reply } } } else if (type->valueint == CMD_TYPE_EVENT) { sscma_client_request_t *first_req, *next_req = NULL; bool found = false; // discard all the events while AT+BREAK is found if (listCURRENT_LIST_LENGTH(client->request_list) > (UBaseType_t)0) { listGET_OWNER_OF_NEXT_ENTRY(first_req, client->request_list); do { listGET_OWNER_OF_NEXT_ENTRY(next_req, client->request_list); if (strnstr(next_req->cmd, CMD_AT_BREAK, strlen(next_req->cmd)) != NULL) { found = true; break; } } while (next_req != first_req); } if (client->on_event == NULL || found || xQueueSend(client->reply_queue, &reply, 0) != pdTRUE) { sscma_client_reply_clear(&reply); // discard this reply } } else { ESP_LOGW(TAG, "Invalid reply: %s", reply.data); sscma_client_reply_clear(&reply); } } else { ESP_LOGW(TAG, "Invalid reply: %s cc", reply.data); sscma_client_reply_clear(&reply); } }else{ printf("reply.data == NULL\r\n"); client->rx_buffer.pos -= len; } } else { // discard this reply ESP_LOGW(TAG, "Invalid reply: %d/%d", client->rx_buffer.pos, client->rx_buffer.len); memmove(client->rx_buffer.data, suffix + RESPONSE_SUFFIX_LEN, client->rx_buffer.pos - (suffix - client->rx_buffer.data) - RESPONSE_PREFIX_LEN); client->rx_buffer.pos -= suffix - client->rx_buffer.data + RESPONSE_SUFFIX_LEN; client->rx_buffer.data[client->rx_buffer.pos] = 0; } } } } } esp_err_t sscma_client_new(const sscma_client_io_handle_t io, const sscma_client_config_t *config, sscma_client_handle_t *ret_client) { #if CONFIG_SSCMA_ENABLE_DEBUG_LOG esp_log_level_set(TAG, ESP_LOG_DEBUG); #endif esp_err_t ret = ESP_OK; BaseType_t res; sscma_client_handle_t client = NULL; ESP_GOTO_ON_FALSE(io && config && ret_client, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); client = (sscma_client_handle_t)malloc(sizeof(struct sscma_client_t)); memset(client, 0, sizeof(struct sscma_client_t)); ESP_GOTO_ON_FALSE(client, ESP_ERR_NO_MEM, err, TAG, "no mem for sscma client"); client->io = io; client->inited = false; client->flasher = NULL; if (config->reset_gpio_num >= 0) { if (config->flags.reset_use_expander) { client->io_expander = config->io_expander; ESP_GOTO_ON_FALSE(client->io_expander, ESP_ERR_INVALID_ARG, err, TAG, "invalid io expander"); ESP_GOTO_ON_ERROR(esp_io_expander_set_dir(client->io_expander, config->reset_gpio_num, IO_EXPANDER_OUTPUT), err, TAG, "set GPIO direction failed"); } else { gpio_config_t io_conf = { .mode = GPIO_MODE_INPUT, .pin_bit_mask = 1ULL << config->reset_gpio_num, }; ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); } } client->rx_buffer.data = (char *)malloc(config->rx_buffer_size); ESP_GOTO_ON_FALSE(client->rx_buffer.data, ESP_ERR_NO_MEM, err, TAG, "no mem for rx buffer"); client->rx_buffer.pos = 0; client->rx_buffer.len = config->rx_buffer_size; client->tx_buffer.data = (char *)malloc(config->tx_buffer_size); ESP_GOTO_ON_FALSE(client->tx_buffer.data, ESP_ERR_NO_MEM, err, TAG, "no mem for tx buffer"); client->tx_buffer.pos = 0; client->tx_buffer.len = config->tx_buffer_size; client->reset_gpio_num = config->reset_gpio_num; client->reset_level = config->flags.reset_active_high; client->user_ctx = config->user_ctx; client->request_list = (List_t *)malloc(sizeof(List_t)); ESP_GOTO_ON_FALSE(client->request_list, ESP_ERR_NO_MEM, err, TAG, "no mem for request list"); client->reply_queue = xQueueCreate(config->event_queue_size, sizeof(sscma_client_reply_t)); ESP_GOTO_ON_FALSE(client->reply_queue, ESP_ERR_NO_MEM, err, TAG, "no mem for reply queue"); vListInitialise(client->request_list); #ifdef CONFIG_SSCMA_PROCESS_TASK_STACK_ALLOC_EXTERNAL client->process_task.task = heap_caps_calloc(1, sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); ESP_GOTO_ON_FALSE(client->process_task.task, ESP_ERR_NO_MEM, err, TAG, "no mem for sscma client process task"); client->process_task.stack = heap_caps_calloc(1, config->process_task_stack * sizeof(StackType_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); ESP_GOTO_ON_FALSE(client->process_task.stack, ESP_ERR_NO_MEM, err, TAG, "no mem for sscma client process task stack"); if (config->process_task_affinity < 0) { client->process_task.handle = xTaskCreateStatic(sscma_client_process, "sscma_client_process", config->process_task_stack, client, config->process_task_priority, client->process_task.stack, client->process_task.task); } else { client->process_task.handle = xTaskCreateStaticPinnedToCore(sscma_client_process, "sscma_client_process", config->process_task_stack, client, config->process_task_priority, client->process_task.stack, client->process_task.task, config->process_task_affinity); } ESP_GOTO_ON_FALSE(client->process_task.handle, ESP_FAIL, err, TAG, "create process task failed"); #else if (config->process_task_affinity < 0) { res = xTaskCreate(sscma_client_process, "sscma_client_process", config->process_task_stack, client, config->process_task_priority, &client->process_task.handle); } else { res = xTaskCreatePinnedToCore(sscma_client_process, "sscma_client_process", config->process_task_stack, client, config->process_task_priority, &client->process_task.handle, config->process_task_affinity); } ESP_GOTO_ON_FALSE(res == pdPASS, ESP_FAIL, err, TAG, "create process task failed"); #endif #ifdef CONFIG_SSCMA_PROCESS_TASK_STACK_ALLOC_EXTERNAL client->monitor_task.task = heap_caps_calloc(1, sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); ESP_GOTO_ON_FALSE(client->monitor_task.task, ESP_ERR_NO_MEM, err, TAG, "no mem for sscma client monitor task"); client->monitor_task.stack = heap_caps_calloc(1, config->monitor_task_stack * sizeof(StackType_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); ESP_GOTO_ON_FALSE(client->monitor_task.stack, ESP_ERR_NO_MEM, err, TAG, "no mem for sscma client monitor task stack"); if (config->monitor_task_affinity < 0) { client->monitor_task.handle = xTaskCreateStatic(sscma_client_monitor, "sscma_client_monitor", config->monitor_task_stack, client, config->monitor_task_priority, client->monitor_task.stack, client->monitor_task.task); } else { client->monitor_task.handle = xTaskCreateStaticPinnedToCore(sscma_client_monitor, "sscma_client_monitor", config->monitor_task_stack, client, config->monitor_task_priority, client->monitor_task.stack, client->monitor_task.task, config->monitor_task_affinity); } ESP_GOTO_ON_FALSE(client->monitor_task.handle, ESP_FAIL, err, TAG, "create monitor task failed"); #else if (config->monitor_task_affinity < 0) { res = xTaskCreate(sscma_client_monitor, "sscma_client_monitor", config->monitor_task_stack, client, config->monitor_task_priority, &client->monitor_task.handle); } else { res = xTaskCreatePinnedToCore(sscma_client_monitor, "sscma_client_monitor", config->monitor_task_stack, client, config->monitor_task_priority, &client->monitor_task.handle, config->monitor_task_affinity); } ESP_GOTO_ON_FALSE(res == pdPASS, ESP_FAIL, err, TAG, "create monitor task failed"); #endif client->on_connect = NULL; client->on_disconnect = NULL; client->on_response = NULL; client->on_event = NULL; client->on_log = NULL; *ret_client = client; ESP_LOGD(TAG, "new sscma client @%p", client); return ESP_OK; err: if (client) { if (client->rx_buffer.data) { free(client->rx_buffer.data); } if (client->tx_buffer.data) { free(client->tx_buffer.data); } if (config->reset_gpio_num >= 0) { if (!config->flags.reset_use_expander) { gpio_reset_pin(client->reset_gpio_num); } } if (client->reply_queue) { vQueueDelete(client->reply_queue); } if (client->request_list) { free(client->request_list); } if (client->process_task.handle) { vTaskDelete(client->process_task.handle); #ifdef CONFIG_SSCMA_PROCESS_TASK_STACK_ALLOC_EXTERNAL if (client->process_task.stack) { free(client->process_task.stack); } if (client->process_task.task) { free(client->process_task.task); } #endif } if (client->monitor_task.handle) { vTaskDelete(client->monitor_task.handle); #ifdef CONFIG_SSCMA_MONITOR_TASK_STACK_ALLOC_EXTERNAL if (client->monitor_task.stack) { free(client->monitor_task.stack); } if (client->monitor_task.task) { free(client->monitor_task.task); } #endif } free(client); } return ret; } esp_err_t sscma_client_del(sscma_client_handle_t client) { if (client) { if (client->reset_gpio_num >= 0) { if (client->io_expander) { esp_io_expander_set_dir(client->io_expander, client->reset_gpio_num, IO_EXPANDER_OUTPUT); esp_io_expander_set_level(client->io_expander, client->reset_gpio_num, client->reset_level); esp_io_expander_set_dir(client->io_expander, client->reset_gpio_num, IO_EXPANDER_INPUT); } else { gpio_config_t io_conf = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << client->reset_gpio_num, }; gpio_config(&io_conf); gpio_set_level(client->reset_gpio_num, client->reset_level); gpio_reset_pin(client->reset_gpio_num); } } vQueueDelete(client->reply_queue); sscma_client_request_t *first_req, *next_req = NULL; if (listCURRENT_LIST_LENGTH(client->request_list) > (UBaseType_t)0) { listGET_OWNER_OF_NEXT_ENTRY(first_req, client->request_list); do { listGET_OWNER_OF_NEXT_ENTRY(next_req, client->request_list); uxListRemove(&(next_req->item)); vQueueDelete(next_req->reply); free(next_req); } while (next_req != first_req); } free(client->request_list); free(client->rx_buffer.data); free(client->tx_buffer.data); vTaskDelete(client->process_task.handle); vTaskDelete(client->monitor_task.handle); #ifdef CONFIG_SSCMA_PROCESS_TASK_STACK_ALLOC_EXTERNAL free(client->process_task.stack); free(client->process_task.task); #endif #ifdef CONFIG_SSCMA_MONITOR_TASK_STACK_ALLOC_EXTERNAL free(client->monitor_task.stack); free(client->monitor_task.task); #endif if (client->info.id != NULL) { free(client->info.id); } if (client->info.name != NULL) { free(client->info.name); } if (client->info.hw_ver != NULL) { free(client->info.hw_ver); } if (client->info.sw_ver != NULL) { free(client->info.sw_ver); } if (client->info.fw_ver != NULL) { free(client->info.fw_ver); } if (client->model.uuid != NULL) { free(client->model.uuid); } if (client->model.name != NULL) { free(client->model.name); } if (client->model.ver != NULL) { free(client->model.ver); } if (client->model.url) { free(client->model.url); } if (client->model.checksum) { free(client->model.checksum); } for (int i = 0; i < sizeof(client->model.classes) / sizeof(client->model.classes[0]); i++) { if (client->model.classes[i] != NULL) { free(client->model.classes[i]); } } free(client); } return ESP_OK; } esp_err_t sscma_client_init(sscma_client_handle_t client) { if (!client->inited) { sscma_client_reset(client); client->inited = true; } memset(&client->info, 0, sizeof(sscma_client_info_t)); memset(&client->model, 0, sizeof(sscma_client_model_t)); return ESP_OK; } esp_err_t sscma_client_reset(sscma_client_handle_t client) { esp_err_t ret = ESP_OK; vTaskSuspend(client->process_task.handle); client->rx_buffer.pos = 0; client->tx_buffer.pos = 0; // perform hardware reset if (client->reset_gpio_num >= 0) { if (client->io_expander) { esp_io_expander_set_level(client->io_expander, client->reset_gpio_num, client->reset_level); vTaskDelay(100 / portTICK_PERIOD_MS); esp_io_expander_set_level(client->io_expander, client->reset_gpio_num, !client->reset_level); vTaskDelay(200 / portTICK_PERIOD_MS); } else { gpio_config_t io_conf = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << client->reset_gpio_num, }; gpio_config(&io_conf); gpio_set_level(client->reset_gpio_num, client->reset_level); vTaskDelay(100 / portTICK_PERIOD_MS); gpio_set_level(client->reset_gpio_num, !client->reset_level); vTaskDelay(200 / portTICK_PERIOD_MS); gpio_reset_pin(client->reset_gpio_num); } } else { // ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_RESET CMD_SUFFIX, NULL, false, 0), TAG, "request reset failed"); vTaskDelay(500 / portTICK_PERIOD_MS); // wait for sscma to be ready } vTaskResume(client->process_task.handle); return ret; } esp_err_t sscma_client_read(sscma_client_handle_t client, void *data, size_t size) { return sscma_client_io_read(client->io, data, size); } esp_err_t sscma_client_write(sscma_client_handle_t client, const void *data, size_t size) { return sscma_client_io_write(client->io, data, size); } esp_err_t sscma_client_available(sscma_client_handle_t client, size_t *ret_avail) { return sscma_client_io_available(client->io, ret_avail); } esp_err_t sscma_client_register_callback(sscma_client_handle_t client, const sscma_client_callback_t *callback, void *user_ctx) { vTaskSuspend(client->process_task.handle); if (client->on_event != NULL) { ESP_LOGW(TAG, "callback on_event already registered, overriding it"); } if (client->on_log != NULL) { ESP_LOGW(TAG, "callback on_log already registered, overriding it"); } client->on_connect = callback->on_connect; client->on_disconnect = callback->on_disconnect; client->on_response = callback->on_response; client->on_event = callback->on_event; client->on_log = callback->on_log; client->user_ctx = user_ctx; vTaskResume(client->process_task.handle); return ESP_OK; } esp_err_t sscma_client_request(sscma_client_handle_t client, const char *cmd, sscma_client_reply_t *reply, bool wait, TickType_t timeout) { esp_err_t ret = ESP_OK; sscma_client_request_t *request = NULL; if (wait) { request = (sscma_client_request_t *)__malloc(sizeof(sscma_client_request_t)); ESP_GOTO_ON_FALSE(request, ESP_ERR_NO_MEM, err, TAG, "no mem for request"); request->reply = xQueueCreate(1, sizeof(sscma_client_reply_t)); strncpy(request->cmd, &cmd[CMD_PREFIX_LEN], sizeof(request->cmd) - 1); request->cmd[sizeof(request->cmd) - 1] = '\0'; for (int i = 0; i < sizeof(request->cmd); i++) { if (request->cmd[i] == '\n' || request->cmd[i] == '\r' || request->cmd[i] == '=') { request->cmd[i] = '\0'; } } ESP_GOTO_ON_FALSE(request->reply, ESP_ERR_NO_MEM, err, TAG, "no mem for reply"); vListInitialiseItem(&(request->item)); listSET_LIST_ITEM_OWNER(&(request->item), request); vListInsertEnd(client->request_list, &(request->item)); } ESP_GOTO_ON_ERROR(sscma_client_write(client, cmd, strlen(cmd)), err, TAG, "write command failed"); if (wait) { if (xQueueReceive(request->reply, reply, timeout) == pdTRUE) { ret = ESP_OK; } else { ret = ESP_ERR_TIMEOUT; } } err: if (wait) { if (listIS_CONTAINED_WITHIN(client->request_list, &(request->item))) { uxListRemove(&(request->item)); } if (request) { vQueueDelete(request->reply); free(request); } } return ret; } esp_err_t sscma_client_get_info(sscma_client_handle_t client, sscma_client_info_t **info, bool cached) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; *info = &client->info; if (cached && client->info.id != NULL) { return ret; } ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_ID CMD_QUERY CMD_SUFFIX, &reply, true, CMD_WAIT_DELAY), TAG, "request id failed"); if (reply.payload != NULL) { fetch_string_from_object(reply.payload, "data", &client->info.id); sscma_client_reply_clear(&reply); } ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_NAME CMD_QUERY CMD_SUFFIX, &reply, true, CMD_WAIT_DELAY), TAG, "request name failed"); if (reply.payload != NULL) { fetch_string_from_object(reply.payload, "data", &(client->info.name)); sscma_client_reply_clear(&reply); } ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_VERSION CMD_QUERY CMD_SUFFIX, &reply, true, CMD_WAIT_DELAY), TAG, "request version failed"); if (reply.payload != NULL) { cJSON *data = cJSON_GetObjectItem(reply.payload, "data"); if (data != NULL) { fetch_string_from_object(data, "hardware", &(client->info.hw_ver)); fetch_string_from_object(data, "software", &(client->info.fw_ver)); fetch_string_from_object(data, "at_api", &(client->info.sw_ver)); } sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_get_model(sscma_client_handle_t client, sscma_client_model_t **model, bool cached) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; bool is_changed = false; char *model_data = NULL; size_t len = 0; *model = &client->model; ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_MODEL CMD_QUERY CMD_SUFFIX, &reply, true, CMD_WAIT_DELAY), TAG, "request model failed"); if (reply.payload != NULL) { cJSON *data = cJSON_GetObjectItem(reply.payload, "data"); if (data != NULL) { if (client->model.id != get_int_from_object(data, "id")) { is_changed = true; client->model.id = get_int_from_object(data, "id"); } } sscma_client_reply_clear(&reply); } if (cached && !is_changed && client->model.uuid != NULL) { return ret; } ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_INFO CMD_QUERY CMD_SUFFIX, &reply, true, CMD_WAIT_DELAY), TAG, "request model failed"); if (reply.payload != NULL) { cJSON *data = cJSON_GetObjectItem(reply.payload, "data"); if (data != NULL) { cJSON *info = cJSON_GetObjectItem(data, "info"); if (info != NULL && cJSON_IsString(info)) { model_data = __malloc(strlen(info->valuestring)); if (model_data != NULL) { if (mbedtls_base64_decode((unsigned char *)model_data, strlen(info->valuestring), &len, (unsigned char *)info->valuestring, strlen(info->valuestring)) == 0) { cJSON *root = cJSON_Parse(model_data); if (root != NULL) { // parse model infomation // old format if (cJSON_GetObjectItem(root, "uuid") != NULL) { fetch_string_from_object(root, "uuid", &client->model.uuid); fetch_string_from_object(root, "name", &client->model.name); fetch_string_from_object(root, "version", &client->model.ver); fetch_string_from_object(root, "url", &client->model.url); fetch_string_from_object(root, "checksum", &client->model.checksum); cJSON *classes = cJSON_GetObjectItem(root, "classes"); if (classes != NULL && cJSON_IsArray(classes)) { int classes_len = cJSON_GetArraySize(classes) > SSCMA_CLIENT_MODEL_MAX_CLASSES ? SSCMA_CLIENT_MODEL_MAX_CLASSES : cJSON_GetArraySize(classes); memset(client->model.classes, 0, sizeof(client->model.classes)); for (int i = 0; i < classes_len; i++) { fetch_string_from_array(classes, i, &client->model.classes[i]); } } } // new format else if (cJSON_GetObjectItem(root, "model_id") != NULL) { cJSON *model_id = cJSON_GetObjectItem(root, "model_id"); if (cJSON_IsString(model_id)) { fetch_string_from_object(root, "model_id", &client->model.uuid); } else if (cJSON_IsNumber(model_id)) { if (client->model.uuid != NULL) { free(client->model.uuid); } client->model.uuid = __malloc(32); sprintf(client->model.uuid, "%d", model_id->valueint); } fetch_string_from_object(root, "model_name", &client->model.name); fetch_string_from_object(root, "version", &client->model.ver); fetch_string_from_object(root, "url", &client->model.url); fetch_string_from_object(root, "checksum", &client->model.checksum); cJSON *classes = cJSON_GetObjectItem(root, "classes"); if (classes != NULL && cJSON_IsArray(classes)) { int classes_len = cJSON_GetArraySize(classes) > SSCMA_CLIENT_MODEL_MAX_CLASSES ? SSCMA_CLIENT_MODEL_MAX_CLASSES : cJSON_GetArraySize(classes); memset(client->model.classes, 0, sizeof(client->model.classes)); for (int i = 0; i < classes_len; i++) { fetch_string_from_array(classes, i, &client->model.classes[i]); } } } } cJSON_Delete(root); } } free(model_data); } } sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_set_model(sscma_client_handle_t client, int model) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; int code = 0; char cmd[64] = { 0 }; snprintf(cmd, sizeof(cmd), CMD_PREFIX CMD_AT_MODEL CMD_SET "%d" CMD_SUFFIX, model); ESP_RETURN_ON_ERROR(sscma_client_request(client, cmd, &reply, true, CMD_WAIT_DELAY), TAG, "request model failed"); if (reply.payload != NULL) { code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_sample(sscma_client_handle_t client, int times) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; int code = 0; char cmd[64] = { 0 }; snprintf(cmd, sizeof(cmd), CMD_PREFIX CMD_AT_SAMPLE CMD_SET "%d" CMD_SUFFIX, times); ESP_RETURN_ON_ERROR(sscma_client_request(client, cmd, &reply, true, CMD_WAIT_DELAY), TAG, "request sample failed"); if (reply.payload != NULL) { code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_invoke(sscma_client_handle_t client, int times, bool fliter, bool show) { esp_err_t ret = ESP_OK; char cmd[64] = { 0 }; int code = 0; sscma_client_reply_t reply; snprintf(cmd, sizeof(cmd), CMD_PREFIX CMD_AT_INVOKE CMD_SET "%d,%d,%d" CMD_SUFFIX, times, fliter ? 1 : 0, show ? 0 : 1); ESP_RETURN_ON_ERROR(sscma_client_request(client, cmd, &reply, true, CMD_WAIT_DELAY), TAG, "request invoke failed"); if (reply.payload != NULL) { code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_set_sensor(sscma_client_handle_t client, int id, int opt_id, bool enable) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; char cmd[64] = { 0 }; snprintf(cmd, sizeof(cmd), CMD_PREFIX CMD_AT_SENSOR CMD_SET "%d,%d,%d" CMD_SUFFIX, id, enable ? 1 : 0, opt_id); ESP_RETURN_ON_ERROR(sscma_client_request(client, cmd, &reply, true, CMD_WAIT_DELAY), TAG, "request set sensor failed"); if (reply.payload != NULL) { int code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_get_sensor(sscma_client_handle_t client, sscma_client_sensor_t *sensor) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_SENSOR CMD_QUERY CMD_SUFFIX, &reply, true, CMD_WAIT_DELAY), TAG, "request get sensor failed"); if (reply.payload != NULL) { int code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); if (ret == ESP_OK) { cJSON *data = cJSON_GetObjectItem(reply.payload, "data"); if (data != NULL) { cJSON *o_sensor = cJSON_GetObjectItem(data, "sensor"); if (sensor != NULL) { sensor->id = get_int_from_object(o_sensor, "id"); sensor->type = get_int_from_object(o_sensor, "type"); sensor->state = get_int_from_object(o_sensor, "state"); sensor->opt_id = get_int_from_object(o_sensor, "opt_id"); fetch_string_from_object(o_sensor, "opt_detail", &(sensor->opt_detail)); } } } sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_break(sscma_client_handle_t client) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_BREAK CMD_SUFFIX, &reply, true, CMD_WAIT_DELAY), TAG, "request break failed"); if (reply.payload != NULL) { int code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_set_iou_threshold(sscma_client_handle_t client, int threshold) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; char cmd[64] = { 0 }; snprintf(cmd, sizeof(cmd), CMD_PREFIX CMD_AT_TIOU CMD_SET "%d" CMD_SUFFIX, threshold); ESP_RETURN_ON_ERROR(sscma_client_request(client, cmd, &reply, true, CMD_WAIT_DELAY), TAG, "request set iou failed"); if (reply.payload != NULL) { int code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_get_iou_threshold(sscma_client_handle_t client, int *threshold) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; ESP_RETURN_ON_FALSE(threshold != NULL, ESP_ERR_INVALID_ARG, TAG, "threshold is NULL"); ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_TIOU CMD_QUERY CMD_SUFFIX, &reply, true, CMD_WAIT_DELAY), TAG, "request get iou failed"); if (reply.payload != NULL) { int code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); if (ret == ESP_OK) { *threshold = get_int_from_object(reply.payload, "data"); } sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_set_confidence_threshold(sscma_client_handle_t client, int threshold) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; char cmd[64] = { 0 }; snprintf(cmd, sizeof(cmd), CMD_PREFIX CMD_AT_TSCORE CMD_SET "%d" CMD_SUFFIX, threshold); ESP_RETURN_ON_ERROR(sscma_client_request(client, cmd, &reply, true, CMD_WAIT_DELAY), TAG, "request set confidence failed"); if (reply.payload != NULL) { int code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_get_confidence_threshold(sscma_client_handle_t client, int *threshold) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; ESP_RETURN_ON_FALSE(threshold != NULL, ESP_ERR_INVALID_ARG, TAG, "threshold is NULL"); ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_TSCORE CMD_QUERY CMD_SUFFIX, &reply, true, CMD_WAIT_DELAY), TAG, "request get confidence failed"); if (reply.payload != NULL) { int code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); if (ret == ESP_OK) { *threshold = get_int_from_object(reply.payload, "data"); } sscma_client_reply_clear(&reply); } return ret; } esp_err_t sscma_client_set_model_info(sscma_client_handle_t client, const char *model_info) { esp_err_t ret = ESP_OK; sscma_client_reply_t reply; size_t length = 0; ESP_RETURN_ON_FALSE(model_info != NULL, ESP_ERR_INVALID_ARG, TAG, "model_info is NULL"); char *cmd = heap_caps_calloc(1, 4000, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // clear info first for (int i = 0; i < 3; i++) { sscma_client_request(client, CMD_PREFIX CMD_AT_INFO CMD_SET "\"\"" CMD_SUFFIX, &reply, true, CMD_WAIT_DELAY); if (reply.payload != NULL) { int code = get_int_from_object(reply.payload, "code"); sscma_client_reply_clear(&reply); if (code == 0) { break; } else { ESP_LOGW(TAG, "clear info failed %d", code); } } } snprintf(cmd, 4000, CMD_PREFIX CMD_AT_INFO CMD_SET "\""); if (mbedtls_base64_encode((unsigned char *)&cmd[strlen(cmd)], sizeof(cmd) - strlen(cmd) - CMD_SUFFIX_LEN, &length, (const unsigned char *)model_info, strlen(model_info)) != 0) { ESP_LOGE(TAG, "mbedtls_base64_encode failed %d %d", sizeof(cmd) - strlen(cmd) - CMD_SUFFIX_LEN, length); ret = ESP_ERR_NO_MEM; goto set_model_info_exit; } // already restricted to 4000 strcat(cmd, "\"" CMD_SUFFIX); ESP_GOTO_ON_ERROR(sscma_client_request(client, cmd, &reply, true, CMD_WAIT_DELAY), set_model_info_exit, TAG, "request set model info failed"); if (reply.payload != NULL) { int code = get_int_from_object(reply.payload, "code"); ret = SSCMA_CLIENT_CMD_ERROR_CODE(code); sscma_client_reply_clear(&reply); } set_model_info_exit: free(cmd); return ret; } esp_err_t sscma_utils_fetch_boxes_from_reply(const sscma_client_reply_t *reply, sscma_client_box_t **boxes, int *num_boxes) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(reply != NULL, ESP_ERR_INVALID_ARG, TAG, "reply is NULL"); ESP_RETURN_ON_FALSE(boxes != NULL, ESP_ERR_INVALID_ARG, TAG, "boxes is NULL"); ESP_RETURN_ON_FALSE(num_boxes != NULL, ESP_ERR_INVALID_ARG, TAG, "num_boxes is NULL"); ESP_RETURN_ON_FALSE(cJSON_IsObject(reply->payload), ESP_ERR_INVALID_ARG, TAG, "reply is not object"); *boxes = NULL; *num_boxes = 0; cJSON *data = cJSON_GetObjectItem(reply->payload, "data"); if (data != NULL) { cJSON *boxes_data = cJSON_GetObjectItem(data, "boxes"); if (boxes_data == NULL) return ESP_OK; *num_boxes = cJSON_GetArraySize(boxes_data); if (*num_boxes == 0) return ESP_OK; *boxes = __malloc(sizeof(sscma_client_box_t) * (*num_boxes)); ESP_RETURN_ON_FALSE(*boxes != NULL, ESP_ERR_NO_MEM, TAG, "malloc boxes failed"); for (int i = 0; i < *num_boxes; i++) { cJSON *box = cJSON_GetArrayItem(boxes_data, i); if (box != NULL) { (*boxes)[i].x = get_int_from_array(box, 0); (*boxes)[i].y = get_int_from_array(box, 1); (*boxes)[i].w = get_int_from_array(box, 2); (*boxes)[i].h = get_int_from_array(box, 3); (*boxes)[i].score = get_int_from_array(box, 4); (*boxes)[i].target = get_int_from_array(box, 5); } } } return ret; } esp_err_t sscma_utils_copy_boxes_from_reply(const sscma_client_reply_t *reply, sscma_client_box_t *boxes, int max_boxes, int *num_boxes) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(reply != NULL, ESP_ERR_INVALID_ARG, TAG, "reply is NULL"); ESP_RETURN_ON_FALSE(boxes != NULL, ESP_ERR_INVALID_ARG, TAG, "classes is NULL"); ESP_RETURN_ON_FALSE(num_boxes != NULL, ESP_ERR_INVALID_ARG, TAG, "num_classes is NULL"); ESP_RETURN_ON_FALSE(cJSON_IsObject(reply->payload), ESP_ERR_INVALID_ARG, TAG, "reply is not object"); *num_boxes = 0; cJSON *data = cJSON_GetObjectItem(reply->payload, "data"); if (data != NULL) { cJSON *boxes_data = cJSON_GetObjectItem(data, "boxes"); if (boxes_data == NULL) return ESP_OK; *num_boxes = cJSON_GetArraySize(boxes_data) > max_boxes ? max_boxes : cJSON_GetArraySize(boxes_data); if (*num_boxes == 0) return ESP_OK; for (int i = 0; i < *num_boxes; i++) { cJSON *item = cJSON_GetArrayItem(boxes_data, i); if (item != NULL) { boxes[i].x = get_int_from_array(item, 0); boxes[i].y = get_int_from_array(item, 1); boxes[i].w = get_int_from_array(item, 2); boxes[i].h = get_int_from_array(item, 3); boxes[i].score = get_int_from_array(item, 4); boxes[i].target = get_int_from_array(item, 5); } } } return ret; } esp_err_t sscma_utils_fetch_classes_from_reply(const sscma_client_reply_t *reply, sscma_client_class_t **classes, int *num_classes) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(reply != NULL, ESP_ERR_INVALID_ARG, TAG, "reply is NULL"); ESP_RETURN_ON_FALSE(classes != NULL, ESP_ERR_INVALID_ARG, TAG, "classes is NULL"); ESP_RETURN_ON_FALSE(num_classes != NULL, ESP_ERR_INVALID_ARG, TAG, "num_classes is NULL"); ESP_RETURN_ON_FALSE(cJSON_IsObject(reply->payload), ESP_ERR_INVALID_ARG, TAG, "reply is not object"); *classes = NULL; *num_classes = 0; cJSON *data = cJSON_GetObjectItem(reply->payload, "data"); if (data != NULL) { cJSON *classes_data = cJSON_GetObjectItem(data, "classes"); if (classes_data == NULL) return ESP_OK; *num_classes = cJSON_GetArraySize(classes_data); if (*num_classes == 0) return ESP_OK; *classes = __malloc(sizeof(sscma_client_class_t) * (*num_classes)); ESP_RETURN_ON_FALSE(*classes != NULL, ESP_ERR_NO_MEM, TAG, "malloc classes failed"); for (int i = 0; i < *num_classes; i++) { cJSON *item = cJSON_GetArrayItem(classes_data, i); if (item != NULL) { (*classes)[i].score = get_int_from_array(item, 0); (*classes)[i].target = get_int_from_array(item, 1); } } } return ret; } esp_err_t sscma_utils_copy_classes_from_reply(const sscma_client_reply_t *reply, sscma_client_class_t *classes, int max_classes, int *num_classes) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(reply != NULL, ESP_ERR_INVALID_ARG, TAG, "reply is NULL"); ESP_RETURN_ON_FALSE(classes != NULL, ESP_ERR_INVALID_ARG, TAG, "classes is NULL"); ESP_RETURN_ON_FALSE(num_classes != NULL, ESP_ERR_INVALID_ARG, TAG, "num_classes is NULL"); ESP_RETURN_ON_FALSE(cJSON_IsObject(reply->payload), ESP_ERR_INVALID_ARG, TAG, "reply is not object"); *num_classes = 0; cJSON *data = cJSON_GetObjectItem(reply->payload, "data"); if (data != NULL) { cJSON *classes_data = cJSON_GetObjectItem(data, "classes"); if (classes_data == NULL) return ESP_OK; *num_classes = cJSON_GetArraySize(classes_data) > max_classes ? max_classes : cJSON_GetArraySize(classes_data); if (*num_classes == 0) return ESP_OK; for (int i = 0; i < *num_classes; i++) { cJSON *item = cJSON_GetArrayItem(classes_data, i); if (item != NULL) { classes[i].score = get_int_from_array(item, 0); classes[i].target = get_int_from_array(item, 1); } } } return ret; } esp_err_t sscma_utils_fetch_points_from_reply(const sscma_client_reply_t *reply, sscma_client_point_t **points, int *num_points) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(reply != NULL, ESP_ERR_INVALID_ARG, TAG, "reply is NULL"); ESP_RETURN_ON_FALSE(points != NULL, ESP_ERR_INVALID_ARG, TAG, "points is NULL"); ESP_RETURN_ON_FALSE(num_points != NULL, ESP_ERR_INVALID_ARG, TAG, "num_points is NULL"); ESP_RETURN_ON_FALSE(cJSON_IsObject(reply->payload), ESP_ERR_INVALID_ARG, TAG, "reply is not object"); *points = NULL; *num_points = 0; cJSON *data = cJSON_GetObjectItem(reply->payload, "data"); if (data != NULL) { cJSON *ponits_data = cJSON_GetObjectItem(data, "points"); if (ponits_data == NULL) return ESP_OK; *num_points = cJSON_GetArraySize(ponits_data); if (*num_points == 0) return ESP_OK; *points = __malloc(sizeof(sscma_client_point_t) * (*num_points)); ESP_RETURN_ON_FALSE(*points != NULL, ESP_ERR_NO_MEM, TAG, "malloc points failed"); for (int i = 0; i < *num_points; i++) { cJSON *item = cJSON_GetArrayItem(ponits_data, i); if (item != NULL) { (*points)[i].x = get_int_from_array(item, 0); (*points)[i].y = get_int_from_array(item, 1); (*points)[i].z = 0; (*points)[i].score = get_int_from_array(item, 2); (*points)[i].target = get_int_from_array(item, 3); } } } return ret; } esp_err_t sscma_utils_copy_points_from_reply(const sscma_client_reply_t *reply, sscma_client_point_t *points, int max_points, int *num_points) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(reply != NULL, ESP_ERR_INVALID_ARG, TAG, "reply is NULL"); ESP_RETURN_ON_FALSE(points != NULL, ESP_ERR_INVALID_ARG, TAG, "points is NULL"); ESP_RETURN_ON_FALSE(num_points != NULL, ESP_ERR_INVALID_ARG, TAG, "num_points is NULL"); ESP_RETURN_ON_FALSE(cJSON_IsObject(reply->payload), ESP_ERR_INVALID_ARG, TAG, "reply is not object"); *num_points = 0; cJSON *data = cJSON_GetObjectItem(reply->payload, "data"); if (data != NULL) { cJSON *ponits_data = cJSON_GetObjectItem(data, "points"); if (ponits_data == NULL) return ESP_OK; *num_points = cJSON_GetArraySize(ponits_data); if (*num_points == 0) return ESP_OK; *num_points = cJSON_GetArraySize(ponits_data) > max_points ? max_points : cJSON_GetArraySize(ponits_data); for (int i = 0; i < *num_points; i++) { cJSON *item = cJSON_GetArrayItem(ponits_data, i); if (item != NULL) { points[i].x = get_int_from_array(item, 0); points[i].y = get_int_from_array(item, 1); points[i].z = 0; points[i].score = get_int_from_array(item, 2); points[i].target = get_int_from_array(item, 3); } } } return ret; } esp_err_t sscma_utils_fetch_keypoints_from_reply(const sscma_client_reply_t *reply, sscma_client_keypoint_t **keypoints, int *num_keypoints) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(reply != NULL, ESP_ERR_INVALID_ARG, TAG, "reply is NULL"); ESP_RETURN_ON_FALSE(keypoints != NULL, ESP_ERR_INVALID_ARG, TAG, "keypoints is NULL"); ESP_RETURN_ON_FALSE(num_keypoints != NULL, ESP_ERR_INVALID_ARG, TAG, "num_keypoints is NULL"); ESP_RETURN_ON_FALSE(cJSON_IsObject(reply->payload), ESP_ERR_INVALID_ARG, TAG, "reply is not object"); *keypoints = NULL; *num_keypoints = 0; cJSON *data = cJSON_GetObjectItem(reply->payload, "data"); if (data != NULL) { cJSON *keypoints_data = cJSON_GetObjectItem(data, "keypoints"); if (keypoints_data == NULL) return ESP_OK; *num_keypoints = cJSON_GetArraySize(keypoints_data); if (*num_keypoints == 0) return ESP_OK; *keypoints = __malloc(sizeof(sscma_client_keypoint_t) * (*num_keypoints)); ESP_RETURN_ON_FALSE(*keypoints != NULL, ESP_ERR_NO_MEM, TAG, "malloc keypoints failed"); for (int i = 0; i < *num_keypoints; i++) { cJSON *item = cJSON_GetArrayItem(keypoints_data, i); if (item != NULL) { cJSON *box = cJSON_GetArrayItem(item, 0); cJSON *points = cJSON_GetArrayItem(item, 1); if (box != NULL && points != NULL) { (*keypoints)[i].box.x = get_int_from_array(box, 0); (*keypoints)[i].box.y = get_int_from_array(box, 1); (*keypoints)[i].box.w = get_int_from_array(box, 2); (*keypoints)[i].box.h = get_int_from_array(box, 3); (*keypoints)[i].box.score = get_int_from_array(box, 4); (*keypoints)[i].box.target = get_int_from_array(box, 5); (*keypoints)[i].points_num = cJSON_GetArraySize(points) > SSCMA_CLIENT_MODEL_KEYPOINTS_MAX ? SSCMA_CLIENT_MODEL_KEYPOINTS_MAX : cJSON_GetArraySize(points); for (int j = 0; j < (*keypoints)[i].points_num; j++) { cJSON *point = cJSON_GetArrayItem(points, j); if (point != NULL) { (*keypoints)[i].points[j].x = get_int_from_array(point, 0); (*keypoints)[i].points[j].y = get_int_from_array(point, 1); (*keypoints)[i].points[j].z = 0; (*keypoints)[i].points[j].score = get_int_from_array(point, 2); (*keypoints)[i].points[j].target = get_int_from_array(point, 3); } } } } } } return ret; } esp_err_t sscma_utils_copy_keypoints_from_reply(const sscma_client_reply_t *reply, sscma_client_keypoint_t *keypoints, int max_keypoints, int *num_keypoints) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(reply != NULL, ESP_ERR_INVALID_ARG, TAG, "reply is NULL"); ESP_RETURN_ON_FALSE(keypoints != NULL, ESP_ERR_INVALID_ARG, TAG, "keypoints is NULL"); ESP_RETURN_ON_FALSE(num_keypoints != NULL, ESP_ERR_INVALID_ARG, TAG, "num_keypoints is NULL"); ESP_RETURN_ON_FALSE(cJSON_IsObject(reply->payload), ESP_ERR_INVALID_ARG, TAG, "reply is not object"); *num_keypoints = 0; cJSON *data = cJSON_GetObjectItem(reply->payload, "data"); if (data != NULL) { cJSON *keypoints_data = cJSON_GetObjectItem(data, "keypoints"); if (keypoints_data == NULL) return ESP_OK; *num_keypoints = cJSON_GetArraySize(keypoints_data) > max_keypoints ? max_keypoints : cJSON_GetArraySize(keypoints_data); for (int i = 0; i < *num_keypoints; i++) { cJSON *item = cJSON_GetArrayItem(keypoints_data, i); if (item != NULL) { cJSON *box = cJSON_GetArrayItem(item, 0); cJSON *points = cJSON_GetArrayItem(item, 1); if (box != NULL && points != NULL) { keypoints[i].box.x = get_int_from_array(box, 0); keypoints[i].box.y = get_int_from_array(box, 1); keypoints[i].box.w = get_int_from_array(box, 2); keypoints[i].box.h = get_int_from_array(box, 3); keypoints[i].box.score = get_int_from_array(box, 4); keypoints[i].box.target = get_int_from_array(box, 5); keypoints[i].points_num = cJSON_GetArraySize(points) > SSCMA_CLIENT_MODEL_KEYPOINTS_MAX ? SSCMA_CLIENT_MODEL_KEYPOINTS_MAX : cJSON_GetArraySize(points); for (int j = 0; j < keypoints[i].points_num; j++) { cJSON *point = cJSON_GetArrayItem(points, j); if (point != NULL) { keypoints[i].points[j].x = get_int_from_array(point, 0); keypoints[i].points[j].y = get_int_from_array(point, 1); keypoints[i].points[j].z = 0; keypoints[i].points[j].score = get_int_from_array(point, 2); keypoints[i].points[j].target = get_int_from_array(point, 3); } } } } } } return ret; } esp_err_t sscma_utils_fetch_image_from_reply(const sscma_client_reply_t *reply, char **image, int *image_size) { ESP_RETURN_ON_FALSE(reply && image && image_size, ESP_ERR_INVALID_ARG, TAG, "Invalid argument(s) detected"); *image = NULL; *image_size = 0; if (!cJSON_IsObject(reply->payload)) { return ESP_ERR_INVALID_ARG; } cJSON *data = cJSON_GetObjectItem(reply->payload, "data"); if (!data) { return ESP_FAIL; } cJSON *image_data = cJSON_GetObjectItem(data, "image"); if (!image_data) { return ESP_FAIL; } const char *image_str = cJSON_GetStringValue(image_data); if (!image_str) { return ESP_FAIL; } *image = __strdup(image_str); if (!(*image)) { return ESP_ERR_NO_MEM; } *image_size = strlen(image_str); return ESP_OK; } esp_err_t sscma_utils_copy_image_from_reply(const sscma_client_reply_t *reply, char *image, int max_image_size, int *image_size) { ESP_RETURN_ON_FALSE(reply && image && image_size, ESP_ERR_INVALID_ARG, TAG, "Invalid argument(s) detected"); if (!cJSON_IsObject(reply->payload)) { return ESP_ERR_INVALID_ARG; } cJSON *data = cJSON_GetObjectItem(reply->payload, "data"); if (!data) { return ESP_FAIL; } cJSON *image_data = cJSON_GetObjectItem(data, "image"); if (!image_data) { return ESP_FAIL; } const char *image_str = cJSON_GetStringValue(image_data); if (!image_str) { return ESP_FAIL; } size_t image_str_len = strlen(image_str); if (image_str_len > max_image_size) { return ESP_ERR_INVALID_SIZE; } *image_size = image_str_len; strcpy(image, image_str); return ESP_OK; } esp_err_t sscma_client_ota_start(sscma_client_handle_t client, const sscma_client_flasher_handle_t flasher, size_t offset) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(client && flasher, ESP_ERR_INVALID_ARG, TAG, "Invalid argument(s) detected"); if (client->flasher != NULL) { ESP_LOGW(TAG, "client OTA flasher already registered, overriding it"); } client->flasher = flasher; sscma_client_reset(client); sscma_client_break(client); vTaskSuspend(client->process_task.handle); vTaskDelay(100 / portTICK_PERIOD_MS); ESP_RETURN_ON_ERROR(sscma_client_request(client, CMD_PREFIX CMD_AT_OTA CMD_SUFFIX, NULL, false, CMD_WAIT_DELAY), TAG, "request failed"); vTaskDelay(100 / portTICK_PERIOD_MS); ESP_GOTO_ON_ERROR(sscma_client_flasher_start(client->flasher, offset), err, TAG, "start flasher failed"); return ret; err: vTaskResume(client->process_task.handle); return ret; } esp_err_t sscma_client_ota_write(sscma_client_handle_t client, const void *data, size_t len) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(client && data && len, ESP_ERR_INVALID_ARG, TAG, "Invalid argument(s) detected"); ESP_GOTO_ON_ERROR(sscma_client_flasher_write(client->flasher, data, len), err, TAG, "write flasher failed"); return ret; err: sscma_client_flasher_abort(client->flasher); vTaskResume(client->process_task.handle); return ret; } esp_err_t sscma_client_ota_finish(sscma_client_handle_t client) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(client, ESP_ERR_INVALID_ARG, TAG, "Invalid argument(s) detected"); ESP_GOTO_ON_ERROR(sscma_client_flasher_finish(client->flasher), err, TAG, "finish flasher failed"); vTaskResume(client->process_task.handle); return ret; err: sscma_client_flasher_abort(client->flasher); vTaskResume(client->process_task.handle); return ret; } esp_err_t sscma_client_ota_abort(sscma_client_handle_t client) { esp_err_t ret = ESP_OK; ESP_RETURN_ON_FALSE(client, ESP_ERR_INVALID_ARG, TAG, "Invalid argument(s) detected"); ESP_GOTO_ON_ERROR(sscma_client_flasher_abort(client->flasher), err, TAG, "abort flasher failed"); err: vTaskResume(client->process_task.handle); return ret; }