From d6fae38d46190058b0eb7c961252fda9a160b5a9 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 28 Feb 2026 21:52:11 +0800 Subject: [PATCH] feat(rest): simplify web ui and prune non-core endpoints --- README.md | 115 +- components/control_plane/CMakeLists.txt | 2 +- .../internal/rest_server_internal.h | 19 - components/control_plane/src/rest_server.c | 1166 +++-------------- .../control_plane/src/rest_server_common.c | 24 - .../control_plane/src/rest_server_jobs.c | 90 +- .../control_plane/src/rest_server_ops.c | 544 +------- .../src/rest_server_print_render.c | 540 -------- .../src/rest_server_print_text.c | 90 ++ 9 files changed, 282 insertions(+), 2308 deletions(-) delete mode 100644 components/control_plane/src/rest_server_print_render.c create mode 100644 components/control_plane/src/rest_server_print_text.c diff --git a/README.md b/README.md index f4c9cc5..1d68011 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ESP32-S3 works as a Wi-Fi REST controller and replaces Android App logic: ## Features - Wi-Fi STA-only mode (configured SSID/password; no SoftAP fallback) -- Built-in web UI at `/` for connect/status, text print, and Z-Image prompt print +- Built-in minimal web UI at `/` for voice tap2talk - BLE central client auto-scan/connect to printer name - Async print queue with jobs (`queued/running/success/failed/canceled`) - Printer precheck (paper / battery / temperature) @@ -26,6 +26,8 @@ ESP32-S3 works as a Wi-Fi REST controller and replaces Android App logic: - `0x00` power off - `0x02` feed paper - REST APIs: + - Root: + - `GET /` - Health/connection: - `GET /v1/health` - `POST /v1/printer/connect` @@ -34,26 +36,10 @@ ESP32-S3 works as a Wi-Fi REST controller and replaces Android App logic: - Print: - `POST /v1/print/raster` - `POST /v1/print/image` (JSON prompt -> generate image -> print, also compatible with direct PNG binary upload) - - `POST /v1/print/qr` - `POST /v1/print/text` - - `POST /v1/print/receipt` - - `POST /v1/print/label` - Jobs: - `GET /v1/jobs` - - `DELETE /v1/jobs` - `GET /v1/jobs/{id}` - - `DELETE /v1/jobs/{id}` - - Label control: - - `POST /v1/label/gap_move` - - `GET /v1/label/offset` - - `POST /v1/label/offset` - - OTA: - - `GET /v1/ota/version` - - `POST /v1/ota/jump_boot` - - `POST /v1/ota/jump_app` - - `POST /v1/ota/erase_page` - - `POST /v1/ota/write_frame` - - `POST /v1/ota/upgrade` - Voice: - `GET /v1/voice/status` - `POST /v1/voice/session/start` @@ -147,14 +133,9 @@ Open the controller IP in a browser: http:/// ``` -The page provides: -- health/status check -- connect/disconnect printer -- submit a simple text print job -- input an image prompt, then let ESP32-S3 call DashScope Z-Image, download PNG, decode/threshold/scale/raster conversion, and print -- view jobs list - -If API key is enabled, input it in the page before invoking actions. +The page provides a minimal voice console: +- single-round tap2talk button +- voice status display ## REST Examples @@ -222,19 +203,6 @@ Request constraints: - Request body limit: 4 MB - Suggested image size: <= 3 MB when using direct PNG upload compatibility mode -### QR print -```bash -curl -X POST http:///v1/print/qr \ - -H 'Content-Type: application/json' \ - -d '{ - "text":"https://example.com/pay/123", - "ecc":"H", - "module_scale":0, - "margin_modules":2, - "density":"中等" - }' -``` - ### Text print ```bash curl -X POST http:///v1/print/text \ @@ -248,74 +216,10 @@ curl -X POST http:///v1/print/text \ }' ``` -### Receipt print -```bash -curl -X POST http:///v1/print/receipt \ - -H 'Content-Type: application/json' \ - -d '{ - "title":"TQ FOOD", - "density":"中等", - "footer":"Thanks!", - "items":[ - {"name":"DishA","qty":2,"price":12.5}, - {"name":"DishB","qty":1,"price":8.0} - ] - }' -``` - -### Label print (with optional gap/offset) -```bash -curl -X POST http:///v1/print/label \ - -H 'Content-Type: application/json' \ - -d '{ - "width":384, - "height":260, - "encoding":"base64_msb_1bpp", - "data":"", - "gap_move_before":true, - "offset_tenths_mm":128, - "density":"中等" - }' -``` - ### Jobs ```bash curl http:///v1/jobs curl http:///v1/jobs/1 -curl -X DELETE http:///v1/jobs/1 -curl -X DELETE http:///v1/jobs -d '{"include_success":true,"include_failed":true,"include_canceled":true}' -``` - -### Label control -```bash -curl -X POST http:///v1/label/gap_move -curl http:///v1/label/offset -curl -X POST http:///v1/label/offset \ - -H 'Content-Type: application/json' \ - -d '{"offset_tenths_mm":128}' -``` - -### OTA primitives -```bash -curl http:///v1/ota/version -curl -X POST http:///v1/ota/jump_boot -curl -X POST http:///v1/ota/erase_page -H 'Content-Type: application/json' -d '{"page_num":0}' -curl -X POST http:///v1/ota/write_frame -H 'Content-Type: application/json' -d '{"packet_num":0,"is_last_frame":false,"data":""}' -curl -X POST http:///v1/ota/jump_app -``` - -### OTA full upgrade -```bash -curl -X POST http:///v1/ota/upgrade \ - -H 'Content-Type: application/json' \ - -d '{ - "firmware":"", - "jump_boot":true, - "jump_app":true, - "page_size":1024, - "packet_size":236, - "timeout_ms_per_step":5000 - }' ``` ### Voice tap2talk @@ -354,13 +258,12 @@ curl -X POST http:///v1/voice/session/stop - BLE side is central/client role, not printer peripheral role. - `base64_msb_1bpp` uses Android-compatible bit order (MSB first). - `/v1/print/image` supports two modes: JSON prompt generation (DashScope Z-Image) and raw `image/png` upload (no base64 wrapper). +- `/v1/print/text` supports UTF-8 Chinese via embedded 16x16 GB2312 glyphs. +- Characters outside embedded glyph set are rendered as square fallback boxes. - Large buffers for image upload/decode prefer PSRAM first, then fallback to internal RAM. - Partition table uses `partitions.csv` with a 4MB `factory` app partition on 16MB flash modules. -- `/v1/print/text` and `/v1/print/receipt` now support UTF-8 Chinese via embedded 16x16 GB2312 glyphs. -- Characters outside embedded glyph set are rendered as square fallback boxes. -- QR encoding uses embedded `qrcodegen` (Project Nayuki C implementation). - Voice flow is now single-round: audio uplink stops after `SpeechEnded`, then `LocalRespondingEnded` and `Stop` are sent after local playback drain. -- Web UI blocks starting the next voice round while voice is Thinking/Responding, marker image generation is running, or printer queue is still busy. +- Voice flow blocks starting the next round while voice is Thinking/Responding, marker image generation is running, or printer queue is still busy. ## Font Assets diff --git a/components/control_plane/CMakeLists.txt b/components/control_plane/CMakeLists.txt index 5b35a3f..6e58054 100644 --- a/components/control_plane/CMakeLists.txt +++ b/components/control_plane/CMakeLists.txt @@ -6,7 +6,7 @@ idf_component_register( "src/rest_server_ops.c" "src/rest_server_print.c" "src/rest_server_print_image.c" - "src/rest_server_print_render.c" + "src/rest_server_print_text.c" "src/rest_server_jobs.c" "src/rest_server_voice.c" INCLUDE_DIRS diff --git a/components/control_plane/internal/rest_server_internal.h b/components/control_plane/internal/rest_server_internal.h index be010f9..b47ef63 100644 --- a/components/control_plane/internal/rest_server_internal.h +++ b/components/control_plane/internal/rest_server_internal.h @@ -14,7 +14,6 @@ esp_err_t rest_server_send_error(httpd_req_t *req, const char *status, const cha esp_err_t rest_server_read_body(httpd_req_t *req, char **out_body); bool rest_server_parse_uri_u32_tail(const char *uri, uint32_t *out_value); esp_err_t rest_server_base64_decode_alloc(const char *b64, uint8_t **out_raw, size_t *out_len); -void rest_server_appendf(char *dst, size_t cap, size_t *offset, const char *fmt, ...); bool rest_server_json_bool_with_default(cJSON *item, bool default_value); esp_err_t rest_server_print_submit_raster_job_and_reply(httpd_req_t *req, const uint8_t *raw, @@ -30,31 +29,13 @@ esp_err_t rest_server_health_get(httpd_req_t *req); esp_err_t rest_server_connect_post(httpd_req_t *req); esp_err_t rest_server_disconnect_post(httpd_req_t *req); esp_err_t rest_server_status_get(httpd_req_t *req); -esp_err_t rest_server_printer_direct_debug_get(httpd_req_t *req); -esp_err_t rest_server_printer_direct_debug_post(httpd_req_t *req); esp_err_t rest_server_print_raster_post(httpd_req_t *req); esp_err_t rest_server_print_image_post(httpd_req_t *req); -esp_err_t rest_server_print_qr_post(httpd_req_t *req); -esp_err_t rest_server_print_label_post(httpd_req_t *req); esp_err_t rest_server_print_text_post(httpd_req_t *req); -esp_err_t rest_server_print_receipt_post(httpd_req_t *req); esp_err_t rest_server_job_get(httpd_req_t *req); esp_err_t rest_server_jobs_get(httpd_req_t *req); -esp_err_t rest_server_job_delete(httpd_req_t *req); -esp_err_t rest_server_jobs_delete(httpd_req_t *req); - -esp_err_t rest_server_label_gap_move_post(httpd_req_t *req); -esp_err_t rest_server_label_offset_get(httpd_req_t *req); -esp_err_t rest_server_label_offset_post(httpd_req_t *req); - -esp_err_t rest_server_ota_version_get(httpd_req_t *req); -esp_err_t rest_server_ota_jump_boot_post(httpd_req_t *req); -esp_err_t rest_server_ota_jump_app_post(httpd_req_t *req); -esp_err_t rest_server_ota_erase_page_post(httpd_req_t *req); -esp_err_t rest_server_ota_write_frame_post(httpd_req_t *req); -esp_err_t rest_server_ota_upgrade_post(httpd_req_t *req); esp_err_t rest_server_voice_status_get(httpd_req_t *req); esp_err_t rest_server_voice_session_start_post(httpd_req_t *req); diff --git a/components/control_plane/src/rest_server.c b/components/control_plane/src/rest_server.c index 85c9b84..08a31cc 100644 --- a/components/control_plane/src/rest_server.c +++ b/components/control_plane/src/rest_server.c @@ -1,7 +1,5 @@ #include "control_plane.h" -#include - #include "rest_server_internal.h" #include "esp_http_server.h" #include "esp_log.h" @@ -12,814 +10,52 @@ static httpd_handle_t s_server; static const char *s_web_index = "\n" - "\n" + "\n" "\n" - " \n" - " \n" - " TQ Printer Controller\n" + " \n" + " \n" + " TQ Voice Console\n" " \n" "\n" "\n" - "
\n" - "

