359 lines
12 KiB
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;
|
|
}
|