#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"; static uint16_t density_to_hot_time(const char *density) { if (density == NULL) { return 2000; } if (strcmp(density, "较淡") == 0) { return 1000; } if (strcmp(density, "中等") == 0) { return 1500; } if (strcmp(density, "较浓") == 0) { return 2000; } if (strcmp(density, "最深") == 0) { return 3000; } return 2000; } static bool precheck_printer_ready(bool ignore_precheck, char *err, size_t err_len) { if (ignore_precheck) { return true; } platform_printer_sensors_t sensors = {0}; if (platform_printer_get_sensors(&sensors) != ESP_OK) { snprintf(err, err_len, "sensor read failed"); return false; } if (!sensors.has_paper) { snprintf(err, err_len, "printer out of paper"); return false; } float temp_min = runtime_policy_printer_temp_min_c(); float temp_max = runtime_policy_printer_temp_max_c(); if (temp_max <= temp_min) { temp_max = temp_min + 1.0f; } if (sensors.temperature_c < temp_min || sensors.temperature_c > temp_max) { snprintf(err, err_len, "temperature out of range"); return false; } if (sensors.battery_percent < runtime_policy_printer_battery_min_percent()) { snprintf(err, err_len, "battery too low"); return false; } return true; } static bool run_print_job_printer_driver(job_slot_t *job) { char check_err[96]; if (!precheck_printer_ready(job->ignore_precheck, check_err, sizeof(check_err))) { snprintf(job->error, sizeof(job->error), "%s", check_err); return false; } platform_printer_print_request_t req = { .width = job->width, .height = job->height, .raster = job->data, .raster_len = job->data_len, .strobe_on_time_us = density_to_hot_time(job->density), .strobe_interval_us = runtime_policy_printer_strobe_interval_us(), .motor_step_delay_us = runtime_policy_printer_motor_step_us(), .motor_steps_per_line = runtime_policy_printer_steps_per_line(), .timeout_ms = runtime_policy_printer_operation_timeout_ms(), .ignore_precheck = job->ignore_precheck, .cancel_flag = &job->cancel_requested, }; if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) { job->progress = 10; xSemaphoreGive(s_mutex); } char print_err[96] = {0}; esp_err_t rc = platform_printer_print(&req, print_err, sizeof(print_err)); if (rc != ESP_OK) { if (print_err[0] != 0) { snprintf(job->error, sizeof(job->error), "%s", print_err); } else if (rc == ESP_ERR_TIMEOUT) { snprintf(job->error, sizeof(job->error), "print timeout"); } else if (rc == ESP_ERR_INVALID_STATE && job->cancel_requested) { snprintf(job->error, sizeof(job->error), "job canceled"); } else { snprintf(job->error, sizeof(job->error), "printer print failed"); } return false; } if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) { job->progress = 95; xSemaphoreGive(s_mutex); } return true; } static bool run_print_job(job_slot_t *job) { ESP_LOGI(TAG, "job %u print start, raster_bytes=%u, size=%ux%u, density=%s, ignore_precheck=%d", (unsigned)job->id, (unsigned)job->data_len, (unsigned)job->width, (unsigned)job->height, job->density, job->ignore_precheck); if (printer_protocol_is_stopping()) { snprintf(job->error, sizeof(job->error), "protocol stopping"); return false; } return run_print_job_printer_driver(job); } void printer_protocol_worker_task(void *arg) { (void)arg; while (true) { 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; } int idx = printer_protocol_find_job_idx_locked(job_id); if (idx < 0) { xSemaphoreGive(s_mutex); continue; } if (s_jobs[idx].state == PRINT_JOB_STATE_CANCELED) { free(s_jobs[idx].data); s_jobs[idx].data = NULL; if (s_jobs[idx].finished_ms == 0) { s_jobs[idx].finished_ms = esp_timer_get_time() / 1000; } xSemaphoreGive(s_mutex); continue; } if (s_busy_refcnt != 0) { xSemaphoreGive(s_mutex); (void)xQueueSendToFront(s_job_queue, &job_id, 0); vTaskDelay(pdMS_TO_TICKS(runtime_policy_printer_queue_retry_delay_ms())); continue; } job_slot_t *job = &s_jobs[idx]; job->state = PRINT_JOB_STATE_RUNNING; job->started_ms = esp_timer_get_time() / 1000; job->progress = 1; s_busy_refcnt = 1; int64_t queue_wait_ms = 0; if (job->started_ms >= job->created_ms) { queue_wait_ms = job->started_ms - job->created_ms; } ESP_LOGI(TAG, "job %u running, queue_wait_ms=%lld, raster_bytes=%u, size=%ux%u, density=%s", (unsigned)job->id, (long long)queue_wait_ms, (unsigned)job->data_len, (unsigned)job->width, (unsigned)job->height, job->density); xSemaphoreGive(s_mutex); bool ok = run_print_job(job); if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) { continue; } if (s_busy_refcnt > 0) { --s_busy_refcnt; } s_jobs[idx].finished_ms = esp_timer_get_time() / 1000; if (s_jobs[idx].state != PRINT_JOB_STATE_CANCELED) { s_jobs[idx].progress = 100; } if (ok) { s_jobs[idx].state = PRINT_JOB_STATE_SUCCESS; s_jobs[idx].error[0] = 0; 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; if (s_jobs[idx].finished_ms >= s_jobs[idx].started_ms) { duration_ms = s_jobs[idx].finished_ms - s_jobs[idx].started_ms; } if (s_jobs[idx].state == PRINT_JOB_STATE_SUCCESS) { ESP_LOGI(TAG, "job %u done, state=%s, duration_ms=%lld", (unsigned)s_jobs[idx].id, printer_protocol_job_state_str(s_jobs[idx].state), (long long)duration_ms); } else { ESP_LOGW(TAG, "job %u done, state=%s, duration_ms=%lld, error=%s", (unsigned)s_jobs[idx].id, printer_protocol_job_state_str(s_jobs[idx].state), (long long)duration_ms, s_jobs[idx].error[0] != 0 ? s_jobs[idx].error : "-"); } 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); }