feat(architecture): enforce lifecycle orchestration and layered component boundaries

This commit is contained in:
admin
2026-02-26 10:18:19 +08:00
parent 64c2c7d8c2
commit cf1fecb004
32 changed files with 1701 additions and 180 deletions

View File

@@ -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,

View File

@@ -0,0 +1,61 @@
#pragma once
#include <stdint.h>
#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

View File

@@ -0,0 +1,30 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -2,12 +2,15 @@
#include "printer_protocol_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

View File

@@ -5,6 +5,7 @@
#include <string.h>
#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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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();