- move runtime policy/diag contracts to domain-owned facade and remove control_plane leapfrog usage - enforce single declaration point for direct debug config in domain public API (constraint #6) - harden timeout/idempotent semantics in REST/voice/printer/wifi/ble paths
1007 lines
32 KiB
C
1007 lines
32 KiB
C
#include "domain.h"
|
|
#include "printer_protocol_internal.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "esp_heap_caps.h"
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
|
|
SemaphoreHandle_t s_mutex;
|
|
QueueHandle_t s_job_queue;
|
|
EventGroupHandle_t s_evt;
|
|
|
|
uint32_t s_busy_refcnt;
|
|
uint32_t s_status_poll_pause_next_token;
|
|
uint32_t s_status_poll_pause_tokens[STATUS_POLL_PAUSE_SLOT_MAX];
|
|
|
|
parsed_status_t s_status = {
|
|
.has_paper = true,
|
|
.paper_gpio_level = -1,
|
|
.paper_present_level = 0,
|
|
.battery = 100,
|
|
.temperature = 25.0f,
|
|
.updated_ms = 0,
|
|
};
|
|
|
|
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];
|
|
TaskHandle_t s_worker_task;
|
|
TaskHandle_t s_status_poll_task_handle;
|
|
bool s_protocol_initialized;
|
|
bool s_protocol_stopping;
|
|
printer_backend_t s_backend = PRINTER_BACKEND_BLE;
|
|
bool s_ble_client_initialized;
|
|
|
|
static const char *TAG = "printer_protocol";
|
|
|
|
#if CONFIG_FREERTOS_UNICORE
|
|
#define PRINT_WORKER_CORE_ID 0
|
|
#else
|
|
#define PRINT_WORKER_CORE_ID 1
|
|
#endif
|
|
|
|
static void on_rx_frame(const uint8_t *data, size_t len);
|
|
|
|
static printer_backend_t default_backend_on_startup(void) {
|
|
// This hardware has a first-class direct path; BLE remains opt-in via API.
|
|
if (runtime_policy_direct_printer_enabled()) {
|
|
return PRINTER_BACKEND_DIRECT;
|
|
}
|
|
return PRINTER_BACKEND_BLE;
|
|
}
|
|
|
|
static void backend_capabilities_for(printer_backend_t backend, printer_capabilities_t *out_caps) {
|
|
if (out_caps == NULL) {
|
|
return;
|
|
}
|
|
memset(out_caps, 0, sizeof(*out_caps));
|
|
out_caps->supports_connect = true;
|
|
out_caps->supports_gap_move = true;
|
|
if (backend == PRINTER_BACKEND_BLE) {
|
|
out_caps->supports_label_offset = true;
|
|
out_caps->supports_ota = true;
|
|
}
|
|
}
|
|
|
|
static esp_err_t ensure_ble_client_ready(void) {
|
|
if (s_ble_client_initialized) {
|
|
return ESP_OK;
|
|
}
|
|
esp_err_t err = ble_printer_client_init(on_rx_frame);
|
|
if (err == ESP_OK) {
|
|
s_ble_client_initialized = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static esp_err_t ensure_backend_ready(printer_backend_t backend) {
|
|
if (backend == PRINTER_BACKEND_BLE) {
|
|
return ensure_ble_client_ready();
|
|
}
|
|
if (backend == PRINTER_BACKEND_DIRECT) {
|
|
if (!runtime_policy_direct_printer_enabled()) {
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
return platform_direct_printer_init();
|
|
}
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
static void refresh_direct_status_locked(void) {
|
|
if (s_backend != PRINTER_BACKEND_DIRECT) {
|
|
return;
|
|
}
|
|
|
|
platform_printer_sensors_t sensors = {0};
|
|
if (platform_direct_printer_get_sensors(&sensors) == ESP_OK) {
|
|
s_status.has_paper = sensors.has_paper;
|
|
s_status.paper_gpio_level = sensors.paper_gpio_level;
|
|
s_status.paper_present_level = sensors.paper_present_level;
|
|
s_status.battery = sensors.battery_percent;
|
|
s_status.temperature = sensors.temperature_c;
|
|
s_status.updated_ms = sensors.updated_ms;
|
|
}
|
|
}
|
|
|
|
static size_t status_poll_pause_depth_locked(void) {
|
|
size_t depth = 0;
|
|
for (size_t i = 0; i < STATUS_POLL_PAUSE_SLOT_MAX; ++i) {
|
|
if (s_status_poll_pause_tokens[i] != 0) {
|
|
++depth;
|
|
}
|
|
}
|
|
return depth;
|
|
}
|
|
|
|
static void status_poll_pause_slots_clear_locked(void) {
|
|
memset(s_status_poll_pause_tokens, 0, sizeof(s_status_poll_pause_tokens));
|
|
}
|
|
|
|
static void reset_jobs_locked(void) {
|
|
for (int i = 0; i < JOB_SLOT_MAX; ++i) {
|
|
free(s_jobs[i].data);
|
|
memset(&s_jobs[i], 0, sizeof(s_jobs[i]));
|
|
}
|
|
s_next_job_id = 1;
|
|
}
|
|
|
|
static void reset_runtime_state_locked(void) {
|
|
s_busy_refcnt = 0;
|
|
s_last_rsp_cmd = 0;
|
|
s_last_rsp_payload_len = 0;
|
|
memset(s_last_rsp_payload, 0, sizeof(s_last_rsp_payload));
|
|
|
|
status_poll_pause_slots_clear_locked();
|
|
s_status_poll_pause_next_token = 1;
|
|
|
|
s_status.has_paper = true;
|
|
s_status.paper_gpio_level = -1;
|
|
s_status.paper_present_level = 0;
|
|
s_status.battery = 100;
|
|
s_status.temperature = 25.0f;
|
|
s_status.updated_ms = 0;
|
|
}
|
|
|
|
bool printer_protocol_is_stopping(void) {
|
|
if (!s_protocol_initialized) {
|
|
return false;
|
|
}
|
|
if (s_mutex == NULL) {
|
|
return true;
|
|
}
|
|
|
|
bool stopping = true;
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
|
|
stopping = s_protocol_stopping;
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
return stopping;
|
|
}
|
|
|
|
static BaseType_t printer_create_task_prefer_psram(TaskFunction_t task_fn,
|
|
const char *name,
|
|
uint32_t stack_size,
|
|
UBaseType_t priority,
|
|
TaskHandle_t *out_task) {
|
|
#if defined(CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY) && \
|
|
defined(CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) && \
|
|
(configSUPPORT_STATIC_ALLOCATION == 1)
|
|
BaseType_t rc = xTaskCreatePinnedToCoreWithCaps(task_fn,
|
|
name,
|
|
(configSTACK_DEPTH_TYPE)stack_size,
|
|
NULL,
|
|
priority,
|
|
out_task,
|
|
PRINT_WORKER_CORE_ID,
|
|
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
if (rc == pdPASS) {
|
|
return pdPASS;
|
|
}
|
|
#endif
|
|
return xTaskCreatePinnedToCore(task_fn,
|
|
name,
|
|
(configSTACK_DEPTH_TYPE)stack_size,
|
|
NULL,
|
|
priority,
|
|
out_task,
|
|
PRINT_WORKER_CORE_ID);
|
|
}
|
|
|
|
printer_status_poll_pause_token_t printer_protocol_status_poll_pause_acquire(void) {
|
|
if (!s_protocol_initialized || s_mutex == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) {
|
|
return 0;
|
|
}
|
|
|
|
if (s_protocol_stopping) {
|
|
xSemaphoreGive(s_mutex);
|
|
return 0;
|
|
}
|
|
|
|
int slot = -1;
|
|
for (int i = 0; i < STATUS_POLL_PAUSE_SLOT_MAX; ++i) {
|
|
if (s_status_poll_pause_tokens[i] == 0) {
|
|
slot = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (slot < 0) {
|
|
xSemaphoreGive(s_mutex);
|
|
ESP_LOGW(TAG, "status poll pause acquire failed: no free slot");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t token = s_status_poll_pause_next_token++;
|
|
if (token == 0) {
|
|
token = s_status_poll_pause_next_token++;
|
|
}
|
|
|
|
s_status_poll_pause_tokens[slot] = token;
|
|
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH,
|
|
(int32_t)status_poll_pause_depth_locked());
|
|
xSemaphoreGive(s_mutex);
|
|
return token;
|
|
}
|
|
|
|
void printer_protocol_status_poll_pause_release(printer_status_poll_pause_token_t *token) {
|
|
if (token == NULL || *token == 0) {
|
|
return;
|
|
}
|
|
|
|
if (!s_protocol_initialized || s_mutex == NULL) {
|
|
*token = 0;
|
|
return;
|
|
}
|
|
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < STATUS_POLL_PAUSE_SLOT_MAX; ++i) {
|
|
if (s_status_poll_pause_tokens[i] == *token) {
|
|
s_status_poll_pause_tokens[i] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH,
|
|
(int32_t)status_poll_pause_depth_locked());
|
|
xSemaphoreGive(s_mutex);
|
|
*token = 0;
|
|
}
|
|
|
|
bool printer_protocol_acquire_control_lane(uint32_t timeout_ms, char *err, size_t err_len) {
|
|
if (!s_protocol_initialized || s_mutex == NULL) {
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "printer protocol not initialized");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "lock timeout");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (s_protocol_stopping) {
|
|
xSemaphoreGive(s_mutex);
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "printer protocol stopping");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (s_busy_refcnt != 0) {
|
|
xSemaphoreGive(s_mutex);
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "printer busy");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
s_busy_refcnt = 1;
|
|
xSemaphoreGive(s_mutex);
|
|
return true;
|
|
}
|
|
|
|
void printer_protocol_release_control_lane(void) {
|
|
if (s_mutex == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
|
return;
|
|
}
|
|
|
|
if (s_busy_refcnt > 0) {
|
|
--s_busy_refcnt;
|
|
}
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
|
|
static uint8_t checksum8(const uint8_t *data, size_t len) {
|
|
uint32_t sum = 0;
|
|
for (size_t i = 0; i < len; ++i) {
|
|
sum += data[i];
|
|
}
|
|
return (uint8_t)(sum & 0xFF);
|
|
}
|
|
|
|
esp_err_t printer_protocol_send_frame(uint8_t cmd,
|
|
const uint8_t *payload,
|
|
uint16_t payload_len,
|
|
bool with_checksum) {
|
|
if (!s_protocol_initialized || printer_protocol_is_stopping()) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
if (s_backend != PRINTER_BACKEND_BLE) {
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
uint8_t frame[260];
|
|
size_t total = 4 + payload_len + (with_checksum ? 1 : 0);
|
|
if (total > sizeof(frame)) {
|
|
return ESP_ERR_INVALID_SIZE;
|
|
}
|
|
|
|
frame[0] = PROTO_ADDR;
|
|
frame[1] = cmd;
|
|
frame[2] = (uint8_t)((payload_len >> 8) & 0xFF);
|
|
frame[3] = (uint8_t)(payload_len & 0xFF);
|
|
|
|
if (payload_len > 0 && payload != NULL) {
|
|
memcpy(&frame[4], payload, payload_len);
|
|
}
|
|
|
|
if (with_checksum) {
|
|
frame[4 + payload_len] = checksum8(frame, 4 + payload_len);
|
|
}
|
|
|
|
return ble_printer_client_write(frame, total);
|
|
}
|
|
|
|
static void clear_ack_signal(void) {
|
|
if (s_evt != NULL) {
|
|
xEventGroupClearBits(s_evt, EVT_ACK);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (timeout_ms == 0 || s_evt == NULL) {
|
|
return false;
|
|
}
|
|
|
|
int64_t deadline = esp_timer_get_time() / 1000 + timeout_ms;
|
|
|
|
while (true) {
|
|
if (printer_protocol_is_stopping()) {
|
|
return false;
|
|
}
|
|
|
|
int64_t now = esp_timer_get_time() / 1000;
|
|
if (now >= deadline) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t wait_ms = (uint32_t)(deadline - now);
|
|
if (wait_ms > 100) {
|
|
wait_ms = 100;
|
|
}
|
|
|
|
EventBits_t bits = xEventGroupWaitBits(s_evt,
|
|
EVT_ACK | EVT_SHUTDOWN,
|
|
pdFALSE,
|
|
pdFALSE,
|
|
pdMS_TO_TICKS(wait_ms));
|
|
if (bits & EVT_SHUTDOWN) {
|
|
return false;
|
|
}
|
|
|
|
if (!(bits & EVT_ACK)) {
|
|
continue;
|
|
}
|
|
xEventGroupClearBits(s_evt, EVT_ACK);
|
|
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) {
|
|
continue;
|
|
}
|
|
|
|
uint8_t rsp_cmd = s_last_rsp_cmd;
|
|
uint16_t rsp_len = s_last_rsp_payload_len;
|
|
uint8_t rsp_copy[sizeof(s_last_rsp_payload)];
|
|
if (rsp_len > sizeof(rsp_copy)) {
|
|
rsp_len = sizeof(rsp_copy);
|
|
}
|
|
if (rsp_len > 0) {
|
|
memcpy(rsp_copy, s_last_rsp_payload, rsp_len);
|
|
}
|
|
xSemaphoreGive(s_mutex);
|
|
|
|
if (rsp_cmd == cmd) {
|
|
if (out_payload != NULL && out_payload_cap > 0 && rsp_len > 0) {
|
|
size_t copy_len = rsp_len;
|
|
if (copy_len > out_payload_cap) {
|
|
copy_len = out_payload_cap;
|
|
}
|
|
memcpy(out_payload, rsp_copy, copy_len);
|
|
}
|
|
if (out_payload_len != NULL) {
|
|
*out_payload_len = rsp_len;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool wait_ack(uint8_t cmd, uint32_t timeout_ms) {
|
|
uint8_t payload[252];
|
|
uint16_t payload_len = 0;
|
|
if (!printer_protocol_wait_response(cmd, payload, sizeof(payload), &payload_len, timeout_ms)) {
|
|
return false;
|
|
}
|
|
return payload_len >= 1 && payload[0] == 0x01;
|
|
}
|
|
|
|
static void reset_transport_after_timeout(void) {
|
|
if (printer_protocol_is_stopping()) {
|
|
return;
|
|
}
|
|
if (printer_protocol_get_backend() == PRINTER_BACKEND_BLE) {
|
|
ble_printer_client_disconnect();
|
|
}
|
|
}
|
|
|
|
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,
|
|
bool reset_transport_on_timeout) {
|
|
clear_ack_signal();
|
|
if (printer_protocol_send_frame(cmd, payload, payload_len, with_checksum) != ESP_OK) {
|
|
return false;
|
|
}
|
|
bool ok = wait_ack(cmd, timeout_ms);
|
|
if (!ok && reset_transport_on_timeout) {
|
|
reset_transport_after_timeout();
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
esp_err_t printer_protocol_send_cmd_wait_response(uint8_t cmd,
|
|
const uint8_t *payload,
|
|
uint16_t payload_len,
|
|
bool with_checksum,
|
|
uint32_t timeout_ms,
|
|
bool expect_ack,
|
|
bool reset_transport_on_timeout,
|
|
uint8_t *out_payload,
|
|
size_t out_payload_cap,
|
|
uint16_t *out_payload_len,
|
|
char *err,
|
|
size_t err_len) {
|
|
clear_ack_signal();
|
|
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");
|
|
}
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
uint8_t rsp[252];
|
|
uint16_t rsp_len = 0;
|
|
if (!printer_protocol_wait_response(cmd, rsp, sizeof(rsp), &rsp_len, timeout_ms)) {
|
|
bool stopping = printer_protocol_is_stopping();
|
|
if (!stopping && reset_transport_on_timeout) {
|
|
reset_transport_after_timeout();
|
|
}
|
|
if (err != NULL && err_len > 0) {
|
|
if (stopping) {
|
|
snprintf(err, err_len, "protocol stopping");
|
|
} else {
|
|
snprintf(err,
|
|
err_len,
|
|
reset_transport_on_timeout ? "response timeout, transport reset"
|
|
: "response timeout");
|
|
}
|
|
}
|
|
return stopping ? ESP_ERR_INVALID_STATE : ESP_ERR_TIMEOUT;
|
|
}
|
|
|
|
if (expect_ack && (rsp_len < 1 || rsp[0] != 0x01)) {
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "cmd 0x%02X rejected", cmd);
|
|
}
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
if (out_payload != NULL && out_payload_cap > 0 && rsp_len > 0) {
|
|
size_t copy_len = rsp_len;
|
|
if (copy_len > out_payload_cap) {
|
|
copy_len = out_payload_cap;
|
|
}
|
|
memcpy(out_payload, rsp, copy_len);
|
|
}
|
|
if (out_payload_len != NULL) {
|
|
*out_payload_len = rsp_len;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void status_poll_task(void *arg) {
|
|
(void)arg;
|
|
|
|
while (true) {
|
|
bool should_stop = false;
|
|
bool can_poll = false;
|
|
printer_backend_t backend = PRINTER_BACKEND_BLE;
|
|
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
|
|
should_stop = s_protocol_stopping;
|
|
can_poll = (s_busy_refcnt == 0) && (status_poll_pause_depth_locked() == 0);
|
|
backend = s_backend;
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
|
|
if (should_stop) {
|
|
break;
|
|
}
|
|
|
|
if (can_poll) {
|
|
if (backend == PRINTER_BACKEND_BLE) {
|
|
if (ble_printer_client_is_connected()) {
|
|
uint8_t dummy = 0x00;
|
|
(void)printer_protocol_send_frame(CMD_GET_STATUS, &dummy, 0, true);
|
|
}
|
|
} else if (backend == PRINTER_BACKEND_DIRECT) {
|
|
if (platform_direct_printer_is_connected() &&
|
|
xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
refresh_direct_status_locked();
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
}
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(runtime_policy_printer_status_poll_interval_ms()));
|
|
}
|
|
|
|
if (s_evt != NULL) {
|
|
xEventGroupSetBits(s_evt, EVT_STATUS_POLL_EXITED);
|
|
}
|
|
s_status_poll_task_handle = NULL;
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
static void on_rx_frame(const uint8_t *data, size_t len) {
|
|
if (data == NULL || len < 4 || s_mutex == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (data[0] != PROTO_ADDR) {
|
|
return;
|
|
}
|
|
|
|
uint8_t cmd = data[1];
|
|
uint16_t payload_len = ((uint16_t)data[2] << 8) | data[3];
|
|
size_t required = (size_t)payload_len + 4;
|
|
if (len < required) {
|
|
return;
|
|
}
|
|
|
|
const uint8_t *payload = &data[4];
|
|
|
|
if (cmd == CMD_GET_STATUS && payload_len >= 5) {
|
|
bool has_paper = payload[0] != 0;
|
|
uint8_t battery = payload[1];
|
|
int sign = (payload[2] == 0x2D) ? -1 : 1;
|
|
int temp_x10 = ((int)payload[3] << 8) | payload[4];
|
|
float temperature = (float)(sign * temp_x10) / 10.0f;
|
|
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
s_status.has_paper = has_paper;
|
|
s_status.paper_gpio_level = -1;
|
|
s_status.paper_present_level = 0;
|
|
s_status.battery = battery;
|
|
s_status.temperature = temperature;
|
|
s_status.updated_ms = esp_timer_get_time() / 1000;
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (payload_len >= 1) {
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
s_last_rsp_cmd = cmd;
|
|
s_last_rsp_payload_len = payload_len;
|
|
if (s_last_rsp_payload_len > sizeof(s_last_rsp_payload)) {
|
|
s_last_rsp_payload_len = sizeof(s_last_rsp_payload);
|
|
}
|
|
memcpy(s_last_rsp_payload, payload, s_last_rsp_payload_len);
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
if (s_evt != NULL) {
|
|
xEventGroupSetBits(s_evt, EVT_ACK);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void destroy_runtime_objects(void) {
|
|
if (s_job_queue != NULL) {
|
|
vQueueDelete(s_job_queue);
|
|
s_job_queue = NULL;
|
|
}
|
|
if (s_evt != NULL) {
|
|
vEventGroupDelete(s_evt);
|
|
s_evt = NULL;
|
|
}
|
|
if (s_mutex != NULL) {
|
|
vSemaphoreDelete(s_mutex);
|
|
s_mutex = NULL;
|
|
}
|
|
}
|
|
|
|
esp_err_t printer_protocol_init(void) {
|
|
if (s_protocol_initialized) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
s_mutex = xSemaphoreCreateMutex();
|
|
if (s_mutex == NULL) {
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
s_evt = xEventGroupCreate();
|
|
if (s_evt == NULL) {
|
|
destroy_runtime_objects();
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
s_job_queue = xQueueCreate(JOB_QUEUE_LEN, sizeof(uint32_t));
|
|
if (s_job_queue == NULL) {
|
|
destroy_runtime_objects();
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
|
reset_jobs_locked();
|
|
reset_runtime_state_locked();
|
|
s_protocol_stopping = false;
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
|
|
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, 0);
|
|
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, 0);
|
|
s_protocol_initialized = true;
|
|
s_protocol_stopping = false;
|
|
s_ble_client_initialized = false;
|
|
s_backend = default_backend_on_startup();
|
|
|
|
esp_err_t err = ensure_backend_ready(s_backend);
|
|
if (err != ESP_OK) {
|
|
s_protocol_initialized = false;
|
|
destroy_runtime_objects();
|
|
return err;
|
|
}
|
|
|
|
BaseType_t ok = printer_create_task_prefer_psram(printer_protocol_worker_task,
|
|
"print_worker",
|
|
6144,
|
|
6,
|
|
&s_worker_task);
|
|
if (ok != pdPASS) {
|
|
s_protocol_initialized = false;
|
|
destroy_runtime_objects();
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
ok = printer_create_task_prefer_psram(status_poll_task,
|
|
"status_poll",
|
|
4096,
|
|
4,
|
|
&s_status_poll_task_handle);
|
|
if (ok != pdPASS) {
|
|
if (s_worker_task != NULL) {
|
|
vTaskDelete(s_worker_task);
|
|
s_worker_task = NULL;
|
|
}
|
|
s_protocol_initialized = false;
|
|
destroy_runtime_objects();
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "printer protocol initialized");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t printer_protocol_stop(uint32_t timeout_ms) {
|
|
if (!s_protocol_initialized) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
if (timeout_ms == 0) {
|
|
timeout_ms = runtime_policy_printer_stop_timeout_ms();
|
|
}
|
|
|
|
if (s_mutex != NULL && xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
|
|
s_protocol_stopping = true;
|
|
s_busy_refcnt = 0;
|
|
status_poll_pause_slots_clear_locked();
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
|
|
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, 0);
|
|
|
|
if (s_evt != NULL) {
|
|
xEventGroupSetBits(s_evt, EVT_SHUTDOWN);
|
|
}
|
|
|
|
if (s_job_queue != NULL) {
|
|
const uint32_t sentinel = PRINTER_JOB_SENTINEL_STOP;
|
|
(void)xQueueSend(s_job_queue, &sentinel, 0);
|
|
}
|
|
|
|
ble_printer_client_disconnect();
|
|
platform_direct_printer_disconnect();
|
|
|
|
bool worker_done = (s_worker_task == NULL);
|
|
bool poll_done = (s_status_poll_task_handle == NULL);
|
|
|
|
if (s_evt != NULL && (!worker_done || !poll_done)) {
|
|
EventBits_t bits = xEventGroupWaitBits(s_evt,
|
|
EVT_WORKER_EXITED | EVT_STATUS_POLL_EXITED,
|
|
pdFALSE,
|
|
pdTRUE,
|
|
pdMS_TO_TICKS(timeout_ms));
|
|
worker_done = worker_done || ((bits & EVT_WORKER_EXITED) != 0) || s_worker_task == NULL;
|
|
poll_done = poll_done || ((bits & EVT_STATUS_POLL_EXITED) != 0) || s_status_poll_task_handle == NULL;
|
|
}
|
|
|
|
if (!worker_done && s_worker_task != NULL) {
|
|
ESP_LOGW(TAG, "worker task stop timeout, force delete");
|
|
vTaskDelete(s_worker_task);
|
|
s_worker_task = NULL;
|
|
}
|
|
if (!poll_done && s_status_poll_task_handle != NULL) {
|
|
ESP_LOGW(TAG, "status poll task stop timeout, force delete");
|
|
vTaskDelete(s_status_poll_task_handle);
|
|
s_status_poll_task_handle = NULL;
|
|
}
|
|
|
|
if (s_mutex != NULL && xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {
|
|
reset_jobs_locked();
|
|
reset_runtime_state_locked();
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
|
|
destroy_runtime_objects();
|
|
|
|
s_busy_refcnt = 0;
|
|
s_status_poll_pause_next_token = 1;
|
|
memset(s_status_poll_pause_tokens, 0, sizeof(s_status_poll_pause_tokens));
|
|
memset(s_last_rsp_payload, 0, sizeof(s_last_rsp_payload));
|
|
s_last_rsp_cmd = 0;
|
|
s_last_rsp_payload_len = 0;
|
|
s_worker_task = NULL;
|
|
s_status_poll_task_handle = NULL;
|
|
s_protocol_initialized = false;
|
|
s_protocol_stopping = false;
|
|
s_ble_client_initialized = false;
|
|
s_backend = default_backend_on_startup();
|
|
|
|
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, 0);
|
|
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, 0);
|
|
|
|
platform_direct_printer_deinit();
|
|
|
|
ESP_LOGI(TAG, "printer protocol stopped");
|
|
return (worker_done && poll_done) ? ESP_OK : ESP_ERR_TIMEOUT;
|
|
}
|
|
|
|
esp_err_t printer_protocol_set_backend(printer_backend_t backend, char *err, size_t err_len) {
|
|
if (backend != PRINTER_BACKEND_BLE && backend != PRINTER_BACKEND_DIRECT) {
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "invalid backend");
|
|
}
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
if (backend == PRINTER_BACKEND_DIRECT && !runtime_policy_direct_printer_enabled()) {
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "direct backend disabled");
|
|
}
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
if (!s_protocol_initialized || s_protocol_stopping || s_mutex == NULL) {
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "printer protocol unavailable");
|
|
}
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
printer_backend_t old_backend = s_backend;
|
|
if (old_backend == backend) {
|
|
xSemaphoreGive(s_mutex);
|
|
return ESP_OK;
|
|
}
|
|
|
|
if (s_busy_refcnt != 0 || (s_job_queue != NULL && uxQueueMessagesWaiting(s_job_queue) > 0)) {
|
|
xSemaphoreGive(s_mutex);
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "printer busy");
|
|
}
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
s_backend = backend;
|
|
xSemaphoreGive(s_mutex);
|
|
|
|
ble_printer_client_disconnect();
|
|
platform_direct_printer_disconnect();
|
|
|
|
esp_err_t rc = ensure_backend_ready(backend);
|
|
if (rc != ESP_OK) {
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
|
s_backend = old_backend;
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
(void)ensure_backend_ready(old_backend);
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "backend init failed");
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
if (backend == PRINTER_BACKEND_DIRECT && xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
|
refresh_direct_status_locked();
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
printer_backend_t printer_protocol_get_backend(void) {
|
|
if (!s_protocol_initialized || s_mutex == NULL) {
|
|
return s_backend;
|
|
}
|
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) != pdTRUE) {
|
|
return s_backend;
|
|
}
|
|
printer_backend_t backend = s_backend;
|
|
xSemaphoreGive(s_mutex);
|
|
return backend;
|
|
}
|
|
|
|
esp_err_t printer_protocol_connect(const char *target_name, uint32_t timeout_ms) {
|
|
printer_connect_options_t opt = {
|
|
.backend = printer_protocol_get_backend(),
|
|
.name = target_name,
|
|
.timeout_ms = timeout_ms,
|
|
};
|
|
return printer_protocol_connect_ex(&opt, NULL, 0);
|
|
}
|
|
|
|
esp_err_t printer_protocol_connect_ex(const printer_connect_options_t *opt, char *err, size_t err_len) {
|
|
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;
|
|
}
|
|
|
|
printer_backend_t backend = printer_protocol_get_backend();
|
|
const char *name = NULL;
|
|
uint32_t timeout_ms = 0;
|
|
if (opt != NULL) {
|
|
backend = opt->backend;
|
|
name = opt->name;
|
|
timeout_ms = opt->timeout_ms;
|
|
}
|
|
|
|
esp_err_t rc = printer_protocol_set_backend(backend, err, err_len);
|
|
if (rc != ESP_OK) {
|
|
return rc;
|
|
}
|
|
|
|
if (backend == PRINTER_BACKEND_BLE) {
|
|
rc = ensure_backend_ready(PRINTER_BACKEND_BLE);
|
|
if (rc != ESP_OK) {
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "ble backend unavailable");
|
|
}
|
|
return rc;
|
|
}
|
|
return ble_printer_client_connect(name, timeout_ms);
|
|
}
|
|
|
|
if (backend == PRINTER_BACKEND_DIRECT) {
|
|
rc = ensure_backend_ready(PRINTER_BACKEND_DIRECT);
|
|
if (rc != ESP_OK) {
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "direct backend unavailable");
|
|
}
|
|
return rc;
|
|
}
|
|
return platform_direct_printer_connect(timeout_ms);
|
|
}
|
|
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "unsupported backend");
|
|
}
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
void printer_protocol_disconnect(void) {
|
|
ble_printer_client_disconnect();
|
|
platform_direct_printer_disconnect();
|
|
}
|
|
|
|
void printer_protocol_get_capabilities(printer_capabilities_t *out_capabilities) {
|
|
if (out_capabilities == NULL) {
|
|
return;
|
|
}
|
|
backend_capabilities_for(printer_protocol_get_backend(), out_capabilities);
|
|
}
|
|
|
|
void printer_protocol_get_runtime_status(printer_runtime_status_t *out_status) {
|
|
if (out_status == NULL) {
|
|
return;
|
|
}
|
|
|
|
memset(out_status, 0, sizeof(*out_status));
|
|
out_status->paper_gpio_level = -1;
|
|
out_status->backend = printer_protocol_get_backend();
|
|
|
|
printer_capabilities_t caps = {0};
|
|
backend_capabilities_for(out_status->backend, &caps);
|
|
out_status->supports_gap_move = caps.supports_gap_move;
|
|
out_status->supports_label_offset = caps.supports_label_offset;
|
|
out_status->supports_ota = caps.supports_ota;
|
|
|
|
if (out_status->backend == PRINTER_BACKEND_BLE) {
|
|
ble_link_state_t link = {0};
|
|
ble_printer_client_get_link_state(&link);
|
|
out_status->connected = link.connected;
|
|
out_status->notify_ready = link.notify_ready;
|
|
out_status->transport_ready = link.connected && link.notify_ready;
|
|
out_status->mtu = link.mtu;
|
|
} else if (out_status->backend == PRINTER_BACKEND_DIRECT) {
|
|
out_status->connected = platform_direct_printer_is_connected();
|
|
out_status->transport_ready = out_status->connected;
|
|
out_status->notify_ready = false;
|
|
out_status->mtu = 0;
|
|
|
|
platform_printer_sensors_t sensors = {0};
|
|
if (platform_direct_printer_get_sensors(&sensors) == ESP_OK &&
|
|
s_mutex != NULL &&
|
|
xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
s_status.has_paper = sensors.has_paper;
|
|
s_status.paper_gpio_level = sensors.paper_gpio_level;
|
|
s_status.paper_present_level = sensors.paper_present_level;
|
|
s_status.battery = sensors.battery_percent;
|
|
s_status.temperature = sensors.temperature_c;
|
|
s_status.updated_ms = sensors.updated_ms;
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
}
|
|
|
|
if (s_mutex != NULL && xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
|
out_status->busy = s_busy_refcnt > 0;
|
|
out_status->has_paper = s_status.has_paper;
|
|
out_status->paper_gpio_level = s_status.paper_gpio_level;
|
|
out_status->paper_present_level = s_status.paper_present_level;
|
|
out_status->battery_percent = s_status.battery;
|
|
out_status->temperature = s_status.temperature;
|
|
out_status->last_status_ms = s_status.updated_ms;
|
|
out_status->queue_depth = (s_job_queue != NULL)
|
|
? (uint32_t)uxQueueMessagesWaiting(s_job_queue)
|
|
: 0;
|
|
xSemaphoreGive(s_mutex);
|
|
}
|
|
|
|
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, (int32_t)out_status->queue_depth);
|
|
}
|