TQ Printer Controller

\n" - "

ESP32-S3 local control page. This page calls the onboard REST API directly.

\n" + "
\n" + "

TQ Voice Console

\n" + "

ESP32-S3 本地语音控制页。用于单轮语音对话(Tap2Talk)。

\n" "\n" - " \n" - " \n" - "\n" - "
\n" - " \n" - " \n" - " \n" + "
\n" + " \n" "
\n" - "\n" - " \n" - " \n" - " \n" - " \n" - "
\n" - " \n" - " \n" - " \n" - "
\n" - "\n" - " \n" - "
\n" - " \n" - "
\n" - "
纸张状态:未知
\n" - "
Backend: -, Connected: -, Transport: -, GPIO: -, Expect: -
\n" - "
\n" - " \n" - "
\n" - "
Only effective on Direct backend. Use for sensor bring-up/debug.
\n" - "\n" - " \n" - "
参数标签中的字段名与 /v1/printer/direct/debug 的 JSON 键保持一致。
\n" - "
\n" - "
\n" - " \n" - " \n" - "
\n" - "
\n" - " \n" - " \n" - "
\n" - "
\n" - " \n" - " \n" - "
\n" - "
\n" - "
\n" - "
\n" - " \n" - " \n" - "
\n" - "
\n" - " \n" - " \n" - "
\n" - "
\n" - " \n" - " \n" - "
\n" - "
\n" - " \n" - " \n" - "
\n" - "
\n" - "
\n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - "
\n" - "
override_* 参数填 0 表示不覆盖,恢复为运行时默认策略。
\n" - "
Auto Diagnose 使用低功耗测试图案;若点击后仍重启(Brownout),请先改善供电或降低热头功率。
\n" - "\n" - " \n" - " \n" - "
\n" - " \n" - "
\n" - "\n" - " \n" - " \n" - "
\n" - " \n" - " \n" - " \n" - "
\n" - "
\n" - " \n" - " \n" - " \n" - "
\n" - "
\n" - " \n" - " \n" - " \n" - " \n" - "
\n" - "
Submit prompt only: ESP32-S3 calls DashScope Z-Image, downloads PNG, rasterizes and prints.
\n" - "\n" - " \n" - "
\n" - " \n" - "
\n" - "
语音状态:初始化中
\n" - "
\n" - "
单击开始本轮语音输入;识别结束后自动停止上传,等待语音播报和打印完成。
\n" - "\n" - "
Tip: connect printer first, then submit print jobs.
\n" - "
Ready.
\n" + "
语音状态:初始化中
\n" "
\n" "\n" " \n" "\n" - "\n"; + "\n" +; static esp_err_t index_get(httpd_req_t *req) { httpd_resp_set_type(req, "text/html; charset=utf-8"); @@ -966,7 +264,7 @@ esp_err_t rest_server_start(void) { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = CONFIG_TQ_HTTP_PORT; config.uri_match_fn = httpd_uri_match_wildcard; - config.max_uri_handlers = 36; + config.max_uri_handlers = 20; config.stack_size = 10240; #if CONFIG_FREERTOS_UNICORE config.core_id = 0; @@ -1022,20 +320,6 @@ esp_err_t rest_server_start(void) { }; httpd_register_uri_handler(s_server, &status); - httpd_uri_t direct_debug_get_uri = { - .uri = "/v1/printer/direct/debug", - .method = HTTP_GET, - .handler = rest_server_printer_direct_debug_get, - }; - httpd_register_uri_handler(s_server, &direct_debug_get_uri); - - httpd_uri_t direct_debug_post_uri = { - .uri = "/v1/printer/direct/debug", - .method = HTTP_POST, - .handler = rest_server_printer_direct_debug_post, - }; - httpd_register_uri_handler(s_server, &direct_debug_post_uri); - httpd_uri_t voice_status = { .uri = "/v1/voice/status", .method = HTTP_GET, @@ -1085,13 +369,6 @@ esp_err_t rest_server_start(void) { }; httpd_register_uri_handler(s_server, &print_image); - httpd_uri_t print_qr = { - .uri = "/v1/print/qr", - .method = HTTP_POST, - .handler = rest_server_print_qr_post, - }; - httpd_register_uri_handler(s_server, &print_qr); - httpd_uri_t print_text = { .uri = "/v1/print/text", .method = HTTP_POST, @@ -1099,20 +376,6 @@ esp_err_t rest_server_start(void) { }; httpd_register_uri_handler(s_server, &print_text); - httpd_uri_t print_label = { - .uri = "/v1/print/label", - .method = HTTP_POST, - .handler = rest_server_print_label_post, - }; - httpd_register_uri_handler(s_server, &print_label); - - httpd_uri_t print_receipt = { - .uri = "/v1/print/receipt", - .method = HTTP_POST, - .handler = rest_server_print_receipt_post, - }; - httpd_register_uri_handler(s_server, &print_receipt); - httpd_uri_t jobs = { .uri = "/v1/jobs", .method = HTTP_GET, @@ -1120,13 +383,6 @@ esp_err_t rest_server_start(void) { }; httpd_register_uri_handler(s_server, &jobs); - httpd_uri_t jobs_delete_uri = { - .uri = "/v1/jobs", - .method = HTTP_DELETE, - .handler = rest_server_jobs_delete, - }; - httpd_register_uri_handler(s_server, &jobs_delete_uri); - httpd_uri_t job = { .uri = "/v1/jobs/*", .method = HTTP_GET, @@ -1134,76 +390,6 @@ esp_err_t rest_server_start(void) { }; httpd_register_uri_handler(s_server, &job); - httpd_uri_t job_delete_uri = { - .uri = "/v1/jobs/*", - .method = HTTP_DELETE, - .handler = rest_server_job_delete, - }; - httpd_register_uri_handler(s_server, &job_delete_uri); - - httpd_uri_t label_gap = { - .uri = "/v1/label/gap_move", - .method = HTTP_POST, - .handler = rest_server_label_gap_move_post, - }; - httpd_register_uri_handler(s_server, &label_gap); - - httpd_uri_t label_offset_get_uri = { - .uri = "/v1/label/offset", - .method = HTTP_GET, - .handler = rest_server_label_offset_get, - }; - httpd_register_uri_handler(s_server, &label_offset_get_uri); - - httpd_uri_t label_offset_post_uri = { - .uri = "/v1/label/offset", - .method = HTTP_POST, - .handler = rest_server_label_offset_post, - }; - httpd_register_uri_handler(s_server, &label_offset_post_uri); - - httpd_uri_t ota_version = { - .uri = "/v1/ota/version", - .method = HTTP_GET, - .handler = rest_server_ota_version_get, - }; - httpd_register_uri_handler(s_server, &ota_version); - - httpd_uri_t ota_jump_boot = { - .uri = "/v1/ota/jump_boot", - .method = HTTP_POST, - .handler = rest_server_ota_jump_boot_post, - }; - httpd_register_uri_handler(s_server, &ota_jump_boot); - - httpd_uri_t ota_jump_app = { - .uri = "/v1/ota/jump_app", - .method = HTTP_POST, - .handler = rest_server_ota_jump_app_post, - }; - httpd_register_uri_handler(s_server, &ota_jump_app); - - httpd_uri_t ota_erase_page = { - .uri = "/v1/ota/erase_page", - .method = HTTP_POST, - .handler = rest_server_ota_erase_page_post, - }; - httpd_register_uri_handler(s_server, &ota_erase_page); - - httpd_uri_t ota_write_frame = { - .uri = "/v1/ota/write_frame", - .method = HTTP_POST, - .handler = rest_server_ota_write_frame_post, - }; - httpd_register_uri_handler(s_server, &ota_write_frame); - - httpd_uri_t ota_upgrade = { - .uri = "/v1/ota/upgrade", - .method = HTTP_POST, - .handler = rest_server_ota_upgrade_post, - }; - httpd_register_uri_handler(s_server, &ota_upgrade); - ESP_LOGI(TAG, "REST server started on port %d", CONFIG_TQ_HTTP_PORT); return ESP_OK; } diff --git a/components/control_plane/src/rest_server_common.c b/components/control_plane/src/rest_server_common.c index 05953e5..9e17e26 100644 --- a/components/control_plane/src/rest_server_common.c +++ b/components/control_plane/src/rest_server_common.c @@ -1,8 +1,6 @@ #include "rest_server_internal.h" #include -#include -#include #include #include @@ -154,28 +152,6 @@ esp_err_t rest_server_base64_decode_alloc(const char *b64, uint8_t **out_raw, si return ESP_OK; } -void rest_server_appendf(char *dst, size_t cap, size_t *offset, const char *fmt, ...) { - if (dst == NULL || cap == 0 || offset == NULL || fmt == NULL || *offset >= cap) { - return; - } - - va_list ap; - va_start(ap, fmt); - int n = vsnprintf(dst + *offset, cap - *offset, fmt, ap); - va_end(ap); - - if (n <= 0) { - return; - } - - size_t written = (size_t)n; - if (written >= cap - *offset) { - *offset = cap - 1; - return; - } - *offset += written; -} - bool rest_server_json_bool_with_default(cJSON *item, bool default_value) { if (item == NULL) { return default_value; diff --git a/components/control_plane/src/rest_server_jobs.c b/components/control_plane/src/rest_server_jobs.c index 3fdebc0..2f8a0c0 100644 --- a/components/control_plane/src/rest_server_jobs.c +++ b/components/control_plane/src/rest_server_jobs.c @@ -1,8 +1,7 @@ #include "rest_server_internal.h" -#include - #include "domain.h" + esp_err_t rest_server_job_get(httpd_req_t *req) { if (!rest_server_auth_ok(req)) { return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); @@ -84,90 +83,3 @@ esp_err_t rest_server_jobs_get(httpd_req_t *req) { cJSON_Delete(root); return err; } - -esp_err_t rest_server_job_delete(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - uint32_t job_id = 0; - if (!rest_server_parse_uri_u32_tail(req->uri, &job_id)) { - return rest_server_send_error(req, "400 Bad Request", "invalid job id"); - } - - char cancel_err[128] = {0}; - esp_err_t cancel_rc = printer_protocol_cancel_job(job_id, cancel_err, sizeof(cancel_err)); - if (cancel_rc == ESP_ERR_NOT_FOUND) { - return rest_server_send_error(req, "404 Not Found", "job not found"); - } - if (cancel_rc == ESP_ERR_INVALID_STATE) { - return rest_server_send_error(req, - "409 Conflict", - cancel_err[0] != '\0' ? cancel_err : "job already finished"); - } - if (cancel_rc != ESP_OK) { - return rest_server_send_error(req, - "409 Conflict", - cancel_err[0] != '\0' ? cancel_err : "cancel failed"); - } - - print_job_info_t info; - bool ok = printer_protocol_get_job(job_id, &info); - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddNumberToObject(root, "job_id", job_id); - if (ok) { - cJSON_AddStringToObject(root, "state", printer_protocol_job_state_str(info.state)); - cJSON_AddStringToObject(root, "error", info.error); - } else { - cJSON_AddStringToObject(root, "state", "canceled"); - } - - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_jobs_delete(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - bool include_success = true; - bool include_failed = true; - bool include_canceled = true; - - if (req->content_len > 0) { - char *body = NULL; - esp_err_t body_err = rest_server_read_body(req, &body); - if (body_err != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid request body"); - } - - cJSON *json = cJSON_Parse(body); - free(body); - if (json == NULL) { - return rest_server_send_error(req, "400 Bad Request", "invalid json"); - } - - include_success = rest_server_json_bool_with_default(cJSON_GetObjectItemCaseSensitive(json, "include_success"), true); - include_failed = rest_server_json_bool_with_default(cJSON_GetObjectItemCaseSensitive(json, "include_failed"), true); - include_canceled = rest_server_json_bool_with_default(cJSON_GetObjectItemCaseSensitive(json, "include_canceled"), true); - cJSON_Delete(json); - } - - size_t removed = printer_protocol_cleanup_jobs(include_success, include_failed, include_canceled); - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddNumberToObject(root, "removed", (double)removed); - cJSON_AddBoolToObject(root, "include_success", include_success); - cJSON_AddBoolToObject(root, "include_failed", include_failed); - cJSON_AddBoolToObject(root, "include_canceled", include_canceled); - - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - diff --git a/components/control_plane/src/rest_server_ops.c b/components/control_plane/src/rest_server_ops.c index 7a2c773..7678c96 100644 --- a/components/control_plane/src/rest_server_ops.c +++ b/components/control_plane/src/rest_server_ops.c @@ -1,9 +1,8 @@ #include "rest_server_internal.h" -#include #include -#include #include +#include #include "control_plane.h" #include "domain.h" @@ -214,21 +213,21 @@ esp_err_t rest_server_connect_post(httpd_req_t *req) { if (err == ESP_ERR_TIMEOUT) { return rest_server_send_error(req, "504 Gateway Timeout", - connect_err[0] != '\0' ? connect_err : "connect timeout"); + connect_err[0] != 0 ? connect_err : "connect timeout"); } if (err == ESP_ERR_NOT_SUPPORTED || err == ESP_ERR_INVALID_STATE) { return rest_server_send_error(req, "409 Conflict", - connect_err[0] != '\0' ? connect_err : "connect failed"); + connect_err[0] != 0 ? connect_err : "connect failed"); } if (err == ESP_ERR_INVALID_ARG) { return rest_server_send_error(req, "400 Bad Request", - connect_err[0] != '\0' ? connect_err : "invalid connect params"); + connect_err[0] != 0 ? connect_err : "invalid connect params"); } return rest_server_send_error(req, "500 Internal Server Error", - connect_err[0] != '\0' ? connect_err : "connect failed"); + connect_err[0] != 0 ? connect_err : "connect failed"); } cJSON *root = cJSON_CreateObject(); @@ -272,536 +271,3 @@ esp_err_t rest_server_status_get(httpd_req_t *req) { cJSON_Delete(root); return err; } - -static void add_direct_debug_json_fields(cJSON *root, const printer_direct_debug_config_t *cfg) { - cJSON_AddNumberToObject(root, "shift_clock_high_us", cfg->shift_clock_high_us); - cJSON_AddNumberToObject(root, "shift_clock_low_us", cfg->shift_clock_low_us); - cJSON_AddNumberToObject(root, "latch_pulse_us", cfg->latch_pulse_us); - cJSON_AddBoolToObject(root, "strobe_active_high", cfg->strobe_active_high); - cJSON_AddBoolToObject(root, "boost_active_high", cfg->boost_active_high); - cJSON_AddNumberToObject(root, "override_strobe_on_us", cfg->override_strobe_on_us); - cJSON_AddNumberToObject(root, "override_strobe_interval_us", cfg->override_strobe_interval_us); - cJSON_AddNumberToObject(root, "override_motor_step_us", cfg->override_motor_step_us); - cJSON_AddNumberToObject(root, "override_steps_per_line", cfg->override_steps_per_line); -} - -static esp_err_t direct_debug_error_response(httpd_req_t *req, esp_err_t rc, const char *msg) { - if (rc == ESP_ERR_INVALID_ARG) { - return rest_server_send_error(req, "400 Bad Request", msg); - } - if (rc == ESP_ERR_NOT_SUPPORTED || rc == ESP_ERR_INVALID_STATE) { - return rest_server_send_error(req, "409 Conflict", msg); - } - if (rc == ESP_ERR_TIMEOUT) { - return rest_server_send_error(req, "504 Gateway Timeout", msg); - } - return rest_server_send_error(req, "500 Internal Server Error", msg); -} - -esp_err_t rest_server_printer_direct_debug_get(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - printer_direct_debug_config_t cfg = {0}; - char get_err[128] = {0}; - esp_err_t rc = printer_protocol_get_direct_debug_config(&cfg, get_err, sizeof(get_err)); - if (rc != ESP_OK) { - return direct_debug_error_response(req, - rc, - get_err[0] != '\0' ? get_err : "get direct debug failed"); - } - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddStringToObject(root, "backend", printer_backend_str(printer_protocol_get_backend())); - add_direct_debug_json_fields(root, &cfg); - - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_printer_direct_debug_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - printer_direct_debug_config_t cfg = {0}; - char get_err[128] = {0}; - esp_err_t rc = printer_protocol_get_direct_debug_config(&cfg, get_err, sizeof(get_err)); - if (rc != ESP_OK) { - return direct_debug_error_response(req, - rc, - get_err[0] != '\0' ? get_err : "get direct debug failed"); - } - - bool reset_defaults = false; - bool has_update = false; - if (req->content_len > 0) { - char *body = NULL; - esp_err_t body_err = rest_server_read_body(req, &body); - if (body_err != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid request body"); - } - - cJSON *json = cJSON_Parse(body); - free(body); - if (json == NULL) { - return rest_server_send_error(req, "400 Bad Request", "invalid json"); - } - - cJSON *jreset = cJSON_GetObjectItemCaseSensitive(json, "reset_defaults"); - if (cJSON_IsBool(jreset)) { - reset_defaults = cJSON_IsTrue(jreset); - } - - cJSON *jshift_hi = cJSON_GetObjectItemCaseSensitive(json, "shift_clock_high_us"); - if (jshift_hi != NULL) { - if (!cJSON_IsNumber(jshift_hi) || jshift_hi->valuedouble < 1 || jshift_hi->valuedouble > 50) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "shift_clock_high_us must be 1..50"); - } - cfg.shift_clock_high_us = (uint16_t)jshift_hi->valuedouble; - has_update = true; - } - - cJSON *jshift_lo = cJSON_GetObjectItemCaseSensitive(json, "shift_clock_low_us"); - if (jshift_lo != NULL) { - if (!cJSON_IsNumber(jshift_lo) || jshift_lo->valuedouble < 1 || jshift_lo->valuedouble > 50) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "shift_clock_low_us must be 1..50"); - } - cfg.shift_clock_low_us = (uint16_t)jshift_lo->valuedouble; - has_update = true; - } - - cJSON *jlatch = cJSON_GetObjectItemCaseSensitive(json, "latch_pulse_us"); - if (jlatch != NULL) { - if (!cJSON_IsNumber(jlatch) || jlatch->valuedouble < 1 || jlatch->valuedouble > 50) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "latch_pulse_us must be 1..50"); - } - cfg.latch_pulse_us = (uint16_t)jlatch->valuedouble; - has_update = true; - } - - cJSON *jstb = cJSON_GetObjectItemCaseSensitive(json, "strobe_active_high"); - if (cJSON_IsBool(jstb)) { - cfg.strobe_active_high = cJSON_IsTrue(jstb); - has_update = true; - } - - cJSON *jboost = cJSON_GetObjectItemCaseSensitive(json, "boost_active_high"); - if (cJSON_IsBool(jboost)) { - cfg.boost_active_high = cJSON_IsTrue(jboost); - has_update = true; - } - - cJSON *jstrobe_on = cJSON_GetObjectItemCaseSensitive(json, "override_strobe_on_us"); - if (jstrobe_on != NULL) { - if (!cJSON_IsNumber(jstrobe_on) || jstrobe_on->valuedouble < 0 || jstrobe_on->valuedouble > 10000) { - cJSON_Delete(json); - return rest_server_send_error(req, - "400 Bad Request", - "override_strobe_on_us must be 0..10000"); - } - if (jstrobe_on->valuedouble > 0 && jstrobe_on->valuedouble < 100) { - cJSON_Delete(json); - return rest_server_send_error(req, - "400 Bad Request", - "override_strobe_on_us must be 0 or 100..10000"); - } - cfg.override_strobe_on_us = (uint16_t)jstrobe_on->valuedouble; - has_update = true; - } - - cJSON *jstrobe_gap = cJSON_GetObjectItemCaseSensitive(json, "override_strobe_interval_us"); - if (jstrobe_gap != NULL) { - if (!cJSON_IsNumber(jstrobe_gap) || jstrobe_gap->valuedouble < 0 || jstrobe_gap->valuedouble > 10000) { - cJSON_Delete(json); - return rest_server_send_error(req, - "400 Bad Request", - "override_strobe_interval_us must be 0..10000"); - } - cfg.override_strobe_interval_us = (uint16_t)jstrobe_gap->valuedouble; - has_update = true; - } - - cJSON *jmotor = cJSON_GetObjectItemCaseSensitive(json, "override_motor_step_us"); - if (jmotor != NULL) { - if (!cJSON_IsNumber(jmotor) || jmotor->valuedouble < 0 || jmotor->valuedouble > 20000) { - cJSON_Delete(json); - return rest_server_send_error(req, - "400 Bad Request", - "override_motor_step_us must be 0..20000"); - } - if (jmotor->valuedouble > 0 && jmotor->valuedouble < 100) { - cJSON_Delete(json); - return rest_server_send_error(req, - "400 Bad Request", - "override_motor_step_us must be 0 or 100..20000"); - } - cfg.override_motor_step_us = (uint16_t)jmotor->valuedouble; - has_update = true; - } - - cJSON *jsteps = cJSON_GetObjectItemCaseSensitive(json, "override_steps_per_line"); - if (jsteps != NULL) { - if (!cJSON_IsNumber(jsteps) || jsteps->valuedouble < 0 || jsteps->valuedouble > 8) { - cJSON_Delete(json); - return rest_server_send_error(req, - "400 Bad Request", - "override_steps_per_line must be 0..8"); - } - cfg.override_steps_per_line = (uint8_t)jsteps->valuedouble; - has_update = true; - } - - cJSON_Delete(json); - } - - char set_err[128] = {0}; - rc = printer_protocol_set_direct_debug_config(has_update ? &cfg : NULL, - reset_defaults, - set_err, - sizeof(set_err)); - if (rc != ESP_OK) { - return direct_debug_error_response(req, - rc, - set_err[0] != '\0' ? set_err : "set direct debug failed"); - } - - memset(&cfg, 0, sizeof(cfg)); - memset(get_err, 0, sizeof(get_err)); - rc = printer_protocol_get_direct_debug_config(&cfg, get_err, sizeof(get_err)); - if (rc != ESP_OK) { - return direct_debug_error_response(req, - rc, - get_err[0] != '\0' ? get_err : "get direct debug failed"); - } - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddBoolToObject(root, "reset_defaults", reset_defaults); - cJSON_AddBoolToObject(root, "updated", has_update); - cJSON_AddStringToObject(root, "backend", printer_backend_str(printer_protocol_get_backend())); - add_direct_debug_json_fields(root, &cfg); - - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_label_gap_move_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - uint32_t timeout_ms = domain_policy_rest_label_timeout_ms(); - if (req->content_len > 0) { - char *body = NULL; - esp_err_t body_err = rest_server_read_body(req, &body); - if (body_err != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid request body"); - } - cJSON *json = cJSON_Parse(body); - free(body); - if (json == NULL) { - return rest_server_send_error(req, "400 Bad Request", "invalid json"); - } - cJSON *jtimeout = cJSON_GetObjectItemCaseSensitive(json, "timeout_ms"); - if (jtimeout != NULL) { - if (!cJSON_IsNumber(jtimeout) || - jtimeout->valuedouble < (double)domain_policy_rest_label_timeout_min_ms() || - jtimeout->valuedouble > (double)domain_policy_rest_label_timeout_max_ms()) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "timeout_ms out of range"); - } - timeout_ms = (uint32_t)jtimeout->valuedouble; - } - cJSON_Delete(json); - } - - char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_gap_move(timeout_ms, cmd_err, sizeof(cmd_err)); - if (rc != ESP_OK) { - return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "gap move failed"); - } - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddStringToObject(root, "message", "gap move success"); - cJSON_AddNumberToObject(root, "timeout_ms", timeout_ms); - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_label_offset_get(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - uint8_t offset = 0; - char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_get_label_offset(&offset, - domain_policy_rest_label_timeout_ms(), - cmd_err, - sizeof(cmd_err)); - if (rc != ESP_OK) { - return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "get offset failed"); - } - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddNumberToObject(root, "offset_tenths_mm", offset); - cJSON_AddNumberToObject(root, "offset_mm", ((double)offset) / 10.0); - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_label_offset_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - char *body = NULL; - esp_err_t body_err = rest_server_read_body(req, &body); - if (body_err != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid request body"); - } - - cJSON *json = cJSON_Parse(body); - free(body); - if (json == NULL) { - return rest_server_send_error(req, "400 Bad Request", "invalid json"); - } - - cJSON *jvalue = cJSON_GetObjectItemCaseSensitive(json, "offset_tenths_mm"); - if (!cJSON_IsNumber(jvalue) || jvalue->valuedouble < 0 || jvalue->valuedouble > 254) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "offset_tenths_mm must be 0..254"); - } - - uint8_t value = (uint8_t)jvalue->valuedouble; - cJSON_Delete(json); - - char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_set_label_offset(value, - domain_policy_rest_label_timeout_ms(), - cmd_err, - sizeof(cmd_err)); - if (rc != ESP_OK) { - return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "set offset failed"); - } - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddNumberToObject(root, "offset_tenths_mm", value); - cJSON_AddNumberToObject(root, "offset_mm", ((double)value) / 10.0); - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_ota_version_get(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - printer_ota_version_t version = {0}; - char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_ota_get_version(&version, - domain_policy_rest_ota_timeout_ms(), - cmd_err, - sizeof(cmd_err)); - if (rc != ESP_OK) { - return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "get version failed"); - } - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddNumberToObject(root, "major", version.major); - cJSON_AddNumberToObject(root, "minor", version.minor); - cJSON_AddNumberToObject(root, "patch", version.patch); - char version_text[32]; - snprintf(version_text, sizeof(version_text), "V%u.%u.%u", version.major, version.minor, version.patch); - cJSON_AddStringToObject(root, "version", version_text); - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_ota_jump_boot_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_ota_jump_boot(domain_policy_rest_ota_timeout_ms(), - cmd_err, - sizeof(cmd_err)); - if (rc != ESP_OK) { - return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "jump boot failed"); - } - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddStringToObject(root, "message", "jumped to boot"); - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_ota_jump_app_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_ota_jump_app(domain_policy_rest_ota_timeout_ms(), - cmd_err, - sizeof(cmd_err)); - if (rc != ESP_OK) { - return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "jump app failed"); - } - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddStringToObject(root, "message", "jumped to app"); - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_ota_erase_page_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - char *body = NULL; - esp_err_t body_err = rest_server_read_body(req, &body); - if (body_err != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid request body"); - } - cJSON *json = cJSON_Parse(body); - free(body); - if (json == NULL) { - return rest_server_send_error(req, "400 Bad Request", "invalid json"); - } - - cJSON *jpage = cJSON_GetObjectItemCaseSensitive(json, "page_num"); - cJSON *jtimeout = cJSON_GetObjectItemCaseSensitive(json, "timeout_ms"); - if (!cJSON_IsNumber(jpage) || jpage->valuedouble < 0 || jpage->valuedouble > 65535) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "page_num must be 0..65535"); - } - - uint32_t timeout_ms = domain_policy_rest_ota_timeout_ms(); - if (jtimeout != NULL) { - if (!cJSON_IsNumber(jtimeout) || - jtimeout->valuedouble < (double)domain_policy_rest_ota_timeout_min_ms() || - jtimeout->valuedouble > (double)domain_policy_rest_ota_timeout_max_ms()) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "timeout_ms out of range"); - } - timeout_ms = (uint32_t)jtimeout->valuedouble; - } - - uint16_t page_num = (uint16_t)jpage->valuedouble; - cJSON_Delete(json); - - char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_ota_erase_page(page_num, timeout_ms, cmd_err, sizeof(cmd_err)); - if (rc != ESP_OK) { - return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "erase page failed"); - } - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddNumberToObject(root, "page_num", page_num); - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_ota_write_frame_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - char *body = NULL; - esp_err_t body_err = rest_server_read_body(req, &body); - if (body_err != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid request body"); - } - cJSON *json = cJSON_Parse(body); - free(body); - if (json == NULL) { - return rest_server_send_error(req, "400 Bad Request", "invalid json"); - } - - cJSON *jpack = cJSON_GetObjectItemCaseSensitive(json, "packet_num"); - cJSON *jlast = cJSON_GetObjectItemCaseSensitive(json, "is_last_frame"); - cJSON *jdata = cJSON_GetObjectItemCaseSensitive(json, "data"); - cJSON *jtimeout = cJSON_GetObjectItemCaseSensitive(json, "timeout_ms"); - - if (!cJSON_IsNumber(jpack) || jpack->valuedouble < 0 || jpack->valuedouble > 65535 || - !cJSON_IsString(jdata) || jdata->valuestring == NULL) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "packet_num and data are required"); - } - - uint32_t timeout_ms = domain_policy_rest_ota_timeout_ms(); - if (jtimeout != NULL) { - if (!cJSON_IsNumber(jtimeout) || - jtimeout->valuedouble < (double)domain_policy_rest_ota_timeout_min_ms() || - jtimeout->valuedouble > (double)domain_policy_rest_ota_timeout_max_ms()) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "timeout_ms out of range"); - } - timeout_ms = (uint32_t)jtimeout->valuedouble; - } - bool is_last = rest_server_json_bool_with_default(jlast, false); - uint16_t packet_num = (uint16_t)jpack->valuedouble; - - uint8_t *frame_data = NULL; - size_t frame_len = 0; - esp_err_t b64_rc = rest_server_base64_decode_alloc(jdata->valuestring, &frame_data, &frame_len); - cJSON_Delete(json); - if (b64_rc != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid base64 data"); - } - - char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_ota_write_frame(packet_num, - is_last, - frame_data, - frame_len, - timeout_ms, - cmd_err, - sizeof(cmd_err)); - free(frame_data); - if (rc != ESP_OK) { - return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "write frame failed"); - } - - cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); - cJSON_AddNumberToObject(root, "packet_num", packet_num); - cJSON_AddBoolToObject(root, "is_last_frame", is_last); - esp_err_t err = rest_server_send_json(req, "200 OK", root); - cJSON_Delete(root); - return err; -} - -esp_err_t rest_server_ota_upgrade_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - return rest_server_send_error(req, - "409 Conflict", - "bulk ota upgrade endpoint disabled; use jump/erase/write/version step APIs"); -} diff --git a/components/control_plane/src/rest_server_print_render.c b/components/control_plane/src/rest_server_print_render.c deleted file mode 100644 index cab1d82..0000000 --- a/components/control_plane/src/rest_server_print_render.c +++ /dev/null @@ -1,540 +0,0 @@ -#include "rest_server_internal.h" - -#include -#include -#include -#include - -#include "domain.h" - -static esp_err_t decode_image_json_to_raster(cJSON *json, - uint16_t *out_width, - uint16_t *out_height, - uint8_t **out_raster, - size_t *out_len, - char *err, - size_t err_len) { - if (json == NULL || out_width == NULL || out_height == NULL || out_raster == NULL || out_len == NULL) { - if (err != NULL && err_len > 0) { - snprintf(err, err_len, "invalid args"); - } - return ESP_ERR_INVALID_ARG; - } - - *out_width = 0; - *out_height = 0; - *out_raster = NULL; - *out_len = 0; - - cJSON *jwidth = cJSON_GetObjectItemCaseSensitive(json, "width"); - cJSON *jheight = cJSON_GetObjectItemCaseSensitive(json, "height"); - cJSON *jencoding = cJSON_GetObjectItemCaseSensitive(json, "encoding"); - cJSON *jdata = cJSON_GetObjectItemCaseSensitive(json, "data"); - - if (!cJSON_IsNumber(jwidth) || !cJSON_IsNumber(jheight) || - !cJSON_IsString(jencoding) || !cJSON_IsString(jdata)) { - if (err != NULL && err_len > 0) { - snprintf(err, err_len, "missing required image fields"); - } - return ESP_ERR_INVALID_ARG; - } - - uint16_t width = (uint16_t)jwidth->valuedouble; - uint16_t height = (uint16_t)jheight->valuedouble; - if (width == 0 || height == 0) { - if (err != NULL && err_len > 0) { - snprintf(err, err_len, "invalid image size"); - } - return ESP_ERR_INVALID_ARG; - } - - if (strcmp(jencoding->valuestring, "base64_msb_1bpp") == 0) { - uint8_t *raw = NULL; - size_t raw_len = 0; - esp_err_t b64_rc = rest_server_base64_decode_alloc(jdata->valuestring, &raw, &raw_len); - if (b64_rc != ESP_OK) { - if (err != NULL && err_len > 0) { - snprintf(err, err_len, "invalid base64"); - } - return ESP_ERR_INVALID_ARG; - } - *out_width = width; - *out_height = height; - *out_raster = raw; - *out_len = raw_len; - return ESP_OK; - } - - if (strcmp(jencoding->valuestring, "base64_gray8") == 0) { - uint8_t *gray = NULL; - size_t gray_len = 0; - esp_err_t b64_rc = rest_server_base64_decode_alloc(jdata->valuestring, &gray, &gray_len); - if (b64_rc != ESP_OK) { - if (err != NULL && err_len > 0) { - snprintf(err, err_len, "invalid base64"); - } - return ESP_ERR_INVALID_ARG; - } - - size_t expected = (size_t)width * height; - if (gray_len != expected) { - free(gray); - if (err != NULL && err_len > 0) { - snprintf(err, err_len, "gray8 size mismatch"); - } - return ESP_ERR_INVALID_SIZE; - } - - cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "scale_to_width"); - cJSON *jthreshold = cJSON_GetObjectItemCaseSensitive(json, "threshold"); - cJSON *jinvert = cJSON_GetObjectItemCaseSensitive(json, "invert"); - cJSON *jmaxh = cJSON_GetObjectItemCaseSensitive(json, "max_height"); - - bool scale_to_width = rest_server_json_bool_with_default(jscale, true); - bool invert = rest_server_json_bool_with_default(jinvert, false); - uint8_t threshold = 160; - uint16_t max_height = 2200; - if (cJSON_IsNumber(jthreshold) && jthreshold->valuedouble >= 0 && jthreshold->valuedouble <= 255) { - threshold = (uint8_t)jthreshold->valuedouble; - } - if (cJSON_IsNumber(jmaxh) && jmaxh->valuedouble >= 64 && jmaxh->valuedouble <= 3000) { - max_height = (uint16_t)jmaxh->valuedouble; - } - - uint16_t out_w = 0; - uint16_t out_h = 0; - uint8_t *raster = NULL; - size_t raster_len = 0; - esp_err_t conv_rc = raster_tools_convert_gray8_to_raster_384(gray, - width, - height, - scale_to_width, - threshold, - invert, - max_height, - &out_w, - &out_h, - &raster, - &raster_len, - err, - err_len); - free(gray); - if (conv_rc != ESP_OK) { - return conv_rc; - } - - *out_width = out_w; - *out_height = out_h; - *out_raster = raster; - *out_len = raster_len; - return ESP_OK; - } - - if (err != NULL && err_len > 0) { - snprintf(err, err_len, "unsupported encoding"); - } - return ESP_ERR_NOT_SUPPORTED; -} - -esp_err_t rest_server_print_qr_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - char *body = NULL; - esp_err_t body_err = rest_server_read_body(req, &body); - if (body_err != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid request body"); - } - - cJSON *json = cJSON_Parse(body); - free(body); - if (json == NULL) { - return rest_server_send_error(req, "400 Bad Request", "invalid json"); - } - - cJSON *jtext = cJSON_GetObjectItemCaseSensitive(json, "text"); - cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density"); - cJSON *jecc = cJSON_GetObjectItemCaseSensitive(json, "ecc"); - cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "module_scale"); - cJSON *jmargin = cJSON_GetObjectItemCaseSensitive(json, "margin_modules"); - cJSON *jmaxh = cJSON_GetObjectItemCaseSensitive(json, "max_height"); - cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck"); - - if (!cJSON_IsString(jtext) || jtext->valuestring == NULL || jtext->valuestring[0] == '\0') { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "text is required"); - } - - const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL) - ? jdensity->valuestring - : "中等"; - bool direct_ignore_precheck = rest_server_json_bool_with_default(jignore, false); - - uint8_t ecc_level = 3; /* default H */ - if (cJSON_IsString(jecc) && jecc->valuestring != NULL) { - if (strcasecmp(jecc->valuestring, "L") == 0) { - ecc_level = 0; - } else if (strcasecmp(jecc->valuestring, "M") == 0) { - ecc_level = 1; - } else if (strcasecmp(jecc->valuestring, "Q") == 0) { - ecc_level = 2; - } else if (strcasecmp(jecc->valuestring, "H") == 0) { - ecc_level = 3; - } - } - - uint8_t module_scale = 0; - uint8_t margin_modules = 2; - uint16_t max_height = 2200; - if (cJSON_IsNumber(jscale) && jscale->valuedouble >= 0 && jscale->valuedouble <= 16) { - module_scale = (uint8_t)jscale->valuedouble; - } - if (cJSON_IsNumber(jmargin) && jmargin->valuedouble >= 1 && jmargin->valuedouble <= 12) { - margin_modules = (uint8_t)jmargin->valuedouble; - } - if (cJSON_IsNumber(jmaxh) && jmaxh->valuedouble >= 64 && jmaxh->valuedouble <= 3000) { - max_height = (uint16_t)jmaxh->valuedouble; - } - - uint16_t width = 0; - uint16_t height = 0; - uint8_t *raster = NULL; - size_t raster_len = 0; - char render_msg[128] = {0}; - esp_err_t qr_rc = raster_tools_render_qr_384(jtext->valuestring, - module_scale, - margin_modules, - ecc_level, - max_height, - &width, - &height, - &raster, - &raster_len, - render_msg, - sizeof(render_msg)); - cJSON_Delete(json); - if (qr_rc != ESP_OK) { - return rest_server_send_error(req, - "400 Bad Request", - render_msg[0] != '\0' ? render_msg : "qr render failed"); - } - - esp_err_t submit_rc = rest_server_print_submit_raster_job_and_reply(req, - raster, - raster_len, - width, - height, - density, - direct_ignore_precheck, - NULL, - NULL); - free(raster); - return submit_rc; -} - -esp_err_t rest_server_print_label_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - char *body = NULL; - esp_err_t body_err = rest_server_read_body(req, &body); - if (body_err != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid request body"); - } - - cJSON *json = cJSON_Parse(body); - free(body); - if (json == NULL) { - return rest_server_send_error(req, "400 Bad Request", "invalid json"); - } - - cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density"); - cJSON *jgap = cJSON_GetObjectItemCaseSensitive(json, "gap_move_before"); - cJSON *joffset = cJSON_GetObjectItemCaseSensitive(json, "offset_tenths_mm"); - cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck"); - - bool gap_move_before = rest_server_json_bool_with_default(jgap, true); - bool has_offset = cJSON_IsNumber(joffset); - uint8_t offset_value = 0; - if (has_offset) { - if (joffset->valuedouble < 0 || joffset->valuedouble > 254) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "offset_tenths_mm must be 0..254"); - } - offset_value = (uint8_t)joffset->valuedouble; - } - - const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL) - ? jdensity->valuestring - : "中等"; - bool direct_ignore_precheck = rest_server_json_bool_with_default(jignore, false); - - uint16_t width = 0; - uint16_t height = 0; - uint8_t *raster = NULL; - size_t raster_len = 0; - char decode_err[128] = {0}; - esp_err_t decode_rc = decode_image_json_to_raster(json, - &width, - &height, - &raster, - &raster_len, - decode_err, - sizeof(decode_err)); - cJSON_Delete(json); - if (decode_rc != ESP_OK) { - return rest_server_send_error(req, - "400 Bad Request", - decode_err[0] != '\0' ? decode_err : "decode image failed"); - } - - if (has_offset) { - char cmd_err[128] = {0}; - esp_err_t offset_rc = printer_protocol_set_label_offset(offset_value, - domain_policy_rest_label_timeout_ms(), - cmd_err, - sizeof(cmd_err)); - if (offset_rc != ESP_OK) { - free(raster); - return rest_server_send_error(req, - "409 Conflict", - cmd_err[0] != '\0' ? cmd_err : "set label offset failed"); - } - } - - if (gap_move_before) { - char cmd_err[128] = {0}; - esp_err_t gap_rc = printer_protocol_gap_move(domain_policy_rest_label_timeout_ms(), - cmd_err, - sizeof(cmd_err)); - if (gap_rc != ESP_OK) { - free(raster); - return rest_server_send_error(req, - "409 Conflict", - cmd_err[0] != '\0' ? cmd_err : "gap move failed"); - } - } - - esp_err_t submit_rc = rest_server_print_submit_raster_job_and_reply(req, - raster, - raster_len, - width, - height, - density, - direct_ignore_precheck, - NULL, - NULL); - free(raster); - return submit_rc; -} - -esp_err_t rest_server_print_text_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - char *body = NULL; - esp_err_t body_err = rest_server_read_body(req, &body); - if (body_err != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid request body"); - } - - cJSON *json = cJSON_Parse(body); - free(body); - if (json == NULL) { - return rest_server_send_error(req, "400 Bad Request", "invalid json"); - } - - cJSON *jtext = cJSON_GetObjectItemCaseSensitive(json, "text"); - cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density"); - cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "scale"); - cJSON *jline = cJSON_GetObjectItemCaseSensitive(json, "line_spacing"); - cJSON *jmaxh = cJSON_GetObjectItemCaseSensitive(json, "max_height"); - cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck"); - - if (!cJSON_IsString(jtext) || jtext->valuestring == NULL) { - cJSON_Delete(json); - return rest_server_send_error(req, "400 Bad Request", "text is required"); - } - - const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL) - ? jdensity->valuestring - : "中等"; - bool direct_ignore_precheck = rest_server_json_bool_with_default(jignore, false); - uint8_t scale = 2; - uint8_t line_spacing = 2; - uint16_t max_height = 2000; - - if (cJSON_IsNumber(jscale) && jscale->valuedouble >= 1 && jscale->valuedouble <= 6) { - scale = (uint8_t)jscale->valuedouble; - } - if (cJSON_IsNumber(jline) && jline->valuedouble >= 0 && jline->valuedouble <= 12) { - line_spacing = (uint8_t)jline->valuedouble; - } - if (cJSON_IsNumber(jmaxh) && jmaxh->valuedouble >= 64 && jmaxh->valuedouble <= 3000) { - max_height = (uint16_t)jmaxh->valuedouble; - } - - uint16_t width = 0; - uint16_t height = 0; - uint8_t *raster = NULL; - size_t raster_len = 0; - char render_msg[128] = {0}; - - esp_err_t render_rc = raster_tools_render_text_384(jtext->valuestring, - scale, - line_spacing, - max_height, - &width, - &height, - &raster, - &raster_len, - render_msg, - sizeof(render_msg)); - cJSON_Delete(json); - - if (render_rc != ESP_OK) { - return rest_server_send_error(req, - "400 Bad Request", - render_msg[0] != '\0' ? render_msg : "text render failed"); - } - - esp_err_t submit_rc = rest_server_print_submit_raster_job_and_reply(req, - raster, - raster_len, - width, - height, - density, - direct_ignore_precheck, - render_msg, - NULL); - free(raster); - return submit_rc; -} - -esp_err_t rest_server_print_receipt_post(httpd_req_t *req) { - if (!rest_server_auth_ok(req)) { - return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); - } - - char *body = NULL; - esp_err_t body_err = rest_server_read_body(req, &body); - if (body_err != ESP_OK) { - return rest_server_send_error(req, "400 Bad Request", "invalid request body"); - } - - cJSON *json = cJSON_Parse(body); - free(body); - if (json == NULL) { - return rest_server_send_error(req, "400 Bad Request", "invalid json"); - } - - cJSON *jtitle = cJSON_GetObjectItemCaseSensitive(json, "title"); - cJSON *jitems = cJSON_GetObjectItemCaseSensitive(json, "items"); - cJSON *jfooter = cJSON_GetObjectItemCaseSensitive(json, "footer"); - cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density"); - cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "scale"); - cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck"); - - const char *title = (cJSON_IsString(jtitle) && jtitle->valuestring != NULL) - ? jtitle->valuestring - : "TQ RECEIPT"; - const char *footer = (cJSON_IsString(jfooter) && jfooter->valuestring != NULL) - ? jfooter->valuestring - : "Thank you"; - const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL) - ? jdensity->valuestring - : "中等"; - bool direct_ignore_precheck = rest_server_json_bool_with_default(jignore, false); - uint8_t scale = 2; - if (cJSON_IsNumber(jscale) && jscale->valuedouble >= 1 && jscale->valuedouble <= 4) { - scale = (uint8_t)jscale->valuedouble; - } - - size_t text_cap = 8192; - char *text = (char *)calloc(1, text_cap); - if (text == NULL) { - cJSON_Delete(json); - return rest_server_send_error(req, "500 Internal Server Error", "no memory"); - } - - size_t off = 0; - rest_server_appendf(text, text_cap, &off, "%s\n", title); - rest_server_appendf(text, text_cap, &off, "--------------------------------\n"); - - double total = 0.0; - if (cJSON_IsArray(jitems)) { - int n = cJSON_GetArraySize(jitems); - for (int i = 0; i < n; ++i) { - cJSON *it = cJSON_GetArrayItem(jitems, i); - if (!cJSON_IsObject(it)) { - continue; - } - - cJSON *jname = cJSON_GetObjectItemCaseSensitive(it, "name"); - cJSON *jqty = cJSON_GetObjectItemCaseSensitive(it, "qty"); - cJSON *jprice = cJSON_GetObjectItemCaseSensitive(it, "price"); - - const char *name = (cJSON_IsString(jname) && jname->valuestring != NULL) - ? jname->valuestring - : "item"; - int qty = cJSON_IsNumber(jqty) ? (int)jqty->valuedouble : 1; - double price = cJSON_IsNumber(jprice) ? jprice->valuedouble : 0.0; - double line_total = qty * price; - total += line_total; - - rest_server_appendf(text, - text_cap, - &off, - "%-16.16s x%-3d %8.2f\n", - name, - qty, - line_total); - } - } else { - rest_server_appendf(text, text_cap, &off, "(no items)\n"); - } - - rest_server_appendf(text, text_cap, &off, "--------------------------------\n"); - rest_server_appendf(text, text_cap, &off, "TOTAL: %.2f\n", total); - rest_server_appendf(text, text_cap, &off, "%s\n", footer); - - uint16_t width = 0; - uint16_t height = 0; - uint8_t *raster = NULL; - size_t raster_len = 0; - char render_msg[128] = {0}; - - esp_err_t render_rc = raster_tools_render_text_384(text, - scale, - 2, - 2200, - &width, - &height, - &raster, - &raster_len, - render_msg, - sizeof(render_msg)); - free(text); - cJSON_Delete(json); - - if (render_rc != ESP_OK) { - return rest_server_send_error(req, - "400 Bad Request", - render_msg[0] != '\0' ? render_msg : "receipt render failed"); - } - - esp_err_t submit_rc = rest_server_print_submit_raster_job_and_reply(req, - raster, - raster_len, - width, - height, - density, - direct_ignore_precheck, - render_msg, - NULL); - free(raster); - return submit_rc; -} diff --git a/components/control_plane/src/rest_server_print_text.c b/components/control_plane/src/rest_server_print_text.c new file mode 100644 index 0000000..693afe2 --- /dev/null +++ b/components/control_plane/src/rest_server_print_text.c @@ -0,0 +1,90 @@ +#include "rest_server_internal.h" + +#include + +#include "domain.h" + +esp_err_t rest_server_print_text_post(httpd_req_t *req) { + if (!rest_server_auth_ok(req)) { + return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); + } + + char *body = NULL; + esp_err_t body_err = rest_server_read_body(req, &body); + if (body_err != ESP_OK) { + return rest_server_send_error(req, "400 Bad Request", "invalid request body"); + } + + cJSON *json = cJSON_Parse(body); + free(body); + if (json == NULL) { + return rest_server_send_error(req, "400 Bad Request", "invalid json"); + } + + cJSON *jtext = cJSON_GetObjectItemCaseSensitive(json, "text"); + cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density"); + cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "scale"); + cJSON *jline = cJSON_GetObjectItemCaseSensitive(json, "line_spacing"); + cJSON *jmaxh = cJSON_GetObjectItemCaseSensitive(json, "max_height"); + cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck"); + + if (!cJSON_IsString(jtext) || jtext->valuestring == NULL) { + cJSON_Delete(json); + return rest_server_send_error(req, "400 Bad Request", "text is required"); + } + + const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL) + ? jdensity->valuestring + : "中等"; + bool direct_ignore_precheck = rest_server_json_bool_with_default(jignore, false); + + uint8_t scale = 2; + uint8_t line_spacing = 2; + uint16_t max_height = 2000; + + if (cJSON_IsNumber(jscale) && jscale->valuedouble >= 1 && jscale->valuedouble <= 6) { + scale = (uint8_t)jscale->valuedouble; + } + if (cJSON_IsNumber(jline) && jline->valuedouble >= 0 && jline->valuedouble <= 12) { + line_spacing = (uint8_t)jline->valuedouble; + } + if (cJSON_IsNumber(jmaxh) && jmaxh->valuedouble >= 64 && jmaxh->valuedouble <= 3000) { + max_height = (uint16_t)jmaxh->valuedouble; + } + + uint16_t width = 0; + uint16_t height = 0; + uint8_t *raster = NULL; + size_t raster_len = 0; + char render_msg[128] = {0}; + + esp_err_t render_rc = raster_tools_render_text_384(jtext->valuestring, + scale, + line_spacing, + max_height, + &width, + &height, + &raster, + &raster_len, + render_msg, + sizeof(render_msg)); + cJSON_Delete(json); + + if (render_rc != ESP_OK) { + return rest_server_send_error(req, + "400 Bad Request", + render_msg[0] != 0 ? render_msg : "text render failed"); + } + + esp_err_t submit_rc = rest_server_print_submit_raster_job_and_reply(req, + raster, + raster_len, + width, + height, + density, + direct_ignore_precheck, + render_msg, + NULL); + free(raster); + return submit_rc; +}