feat(printer): add direct thermal backend and debug controls
This commit is contained in:
@@ -22,6 +22,7 @@ esp_err_t rest_server_print_submit_raster_job_and_reply(httpd_req_t *req,
|
||||
uint16_t width,
|
||||
uint16_t height,
|
||||
const char *density,
|
||||
bool direct_ignore_precheck,
|
||||
const char *warning,
|
||||
const char *trace_id);
|
||||
|
||||
|
||||
@@ -53,12 +53,31 @@ static const char *s_web_index =
|
||||
"\n"
|
||||
" <label>Printer Name</label>\n"
|
||||
" <input id='printerName' type='text' value='TQPrinter'>\n"
|
||||
" <label>Printer Backend</label>\n"
|
||||
" <select id='printerBackend' style='max-width:220px'>\n"
|
||||
" <option value='direct' selected>Direct (GPIO)</option>\n"
|
||||
" <option value='ble'>BLE</option>\n"
|
||||
" </select>\n"
|
||||
" <div class='row'>\n"
|
||||
" <button id='btnConnect'>Connect Printer</button>\n"
|
||||
" <button id='btnAutoConnect' class='alt'>Auto Connect</button>\n"
|
||||
" <button id='btnDisconnect' class='warn'>Disconnect</button>\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <label>Paper Detect</label>\n"
|
||||
" <div class='row'>\n"
|
||||
" <button id='btnPaperCheck' class='alt'>Check Paper</button>\n"
|
||||
" </div>\n"
|
||||
" <div id='paperState' class='small'>纸张状态:未知</div>\n"
|
||||
" <div id='paperMeta' class='small'>Backend: -, Connected: -, Transport: -, GPIO: -, Expect: -</div>\n"
|
||||
" <div class='row'>\n"
|
||||
" <label style='margin:0;display:flex;align-items:center;gap:6px;'>\n"
|
||||
" <input id='ignorePrecheck' type='checkbox' style='width:auto;'>\n"
|
||||
" Ignore direct precheck (paper/temp/battery)\n"
|
||||
" </label>\n"
|
||||
" </div>\n"
|
||||
" <div class='small'>Only effective on Direct backend. Use for sensor bring-up/debug.</div>\n"
|
||||
"\n"
|
||||
" <label>Text to Print</label>\n"
|
||||
" <textarea id='printText'>Hello from ESP32-S3!\nOrder: A1024\nThank you</textarea>\n"
|
||||
" <div class='row'>\n"
|
||||
@@ -121,6 +140,10 @@ static const char *s_web_index =
|
||||
" const out = document.getElementById('out');\n"
|
||||
" const apiKeyEl = document.getElementById('apiKey');\n"
|
||||
" const printerNameEl = document.getElementById('printerName');\n"
|
||||
" const printerBackendEl = document.getElementById('printerBackend');\n"
|
||||
" const paperStateEl = document.getElementById('paperState');\n"
|
||||
" const paperMetaEl = document.getElementById('paperMeta');\n"
|
||||
" const ignorePrecheckEl = document.getElementById('ignorePrecheck');\n"
|
||||
" const printTextEl = document.getElementById('printText');\n"
|
||||
" const imagePromptEl = document.getElementById('imagePrompt');\n"
|
||||
" const imgSizeEl = document.getElementById('imgSize');\n"
|
||||
@@ -168,6 +191,46 @@ static const char *s_web_index =
|
||||
" return new Promise((resolve) => setTimeout(resolve, ms));\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function selectedPrinterBackend() {\n"
|
||||
" const v = (printerBackendEl && printerBackendEl.value) ? String(printerBackendEl.value).toLowerCase() : 'ble';\n"
|
||||
" return v === 'direct' ? 'direct' : 'ble';\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function selectedIgnorePrecheck() {\n"
|
||||
" return !!(ignorePrecheckEl && ignorePrecheckEl.checked);\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function syncPaperUi(st) {\n"
|
||||
" if (!paperStateEl || !paperMetaEl) {\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" if (!st || typeof st !== 'object') {\n"
|
||||
" paperStateEl.textContent = '纸张状态:未知';\n"
|
||||
" paperMetaEl.textContent = 'Backend: -, Connected: -, Transport: -, GPIO: -, Expect: -';\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" const hasPaper = !!st.has_paper;\n"
|
||||
" paperStateEl.textContent = hasPaper ? '纸张状态:有纸' : '纸张状态:缺纸';\n"
|
||||
"\n"
|
||||
" const backend = st.printer_backend ? String(st.printer_backend) : '-';\n"
|
||||
" const connected = !!st.printer_connected;\n"
|
||||
" const transportReady = !!st.printer_transport_ready;\n"
|
||||
" const paperLevel = (st.paper_gpio_level === undefined || st.paper_gpio_level === null) ? '-' : String(st.paper_gpio_level);\n"
|
||||
" const paperExpect = (st.paper_present_level === undefined || st.paper_present_level === null) ? '-' : String(st.paper_present_level);\n"
|
||||
" paperMetaEl.textContent = 'Backend: ' + backend + ', Connected: ' + connected + ', Transport: ' + transportReady + ', GPIO: ' + paperLevel + ', Expect: ' + paperExpect;\n"
|
||||
"\n"
|
||||
" if (printerBackendEl && (backend === 'ble' || backend === 'direct')) {\n"
|
||||
" printerBackendEl.value = backend;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" async function fetchPrinterStatus() {\n"
|
||||
" const st = await callApi('/v1/printer/status', 'GET');\n"
|
||||
" syncPaperUi(st);\n"
|
||||
" return st;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function voiceRoundBusy(st) {\n"
|
||||
" if (!st) return false;\n"
|
||||
" const state = (st.voice_dialog_state || '').toLowerCase();\n"
|
||||
@@ -363,6 +426,7 @@ static const char *s_web_index =
|
||||
" threshold: toInt(imgThresholdEl.value, 160, 0, 255),\n"
|
||||
" max_height: toInt(imgMaxHeightEl.value, 2200, 64, 3000),\n"
|
||||
" density: imgDensityEl.value || '中等',\n"
|
||||
" ignore_precheck: selectedIgnorePrecheck(),\n"
|
||||
" scale_to_width: imgScaleToWidthEl.checked,\n"
|
||||
" invert: imgInvertEl.checked,\n"
|
||||
" timeout_ms: toInt(imgTimeoutEl.value, 45000, 5000, 180000),\n"
|
||||
@@ -393,24 +457,41 @@ static const char *s_web_index =
|
||||
" }\n"
|
||||
"\n"
|
||||
" document.getElementById('btnHealth').onclick = () => run(() => callApi('/v1/health', 'GET'));\n"
|
||||
" document.getElementById('btnStatus').onclick = () => run(() => callApi('/v1/printer/status', 'GET'));\n"
|
||||
" document.getElementById('btnStatus').onclick = () => run(() => fetchPrinterStatus());\n"
|
||||
" document.getElementById('btnJobs').onclick = () => run(() => callApi('/v1/jobs', 'GET'));\n"
|
||||
"\n"
|
||||
" document.getElementById('btnConnect').onclick = () => run(() => callApi('/v1/printer/connect', 'POST', {\n"
|
||||
" document.getElementById('btnConnect').onclick = () => run(async () => {\n"
|
||||
" const data = await callApi('/v1/printer/connect', 'POST', {\n"
|
||||
" backend: selectedPrinterBackend(),\n"
|
||||
" name: printerNameEl.value.trim() || 'TQPrinter',\n"
|
||||
" timeout_ms: 15000\n"
|
||||
" }));\n"
|
||||
" });\n"
|
||||
" syncPaperUi(data);\n"
|
||||
" return data;\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" document.getElementById('btnAutoConnect').onclick = () => run(() => callApi('/v1/printer/connect', 'POST', {\n"
|
||||
" document.getElementById('btnAutoConnect').onclick = () => run(async () => {\n"
|
||||
" const data = await callApi('/v1/printer/connect', 'POST', {\n"
|
||||
" backend: selectedPrinterBackend(),\n"
|
||||
" name: '*',\n"
|
||||
" timeout_ms: 20000\n"
|
||||
" }));\n"
|
||||
" });\n"
|
||||
" syncPaperUi(data);\n"
|
||||
" return data;\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" document.getElementById('btnDisconnect').onclick = () => run(() => callApi('/v1/printer/disconnect', 'POST', {}));\n"
|
||||
" document.getElementById('btnDisconnect').onclick = () => run(async () => {\n"
|
||||
" const data = await callApi('/v1/printer/disconnect', 'POST', {});\n"
|
||||
" try { await fetchPrinterStatus(); } catch (_) {}\n"
|
||||
" return data;\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" document.getElementById('btnPaperCheck').onclick = () => run(() => fetchPrinterStatus());\n"
|
||||
"\n"
|
||||
" document.getElementById('btnPrintText').onclick = () => run(() => callApi('/v1/print/text', 'POST', {\n"
|
||||
" text: printTextEl.value,\n"
|
||||
" density: '中等',\n"
|
||||
" ignore_precheck: selectedIgnorePrecheck(),\n"
|
||||
" scale: 2,\n"
|
||||
" line_spacing: 2,\n"
|
||||
" max_height: 1800\n"
|
||||
@@ -447,6 +528,9 @@ static const char *s_web_index =
|
||||
" setVoiceTalkButtonState(false);\n"
|
||||
" (async () => {\n"
|
||||
" try {\n"
|
||||
" await fetchPrinterStatus();\n"
|
||||
" } catch (_) {}\n"
|
||||
" try {\n"
|
||||
" await refreshVoiceStatus();\n"
|
||||
" } catch (_) {}\n"
|
||||
" })();\n"
|
||||
|
||||
@@ -2,11 +2,42 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "control_plane.h"
|
||||
#include "domain.h"
|
||||
|
||||
static const char *printer_backend_str(printer_backend_t backend) {
|
||||
switch (backend) {
|
||||
case PRINTER_BACKEND_BLE:
|
||||
return "ble";
|
||||
case PRINTER_BACKEND_DIRECT:
|
||||
return "direct";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static bool parse_printer_backend(const cJSON *json, printer_backend_t *out_backend) {
|
||||
if (json == NULL || out_backend == NULL) {
|
||||
return false;
|
||||
}
|
||||
cJSON *jbackend = cJSON_GetObjectItemCaseSensitive((cJSON *)json, "backend");
|
||||
if (!cJSON_IsString(jbackend) || jbackend->valuestring == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (strcasecmp(jbackend->valuestring, "ble") == 0) {
|
||||
*out_backend = PRINTER_BACKEND_BLE;
|
||||
return true;
|
||||
}
|
||||
if (strcasecmp(jbackend->valuestring, "direct") == 0) {
|
||||
*out_backend = PRINTER_BACKEND_DIRECT;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void fill_runtime_diag_json(cJSON *root) {
|
||||
runtime_diag_snapshot_t snapshot = {0};
|
||||
runtime_diag_get_snapshot(&snapshot);
|
||||
@@ -54,12 +85,20 @@ static void fill_runtime_json(cJSON *root) {
|
||||
cJSON_AddStringToObject(root, "wifi_mode", "sta");
|
||||
cJSON_AddStringToObject(root, "ip", ip);
|
||||
|
||||
cJSON_AddBoolToObject(root, "ble_connected", st.connected);
|
||||
cJSON_AddBoolToObject(root, "ble_notify_ready", st.notify_ready);
|
||||
cJSON_AddStringToObject(root, "printer_backend", printer_backend_str(st.backend));
|
||||
cJSON_AddBoolToObject(root, "printer_connected", st.connected);
|
||||
cJSON_AddBoolToObject(root, "printer_transport_ready", st.transport_ready);
|
||||
cJSON_AddBoolToObject(root, "ble_connected", st.backend == PRINTER_BACKEND_BLE && st.connected);
|
||||
cJSON_AddBoolToObject(root, "ble_notify_ready", st.backend == PRINTER_BACKEND_BLE && st.notify_ready);
|
||||
cJSON_AddBoolToObject(root, "printer_busy", st.busy);
|
||||
cJSON_AddBoolToObject(root, "has_paper", st.has_paper);
|
||||
cJSON_AddNumberToObject(root, "paper_gpio_level", st.paper_gpio_level);
|
||||
cJSON_AddNumberToObject(root, "paper_present_level", st.paper_present_level);
|
||||
cJSON_AddNumberToObject(root, "battery_percent", st.battery_percent);
|
||||
cJSON_AddNumberToObject(root, "temperature", st.temperature);
|
||||
cJSON_AddBoolToObject(root, "supports_gap_move", st.supports_gap_move);
|
||||
cJSON_AddBoolToObject(root, "supports_label_offset", st.supports_label_offset);
|
||||
cJSON_AddBoolToObject(root, "supports_ota", st.supports_ota);
|
||||
cJSON_AddNumberToObject(root, "queue_depth", st.queue_depth);
|
||||
cJSON_AddNumberToObject(root, "mtu", st.mtu);
|
||||
cJSON_AddNumberToObject(root, "last_status_ms", (double)st.last_status_ms);
|
||||
@@ -117,6 +156,8 @@ esp_err_t rest_server_connect_post(httpd_req_t *req) {
|
||||
char *body = NULL;
|
||||
char name[32] = "TQPrinter";
|
||||
uint32_t timeout_ms = runtime_policy_rest_printer_connect_timeout_ms();
|
||||
printer_backend_t backend = printer_protocol_get_backend();
|
||||
bool backend_specified = false;
|
||||
|
||||
if (req->content_len > 0) {
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
@@ -140,21 +181,53 @@ esp_err_t rest_server_connect_post(httpd_req_t *req) {
|
||||
timeout_ms = (uint32_t)jtimeout->valuedouble;
|
||||
}
|
||||
|
||||
cJSON *jbackend = cJSON_GetObjectItemCaseSensitive(json, "backend");
|
||||
if (jbackend != NULL) {
|
||||
backend_specified = true;
|
||||
if (!parse_printer_backend(json, &backend)) {
|
||||
cJSON_Delete(json);
|
||||
free(body);
|
||||
return rest_server_send_error(req, "400 Bad Request", "backend must be ble or direct");
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(json);
|
||||
free(body);
|
||||
}
|
||||
|
||||
esp_err_t err = printer_protocol_connect(name, timeout_ms);
|
||||
printer_connect_options_t opt = {
|
||||
.backend = backend,
|
||||
.name = backend == PRINTER_BACKEND_BLE ? name : NULL,
|
||||
.timeout_ms = timeout_ms,
|
||||
};
|
||||
char connect_err[96] = {0};
|
||||
esp_err_t err = printer_protocol_connect_ex(&opt, connect_err, sizeof(connect_err));
|
||||
if (err != ESP_OK) {
|
||||
if (err == ESP_ERR_TIMEOUT) {
|
||||
return rest_server_send_error(req, "504 Gateway Timeout", "connect timeout");
|
||||
return rest_server_send_error(req,
|
||||
"504 Gateway Timeout",
|
||||
connect_err[0] != '\0' ? connect_err : "connect timeout");
|
||||
}
|
||||
return rest_server_send_error(req, "500 Internal Server Error", "connect failed");
|
||||
if (err == ESP_ERR_NOT_SUPPORTED || err == ESP_ERR_INVALID_STATE) {
|
||||
return rest_server_send_error(req,
|
||||
"409 Conflict",
|
||||
connect_err[0] != '\0' ? connect_err : "connect failed");
|
||||
}
|
||||
if (err == ESP_ERR_INVALID_ARG) {
|
||||
return rest_server_send_error(req,
|
||||
"400 Bad Request",
|
||||
connect_err[0] != '\0' ? connect_err : "invalid connect params");
|
||||
}
|
||||
return rest_server_send_error(req,
|
||||
"500 Internal Server Error",
|
||||
connect_err[0] != '\0' ? connect_err : "connect failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddStringToObject(root, "message", "connected");
|
||||
cJSON_AddStringToObject(root, "backend", printer_backend_str(backend));
|
||||
cJSON_AddBoolToObject(root, "backend_specified", backend_specified);
|
||||
fill_runtime_json(root);
|
||||
|
||||
err = rest_server_send_json(req, "200 OK", root);
|
||||
|
||||
@@ -18,24 +18,30 @@ esp_err_t rest_server_print_submit_raster_job_and_reply(httpd_req_t *req,
|
||||
uint16_t width,
|
||||
uint16_t height,
|
||||
const char *density,
|
||||
bool direct_ignore_precheck,
|
||||
const char *warning,
|
||||
const char *trace_id) {
|
||||
const char *trace = trace_id_or_default(trace_id);
|
||||
ESP_LOGI(TAG,
|
||||
"print submit start, trace=%s, raster_bytes=%u, size=%ux%u, density=%s",
|
||||
"print submit start, trace=%s, raster_bytes=%u, size=%ux%u, density=%s, ignore_precheck=%d",
|
||||
trace,
|
||||
(unsigned)raw_len,
|
||||
(unsigned)width,
|
||||
(unsigned)height,
|
||||
density != NULL ? density : "中等");
|
||||
density != NULL ? density : "中等",
|
||||
direct_ignore_precheck);
|
||||
|
||||
char submit_err[128] = {0};
|
||||
uint32_t job_id = 0;
|
||||
esp_err_t submit_rc = printer_protocol_submit_raster_job(raw,
|
||||
printer_print_options_t options = {
|
||||
.direct_ignore_precheck = direct_ignore_precheck,
|
||||
};
|
||||
esp_err_t submit_rc = printer_protocol_submit_raster_job_ex(raw,
|
||||
raw_len,
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
&options,
|
||||
&job_id,
|
||||
submit_err,
|
||||
sizeof(submit_err));
|
||||
@@ -56,6 +62,7 @@ esp_err_t rest_server_print_submit_raster_job_and_reply(httpd_req_t *req,
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "job_id", job_id);
|
||||
cJSON_AddStringToObject(root, "state", "queued");
|
||||
cJSON_AddBoolToObject(root, "ignore_precheck", direct_ignore_precheck);
|
||||
if (warning != NULL && warning[0] != '\0') {
|
||||
cJSON_AddStringToObject(root, "warning", warning);
|
||||
}
|
||||
@@ -87,6 +94,7 @@ esp_err_t rest_server_print_raster_post(httpd_req_t *req) {
|
||||
cJSON *jencoding = cJSON_GetObjectItemCaseSensitive(json, "encoding");
|
||||
cJSON *jdata = cJSON_GetObjectItemCaseSensitive(json, "data");
|
||||
cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density");
|
||||
cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck");
|
||||
|
||||
if (!cJSON_IsNumber(jwidth) || !cJSON_IsNumber(jheight) ||
|
||||
!cJSON_IsString(jencoding) || !cJSON_IsString(jdata)) {
|
||||
@@ -102,6 +110,7 @@ esp_err_t rest_server_print_raster_post(httpd_req_t *req) {
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
bool direct_ignore_precheck = rest_server_json_bool_with_default(jignore, false);
|
||||
|
||||
uint8_t *raw = NULL;
|
||||
size_t raw_len = 0;
|
||||
@@ -117,6 +126,7 @@ esp_err_t rest_server_print_raster_post(httpd_req_t *req) {
|
||||
(uint16_t)jwidth->valuedouble,
|
||||
(uint16_t)jheight->valuedouble,
|
||||
density,
|
||||
direct_ignore_precheck,
|
||||
NULL,
|
||||
NULL);
|
||||
free(raw);
|
||||
|
||||
@@ -16,6 +16,7 @@ typedef struct {
|
||||
bool invert;
|
||||
uint16_t max_height;
|
||||
const char *density;
|
||||
bool direct_ignore_precheck;
|
||||
} image_print_options_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -68,7 +69,8 @@ static void parse_image_upload_options(httpd_req_t *req,
|
||||
uint8_t *out_threshold,
|
||||
bool *out_invert,
|
||||
uint16_t *out_max_height,
|
||||
const char **out_density) {
|
||||
const char **out_density,
|
||||
bool *out_direct_ignore_precheck) {
|
||||
if (out_scale_to_width != NULL) {
|
||||
*out_scale_to_width = true;
|
||||
}
|
||||
@@ -84,6 +86,9 @@ static void parse_image_upload_options(httpd_req_t *req,
|
||||
if (out_density != NULL) {
|
||||
*out_density = "中等";
|
||||
}
|
||||
if (out_direct_ignore_precheck != NULL) {
|
||||
*out_direct_ignore_precheck = false;
|
||||
}
|
||||
|
||||
if (req == NULL) {
|
||||
return;
|
||||
@@ -138,6 +143,14 @@ static void parse_image_upload_options(httpd_req_t *req,
|
||||
*out_density = map_density_text(value);
|
||||
}
|
||||
|
||||
if (httpd_query_key_value(query, "ignore_precheck", value, sizeof(value)) == ESP_OK &&
|
||||
out_direct_ignore_precheck != NULL) {
|
||||
bool parsed = *out_direct_ignore_precheck;
|
||||
if (parse_bool_text(value, &parsed)) {
|
||||
*out_direct_ignore_precheck = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
free(query);
|
||||
}
|
||||
|
||||
@@ -150,6 +163,7 @@ static void image_print_options_set_defaults(image_print_options_t *out) {
|
||||
out->invert = false;
|
||||
out->max_height = 2200;
|
||||
out->density = "中等";
|
||||
out->direct_ignore_precheck = false;
|
||||
}
|
||||
|
||||
static void parse_image_print_options_from_json(cJSON *json, image_print_options_t *out) {
|
||||
@@ -162,6 +176,7 @@ static void parse_image_print_options_from_json(cJSON *json, image_print_options
|
||||
cJSON *jinvert = cJSON_GetObjectItemCaseSensitive(json, "invert");
|
||||
cJSON *jmaxh = cJSON_GetObjectItemCaseSensitive(json, "max_height");
|
||||
cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density");
|
||||
cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck");
|
||||
|
||||
out->scale_to_width = rest_server_json_bool_with_default(jscale, out->scale_to_width);
|
||||
out->invert = rest_server_json_bool_with_default(jinvert, out->invert);
|
||||
@@ -174,6 +189,7 @@ static void parse_image_print_options_from_json(cJSON *json, image_print_options
|
||||
if (cJSON_IsString(jdensity) && jdensity->valuestring != NULL) {
|
||||
out->density = map_density_text(jdensity->valuestring);
|
||||
}
|
||||
out->direct_ignore_precheck = rest_server_json_bool_with_default(jignore, out->direct_ignore_precheck);
|
||||
}
|
||||
|
||||
static bool parse_image_size_text(const char *text, uint32_t *out_w, uint32_t *out_h) {
|
||||
@@ -345,21 +361,24 @@ static esp_err_t rest_server_print_image_binary(httpd_req_t *req) {
|
||||
bool invert = false;
|
||||
uint16_t max_height = 2200;
|
||||
const char *density = "中等";
|
||||
bool direct_ignore_precheck = false;
|
||||
parse_image_upload_options(req,
|
||||
&scale_to_width,
|
||||
&threshold,
|
||||
&invert,
|
||||
&max_height,
|
||||
&density);
|
||||
&density,
|
||||
&direct_ignore_precheck);
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"image process start (upload), png_bytes=%u, scale_to_width=%d, threshold=%u, invert=%d, max_height=%u, density=%s",
|
||||
"image process start (upload), png_bytes=%u, scale_to_width=%d, threshold=%u, invert=%d, max_height=%u, density=%s, ignore_precheck=%d",
|
||||
(unsigned)req->content_len,
|
||||
scale_to_width,
|
||||
(unsigned)threshold,
|
||||
invert,
|
||||
(unsigned)max_height,
|
||||
density != NULL ? density : "中等");
|
||||
density != NULL ? density : "中等",
|
||||
direct_ignore_precheck);
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
@@ -407,6 +426,7 @@ static esp_err_t rest_server_print_image_binary(httpd_req_t *req) {
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
direct_ignore_precheck,
|
||||
NULL,
|
||||
NULL);
|
||||
free(raster);
|
||||
@@ -458,12 +478,13 @@ static esp_err_t rest_server_print_image_generate(httpd_req_t *req) {
|
||||
(unsigned)gen_opt.timeout_ms,
|
||||
(unsigned)gen_opt.fetch_timeout_ms);
|
||||
ESP_LOGI(TAG,
|
||||
"image process config, scale_to_width=%d, threshold=%u, invert=%d, max_height=%u, density=%s",
|
||||
"image process config, scale_to_width=%d, threshold=%u, invert=%d, max_height=%u, density=%s, ignore_precheck=%d",
|
||||
print_opt.scale_to_width,
|
||||
(unsigned)print_opt.threshold,
|
||||
print_opt.invert,
|
||||
(unsigned)print_opt.max_height,
|
||||
print_opt.density != NULL ? print_opt.density : "中等");
|
||||
print_opt.density != NULL ? print_opt.density : "中等",
|
||||
print_opt.direct_ignore_precheck);
|
||||
|
||||
image_generation_request_t gen_req = {
|
||||
.prompt = gen_opt.prompt,
|
||||
@@ -553,6 +574,7 @@ static esp_err_t rest_server_print_image_generate(httpd_req_t *req) {
|
||||
width,
|
||||
height,
|
||||
print_opt.density,
|
||||
print_opt.direct_ignore_precheck,
|
||||
warning,
|
||||
request_id);
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ esp_err_t rest_server_print_qr_post(httpd_req_t *req) {
|
||||
cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "module_scale");
|
||||
cJSON *jmargin = cJSON_GetObjectItemCaseSensitive(json, "margin_modules");
|
||||
cJSON *jmaxh = cJSON_GetObjectItemCaseSensitive(json, "max_height");
|
||||
cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck");
|
||||
|
||||
if (!cJSON_IsString(jtext) || jtext->valuestring == NULL || jtext->valuestring[0] == '\0') {
|
||||
cJSON_Delete(json);
|
||||
@@ -168,6 +169,7 @@ esp_err_t rest_server_print_qr_post(httpd_req_t *req) {
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
bool direct_ignore_precheck = rest_server_json_bool_with_default(jignore, false);
|
||||
|
||||
uint8_t ecc_level = 3; /* default H */
|
||||
if (cJSON_IsString(jecc) && jecc->valuestring != NULL) {
|
||||
@@ -224,6 +226,7 @@ esp_err_t rest_server_print_qr_post(httpd_req_t *req) {
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
direct_ignore_precheck,
|
||||
NULL,
|
||||
NULL);
|
||||
free(raster);
|
||||
@@ -250,6 +253,7 @@ esp_err_t rest_server_print_label_post(httpd_req_t *req) {
|
||||
cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density");
|
||||
cJSON *jgap = cJSON_GetObjectItemCaseSensitive(json, "gap_move_before");
|
||||
cJSON *joffset = cJSON_GetObjectItemCaseSensitive(json, "offset_tenths_mm");
|
||||
cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck");
|
||||
|
||||
bool gap_move_before = rest_server_json_bool_with_default(jgap, true);
|
||||
bool has_offset = cJSON_IsNumber(joffset);
|
||||
@@ -287,6 +291,7 @@ esp_err_t rest_server_print_label_post(httpd_req_t *req) {
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
bool direct_ignore_precheck = rest_server_json_bool_with_default(jignore, false);
|
||||
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
@@ -313,6 +318,7 @@ esp_err_t rest_server_print_label_post(httpd_req_t *req) {
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
direct_ignore_precheck,
|
||||
NULL,
|
||||
NULL);
|
||||
free(raster);
|
||||
@@ -341,6 +347,7 @@ esp_err_t rest_server_print_text_post(httpd_req_t *req) {
|
||||
cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "scale");
|
||||
cJSON *jline = cJSON_GetObjectItemCaseSensitive(json, "line_spacing");
|
||||
cJSON *jmaxh = cJSON_GetObjectItemCaseSensitive(json, "max_height");
|
||||
cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck");
|
||||
|
||||
if (!cJSON_IsString(jtext) || jtext->valuestring == NULL) {
|
||||
cJSON_Delete(json);
|
||||
@@ -350,6 +357,7 @@ esp_err_t rest_server_print_text_post(httpd_req_t *req) {
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
bool direct_ignore_precheck = rest_server_json_bool_with_default(jignore, false);
|
||||
uint8_t scale = 2;
|
||||
uint8_t line_spacing = 2;
|
||||
uint16_t max_height = 2000;
|
||||
@@ -394,6 +402,7 @@ esp_err_t rest_server_print_text_post(httpd_req_t *req) {
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
direct_ignore_precheck,
|
||||
render_msg,
|
||||
NULL);
|
||||
free(raster);
|
||||
@@ -422,6 +431,7 @@ esp_err_t rest_server_print_receipt_post(httpd_req_t *req) {
|
||||
cJSON *jfooter = cJSON_GetObjectItemCaseSensitive(json, "footer");
|
||||
cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density");
|
||||
cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "scale");
|
||||
cJSON *jignore = cJSON_GetObjectItemCaseSensitive(json, "ignore_precheck");
|
||||
|
||||
const char *title = (cJSON_IsString(jtitle) && jtitle->valuestring != NULL)
|
||||
? jtitle->valuestring
|
||||
@@ -432,6 +442,7 @@ esp_err_t rest_server_print_receipt_post(httpd_req_t *req) {
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
bool direct_ignore_precheck = rest_server_json_bool_with_default(jignore, false);
|
||||
uint8_t scale = 2;
|
||||
if (cJSON_IsNumber(jscale) && jscale->valuedouble >= 1 && jscale->valuedouble <= 4) {
|
||||
scale = (uint8_t)jscale->valuedouble;
|
||||
@@ -516,6 +527,7 @@ esp_err_t rest_server_print_receipt_post(httpd_req_t *req) {
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
direct_ignore_precheck,
|
||||
render_msg,
|
||||
NULL);
|
||||
free(raster);
|
||||
|
||||
@@ -130,14 +130,44 @@ typedef struct {
|
||||
uint8_t patch;
|
||||
} printer_ota_version_t;
|
||||
|
||||
typedef enum {
|
||||
PRINTER_BACKEND_BLE = 0,
|
||||
PRINTER_BACKEND_DIRECT = 1,
|
||||
} printer_backend_t;
|
||||
|
||||
typedef struct {
|
||||
printer_backend_t backend;
|
||||
const char *name;
|
||||
uint32_t timeout_ms;
|
||||
} printer_connect_options_t;
|
||||
|
||||
typedef struct {
|
||||
// Direct backend only: bypass paper/temp/battery precheck for this print job.
|
||||
bool direct_ignore_precheck;
|
||||
} printer_print_options_t;
|
||||
|
||||
typedef struct {
|
||||
bool supports_connect;
|
||||
bool supports_gap_move;
|
||||
bool supports_label_offset;
|
||||
bool supports_ota;
|
||||
} printer_capabilities_t;
|
||||
|
||||
typedef struct {
|
||||
printer_backend_t backend;
|
||||
bool connected;
|
||||
bool transport_ready;
|
||||
bool notify_ready;
|
||||
bool busy;
|
||||
bool has_paper;
|
||||
int8_t paper_gpio_level;
|
||||
uint8_t paper_present_level;
|
||||
uint8_t battery_percent;
|
||||
float temperature;
|
||||
uint16_t mtu;
|
||||
bool supports_gap_move;
|
||||
bool supports_label_offset;
|
||||
bool supports_ota;
|
||||
uint32_t queue_depth;
|
||||
int64_t last_status_ms;
|
||||
} printer_runtime_status_t;
|
||||
@@ -147,8 +177,13 @@ typedef uint32_t printer_status_poll_pause_token_t;
|
||||
esp_err_t printer_protocol_init(void);
|
||||
esp_err_t printer_protocol_stop(uint32_t timeout_ms);
|
||||
|
||||
esp_err_t printer_protocol_set_backend(printer_backend_t backend, char *err, size_t err_len);
|
||||
printer_backend_t printer_protocol_get_backend(void);
|
||||
|
||||
esp_err_t printer_protocol_connect(const char *target_name, uint32_t timeout_ms);
|
||||
esp_err_t printer_protocol_connect_ex(const printer_connect_options_t *opt, char *err, size_t err_len);
|
||||
void printer_protocol_disconnect(void);
|
||||
void printer_protocol_get_capabilities(printer_capabilities_t *out_capabilities);
|
||||
|
||||
void printer_protocol_get_runtime_status(printer_runtime_status_t *out_status);
|
||||
printer_status_poll_pause_token_t printer_protocol_status_poll_pause_acquire(void);
|
||||
@@ -162,6 +197,15 @@ esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster,
|
||||
uint32_t *out_job_id,
|
||||
char *err,
|
||||
size_t err_len);
|
||||
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);
|
||||
|
||||
bool printer_protocol_get_job(uint32_t job_id, print_job_info_t *out_info);
|
||||
esp_err_t printer_protocol_list_jobs(print_job_info_t *out_jobs, size_t max_jobs, size_t *out_count);
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
typedef struct {
|
||||
bool used;
|
||||
bool cancel_requested;
|
||||
bool direct_ignore_precheck;
|
||||
uint32_t id;
|
||||
print_job_state_t state;
|
||||
uint8_t progress;
|
||||
@@ -60,6 +61,8 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
bool has_paper;
|
||||
int8_t paper_gpio_level;
|
||||
uint8_t paper_present_level;
|
||||
uint8_t battery;
|
||||
float temperature;
|
||||
int64_t updated_ms;
|
||||
|
||||
@@ -21,6 +21,8 @@ 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,
|
||||
@@ -36,6 +38,8 @@ 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";
|
||||
|
||||
@@ -45,6 +49,61 @@ static const char *TAG = "printer_protocol";
|
||||
#define PRINT_WORKER_CORE_ID 1
|
||||
#endif
|
||||
|
||||
static void on_rx_frame(const uint8_t *data, size_t len);
|
||||
|
||||
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) {
|
||||
@@ -77,6 +136,8 @@ static void reset_runtime_state_locked(void) {
|
||||
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;
|
||||
@@ -260,6 +321,9 @@ esp_err_t printer_protocol_send_frame(uint8_t cmd,
|
||||
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);
|
||||
@@ -440,10 +504,12 @@ static void status_poll_task(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);
|
||||
}
|
||||
|
||||
@@ -451,10 +517,20 @@ static void status_poll_task(void *arg) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ble_printer_client_is_connected() && can_poll) {
|
||||
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()));
|
||||
}
|
||||
|
||||
@@ -492,6 +568,8 @@ static void on_rx_frame(const uint8_t *data, size_t len) {
|
||||
|
||||
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;
|
||||
@@ -564,8 +642,12 @@ esp_err_t printer_protocol_init(void) {
|
||||
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 = (runtime_policy_printer_default_backend() == 1 && runtime_policy_direct_printer_enabled())
|
||||
? PRINTER_BACKEND_DIRECT
|
||||
: PRINTER_BACKEND_BLE;
|
||||
|
||||
esp_err_t err = ble_printer_client_init(on_rx_frame);
|
||||
esp_err_t err = ensure_backend_ready(s_backend);
|
||||
if (err != ESP_OK) {
|
||||
s_protocol_initialized = false;
|
||||
destroy_runtime_objects();
|
||||
@@ -630,6 +712,7 @@ esp_err_t printer_protocol_stop(uint32_t timeout_ms) {
|
||||
}
|
||||
|
||||
ble_printer_client_disconnect();
|
||||
platform_direct_printer_disconnect();
|
||||
|
||||
bool worker_done = (s_worker_task == NULL);
|
||||
bool poll_done = (s_status_poll_task_handle == NULL);
|
||||
@@ -673,23 +756,166 @@ esp_err_t printer_protocol_stop(uint32_t timeout_ms) {
|
||||
s_status_poll_task_handle = NULL;
|
||||
s_protocol_initialized = false;
|
||||
s_protocol_stopping = false;
|
||||
s_ble_client_initialized = false;
|
||||
s_backend = PRINTER_BACKEND_BLE;
|
||||
|
||||
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_connect(const char *target_name, uint32_t timeout_ms) {
|
||||
if (!s_protocol_initialized || s_protocol_stopping) {
|
||||
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;
|
||||
}
|
||||
return ble_printer_client_connect(target_name, timeout_ms);
|
||||
|
||||
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) {
|
||||
@@ -698,17 +924,47 @@ void printer_protocol_get_runtime_status(printer_runtime_status_t *out_status) {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -4,8 +4,22 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool is_direct_backend(void) {
|
||||
return printer_protocol_get_backend() == PRINTER_BACKEND_DIRECT;
|
||||
}
|
||||
|
||||
static esp_err_t unsupported_for_direct(char *err, size_t err_len, const char *op) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "%s not supported on direct backend", op);
|
||||
}
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_gap_move(uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (is_direct_backend()) {
|
||||
return platform_direct_printer_gap_move(timeout_ms, err, err_len);
|
||||
}
|
||||
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
@@ -34,6 +48,10 @@ esp_err_t printer_protocol_gap_move(uint32_t timeout_ms, char *err, size_t err_l
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_get_label_offset(uint8_t *out_offset, uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (is_direct_backend()) {
|
||||
return unsupported_for_direct(err, err_len, "label offset");
|
||||
}
|
||||
|
||||
if (out_offset == NULL) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid args");
|
||||
@@ -88,6 +106,10 @@ esp_err_t printer_protocol_get_label_offset(uint8_t *out_offset, uint32_t timeou
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_set_label_offset(uint8_t offset, uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (is_direct_backend()) {
|
||||
return unsupported_for_direct(err, err_len, "label offset");
|
||||
}
|
||||
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
@@ -115,6 +137,10 @@ esp_err_t printer_protocol_set_label_offset(uint8_t offset, uint32_t timeout_ms,
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_ota_jump_boot(uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (is_direct_backend()) {
|
||||
return unsupported_for_direct(err, err_len, "ota jump boot");
|
||||
}
|
||||
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
@@ -142,6 +168,10 @@ esp_err_t printer_protocol_ota_jump_boot(uint32_t timeout_ms, char *err, size_t
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_ota_jump_app(uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (is_direct_backend()) {
|
||||
return unsupported_for_direct(err, err_len, "ota jump app");
|
||||
}
|
||||
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
@@ -169,6 +199,10 @@ esp_err_t printer_protocol_ota_jump_app(uint32_t timeout_ms, char *err, size_t e
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_ota_erase_page(uint16_t page_num, uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (is_direct_backend()) {
|
||||
return unsupported_for_direct(err, err_len, "ota erase");
|
||||
}
|
||||
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
@@ -207,6 +241,10 @@ esp_err_t printer_protocol_ota_write_frame(uint16_t packet_num,
|
||||
uint32_t timeout_ms,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if (is_direct_backend()) {
|
||||
return unsupported_for_direct(err, err_len, "ota write");
|
||||
}
|
||||
|
||||
if ((data_len > 0 && data == NULL) || data_len > OTA_MAX_DATA_PER_FRAME) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid frame length");
|
||||
@@ -252,6 +290,10 @@ esp_err_t printer_protocol_ota_get_version(printer_ota_version_t *out_version,
|
||||
uint32_t timeout_ms,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if (is_direct_backend()) {
|
||||
return unsupported_for_direct(err, err_len, "ota version");
|
||||
}
|
||||
|
||||
if (out_version == NULL) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid args");
|
||||
|
||||
@@ -48,11 +48,12 @@ bool printer_protocol_is_terminal_state(print_job_state_t state) {
|
||||
state == PRINT_JOB_STATE_CANCELED;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster,
|
||||
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) {
|
||||
@@ -134,9 +135,11 @@ esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster,
|
||||
s_jobs[idx].width = width;
|
||||
s_jobs[idx].height = height;
|
||||
s_jobs[idx].data_len = raster_len;
|
||||
s_jobs[idx].direct_ignore_precheck = (options != NULL && options->direct_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].direct_ignore_precheck;
|
||||
|
||||
xSemaphoreGive(s_mutex);
|
||||
|
||||
@@ -166,12 +169,13 @@ esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster,
|
||||
|
||||
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",
|
||||
"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);
|
||||
runtime_diag_counter_add(RUNTIME_DIAG_COUNTER_PRINTER_JOB_SUBMITTED, 1);
|
||||
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, (int32_t)queue_depth);
|
||||
@@ -179,6 +183,25 @@ esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster,
|
||||
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;
|
||||
|
||||
@@ -15,6 +15,10 @@ static const char *TAG = "printer_protocol";
|
||||
static bool request_status_sync(uint32_t timeout_ms) {
|
||||
int64_t old_ms;
|
||||
|
||||
if (printer_protocol_get_backend() != PRINTER_BACKEND_BLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (printer_protocol_is_stopping()) {
|
||||
return false;
|
||||
}
|
||||
@@ -72,7 +76,8 @@ static uint16_t density_to_hot_time(const char *density) {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
static bool precheck_printer_ready(char *err, size_t err_len) {
|
||||
static bool precheck_printer_ready(bool direct_ignore_precheck, char *err, size_t err_len) {
|
||||
if (printer_protocol_get_backend() == PRINTER_BACKEND_BLE) {
|
||||
if (!request_status_sync(1200)) {
|
||||
snprintf(err, err_len, "status timeout");
|
||||
return false;
|
||||
@@ -101,35 +106,52 @@ static bool precheck_printer_ready(char *err, size_t err_len) {
|
||||
snprintf(err, err_len, "temperature too high");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool run_print_job(job_slot_t *job) {
|
||||
if (direct_ignore_precheck) {
|
||||
return true;
|
||||
}
|
||||
|
||||
platform_printer_sensors_t sensors = {0};
|
||||
if (platform_direct_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_direct_printer_temp_min_c();
|
||||
float temp_max = runtime_policy_direct_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_direct_printer_battery_min_percent()) {
|
||||
snprintf(err, err_len, "battery too low");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool run_print_job_ble(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 (printer_protocol_is_stopping()) {
|
||||
snprintf(job->error, sizeof(job->error), "protocol stopping");
|
||||
return false;
|
||||
}
|
||||
|
||||
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))) {
|
||||
if (!precheck_printer_ready(false, 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;
|
||||
@@ -232,6 +254,81 @@ static bool run_print_job(job_slot_t *job) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool run_print_job_direct(job_slot_t *job) {
|
||||
if (!platform_direct_printer_is_connected()) {
|
||||
snprintf(job->error, sizeof(job->error), "printer not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
char check_err[96];
|
||||
if (!precheck_printer_ready(job->direct_ignore_precheck, check_err, sizeof(check_err))) {
|
||||
snprintf(job->error, sizeof(job->error), "%s", check_err);
|
||||
return false;
|
||||
}
|
||||
|
||||
platform_direct_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_direct_printer_strobe_interval_us(),
|
||||
.motor_step_delay_us = runtime_policy_direct_printer_motor_step_us(),
|
||||
.motor_steps_per_line = runtime_policy_direct_printer_steps_per_line(),
|
||||
.timeout_ms = runtime_policy_direct_printer_operation_timeout_ms(),
|
||||
.ignore_precheck = job->direct_ignore_precheck,
|
||||
.cancel_flag = &job->cancel_requested,
|
||||
};
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
||||
job->progress = 10;
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
|
||||
char direct_err[96] = {0};
|
||||
esp_err_t rc = platform_direct_printer_print(&req, direct_err, sizeof(direct_err));
|
||||
if (rc != ESP_OK) {
|
||||
if (direct_err[0] != '\0') {
|
||||
snprintf(job->error, sizeof(job->error), "%s", direct_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), "direct 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, backend=%s, raster_bytes=%u, size=%ux%u, density=%s, ignore_precheck=%d",
|
||||
(unsigned)job->id,
|
||||
printer_protocol_get_backend() == PRINTER_BACKEND_BLE ? "ble" : "direct",
|
||||
(unsigned)job->data_len,
|
||||
(unsigned)job->width,
|
||||
(unsigned)job->height,
|
||||
job->density,
|
||||
job->direct_ignore_precheck);
|
||||
|
||||
if (printer_protocol_is_stopping()) {
|
||||
snprintf(job->error, sizeof(job->error), "protocol stopping");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (printer_protocol_get_backend() == PRINTER_BACKEND_BLE) {
|
||||
return run_print_job_ble(job);
|
||||
}
|
||||
return run_print_job_direct(job);
|
||||
}
|
||||
|
||||
void printer_protocol_worker_task(void *arg) {
|
||||
(void)arg;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ idf_component_register(
|
||||
"src/platform_bootstrap.c"
|
||||
"src/wifi_manager.c"
|
||||
"src/ble_printer_client.c"
|
||||
"src/direct_thermal_printer.c"
|
||||
"src/display_st7789.c"
|
||||
"src/voice_audio.c"
|
||||
"src/runtime_policy.c"
|
||||
@@ -19,6 +20,7 @@ idf_component_register(
|
||||
esp_event
|
||||
nvs_flash
|
||||
mbedtls
|
||||
esp_adc
|
||||
esp_lcd
|
||||
esp_codec_dev
|
||||
esp_audio_codec
|
||||
|
||||
@@ -37,6 +37,41 @@ bool ble_printer_client_is_connected(void);
|
||||
void ble_printer_client_get_link_state(ble_link_state_t *out_state);
|
||||
esp_err_t ble_printer_client_write(const uint8_t *data, size_t len);
|
||||
|
||||
// ---------- direct_thermal_printer ----------
|
||||
typedef struct {
|
||||
bool has_paper;
|
||||
int8_t paper_gpio_level;
|
||||
uint8_t paper_present_level;
|
||||
uint8_t battery_percent;
|
||||
float temperature_c;
|
||||
int64_t updated_ms;
|
||||
} platform_printer_sensors_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
const uint8_t *raster;
|
||||
size_t raster_len;
|
||||
uint16_t strobe_on_time_us;
|
||||
uint16_t strobe_interval_us;
|
||||
uint16_t motor_step_delay_us;
|
||||
uint8_t motor_steps_per_line;
|
||||
uint32_t timeout_ms;
|
||||
bool ignore_precheck;
|
||||
const volatile bool *cancel_flag;
|
||||
} platform_direct_print_request_t;
|
||||
|
||||
esp_err_t platform_direct_printer_init(void);
|
||||
void platform_direct_printer_deinit(void);
|
||||
esp_err_t platform_direct_printer_connect(uint32_t timeout_ms);
|
||||
void platform_direct_printer_disconnect(void);
|
||||
bool platform_direct_printer_is_connected(void);
|
||||
esp_err_t platform_direct_printer_get_sensors(platform_printer_sensors_t *out_sensors);
|
||||
esp_err_t platform_direct_printer_print(const platform_direct_print_request_t *request,
|
||||
char *err,
|
||||
size_t err_len);
|
||||
esp_err_t platform_direct_printer_gap_move(uint32_t timeout_ms, char *err, size_t err_len);
|
||||
|
||||
// ---------- display_st7789 ----------
|
||||
esp_err_t platform_display_init(void);
|
||||
esp_err_t platform_display_show_test_pattern(void);
|
||||
@@ -79,6 +114,26 @@ uint32_t runtime_policy_printer_worker_queue_wait_ms(void);
|
||||
uint32_t runtime_policy_printer_queue_retry_delay_ms(void);
|
||||
uint32_t runtime_policy_printer_status_poll_interval_ms(void);
|
||||
uint32_t runtime_policy_printer_stop_timeout_ms(void);
|
||||
uint8_t runtime_policy_printer_default_backend(void);
|
||||
|
||||
bool runtime_policy_direct_printer_enabled(void);
|
||||
uint32_t runtime_policy_direct_printer_operation_timeout_ms(void);
|
||||
uint16_t runtime_policy_direct_printer_strobe_on_us(void);
|
||||
uint16_t runtime_policy_direct_printer_strobe_interval_us(void);
|
||||
uint16_t runtime_policy_direct_printer_motor_step_us(void);
|
||||
bool runtime_policy_direct_printer_motor_reverse(void);
|
||||
uint8_t runtime_policy_direct_printer_steps_per_line(void);
|
||||
uint16_t runtime_policy_direct_printer_gap_steps(void);
|
||||
float runtime_policy_direct_printer_temp_min_c(void);
|
||||
float runtime_policy_direct_printer_temp_max_c(void);
|
||||
uint8_t runtime_policy_direct_printer_battery_min_percent(void);
|
||||
uint8_t runtime_policy_direct_printer_paper_present_level(void);
|
||||
uint16_t runtime_policy_direct_printer_battery_empty_mv(void);
|
||||
uint16_t runtime_policy_direct_printer_battery_full_mv(void);
|
||||
uint16_t runtime_policy_direct_printer_battery_divider_ratio_x1000(void);
|
||||
uint32_t runtime_policy_direct_printer_ntc_pullup_ohms(void);
|
||||
uint32_t runtime_policy_direct_printer_ntc_r25_ohms(void);
|
||||
uint32_t runtime_policy_direct_printer_ntc_beta(void);
|
||||
|
||||
uint32_t runtime_policy_rest_printer_connect_timeout_ms(void);
|
||||
uint32_t runtime_policy_rest_label_timeout_ms(void);
|
||||
|
||||
769
components/platform/src/direct_thermal_printer.c
Normal file
769
components/platform/src/direct_thermal_printer.c
Normal file
@@ -0,0 +1,769 @@
|
||||
#include "platform.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_ENABLE
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_ENABLE 1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_KEY_PRINT_BOOST_ACTIVE_HIGH
|
||||
#define CONFIG_TQ_KEY_PRINT_BOOST_ACTIVE_HIGH 1
|
||||
#endif
|
||||
|
||||
#if CONFIG_TQ_KEY_PRINT_BOOST_ACTIVE_HIGH
|
||||
#define BOOST_ON_LEVEL 1
|
||||
#define BOOST_OFF_LEVEL 0
|
||||
#else
|
||||
#define BOOST_ON_LEVEL 0
|
||||
#define BOOST_OFF_LEVEL 1
|
||||
#endif
|
||||
|
||||
#define ADC_RAW_MAX 4095
|
||||
|
||||
static const char *TAG = "direct_printer";
|
||||
static const uint8_t k_print_yield_lines = 8;
|
||||
static const uint16_t k_gap_yield_steps = 32;
|
||||
|
||||
typedef struct {
|
||||
bool ready;
|
||||
adc_unit_t unit;
|
||||
adc_channel_t channel;
|
||||
} adc_pin_t;
|
||||
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
bool connected;
|
||||
uint8_t motor_phase;
|
||||
SemaphoreHandle_t lock;
|
||||
adc_oneshot_unit_handle_t adc1_handle;
|
||||
adc_oneshot_unit_handle_t adc2_handle;
|
||||
adc_pin_t battery_adc;
|
||||
adc_pin_t ntc_adc;
|
||||
} direct_printer_state_t;
|
||||
|
||||
static direct_printer_state_t s_state;
|
||||
|
||||
static const uint8_t k_motor_step_table[4] = {0x05, 0x09, 0x0A, 0x06};
|
||||
|
||||
static bool is_gpio_valid(int gpio_num) {
|
||||
return gpio_num >= 0;
|
||||
}
|
||||
|
||||
static void write_err(char *err, size_t err_len, const char *text) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
strlcpy(err, text, err_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void set_gpio_level_if_valid(int gpio_num, int level) {
|
||||
if (!is_gpio_valid(gpio_num)) {
|
||||
return;
|
||||
}
|
||||
(void)gpio_set_level(gpio_num, level);
|
||||
}
|
||||
|
||||
static esp_err_t config_output_pin(int gpio_num, int level, const char *name) {
|
||||
if (!is_gpio_valid(gpio_num)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
gpio_config_t cfg = {
|
||||
.pin_bit_mask = 1ULL << gpio_num,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
esp_err_t err = gpio_config(&cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "gpio_config output failed: %s pin=%d err=%s", name, gpio_num, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = gpio_set_level(gpio_num, level);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "gpio_set_level failed: %s pin=%d level=%d err=%s", name, gpio_num, level, esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t config_input_pin(int gpio_num, bool pull_up, const char *name) {
|
||||
if (!is_gpio_valid(gpio_num)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
gpio_config_t cfg = {
|
||||
.pin_bit_mask = 1ULL << gpio_num,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = pull_up ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
esp_err_t err = gpio_config(&cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "gpio_config input failed: %s pin=%d err=%s", name, gpio_num, esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static void set_stb_level_locked(int level) {
|
||||
set_gpio_level_if_valid(CONFIG_TQ_PRINT_STB_12_PIN, level);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_PRINT_STB_34_PIN, level);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_PRINT_STB_56_PIN, level);
|
||||
}
|
||||
|
||||
static void motor_apply_pattern_locked(uint8_t pattern) {
|
||||
set_gpio_level_if_valid(CONFIG_TQ_PRINT_OUTA_P_PIN, (pattern >> 0) & 0x01);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_PRINT_OUTA_N_PIN, (pattern >> 1) & 0x01);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_PRINT_OUTB_P_PIN, (pattern >> 2) & 0x01);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_PRINT_OUTB_N_PIN, (pattern >> 3) & 0x01);
|
||||
}
|
||||
|
||||
static void motor_off_locked(void) {
|
||||
motor_apply_pattern_locked(0x00);
|
||||
}
|
||||
|
||||
static void safe_drive_off_locked(bool keep_boost_on) {
|
||||
set_stb_level_locked(0);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_SPI_SHARED_MOSI_PIN, 0);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_SPI_SHARED_CLK_PIN, 0);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_PRINT_LAT_PIN, 1);
|
||||
motor_off_locked();
|
||||
if (!keep_boost_on) {
|
||||
set_gpio_level_if_valid(CONFIG_TQ_KEY_PRINT_PIN, BOOST_OFF_LEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
static void pulse_latch_locked(void) {
|
||||
set_gpio_level_if_valid(CONFIG_TQ_PRINT_LAT_PIN, 0);
|
||||
esp_rom_delay_us(1);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_PRINT_LAT_PIN, 1);
|
||||
}
|
||||
|
||||
static void fire_strobe_locked(uint16_t on_us, uint16_t interval_us) {
|
||||
set_stb_level_locked(1);
|
||||
esp_rom_delay_us(on_us);
|
||||
set_stb_level_locked(0);
|
||||
if (interval_us > 0) {
|
||||
esp_rom_delay_us(interval_us);
|
||||
}
|
||||
}
|
||||
|
||||
static void motor_step_once_locked(uint16_t step_delay_us) {
|
||||
uint8_t pattern = k_motor_step_table[s_state.motor_phase & 0x03];
|
||||
motor_apply_pattern_locked(pattern);
|
||||
if (step_delay_us > 0) {
|
||||
esp_rom_delay_us(step_delay_us);
|
||||
}
|
||||
if (runtime_policy_direct_printer_motor_reverse()) {
|
||||
s_state.motor_phase = (uint8_t)((s_state.motor_phase + 3) & 0x03);
|
||||
} else {
|
||||
s_state.motor_phase = (uint8_t)((s_state.motor_phase + 1) & 0x03);
|
||||
}
|
||||
}
|
||||
|
||||
static void write_line_bits_locked(const uint8_t *line, size_t line_bytes) {
|
||||
for (size_t i = 0; i < line_bytes; ++i) {
|
||||
uint8_t byte = line[i];
|
||||
for (int bit = 7; bit >= 0; --bit) {
|
||||
int pixel = ((byte >> bit) & 0x01) ? 1 : 0;
|
||||
set_gpio_level_if_valid(CONFIG_TQ_SPI_SHARED_MOSI_PIN, pixel);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_SPI_SHARED_CLK_PIN, 1);
|
||||
esp_rom_delay_us(1);
|
||||
set_gpio_level_if_valid(CONFIG_TQ_SPI_SHARED_CLK_PIN, 0);
|
||||
esp_rom_delay_us(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t ensure_adc_unit(adc_unit_t unit, adc_oneshot_unit_handle_t *out_handle) {
|
||||
if (out_handle == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
adc_oneshot_unit_handle_t *slot = NULL;
|
||||
if (unit == ADC_UNIT_1) {
|
||||
slot = &s_state.adc1_handle;
|
||||
} else if (unit == ADC_UNIT_2) {
|
||||
slot = &s_state.adc2_handle;
|
||||
} else {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (*slot == NULL) {
|
||||
adc_oneshot_unit_init_cfg_t cfg = {
|
||||
.unit_id = unit,
|
||||
.clk_src = ADC_RTC_CLK_SRC_DEFAULT,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
esp_err_t err = adc_oneshot_new_unit(&cfg, slot);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "adc unit init failed: unit=%d err=%s", (int)unit, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
*out_handle = *slot;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void init_adc_pin_if_possible(int gpio_num, const char *name, adc_pin_t *out_pin) {
|
||||
if (out_pin == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(out_pin, 0, sizeof(*out_pin));
|
||||
if (!is_gpio_valid(gpio_num)) {
|
||||
return;
|
||||
}
|
||||
|
||||
adc_unit_t unit = ADC_UNIT_1;
|
||||
adc_channel_t channel = ADC_CHANNEL_0;
|
||||
esp_err_t err = adc_oneshot_io_to_channel(gpio_num, &unit, &channel);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "adc gpio unsupported: %s pin=%d err=%s", name, gpio_num, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
adc_oneshot_unit_handle_t handle = NULL;
|
||||
err = ensure_adc_unit(unit, &handle);
|
||||
if (err != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
adc_oneshot_chan_cfg_t chan_cfg = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
err = adc_oneshot_config_channel(handle, channel, &chan_cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "adc channel config failed: %s pin=%d err=%s", name, gpio_num, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
out_pin->ready = true;
|
||||
out_pin->unit = unit;
|
||||
out_pin->channel = channel;
|
||||
}
|
||||
|
||||
static esp_err_t read_adc_raw_locked(const adc_pin_t *pin, int *out_raw) {
|
||||
if (pin == NULL || out_raw == NULL || !pin->ready) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
adc_oneshot_unit_handle_t handle = NULL;
|
||||
if (pin->unit == ADC_UNIT_1) {
|
||||
handle = s_state.adc1_handle;
|
||||
} else if (pin->unit == ADC_UNIT_2) {
|
||||
handle = s_state.adc2_handle;
|
||||
}
|
||||
|
||||
if (handle == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return adc_oneshot_read(handle, pin->channel, out_raw);
|
||||
}
|
||||
|
||||
static bool read_paper_present_locked(int *out_level) {
|
||||
if (!is_gpio_valid(CONFIG_TQ_PRINT_PAPER_PIN)) {
|
||||
if (out_level != NULL) {
|
||||
*out_level = -1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
int level = gpio_get_level(CONFIG_TQ_PRINT_PAPER_PIN);
|
||||
if (out_level != NULL) {
|
||||
*out_level = level;
|
||||
}
|
||||
return level == (int)runtime_policy_direct_printer_paper_present_level();
|
||||
}
|
||||
|
||||
static uint16_t battery_raw_to_mv(int raw) {
|
||||
if (raw < 0) {
|
||||
raw = 0;
|
||||
}
|
||||
if (raw > ADC_RAW_MAX) {
|
||||
raw = ADC_RAW_MAX;
|
||||
}
|
||||
float adc_mv = ((float)raw * 3300.0f) / (float)ADC_RAW_MAX;
|
||||
float ratio = (float)runtime_policy_direct_printer_battery_divider_ratio_x1000() / 1000.0f;
|
||||
float batt_mv = adc_mv * ratio;
|
||||
if (batt_mv < 0.0f) {
|
||||
batt_mv = 0.0f;
|
||||
}
|
||||
if (batt_mv > 65535.0f) {
|
||||
batt_mv = 65535.0f;
|
||||
}
|
||||
return (uint16_t)batt_mv;
|
||||
}
|
||||
|
||||
static uint8_t battery_mv_to_percent(uint16_t batt_mv) {
|
||||
uint16_t empty_mv = runtime_policy_direct_printer_battery_empty_mv();
|
||||
uint16_t full_mv = runtime_policy_direct_printer_battery_full_mv();
|
||||
if (full_mv <= empty_mv) {
|
||||
return 100;
|
||||
}
|
||||
if (batt_mv <= empty_mv) {
|
||||
return 0;
|
||||
}
|
||||
if (batt_mv >= full_mv) {
|
||||
return 100;
|
||||
}
|
||||
return (uint8_t)(((uint32_t)(batt_mv - empty_mv) * 100u) / (uint32_t)(full_mv - empty_mv));
|
||||
}
|
||||
|
||||
static float ntc_raw_to_temp_c(int raw) {
|
||||
if (raw <= 0 || raw >= ADC_RAW_MAX) {
|
||||
return 25.0f;
|
||||
}
|
||||
|
||||
float pullup = (float)runtime_policy_direct_printer_ntc_pullup_ohms();
|
||||
float r25 = (float)runtime_policy_direct_printer_ntc_r25_ohms();
|
||||
float beta = (float)runtime_policy_direct_printer_ntc_beta();
|
||||
if (pullup <= 0.0f || r25 <= 0.0f || beta <= 0.0f) {
|
||||
return 25.0f;
|
||||
}
|
||||
|
||||
float ratio = (float)raw / (float)(ADC_RAW_MAX - raw);
|
||||
float r_ntc = pullup * ratio;
|
||||
if (r_ntc <= 1.0f) {
|
||||
return 25.0f;
|
||||
}
|
||||
|
||||
float inv_t = (1.0f / (273.15f + 25.0f)) + (logf(r_ntc / r25) / beta);
|
||||
if (inv_t <= 0.0f) {
|
||||
return 25.0f;
|
||||
}
|
||||
return (1.0f / inv_t) - 273.15f;
|
||||
}
|
||||
|
||||
static void sample_sensors_locked(platform_printer_sensors_t *out_sensors) {
|
||||
if (out_sensors == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(out_sensors, 0, sizeof(*out_sensors));
|
||||
int paper_level = -1;
|
||||
out_sensors->has_paper = read_paper_present_locked(&paper_level);
|
||||
out_sensors->paper_gpio_level = (int8_t)paper_level;
|
||||
out_sensors->paper_present_level = runtime_policy_direct_printer_paper_present_level();
|
||||
out_sensors->battery_percent = 100;
|
||||
out_sensors->temperature_c = 25.0f;
|
||||
out_sensors->updated_ms = esp_timer_get_time() / 1000;
|
||||
|
||||
int battery_raw = 0;
|
||||
if (read_adc_raw_locked(&s_state.battery_adc, &battery_raw) == ESP_OK) {
|
||||
uint16_t batt_mv = battery_raw_to_mv(battery_raw);
|
||||
out_sensors->battery_percent = battery_mv_to_percent(batt_mv);
|
||||
}
|
||||
|
||||
int ntc_raw = 0;
|
||||
if (read_adc_raw_locked(&s_state.ntc_adc, &ntc_raw) == ESP_OK) {
|
||||
out_sensors->temperature_c = ntc_raw_to_temp_c(ntc_raw);
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t precheck_before_print_locked(char *err, size_t err_len) {
|
||||
platform_printer_sensors_t sensors = {0};
|
||||
sample_sensors_locked(&sensors);
|
||||
|
||||
if (!sensors.has_paper) {
|
||||
write_err(err, err_len, "printer out of paper");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
float temp_min = runtime_policy_direct_printer_temp_min_c();
|
||||
float temp_max = runtime_policy_direct_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) {
|
||||
write_err(err, err_len, "temperature out of range");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint8_t battery_min = runtime_policy_direct_printer_battery_min_percent();
|
||||
if (sensors.battery_percent < battery_min) {
|
||||
write_err(err, err_len, "battery too low");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static bool has_timed_out(int64_t deadline_ms) {
|
||||
if (deadline_ms <= 0) {
|
||||
return false;
|
||||
}
|
||||
return (esp_timer_get_time() / 1000) > deadline_ms;
|
||||
}
|
||||
|
||||
esp_err_t platform_direct_printer_init(void) {
|
||||
#if !CONFIG_TQ_DIRECT_PRINTER_ENABLE
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
if (s_state.initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
memset(&s_state, 0, sizeof(s_state));
|
||||
s_state.lock = xSemaphoreCreateMutex();
|
||||
if (s_state.lock == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_err_t err = ESP_OK;
|
||||
err = config_output_pin(CONFIG_TQ_PRINT_STB_12_PIN, 0, "print_stb12");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = config_output_pin(CONFIG_TQ_PRINT_STB_34_PIN, 0, "print_stb34");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = config_output_pin(CONFIG_TQ_PRINT_STB_56_PIN, 0, "print_stb56");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = config_output_pin(CONFIG_TQ_PRINT_LAT_PIN, 1, "print_lat");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = config_output_pin(CONFIG_TQ_SPI_SHARED_MOSI_PIN, 0, "print_mosi");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = config_output_pin(CONFIG_TQ_SPI_SHARED_CLK_PIN, 0, "print_clk");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = config_output_pin(CONFIG_TQ_PRINT_OUTA_P_PIN, 0, "print_outa_p");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = config_output_pin(CONFIG_TQ_PRINT_OUTA_N_PIN, 0, "print_outa_n");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = config_output_pin(CONFIG_TQ_PRINT_OUTB_P_PIN, 0, "print_outb_p");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = config_output_pin(CONFIG_TQ_PRINT_OUTB_N_PIN, 0, "print_outb_n");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = config_output_pin(CONFIG_TQ_KEY_PRINT_PIN, BOOST_OFF_LEVEL, "print_boost");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = config_input_pin(CONFIG_TQ_PRINT_PAPER_PIN, true, "print_paper");
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
init_adc_pin_if_possible(CONFIG_TQ_BATTERY_ADC_PIN, "battery_adc", &s_state.battery_adc);
|
||||
init_adc_pin_if_possible(CONFIG_TQ_NTC_ADC_PIN, "ntc_adc", &s_state.ntc_adc);
|
||||
|
||||
s_state.motor_phase = 0;
|
||||
s_state.connected = false;
|
||||
s_state.initialized = true;
|
||||
ESP_LOGI(TAG, "direct printer initialized");
|
||||
return ESP_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
void platform_direct_printer_deinit(void) {
|
||||
if (!s_state.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_state.lock != NULL && xSemaphoreTake(s_state.lock, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
safe_drive_off_locked(false);
|
||||
s_state.connected = false;
|
||||
xSemaphoreGive(s_state.lock);
|
||||
}
|
||||
|
||||
if (s_state.adc1_handle != NULL) {
|
||||
(void)adc_oneshot_del_unit(s_state.adc1_handle);
|
||||
s_state.adc1_handle = NULL;
|
||||
}
|
||||
if (s_state.adc2_handle != NULL) {
|
||||
(void)adc_oneshot_del_unit(s_state.adc2_handle);
|
||||
s_state.adc2_handle = NULL;
|
||||
}
|
||||
if (s_state.lock != NULL) {
|
||||
vSemaphoreDelete(s_state.lock);
|
||||
}
|
||||
memset(&s_state, 0, sizeof(s_state));
|
||||
}
|
||||
|
||||
esp_err_t platform_direct_printer_connect(uint32_t timeout_ms) {
|
||||
(void)timeout_ms;
|
||||
#if !CONFIG_TQ_DIRECT_PRINTER_ENABLE
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
esp_err_t err = platform_direct_printer_init();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(s_state.lock, pdMS_TO_TICKS(500)) != pdTRUE) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (!s_state.connected) {
|
||||
set_gpio_level_if_valid(CONFIG_TQ_KEY_PRINT_PIN, BOOST_ON_LEVEL);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
safe_drive_off_locked(true);
|
||||
s_state.connected = true;
|
||||
}
|
||||
|
||||
xSemaphoreGive(s_state.lock);
|
||||
return ESP_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
void platform_direct_printer_disconnect(void) {
|
||||
if (!s_state.initialized || s_state.lock == NULL) {
|
||||
return;
|
||||
}
|
||||
if (xSemaphoreTake(s_state.lock, pdMS_TO_TICKS(500)) != pdTRUE) {
|
||||
return;
|
||||
}
|
||||
safe_drive_off_locked(false);
|
||||
s_state.connected = false;
|
||||
xSemaphoreGive(s_state.lock);
|
||||
}
|
||||
|
||||
bool platform_direct_printer_is_connected(void) {
|
||||
if (!s_state.initialized || s_state.lock == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (xSemaphoreTake(s_state.lock, pdMS_TO_TICKS(100)) != pdTRUE) {
|
||||
return false;
|
||||
}
|
||||
bool connected = s_state.connected;
|
||||
xSemaphoreGive(s_state.lock);
|
||||
return connected;
|
||||
}
|
||||
|
||||
esp_err_t platform_direct_printer_get_sensors(platform_printer_sensors_t *out_sensors) {
|
||||
if (out_sensors == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!s_state.initialized || s_state.lock == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (xSemaphoreTake(s_state.lock, pdMS_TO_TICKS(500)) != pdTRUE) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
sample_sensors_locked(out_sensors);
|
||||
xSemaphoreGive(s_state.lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t platform_direct_printer_print(const platform_direct_print_request_t *request,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
#if !CONFIG_TQ_DIRECT_PRINTER_ENABLE
|
||||
write_err(err, err_len, "direct backend disabled");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
if (request == NULL || request->raster == NULL || request->width == 0 || request->height == 0) {
|
||||
write_err(err, err_len, "invalid args");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (request->width != 384) {
|
||||
write_err(err, err_len, "width must be 384");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
const size_t line_bytes = (size_t)request->width / 8u;
|
||||
const size_t expected_len = line_bytes * (size_t)request->height;
|
||||
if (request->raster_len != expected_len) {
|
||||
write_err(err, err_len, "raster size mismatch");
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (!s_state.initialized || s_state.lock == NULL) {
|
||||
write_err(err, err_len, "direct backend not initialized");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint32_t timeout_ms = request->timeout_ms;
|
||||
if (timeout_ms == 0) {
|
||||
timeout_ms = runtime_policy_direct_printer_operation_timeout_ms();
|
||||
}
|
||||
|
||||
const uint16_t strobe_on_us = (request->strobe_on_time_us > 0)
|
||||
? request->strobe_on_time_us
|
||||
: runtime_policy_direct_printer_strobe_on_us();
|
||||
const uint16_t strobe_interval_us = (request->strobe_interval_us > 0)
|
||||
? request->strobe_interval_us
|
||||
: runtime_policy_direct_printer_strobe_interval_us();
|
||||
const uint16_t motor_step_us = (request->motor_step_delay_us > 0)
|
||||
? request->motor_step_delay_us
|
||||
: runtime_policy_direct_printer_motor_step_us();
|
||||
const uint8_t steps_per_line = (request->motor_steps_per_line > 0)
|
||||
? request->motor_steps_per_line
|
||||
: runtime_policy_direct_printer_steps_per_line();
|
||||
|
||||
if (xSemaphoreTake(s_state.lock, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||
write_err(err, err_len, "direct printer lock timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (!s_state.connected) {
|
||||
xSemaphoreGive(s_state.lock);
|
||||
write_err(err, err_len, "direct printer not connected");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t rc = ESP_OK;
|
||||
if (!request->ignore_precheck) {
|
||||
rc = precheck_before_print_locked(err, err_len);
|
||||
if (rc != ESP_OK) {
|
||||
safe_drive_off_locked(true);
|
||||
xSemaphoreGive(s_state.lock);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t deadline_ms = (int64_t)(esp_timer_get_time() / 1000) + (int64_t)timeout_ms;
|
||||
|
||||
for (uint16_t line = 0; line < request->height; ++line) {
|
||||
if (request->cancel_flag != NULL && *request->cancel_flag) {
|
||||
write_err(err, err_len, "job canceled");
|
||||
rc = ESP_ERR_INVALID_STATE;
|
||||
break;
|
||||
}
|
||||
if (has_timed_out(deadline_ms)) {
|
||||
write_err(err, err_len, "print timeout");
|
||||
rc = ESP_ERR_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!request->ignore_precheck && (line & 0x0F) == 0) {
|
||||
rc = precheck_before_print_locked(err, err_len);
|
||||
if (rc != ESP_OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t *line_ptr = &request->raster[(size_t)line * line_bytes];
|
||||
write_line_bits_locked(line_ptr, line_bytes);
|
||||
pulse_latch_locked();
|
||||
fire_strobe_locked(strobe_on_us, strobe_interval_us);
|
||||
|
||||
for (uint8_t i = 0; i < steps_per_line; ++i) {
|
||||
if (request->cancel_flag != NULL && *request->cancel_flag) {
|
||||
write_err(err, err_len, "job canceled");
|
||||
rc = ESP_ERR_INVALID_STATE;
|
||||
break;
|
||||
}
|
||||
if (has_timed_out(deadline_ms)) {
|
||||
write_err(err, err_len, "print timeout");
|
||||
rc = ESP_ERR_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
motor_step_once_locked(motor_step_us);
|
||||
}
|
||||
if (rc != ESP_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Yield periodically so IDLE task can run and service task WDT on the same CPU.
|
||||
if (((line + 1u) % k_print_yield_lines) == 0u) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
safe_drive_off_locked(true);
|
||||
xSemaphoreGive(s_state.lock);
|
||||
return rc;
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_err_t platform_direct_printer_gap_move(uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
#if !CONFIG_TQ_DIRECT_PRINTER_ENABLE
|
||||
write_err(err, err_len, "direct backend disabled");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
if (!s_state.initialized || s_state.lock == NULL) {
|
||||
write_err(err, err_len, "direct backend not initialized");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (timeout_ms == 0) {
|
||||
timeout_ms = runtime_policy_direct_printer_operation_timeout_ms();
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(s_state.lock, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||
write_err(err, err_len, "direct printer lock timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
if (!s_state.connected) {
|
||||
xSemaphoreGive(s_state.lock);
|
||||
write_err(err, err_len, "direct printer not connected");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t rc = ESP_OK;
|
||||
uint16_t steps = runtime_policy_direct_printer_gap_steps();
|
||||
uint16_t step_delay_us = runtime_policy_direct_printer_motor_step_us();
|
||||
platform_printer_sensors_t sensors = {0};
|
||||
sample_sensors_locked(&sensors);
|
||||
|
||||
uint8_t battery_min = runtime_policy_direct_printer_battery_min_percent();
|
||||
if (sensors.battery_percent < battery_min) {
|
||||
write_err(err, err_len, "battery too low");
|
||||
safe_drive_off_locked(true);
|
||||
xSemaphoreGive(s_state.lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// Per JX-2R-01 guidance: out-of-paper feed should run at reduced speed (roughly <=600 PPS).
|
||||
if (!sensors.has_paper && step_delay_us < 1667) {
|
||||
step_delay_us = 1667;
|
||||
}
|
||||
if (!sensors.has_paper) {
|
||||
ESP_LOGW(TAG,
|
||||
"gap move while out-of-paper, gpio_level=%d expect=%u step_us=%u",
|
||||
sensors.paper_gpio_level,
|
||||
sensors.paper_present_level,
|
||||
step_delay_us);
|
||||
}
|
||||
|
||||
int64_t deadline_ms = (int64_t)(esp_timer_get_time() / 1000) + (int64_t)timeout_ms;
|
||||
|
||||
for (uint16_t i = 0; i < steps; ++i) {
|
||||
if (has_timed_out(deadline_ms)) {
|
||||
write_err(err, err_len, "gap move timeout");
|
||||
rc = ESP_ERR_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
motor_step_once_locked(step_delay_us);
|
||||
if (((i + 1u) % k_gap_yield_steps) == 0u) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
safe_drive_off_locked(true);
|
||||
xSemaphoreGive(s_state.lock);
|
||||
return rc;
|
||||
#endif
|
||||
}
|
||||
@@ -34,6 +34,82 @@
|
||||
#define CONFIG_TQ_PRINTER_STOP_TIMEOUT_MS 8000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_PRINTER_DEFAULT_BACKEND
|
||||
#define CONFIG_TQ_PRINTER_DEFAULT_BACKEND 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_ENABLE
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_ENABLE 1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_OPERATION_TIMEOUT_MS
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_OPERATION_TIMEOUT_MS 90000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_STROBE_ON_US
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_STROBE_ON_US 1000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_STROBE_INTERVAL_US
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_STROBE_INTERVAL_US 200
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_MOTOR_STEP_US
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_MOTOR_STEP_US 2000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_MOTOR_REVERSE
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_MOTOR_REVERSE 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_STEPS_PER_LINE
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_STEPS_PER_LINE 2
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_GAP_STEPS
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_GAP_STEPS 96
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_TEMP_MIN_C
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_TEMP_MIN_C 15
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_TEMP_MAX_C
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_TEMP_MAX_C 55
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_BATTERY_MIN_PERCENT
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_BATTERY_MIN_PERCENT 5
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_PAPER_PRESENT_LEVEL
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_PAPER_PRESENT_LEVEL 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_BATTERY_EMPTY_MV
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_BATTERY_EMPTY_MV 3300
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_BATTERY_FULL_MV
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_BATTERY_FULL_MV 4200
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_BATTERY_DIVIDER_RATIO_X1000
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_BATTERY_DIVIDER_RATIO_X1000 2000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_NTC_PULLUP_OHMS
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_NTC_PULLUP_OHMS 10000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_NTC_R25_OHMS
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_NTC_R25_OHMS 10000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_DIRECT_PRINTER_NTC_BETA
|
||||
#define CONFIG_TQ_DIRECT_PRINTER_NTC_BETA 3950
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TQ_REST_PRINTER_CONNECT_TIMEOUT_MS
|
||||
#define CONFIG_TQ_REST_PRINTER_CONNECT_TIMEOUT_MS 15000
|
||||
#endif
|
||||
@@ -64,6 +140,26 @@ static uint32_t clamp_u32(uint32_t value, uint32_t min_value, uint32_t max_value
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint16_t clamp_u16(uint16_t value, uint16_t min_value, uint16_t max_value) {
|
||||
if (value < min_value) {
|
||||
return min_value;
|
||||
}
|
||||
if (value > max_value) {
|
||||
return max_value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint8_t clamp_u8(uint8_t value, uint8_t min_value, uint8_t max_value) {
|
||||
if (value < min_value) {
|
||||
return min_value;
|
||||
}
|
||||
if (value > max_value) {
|
||||
return max_value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t runtime_policy_wifi_connect_timeout_ms(void) {
|
||||
return clamp_u32(CONFIG_TQ_WIFI_CONNECT_TIMEOUT_MS, 3000, 60000);
|
||||
}
|
||||
@@ -105,6 +201,87 @@ uint32_t runtime_policy_printer_stop_timeout_ms(void) {
|
||||
return clamp_u32(CONFIG_TQ_PRINTER_STOP_TIMEOUT_MS, 1000, 30000);
|
||||
}
|
||||
|
||||
uint8_t runtime_policy_printer_default_backend(void) {
|
||||
return (uint8_t)clamp_u32(CONFIG_TQ_PRINTER_DEFAULT_BACKEND, 0, 1);
|
||||
}
|
||||
|
||||
bool runtime_policy_direct_printer_enabled(void) {
|
||||
return CONFIG_TQ_DIRECT_PRINTER_ENABLE != 0;
|
||||
}
|
||||
|
||||
uint32_t runtime_policy_direct_printer_operation_timeout_ms(void) {
|
||||
return clamp_u32(CONFIG_TQ_DIRECT_PRINTER_OPERATION_TIMEOUT_MS, 1000, 300000);
|
||||
}
|
||||
|
||||
uint16_t runtime_policy_direct_printer_strobe_on_us(void) {
|
||||
return clamp_u16((uint16_t)CONFIG_TQ_DIRECT_PRINTER_STROBE_ON_US, 100, 10000);
|
||||
}
|
||||
|
||||
uint16_t runtime_policy_direct_printer_strobe_interval_us(void) {
|
||||
return clamp_u16((uint16_t)CONFIG_TQ_DIRECT_PRINTER_STROBE_INTERVAL_US, 0, 10000);
|
||||
}
|
||||
|
||||
uint16_t runtime_policy_direct_printer_motor_step_us(void) {
|
||||
return clamp_u16((uint16_t)CONFIG_TQ_DIRECT_PRINTER_MOTOR_STEP_US, 100, 20000);
|
||||
}
|
||||
|
||||
bool runtime_policy_direct_printer_motor_reverse(void) {
|
||||
return CONFIG_TQ_DIRECT_PRINTER_MOTOR_REVERSE != 0;
|
||||
}
|
||||
|
||||
uint8_t runtime_policy_direct_printer_steps_per_line(void) {
|
||||
return clamp_u8((uint8_t)CONFIG_TQ_DIRECT_PRINTER_STEPS_PER_LINE, 1, 8);
|
||||
}
|
||||
|
||||
uint16_t runtime_policy_direct_printer_gap_steps(void) {
|
||||
return clamp_u16((uint16_t)CONFIG_TQ_DIRECT_PRINTER_GAP_STEPS, 1, 2000);
|
||||
}
|
||||
|
||||
float runtime_policy_direct_printer_temp_min_c(void) {
|
||||
return (float)clamp_u32((uint32_t)CONFIG_TQ_DIRECT_PRINTER_TEMP_MIN_C, 0, 120);
|
||||
}
|
||||
|
||||
float runtime_policy_direct_printer_temp_max_c(void) {
|
||||
return (float)clamp_u32((uint32_t)CONFIG_TQ_DIRECT_PRINTER_TEMP_MAX_C, 1, 150);
|
||||
}
|
||||
|
||||
uint8_t runtime_policy_direct_printer_battery_min_percent(void) {
|
||||
return clamp_u8((uint8_t)CONFIG_TQ_DIRECT_PRINTER_BATTERY_MIN_PERCENT, 0, 100);
|
||||
}
|
||||
|
||||
uint8_t runtime_policy_direct_printer_paper_present_level(void) {
|
||||
return clamp_u8((uint8_t)CONFIG_TQ_DIRECT_PRINTER_PAPER_PRESENT_LEVEL, 0, 1);
|
||||
}
|
||||
|
||||
uint16_t runtime_policy_direct_printer_battery_empty_mv(void) {
|
||||
return clamp_u16((uint16_t)CONFIG_TQ_DIRECT_PRINTER_BATTERY_EMPTY_MV, 2500, 5000);
|
||||
}
|
||||
|
||||
uint16_t runtime_policy_direct_printer_battery_full_mv(void) {
|
||||
uint16_t empty_mv = runtime_policy_direct_printer_battery_empty_mv();
|
||||
uint16_t full_mv = clamp_u16((uint16_t)CONFIG_TQ_DIRECT_PRINTER_BATTERY_FULL_MV, 2600, 6000);
|
||||
if (full_mv <= empty_mv) {
|
||||
full_mv = (uint16_t)(empty_mv + 100);
|
||||
}
|
||||
return full_mv;
|
||||
}
|
||||
|
||||
uint16_t runtime_policy_direct_printer_battery_divider_ratio_x1000(void) {
|
||||
return clamp_u16((uint16_t)CONFIG_TQ_DIRECT_PRINTER_BATTERY_DIVIDER_RATIO_X1000, 1000, 10000);
|
||||
}
|
||||
|
||||
uint32_t runtime_policy_direct_printer_ntc_pullup_ohms(void) {
|
||||
return clamp_u32(CONFIG_TQ_DIRECT_PRINTER_NTC_PULLUP_OHMS, 1000, 200000);
|
||||
}
|
||||
|
||||
uint32_t runtime_policy_direct_printer_ntc_r25_ohms(void) {
|
||||
return clamp_u32(CONFIG_TQ_DIRECT_PRINTER_NTC_R25_OHMS, 1000, 200000);
|
||||
}
|
||||
|
||||
uint32_t runtime_policy_direct_printer_ntc_beta(void) {
|
||||
return clamp_u32(CONFIG_TQ_DIRECT_PRINTER_NTC_BETA, 1000, 10000);
|
||||
}
|
||||
|
||||
uint32_t runtime_policy_rest_printer_connect_timeout_ms(void) {
|
||||
return clamp_u32(CONFIG_TQ_REST_PRINTER_CONNECT_TIMEOUT_MS, 1000, 60000);
|
||||
}
|
||||
|
||||
@@ -71,6 +71,116 @@ config TQ_PRINTER_STOP_TIMEOUT_MS
|
||||
range 1000 30000
|
||||
default 8000
|
||||
|
||||
config TQ_PRINTER_DEFAULT_BACKEND
|
||||
int "Printer default backend (0=BLE, 1=Direct)"
|
||||
range 0 1
|
||||
default 0
|
||||
|
||||
config TQ_DIRECT_PRINTER_ENABLE
|
||||
bool "Enable direct thermal printer backend"
|
||||
default y
|
||||
|
||||
config TQ_DIRECT_PRINTER_OPERATION_TIMEOUT_MS
|
||||
int "Direct printer max operation timeout (ms)"
|
||||
range 1000 300000
|
||||
default 90000
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_STROBE_ON_US
|
||||
int "Direct printer strobe on-time (us)"
|
||||
range 100 10000
|
||||
default 1000
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_STROBE_INTERVAL_US
|
||||
int "Direct printer strobe interval (us)"
|
||||
range 0 10000
|
||||
default 200
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_MOTOR_STEP_US
|
||||
int "Direct printer motor step delay (us)"
|
||||
range 100 20000
|
||||
default 2000
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_MOTOR_REVERSE
|
||||
bool "Direct printer motor direction reversed"
|
||||
default n
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_STEPS_PER_LINE
|
||||
int "Direct printer motor steps per printed line"
|
||||
range 1 8
|
||||
default 2
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_GAP_STEPS
|
||||
int "Direct printer gap move steps"
|
||||
range 1 2000
|
||||
default 96
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_TEMP_MIN_C
|
||||
int "Direct printer allowed minimum temperature (C)"
|
||||
range 0 120
|
||||
default 15
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_TEMP_MAX_C
|
||||
int "Direct printer allowed maximum temperature (C)"
|
||||
range 1 150
|
||||
default 55
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_BATTERY_MIN_PERCENT
|
||||
int "Direct printer minimum battery percent"
|
||||
range 0 100
|
||||
default 5
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_PAPER_PRESENT_LEVEL
|
||||
int "Direct printer paper-present GPIO level (0/1)"
|
||||
range 0 1
|
||||
default 0
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_BATTERY_EMPTY_MV
|
||||
int "Direct printer battery empty voltage (mV)"
|
||||
range 2500 5000
|
||||
default 3300
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_BATTERY_FULL_MV
|
||||
int "Direct printer battery full voltage (mV)"
|
||||
range 2600 6000
|
||||
default 4200
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_BATTERY_DIVIDER_RATIO_X1000
|
||||
int "Direct printer battery divider ratio x1000"
|
||||
range 1000 10000
|
||||
default 2000
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_NTC_PULLUP_OHMS
|
||||
int "Direct printer NTC pull-up resistor (ohms)"
|
||||
range 1000 200000
|
||||
default 10000
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_NTC_R25_OHMS
|
||||
int "Direct printer NTC R25 resistance (ohms)"
|
||||
range 1000 200000
|
||||
default 10000
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_DIRECT_PRINTER_NTC_BETA
|
||||
int "Direct printer NTC beta constant"
|
||||
range 1000 10000
|
||||
default 3950
|
||||
depends on TQ_DIRECT_PRINTER_ENABLE
|
||||
|
||||
config TQ_API_KEY
|
||||
string "REST API Key (optional, empty means disabled)"
|
||||
default ""
|
||||
|
||||
Reference in New Issue
Block a user