From a4d65c367dd5837c7a1731352f390b0f26a28ea2 Mon Sep 17 00:00:00 2001 From: kerms Date: Fri, 1 Mar 2024 14:33:04 +0800 Subject: [PATCH] feat(websocket) json api --- main/main.c | 4 +- .../api_router/api_json_module.h | 6 - .../request_runner/request_runner.c | 7 +- project_components/web_server/CMakeLists.txt | 7 +- .../web_server/uri_modules/uri_api.c | 10 +- .../web_server/uri_modules/uri_html_base.c | 4 +- .../web_server/uri_modules/uri_ws.c | 265 ++++++++++++++++++ project_components/web_server/web_server.c | 6 +- .../web_server/web_uri_module.c | 46 +-- .../web_server/web_uri_module.h | 5 +- project_components/wifi_manager/wifi_api.c | 7 +- .../wifi_manager/wifi_manager.c | 16 +- 12 files changed, 330 insertions(+), 53 deletions(-) create mode 100644 project_components/web_server/uri_modules/uri_ws.c diff --git a/main/main.c b/main/main.c index 7f9ac6f..817a624 100644 --- a/main/main.c +++ b/main/main.c @@ -13,13 +13,15 @@ #include "static_buffer.h" #include "request_runner.h" +#include + void app_main() { assert(static_buffer_init() == 0); assert(request_runner_init() == 0); wt_storage_init(); - ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); wifi_manager_init(); DAP_Setup(); diff --git a/project_components/api_router/api_json_module.h b/project_components/api_router/api_json_module.h index 277888e..0c720d2 100644 --- a/project_components/api_router/api_json_module.h +++ b/project_components/api_router/api_json_module.h @@ -5,7 +5,6 @@ #include #include - typedef struct api_json_req_t { cJSON *in; cJSON *out; @@ -21,11 +20,6 @@ typedef struct api_json_module_async_t { req_task_cb_t req_task; } api_json_module_async_t; -//typedef struct api_json_send_out_t { -// void (*func)(void *arg); -// void *arg; /* socket context */ -//} api_json_send_out_t; - typedef enum api_json_req_status_e { API_JSON_OK = 0, diff --git a/project_components/request_runner/request_runner.c b/project_components/request_runner/request_runner.c index 82f2766..a0a5c7f 100644 --- a/project_components/request_runner/request_runner.c +++ b/project_components/request_runner/request_runner.c @@ -1,18 +1,17 @@ -#include -#include -#include -#include /* * SPDX-FileCopyrightText: 2024 kerms * * SPDX-License-Identifier: Apache-2.0 */ + #include "request_runner.h" #include #include #include +#include + static QueueHandle_t long_run_queue = NULL; static QueueHandle_t send_out_queue = NULL; diff --git a/project_components/web_server/CMakeLists.txt b/project_components/web_server/CMakeLists.txt index 893f4aa..0bee70e 100644 --- a/project_components/web_server/CMakeLists.txt +++ b/project_components/web_server/CMakeLists.txt @@ -1,15 +1,16 @@ file(GLOB SOURCES web_server.c web_uri_module.c - uri_modules/uri_html_base.c + uri_modules/uri_ws.c uri_modules/uri_api.c + uri_modules/uri_html_base.c ) idf_component_register( SRCS ${SOURCES} INCLUDE_DIRS "." - REQUIRES esp_http_server api_router json - PRIV_REQUIRES request_runner static_buffer + REQUIRES esp_http_server + PRIV_REQUIRES request_runner api_router json static_buffer utils ) idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE ON) diff --git a/project_components/web_server/uri_modules/uri_api.c b/project_components/web_server/uri_modules/uri_api.c index 7199c29..3d2cf72 100644 --- a/project_components/web_server/uri_modules/uri_api.c +++ b/project_components/web_server/uri_modules/uri_api.c @@ -19,7 +19,7 @@ typedef struct post_request_t { char buf[0]; } post_request_t; -static void async_send_out_cb(void *req, int module_status); +static void async_send_out_cb(void *arg, int module_status); static int uri_api_send_out(httpd_req_t *req, post_request_t *post_req); @@ -66,12 +66,12 @@ static esp_err_t api_post_handler(httpd_req_t *req) post_req->json.out = NULL; err = api_json_route(&post_req->json, &post_req->async); - cJSON_Delete(post_req->json.in); if (err == API_JSON_ASYNC) { httpd_req_async_handler_begin(req, &post_req->req_out); post_req->async.req_task.send_out.cb = async_send_out_cb; post_req->async.req_task.send_out.arg = post_req; if (req_queue_push_long_run(&post_req->async.req_task, pdMS_TO_TICKS(20))) { + /* queued failed */ httpd_req_async_handler_complete(post_req->req_out); httpd_resp_set_status(req, HTTPD_STATUS_503); goto end; @@ -91,6 +91,7 @@ static esp_err_t api_post_handler(httpd_req_t *req) goto put_buf; end: + cJSON_Delete(post_req->json.in); err = httpd_resp_send(req, NULL, 0); if (unlikely(err)) { ESP_LOGE(TAG, "resp_send err: %s", esp_err_to_name(err)); @@ -112,6 +113,7 @@ int uri_api_send_out(httpd_req_t *req, post_request_t *post_req) httpd_resp_set_type(req, HTTPD_TYPE_JSON); err = !cJSON_PrintPreallocated(post_req->json.out, buf, buf_len - 5, 0); cJSON_Delete(post_req->json.out); + cJSON_Delete(post_req->json.in); if (unlikely(err)) { httpd_resp_set_status(req, HTTPD_500); return httpd_resp_send(req, NULL, 0); @@ -146,13 +148,13 @@ static const httpd_uri_t uri_api = { .user_ctx = NULL }; -int URI_API_INIT(const httpd_uri_t **uri_conf) { +static int URI_API_INIT(const httpd_uri_t **uri_conf) { *uri_conf = &uri_api; api_json_router_init(); return 0; } -int URI_API_EXIT(const httpd_uri_t **uri_conf) { +static int URI_API_EXIT(const httpd_uri_t **uri_conf) { *uri_conf = &uri_api; return 0; } diff --git a/project_components/web_server/uri_modules/uri_html_base.c b/project_components/web_server/uri_modules/uri_html_base.c index fe731f6..5d8d02c 100644 --- a/project_components/web_server/uri_modules/uri_html_base.c +++ b/project_components/web_server/uri_modules/uri_html_base.c @@ -83,12 +83,12 @@ static const httpd_uri_t hello = { .user_ctx = "Hello World!" }; -int URI_HTML_BASE_INIT(const httpd_uri_t **uri_conf) { +static int URI_HTML_BASE_INIT(const httpd_uri_t **uri_conf) { *uri_conf = &hello; return 0; } -int URI_HTML_BASE_EXIT(const httpd_uri_t **uri_conf) { +static int URI_HTML_BASE_EXIT(const httpd_uri_t **uri_conf) { *uri_conf = &hello; return 0; } diff --git a/project_components/web_server/uri_modules/uri_ws.c b/project_components/web_server/uri_modules/uri_ws.c new file mode 100644 index 0000000..35ae4d6 --- /dev/null +++ b/project_components/web_server/uri_modules/uri_ws.c @@ -0,0 +1,265 @@ +/* + * SPDX-FileCopyrightText: 2024 kerms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "web_uri_module.h" +#include "api_json_router.h" +#include "static_buffer.h" + +#include +#include +#include + +#include +#include + +#define TAG __FILE_NAME__ +#define MSG_BUSY_ERROR "{\"error\":\"Resource busy\"}" +#define MSG_JSON_ERROR "{\"error\":\"JSON parse error\"}" +#define MSG_SEND_JSON_ERROR "{\"error\":\"JSON generation error\"}" +#define MSG_INTERNAL_ERROR "{\"error\":\"\"}" + +typedef struct ws_msg_t { + api_json_req_t json; + api_json_module_async_t async; + httpd_handle_t hd; + int fd; + httpd_ws_frame_t ws_pkt; + uint8_t delim[0]; + uint8_t payload[0]; /* size = static_buf_size - offsetof(this, delim) */ +} ws_msg_t; + +#define PAYLOAD_LEN static_buffer_get_buf_size() - sizeof(ws_msg_t) + +static int ws_on_text_data(httpd_req_t *req, ws_msg_t *ws_msg); +static int ws_on_binary_data(httpd_req_t *req, ws_msg_t *ws_msg); +static void ws_async_resp(void *arg); +static void async_send_out_cb(void *arg, int module_status); +static void json_to_text(ws_msg_t *msg); + +static void test(void *arg) +{ + ESP_LOGE(TAG, "this is a test"); +} + +static esp_err_t ws_req_handler(httpd_req_t *req) +{ + if (unlikely(req->method == HTTP_GET)) { + int sock_fd = httpd_req_to_sockfd(req); + /** + * TODO: add socket to array + * */ + ESP_LOGI(TAG, "ws open: %d", sock_fd); + return ESP_OK; + } + + ESP_LOGI(TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", req->handle, + httpd_req_to_sockfd(req), httpd_ws_get_fd_info(req->handle, httpd_req_to_sockfd(req))); + + int err = ESP_OK; + httpd_ws_frame_t *ws_pkt; + ws_msg_t *ws_msg; + + ws_msg = static_buffer_get(pdMS_TO_TICKS(10)); + if (unlikely(ws_msg == NULL)) { + httpd_ws_frame_t resp_pkt; + resp_pkt.type = HTTPD_WS_TYPE_TEXT; + resp_pkt.len = strlen(MSG_BUSY_ERROR); + resp_pkt.payload = (uint8_t *)MSG_BUSY_ERROR; + resp_pkt.final = 1; + httpd_ws_send_frame_async(req->handle, httpd_req_to_sockfd(req), &resp_pkt); + goto end; + } + ws_pkt = &ws_msg->ws_pkt; + ws_pkt->len = 0; + + /* get and check frame size */ + err = httpd_ws_recv_frame(req, ws_pkt, 0); + if (unlikely(err != ESP_OK)) { + ESP_LOGE(TAG, "ws recv len error"); + goto end; + } + ESP_LOGI(TAG, "frame len: %d, type: %d", ws_pkt->len, ws_pkt->type); + if (unlikely(ws_pkt->len > PAYLOAD_LEN)) { + ESP_LOGE(TAG, "frame len is too big"); + err = ESP_FAIL; + goto end; + } + + ws_pkt->payload = ws_msg->payload; + /* read incoming data */ + err = httpd_ws_recv_frame(req, ws_pkt, ws_pkt->len); + if (unlikely(err != ESP_OK)) { + ESP_LOGE(TAG, "ws recv data error"); + goto end; + } + + switch (ws_pkt->type) { + case HTTPD_WS_TYPE_CONTINUE: + break; + case HTTPD_WS_TYPE_TEXT: + return ws_on_text_data(req, ws_msg); + case HTTPD_WS_TYPE_BINARY: + return ws_on_binary_data(req, ws_msg); + case HTTPD_WS_TYPE_CLOSE: + /* Read the rest of the CLOSE frame and response */ + /* Please refer to RFC6455 Section 5.5.1 for more details */ + ws_pkt->len = 0; + ws_pkt->type = HTTPD_WS_TYPE_CLOSE; + err = httpd_ws_send_frame(req, ws_pkt); + break; + case HTTPD_WS_TYPE_PING: + /* Now turn the frame to PONG */ + ws_pkt->type = HTTPD_WS_TYPE_PONG; + err = httpd_ws_send_frame(req, ws_pkt); + break; + case HTTPD_WS_TYPE_PONG: + err = ESP_OK; + break; + } + +end: + static_buffer_put(ws_msg); + return err; +} + +/** + * REGISTER MODULE + * */ + +static const httpd_uri_t uri_api = { + .uri = "/ws", + .method = HTTP_GET, + .handler = ws_req_handler, + .user_ctx = NULL, + .is_websocket = true, + .supported_subprotocol = NULL, + /* in esp-idf v5.2.0, set to false cause infinite queue_work fail on wifi disconnect when at least 1 ws client */ + .handle_ws_control_frames = true, +}; + +static int WS_REQ_INIT(const httpd_uri_t **uri_conf) { + *uri_conf = &uri_api; + api_json_router_init(); + return 0; +} + +static int WS_REQ_EXIT(const httpd_uri_t **uri_conf) { + *uri_conf = &uri_api; + return 0; +} + +WEB_URI_MODULE_REGISTER(101, WS_REQ_INIT, WS_REQ_EXIT) + +int ws_on_text_data(httpd_req_t *req, ws_msg_t *ws_msg) +{ + int err = ESP_OK; + int ret; + httpd_ws_frame_t *ws_pkt; + + ws_pkt = &ws_msg->ws_pkt; + ESP_LOGI(TAG, "=========== RECEIVED DATA =========="); + ESP_LOGI(TAG, "%.*s", ws_pkt->len, ws_pkt->payload); + ESP_LOGI(TAG, "===================================="); + ESP_LOGI(TAG, "heap min: %lu, cur: %lu", esp_get_minimum_free_heap_size(), esp_get_free_heap_size()); + + /* Decode */ + ws_msg->json.in = cJSON_ParseWithLength((char *) ws_pkt->payload, ws_pkt->len); + if (unlikely(ws_msg->json.in == NULL)) { + ws_pkt->payload = (uint8_t *) MSG_JSON_ERROR; + ws_pkt->len = strlen(MSG_JSON_ERROR); + goto put_buf; + } + + ws_msg->json.out = NULL; + ret = api_json_route(&ws_msg->json, &ws_msg->async); + if (ret == API_JSON_ASYNC) { + ws_msg->hd = req->handle; + ws_msg->fd = httpd_req_to_sockfd(req); + ws_msg->async.req_task.send_out.cb = async_send_out_cb; + ws_msg->async.req_task.send_out.arg = ws_msg; + if (req_queue_push_long_run(&ws_msg->async.req_task, pdMS_TO_TICKS(20))) { + /* queued failed */ + goto end; + } + /* ret, buf will be release latter in async send out */ + return ESP_OK; + } else if (ret != API_JSON_OK) { + ws_pkt->len = strlen(MSG_BUSY_ERROR); + ws_pkt->payload = (uint8_t *)MSG_BUSY_ERROR; + ws_pkt->final = 1; + err = httpd_ws_send_frame_async(req->handle, httpd_req_to_sockfd(req), ws_pkt); + goto end; + } else if (ws_msg->json.out == NULL) { + goto end; + } + + /* api function returns something, send it to http client */ + json_to_text(ws_msg); + +end: + cJSON_Delete(ws_msg->json.in); +put_buf: + httpd_ws_send_frame_async(req->handle, httpd_req_to_sockfd(req), ws_pkt); + static_buffer_put(ws_msg); + return err; +} + +int ws_on_binary_data(httpd_req_t *req, ws_msg_t *ws_msg) +{ + static_buffer_put(ws_msg); + return 0; +} + +static void ws_async_resp(void *arg) +{ + ws_msg_t *req = arg; + httpd_handle_t hd = req->hd; + int fd = req->fd; + int err; + + ESP_LOGI(TAG, "ws async fd : %d", fd); + err = httpd_ws_send_frame_async(hd, fd, &req->ws_pkt); + if (err) { + ESP_LOGE(TAG, "%s", esp_err_to_name(err)); + } + static_buffer_put(req); +} + +void async_send_out_cb(void *arg, int module_status) +{ + ws_msg_t *req = arg; + if (module_status != API_JSON_OK) { + goto end; + } + + int err; + json_to_text(req); + err = httpd_queue_work(req->hd, ws_async_resp, req); + if (likely(err == ESP_OK)) { + return; + } + + ESP_LOGE(TAG, "errno: %d, fd %p: %s", errno, req->hd, esp_err_to_name(err)); + +end: + /* clean resources */ + static_buffer_put(req); +} + +void json_to_text(ws_msg_t *ws_msg) +{ + int err; + httpd_ws_frame_t *ws_pkt = &ws_msg->ws_pkt; + /* api function returns something, send it to http client */ + err = !cJSON_PrintPreallocated(ws_msg->json.out, (char *) ws_msg->payload, PAYLOAD_LEN - 5, 0); + cJSON_Delete(ws_msg->json.out); + if (unlikely(err)) { + ws_pkt->len = strlen(MSG_SEND_JSON_ERROR); + ws_pkt->payload = (uint8_t *) MSG_SEND_JSON_ERROR; + ws_pkt->final = 1; + } + ws_pkt->len = strlen((char *) ws_pkt->payload); +}; diff --git a/project_components/web_server/web_server.c b/project_components/web_server/web_server.c index bbc1d69..4559685 100644 --- a/project_components/web_server/web_server.c +++ b/project_components/web_server/web_server.c @@ -96,7 +96,6 @@ static void web_server_on_close(httpd_handle_t hd, int sockfd) void start_webserver(void) { - httpd_handle_t server = NULL; httpd_config_t config = HTTPD_DEFAULT_CONFIG(); int err; @@ -108,14 +107,13 @@ void start_webserver(void) config.close_fn = web_server_on_close; ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port); - if ((err = httpd_start(&server, &config) != ESP_OK)) { + if ((err = httpd_start(&http_server, &config)) != ESP_OK) { ESP_LOGE(TAG, "Error starting server!"); ESP_ERROR_CHECK(err); } ESP_LOGI(TAG, "Registering URI handlers"); - uri_module_init(server); - http_server = server; + uri_module_init(http_server); } static esp_err_t stop_webserver(httpd_handle_t server) diff --git a/project_components/web_server/web_uri_module.c b/project_components/web_server/web_uri_module.c index 1a1cc5f..170e766 100644 --- a/project_components/web_server/web_uri_module.c +++ b/project_components/web_server/web_uri_module.c @@ -1,49 +1,52 @@ #include "web_uri_module.h" +#include "list.h" #include + #define URI_MODULE_MAX 8 #define TAG __FILE_NAME__ -static uri_module_t module_arr[URI_MODULE_MAX]; +typedef struct uri_module_list_t { + struct dl_list list; + uri_module_t module; +} uri_module_list_t; + +static uri_module_list_t module_arr[URI_MODULE_MAX]; static uint8_t module_count = 0; +static DEFINE_DL_LIST(list_head); int uri_module_init(httpd_handle_t server) { int err; const httpd_uri_t *uri; + uri_module_list_t *item; + uint8_t index = 0; - for (int i = 0; i < module_count; ++i) { - err = module_arr[i].init(&uri); + dl_list_for_each(item, &list_head, uri_module_list_t, list) { + err = item->module.init(&uri); ESP_LOGI(TAG, "uri %s init", uri->uri); if (err) { - ESP_LOGE(TAG, "%d init error", i); + ESP_LOGE(TAG, "%d init error", index); } err = httpd_register_uri_handler(server, uri); if (err) { - ESP_LOGE(TAG, "%d %s", i, esp_err_to_name(err)); + ESP_LOGE(TAG, "%d %s", index, esp_err_to_name(err)); } + index++; } return 0; } int uri_module_exit(httpd_handle_t server) { - int err; - const httpd_uri_t *uri; - for (int i = 0; i < module_count; ++i) { - module_arr[i].exit(&uri); - err = httpd_unregister_uri_handler(server, uri->uri, uri->method); - if (err) { - ESP_LOGE(TAG, "%d %s", i, esp_err_to_name(err)); - } - } + return 0; } -int uri_module_add(uri_module_func init, uri_module_func exit) +int uri_module_add(uint8_t priority, uri_module_func init, uri_module_func exit) { ESP_LOGE(TAG, "adding module %p", init); @@ -52,8 +55,17 @@ int uri_module_add(uri_module_func init, uri_module_func exit) return 1; } - module_arr[module_count].exit = exit; - module_arr[module_count].init = init; + module_arr[module_count].module.exit = exit; + module_arr[module_count].module.init = init; + module_arr[module_count].module.priority = priority; + + uri_module_list_t *item; + dl_list_for_each(item, &list_head, uri_module_list_t, list) { + if (item->module.priority <= priority) { + break; + } + } + dl_list_add(&item->list, &module_arr[module_count].list); module_count++; return 0; } diff --git a/project_components/web_server/web_uri_module.h b/project_components/web_server/web_uri_module.h index 89ece87..6181a86 100644 --- a/project_components/web_server/web_uri_module.h +++ b/project_components/web_server/web_uri_module.h @@ -8,16 +8,17 @@ typedef int (*uri_module_func)(const httpd_uri_t **uri); typedef struct uri_module_t { uri_module_func init; uri_module_func exit; + uint8_t priority; } uri_module_t; -int uri_module_add(uri_module_func init, uri_module_func exit); +int uri_module_add(uint8_t priority, uri_module_func init, uri_module_func exit); /** * @brief Register a uri module that will be init with PRI(priority) order. */ #define WEB_URI_MODULE_REGISTER(PRI, INIT, EXIT) \ __attribute__((used, constructor(PRI))) void cons_ ## INIT(); \ - void cons_ ## INIT() { uri_module_add(INIT, EXIT); } + void cons_ ## INIT() { uri_module_add(PRI, INIT, EXIT); } int uri_module_init(httpd_handle_t server); int uri_module_exit(httpd_handle_t server); diff --git a/project_components/wifi_manager/wifi_api.c b/project_components/wifi_manager/wifi_api.c index 4348797..b5328c8 100644 --- a/project_components/wifi_manager/wifi_api.c +++ b/project_components/wifi_manager/wifi_api.c @@ -76,10 +76,13 @@ static void wifi_manager_scan_done(uint16_t ap_found, wifi_ap_record_t *records, scan_done_cb(ap_found, ap_info, arg); } - int wifi_api_trigger_scan(uint16_t *max_ap_count, wifi_api_scan_done_cb cb, void *cb_arg) { - wifi_manager_trigger_scan(wifi_manager_scan_done, cb_arg); + int err; + err = wifi_manager_trigger_scan(wifi_manager_scan_done, cb_arg); + if (err) { + return err; + } scan_done_cb = cb; return 0; } diff --git a/project_components/wifi_manager/wifi_manager.c b/project_components/wifi_manager/wifi_manager.c index fc12efa..234d5c8 100644 --- a/project_components/wifi_manager/wifi_manager.c +++ b/project_components/wifi_manager/wifi_manager.c @@ -12,6 +12,7 @@ #include #include +#include #define TAG __FILENAME__ @@ -123,7 +124,7 @@ static void scan_loop() wifi_event_scan_channel_done, number, &scan_ctx.ap[scan_ctx.total_aps])) { ESP_LOGE(TAG, "trigger scan %d error", scan_channel); - goto end; + return; } /* shadow wifi_event_scan_channel_done() called */ vTaskDelay(100); @@ -132,20 +133,19 @@ static void scan_loop() if (ret == 0) { /* timeout */ ESP_LOGE(TAG, "scan channel %d timeout", scan_channel); - goto end; + return; } } - -end: - if (scan_ctx.cb) { -// scan_ctx.cb(scan_ctx.total_aps, scan_ctx.ap); - } } static void wifi_manager_scan_task(void *arg) { scan_loop(); free(scan_ctx.ap); + /* callback */ + if (scan_ctx.cb) { + scan_ctx.cb(scan_ctx.total_aps, scan_ctx.ap, arg); + } xSemaphoreGive(scan_ctx.lock); vTaskDelete(NULL); } @@ -167,7 +167,7 @@ int wifi_manager_trigger_scan(wifi_manager_scan_done_cb cb, void *arg) ulTaskNotifyTake(pdTRUE, 0); xTaskCreatePinnedToCore(wifi_manager_scan_task, "scan task", 4 * 1024, - NULL, 7, &scan_ctx.task, 0); + arg, 7, &scan_ctx.task, 0); return 0; }