refactor(control_plane,domain): split oversized print and protocol modules

This commit is contained in:
admin
2026-02-25 13:46:44 +08:00
parent 63c4078d05
commit 7965818d13
9 changed files with 1886 additions and 1782 deletions

View File

@@ -4,8 +4,20 @@
#include <stddef.h>
#include <stdint.h>
#include "printer_protocol.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#define PROTO_ADDR 0x01
#define CMD_POWER 0x00
#define CMD_GET_STATUS 0x01
#define CMD_SET_DISTANCE 0x02
#define CMD_SET_PARAM 0x03
#define CMD_SEND_DATA 0x04
#define CMD_GAP_MOVE 0x05
#define CMD_GET_OFFSET 0x06
#define CMD_SET_OFFSET 0x07
@@ -15,8 +27,49 @@
#define CMD_BOOT_JUMP_APP 0xA3
#define CMD_BOOT_GET_VERSION 0xA4
#define EVT_ACK BIT0
#define JOB_QUEUE_LEN 8
#define JOB_SLOT_MAX 16
#define MAX_RASTER_BYTES (384 * 3000 / 8)
#define OTA_MAX_DATA_PER_FRAME 236u
typedef struct {
bool used;
bool cancel_requested;
uint32_t id;
print_job_state_t state;
uint8_t progress;
uint16_t width;
uint16_t height;
size_t data_len;
char density[16];
char error[96];
int64_t created_ms;
int64_t started_ms;
int64_t finished_ms;
uint8_t *data;
} job_slot_t;
typedef struct {
bool has_paper;
uint8_t battery;
float temperature;
int64_t updated_ms;
} parsed_status_t;
extern SemaphoreHandle_t s_mutex;
extern QueueHandle_t s_job_queue;
extern EventGroupHandle_t s_evt;
extern uint32_t s_busy_refcnt;
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_next_job_id;
extern job_slot_t s_jobs[JOB_SLOT_MAX];
bool printer_protocol_acquire_control_lane(uint32_t timeout_ms, char *err, size_t err_len);
void printer_protocol_release_control_lane(void);
esp_err_t printer_protocol_send_cmd_wait_response(uint8_t cmd,
@@ -30,3 +83,23 @@ esp_err_t printer_protocol_send_cmd_wait_response(uint8_t cmd,
uint16_t *out_payload_len,
char *err,
size_t err_len);
esp_err_t printer_protocol_send_frame(uint8_t cmd,
const uint8_t *payload,
uint16_t payload_len,
bool with_checksum);
bool printer_protocol_wait_response(uint8_t cmd,
uint8_t *out_payload,
size_t out_payload_cap,
uint16_t *out_payload_len,
uint32_t timeout_ms);
bool printer_protocol_send_cmd_with_ack(uint8_t cmd,
const uint8_t *payload,
uint16_t payload_len,
bool with_checksum,
uint32_t timeout_ms);
int printer_protocol_find_job_idx_locked(uint32_t id);
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);

View File

