Files
AI_Printer/components/domain/src/printer_protocol_jobs.c

359 lines
12 KiB
C

#include "domain.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_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;
}