#include "domain.h" #include "printer_protocol_internal.h" #include #include #include #include "esp_log.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" static const char *TAG = "printer_protocol"; int printer_protocol_find_job_idx_locked(uint32_t id) { for (int i = 0; i < JOB_SLOT_MAX; ++i) { if (s_jobs[i].used && s_jobs[i].id == id) { return i; } } return -1; } int printer_protocol_alloc_job_slot_locked(void) { for (int i = 0; i < JOB_SLOT_MAX; ++i) { if (!s_jobs[i].used) { return i; } } /* Reuse completed slot when table is full. */ for (int i = 0; i < JOB_SLOT_MAX; ++i) { if (s_jobs[i].state == PRINT_JOB_STATE_SUCCESS || s_jobs[i].state == PRINT_JOB_STATE_FAILED || s_jobs[i].state == PRINT_JOB_STATE_CANCELED) { free(s_jobs[i].data); memset(&s_jobs[i], 0, sizeof(s_jobs[i])); return i; } } return -1; } bool printer_protocol_is_terminal_state(print_job_state_t state) { return state == PRINT_JOB_STATE_SUCCESS || state == PRINT_JOB_STATE_FAILED || state == PRINT_JOB_STATE_CANCELED; } esp_err_t printer_protocol_submit_raster_job_ex(const uint8_t *raster, size_t raster_len, uint16_t width, uint16_t height, const char *density, const printer_print_options_t *options, uint32_t *out_job_id, char *err, size_t err_len) { if (raster == NULL || raster_len == 0 || width == 0 || height == 0) { if (err != NULL && err_len > 0) { snprintf(err, err_len, "invalid args"); } 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"); } return ESP_ERR_INVALID_ARG; } if (raster_len > MAX_RASTER_BYTES) { if (err != NULL && err_len > 0) { snprintf(err, err_len, "raster too large"); } return ESP_ERR_INVALID_SIZE; } size_t expected_len = ((size_t)width / 8u) * (size_t)height; if (expected_len != raster_len) { if (err != NULL && err_len > 0) { snprintf(err, err_len, "size mismatch exp=%u got=%u", (unsigned)expected_len, (unsigned)raster_len); } return ESP_ERR_INVALID_SIZE; } 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"); } return ESP_ERR_TIMEOUT; } int idx = printer_protocol_alloc_job_slot_locked(); if (idx < 0) { xSemaphoreGive(s_mutex); if (err != NULL && err_len > 0) { snprintf(err, err_len, "job table full"); } return ESP_ERR_NO_MEM; } uint8_t *copy = (uint8_t *)malloc(raster_len); if (copy == NULL) { xSemaphoreGive(s_mutex); if (err != NULL && err_len > 0) { snprintf(err, err_len, "malloc failed"); } return ESP_ERR_NO_MEM; } memcpy(copy, raster, raster_len); uint32_t id = s_next_job_id++; if (s_next_job_id == 0) { s_next_job_id = 1; } memset(&s_jobs[idx], 0, sizeof(s_jobs[idx])); s_jobs[idx].used = true; s_jobs[idx].id = id; s_jobs[idx].state = PRINT_JOB_STATE_QUEUED; s_jobs[idx].progress = 0; s_jobs[idx].width = width; s_jobs[idx].height = height; s_jobs[idx].data_len = raster_len; s_jobs[idx].ignore_precheck = (options != NULL && options->ignore_precheck); s_jobs[idx].created_ms = esp_timer_get_time() / 1000; s_jobs[idx].data = copy; strlcpy(s_jobs[idx].density, density != NULL ? density : "中等", sizeof(s_jobs[idx].density)); bool ignore_precheck = s_jobs[idx].ignore_precheck; xSemaphoreGive(s_mutex); if (xQueueSend(s_job_queue, &id, pdMS_TO_TICKS(500)) != pdTRUE) { if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) == pdTRUE) { int rollback_idx = printer_protocol_find_job_idx_locked(id); if (rollback_idx >= 0) { free(s_jobs[rollback_idx].data); memset(&s_jobs[rollback_idx], 0, sizeof(s_jobs[rollback_idx])); } xSemaphoreGive(s_mutex); } if (err != NULL && err_len > 0) { snprintf(err, err_len, "job queue full"); } ESP_LOGW(TAG, "job enqueue failed, job_id=%u, reason=%s", (unsigned)id, (err != NULL && err[0] != '\0') ? err : "job queue full"); return ESP_ERR_TIMEOUT; } if (out_job_id != NULL) { *out_job_id = id; } UBaseType_t queue_depth = uxQueueMessagesWaiting(s_job_queue); ESP_LOGI(TAG, "job queued, job_id=%u, raster_bytes=%u, size=%ux%u, density=%s, ignore_precheck=%d, queue_depth=%u", (unsigned)id, (unsigned)raster_len, (unsigned)width, (unsigned)height, density != NULL ? density : "中等", ignore_precheck, (unsigned)queue_depth); return ESP_OK; } esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster, size_t raster_len, uint16_t width, uint16_t height, const char *density, uint32_t *out_job_id, char *err, size_t err_len) { return printer_protocol_submit_raster_job_ex(raster, raster_len, width, height, density, NULL, out_job_id, err, err_len); } bool printer_protocol_get_job(uint32_t job_id, print_job_info_t *out_info) { if (out_info == NULL || job_id == 0) { return false; } if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) != pdTRUE) { return false; } int idx = printer_protocol_find_job_idx_locked(job_id); if (idx < 0) { xSemaphoreGive(s_mutex); return false; } memset(out_info, 0, sizeof(*out_info)); out_info->id = s_jobs[idx].id; out_info->state = s_jobs[idx].state; out_info->progress = s_jobs[idx].progress; out_info->width = s_jobs[idx].width; out_info->height = s_jobs[idx].height; out_info->data_len = s_jobs[idx].data_len; out_info->created_ms = s_jobs[idx].created_ms; out_info->started_ms = s_jobs[idx].started_ms; out_info->finished_ms = s_jobs[idx].finished_ms; strlcpy(out_info->density, s_jobs[idx].density, sizeof(out_info->density)); strlcpy(out_info->error, s_jobs[idx].error, sizeof(out_info->error)); xSemaphoreGive(s_mutex); return true; } esp_err_t printer_protocol_list_jobs(print_job_info_t *out_jobs, size_t max_jobs, size_t *out_count) { if (out_count == NULL) { return ESP_ERR_INVALID_ARG; } if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) != pdTRUE) { return ESP_ERR_TIMEOUT; } size_t count = 0; for (int i = 0; i < JOB_SLOT_MAX; ++i) { if (!s_jobs[i].used || s_jobs[i].state == PRINT_JOB_STATE_NONE) { continue; } if (out_jobs != NULL && count < max_jobs) { memset(&out_jobs[count], 0, sizeof(out_jobs[count])); out_jobs[count].id = s_jobs[i].id; out_jobs[count].state = s_jobs[i].state; out_jobs[count].progress = s_jobs[i].progress; out_jobs[count].width = s_jobs[i].width; out_jobs[count].height = s_jobs[i].height; out_jobs[count].data_len = s_jobs[i].data_len; out_jobs[count].created_ms = s_jobs[i].created_ms; out_jobs[count].started_ms = s_jobs[i].started_ms; out_jobs[count].finished_ms = s_jobs[i].finished_ms; strlcpy(out_jobs[count].density, s_jobs[i].density, sizeof(out_jobs[count].density)); strlcpy(out_jobs[count].error, s_jobs[i].error, sizeof(out_jobs[count].error)); } ++count; } xSemaphoreGive(s_mutex); *out_count = count; return ESP_OK; } esp_err_t printer_protocol_cancel_job(uint32_t job_id, char *err, size_t err_len) { bool canceled_finalized = false; if (job_id == 0) { if (err != NULL && err_len > 0) { snprintf(err, err_len, "invalid job id"); } return ESP_ERR_INVALID_ARG; } if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) != pdTRUE) { if (err != NULL && err_len > 0) { snprintf(err, err_len, "lock timeout"); } return ESP_ERR_TIMEOUT; } int idx = printer_protocol_find_job_idx_locked(job_id); if (idx < 0) { xSemaphoreGive(s_mutex); if (err != NULL && err_len > 0) { snprintf(err, err_len, "job not found"); } return ESP_ERR_NOT_FOUND; } if (printer_protocol_is_terminal_state(s_jobs[idx].state)) { xSemaphoreGive(s_mutex); if (err != NULL && err_len > 0) { snprintf(err, err_len, "job already finished"); } return ESP_ERR_INVALID_STATE; } s_jobs[idx].cancel_requested = true; if (s_jobs[idx].state == PRINT_JOB_STATE_QUEUED) { s_jobs[idx].state = PRINT_JOB_STATE_CANCELED; s_jobs[idx].finished_ms = esp_timer_get_time() / 1000; s_jobs[idx].progress = 0; strlcpy(s_jobs[idx].error, "job canceled", sizeof(s_jobs[idx].error)); free(s_jobs[idx].data); s_jobs[idx].data = NULL; canceled_finalized = true; } else if (s_jobs[idx].state == PRINT_JOB_STATE_RUNNING) { strlcpy(s_jobs[idx].error, "cancel requested", sizeof(s_jobs[idx].error)); } xSemaphoreGive(s_mutex); if (canceled_finalized) { } return ESP_OK; } size_t printer_protocol_cleanup_jobs(bool include_success, bool include_failed, bool include_canceled) { if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) != pdTRUE) { return 0; } size_t removed = 0; for (int i = 0; i < JOB_SLOT_MAX; ++i) { if (!s_jobs[i].used) { continue; } bool match = false; if (include_success && s_jobs[i].state == PRINT_JOB_STATE_SUCCESS) { match = true; } if (include_failed && s_jobs[i].state == PRINT_JOB_STATE_FAILED) { match = true; } if (include_canceled && s_jobs[i].state == PRINT_JOB_STATE_CANCELED) { match = true; } if (!match) { continue; } free(s_jobs[i].data); memset(&s_jobs[i], 0, sizeof(s_jobs[i])); ++removed; } xSemaphoreGive(s_mutex); return removed; }