@@ -1,115 +1,37 @@
#include "printer_protocol.h"
#include "printer_protocol_internal.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ble_printer_client.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#define PROTO_ADDR 0x01
SemaphoreHandle_t s_mutex;
QueueHandle_t s_job_queue;
EventGroupHandle_t s_evt;
#define CMD_POWER 0x00
#define CMD_GET_STATUS 0x01
#define CMD_SET_DISTANCE 0x02
#define CMD_SET_PARAM 0x03
#define CMD_SEND_DATA 0x04
uint32_t s_busy_refcnt;
#define EVT_ACK BIT0
#define JOB_QUEUE_LEN 8
#define JOB_SLOT_MAX 16
#define MAX_RASTER_BYTES (384 * 3000 / 8)
typedef struct {
bool used;
bool cancel_requested;
uint32_t id;
print_job_state_t state;
uint8_t progress;
uint16_t width;
uint16_t height;
size_t data_len;
char density[16];
char error[96];
int64_t created_ms;
int64_t started_ms;
int64_t finished_ms;
uint8_t *data;
} job_slot_t;
typedef struct {
bool has_paper;
uint8_t battery;
float temperature;
int64_t updated_ms;
} parsed_status_t;
static SemaphoreHandle_t s_mutex;
static QueueHandle_t s_job_queue;
static EventGroupHandle_t s_evt;
static uint32_t s_busy_refcnt;
static parsed_status_t s_status = {
parsed_status_t s_status = {
.has_paper = true,
.battery = 100,
.temperature = 25.0f,
.updated_ms = 0,
};
static uint8_t s_last_rsp_cmd;
static uint16_t s_last_rsp_payload_len;
static uint8_t s_last_rsp_payload[252];
uint8_t s_last_rsp_cmd;
uint16_t s_last_rsp_payload_len;
uint8_t s_last_rsp_payload[252];
uint32_t s_next_job_id = 1;
job_slot_t s_jobs[JOB_SLOT_MAX];
static uint32_t s_next_job_id = 1;
static job_slot_t s_jobs[JOB_SLOT_MAX];
static const char *TAG = "printer_protocol";
static int 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;
}
static int 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;
}
static bool is_terminal_state(print_job_state_t state) {
return state == PRINT_JOB_STATE_SUCCESS ||
state == PRINT_JOB_STATE_FAILED ||
state == PRINT_JOB_STATE_CANCELED;
}
bool printer_protocol_acquire_control_lane(uint32_t timeout_ms, char *err, size_t err_len) {
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
if (err != NULL && err_len > 0) {
@@ -150,7 +72,10 @@ static uint8_t checksum8(const uint8_t *data, size_t len) {
return (uint8_t)(sum & 0xFF);
}
static esp_err_t send_frame(uint8_t cmd, const uint8_t *payload, uint16_t payload_len, bool with_checksum) {
esp_err_t printer_protocol_send_frame(uint8_t cmd,
const uint8_t *payload,
uint16_t payload_len,
bool with_checksum) {
uint8_t frame[260];
size_t total = 4 + payload_len + (with_checksum ? 1 : 0);
if (total > sizeof(frame)) {
@@ -177,11 +102,11 @@ static void clear_ack_signal(void) {
xEventGroupClearBits(s_evt, EVT_ACK);
}
static bool wait_response(uint8_t cmd,
uint8_t *out_payload,
size_t out_payload_cap,
uint16_t *out_payload_len,
uint32_t timeout_ms) {
bool printer_protocol_wait_response(uint8_t cmd,
uint8_t *out_payload,
size_t out_payload_cap,
uint16_t *out_payload_len,
uint32_t timeout_ms) {
int64_t deadline = esp_timer_get_time() / 1000 + timeout_ms;
while (true) {
@@ -234,19 +159,19 @@ static bool wait_response(uint8_t cmd,
static bool wait_ack(uint8_t cmd, uint32_t timeout_ms) {
uint8_t payload[252];
uint16_t payload_len = 0;
if (!wait_response(cmd, payload, sizeof(payload), &payload_len, timeout_ms)) {
if (!printer_protocol_wait_response(cmd, payload, sizeof(payload), &payload_len, timeout_ms)) {
return false;
}
return payload_len >= 1 && payload[0] == 0x01;
}
static bool send_cmd_with_ack(uint8_t cmd,
const uint8_t *payload,
uint16_t payload_len,
bool with_checksum,
uint32_t timeout_ms) {
bool printer_protocol_send_cmd_with_ack(uint8_t cmd,
const uint8_t *payload,
uint16_t payload_len,
bool with_checksum,
uint32_t timeout_ms) {
clear_ack_signal();
if (send_frame(cmd, payload, payload_len, with_checksum) != ESP_OK) {
if (printer_protocol_send_frame(cmd, payload, payload_len, with_checksum) != ESP_OK) {
return false;
}
return wait_ack(cmd, timeout_ms);
@@ -264,7 +189,7 @@ esp_err_t printer_protocol_send_cmd_wait_response(uint8_t cmd,
char *err,
size_t err_len) {
clear_ack_signal();
if (send_frame(cmd, payload, payload_len, with_checksum) != ESP_OK) {
if (printer_protocol_send_frame(cmd, payload, payload_len, with_checksum) != ESP_OK) {
if (err != NULL && err_len > 0) {
snprintf(err, err_len, "send frame failed");
}
@@ -273,7 +198,7 @@ esp_err_t printer_protocol_send_cmd_wait_response(uint8_t cmd,
uint8_t rsp[252];
uint16_t rsp_len = 0;
if (!wait_response(cmd, rsp, sizeof(rsp), &rsp_len, timeout_ms)) {
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");
}
@@ -301,310 +226,6 @@ esp_err_t printer_protocol_send_cmd_wait_response(uint8_t cmd,
return ESP_OK;
}
static bool request_status_sync(uint32_t timeout_ms) {
int64_t old_ms;
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) {
return false;
}
old_ms = s_status.updated_ms;
xSemaphoreGive(s_mutex);
uint8_t payload = 0x00;
if (send_frame(CMD_GET_STATUS, &payload, 0, true) != ESP_OK) {
return false;
}
int64_t deadline = esp_timer_get_time() / 1000 + timeout_ms;
while ((esp_timer_get_time() / 1000) < deadline) {
vTaskDelay(pdMS_TO_TICKS(20));
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) != pdTRUE) {
continue;
}
bool updated = s_status.updated_ms > old_ms;
xSemaphoreGive(s_mutex);
if (updated) {
return true;
}
}
return false;
}
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(char *err, size_t err_len) {
if (!request_status_sync(1200)) {
snprintf(err, err_len, "status timeout");
return false;
}
parsed_status_t status;
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) {
snprintf(err, err_len, "status lock failed");
return false;
}
status = s_status;
xSemaphoreGive(s_mutex);
if (!status.has_paper) {
snprintf(err, err_len, "printer out of paper");
return false;
}
if (status.battery <= 40) {
snprintf(err, err_len, "battery too low");
return false;
}
if (status.temperature >= 60.0f) {
snprintf(err, err_len, "temperature too high");
return false;
}
return true;
}
static bool run_print_job(job_slot_t *job) {
char err[96];
err[0] = '\0';
uint8_t next_progress_log = 25;
ESP_LOGI(TAG,
"job %u print start, raster_bytes=%u, size=%ux%u, density=%s",
(unsigned)job->id,
(unsigned)job->data_len,
(unsigned)job->width,
(unsigned)job->height,
job->density);
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);
return false;
}
if (!precheck_printer_ready(err, sizeof(err))) {
snprintf(job->error, sizeof(job->error), "%s", err);
ESP_LOGW(TAG, "job %u print aborted: %s", (unsigned)job->id, job->error);
return false;
}
if (job->cancel_requested) {
snprintf(job->error, sizeof(job->error), "job canceled");
ESP_LOGW(TAG, "job %u print canceled before start", (unsigned)job->id);
return false;
}
uint8_t power_on = 0x01;
if (!send_cmd_with_ack(CMD_POWER, &power_on, 1, true, 1500)) {
snprintf(job->error, sizeof(job->error), "power on failed");
ESP_LOGW(TAG, "job %u print aborted: %s", (unsigned)job->id, job->error);
return false;
}
uint16_t hot_time = density_to_hot_time(job->density);
uint8_t param[4] = {
0x01,
0x02,
(uint8_t)((hot_time >> 8) & 0xFF),
(uint8_t)(hot_time & 0xFF),
};
if (!send_cmd_with_ack(CMD_SET_PARAM, param, sizeof(param), true, 1500)) {
snprintf(job->error, sizeof(job->error), "set print param failed");
ESP_LOGW(TAG, "job %u print aborted: %s", (unsigned)job->id, job->error);
return false;
}
const size_t chunk_max = 240;
size_t total_chunks = (size_t)ceil((double)job->data_len / (double)chunk_max);
ESP_LOGI(TAG,
"job %u data transfer start, chunks=%u, chunk_bytes=%u, hot_time=%u",
(unsigned)job->id,
(unsigned)total_chunks,
(unsigned)chunk_max,
(unsigned)hot_time);
for (size_t i = 0; i < total_chunks; ++i) {
if (job->cancel_requested) {
snprintf(job->error, sizeof(job->error), "job canceled");
ESP_LOGW(TAG, "job %u print canceled at chunk %u/%u", (unsigned)job->id, (unsigned)(i + 1), (unsigned)total_chunks);
return false;
}
size_t start = i * chunk_max;
size_t remain = job->data_len - start;
size_t chunk_len = remain > chunk_max ? chunk_max : remain;
if (!send_cmd_with_ack(CMD_SEND_DATA,
&job->data[start],
(uint16_t)chunk_len,
false,
2500)) {
snprintf(job->error, sizeof(job->error), "send chunk timeout at %u", (unsigned)i);
ESP_LOGW(TAG, "job %u print aborted: %s", (unsigned)job->id, job->error);
return false;
}
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
job->progress = (uint8_t)(((i + 1) * 90) / total_chunks);
if (job->progress < 5) {
job->progress = 5;
}
xSemaphoreGive(s_mutex);
}
while (job->progress >= next_progress_log && next_progress_log <= 90) {
ESP_LOGI(TAG,
"job %u print progress=%u%% (%u/%u chunks)",
(unsigned)job->id,
(unsigned)job->progress,
(unsigned)(i + 1),
(unsigned)total_chunks);
next_progress_log = (uint8_t)(next_progress_log + 25);
}
}
ESP_LOGI(TAG, "job %u data transfer done", (unsigned)job->id);
uint8_t power_off = 0x00;
(void)send_cmd_with_ack(CMD_POWER, &power_off, 1, true, 1200);
uint8_t feed_payload[3] = {0x2B, 0x00, 0x0C};
(void)send_cmd_with_ack(CMD_SET_DISTANCE, feed_payload, sizeof(feed_payload), true, 1200);
ESP_LOGI(TAG, "job %u print command sequence done", (unsigned)job->id);
return true;
}
static void worker_task(void *arg) {
(void)arg;
while (true) {
uint32_t job_id;
if (xQueueReceive(s_job_queue, &job_id, portMAX_DELAY) != pdTRUE) {
continue;
}
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
continue;
}
int idx = 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);
xQueueSendToFront(s_job_queue, &job_id, 0);
vTaskDelay(pdMS_TO_TICKS(20));
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';
} else if (s_jobs[idx].cancel_requested) {
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));
}
} else {
s_jobs[idx].state = PRINT_JOB_STATE_FAILED;
}
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;
xSemaphoreGive(s_mutex);
}
}
static void status_poll_task(void *arg) {
(void)arg;
@@ -617,7 +238,7 @@ static void status_poll_task(void *arg) {
if (ble_printer_client_is_connected() && can_poll) {
uint8_t dummy = 0x00;
(void)send_frame(CMD_GET_STATUS, &dummy, 0, true);
(void)printer_protocol_send_frame(CMD_GET_STATUS, &dummy, 0, true);
}
vTaskDelay(pdMS_TO_TICKS(5000));
}
@@ -693,7 +314,7 @@ esp_err_t printer_protocol_init(void) {
return err;
}
BaseType_t ok = xTaskCreate(worker_task, "print_worker", 6144, NULL, 6, NULL);
BaseType_t ok = xTaskCreate(printer_protocol_worker_task, "print_worker", 6144, NULL, 6, NULL);
if (ok != pdPASS) {
return ESP_ERR_NO_MEM;
}
@@ -703,6 +324,7 @@ esp_err_t printer_protocol_init(void) {
return ESP_ERR_NO_MEM;
}
ESP_LOGI(TAG, "printer protocol initialized");
return ESP_OK;
}
@@ -738,277 +360,3 @@ void printer_protocol_get_runtime_status(printer_runtime_status_t *out_status) {
xSemaphoreGive(s_mutex);
}
}
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) {
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 (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(1000)) != pdTRUE) {
if (err != NULL && err_len > 0) {
snprintf(err, err_len, "lock timeout");
}
return ESP_ERR_TIMEOUT;
}
int idx = 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].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));
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 = 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, queue_depth=%u",
(unsigned)id,
(unsigned)raster_len,
(unsigned)width,
(unsigned)height,
density != NULL ? density : "中等",
(unsigned)queue_depth);
return ESP_OK;
}
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 = 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) {
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 = 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 (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;
} 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);
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;
}

