From cf1fecb004f9af46e71328039319e0bfbe62b59d Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 26 Feb 2026 10:18:19 +0800 Subject: [PATCH] feat(architecture): enforce lifecycle orchestration and layered component boundaries --- components/app_composition/CMakeLists.txt | 10 + components/control_plane/CMakeLists.txt | 21 ++ components/domain/CMakeLists.txt | 35 ++ components/platform/CMakeLists.txt | 24 ++ main/CMakeLists.txt | 65 +--- main/Kconfig.projbuild | 55 +++ main/app_composition/src/app_main.c | 6 +- .../include/controller_lifecycle.h | 22 +- main/control_plane/src/controller_lifecycle.c | 312 +++++++++++++++- main/control_plane/src/rest_server.c | 4 + main/control_plane/src/rest_server_common.c | 4 + main/control_plane/src/rest_server_ops.c | 90 ++++- .../src/rest_server_print_image.c | 35 +- main/domain/include/printer_protocol.h | 6 +- main/domain/include/runtime_diagnostics.h | 61 +++ main/domain/include/runtime_policy.h | 30 ++ main/domain/include/system_runtime.h | 2 + .../internal/printer_protocol_internal.h | 13 + main/domain/src/image_generation.c | 22 +- main/domain/src/printer_protocol.c | 352 ++++++++++++++++-- main/domain/src/printer_protocol_commands.c | 17 +- main/domain/src/printer_protocol_jobs.c | 16 +- main/domain/src/printer_protocol_worker.c | 52 ++- main/domain/src/system_runtime.c | 31 +- main/domain/src/voice_interaction_marker.c | 12 +- main/platform/include/runtime_diagnostics.h | 61 +++ main/platform/include/runtime_policy.h | 30 ++ main/platform/include/wifi_manager.h | 1 + main/platform/src/ble_printer_client.c | 20 + main/platform/src/runtime_diagnostics.c | 136 +++++++ main/platform/src/runtime_policy.c | 142 +++++++ main/platform/src/wifi_manager.c | 194 ++++++++-- 32 files changed, 1701 insertions(+), 180 deletions(-) create mode 100644 components/app_composition/CMakeLists.txt create mode 100644 components/control_plane/CMakeLists.txt create mode 100644 components/domain/CMakeLists.txt create mode 100644 components/platform/CMakeLists.txt create mode 100644 main/domain/include/runtime_diagnostics.h create mode 100644 main/domain/include/runtime_policy.h create mode 100644 main/platform/include/runtime_diagnostics.h create mode 100644 main/platform/include/runtime_policy.h create mode 100644 main/platform/src/runtime_diagnostics.c create mode 100644 main/platform/src/runtime_policy.c diff --git a/components/app_composition/CMakeLists.txt b/components/app_composition/CMakeLists.txt new file mode 100644 index 0000000..a7ff764 --- /dev/null +++ b/components/app_composition/CMakeLists.txt @@ -0,0 +1,10 @@ +idf_component_register( + SRCS + "../../main/app_composition/src/app_main.c" + INCLUDE_DIRS + "../../main/app_composition/include" + PRIV_INCLUDE_DIRS + "../../main/app_composition/internal" + REQUIRES + control_plane +) diff --git a/components/control_plane/CMakeLists.txt b/components/control_plane/CMakeLists.txt new file mode 100644 index 0000000..dc2003f --- /dev/null +++ b/components/control_plane/CMakeLists.txt @@ -0,0 +1,21 @@ +idf_component_register( + SRCS + "../../main/control_plane/src/controller_lifecycle.c" + "../../main/control_plane/src/rest_server.c" + "../../main/control_plane/src/rest_server_common.c" + "../../main/control_plane/src/rest_server_ops.c" + "../../main/control_plane/src/rest_server_print.c" + "../../main/control_plane/src/rest_server_print_image.c" + "../../main/control_plane/src/rest_server_print_render.c" + "../../main/control_plane/src/rest_server_jobs.c" + "../../main/control_plane/src/rest_server_voice.c" + INCLUDE_DIRS + "../../main/control_plane/include" + PRIV_INCLUDE_DIRS + "../../main/control_plane/internal" + REQUIRES + domain + esp_http_server + json + mbedtls +) diff --git a/components/domain/CMakeLists.txt b/components/domain/CMakeLists.txt new file mode 100644 index 0000000..41077a6 --- /dev/null +++ b/components/domain/CMakeLists.txt @@ -0,0 +1,35 @@ +idf_component_register( + SRCS + "../../main/domain/src/printer_protocol.c" + "../../main/domain/src/printer_protocol_jobs.c" + "../../main/domain/src/printer_protocol_worker.c" + "../../main/domain/src/printer_protocol_commands.c" + "../../main/domain/src/raster_tools.c" + "../../main/domain/src/raster_tools_image_qr.c" + "../../main/domain/src/image_generation.c" + "../../main/domain/src/system_runtime.c" + "../../main/domain/src/voice_interaction.c" + "../../main/domain/src/voice_interaction_common.c" + "../../main/domain/src/voice_interaction_ws.c" + "../../main/domain/src/voice_interaction_ws_protocol.c" + "../../main/domain/src/voice_interaction_ws_audio.c" + "../../main/domain/src/voice_interaction_marker.c" + "../../main/domain/src/voice_interaction_tasks.c" + "../../main/third_party/qrcodegen.c" + INCLUDE_DIRS + "../../main/domain/include" + "../../main/third_party" + PRIV_INCLUDE_DIRS + "../../main/domain/internal" + REQUIRES + platform + esp_http_client + esp_websocket_client + espressif__libpng + json + mbedtls + esp_coex + EMBED_FILES + "../../main/domain/assets/fonts/cn16_index.bin" + "../../main/domain/assets/fonts/cn16_glyphs.bin" +) diff --git a/components/platform/CMakeLists.txt b/components/platform/CMakeLists.txt new file mode 100644 index 0000000..cedf89b --- /dev/null +++ b/components/platform/CMakeLists.txt @@ -0,0 +1,24 @@ +idf_component_register( + SRCS + "../../main/platform/src/platform_bootstrap.c" + "../../main/platform/src/wifi_manager.c" + "../../main/platform/src/ble_printer_client.c" + "../../main/platform/src/voice_audio.c" + "../../main/platform/src/runtime_policy.c" + "../../main/platform/src/runtime_diagnostics.c" + INCLUDE_DIRS + "../../main/platform/include" + PRIV_INCLUDE_DIRS + "../../main/platform/internal" + REQUIRES + bt + esp_coex + esp_wifi + esp_netif + esp_event + nvs_flash + mbedtls + esp_codec_dev + esp_audio_codec + driver +) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 2491619..0784f7b 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,62 +1,3 @@ -idf_component_register( - SRCS - "app_composition/src/app_main.c" - "control_plane/src/controller_lifecycle.c" - "control_plane/src/rest_server.c" - "control_plane/src/rest_server_common.c" - "control_plane/src/rest_server_ops.c" - "control_plane/src/rest_server_print.c" - "control_plane/src/rest_server_print_image.c" - "control_plane/src/rest_server_print_render.c" - "control_plane/src/rest_server_jobs.c" - "control_plane/src/rest_server_voice.c" - "domain/src/printer_protocol.c" - "domain/src/printer_protocol_jobs.c" - "domain/src/printer_protocol_worker.c" - "domain/src/printer_protocol_commands.c" - "domain/src/raster_tools.c" - "domain/src/raster_tools_image_qr.c" - "domain/src/image_generation.c" - "domain/src/system_runtime.c" - "domain/src/voice_interaction.c" - "domain/src/voice_interaction_common.c" - "domain/src/voice_interaction_ws.c" - "domain/src/voice_interaction_ws_protocol.c" - "domain/src/voice_interaction_ws_audio.c" - "domain/src/voice_interaction_marker.c" - "domain/src/voice_interaction_tasks.c" - "platform/src/platform_bootstrap.c" - "platform/src/wifi_manager.c" - "platform/src/ble_printer_client.c" - "platform/src/voice_audio.c" - "third_party/qrcodegen.c" - INCLUDE_DIRS - "app_composition/include" - "control_plane/include" - "domain/include" - "platform/include" - "third_party" - PRIV_INCLUDE_DIRS - "app_composition/internal" - "control_plane/internal" - "domain/internal" - "platform/internal" - REQUIRES - bt - esp_coex - esp_wifi - esp_netif - esp_event - esp_http_server - esp_http_client - nvs_flash - json - mbedtls - esp_websocket_client - esp_codec_dev - esp_audio_codec - driver - EMBED_FILES - "domain/assets/fonts/cn16_index.bin" - "domain/assets/fonts/cn16_glyphs.bin" -) +# Keep `main` as a lightweight component holder for project-wide Kconfig. +# Runtime sources are split into layered components under `components/`. +idf_component_register() diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 742fc7e..ad64962 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -12,10 +12,65 @@ config TQ_WIFI_MAXIMUM_RETRY int "Wi-Fi Maximum Retry" default 10 +config TQ_WIFI_CONNECT_TIMEOUT_MS + int "Wi-Fi connect timeout (ms)" + range 3000 60000 + default 15000 + +config TQ_LIFECYCLE_START_RETRY_COUNT + int "Lifecycle start retry count" + range 0 5 + default 2 + +config TQ_LIFECYCLE_RETRY_BACKOFF_MS + int "Lifecycle retry base backoff (ms)" + range 100 10000 + default 800 + config TQ_HTTP_PORT int "REST HTTP Port" default 80 +config TQ_REST_PRINTER_CONNECT_TIMEOUT_MS + int "REST printer connect timeout default (ms)" + range 1000 60000 + default 15000 + +config TQ_REST_LABEL_TIMEOUT_MS + int "REST label/offset timeout default (ms)" + range 500 30000 + default 5000 + +config TQ_REST_OTA_STEP_TIMEOUT_MS + int "REST OTA step timeout default (ms)" + range 500 60000 + default 5000 + +config TQ_PRINTER_CONTROL_LOCK_TIMEOUT_MS + int "Printer control lane lock timeout (ms)" + range 100 5000 + default 1000 + +config TQ_PRINTER_WORKER_QUEUE_WAIT_MS + int "Printer worker queue poll wait (ms)" + range 50 2000 + default 250 + +config TQ_PRINTER_QUEUE_RETRY_DELAY_MS + int "Printer queue retry delay (ms)" + range 5 500 + default 20 + +config TQ_PRINTER_STATUS_POLL_INTERVAL_MS + int "Printer status poll interval (ms)" + range 1000 30000 + default 5000 + +config TQ_PRINTER_STOP_TIMEOUT_MS + int "Printer protocol stop timeout (ms)" + range 1000 30000 + default 8000 + config TQ_API_KEY string "REST API Key (optional, empty means disabled)" default "" diff --git a/main/app_composition/src/app_main.c b/main/app_composition/src/app_main.c index ca7c147..baeb1c5 100644 --- a/main/app_composition/src/app_main.c +++ b/main/app_composition/src/app_main.c @@ -6,7 +6,11 @@ static const char *TAG = "app_main"; void app_main(void) { - ESP_ERROR_CHECK(controller_lifecycle_start()); + esp_err_t rc = controller_lifecycle_start(); + if (rc != ESP_OK) { + ESP_LOGE(TAG, "Controller lifecycle start failed: %s", esp_err_to_name(rc)); + return; + } ESP_LOGI(TAG, "TQ controller started"); } diff --git a/main/control_plane/include/controller_lifecycle.h b/main/control_plane/include/controller_lifecycle.h index 64061da..0a22000 100644 --- a/main/control_plane/include/controller_lifecycle.h +++ b/main/control_plane/include/controller_lifecycle.h @@ -1,6 +1,26 @@ #pragma once +#include + #include "esp_err.h" +typedef enum { + CONTROLLER_LIFECYCLE_STATE_STOPPED = 0, + CONTROLLER_LIFECYCLE_STATE_STARTING, + CONTROLLER_LIFECYCLE_STATE_RUNNING, + CONTROLLER_LIFECYCLE_STATE_DEGRADED, + CONTROLLER_LIFECYCLE_STATE_STOPPING, +} controller_lifecycle_state_t; + +typedef struct { + controller_lifecycle_state_t state; + esp_err_t last_error; + uint32_t start_attempt; + int64_t last_transition_ms; + char last_stage[32]; +} controller_lifecycle_status_t; + esp_err_t controller_lifecycle_start(void); -void controller_lifecycle_stop(void); +esp_err_t controller_lifecycle_stop(void); +void controller_lifecycle_get_status(controller_lifecycle_status_t *out_status); +const char *controller_lifecycle_state_str(controller_lifecycle_state_t state); diff --git a/main/control_plane/src/controller_lifecycle.c b/main/control_plane/src/controller_lifecycle.c index 1d7c64e..024298a 100644 --- a/main/control_plane/src/controller_lifecycle.c +++ b/main/control_plane/src/controller_lifecycle.c @@ -1,36 +1,316 @@ #include "controller_lifecycle.h" +#include + #include "esp_log.h" +#include "esp_timer.h" #include "image_generation.h" #include "printer_protocol.h" #include "rest_server.h" +#include "runtime_diagnostics.h" +#include "runtime_policy.h" #include "system_runtime.h" #include "voice_interaction.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" static const char *TAG = "controller_lifecycle"; +static SemaphoreHandle_t s_lock; +static controller_lifecycle_status_t s_status = { + .state = CONTROLLER_LIFECYCLE_STATE_STOPPED, + .last_error = ESP_OK, + .start_attempt = 0, + .last_transition_ms = 0, + .last_stage = "stopped", +}; -esp_err_t controller_lifecycle_start(void) { - ESP_LOGI(TAG, "Starting system runtime"); - ESP_ERROR_CHECK(system_runtime_bootstrap()); +static void lifecycle_lock_init_if_needed(void) { + if (s_lock == NULL) { + s_lock = xSemaphoreCreateMutex(); + } +} - ESP_LOGI(TAG, "Starting printer protocol"); - ESP_ERROR_CHECK(printer_protocol_init()); +static bool lifecycle_lock_take(uint32_t timeout_ms) { + lifecycle_lock_init_if_needed(); + return s_lock != NULL && xSemaphoreTake(s_lock, pdMS_TO_TICKS(timeout_ms)) == pdTRUE; +} - ESP_LOGI(TAG, "Starting voice interaction"); - ESP_ERROR_CHECK(voice_interaction_init()); +static void lifecycle_set_state_locked(controller_lifecycle_state_t state, + esp_err_t last_error, + const char *stage) { + s_status.state = state; + s_status.last_error = last_error; + s_status.last_transition_ms = esp_timer_get_time() / 1000; + strlcpy(s_status.last_stage, stage != NULL ? stage : "unknown", sizeof(s_status.last_stage)); + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_LIFECYCLE_STATE, (int32_t)state); +} - ESP_LOGI(TAG, "Scheduling HTTPS prewarm"); - image_generation_schedule_prewarm(); +const char *controller_lifecycle_state_str(controller_lifecycle_state_t state) { + switch (state) { + case CONTROLLER_LIFECYCLE_STATE_STOPPED: + return "stopped"; + case CONTROLLER_LIFECYCLE_STATE_STARTING: + return "starting"; + case CONTROLLER_LIFECYCLE_STATE_RUNNING: + return "running"; + case CONTROLLER_LIFECYCLE_STATE_DEGRADED: + return "degraded"; + case CONTROLLER_LIFECYCLE_STATE_STOPPING: + return "stopping"; + default: + return "unknown"; + } +} - ESP_LOGI(TAG, "Starting REST server"); - ESP_ERROR_CHECK(rest_server_start()); +static esp_err_t lifecycle_start_system_runtime(void) { + return system_runtime_start(); +} +static esp_err_t lifecycle_start_printer_protocol(void) { + return printer_protocol_init(); +} + +static esp_err_t lifecycle_start_voice(void) { + return voice_interaction_init(); +} + +static esp_err_t lifecycle_start_rest_server(void) { + return rest_server_start(); +} + +static esp_err_t lifecycle_stop_rest_server(void) { + rest_server_stop(); return ESP_OK; } -void controller_lifecycle_stop(void) { - rest_server_stop(); - (void)voice_interaction_stop(NULL, 0); - printer_protocol_disconnect(); - ESP_LOGI(TAG, "Controller lifecycle stopped"); +static esp_err_t lifecycle_stop_voice(void) { + esp_err_t rc = voice_interaction_stop(NULL, 0); + if (rc == ESP_OK || rc == ESP_ERR_INVALID_STATE) { + return ESP_OK; + } + return rc; +} + +static esp_err_t lifecycle_stop_printer_protocol(void) { + esp_err_t rc = printer_protocol_stop(runtime_policy_printer_stop_timeout_ms()); + if (rc == ESP_OK || rc == ESP_ERR_INVALID_STATE) { + return ESP_OK; + } + return rc; +} + +static esp_err_t lifecycle_stop_system_runtime(void) { + return system_runtime_shutdown(); +} + +typedef esp_err_t (*lifecycle_step_fn_t)(void); + +static esp_err_t lifecycle_run_step_with_retry(const char *stage, lifecycle_step_fn_t fn) { + uint32_t retry_count = runtime_policy_lifecycle_start_retry_count(); + uint32_t max_attempts = retry_count + 1; + + for (uint32_t attempt = 0; attempt < max_attempts; ++attempt) { + esp_err_t rc = fn(); + if (rc == ESP_OK) { + if (attempt > 0) { + ESP_LOGW(TAG, "stage %s recovered after retries, attempt=%u", stage, (unsigned)(attempt + 1)); + } + return ESP_OK; + } + + bool can_retry = (attempt + 1 < max_attempts) && runtime_policy_is_retryable_error(rc); + ESP_LOGW(TAG, + "stage %s failed, rc=0x%x, attempt=%u/%u, retry=%d", + stage, + (unsigned)rc, + (unsigned)(attempt + 1), + (unsigned)max_attempts, + can_retry ? 1 : 0); + + if (!can_retry) { + return rc; + } + + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_LIFECYCLE_START_RETRY, 1); + vTaskDelay(pdMS_TO_TICKS(runtime_policy_lifecycle_retry_backoff_ms(attempt))); + } + + return ESP_FAIL; +} + +esp_err_t controller_lifecycle_start(void) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_LIFECYCLE_START_ATTEMPT, 1); + + if (!lifecycle_lock_take(1000)) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_LIFECYCLE_START_FAILED, 1); + runtime_diag_record_error("lifecycle_start", ESP_ERR_TIMEOUT, "lifecycle lock timeout"); + return ESP_ERR_TIMEOUT; + } + + if (s_status.state == CONTROLLER_LIFECYCLE_STATE_RUNNING) { + xSemaphoreGive(s_lock); + return ESP_OK; + } + if (s_status.state == CONTROLLER_LIFECYCLE_STATE_STARTING || + s_status.state == CONTROLLER_LIFECYCLE_STATE_STOPPING) { + xSemaphoreGive(s_lock); + return ESP_ERR_INVALID_STATE; + } + + s_status.start_attempt++; + lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_STARTING, ESP_OK, "bootstrap"); + xSemaphoreGive(s_lock); + + bool system_started = false; + bool printer_started = false; + bool voice_started = false; + bool rest_started = false; + const char *failed_stage = "unknown"; + + failed_stage = "system_runtime"; + esp_err_t rc = lifecycle_run_step_with_retry("system_runtime", lifecycle_start_system_runtime); + if (rc != ESP_OK) { + goto start_failed; + } + system_started = true; + + failed_stage = "printer_protocol"; + rc = lifecycle_run_step_with_retry("printer_protocol", lifecycle_start_printer_protocol); + if (rc != ESP_OK) { + goto start_failed; + } + printer_started = true; + + failed_stage = "voice_interaction"; + rc = lifecycle_run_step_with_retry("voice_interaction", lifecycle_start_voice); + if (rc != ESP_OK) { + goto start_failed; + } + voice_started = true; + + failed_stage = "rest_server"; + rc = lifecycle_run_step_with_retry("rest_server", lifecycle_start_rest_server); + if (rc != ESP_OK) { + goto start_failed; + } + rest_started = true; + + image_generation_schedule_prewarm(); + + if (lifecycle_lock_take(1000)) { + lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_RUNNING, ESP_OK, "running"); + xSemaphoreGive(s_lock); + } + + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_LIFECYCLE_START_SUCCESS, 1); + ESP_LOGI(TAG, "controller lifecycle started"); + return ESP_OK; + +start_failed: + if (rest_started) { + (void)lifecycle_stop_rest_server(); + } + if (voice_started) { + (void)lifecycle_stop_voice(); + } + if (printer_started) { + (void)lifecycle_stop_printer_protocol(); + } + if (system_started) { + (void)lifecycle_stop_system_runtime(); + } + + if (lifecycle_lock_take(1000)) { + lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_DEGRADED, rc, failed_stage); + xSemaphoreGive(s_lock); + } + + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_LIFECYCLE_START_FAILED, 1); + runtime_diag_record_error("lifecycle_start", rc, failed_stage); + ESP_LOGE(TAG, + "controller lifecycle start failed at stage=%s, rc=0x%x", + failed_stage, + (unsigned)rc); + return rc; +} + +esp_err_t controller_lifecycle_stop(void) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_LIFECYCLE_STOP_ATTEMPT, 1); + + if (!lifecycle_lock_take(1000)) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_LIFECYCLE_STOP_FAILED, 1); + runtime_diag_record_error("lifecycle_stop", ESP_ERR_TIMEOUT, "lifecycle lock timeout"); + return ESP_ERR_TIMEOUT; + } + + if (s_status.state == CONTROLLER_LIFECYCLE_STATE_STOPPED) { + xSemaphoreGive(s_lock); + return ESP_OK; + } + if (s_status.state == CONTROLLER_LIFECYCLE_STATE_STARTING || + s_status.state == CONTROLLER_LIFECYCLE_STATE_STOPPING) { + xSemaphoreGive(s_lock); + return ESP_ERR_INVALID_STATE; + } + + lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_STOPPING, ESP_OK, "stopping"); + xSemaphoreGive(s_lock); + + esp_err_t first_err = ESP_OK; + + esp_err_t rc = lifecycle_stop_rest_server(); + if (rc != ESP_OK && first_err == ESP_OK) { + first_err = rc; + } + + rc = lifecycle_stop_voice(); + if (rc != ESP_OK && first_err == ESP_OK) { + first_err = rc; + } + + rc = lifecycle_stop_printer_protocol(); + if (rc != ESP_OK && first_err == ESP_OK) { + first_err = rc; + } + + rc = lifecycle_stop_system_runtime(); + if (rc != ESP_OK && first_err == ESP_OK) { + first_err = rc; + } + + if (lifecycle_lock_take(1000)) { + if (first_err == ESP_OK) { + lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_STOPPED, ESP_OK, "stopped"); + } else { + lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_DEGRADED, first_err, "stop_failed"); + } + xSemaphoreGive(s_lock); + } + + if (first_err == ESP_OK) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_LIFECYCLE_STOP_SUCCESS, 1); + ESP_LOGI(TAG, "controller lifecycle stopped"); + } else { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_LIFECYCLE_STOP_FAILED, 1); + runtime_diag_record_error("lifecycle_stop", first_err, "controller stop failed"); + ESP_LOGE(TAG, "controller lifecycle stop failed, rc=0x%x", (unsigned)first_err); + } + + return first_err; +} + +void controller_lifecycle_get_status(controller_lifecycle_status_t *out_status) { + if (out_status == NULL) { + return; + } + + memset(out_status, 0, sizeof(*out_status)); + + if (!lifecycle_lock_take(200)) { + return; + } + + *out_status = s_status; + xSemaphoreGive(s_lock); } diff --git a/main/control_plane/src/rest_server.c b/main/control_plane/src/rest_server.c index 501b576..6e4736a 100644 --- a/main/control_plane/src/rest_server.c +++ b/main/control_plane/src/rest_server.c @@ -365,6 +365,10 @@ static esp_err_t favicon_get(httpd_req_t *req) { } esp_err_t rest_server_start(void) { + if (s_server != NULL) { + return ESP_OK; + } + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = CONFIG_TQ_HTTP_PORT; config.uri_match_fn = httpd_uri_match_wildcard; diff --git a/main/control_plane/src/rest_server_common.c b/main/control_plane/src/rest_server_common.c index 0718aa3..c1df23d 100644 --- a/main/control_plane/src/rest_server_common.c +++ b/main/control_plane/src/rest_server_common.c @@ -8,6 +8,7 @@ #include "esp_heap_caps.h" #include "mbedtls/base64.h" +#include "runtime_diagnostics.h" #define REST_SERVER_MAX_BODY_BYTES (4 * 1024 * 1024) @@ -49,6 +50,7 @@ esp_err_t rest_server_send_json(httpd_req_t *req, const char *status, cJSON *roo httpd_resp_set_status(req, status); esp_err_t err = httpd_resp_sendstr(req, text); cJSON_free(text); + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_REST_RESPONSES_TOTAL, 1); return err; } @@ -58,6 +60,8 @@ esp_err_t rest_server_send_error(httpd_req_t *req, const char *status, const cha cJSON_AddStringToObject(root, "error", message != NULL ? message : "unknown"); esp_err_t err = rest_server_send_json(req, status, root); cJSON_Delete(root); + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_REST_ERRORS_TOTAL, 1); + runtime_diag_record_error("rest_api", ESP_FAIL, message != NULL ? message : "unknown"); return err; } diff --git a/main/control_plane/src/rest_server_ops.c b/main/control_plane/src/rest_server_ops.c index 0ce2c27..b5fdc34 100644 --- a/main/control_plane/src/rest_server_ops.c +++ b/main/control_plane/src/rest_server_ops.c @@ -4,9 +4,49 @@ #include #include +#include "controller_lifecycle.h" #include "printer_protocol.h" +#include "runtime_diagnostics.h" +#include "runtime_policy.h" #include "system_runtime.h" #include "voice_interaction.h" + +static void fill_runtime_diag_json(cJSON *root) { + runtime_diag_snapshot_t snapshot = {0}; + runtime_diag_get_snapshot(&snapshot); + + cJSON *diag = cJSON_AddObjectToObject(root, "diagnostics"); + if (diag == NULL) { + return; + } + + cJSON *counters = cJSON_AddObjectToObject(diag, "counters"); + if (counters != NULL) { + for (int i = 0; i < RUNTIME_DIAG_COUNTER_MAX; ++i) { + cJSON_AddNumberToObject(counters, + runtime_diag_counter_name((runtime_diag_counter_t)i), + (double)snapshot.counters[i]); + } + } + + cJSON *gauges = cJSON_AddObjectToObject(diag, "gauges"); + if (gauges != NULL) { + for (int i = 0; i < RUNTIME_DIAG_GAUGE_MAX; ++i) { + cJSON_AddNumberToObject(gauges, + runtime_diag_gauge_name((runtime_diag_gauge_t)i), + snapshot.gauges[i]); + } + } + + cJSON *last_error = cJSON_AddObjectToObject(diag, "last_error"); + if (last_error != NULL) { + cJSON_AddNumberToObject(last_error, "ts_ms", (double)snapshot.last_error_ms); + cJSON_AddNumberToObject(last_error, "code", (double)snapshot.last_error_code); + cJSON_AddStringToObject(last_error, "source", snapshot.last_error_source); + cJSON_AddStringToObject(last_error, "message", snapshot.last_error_message); + } +} + static void fill_runtime_json(cJSON *root) { char ip[32] = {0}; system_runtime_get_ip(ip, sizeof(ip)); @@ -44,6 +84,16 @@ static void fill_runtime_json(cJSON *root) { cJSON_AddNumberToObject(root, "voice_last_event_ms", (double)voice.last_event_ms); cJSON_AddNumberToObject(root, "voice_upstream_packets", (double)voice.upstream_packets); cJSON_AddNumberToObject(root, "voice_downstream_packets", (double)voice.downstream_packets); + + controller_lifecycle_status_t lifecycle = {0}; + controller_lifecycle_get_status(&lifecycle); + cJSON_AddStringToObject(root, "lifecycle_state", controller_lifecycle_state_str(lifecycle.state)); + cJSON_AddStringToObject(root, "lifecycle_stage", lifecycle.last_stage); + cJSON_AddNumberToObject(root, "lifecycle_last_error", (double)lifecycle.last_error); + cJSON_AddNumberToObject(root, "lifecycle_start_attempt", (double)lifecycle.start_attempt); + cJSON_AddNumberToObject(root, "lifecycle_last_transition_ms", (double)lifecycle.last_transition_ms); + + fill_runtime_diag_json(root); } esp_err_t rest_server_health_get(httpd_req_t *req) { @@ -52,10 +102,13 @@ esp_err_t rest_server_health_get(httpd_req_t *req) { } cJSON *root = cJSON_CreateObject(); - cJSON_AddBoolToObject(root, "ok", true); + controller_lifecycle_status_t lifecycle = {0}; + controller_lifecycle_get_status(&lifecycle); + bool healthy = (lifecycle.state == CONTROLLER_LIFECYCLE_STATE_RUNNING); + cJSON_AddBoolToObject(root, "ok", healthy); fill_runtime_json(root); - esp_err_t err = rest_server_send_json(req, "200 OK", root); + esp_err_t err = rest_server_send_json(req, healthy ? "200 OK" : "503 Service Unavailable", root); cJSON_Delete(root); return err; } @@ -67,7 +120,7 @@ esp_err_t rest_server_connect_post(httpd_req_t *req) { char *body = NULL; char name[32] = "TQPrinter"; - uint32_t timeout_ms = 15000; + uint32_t timeout_ms = runtime_policy_rest_printer_connect_timeout_ms(); if (req->content_len > 0) { esp_err_t body_err = rest_server_read_body(req, &body); @@ -148,7 +201,7 @@ esp_err_t rest_server_label_gap_move_post(httpd_req_t *req) { return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); } - uint32_t timeout_ms = 5000; + uint32_t timeout_ms = runtime_policy_rest_label_timeout_ms(); if (req->content_len > 0) { char *body = NULL; esp_err_t body_err = rest_server_read_body(req, &body); @@ -189,7 +242,10 @@ esp_err_t rest_server_label_offset_get(httpd_req_t *req) { uint8_t offset = 0; char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_get_label_offset(&offset, 3000, cmd_err, sizeof(cmd_err)); + esp_err_t rc = printer_protocol_get_label_offset(&offset, + runtime_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"); } @@ -230,7 +286,10 @@ esp_err_t rest_server_label_offset_post(httpd_req_t *req) { cJSON_Delete(json); char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_set_label_offset(value, 3000, cmd_err, sizeof(cmd_err)); + esp_err_t rc = printer_protocol_set_label_offset(value, + runtime_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"); } @@ -251,7 +310,10 @@ esp_err_t rest_server_ota_version_get(httpd_req_t *req) { printer_ota_version_t version = {0}; char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_ota_get_version(&version, 4000, cmd_err, sizeof(cmd_err)); + esp_err_t rc = printer_protocol_ota_get_version(&version, + runtime_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"); } @@ -275,7 +337,9 @@ esp_err_t rest_server_ota_jump_boot_post(httpd_req_t *req) { } char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_ota_jump_boot(5000, cmd_err, sizeof(cmd_err)); + esp_err_t rc = printer_protocol_ota_jump_boot(runtime_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"); } @@ -294,7 +358,9 @@ esp_err_t rest_server_ota_jump_app_post(httpd_req_t *req) { } char cmd_err[128] = {0}; - esp_err_t rc = printer_protocol_ota_jump_app(5000, cmd_err, sizeof(cmd_err)); + esp_err_t rc = printer_protocol_ota_jump_app(runtime_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"); } @@ -330,7 +396,7 @@ esp_err_t rest_server_ota_erase_page_post(httpd_req_t *req) { return rest_server_send_error(req, "400 Bad Request", "page_num must be 0..65535"); } - uint32_t timeout_ms = 5000; + uint32_t timeout_ms = runtime_policy_rest_ota_timeout_ms(); if (cJSON_IsNumber(jtimeout) && jtimeout->valuedouble > 0 && jtimeout->valuedouble <= 30000) { timeout_ms = (uint32_t)jtimeout->valuedouble; } @@ -379,7 +445,7 @@ esp_err_t rest_server_ota_write_frame_post(httpd_req_t *req) { return rest_server_send_error(req, "400 Bad Request", "packet_num and data are required"); } - uint32_t timeout_ms = 4000; + uint32_t timeout_ms = runtime_policy_rest_ota_timeout_ms(); if (cJSON_IsNumber(jtimeout) && jtimeout->valuedouble > 0 && jtimeout->valuedouble <= 30000) { timeout_ms = (uint32_t)jtimeout->valuedouble; } @@ -451,7 +517,7 @@ esp_err_t rest_server_ota_upgrade_post(httpd_req_t *req) { uint16_t page_size = 1024; uint16_t packet_size = 236; - uint32_t timeout_ms = 5000; + uint32_t timeout_ms = runtime_policy_rest_ota_timeout_ms(); if (cJSON_IsNumber(jpagesize) && jpagesize->valuedouble >= 256 && jpagesize->valuedouble <= 4096) { page_size = (uint16_t)jpagesize->valuedouble; } diff --git a/main/control_plane/src/rest_server_print_image.c b/main/control_plane/src/rest_server_print_image.c index e231a76..5cd59d4 100644 --- a/main/control_plane/src/rest_server_print_image.c +++ b/main/control_plane/src/rest_server_print_image.c @@ -9,6 +9,7 @@ #include "image_generation.h" #include "printer_protocol.h" #include "raster_tools.h" +#include "runtime_policy.h" static const char *TAG = "rest_print"; @@ -229,8 +230,8 @@ static bool parse_image_generation_options(cJSON *json, } memset(out, 0, sizeof(*out)); - out->timeout_ms = CONFIG_TQ_Z_IMAGE_TIMEOUT_MS; - out->fetch_timeout_ms = CONFIG_TQ_Z_IMAGE_DOWNLOAD_TIMEOUT_MS; + out->timeout_ms = runtime_policy_image_generation_timeout_default_ms(); + out->fetch_timeout_ms = runtime_policy_image_download_timeout_default_ms(); cJSON *jprompt = cJSON_GetObjectItemCaseSensitive(json, "prompt"); if (!cJSON_IsString(jprompt) || jprompt->valuestring == NULL || jprompt->valuestring[0] == '\0') { @@ -289,9 +290,14 @@ static bool parse_image_generation_options(cJSON *json, cJSON *jtimeout = cJSON_GetObjectItemCaseSensitive(json, "timeout_ms"); if (cJSON_IsNumber(jtimeout)) { - if (jtimeout->valuedouble < 5000 || jtimeout->valuedouble > 180000) { + if (jtimeout->valuedouble < runtime_policy_image_generation_timeout_min_ms() || + jtimeout->valuedouble > runtime_policy_image_generation_timeout_max_ms()) { if (err != NULL && err_len > 0) { - snprintf(err, err_len, "timeout_ms must be 5000..180000"); + snprintf(err, + err_len, + "timeout_ms must be %u..%u", + (unsigned)runtime_policy_image_generation_timeout_min_ms(), + (unsigned)runtime_policy_image_generation_timeout_max_ms()); } return false; } @@ -300,9 +306,14 @@ static bool parse_image_generation_options(cJSON *json, cJSON *jfetch_timeout = cJSON_GetObjectItemCaseSensitive(json, "fetch_timeout_ms"); if (cJSON_IsNumber(jfetch_timeout)) { - if (jfetch_timeout->valuedouble < 2000 || jfetch_timeout->valuedouble > 120000) { + if (jfetch_timeout->valuedouble < runtime_policy_image_download_timeout_min_ms() || + jfetch_timeout->valuedouble > runtime_policy_image_download_timeout_max_ms()) { if (err != NULL && err_len > 0) { - snprintf(err, err_len, "fetch_timeout_ms must be 2000..120000"); + snprintf(err, + err_len, + "fetch_timeout_ms must be %u..%u", + (unsigned)runtime_policy_image_download_timeout_min_ms(), + (unsigned)runtime_policy_image_download_timeout_max_ms()); } return false; } @@ -411,7 +422,7 @@ static esp_err_t rest_server_print_image_generate(httpd_req_t *req) { image_generation_result_t *gen_result = NULL; uint8_t *raster = NULL; char request_id[80] = {0}; - bool status_poll_paused = false; + printer_status_poll_pause_token_t status_poll_pause_token = 0; esp_err_t ret = ESP_FAIL; esp_err_t body_err = rest_server_read_body(req, &body); @@ -475,14 +486,12 @@ static esp_err_t rest_server_print_image_generate(httpd_req_t *req) { image_generation_result_reset(gen_result); char model_err[160] = {0}; - printer_protocol_set_status_poll_paused(true); - status_poll_paused = true; + status_poll_pause_token = printer_protocol_status_poll_pause_acquire(); esp_err_t gen_rc = image_generation_generate_png(&gen_req, gen_result, model_err, sizeof(model_err)); - printer_protocol_set_status_poll_paused(false); - status_poll_paused = false; + printer_protocol_status_poll_pause_release(&status_poll_pause_token); if (gen_rc != ESP_OK) { ESP_LOGW(TAG, "image generate failed, rc=0x%x, msg=%s", @@ -551,9 +560,7 @@ static esp_err_t rest_server_print_image_generate(httpd_req_t *req) { request_id); cleanup: - if (status_poll_paused) { - printer_protocol_set_status_poll_paused(false); - } + printer_protocol_status_poll_pause_release(&status_poll_pause_token); if (body != NULL) { free(body); } diff --git a/main/domain/include/printer_protocol.h b/main/domain/include/printer_protocol.h index 14b1252..cf62258 100644 --- a/main/domain/include/printer_protocol.h +++ b/main/domain/include/printer_protocol.h @@ -47,13 +47,17 @@ typedef struct { int64_t last_status_ms; } printer_runtime_status_t; +typedef uint32_t printer_status_poll_pause_token_t; + esp_err_t printer_protocol_init(void); +esp_err_t printer_protocol_stop(uint32_t timeout_ms); esp_err_t printer_protocol_connect(const char *target_name, uint32_t timeout_ms); void printer_protocol_disconnect(void); void printer_protocol_get_runtime_status(printer_runtime_status_t *out_status); -void printer_protocol_set_status_poll_paused(bool paused); +printer_status_poll_pause_token_t printer_protocol_status_poll_pause_acquire(void); +void printer_protocol_status_poll_pause_release(printer_status_poll_pause_token_t *token); esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster, size_t raster_len, diff --git a/main/domain/include/runtime_diagnostics.h b/main/domain/include/runtime_diagnostics.h new file mode 100644 index 0000000..bdf4312 --- /dev/null +++ b/main/domain/include/runtime_diagnostics.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + RUNTIME_DIAG_COUNTER_LIFECYCLE_START_ATTEMPT = 0, + RUNTIME_DIAG_COUNTER_LIFECYCLE_START_SUCCESS, + RUNTIME_DIAG_COUNTER_LIFECYCLE_START_FAILED, + RUNTIME_DIAG_COUNTER_LIFECYCLE_START_RETRY, + RUNTIME_DIAG_COUNTER_LIFECYCLE_STOP_ATTEMPT, + RUNTIME_DIAG_COUNTER_LIFECYCLE_STOP_SUCCESS, + RUNTIME_DIAG_COUNTER_LIFECYCLE_STOP_FAILED, + RUNTIME_DIAG_COUNTER_WIFI_CONNECT_SUCCESS, + RUNTIME_DIAG_COUNTER_WIFI_CONNECT_FAILED, + RUNTIME_DIAG_COUNTER_WIFI_CONNECT_TIMEOUT, + RUNTIME_DIAG_COUNTER_PRINTER_JOB_SUBMITTED, + RUNTIME_DIAG_COUNTER_PRINTER_JOB_SUCCESS, + RUNTIME_DIAG_COUNTER_PRINTER_JOB_FAILED, + RUNTIME_DIAG_COUNTER_PRINTER_JOB_CANCELED, + RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_ATTEMPT, + RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_SUCCESS, + RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_FAILED, + RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_TIMEOUT, + RUNTIME_DIAG_COUNTER_REST_RESPONSES_TOTAL, + RUNTIME_DIAG_COUNTER_REST_ERRORS_TOTAL, + RUNTIME_DIAG_COUNTER_MAX, +} runtime_diag_counter_t; + +typedef enum { + RUNTIME_DIAG_GAUGE_LIFECYCLE_STATE = 0, + RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, + RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, + RUNTIME_DIAG_GAUGE_MAX, +} runtime_diag_gauge_t; + +typedef struct { + uint64_t counters[RUNTIME_DIAG_COUNTER_MAX]; + int32_t gauges[RUNTIME_DIAG_GAUGE_MAX]; + int64_t last_error_ms; + esp_err_t last_error_code; + char last_error_source[32]; + char last_error_message[96]; +} runtime_diag_snapshot_t; + +void runtime_diag_counter_add(runtime_diag_counter_t counter, uint32_t delta); +void runtime_diag_set_gauge(runtime_diag_gauge_t gauge, int32_t value); +void runtime_diag_record_error(const char *source, esp_err_t code, const char *message); +void runtime_diag_get_snapshot(runtime_diag_snapshot_t *out_snapshot); + +const char *runtime_diag_counter_name(runtime_diag_counter_t counter); +const char *runtime_diag_gauge_name(runtime_diag_gauge_t gauge); + +#ifdef __cplusplus +} +#endif diff --git a/main/domain/include/runtime_policy.h b/main/domain/include/runtime_policy.h new file mode 100644 index 0000000..c488727 --- /dev/null +++ b/main/domain/include/runtime_policy.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "esp_err.h" + +uint32_t runtime_policy_wifi_connect_timeout_ms(void); + +uint32_t runtime_policy_lifecycle_start_retry_count(void); +uint32_t runtime_policy_lifecycle_retry_backoff_ms(uint32_t attempt); +bool runtime_policy_is_retryable_error(esp_err_t err); + +uint32_t runtime_policy_printer_control_lock_timeout_ms(void); +uint32_t runtime_policy_printer_worker_queue_wait_ms(void); +uint32_t runtime_policy_printer_queue_retry_delay_ms(void); +uint32_t runtime_policy_printer_status_poll_interval_ms(void); +uint32_t runtime_policy_printer_stop_timeout_ms(void); + +uint32_t runtime_policy_rest_printer_connect_timeout_ms(void); +uint32_t runtime_policy_rest_label_timeout_ms(void); +uint32_t runtime_policy_rest_ota_timeout_ms(void); + +uint32_t runtime_policy_image_generation_timeout_default_ms(void); +uint32_t runtime_policy_image_generation_timeout_min_ms(void); +uint32_t runtime_policy_image_generation_timeout_max_ms(void); + +uint32_t runtime_policy_image_download_timeout_default_ms(void); +uint32_t runtime_policy_image_download_timeout_min_ms(void); +uint32_t runtime_policy_image_download_timeout_max_ms(void); diff --git a/main/domain/include/system_runtime.h b/main/domain/include/system_runtime.h index 675e188..f41cc4d 100644 --- a/main/domain/include/system_runtime.h +++ b/main/domain/include/system_runtime.h @@ -5,6 +5,8 @@ #include "esp_err.h" +esp_err_t system_runtime_start(void); +esp_err_t system_runtime_shutdown(void); esp_err_t system_runtime_bootstrap(void); bool system_runtime_wifi_ready(void); diff --git a/main/domain/internal/printer_protocol_internal.h b/main/domain/internal/printer_protocol_internal.h index ba7f41d..2d40bfd 100644 --- a/main/domain/internal/printer_protocol_internal.h +++ b/main/domain/internal/printer_protocol_internal.h @@ -10,6 +10,7 @@ #include "freertos/event_groups.h" #include "freertos/queue.h" #include "freertos/semphr.h" +#include "freertos/task.h" #define PROTO_ADDR 0x01 @@ -28,10 +29,15 @@ #define CMD_BOOT_GET_VERSION 0xA4 #define EVT_ACK BIT0 +#define EVT_SHUTDOWN BIT1 +#define EVT_WORKER_EXITED BIT2 +#define EVT_STATUS_POLL_EXITED BIT3 #define JOB_QUEUE_LEN 8 #define JOB_SLOT_MAX 16 #define MAX_RASTER_BYTES (384 * 3000 / 8) +#define STATUS_POLL_PAUSE_SLOT_MAX 8 +#define PRINTER_JOB_SENTINEL_STOP 0u #define OTA_MAX_DATA_PER_FRAME 236u @@ -67,8 +73,14 @@ extern parsed_status_t s_status; extern uint8_t s_last_rsp_cmd; extern uint16_t s_last_rsp_payload_len; extern uint8_t s_last_rsp_payload[252]; +extern uint32_t s_status_poll_pause_next_token; +extern uint32_t s_status_poll_pause_tokens[STATUS_POLL_PAUSE_SLOT_MAX]; extern uint32_t s_next_job_id; extern job_slot_t s_jobs[JOB_SLOT_MAX]; +extern TaskHandle_t s_worker_task; +extern TaskHandle_t s_status_poll_task_handle; +extern bool s_protocol_initialized; +extern bool s_protocol_stopping; bool printer_protocol_acquire_control_lane(uint32_t timeout_ms, char *err, size_t err_len); void printer_protocol_release_control_lane(void); @@ -103,3 +115,4 @@ int printer_protocol_alloc_job_slot_locked(void); bool printer_protocol_is_terminal_state(print_job_state_t state); void printer_protocol_worker_task(void *arg); +bool printer_protocol_is_stopping(void); diff --git a/main/domain/src/image_generation.c b/main/domain/src/image_generation.c index 2174825..405fd40 100644 --- a/main/domain/src/image_generation.c +++ b/main/domain/src/image_generation.c @@ -11,6 +11,8 @@ #include "esp_http_client.h" #include "esp_log.h" #include "esp_timer.h" +#include "runtime_diagnostics.h" +#include "runtime_policy.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" @@ -881,8 +883,11 @@ esp_err_t image_generation_generate_png(const image_generation_request_t *req, image_generation_result_t *out_result, char *err, size_t err_len) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_ATTEMPT, 1); + if (req == NULL || out_result == NULL || req->prompt == NULL || req->prompt[0] == '\0') { image_generation_fill_err(err, err_len, "prompt is required"); + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_FAILED, 1); return ESP_ERR_INVALID_ARG; } @@ -890,10 +895,10 @@ esp_err_t image_generation_generate_png(const image_generation_request_t *req, image_generation_request_t actual = *req; if (actual.generation_timeout_ms == 0) { - actual.generation_timeout_ms = CONFIG_TQ_Z_IMAGE_TIMEOUT_MS; + actual.generation_timeout_ms = runtime_policy_image_generation_timeout_default_ms(); } if (actual.download_timeout_ms == 0) { - actual.download_timeout_ms = CONFIG_TQ_Z_IMAGE_DOWNLOAD_TIMEOUT_MS; + actual.download_timeout_ms = runtime_policy_image_download_timeout_default_ms(); } ESP_LOGI(TAG, @@ -912,6 +917,12 @@ esp_err_t image_generation_generate_png(const image_generation_request_t *req, "image generate stage failed, rc=0x%x, msg=%s", (unsigned)gen_rc, (err != NULL && err[0] != '\0') ? err : "model invoke failed"); + if (gen_rc == ESP_ERR_TIMEOUT) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_TIMEOUT, 1); + } else { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_FAILED, 1); + } + runtime_diag_record_error("image_generate", gen_rc, err != NULL ? err : "model invoke failed"); image_generation_result_free(out_result); return gen_rc; } @@ -927,6 +938,12 @@ esp_err_t image_generation_generate_png(const image_generation_request_t *req, "image download stage failed, rc=0x%x, msg=%s", (unsigned)dl_rc, (err != NULL && err[0] != '\0') ? err : "download failed"); + if (dl_rc == ESP_ERR_TIMEOUT) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_TIMEOUT, 1); + } else { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_FAILED, 1); + } + runtime_diag_record_error("image_download", dl_rc, err != NULL ? err : "download failed"); image_generation_result_free(out_result); return dl_rc; } @@ -940,6 +957,7 @@ esp_err_t image_generation_generate_png(const image_generation_request_t *req, (long long)(t_gen_done_ms - t0_ms), (long long)(t_dl_done_ms - t_gen_done_ms), (long long)(t_dl_done_ms - t0_ms)); + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_SUCCESS, 1); return ESP_OK; } diff --git a/main/domain/src/printer_protocol.c b/main/domain/src/printer_protocol.c index 1b86475..d252332 100644 --- a/main/domain/src/printer_protocol.c +++ b/main/domain/src/printer_protocol.c @@ -2,12 +2,15 @@ #include "printer_protocol_internal.h" #include +#include #include #include "ble_printer_client.h" #include "esp_heap_caps.h" #include "esp_log.h" #include "esp_timer.h" +#include "runtime_diagnostics.h" +#include "runtime_policy.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -16,7 +19,8 @@ QueueHandle_t s_job_queue; EventGroupHandle_t s_evt; uint32_t s_busy_refcnt; -static bool s_status_poll_paused; +uint32_t s_status_poll_pause_next_token; +uint32_t s_status_poll_pause_tokens[STATUS_POLL_PAUSE_SLOT_MAX]; parsed_status_t s_status = { .has_paper = true, @@ -31,6 +35,10 @@ uint8_t s_last_rsp_payload[252]; uint32_t s_next_job_id = 1; job_slot_t s_jobs[JOB_SLOT_MAX]; +TaskHandle_t s_worker_task; +TaskHandle_t s_status_poll_task_handle; +bool s_protocol_initialized; +bool s_protocol_stopping; static const char *TAG = "printer_protocol"; @@ -40,10 +48,64 @@ static const char *TAG = "printer_protocol"; #define PRINT_WORKER_CORE_ID 1 #endif +static size_t status_poll_pause_depth_locked(void) { + size_t depth = 0; + for (size_t i = 0; i < STATUS_POLL_PAUSE_SLOT_MAX; ++i) { + if (s_status_poll_pause_tokens[i] != 0) { + ++depth; + } + } + return depth; +} + +static void status_poll_pause_slots_clear_locked(void) { + memset(s_status_poll_pause_tokens, 0, sizeof(s_status_poll_pause_tokens)); +} + +static void reset_jobs_locked(void) { + for (int i = 0; i < JOB_SLOT_MAX; ++i) { + free(s_jobs[i].data); + memset(&s_jobs[i], 0, sizeof(s_jobs[i])); + } + s_next_job_id = 1; +} + +static void reset_runtime_state_locked(void) { + s_busy_refcnt = 0; + s_last_rsp_cmd = 0; + s_last_rsp_payload_len = 0; + memset(s_last_rsp_payload, 0, sizeof(s_last_rsp_payload)); + + status_poll_pause_slots_clear_locked(); + s_status_poll_pause_next_token = 1; + + s_status.has_paper = true; + s_status.battery = 100; + s_status.temperature = 25.0f; + s_status.updated_ms = 0; +} + +bool printer_protocol_is_stopping(void) { + if (!s_protocol_initialized) { + return false; + } + if (s_mutex == NULL) { + return true; + } + + bool stopping = true; + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(50)) == pdTRUE) { + stopping = s_protocol_stopping; + xSemaphoreGive(s_mutex); + } + return stopping; +} + static BaseType_t printer_create_task_prefer_psram(TaskFunction_t task_fn, const char *name, uint32_t stack_size, - UBaseType_t priority) { + UBaseType_t priority, + TaskHandle_t *out_task) { #if defined(CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY) && \ defined(CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) && \ (configSUPPORT_STATIC_ALLOCATION == 1) @@ -52,7 +114,7 @@ static BaseType_t printer_create_task_prefer_psram(TaskFunction_t task_fn, (configSTACK_DEPTH_TYPE)stack_size, NULL, priority, - NULL, + out_task, PRINT_WORKER_CORE_ID, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); if (rc == pdPASS) { @@ -64,22 +126,85 @@ static BaseType_t printer_create_task_prefer_psram(TaskFunction_t task_fn, (configSTACK_DEPTH_TYPE)stack_size, NULL, priority, - NULL, + out_task, PRINT_WORKER_CORE_ID); } -void printer_protocol_set_status_poll_paused(bool paused) { - if (s_mutex == NULL) { +printer_status_poll_pause_token_t printer_protocol_status_poll_pause_acquire(void) { + if (!s_protocol_initialized || s_mutex == NULL) { + return 0; + } + + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) { + return 0; + } + + if (s_protocol_stopping) { + xSemaphoreGive(s_mutex); + return 0; + } + + int slot = -1; + for (int i = 0; i < STATUS_POLL_PAUSE_SLOT_MAX; ++i) { + if (s_status_poll_pause_tokens[i] == 0) { + slot = i; + break; + } + } + + if (slot < 0) { + xSemaphoreGive(s_mutex); + ESP_LOGW(TAG, "status poll pause acquire failed: no free slot"); + return 0; + } + + uint32_t token = s_status_poll_pause_next_token++; + if (token == 0) { + token = s_status_poll_pause_next_token++; + } + + s_status_poll_pause_tokens[slot] = token; + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, + (int32_t)status_poll_pause_depth_locked()); + xSemaphoreGive(s_mutex); + return token; +} + +void printer_protocol_status_poll_pause_release(printer_status_poll_pause_token_t *token) { + if (token == NULL || *token == 0) { return; } + + if (!s_protocol_initialized || s_mutex == NULL) { + *token = 0; + return; + } + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) { return; } - s_status_poll_paused = paused; + + for (int i = 0; i < STATUS_POLL_PAUSE_SLOT_MAX; ++i) { + if (s_status_poll_pause_tokens[i] == *token) { + s_status_poll_pause_tokens[i] = 0; + break; + } + } + + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, + (int32_t)status_poll_pause_depth_locked()); xSemaphoreGive(s_mutex); + *token = 0; } bool printer_protocol_acquire_control_lane(uint32_t timeout_ms, char *err, size_t err_len) { + if (!s_protocol_initialized || s_mutex == NULL) { + if (err != NULL && err_len > 0) { + snprintf(err, err_len, "printer protocol not initialized"); + } + return false; + } + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) { if (err != NULL && err_len > 0) { snprintf(err, err_len, "lock timeout"); @@ -87,6 +212,14 @@ bool printer_protocol_acquire_control_lane(uint32_t timeout_ms, char *err, size_ return false; } + if (s_protocol_stopping) { + xSemaphoreGive(s_mutex); + if (err != NULL && err_len > 0) { + snprintf(err, err_len, "printer protocol stopping"); + } + return false; + } + if (s_busy_refcnt != 0) { xSemaphoreGive(s_mutex); if (err != NULL && err_len > 0) { @@ -101,6 +234,10 @@ bool printer_protocol_acquire_control_lane(uint32_t timeout_ms, char *err, size_ } void printer_protocol_release_control_lane(void) { + if (s_mutex == NULL) { + return; + } + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) { return; } @@ -123,6 +260,10 @@ esp_err_t printer_protocol_send_frame(uint8_t cmd, const uint8_t *payload, uint16_t payload_len, bool with_checksum) { + if (!s_protocol_initialized || printer_protocol_is_stopping()) { + return ESP_ERR_INVALID_STATE; + } + uint8_t frame[260]; size_t total = 4 + payload_len + (with_checksum ? 1 : 0); if (total > sizeof(frame)) { @@ -146,7 +287,9 @@ esp_err_t printer_protocol_send_frame(uint8_t cmd, } static void clear_ack_signal(void) { - xEventGroupClearBits(s_evt, EVT_ACK); + if (s_evt != NULL) { + xEventGroupClearBits(s_evt, EVT_ACK); + } } bool printer_protocol_wait_response(uint8_t cmd, @@ -154,24 +297,41 @@ bool printer_protocol_wait_response(uint8_t cmd, size_t out_payload_cap, uint16_t *out_payload_len, uint32_t timeout_ms) { + if (timeout_ms == 0 || s_evt == NULL) { + return false; + } + int64_t deadline = esp_timer_get_time() / 1000 + timeout_ms; while (true) { + if (printer_protocol_is_stopping()) { + return false; + } + int64_t now = esp_timer_get_time() / 1000; if (now >= deadline) { return false; } uint32_t wait_ms = (uint32_t)(deadline - now); + if (wait_ms > 100) { + wait_ms = 100; + } + EventBits_t bits = xEventGroupWaitBits(s_evt, - EVT_ACK, - pdTRUE, + EVT_ACK | EVT_SHUTDOWN, + pdFALSE, pdFALSE, pdMS_TO_TICKS(wait_ms)); - if (!(bits & EVT_ACK)) { + if (bits & EVT_SHUTDOWN) { return false; } + if (!(bits & EVT_ACK)) { + continue; + } + xEventGroupClearBits(s_evt, EVT_ACK); + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) { continue; } @@ -247,9 +407,13 @@ esp_err_t printer_protocol_send_cmd_wait_response(uint8_t cmd, uint16_t rsp_len = 0; if (!printer_protocol_wait_response(cmd, rsp, sizeof(rsp), &rsp_len, timeout_ms)) { if (err != NULL && err_len > 0) { - snprintf(err, err_len, "response timeout"); + if (printer_protocol_is_stopping()) { + snprintf(err, err_len, "protocol stopping"); + } else { + snprintf(err, err_len, "response timeout"); + } } - return ESP_ERR_TIMEOUT; + return printer_protocol_is_stopping() ? ESP_ERR_INVALID_STATE : ESP_ERR_TIMEOUT; } if (expect_ack && (rsp_len < 1 || rsp[0] != 0x01)) { @@ -277,22 +441,35 @@ static void status_poll_task(void *arg) { (void)arg; while (true) { + bool should_stop = false; bool can_poll = false; + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(50)) == pdTRUE) { - can_poll = (s_busy_refcnt == 0) && !s_status_poll_paused; + should_stop = s_protocol_stopping; + can_poll = (s_busy_refcnt == 0) && (status_poll_pause_depth_locked() == 0); xSemaphoreGive(s_mutex); } + if (should_stop) { + break; + } + if (ble_printer_client_is_connected() && can_poll) { uint8_t dummy = 0x00; (void)printer_protocol_send_frame(CMD_GET_STATUS, &dummy, 0, true); } - vTaskDelay(pdMS_TO_TICKS(5000)); + vTaskDelay(pdMS_TO_TICKS(runtime_policy_printer_status_poll_interval_ms())); } + + if (s_evt != NULL) { + xEventGroupSetBits(s_evt, EVT_STATUS_POLL_EXITED); + } + s_status_poll_task_handle = NULL; + vTaskDelete(NULL); } static void on_rx_frame(const uint8_t *data, size_t len) { - if (data == NULL || len < 4) { + if (data == NULL || len < 4 || s_mutex == NULL) { return; } @@ -336,11 +513,32 @@ static void on_rx_frame(const uint8_t *data, size_t len) { memcpy(s_last_rsp_payload, payload, s_last_rsp_payload_len); xSemaphoreGive(s_mutex); } - xEventGroupSetBits(s_evt, EVT_ACK); + if (s_evt != NULL) { + xEventGroupSetBits(s_evt, EVT_ACK); + } + } +} + +static void destroy_runtime_objects(void) { + if (s_job_queue != NULL) { + vQueueDelete(s_job_queue); + s_job_queue = NULL; + } + if (s_evt != NULL) { + vEventGroupDelete(s_evt); + s_evt = NULL; + } + if (s_mutex != NULL) { + vSemaphoreDelete(s_mutex); + s_mutex = NULL; } } esp_err_t printer_protocol_init(void) { + if (s_protocol_initialized) { + return ESP_OK; + } + s_mutex = xSemaphoreCreateMutex(); if (s_mutex == NULL) { return ESP_ERR_NO_MEM; @@ -348,32 +546,58 @@ esp_err_t printer_protocol_init(void) { s_evt = xEventGroupCreate(); if (s_evt == NULL) { + destroy_runtime_objects(); return ESP_ERR_NO_MEM; } s_job_queue = xQueueCreate(JOB_QUEUE_LEN, sizeof(uint32_t)); if (s_job_queue == NULL) { + destroy_runtime_objects(); return ESP_ERR_NO_MEM; } + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) { + reset_jobs_locked(); + reset_runtime_state_locked(); + s_protocol_stopping = false; + xSemaphoreGive(s_mutex); + } + + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, 0); + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, 0); + s_protocol_initialized = true; + s_protocol_stopping = false; + esp_err_t err = ble_printer_client_init(on_rx_frame); if (err != ESP_OK) { + s_protocol_initialized = false; + destroy_runtime_objects(); return err; } BaseType_t ok = printer_create_task_prefer_psram(printer_protocol_worker_task, "print_worker", 6144, - 6); + 6, + &s_worker_task); if (ok != pdPASS) { + s_protocol_initialized = false; + destroy_runtime_objects(); return ESP_ERR_NO_MEM; } ok = printer_create_task_prefer_psram(status_poll_task, "status_poll", 4096, - 4); + 4, + &s_status_poll_task_handle); if (ok != pdPASS) { + if (s_worker_task != NULL) { + vTaskDelete(s_worker_task); + s_worker_task = NULL; + } + s_protocol_initialized = false; + destroy_runtime_objects(); return ESP_ERR_NO_MEM; } @@ -381,7 +605,89 @@ esp_err_t printer_protocol_init(void) { return ESP_OK; } +esp_err_t printer_protocol_stop(uint32_t timeout_ms) { + if (!s_protocol_initialized) { + return ESP_OK; + } + + if (timeout_ms == 0) { + timeout_ms = runtime_policy_printer_stop_timeout_ms(); + } + + if (s_mutex != NULL && xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) == pdTRUE) { + s_protocol_stopping = true; + s_busy_refcnt = 0; + status_poll_pause_slots_clear_locked(); + xSemaphoreGive(s_mutex); + } + + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, 0); + + if (s_evt != NULL) { + xEventGroupSetBits(s_evt, EVT_SHUTDOWN); + } + + if (s_job_queue != NULL) { + const uint32_t sentinel = PRINTER_JOB_SENTINEL_STOP; + (void)xQueueSend(s_job_queue, &sentinel, 0); + } + + ble_printer_client_disconnect(); + + bool worker_done = (s_worker_task == NULL); + bool poll_done = (s_status_poll_task_handle == NULL); + + if (s_evt != NULL && (!worker_done || !poll_done)) { + EventBits_t bits = xEventGroupWaitBits(s_evt, + EVT_WORKER_EXITED | EVT_STATUS_POLL_EXITED, + pdFALSE, + pdTRUE, + pdMS_TO_TICKS(timeout_ms)); + worker_done = worker_done || ((bits & EVT_WORKER_EXITED) != 0) || s_worker_task == NULL; + poll_done = poll_done || ((bits & EVT_STATUS_POLL_EXITED) != 0) || s_status_poll_task_handle == NULL; + } + + if (!worker_done && s_worker_task != NULL) { + ESP_LOGW(TAG, "worker task stop timeout, force delete"); + vTaskDelete(s_worker_task); + s_worker_task = NULL; + } + if (!poll_done && s_status_poll_task_handle != NULL) { + ESP_LOGW(TAG, "status poll task stop timeout, force delete"); + vTaskDelete(s_status_poll_task_handle); + s_status_poll_task_handle = NULL; + } + + if (s_mutex != NULL && xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) == pdTRUE) { + reset_jobs_locked(); + reset_runtime_state_locked(); + xSemaphoreGive(s_mutex); + } + + destroy_runtime_objects(); + + s_busy_refcnt = 0; + s_status_poll_pause_next_token = 1; + memset(s_status_poll_pause_tokens, 0, sizeof(s_status_poll_pause_tokens)); + memset(s_last_rsp_payload, 0, sizeof(s_last_rsp_payload)); + s_last_rsp_cmd = 0; + s_last_rsp_payload_len = 0; + s_worker_task = NULL; + s_status_poll_task_handle = NULL; + s_protocol_initialized = false; + s_protocol_stopping = false; + + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, 0); + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, 0); + + ESP_LOGI(TAG, "printer protocol stopped"); + return (worker_done && poll_done) ? ESP_OK : ESP_ERR_TIMEOUT; +} + esp_err_t printer_protocol_connect(const char *target_name, uint32_t timeout_ms) { + if (!s_protocol_initialized || s_protocol_stopping) { + return ESP_ERR_INVALID_STATE; + } return ble_printer_client_connect(target_name, timeout_ms); } @@ -403,13 +709,17 @@ void printer_protocol_get_runtime_status(printer_runtime_status_t *out_status) { out_status->notify_ready = link.notify_ready; out_status->mtu = link.mtu; - if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) { + if (s_mutex != NULL && xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) { out_status->busy = s_busy_refcnt > 0; out_status->has_paper = s_status.has_paper; out_status->battery_percent = s_status.battery; out_status->temperature = s_status.temperature; out_status->last_status_ms = s_status.updated_ms; - out_status->queue_depth = (uint32_t)uxQueueMessagesWaiting(s_job_queue); + out_status->queue_depth = (s_job_queue != NULL) + ? (uint32_t)uxQueueMessagesWaiting(s_job_queue) + : 0; xSemaphoreGive(s_mutex); } + + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, (int32_t)out_status->queue_depth); } diff --git a/main/domain/src/printer_protocol_commands.c b/main/domain/src/printer_protocol_commands.c index 734f8e0..fe25428 100644 --- a/main/domain/src/printer_protocol_commands.c +++ b/main/domain/src/printer_protocol_commands.c @@ -5,6 +5,7 @@ #include #include "ble_printer_client.h" +#include "runtime_policy.h" esp_err_t printer_protocol_gap_move(uint32_t timeout_ms, char *err, size_t err_len) { if (!ble_printer_client_is_connected()) { @@ -14,7 +15,7 @@ esp_err_t printer_protocol_gap_move(uint32_t timeout_ms, char *err, size_t err_l return ESP_ERR_INVALID_STATE; } - if (!printer_protocol_acquire_control_lane(1000, err, err_len)) { + if (!printer_protocol_acquire_control_lane(runtime_policy_printer_control_lock_timeout_ms(), err, err_len)) { return ESP_ERR_INVALID_STATE; } @@ -49,7 +50,7 @@ esp_err_t printer_protocol_get_label_offset(uint8_t *out_offset, uint32_t timeou return ESP_ERR_INVALID_STATE; } - if (!printer_protocol_acquire_control_lane(1000, err, err_len)) { + if (!printer_protocol_acquire_control_lane(runtime_policy_printer_control_lock_timeout_ms(), err, err_len)) { return ESP_ERR_INVALID_STATE; } @@ -96,7 +97,7 @@ esp_err_t printer_protocol_set_label_offset(uint8_t offset, uint32_t timeout_ms, return ESP_ERR_INVALID_STATE; } - if (!printer_protocol_acquire_control_lane(1000, err, err_len)) { + if (!printer_protocol_acquire_control_lane(runtime_policy_printer_control_lock_timeout_ms(), err, err_len)) { return ESP_ERR_INVALID_STATE; } @@ -123,7 +124,7 @@ esp_err_t printer_protocol_ota_jump_boot(uint32_t timeout_ms, char *err, size_t return ESP_ERR_INVALID_STATE; } - if (!printer_protocol_acquire_control_lane(1000, err, err_len)) { + if (!printer_protocol_acquire_control_lane(runtime_policy_printer_control_lock_timeout_ms(), err, err_len)) { return ESP_ERR_INVALID_STATE; } @@ -150,7 +151,7 @@ esp_err_t printer_protocol_ota_jump_app(uint32_t timeout_ms, char *err, size_t e return ESP_ERR_INVALID_STATE; } - if (!printer_protocol_acquire_control_lane(1000, err, err_len)) { + if (!printer_protocol_acquire_control_lane(runtime_policy_printer_control_lock_timeout_ms(), err, err_len)) { return ESP_ERR_INVALID_STATE; } @@ -177,7 +178,7 @@ esp_err_t printer_protocol_ota_erase_page(uint16_t page_num, uint32_t timeout_ms return ESP_ERR_INVALID_STATE; } - if (!printer_protocol_acquire_control_lane(1000, err, err_len)) { + if (!printer_protocol_acquire_control_lane(runtime_policy_printer_control_lock_timeout_ms(), err, err_len)) { return ESP_ERR_INVALID_STATE; } @@ -222,7 +223,7 @@ esp_err_t printer_protocol_ota_write_frame(uint16_t packet_num, return ESP_ERR_INVALID_STATE; } - if (!printer_protocol_acquire_control_lane(1000, err, err_len)) { + if (!printer_protocol_acquire_control_lane(runtime_policy_printer_control_lock_timeout_ms(), err, err_len)) { return ESP_ERR_INVALID_STATE; } @@ -267,7 +268,7 @@ esp_err_t printer_protocol_ota_get_version(printer_ota_version_t *out_version, return ESP_ERR_INVALID_STATE; } - if (!printer_protocol_acquire_control_lane(1000, err, err_len)) { + if (!printer_protocol_acquire_control_lane(runtime_policy_printer_control_lock_timeout_ms(), err, err_len)) { return ESP_ERR_INVALID_STATE; } diff --git a/main/domain/src/printer_protocol_jobs.c b/main/domain/src/printer_protocol_jobs.c index aefb280..cbf6814 100644 --- a/main/domain/src/printer_protocol_jobs.c +++ b/main/domain/src/printer_protocol_jobs.c @@ -7,6 +7,8 @@ #include "esp_log.h" #include "esp_timer.h" +#include "runtime_diagnostics.h" +#include "runtime_policy.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -63,6 +65,13 @@ esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster, return ESP_ERR_INVALID_ARG; } + if (!s_protocol_initialized || s_protocol_stopping) { + if (err != NULL && err_len > 0) { + snprintf(err, err_len, "printer protocol unavailable"); + } + return ESP_ERR_INVALID_STATE; + } + if (width != 384) { if (err != NULL && err_len > 0) { snprintf(err, err_len, "width must be 384"); @@ -87,7 +96,7 @@ esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster, return ESP_ERR_INVALID_SIZE; } - if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) { + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(runtime_policy_printer_control_lock_timeout_ms())) != pdTRUE) { if (err != NULL && err_len > 0) { snprintf(err, err_len, "lock timeout"); } @@ -166,6 +175,8 @@ esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster, (unsigned)height, density != NULL ? density : "中等", (unsigned)queue_depth); + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_PRINTER_JOB_SUBMITTED, 1); + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, (int32_t)queue_depth); return ESP_OK; } @@ -284,6 +295,7 @@ esp_err_t printer_protocol_cancel_job(uint32_t job_id, char *err, size_t err_len } xSemaphoreGive(s_mutex); + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_PRINTER_JOB_CANCELED, 1); return ESP_OK; } @@ -319,5 +331,7 @@ size_t printer_protocol_cleanup_jobs(bool include_success, bool include_failed, } xSemaphoreGive(s_mutex); + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, + s_job_queue != NULL ? (int32_t)uxQueueMessagesWaiting(s_job_queue) : 0); return removed; } diff --git a/main/domain/src/printer_protocol_worker.c b/main/domain/src/printer_protocol_worker.c index a712eae..6dbba0c 100644 --- a/main/domain/src/printer_protocol_worker.c +++ b/main/domain/src/printer_protocol_worker.c @@ -8,6 +8,8 @@ #include "ble_printer_client.h" #include "esp_log.h" #include "esp_timer.h" +#include "runtime_diagnostics.h" +#include "runtime_policy.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -16,6 +18,10 @@ static const char *TAG = "printer_protocol"; static bool request_status_sync(uint32_t timeout_ms) { int64_t old_ms; + if (printer_protocol_is_stopping()) { + return false; + } + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) { return false; } @@ -29,6 +35,10 @@ static bool request_status_sync(uint32_t timeout_ms) { int64_t deadline = esp_timer_get_time() / 1000 + timeout_ms; while ((esp_timer_get_time() / 1000) < deadline) { + if (printer_protocol_is_stopping()) { + return false; + } + vTaskDelay(pdMS_TO_TICKS(20)); if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) != pdTRUE) { @@ -111,6 +121,11 @@ static bool run_print_job(job_slot_t *job) { (unsigned)job->height, job->density); + if (printer_protocol_is_stopping()) { + snprintf(job->error, sizeof(job->error), "protocol stopping"); + return false; + } + if (!ble_printer_client_is_connected()) { snprintf(job->error, sizeof(job->error), "printer not connected"); ESP_LOGW(TAG, "job %u print aborted: %s", (unsigned)job->id, job->error); @@ -160,6 +175,11 @@ static bool run_print_job(job_slot_t *job) { (unsigned)hot_time); for (size_t i = 0; i < total_chunks; ++i) { + if (printer_protocol_is_stopping()) { + snprintf(job->error, sizeof(job->error), "protocol stopping"); + return false; + } + if (job->cancel_requested) { snprintf(job->error, sizeof(job->error), "job canceled"); ESP_LOGW(TAG, @@ -219,11 +239,21 @@ void printer_protocol_worker_task(void *arg) { (void)arg; while (true) { - uint32_t job_id; - if (xQueueReceive(s_job_queue, &job_id, portMAX_DELAY) != pdTRUE) { + if (printer_protocol_is_stopping()) { + break; + } + + uint32_t job_id = 0; + if (xQueueReceive(s_job_queue, + &job_id, + pdMS_TO_TICKS(runtime_policy_printer_worker_queue_wait_ms())) != pdTRUE) { continue; } + if (job_id == PRINTER_JOB_SENTINEL_STOP || printer_protocol_is_stopping()) { + break; + } + if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) { continue; } @@ -241,13 +271,14 @@ void printer_protocol_worker_task(void *arg) { s_jobs[idx].finished_ms = esp_timer_get_time() / 1000; } xSemaphoreGive(s_mutex); + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_PRINTER_JOB_CANCELED, 1); continue; } if (s_busy_refcnt != 0) { xSemaphoreGive(s_mutex); - xQueueSendToFront(s_job_queue, &job_id, 0); - vTaskDelay(pdMS_TO_TICKS(20)); + (void)xQueueSendToFront(s_job_queue, &job_id, 0); + vTaskDelay(pdMS_TO_TICKS(runtime_policy_printer_queue_retry_delay_ms())); continue; } @@ -287,13 +318,16 @@ void printer_protocol_worker_task(void *arg) { if (ok) { s_jobs[idx].state = PRINT_JOB_STATE_SUCCESS; s_jobs[idx].error[0] = '\0'; - } else if (s_jobs[idx].cancel_requested) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_PRINTER_JOB_SUCCESS, 1); + } else if (s_jobs[idx].cancel_requested || printer_protocol_is_stopping()) { s_jobs[idx].state = PRINT_JOB_STATE_CANCELED; if (s_jobs[idx].error[0] == '\0') { strlcpy(s_jobs[idx].error, "job canceled", sizeof(s_jobs[idx].error)); } + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_PRINTER_JOB_CANCELED, 1); } else { s_jobs[idx].state = PRINT_JOB_STATE_FAILED; + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_PRINTER_JOB_FAILED, 1); } int64_t duration_ms = 0; @@ -317,6 +351,14 @@ void printer_protocol_worker_task(void *arg) { free(s_jobs[idx].data); s_jobs[idx].data = NULL; + runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, + (int32_t)uxQueueMessagesWaiting(s_job_queue)); xSemaphoreGive(s_mutex); } + + if (s_evt != NULL) { + xEventGroupSetBits(s_evt, EVT_WORKER_EXITED); + } + s_worker_task = NULL; + vTaskDelete(NULL); } diff --git a/main/domain/src/system_runtime.c b/main/domain/src/system_runtime.c index 3529afa..6bdc0c9 100644 --- a/main/domain/src/system_runtime.c +++ b/main/domain/src/system_runtime.c @@ -3,12 +3,39 @@ #include "platform_bootstrap.h" #include "wifi_manager.h" -esp_err_t system_runtime_bootstrap(void) { +static bool s_runtime_started; + +esp_err_t system_runtime_start(void) { + if (s_runtime_started) { + return ESP_OK; + } + esp_err_t err = platform_bootstrap_init(); if (err != ESP_OK) { return err; } - return wifi_manager_start(); + + err = wifi_manager_start(); + if (err != ESP_OK) { + return err; + } + + s_runtime_started = true; + return ESP_OK; +} + +esp_err_t system_runtime_shutdown(void) { + if (!s_runtime_started) { + return ESP_OK; + } + + esp_err_t err = wifi_manager_stop(); + s_runtime_started = false; + return err; +} + +esp_err_t system_runtime_bootstrap(void) { + return system_runtime_start(); } bool system_runtime_wifi_ready(void) { diff --git a/main/domain/src/voice_interaction_marker.c b/main/domain/src/voice_interaction_marker.c index c16dcc9..286e468 100644 --- a/main/domain/src/voice_interaction_marker.c +++ b/main/domain/src/voice_interaction_marker.c @@ -331,17 +331,15 @@ static void voice_image_generation_task(void *arg) { image_generation_result_t result = {0}; image_generation_result_reset(&result); - bool status_poll_paused = false; + printer_status_poll_pause_token_t status_poll_pause_token = 0; char gen_err[160] = {0}; ESP_LOGI(TAG, "[stage] marker_image_gen_start: dialog_id=%s prompt_len=%u", task_arg->dialog_id[0] != '\0' ? task_arg->dialog_id : "-", (unsigned)strlen(task_arg->prompt)); - printer_protocol_set_status_poll_paused(true); - status_poll_paused = true; + status_poll_pause_token = printer_protocol_status_poll_pause_acquire(); esp_err_t rc = image_generation_generate_png(&req, &result, gen_err, sizeof(gen_err)); - printer_protocol_set_status_poll_paused(false); - status_poll_paused = false; + printer_protocol_status_poll_pause_release(&status_poll_pause_token); if (rc == ESP_OK) { ESP_LOGI(TAG, "[stage] marker_image_gen_done: dialog_id=%s request_id=%s size=%ux%u png_bytes=%u", @@ -375,9 +373,7 @@ static void voice_image_generation_task(void *arg) { gen_err[0] != '\0' ? gen_err : "image generation failed"); } - if (status_poll_paused) { - printer_protocol_set_status_poll_paused(false); - } + printer_protocol_status_poll_pause_release(&status_poll_pause_token); image_generation_result_free(&result); voice_marker_image_generation_release(); image_generation_schedule_prewarm(); diff --git a/main/platform/include/runtime_diagnostics.h b/main/platform/include/runtime_diagnostics.h new file mode 100644 index 0000000..bdf4312 --- /dev/null +++ b/main/platform/include/runtime_diagnostics.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + RUNTIME_DIAG_COUNTER_LIFECYCLE_START_ATTEMPT = 0, + RUNTIME_DIAG_COUNTER_LIFECYCLE_START_SUCCESS, + RUNTIME_DIAG_COUNTER_LIFECYCLE_START_FAILED, + RUNTIME_DIAG_COUNTER_LIFECYCLE_START_RETRY, + RUNTIME_DIAG_COUNTER_LIFECYCLE_STOP_ATTEMPT, + RUNTIME_DIAG_COUNTER_LIFECYCLE_STOP_SUCCESS, + RUNTIME_DIAG_COUNTER_LIFECYCLE_STOP_FAILED, + RUNTIME_DIAG_COUNTER_WIFI_CONNECT_SUCCESS, + RUNTIME_DIAG_COUNTER_WIFI_CONNECT_FAILED, + RUNTIME_DIAG_COUNTER_WIFI_CONNECT_TIMEOUT, + RUNTIME_DIAG_COUNTER_PRINTER_JOB_SUBMITTED, + RUNTIME_DIAG_COUNTER_PRINTER_JOB_SUCCESS, + RUNTIME_DIAG_COUNTER_PRINTER_JOB_FAILED, + RUNTIME_DIAG_COUNTER_PRINTER_JOB_CANCELED, + RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_ATTEMPT, + RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_SUCCESS, + RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_FAILED, + RUNTIME_DIAG_COUNTER_IMAGE_GENERATE_TIMEOUT, + RUNTIME_DIAG_COUNTER_REST_RESPONSES_TOTAL, + RUNTIME_DIAG_COUNTER_REST_ERRORS_TOTAL, + RUNTIME_DIAG_COUNTER_MAX, +} runtime_diag_counter_t; + +typedef enum { + RUNTIME_DIAG_GAUGE_LIFECYCLE_STATE = 0, + RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, + RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, + RUNTIME_DIAG_GAUGE_MAX, +} runtime_diag_gauge_t; + +typedef struct { + uint64_t counters[RUNTIME_DIAG_COUNTER_MAX]; + int32_t gauges[RUNTIME_DIAG_GAUGE_MAX]; + int64_t last_error_ms; + esp_err_t last_error_code; + char last_error_source[32]; + char last_error_message[96]; +} runtime_diag_snapshot_t; + +void runtime_diag_counter_add(runtime_diag_counter_t counter, uint32_t delta); +void runtime_diag_set_gauge(runtime_diag_gauge_t gauge, int32_t value); +void runtime_diag_record_error(const char *source, esp_err_t code, const char *message); +void runtime_diag_get_snapshot(runtime_diag_snapshot_t *out_snapshot); + +const char *runtime_diag_counter_name(runtime_diag_counter_t counter); +const char *runtime_diag_gauge_name(runtime_diag_gauge_t gauge); + +#ifdef __cplusplus +} +#endif diff --git a/main/platform/include/runtime_policy.h b/main/platform/include/runtime_policy.h new file mode 100644 index 0000000..c488727 --- /dev/null +++ b/main/platform/include/runtime_policy.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "esp_err.h" + +uint32_t runtime_policy_wifi_connect_timeout_ms(void); + +uint32_t runtime_policy_lifecycle_start_retry_count(void); +uint32_t runtime_policy_lifecycle_retry_backoff_ms(uint32_t attempt); +bool runtime_policy_is_retryable_error(esp_err_t err); + +uint32_t runtime_policy_printer_control_lock_timeout_ms(void); +uint32_t runtime_policy_printer_worker_queue_wait_ms(void); +uint32_t runtime_policy_printer_queue_retry_delay_ms(void); +uint32_t runtime_policy_printer_status_poll_interval_ms(void); +uint32_t runtime_policy_printer_stop_timeout_ms(void); + +uint32_t runtime_policy_rest_printer_connect_timeout_ms(void); +uint32_t runtime_policy_rest_label_timeout_ms(void); +uint32_t runtime_policy_rest_ota_timeout_ms(void); + +uint32_t runtime_policy_image_generation_timeout_default_ms(void); +uint32_t runtime_policy_image_generation_timeout_min_ms(void); +uint32_t runtime_policy_image_generation_timeout_max_ms(void); + +uint32_t runtime_policy_image_download_timeout_default_ms(void); +uint32_t runtime_policy_image_download_timeout_min_ms(void); +uint32_t runtime_policy_image_download_timeout_max_ms(void); diff --git a/main/platform/include/wifi_manager.h b/main/platform/include/wifi_manager.h index 09bd29c..6c6e7ff 100644 --- a/main/platform/include/wifi_manager.h +++ b/main/platform/include/wifi_manager.h @@ -5,6 +5,7 @@ #include "esp_err.h" esp_err_t wifi_manager_start(void); +esp_err_t wifi_manager_stop(void); bool wifi_manager_is_ready(void); diff --git a/main/platform/src/ble_printer_client.c b/main/platform/src/ble_printer_client.c index fd4787e..3843f22 100644 --- a/main/platform/src/ble_printer_client.c +++ b/main/platform/src/ble_printer_client.c @@ -51,6 +51,7 @@ static uint16_t s_mtu = 23; static bool s_scanning; static bool s_notify_ready; static bool s_host_synced; +static bool s_initialized; static uint16_t uuid16(const ble_uuid_t *uuid) { if (uuid == NULL || uuid->type != BLE_UUID_TYPE_16) { @@ -519,6 +520,11 @@ static void nimble_host_task(void *param) { esp_err_t ble_printer_client_init(ble_frame_rx_cb_t rx_cb) { s_rx_cb = rx_cb; + if (s_initialized) { + reset_discovery_state(); + return ESP_OK; + } + // NimBLE emits very chatty INFO logs during each chunk write. // Lowering these logs reduces serial I/O overhead and improves runtime smoothness. esp_log_level_set("NimBLE", ESP_LOG_WARN); @@ -531,6 +537,8 @@ esp_err_t ble_printer_client_init(ble_frame_rx_cb_t rx_cb) { s_lock = xSemaphoreCreateMutex(); if (s_lock == NULL) { + vEventGroupDelete(s_evt_group); + s_evt_group = NULL; return ESP_ERR_NO_MEM; } @@ -547,10 +555,15 @@ esp_err_t ble_printer_client_init(ble_frame_rx_cb_t rx_cb) { nimble_port_freertos_init(nimble_host_task); reset_discovery_state(); + s_initialized = true; return ESP_OK; } esp_err_t ble_printer_client_connect(const char *target_name, uint32_t timeout_ms) { + if (!s_initialized || s_lock == NULL || s_evt_group == NULL) { + return ESP_ERR_INVALID_STATE; + } + s_match_any_compatible = false; if (target_name != NULL && target_name[0] != '\0') { if (strcmp(target_name, "*") == 0) { @@ -626,6 +639,10 @@ esp_err_t ble_printer_client_connect(const char *target_name, uint32_t timeout_m } void ble_printer_client_disconnect(void) { + if (!s_initialized || s_lock == NULL) { + return; + } + if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(2000)) != pdTRUE) { return; } @@ -642,6 +659,9 @@ void ble_printer_client_disconnect(void) { } bool ble_printer_client_is_connected(void) { + if (!s_initialized) { + return false; + } return (s_conn_handle != BLE_HS_CONN_HANDLE_NONE) && s_notify_ready; } diff --git a/main/platform/src/runtime_diagnostics.c b/main/platform/src/runtime_diagnostics.c new file mode 100644 index 0000000..bbfcfed --- /dev/null +++ b/main/platform/src/runtime_diagnostics.c @@ -0,0 +1,136 @@ +#include "runtime_diagnostics.h" + +#include +#include + +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +static SemaphoreHandle_t s_lock; +static runtime_diag_snapshot_t s_snapshot; + +static const char *const s_counter_names[RUNTIME_DIAG_COUNTER_MAX] = { + "lifecycle_start_attempt", + "lifecycle_start_success", + "lifecycle_start_failed", + "lifecycle_start_retry", + "lifecycle_stop_attempt", + "lifecycle_stop_success", + "lifecycle_stop_failed", + "wifi_connect_success", + "wifi_connect_failed", + "wifi_connect_timeout", + "printer_job_submitted", + "printer_job_success", + "printer_job_failed", + "printer_job_canceled", + "image_generate_attempt", + "image_generate_success", + "image_generate_failed", + "image_generate_timeout", + "rest_responses_total", + "rest_errors_total", +}; + +static const char *const s_gauge_names[RUNTIME_DIAG_GAUGE_MAX] = { + "lifecycle_state", + "status_poll_pause_depth", + "printer_queue_depth", +}; + +static void runtime_diag_ensure_lock(void) { + if (s_lock == NULL) { + s_lock = xSemaphoreCreateMutex(); + } +} + +void runtime_diag_counter_add(runtime_diag_counter_t counter, uint32_t delta) { + if (counter < 0 || counter >= RUNTIME_DIAG_COUNTER_MAX || delta == 0) { + return; + } + + runtime_diag_ensure_lock(); + if (s_lock == NULL) { + return; + } + + if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(50)) != pdTRUE) { + return; + } + s_snapshot.counters[counter] += delta; + xSemaphoreGive(s_lock); +} + +void runtime_diag_set_gauge(runtime_diag_gauge_t gauge, int32_t value) { + if (gauge < 0 || gauge >= RUNTIME_DIAG_GAUGE_MAX) { + return; + } + + runtime_diag_ensure_lock(); + if (s_lock == NULL) { + return; + } + + if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(50)) != pdTRUE) { + return; + } + s_snapshot.gauges[gauge] = value; + xSemaphoreGive(s_lock); +} + +void runtime_diag_record_error(const char *source, esp_err_t code, const char *message) { + runtime_diag_ensure_lock(); + if (s_lock == NULL) { + return; + } + + if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(100)) != pdTRUE) { + return; + } + + s_snapshot.last_error_ms = esp_timer_get_time() / 1000; + s_snapshot.last_error_code = code; + strlcpy(s_snapshot.last_error_source, + source != NULL ? source : "unknown", + sizeof(s_snapshot.last_error_source)); + strlcpy(s_snapshot.last_error_message, + message != NULL ? message : "unknown", + sizeof(s_snapshot.last_error_message)); + + xSemaphoreGive(s_lock); +} + +void runtime_diag_get_snapshot(runtime_diag_snapshot_t *out_snapshot) { + if (out_snapshot == NULL) { + return; + } + + memset(out_snapshot, 0, sizeof(*out_snapshot)); + + runtime_diag_ensure_lock(); + if (s_lock == NULL) { + return; + } + + if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(100)) != pdTRUE) { + return; + } + + *out_snapshot = s_snapshot; + xSemaphoreGive(s_lock); +} + +const char *runtime_diag_counter_name(runtime_diag_counter_t counter) { + if (counter < 0 || counter >= RUNTIME_DIAG_COUNTER_MAX) { + return "unknown"; + } + return s_counter_names[counter]; +} + +const char *runtime_diag_gauge_name(runtime_diag_gauge_t gauge) { + if (gauge < 0 || gauge >= RUNTIME_DIAG_GAUGE_MAX) { + return "unknown"; + } + return s_gauge_names[gauge]; +} diff --git a/main/platform/src/runtime_policy.c b/main/platform/src/runtime_policy.c new file mode 100644 index 0000000..1490fd6 --- /dev/null +++ b/main/platform/src/runtime_policy.c @@ -0,0 +1,142 @@ +#include "runtime_policy.h" + +#include + +#ifndef CONFIG_TQ_WIFI_CONNECT_TIMEOUT_MS +#define CONFIG_TQ_WIFI_CONNECT_TIMEOUT_MS 15000 +#endif + +#ifndef CONFIG_TQ_LIFECYCLE_START_RETRY_COUNT +#define CONFIG_TQ_LIFECYCLE_START_RETRY_COUNT 2 +#endif + +#ifndef CONFIG_TQ_LIFECYCLE_RETRY_BACKOFF_MS +#define CONFIG_TQ_LIFECYCLE_RETRY_BACKOFF_MS 800 +#endif + +#ifndef CONFIG_TQ_PRINTER_CONTROL_LOCK_TIMEOUT_MS +#define CONFIG_TQ_PRINTER_CONTROL_LOCK_TIMEOUT_MS 1000 +#endif + +#ifndef CONFIG_TQ_PRINTER_WORKER_QUEUE_WAIT_MS +#define CONFIG_TQ_PRINTER_WORKER_QUEUE_WAIT_MS 250 +#endif + +#ifndef CONFIG_TQ_PRINTER_QUEUE_RETRY_DELAY_MS +#define CONFIG_TQ_PRINTER_QUEUE_RETRY_DELAY_MS 20 +#endif + +#ifndef CONFIG_TQ_PRINTER_STATUS_POLL_INTERVAL_MS +#define CONFIG_TQ_PRINTER_STATUS_POLL_INTERVAL_MS 5000 +#endif + +#ifndef CONFIG_TQ_PRINTER_STOP_TIMEOUT_MS +#define CONFIG_TQ_PRINTER_STOP_TIMEOUT_MS 8000 +#endif + +#ifndef CONFIG_TQ_REST_PRINTER_CONNECT_TIMEOUT_MS +#define CONFIG_TQ_REST_PRINTER_CONNECT_TIMEOUT_MS 15000 +#endif + +#ifndef CONFIG_TQ_REST_LABEL_TIMEOUT_MS +#define CONFIG_TQ_REST_LABEL_TIMEOUT_MS 5000 +#endif + +#ifndef CONFIG_TQ_REST_OTA_STEP_TIMEOUT_MS +#define CONFIG_TQ_REST_OTA_STEP_TIMEOUT_MS 5000 +#endif + +#ifndef CONFIG_TQ_Z_IMAGE_TIMEOUT_MS +#define CONFIG_TQ_Z_IMAGE_TIMEOUT_MS 45000 +#endif + +#ifndef CONFIG_TQ_Z_IMAGE_DOWNLOAD_TIMEOUT_MS +#define CONFIG_TQ_Z_IMAGE_DOWNLOAD_TIMEOUT_MS 15000 +#endif + +static uint32_t clamp_u32(uint32_t value, uint32_t min_value, uint32_t max_value) { + if (value < min_value) { + return min_value; + } + if (value > max_value) { + return max_value; + } + return value; +} + +uint32_t runtime_policy_wifi_connect_timeout_ms(void) { + return clamp_u32(CONFIG_TQ_WIFI_CONNECT_TIMEOUT_MS, 3000, 60000); +} + +uint32_t runtime_policy_lifecycle_start_retry_count(void) { + return clamp_u32(CONFIG_TQ_LIFECYCLE_START_RETRY_COUNT, 0, 5); +} + +uint32_t runtime_policy_lifecycle_retry_backoff_ms(uint32_t attempt) { + uint32_t base = clamp_u32(CONFIG_TQ_LIFECYCLE_RETRY_BACKOFF_MS, 100, 10000); + if (attempt > 8) { + attempt = 8; + } + uint32_t factor = (1u << attempt); + return clamp_u32(base * factor, 100, 30000); +} + +bool runtime_policy_is_retryable_error(esp_err_t err) { + return err == ESP_ERR_TIMEOUT || err == ESP_FAIL || err == ESP_ERR_NO_MEM; +} + +uint32_t runtime_policy_printer_control_lock_timeout_ms(void) { + return clamp_u32(CONFIG_TQ_PRINTER_CONTROL_LOCK_TIMEOUT_MS, 100, 5000); +} + +uint32_t runtime_policy_printer_worker_queue_wait_ms(void) { + return clamp_u32(CONFIG_TQ_PRINTER_WORKER_QUEUE_WAIT_MS, 50, 2000); +} + +uint32_t runtime_policy_printer_queue_retry_delay_ms(void) { + return clamp_u32(CONFIG_TQ_PRINTER_QUEUE_RETRY_DELAY_MS, 5, 500); +} + +uint32_t runtime_policy_printer_status_poll_interval_ms(void) { + return clamp_u32(CONFIG_TQ_PRINTER_STATUS_POLL_INTERVAL_MS, 1000, 30000); +} + +uint32_t runtime_policy_printer_stop_timeout_ms(void) { + return clamp_u32(CONFIG_TQ_PRINTER_STOP_TIMEOUT_MS, 1000, 30000); +} + +uint32_t runtime_policy_rest_printer_connect_timeout_ms(void) { + return clamp_u32(CONFIG_TQ_REST_PRINTER_CONNECT_TIMEOUT_MS, 1000, 60000); +} + +uint32_t runtime_policy_rest_label_timeout_ms(void) { + return clamp_u32(CONFIG_TQ_REST_LABEL_TIMEOUT_MS, 500, 30000); +} + +uint32_t runtime_policy_rest_ota_timeout_ms(void) { + return clamp_u32(CONFIG_TQ_REST_OTA_STEP_TIMEOUT_MS, 500, 60000); +} + +uint32_t runtime_policy_image_generation_timeout_default_ms(void) { + return clamp_u32(CONFIG_TQ_Z_IMAGE_TIMEOUT_MS, 5000, 180000); +} + +uint32_t runtime_policy_image_generation_timeout_min_ms(void) { + return 5000; +} + +uint32_t runtime_policy_image_generation_timeout_max_ms(void) { + return 180000; +} + +uint32_t runtime_policy_image_download_timeout_default_ms(void) { + return clamp_u32(CONFIG_TQ_Z_IMAGE_DOWNLOAD_TIMEOUT_MS, 2000, 120000); +} + +uint32_t runtime_policy_image_download_timeout_min_ms(void) { + return 2000; +} + +uint32_t runtime_policy_image_download_timeout_max_ms(void) { + return 120000; +} diff --git a/main/platform/src/wifi_manager.c b/main/platform/src/wifi_manager.c index ee5403d..eb1a244 100644 --- a/main/platform/src/wifi_manager.c +++ b/main/platform/src/wifi_manager.c @@ -7,6 +7,9 @@ #include "esp_log.h" #include "esp_netif.h" #include "esp_wifi.h" +#include "esp_wifi_default.h" +#include "runtime_diagnostics.h" +#include "runtime_policy.h" #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" @@ -18,7 +21,10 @@ static const char *TAG = "wifi_manager"; static EventGroupHandle_t s_wifi_event_group; static int s_retry_num; static bool s_ready; +static bool s_started; static esp_netif_t *s_sta_netif; +static esp_event_handler_instance_t s_wifi_event_inst; +static esp_event_handler_instance_t s_ip_event_inst; static void wifi_manager_apply_sta_throughput_profile(void) { const uint8_t protocol = WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N; @@ -49,18 +55,20 @@ static void wifi_event_handler(void *arg, int32_t event_id, void *event_data) { (void)arg; + (void)event_data; if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { - esp_wifi_connect(); + (void)esp_wifi_connect(); return; } if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + s_ready = false; if (s_retry_num < CONFIG_TQ_WIFI_MAXIMUM_RETRY) { - esp_wifi_connect(); + (void)esp_wifi_connect(); s_retry_num++; ESP_LOGW(TAG, "retry to connect to the AP (%d/%d)", s_retry_num, CONFIG_TQ_WIFI_MAXIMUM_RETRY); - } else { + } else if (s_wifi_event_group != NULL) { xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); } return; @@ -70,7 +78,10 @@ static void wifi_event_handler(void *arg, ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); s_retry_num = 0; - xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + s_ready = true; + if (s_wifi_event_group != NULL) { + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + } return; } } @@ -88,9 +99,18 @@ static esp_err_t start_sta_mode(void) { wifi_config.sta.pmf_cfg.capable = true; wifi_config.sta.pmf_cfg.required = false; - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); - ESP_ERROR_CHECK(esp_wifi_start()); + esp_err_t rc = esp_wifi_set_mode(WIFI_MODE_STA); + if (rc != ESP_OK) { + return rc; + } + rc = esp_wifi_set_config(WIFI_IF_STA, &wifi_config); + if (rc != ESP_OK) { + return rc; + } + rc = esp_wifi_start(); + if (rc != ESP_OK && rc != ESP_ERR_INVALID_STATE) { + return rc; + } ESP_LOGI(TAG, "wifi_init_sta finished"); @@ -98,7 +118,7 @@ static esp_err_t start_sta_mode(void) { WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, - pdMS_TO_TICKS(15000)); + pdMS_TO_TICKS(runtime_policy_wifi_connect_timeout_ms())); if (bits & WIFI_CONNECTED_BIT) { ESP_LOGI(TAG, "connected to AP SSID:%s", CONFIG_TQ_WIFI_SSID); @@ -123,35 +143,157 @@ static esp_err_t start_sta_mode(void) { } esp_err_t wifi_manager_start(void) { - s_wifi_event_group = xEventGroupCreate(); - if (s_wifi_event_group == NULL) { - return ESP_ERR_NO_MEM; + if (s_started) { + return s_ready ? ESP_OK : ESP_ERR_INVALID_STATE; } - ESP_ERROR_CHECK(esp_netif_init()); - ESP_ERROR_CHECK(esp_event_loop_create_default()); + s_retry_num = 0; + s_ready = false; - s_sta_netif = esp_netif_create_default_wifi_sta(); + if (s_wifi_event_group == NULL) { + s_wifi_event_group = xEventGroupCreate(); + if (s_wifi_event_group == NULL) { + return ESP_ERR_NO_MEM; + } + } + xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT); + + esp_err_t err = esp_netif_init(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + runtime_diag_record_error("wifi_start", err, "esp_netif_init failed"); + return err; + } + + err = esp_event_loop_create_default(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + runtime_diag_record_error("wifi_start", err, "event loop init failed"); + return err; + } + + if (s_sta_netif == NULL) { + s_sta_netif = esp_netif_create_default_wifi_sta(); + if (s_sta_netif == NULL) { + runtime_diag_record_error("wifi_start", ESP_ERR_NO_MEM, "create sta netif failed"); + return ESP_ERR_NO_MEM; + } + } wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + err = esp_wifi_init(&cfg); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + runtime_diag_record_error("wifi_start", err, "esp_wifi_init failed"); + return err; + } - ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, - ESP_EVENT_ANY_ID, - &wifi_event_handler, - NULL)); - ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, - IP_EVENT_STA_GOT_IP, - &wifi_event_handler, - NULL)); + if (s_wifi_event_inst == NULL) { + err = esp_event_handler_instance_register(WIFI_EVENT, + ESP_EVENT_ANY_ID, + &wifi_event_handler, + NULL, + &s_wifi_event_inst); + if (err != ESP_OK) { + runtime_diag_record_error("wifi_start", err, "register WIFI event failed"); + return err; + } + } - esp_err_t err = start_sta_mode(); - if (err != ESP_OK) { - ESP_LOGE(TAG, "STA-only mode failed to connect (%s)", esp_err_to_name(err)); + if (s_ip_event_inst == NULL) { + err = esp_event_handler_instance_register(IP_EVENT, + IP_EVENT_STA_GOT_IP, + &wifi_event_handler, + NULL, + &s_ip_event_inst); + if (err != ESP_OK) { + runtime_diag_record_error("wifi_start", err, "register IP event failed"); + return err; + } + } + + s_started = true; + err = start_sta_mode(); + if (err == ESP_OK) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_WIFI_CONNECT_SUCCESS, 1); + } else { + if (err == ESP_ERR_TIMEOUT) { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_WIFI_CONNECT_TIMEOUT, 1); + } else { + runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_WIFI_CONNECT_FAILED, 1); + } + runtime_diag_record_error("wifi_connect", err, "STA connect failed"); } return err; } +esp_err_t wifi_manager_stop(void) { + if (!s_started) { + return ESP_OK; + } + + esp_err_t first_err = ESP_OK; + + s_ready = false; + s_retry_num = 0; + + if (s_wifi_event_inst != NULL) { + esp_err_t err = esp_event_handler_instance_unregister(WIFI_EVENT, + ESP_EVENT_ANY_ID, + s_wifi_event_inst); + if (err != ESP_OK && first_err == ESP_OK) { + first_err = err; + } + s_wifi_event_inst = NULL; + } + + if (s_ip_event_inst != NULL) { + esp_err_t err = esp_event_handler_instance_unregister(IP_EVENT, + IP_EVENT_STA_GOT_IP, + s_ip_event_inst); + if (err != ESP_OK && first_err == ESP_OK) { + first_err = err; + } + s_ip_event_inst = NULL; + } + + esp_err_t err = esp_wifi_disconnect(); + if (err != ESP_OK && err != ESP_ERR_WIFI_NOT_STARTED && err != ESP_ERR_WIFI_CONN) { + if (first_err == ESP_OK) { + first_err = err; + } + } + + err = esp_wifi_stop(); + if (err != ESP_OK && err != ESP_ERR_WIFI_NOT_INIT && err != ESP_ERR_WIFI_NOT_STARTED) { + if (first_err == ESP_OK) { + first_err = err; + } + } + + err = esp_wifi_deinit(); + if (err != ESP_OK && err != ESP_ERR_WIFI_NOT_INIT) { + if (first_err == ESP_OK) { + first_err = err; + } + } + + if (s_sta_netif != NULL) { + esp_netif_destroy_default_wifi(s_sta_netif); + s_sta_netif = NULL; + } + + if (s_wifi_event_group != NULL) { + vEventGroupDelete(s_wifi_event_group); + s_wifi_event_group = NULL; + } + + s_started = false; + + if (first_err != ESP_OK) { + runtime_diag_record_error("wifi_stop", first_err, "wifi stop failed"); + } + + return first_err; +} + bool wifi_manager_is_ready(void) { return s_ready; }