View File

@@ -0,0 +1,323 @@
#include "printer_protocol.h"
#include "printer_protocol_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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(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) {
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 (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(1000)) != 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].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));
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, queue_depth=%u",
(unsigned)id,
(unsigned)raster_len,
(unsigned)width,
(unsigned)height,
density != NULL ? density : "中等",
(unsigned)queue_depth);
return ESP_OK;
}
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) {
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;
} 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);
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;
}

View File

@@ -0,0 +1,322 @@
#include "printer_protocol.h"
#include "printer_protocol_internal.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "ble_printer_client.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "printer_protocol";
static bool request_status_sync(uint32_t timeout_ms) {
int64_t old_ms;
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) {
return false;
}
old_ms = s_status.updated_ms;
xSemaphoreGive(s_mutex);
uint8_t payload = 0x00;
if (printer_protocol_send_frame(CMD_GET_STATUS, &payload, 0, true) != ESP_OK) {
return false;
}
int64_t deadline = esp_timer_get_time() / 1000 + timeout_ms;
while ((esp_timer_get_time() / 1000) < deadline) {
vTaskDelay(pdMS_TO_TICKS(20));
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) != pdTRUE) {
continue;
}
bool updated = s_status.updated_ms > old_ms;
xSemaphoreGive(s_mutex);
if (updated) {
return true;
}
}
return false;
}
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(char *err, size_t err_len) {
if (!request_status_sync(1200)) {
snprintf(err, err_len, "status timeout");
return false;
}
parsed_status_t status;
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) {
snprintf(err, err_len, "status lock failed");
return false;
}
status = s_status;
xSemaphoreGive(s_mutex);
if (!status.has_paper) {
snprintf(err, err_len, "printer out of paper");
return false;
}
if (status.battery <= 40) {
snprintf(err, err_len, "battery too low");
return false;
}
if (status.temperature >= 60.0f) {
snprintf(err, err_len, "temperature too high");
return false;
}
return true;
}
static bool run_print_job(job_slot_t *job) {
char err[96];
err[0] = '\0';
uint8_t next_progress_log = 25;
ESP_LOGI(TAG,
"job %u print start, raster_bytes=%u, size=%ux%u, density=%s",
(unsigned)job->id,
(unsigned)job->data_len,
(unsigned)job->width,
(unsigned)job->height,
job->density);
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);
return false;
}
if (!precheck_printer_ready(err, sizeof(err))) {
snprintf(job->error, sizeof(job->error), "%s", err);
ESP_LOGW(TAG, "job %u print aborted: %s", (unsigned)job->id, job->error);
return false;
}
if (job->cancel_requested) {
snprintf(job->error, sizeof(job->error), "job canceled");
ESP_LOGW(TAG, "job %u print canceled before start", (unsigned)job->id);
return false;
}
uint8_t power_on = 0x01;
if (!printer_protocol_send_cmd_with_ack(CMD_POWER, &power_on, 1, true, 1500)) {
snprintf(job->error, sizeof(job->error), "power on failed");
ESP_LOGW(TAG, "job %u print aborted: %s", (unsigned)job->id, job->error);
return false;
}
uint16_t hot_time = density_to_hot_time(job->density);
uint8_t param[4] = {
0x01,
0x02,
(uint8_t)((hot_time >> 8) & 0xFF),
(uint8_t)(hot_time & 0xFF),
};
if (!printer_protocol_send_cmd_with_ack(CMD_SET_PARAM, param, sizeof(param), true, 1500)) {
snprintf(job->error, sizeof(job->error), "set print param failed");
ESP_LOGW(TAG, "job %u print aborted: %s", (unsigned)job->id, job->error);
return false;
}
const size_t chunk_max = 240;
size_t total_chunks = (size_t)ceil((double)job->data_len / (double)chunk_max);
ESP_LOGI(TAG,
"job %u data transfer start, chunks=%u, chunk_bytes=%u, hot_time=%u",
(unsigned)job->id,
(unsigned)total_chunks,
(unsigned)chunk_max,
(unsigned)hot_time);
for (size_t i = 0; i < total_chunks; ++i) {
if (job->cancel_requested) {
snprintf(job->error, sizeof(job->error), "job canceled");
ESP_LOGW(TAG,
"job %u print canceled at chunk %u/%u",
(unsigned)job->id,
(unsigned)(i + 1),
(unsigned)total_chunks);
return false;
}
size_t start = i * chunk_max;
size_t remain = job->data_len - start;
size_t chunk_len = remain > chunk_max ? chunk_max : remain;
if (!printer_protocol_send_cmd_with_ack(CMD_SEND_DATA,
&job->data[start],
(uint16_t)chunk_len,
false,
2500)) {
snprintf(job->error, sizeof(job->error), "send chunk timeout at %u", (unsigned)i);
ESP_LOGW(TAG, "job %u print aborted: %s", (unsigned)job->id, job->error);
return false;
}
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
job->progress = (uint8_t)(((i + 1) * 90) / total_chunks);
if (job->progress < 5) {
job->progress = 5;
}
xSemaphoreGive(s_mutex);
}
while (job->progress >= next_progress_log && next_progress_log <= 90) {
ESP_LOGI(TAG,
"job %u print progress=%u%% (%u/%u chunks)",
(unsigned)job->id,
(unsigned)job->progress,
(unsigned)(i + 1),
(unsigned)total_chunks);
next_progress_log = (uint8_t)(next_progress_log + 25);
}
}
ESP_LOGI(TAG, "job %u data transfer done", (unsigned)job->id);
uint8_t power_off = 0x00;
(void)printer_protocol_send_cmd_with_ack(CMD_POWER, &power_off, 1, true, 1200);
uint8_t feed_payload[3] = {0x2B, 0x00, 0x0C};
(void)printer_protocol_send_cmd_with_ack(CMD_SET_DISTANCE, feed_payload, sizeof(feed_payload), true, 1200);
ESP_LOGI(TAG, "job %u print command sequence done", (unsigned)job->id);
return true;
}
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) {
continue;
}
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);
xQueueSendToFront(s_job_queue, &job_id, 0);
vTaskDelay(pdMS_TO_TICKS(20));
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';
} else if (s_jobs[idx].cancel_requested) {
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));
}
} else {
s_jobs[idx].state = PRINT_JOB_STATE_FAILED;
}
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;
xSemaphoreGive(s_mutex);
}
}