diff --git a/.gitignore b/.gitignore
index 776aeec..eef899c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ __pycache__/
.pytest_cache/
managed_components
+JX_2R_01测试程序_STM32F103RF
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 191e7bc..235fa80 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,7 +1,7 @@
{
"idf.currentSetup": "/Users/moyyang/esp/v5.5.2/esp-idf",
"idf.flashType": "UART",
- "idf.port": "/dev/tty.wchusbserial5B0B0251771",
+ "idf.port": "/dev/tty.usbserial-110",
"idf.openOcdConfigs": [
"board/esp32s3-builtin.cfg"
],
diff --git a/components/control_plane/internal/rest_server_internal.h b/components/control_plane/internal/rest_server_internal.h
index 330d2b2..be010f9 100644
--- a/components/control_plane/internal/rest_server_internal.h
+++ b/components/control_plane/internal/rest_server_internal.h
@@ -30,6 +30,8 @@ esp_err_t rest_server_health_get(httpd_req_t *req);
esp_err_t rest_server_connect_post(httpd_req_t *req);
esp_err_t rest_server_disconnect_post(httpd_req_t *req);
esp_err_t rest_server_status_get(httpd_req_t *req);
+esp_err_t rest_server_printer_direct_debug_get(httpd_req_t *req);
+esp_err_t rest_server_printer_direct_debug_post(httpd_req_t *req);
esp_err_t rest_server_print_raster_post(httpd_req_t *req);
esp_err_t rest_server_print_image_post(httpd_req_t *req);
diff --git a/components/control_plane/src/rest_server.c b/components/control_plane/src/rest_server.c
index 95e78ae..85c9b84 100644
--- a/components/control_plane/src/rest_server.c
+++ b/components/control_plane/src/rest_server.c
@@ -78,6 +78,57 @@ static const char *s_web_index =
" \n"
"
Only effective on Direct backend. Use for sensor bring-up/debug.
\n"
"\n"
+ " \n"
+ " 参数标签中的字段名与 /v1/printer/direct/debug 的 JSON 键保持一致。
\n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ "
\n"
+ " override_* 参数填 0 表示不覆盖,恢复为运行时默认策略。
\n"
+ " Auto Diagnose 使用低功耗测试图案;若点击后仍重启(Brownout),请先改善供电或降低热头功率。
\n"
+ "\n"
" \n"
" \n"
" \n"
@@ -155,6 +206,16 @@ static const char *s_web_index =
" const imgScaleToWidthEl = document.getElementById('imgScaleToWidth');\n"
" const imgInvertEl = document.getElementById('imgInvert');\n"
" const imgPromptExtendEl = document.getElementById('imgPromptExtend');\n"
+ " const dbgShiftHighUsEl = document.getElementById('dbgShiftHighUs');\n"
+ " const dbgShiftLowUsEl = document.getElementById('dbgShiftLowUs');\n"
+ " const dbgLatchUsEl = document.getElementById('dbgLatchUs');\n"
+ " const dbgHotUsEl = document.getElementById('dbgHotUs');\n"
+ " const dbgStrobeGapUsEl = document.getElementById('dbgStrobeGapUs');\n"
+ " const dbgMotorUsEl = document.getElementById('dbgMotorUs');\n"
+ " const dbgStepsPerLineEl = document.getElementById('dbgStepsPerLine');\n"
+ " const dbgStbActiveHighEl = document.getElementById('dbgStbActiveHigh');\n"
+ " const dbgBoostActiveHighEl = document.getElementById('dbgBoostActiveHigh');\n"
+ " const btnDebugAutoScanEl = document.getElementById('btnDebugAutoScan');\n"
" const btnVoiceTalkEl = document.getElementById('btnVoiceTalk');\n"
" const voiceStateEl = document.getElementById('voiceState');\n"
" const voiceErrEl = document.getElementById('voiceErr');\n"
@@ -162,6 +223,7 @@ static const char *s_web_index =
" let voiceTalkHolding = false;\n"
" let voiceTalkBusy = false;\n"
" let voiceStatusSyncing = false;\n"
+ " let debugAutoScanBusy = false;\n"
"\n"
" function log(obj) {\n"
" if (typeof obj === 'string') {\n"
@@ -200,6 +262,330 @@ static const char *s_web_index =
" return !!(ignorePrecheckEl && ignorePrecheckEl.checked);\n"
" }\n"
"\n"
+ " function fillDirectDebugForm(data) {\n"
+ " if (!data || typeof data !== 'object') {\n"
+ " return;\n"
+ " }\n"
+ " if (dbgShiftHighUsEl && data.shift_clock_high_us !== undefined) dbgShiftHighUsEl.value = String(data.shift_clock_high_us);\n"
+ " if (dbgShiftLowUsEl && data.shift_clock_low_us !== undefined) dbgShiftLowUsEl.value = String(data.shift_clock_low_us);\n"
+ " if (dbgLatchUsEl && data.latch_pulse_us !== undefined) dbgLatchUsEl.value = String(data.latch_pulse_us);\n"
+ " if (dbgHotUsEl && data.override_strobe_on_us !== undefined) dbgHotUsEl.value = String(data.override_strobe_on_us);\n"
+ " if (dbgStrobeGapUsEl && data.override_strobe_interval_us !== undefined) dbgStrobeGapUsEl.value = String(data.override_strobe_interval_us);\n"
+ " if (dbgMotorUsEl && data.override_motor_step_us !== undefined) dbgMotorUsEl.value = String(data.override_motor_step_us);\n"
+ " if (dbgStepsPerLineEl && data.override_steps_per_line !== undefined) dbgStepsPerLineEl.value = String(data.override_steps_per_line);\n"
+ " if (dbgStbActiveHighEl && data.strobe_active_high !== undefined) dbgStbActiveHighEl.checked = !!data.strobe_active_high;\n"
+ " if (dbgBoostActiveHighEl && data.boost_active_high !== undefined) dbgBoostActiveHighEl.checked = !!data.boost_active_high;\n"
+ " }\n"
+ "\n"
+ " function buildDirectDebugRequest() {\n"
+ " return {\n"
+ " shift_clock_high_us: toInt(dbgShiftHighUsEl ? dbgShiftHighUsEl.value : '5', 5, 1, 50),\n"
+ " shift_clock_low_us: toInt(dbgShiftLowUsEl ? dbgShiftLowUsEl.value : '5', 5, 1, 50),\n"
+ " latch_pulse_us: toInt(dbgLatchUsEl ? dbgLatchUsEl.value : '1', 1, 1, 50),\n"
+ " strobe_active_high: !!(dbgStbActiveHighEl && dbgStbActiveHighEl.checked),\n"
+ " boost_active_high: !!(dbgBoostActiveHighEl && dbgBoostActiveHighEl.checked),\n"
+ " override_strobe_on_us: toInt(dbgHotUsEl ? dbgHotUsEl.value : '0', 0, 0, 10000),\n"
+ " override_strobe_interval_us: toInt(dbgStrobeGapUsEl ? dbgStrobeGapUsEl.value : '0', 0, 0, 10000),\n"
+ " override_motor_step_us: toInt(dbgMotorUsEl ? dbgMotorUsEl.value : '0', 0, 0, 20000),\n"
+ " override_steps_per_line: toInt(dbgStepsPerLineEl ? dbgStepsPerLineEl.value : '0', 0, 0, 8)\n"
+ " };\n"
+ " }\n"
+ "\n"
+ " async function fetchDirectDebug() {\n"
+ " const data = await callApi('/v1/printer/direct/debug', 'GET');\n"
+ " fillDirectDebugForm(data);\n"
+ " return data;\n"
+ " }\n"
+ "\n"
+ " function sanitizeDirectDebugConfig(data) {\n"
+ " if (!data || typeof data !== 'object') {\n"
+ " return null;\n"
+ " }\n"
+ " return {\n"
+ " shift_clock_high_us: toInt(data.shift_clock_high_us, 5, 1, 50),\n"
+ " shift_clock_low_us: toInt(data.shift_clock_low_us, 5, 1, 50),\n"
+ " latch_pulse_us: toInt(data.latch_pulse_us, 1, 1, 50),\n"
+ " strobe_active_high: data.strobe_active_high === undefined ? true : !!data.strobe_active_high,\n"
+ " boost_active_high: data.boost_active_high === undefined ? true : !!data.boost_active_high,\n"
+ " override_strobe_on_us: toInt(data.override_strobe_on_us, 0, 0, 10000),\n"
+ " override_strobe_interval_us: toInt(data.override_strobe_interval_us, 0, 0, 10000),\n"
+ " override_motor_step_us: toInt(data.override_motor_step_us, 0, 0, 20000),\n"
+ " override_steps_per_line: toInt(data.override_steps_per_line, 0, 0, 8)\n"
+ " };\n"
+ " }\n"
+ "\n"
+ " function errorToText(err) {\n"
+ " if (err === null || err === undefined) {\n"
+ " return '';\n"
+ " }\n"
+ " if (typeof err === 'string') {\n"
+ " return err;\n"
+ " }\n"
+ " if (err.data && typeof err.data === 'object' && err.data.error) {\n"
+ " return String(err.data.error);\n"
+ " }\n"
+ " if (typeof err.message === 'string' && err.message) {\n"
+ " return err.message;\n"
+ " }\n"
+ " try {\n"
+ " return JSON.stringify(err);\n"
+ " } catch (_) {\n"
+ " return String(err);\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " function buildDirectDiagRasterPayload() {\n"
+ " const width = 384;\n"
+ " const height = 72;\n"
+ " const lineBytes = width / 8;\n"
+ " const bytes = new Uint8Array(lineBytes * height);\n"
+ "\n"
+ " for (let y = 0; y < height; ++y) {\n"
+ " const offset = y * lineBytes;\n"
+ " for (let i = 0; i < lineBytes; ++i) {\n"
+ " bytes[offset + i] = 0x00;\n"
+ " }\n"
+ " bytes[offset] = 0xF0;\n"
+ " bytes[offset + 1] = 0x0F;\n"
+ " bytes[offset + lineBytes - 2] = 0x0F;\n"
+ " bytes[offset + lineBytes - 1] = 0xF0;\n"
+ " if ((y % 12) === 5) {\n"
+ " for (let i = 16; i < 32; ++i) {\n"
+ " bytes[offset + i] = 0xFF;\n"
+ " }\n"
+ " }\n"
+ " if ((y % 12) === 6) {\n"
+ " for (let i = 20; i < 28; ++i) {\n"
+ " bytes[offset + i] = 0xFF;\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " let binary = '';\n"
+ " const chunk = 1024;\n"
+ " for (let i = 0; i < bytes.length; i += chunk) {\n"
+ " const part = bytes.subarray(i, i + chunk);\n"
+ " binary += String.fromCharCode(...part);\n"
+ " }\n"
+ "\n"
+ " return {\n"
+ " width,\n"
+ " height,\n"
+ " data: btoa(binary)\n"
+ " };\n"
+ " }\n"
+ "\n"
+ " async function waitJobTerminal(jobId, timeoutMs) {\n"
+ " const deadline = Date.now() + timeoutMs;\n"
+ " while (Date.now() < deadline) {\n"
+ " const info = await callApi('/v1/jobs/' + String(jobId), 'GET');\n"
+ " const state = info && info.state ? String(info.state).toLowerCase() : '';\n"
+ " if (state === 'success' || state === 'failed' || state === 'canceled') {\n"
+ " return info;\n"
+ " }\n"
+ " await sleep(500);\n"
+ " }\n"
+ " throw new Error('wait job timeout: ' + String(jobId));\n"
+ " }\n"
+ "\n"
+ " function inferDirectDiagConclusion(results) {\n"
+ " const conclusion = {\n"
+ " summary: '',\n"
+ " likely_causes: [],\n"
+ " next_actions: []\n"
+ " };\n"
+ "\n"
+ " if (!Array.isArray(results) || results.length === 0) {\n"
+ " conclusion.summary = '未执行任何测试。';\n"
+ " return conclusion;\n"
+ " }\n"
+ "\n"
+ " const visible = results.find((item) => item && item.user_visible === true);\n"
+ " if (visible) {\n"
+ " const cfg = visible.config || {};\n"
+ " conclusion.summary = '已找到可见输出参数:' + String(visible.name || visible.index || 'unknown');\n"
+ " if (cfg.boost_active_high === false) {\n"
+ " conclusion.likely_causes.push('BOOST 极性可能与默认配置相反。');\n"
+ " }\n"
+ " if (cfg.strobe_active_high === false) {\n"
+ " conclusion.likely_causes.push('STB 极性可能与默认配置相反。');\n"
+ " }\n"
+ " if (Number(cfg.override_strobe_on_us || 0) >= 2000) {\n"
+ " conclusion.likely_causes.push('默认加热点火时长偏短,热量不足。');\n"
+ " }\n"
+ " if (conclusion.likely_causes.length === 0) {\n"
+ " conclusion.likely_causes.push('直连默认时序与当前硬件存在偏差。');\n"
+ " }\n"
+ " conclusion.next_actions.push('将可见输出对应参数固定为直连默认配置,再复测文本和图片打印。');\n"
+ " return conclusion;\n"
+ " }\n"
+ "\n"
+ " const mergedError = results\n"
+ " .map((item) => String((item && (item.error || item.api_error)) || '').toLowerCase())\n"
+ " .join(' | ');\n"
+ " if (mergedError.includes('paper')) {\n"
+ " conclusion.likely_causes.push('纸张检测状态异常(缺纸或纸检测电平不匹配)。');\n"
+ " }\n"
+ " if (mergedError.includes('battery')) {\n"
+ " conclusion.likely_causes.push('电池电量过低,直连预检拦截或加热功率不足。');\n"
+ " }\n"
+ " if (mergedError.includes('temperature')) {\n"
+ " conclusion.likely_causes.push('温度超范围,直连预检拦截。');\n"
+ " }\n"
+ "\n"
+ " const successCount = results.filter((item) => String((item && item.state) || '').toLowerCase() === 'success').length;\n"
+ " if (successCount > 0) {\n"
+ " conclusion.likely_causes.push('任务执行成功但肉眼无输出,优先排查热头加热链路(BOOST、STB、热头供电与地)。');\n"
+ " conclusion.likely_causes.push('若极性扫描均无效,继续排查 MOSI/CLK/LAT 连线或排线接触问题。');\n"
+ " conclusion.next_actions.push('用万用表或示波器确认 KEY_PRINT(BOOST) 与 STB 在打印时是否出现有效电平翻转。');\n"
+ " } else {\n"
+ " conclusion.likely_causes.push('直连打印任务未成功完成,需先处理接口错误再判断打印质量。');\n"
+ " conclusion.next_actions.push('打开 Jobs 查看每条失败任务的 error 字段并逐项修复。');\n"
+ " }\n"
+ "\n"
+ " conclusion.summary = '未找到可见输出参数。';\n"
+ " return conclusion;\n"
+ " }\n"
+ "\n"
+ " async function runDirectAutoScan() {\n"
+ " if (debugAutoScanBusy) {\n"
+ " throw new Error('direct auto diagnosis is already running');\n"
+ " }\n"
+ "\n"
+ " debugAutoScanBusy = true;\n"
+ " if (btnDebugAutoScanEl) {\n"
+ " btnDebugAutoScanEl.disabled = true;\n"
+ " btnDebugAutoScanEl.textContent = 'Running Auto Diagnose...';\n"
+ " }\n"
+ "\n"
+ " const report = {\n"
+ " ok: true,\n"
+ " mode: 'direct_debug_auto_scan',\n"
+ " started_at: new Date().toISOString(),\n"
+ " tests: []\n"
+ " };\n"
+ "\n"
+ " const previousBackend = selectedPrinterBackend();\n"
+ " let originalDebug = null;\n"
+ "\n"
+ " try {\n"
+ " originalDebug = sanitizeDirectDebugConfig(await fetchDirectDebug());\n"
+ "\n"
+ " if (printerBackendEl) {\n"
+ " printerBackendEl.value = 'direct';\n"
+ " }\n"
+ "\n"
+ " const connectData = await callApi('/v1/printer/connect', 'POST', {\n"
+ " backend: 'direct',\n"
+ " name: printerNameEl.value.trim() || 'TQPrinter',\n"
+ " timeout_ms: 15000\n"
+ " });\n"
+ " syncPaperUi(connectData);\n"
+ " report.connect_backend = connectData && connectData.backend ? connectData.backend : 'direct';\n"
+ "\n"
+ " const rasterPayload = buildDirectDiagRasterPayload();\n"
+ " const makeConfig = function(strobeHigh, boostHigh, hotUs, gapUs, motorUs) {\n"
+ " return {\n"
+ " shift_clock_high_us: 5,\n"
+ " shift_clock_low_us: 5,\n"
+ " latch_pulse_us: 1,\n"
+ " strobe_active_high: strobeHigh,\n"
+ " boost_active_high: boostHigh,\n"
+ " override_strobe_on_us: hotUs,\n"
+ " override_strobe_interval_us: gapUs,\n"
+ " override_motor_step_us: motorUs,\n"
+ " override_steps_per_line: 2\n"
+ " };\n"
+ " };\n"
+ "\n"
+ " const combos = [\n"
+ " { name: '默认极性 + 点火1200us', config: makeConfig(true, true, 1200, 260, 2800) },\n"
+ " { name: '默认极性 + 点火1600us', config: makeConfig(true, true, 1600, 300, 3000) },\n"
+ " { name: '翻转 STB 极性', config: makeConfig(false, true, 1600, 300, 3000) },\n"
+ " { name: '翻转 BOOST 极性', config: makeConfig(true, false, 1600, 300, 3000) },\n"
+ " { name: '翻转 STB + BOOST 极性', config: makeConfig(false, false, 1600, 300, 3000) },\n"
+ " { name: '翻转 STB + BOOST + 点火2200us', config: makeConfig(false, false, 2200, 360, 3200) }\n"
+ " ];\n"
+ "\n"
+ " for (let i = 0; i < combos.length; ++i) {\n"
+ " const combo = combos[i];\n"
+ " const entry = {\n"
+ " index: i + 1,\n"
+ " name: combo.name,\n"
+ " config: combo.config\n"
+ " };\n"
+ "\n"
+ " try {\n"
+ " const applyData = await callApi('/v1/printer/direct/debug', 'POST', combo.config);\n"
+ " fillDirectDebugForm(applyData);\n"
+ "\n"
+ " const submit = await callApi('/v1/print/raster', 'POST', {\n"
+ " width: rasterPayload.width,\n"
+ " height: rasterPayload.height,\n"
+ " density: '中等',\n"
+ " encoding: 'base64_msb_1bpp',\n"
+ " data: rasterPayload.data,\n"
+ " ignore_precheck: true\n"
+ " });\n"
+ "\n"
+ " entry.job_id = submit.job_id;\n"
+ " const job = await waitJobTerminal(submit.job_id, 90000);\n"
+ " entry.state = job.state;\n"
+ " entry.error = job.error;\n"
+ "\n"
+ " if (String(job.state || '').toLowerCase() === 'success') {\n"
+ " const prompt = '测试 ' + String(i + 1) + '/' + String(combos.length) + ' 已完成(' + combo.name + ')。\\n是否看到了明显黑白条纹?\\n确定=看到了,取消=没看到。';\n"
+ " entry.user_visible = window.confirm(prompt);\n"
+ " report.tests.push(entry);\n"
+ " if (entry.user_visible) {\n"
+ " report.stopped_early = true;\n"
+ " report.first_visible_test = entry.index;\n"
+ " report.first_visible_name = combo.name;\n"
+ " break;\n"
+ " }\n"
+ " } else {\n"
+ " report.tests.push(entry);\n"
+ " }\n"
+ " } catch (err) {\n"
+ " entry.api_error = errorToText(err);\n"
+ " report.tests.push(entry);\n"
+ " }\n"
+ "\n"
+ " await sleep(300);\n"
+ " }\n"
+ "\n"
+ " report.conclusion = inferDirectDiagConclusion(report.tests);\n"
+ " report.finished_at = new Date().toISOString();\n"
+ " return report;\n"
+ " } catch (e) {\n"
+ " report.ok = false;\n"
+ " report.error = errorToText(e);\n"
+ " report.finished_at = new Date().toISOString();\n"
+ " return report;\n"
+ " } finally {\n"
+ " if (originalDebug != null) {\n"
+ " try {\n"
+ " const restored = await callApi('/v1/printer/direct/debug', 'POST', originalDebug);\n"
+ " fillDirectDebugForm(restored);\n"
+ " report.restored_original_debug = true;\n"
+ " } catch (_) {\n"
+ " report.restored_original_debug = false;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " if (printerBackendEl && previousBackend) {\n"
+ " printerBackendEl.value = previousBackend;\n"
+ " }\n"
+ " try { await fetchPrinterStatus(); } catch (_) {}\n"
+ "\n"
+ " debugAutoScanBusy = false;\n"
+ " if (btnDebugAutoScanEl) {\n"
+ " btnDebugAutoScanEl.disabled = false;\n"
+ " btnDebugAutoScanEl.textContent = 'Auto Diagnose (Direct)';\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "\n"
" function syncPaperUi(st) {\n"
" if (!paperStateEl || !paperMetaEl) {\n"
" return;\n"
@@ -487,6 +873,19 @@ static const char *s_web_index =
" });\n"
"\n"
" document.getElementById('btnPaperCheck').onclick = () => run(() => fetchPrinterStatus());\n"
+ " document.getElementById('btnDebugLoad').onclick = () => run(() => fetchDirectDebug());\n"
+ " document.getElementById('btnDebugApply').onclick = () => run(async () => {\n"
+ " const payload = buildDirectDebugRequest();\n"
+ " const data = await callApi('/v1/printer/direct/debug', 'POST', payload);\n"
+ " fillDirectDebugForm(data);\n"
+ " return data;\n"
+ " });\n"
+ " document.getElementById('btnDebugReset').onclick = () => run(async () => {\n"
+ " const data = await callApi('/v1/printer/direct/debug', 'POST', { reset_defaults: true });\n"
+ " fillDirectDebugForm(data);\n"
+ " return data;\n"
+ " });\n"
+ " document.getElementById('btnDebugAutoScan').onclick = () => run(() => runDirectAutoScan());\n"
"\n"
" document.getElementById('btnPrintText').onclick = () => run(() => callApi('/v1/print/text', 'POST', {\n"
" text: printTextEl.value,\n"
@@ -531,6 +930,9 @@ static const char *s_web_index =
" await fetchPrinterStatus();\n"
" } catch (_) {}\n"
" try {\n"
+ " await fetchDirectDebug();\n"
+ " } catch (_) {}\n"
+ " try {\n"
" await refreshVoiceStatus();\n"
" } catch (_) {}\n"
" })();\n"
@@ -564,7 +966,7 @@ esp_err_t rest_server_start(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = CONFIG_TQ_HTTP_PORT;
config.uri_match_fn = httpd_uri_match_wildcard;
- config.max_uri_handlers = 32;
+ config.max_uri_handlers = 36;
config.stack_size = 10240;
#if CONFIG_FREERTOS_UNICORE
config.core_id = 0;
@@ -620,6 +1022,20 @@ esp_err_t rest_server_start(void) {
};
httpd_register_uri_handler(s_server, &status);
+ httpd_uri_t direct_debug_get_uri = {
+ .uri = "/v1/printer/direct/debug",
+ .method = HTTP_GET,
+ .handler = rest_server_printer_direct_debug_get,
+ };
+ httpd_register_uri_handler(s_server, &direct_debug_get_uri);
+
+ httpd_uri_t direct_debug_post_uri = {
+ .uri = "/v1/printer/direct/debug",
+ .method = HTTP_POST,
+ .handler = rest_server_printer_direct_debug_post,
+ };
+ httpd_register_uri_handler(s_server, &direct_debug_post_uri);
+
httpd_uri_t voice_status = {
.uri = "/v1/voice/status",
.method = HTTP_GET,
diff --git a/components/control_plane/src/rest_server_ops.c b/components/control_plane/src/rest_server_ops.c
index c7c4d00..3993a36 100644
--- a/components/control_plane/src/rest_server_ops.c
+++ b/components/control_plane/src/rest_server_ops.c
@@ -265,6 +265,226 @@ esp_err_t rest_server_status_get(httpd_req_t *req) {
return err;
}
+static void add_direct_debug_json_fields(cJSON *root, const printer_direct_debug_config_t *cfg) {
+ cJSON_AddNumberToObject(root, "shift_clock_high_us", cfg->shift_clock_high_us);
+ cJSON_AddNumberToObject(root, "shift_clock_low_us", cfg->shift_clock_low_us);
+ cJSON_AddNumberToObject(root, "latch_pulse_us", cfg->latch_pulse_us);
+ cJSON_AddBoolToObject(root, "strobe_active_high", cfg->strobe_active_high);
+ cJSON_AddBoolToObject(root, "boost_active_high", cfg->boost_active_high);
+ cJSON_AddNumberToObject(root, "override_strobe_on_us", cfg->override_strobe_on_us);
+ cJSON_AddNumberToObject(root, "override_strobe_interval_us", cfg->override_strobe_interval_us);
+ cJSON_AddNumberToObject(root, "override_motor_step_us", cfg->override_motor_step_us);
+ cJSON_AddNumberToObject(root, "override_steps_per_line", cfg->override_steps_per_line);
+}
+
+static esp_err_t direct_debug_error_response(httpd_req_t *req, esp_err_t rc, const char *msg) {
+ if (rc == ESP_ERR_INVALID_ARG) {
+ return rest_server_send_error(req, "400 Bad Request", msg);
+ }
+ if (rc == ESP_ERR_NOT_SUPPORTED || rc == ESP_ERR_INVALID_STATE) {
+ return rest_server_send_error(req, "409 Conflict", msg);
+ }
+ if (rc == ESP_ERR_TIMEOUT) {
+ return rest_server_send_error(req, "504 Gateway Timeout", msg);
+ }
+ return rest_server_send_error(req, "500 Internal Server Error", msg);
+}
+
+esp_err_t rest_server_printer_direct_debug_get(httpd_req_t *req) {
+ if (!rest_server_auth_ok(req)) {
+ return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
+ }
+
+ printer_direct_debug_config_t cfg = {0};
+ char get_err[128] = {0};
+ esp_err_t rc = printer_protocol_get_direct_debug_config(&cfg, get_err, sizeof(get_err));
+ if (rc != ESP_OK) {
+ return direct_debug_error_response(req,
+ rc,
+ get_err[0] != '\0' ? get_err : "get direct debug failed");
+ }
+
+ cJSON *root = cJSON_CreateObject();
+ cJSON_AddBoolToObject(root, "ok", true);
+ cJSON_AddStringToObject(root, "backend", printer_backend_str(printer_protocol_get_backend()));
+ add_direct_debug_json_fields(root, &cfg);
+
+ esp_err_t err = rest_server_send_json(req, "200 OK", root);
+ cJSON_Delete(root);
+ return err;
+}
+
+esp_err_t rest_server_printer_direct_debug_post(httpd_req_t *req) {
+ if (!rest_server_auth_ok(req)) {
+ return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
+ }
+
+ printer_direct_debug_config_t cfg = {0};
+ char get_err[128] = {0};
+ esp_err_t rc = printer_protocol_get_direct_debug_config(&cfg, get_err, sizeof(get_err));
+ if (rc != ESP_OK) {
+ return direct_debug_error_response(req,
+ rc,
+ get_err[0] != '\0' ? get_err : "get direct debug failed");
+ }
+
+ bool reset_defaults = false;
+ bool has_update = false;
+ if (req->content_len > 0) {
+ char *body = NULL;
+ esp_err_t body_err = rest_server_read_body(req, &body);
+ if (body_err != ESP_OK) {
+ return rest_server_send_error(req, "400 Bad Request", "invalid request body");
+ }
+
+ cJSON *json = cJSON_Parse(body);
+ free(body);
+ if (json == NULL) {
+ return rest_server_send_error(req, "400 Bad Request", "invalid json");
+ }
+
+ cJSON *jreset = cJSON_GetObjectItemCaseSensitive(json, "reset_defaults");
+ if (cJSON_IsBool(jreset)) {
+ reset_defaults = cJSON_IsTrue(jreset);
+ }
+
+ cJSON *jshift_hi = cJSON_GetObjectItemCaseSensitive(json, "shift_clock_high_us");
+ if (jshift_hi != NULL) {
+ if (!cJSON_IsNumber(jshift_hi) || jshift_hi->valuedouble < 1 || jshift_hi->valuedouble > 50) {
+ cJSON_Delete(json);
+ return rest_server_send_error(req, "400 Bad Request", "shift_clock_high_us must be 1..50");
+ }
+ cfg.shift_clock_high_us = (uint16_t)jshift_hi->valuedouble;
+ has_update = true;
+ }
+
+ cJSON *jshift_lo = cJSON_GetObjectItemCaseSensitive(json, "shift_clock_low_us");
+ if (jshift_lo != NULL) {
+ if (!cJSON_IsNumber(jshift_lo) || jshift_lo->valuedouble < 1 || jshift_lo->valuedouble > 50) {
+ cJSON_Delete(json);
+ return rest_server_send_error(req, "400 Bad Request", "shift_clock_low_us must be 1..50");
+ }
+ cfg.shift_clock_low_us = (uint16_t)jshift_lo->valuedouble;
+ has_update = true;
+ }
+
+ cJSON *jlatch = cJSON_GetObjectItemCaseSensitive(json, "latch_pulse_us");
+ if (jlatch != NULL) {
+ if (!cJSON_IsNumber(jlatch) || jlatch->valuedouble < 1 || jlatch->valuedouble > 50) {
+ cJSON_Delete(json);
+ return rest_server_send_error(req, "400 Bad Request", "latch_pulse_us must be 1..50");
+ }
+ cfg.latch_pulse_us = (uint16_t)jlatch->valuedouble;
+ has_update = true;
+ }
+
+ cJSON *jstb = cJSON_GetObjectItemCaseSensitive(json, "strobe_active_high");
+ if (cJSON_IsBool(jstb)) {
+ cfg.strobe_active_high = cJSON_IsTrue(jstb);
+ has_update = true;
+ }
+
+ cJSON *jboost = cJSON_GetObjectItemCaseSensitive(json, "boost_active_high");
+ if (cJSON_IsBool(jboost)) {
+ cfg.boost_active_high = cJSON_IsTrue(jboost);
+ has_update = true;
+ }
+
+ cJSON *jstrobe_on = cJSON_GetObjectItemCaseSensitive(json, "override_strobe_on_us");
+ if (jstrobe_on != NULL) {
+ if (!cJSON_IsNumber(jstrobe_on) || jstrobe_on->valuedouble < 0 || jstrobe_on->valuedouble > 10000) {
+ cJSON_Delete(json);
+ return rest_server_send_error(req,
+ "400 Bad Request",
+ "override_strobe_on_us must be 0..10000");
+ }
+ if (jstrobe_on->valuedouble > 0 && jstrobe_on->valuedouble < 100) {
+ cJSON_Delete(json);
+ return rest_server_send_error(req,
+ "400 Bad Request",
+ "override_strobe_on_us must be 0 or 100..10000");
+ }
+ cfg.override_strobe_on_us = (uint16_t)jstrobe_on->valuedouble;
+ has_update = true;
+ }
+
+ cJSON *jstrobe_gap = cJSON_GetObjectItemCaseSensitive(json, "override_strobe_interval_us");
+ if (jstrobe_gap != NULL) {
+ if (!cJSON_IsNumber(jstrobe_gap) || jstrobe_gap->valuedouble < 0 || jstrobe_gap->valuedouble > 10000) {
+ cJSON_Delete(json);
+ return rest_server_send_error(req,
+ "400 Bad Request",
+ "override_strobe_interval_us must be 0..10000");
+ }
+ cfg.override_strobe_interval_us = (uint16_t)jstrobe_gap->valuedouble;
+ has_update = true;
+ }
+
+ cJSON *jmotor = cJSON_GetObjectItemCaseSensitive(json, "override_motor_step_us");
+ if (jmotor != NULL) {
+ if (!cJSON_IsNumber(jmotor) || jmotor->valuedouble < 0 || jmotor->valuedouble > 20000) {
+ cJSON_Delete(json);
+ return rest_server_send_error(req,
+ "400 Bad Request",
+ "override_motor_step_us must be 0..20000");
+ }
+ if (jmotor->valuedouble > 0 && jmotor->valuedouble < 100) {
+ cJSON_Delete(json);
+ return rest_server_send_error(req,
+ "400 Bad Request",
+ "override_motor_step_us must be 0 or 100..20000");
+ }
+ cfg.override_motor_step_us = (uint16_t)jmotor->valuedouble;
+ has_update = true;
+ }
+
+ cJSON *jsteps = cJSON_GetObjectItemCaseSensitive(json, "override_steps_per_line");
+ if (jsteps != NULL) {
+ if (!cJSON_IsNumber(jsteps) || jsteps->valuedouble < 0 || jsteps->valuedouble > 8) {
+ cJSON_Delete(json);
+ return rest_server_send_error(req,
+ "400 Bad Request",
+ "override_steps_per_line must be 0..8");
+ }
+ cfg.override_steps_per_line = (uint8_t)jsteps->valuedouble;
+ has_update = true;
+ }
+
+ cJSON_Delete(json);
+ }
+
+ char set_err[128] = {0};
+ rc = printer_protocol_set_direct_debug_config(has_update ? &cfg : NULL,
+ reset_defaults,
+ set_err,
+ sizeof(set_err));
+ if (rc != ESP_OK) {
+ return direct_debug_error_response(req,
+ rc,
+ set_err[0] != '\0' ? set_err : "set direct debug failed");
+ }
+
+ memset(&cfg, 0, sizeof(cfg));
+ memset(get_err, 0, sizeof(get_err));
+ rc = printer_protocol_get_direct_debug_config(&cfg, get_err, sizeof(get_err));
+ if (rc != ESP_OK) {
+ return direct_debug_error_response(req,
+ rc,
+ get_err[0] != '\0' ? get_err : "get direct debug failed");
+ }
+
+ cJSON *root = cJSON_CreateObject();
+ cJSON_AddBoolToObject(root, "ok", true);
+ cJSON_AddBoolToObject(root, "reset_defaults", reset_defaults);
+ cJSON_AddBoolToObject(root, "updated", has_update);
+ cJSON_AddStringToObject(root, "backend", printer_backend_str(printer_protocol_get_backend()));
+ add_direct_debug_json_fields(root, &cfg);
+
+ esp_err_t err = rest_server_send_json(req, "200 OK", root);
+ cJSON_Delete(root);
+ return err;
+}
+
esp_err_t rest_server_label_gap_move_post(httpd_req_t *req) {
if (!rest_server_auth_ok(req)) {
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
diff --git a/components/domain/include/domain.h b/components/domain/include/domain.h
index fa32c95..ab9ae99 100644
--- a/components/domain/include/domain.h
+++ b/components/domain/include/domain.h
@@ -146,6 +146,18 @@ typedef struct {
bool direct_ignore_precheck;
} printer_print_options_t;
+typedef struct {
+ uint16_t shift_clock_high_us;
+ uint16_t shift_clock_low_us;
+ uint16_t latch_pulse_us;
+ bool strobe_active_high;
+ bool boost_active_high;
+ uint16_t override_strobe_on_us;
+ uint16_t override_strobe_interval_us;
+ uint16_t override_motor_step_us;
+ uint8_t override_steps_per_line;
+} printer_direct_debug_config_t;
+
typedef struct {
bool supports_connect;
bool supports_gap_move;
@@ -184,6 +196,13 @@ 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);
+esp_err_t printer_protocol_get_direct_debug_config(printer_direct_debug_config_t *out_config,
+ char *err,
+ size_t err_len);
+esp_err_t printer_protocol_set_direct_debug_config(const printer_direct_debug_config_t *config,
+ bool reset_defaults,
+ char *err,
+ size_t err_len);
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);
diff --git a/components/domain/src/printer_protocol.c b/components/domain/src/printer_protocol.c
index adb3038..ca5befb 100644
--- a/components/domain/src/printer_protocol.c
+++ b/components/domain/src/printer_protocol.c
@@ -51,6 +51,14 @@ static const char *TAG = "printer_protocol";
static void on_rx_frame(const uint8_t *data, size_t len);
+static printer_backend_t default_backend_on_startup(void) {
+ // This hardware has a first-class direct path; BLE remains opt-in via API.
+ if (runtime_policy_direct_printer_enabled()) {
+ return PRINTER_BACKEND_DIRECT;
+ }
+ return PRINTER_BACKEND_BLE;
+}
+
static void backend_capabilities_for(printer_backend_t backend, printer_capabilities_t *out_caps) {
if (out_caps == NULL) {
return;
@@ -643,9 +651,7 @@ esp_err_t printer_protocol_init(void) {
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;
+ s_backend = default_backend_on_startup();
esp_err_t err = ensure_backend_ready(s_backend);
if (err != ESP_OK) {
@@ -757,7 +763,7 @@ esp_err_t printer_protocol_stop(uint32_t timeout_ms) {
s_protocol_initialized = false;
s_protocol_stopping = false;
s_ble_client_initialized = false;
- s_backend = PRINTER_BACKEND_BLE;
+ s_backend = default_backend_on_startup();
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_STATUS_POLL_PAUSE_DEPTH, 0);
runtime_diag_set_gauge(RUNTIME_DIAG_GAUGE_PRINTER_QUEUE_DEPTH, 0);
diff --git a/components/domain/src/printer_protocol_commands.c b/components/domain/src/printer_protocol_commands.c
index 970871f..54b50bc 100644
--- a/components/domain/src/printer_protocol_commands.c
+++ b/components/domain/src/printer_protocol_commands.c
@@ -15,6 +15,12 @@ static esp_err_t unsupported_for_direct(char *err, size_t err_len, const char *o
return ESP_ERR_NOT_SUPPORTED;
}
+static void write_err(char *err, size_t err_len, const char *msg) {
+ if (err != NULL && err_len > 0) {
+ snprintf(err, err_len, "%s", msg);
+ }
+}
+
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);
@@ -343,6 +349,68 @@ esp_err_t printer_protocol_ota_get_version(printer_ota_version_t *out_version,
return ESP_OK;
}
+esp_err_t printer_protocol_get_direct_debug_config(printer_direct_debug_config_t *out_config,
+ char *err,
+ size_t err_len) {
+ if (out_config == NULL) {
+ write_err(err, err_len, "invalid args");
+ return ESP_ERR_INVALID_ARG;
+ }
+ if (!runtime_policy_direct_printer_enabled()) {
+ write_err(err, err_len, "direct backend disabled");
+ return ESP_ERR_NOT_SUPPORTED;
+ }
+
+ platform_direct_debug_config_t cfg = {0};
+ esp_err_t rc = platform_direct_printer_get_debug_config(&cfg);
+ if (rc != ESP_OK) {
+ write_err(err, err_len, "get direct debug config failed");
+ return rc;
+ }
+
+ out_config->shift_clock_high_us = cfg.shift_clock_high_us;
+ out_config->shift_clock_low_us = cfg.shift_clock_low_us;
+ out_config->latch_pulse_us = cfg.latch_pulse_us;
+ out_config->strobe_active_high = cfg.strobe_active_high;
+ out_config->boost_active_high = cfg.boost_active_high;
+ out_config->override_strobe_on_us = cfg.override_strobe_on_us;
+ out_config->override_strobe_interval_us = cfg.override_strobe_interval_us;
+ out_config->override_motor_step_us = cfg.override_motor_step_us;
+ out_config->override_steps_per_line = cfg.override_steps_per_line;
+ return ESP_OK;
+}
+
+esp_err_t printer_protocol_set_direct_debug_config(const printer_direct_debug_config_t *config,
+ bool reset_defaults,
+ char *err,
+ size_t err_len) {
+ if (config == NULL && !reset_defaults) {
+ write_err(err, err_len, "invalid args");
+ return ESP_ERR_INVALID_ARG;
+ }
+ if (!runtime_policy_direct_printer_enabled()) {
+ write_err(err, err_len, "direct backend disabled");
+ return ESP_ERR_NOT_SUPPORTED;
+ }
+
+ platform_direct_debug_config_t cfg = {0};
+ platform_direct_debug_config_t *pcfg = NULL;
+ if (config != NULL) {
+ cfg.shift_clock_high_us = config->shift_clock_high_us;
+ cfg.shift_clock_low_us = config->shift_clock_low_us;
+ cfg.latch_pulse_us = config->latch_pulse_us;
+ cfg.strobe_active_high = config->strobe_active_high;
+ cfg.boost_active_high = config->boost_active_high;
+ cfg.override_strobe_on_us = config->override_strobe_on_us;
+ cfg.override_strobe_interval_us = config->override_strobe_interval_us;
+ cfg.override_motor_step_us = config->override_motor_step_us;
+ cfg.override_steps_per_line = config->override_steps_per_line;
+ pcfg = &cfg;
+ }
+
+ return platform_direct_printer_set_debug_config(pcfg, reset_defaults, err, err_len);
+}
+
const char *printer_protocol_job_state_str(print_job_state_t state) {
switch (state) {
case PRINT_JOB_STATE_NONE:
diff --git a/components/platform/include/platform.h b/components/platform/include/platform.h
index 6dee0b4..65baaa8 100644
--- a/components/platform/include/platform.h
+++ b/components/platform/include/platform.h
@@ -61,12 +61,29 @@ typedef struct {
const volatile bool *cancel_flag;
} platform_direct_print_request_t;
+typedef struct {
+ uint16_t shift_clock_high_us;
+ uint16_t shift_clock_low_us;
+ uint16_t latch_pulse_us;
+ bool strobe_active_high;
+ bool boost_active_high;
+ uint16_t override_strobe_on_us;
+ uint16_t override_strobe_interval_us;
+ uint16_t override_motor_step_us;
+ uint8_t override_steps_per_line;
+} platform_direct_debug_config_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_get_debug_config(platform_direct_debug_config_t *out_config);
+esp_err_t platform_direct_printer_set_debug_config(const platform_direct_debug_config_t *config,
+ bool reset_defaults,
+ char *err,
+ size_t err_len);
esp_err_t platform_direct_printer_print(const platform_direct_print_request_t *request,
char *err,
size_t err_len);
diff --git a/components/platform/src/direct_thermal_printer.c b/components/platform/src/direct_thermal_printer.c
index 8c446ef..06f161d 100644
--- a/components/platform/src/direct_thermal_printer.c
+++ b/components/platform/src/direct_thermal_printer.c
@@ -23,14 +23,21 @@
#endif
#if CONFIG_TQ_KEY_PRINT_BOOST_ACTIVE_HIGH
-#define BOOST_ON_LEVEL 1
-#define BOOST_OFF_LEVEL 0
+#define BOOST_DEFAULT_ACTIVE_HIGH 1
#else
-#define BOOST_ON_LEVEL 0
-#define BOOST_OFF_LEVEL 1
+#define BOOST_DEFAULT_ACTIVE_HIGH 0
#endif
#define ADC_RAW_MAX 4095
+#define DEBUG_SHIFT_CLOCK_US_MIN 1
+#define DEBUG_SHIFT_CLOCK_US_MAX 50
+#define DEBUG_LATCH_PULSE_US_MIN 1
+#define DEBUG_LATCH_PULSE_US_MAX 50
+
+static const uint16_t k_default_shift_clock_high_us = 5;
+static const uint16_t k_default_shift_clock_low_us = 5;
+static const uint16_t k_default_latch_pulse_us = 1;
+static const bool k_default_strobe_active_high = true;
static const char *TAG = "direct_printer";
static const uint8_t k_print_yield_lines = 8;
@@ -46,6 +53,7 @@ typedef struct {
bool initialized;
bool connected;
uint8_t motor_phase;
+ platform_direct_debug_config_t debug;
SemaphoreHandle_t lock;
adc_oneshot_unit_handle_t adc1_handle;
adc_oneshot_unit_handle_t adc2_handle;
@@ -57,6 +65,31 @@ static direct_printer_state_t s_state;
static const uint8_t k_motor_step_table[4] = {0x05, 0x09, 0x0A, 0x06};
+static int boost_on_level_locked(void) {
+ return s_state.debug.boost_active_high ? 1 : 0;
+}
+
+static int boost_off_level_locked(void) {
+ return s_state.debug.boost_active_high ? 0 : 1;
+}
+
+static int strobe_active_level_locked(void) {
+ return s_state.debug.strobe_active_high ? 1 : 0;
+}
+
+static int strobe_idle_level_locked(void) {
+ return s_state.debug.strobe_active_high ? 0 : 1;
+}
+
+static void set_debug_defaults_locked(void) {
+ memset(&s_state.debug, 0, sizeof(s_state.debug));
+ s_state.debug.shift_clock_high_us = k_default_shift_clock_high_us;
+ s_state.debug.shift_clock_low_us = k_default_shift_clock_low_us;
+ s_state.debug.latch_pulse_us = k_default_latch_pulse_us;
+ s_state.debug.strobe_active_high = k_default_strobe_active_high;
+ s_state.debug.boost_active_high = BOOST_DEFAULT_ACTIVE_HIGH != 0;
+}
+
static bool is_gpio_valid(int gpio_num) {
return gpio_num >= 0;
}
@@ -118,9 +151,10 @@ static esp_err_t config_input_pin(int gpio_num, bool pull_up, const char *name)
}
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);
+ int out_level = level ? strobe_active_level_locked() : strobe_idle_level_locked();
+ set_gpio_level_if_valid(CONFIG_TQ_PRINT_STB_12_PIN, out_level);
+ set_gpio_level_if_valid(CONFIG_TQ_PRINT_STB_34_PIN, out_level);
+ set_gpio_level_if_valid(CONFIG_TQ_PRINT_STB_56_PIN, out_level);
}
static void motor_apply_pattern_locked(uint8_t pattern) {
@@ -141,13 +175,19 @@ static void safe_drive_off_locked(bool keep_boost_on) {
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);
+ set_gpio_level_if_valid(CONFIG_TQ_KEY_PRINT_PIN, boost_off_level_locked());
}
}
static void pulse_latch_locked(void) {
+ uint16_t latch_us = s_state.debug.latch_pulse_us;
+ if (latch_us < DEBUG_LATCH_PULSE_US_MIN) {
+ latch_us = DEBUG_LATCH_PULSE_US_MIN;
+ } else if (latch_us > DEBUG_LATCH_PULSE_US_MAX) {
+ latch_us = DEBUG_LATCH_PULSE_US_MAX;
+ }
set_gpio_level_if_valid(CONFIG_TQ_PRINT_LAT_PIN, 0);
- esp_rom_delay_us(1);
+ esp_rom_delay_us(latch_us);
set_gpio_level_if_valid(CONFIG_TQ_PRINT_LAT_PIN, 1);
}
@@ -174,15 +214,28 @@ static void motor_step_once_locked(uint16_t step_delay_us) {
}
static void write_line_bits_locked(const uint8_t *line, size_t line_bytes) {
+ uint16_t high_us = s_state.debug.shift_clock_high_us;
+ uint16_t low_us = s_state.debug.shift_clock_low_us;
+ if (high_us < DEBUG_SHIFT_CLOCK_US_MIN) {
+ high_us = DEBUG_SHIFT_CLOCK_US_MIN;
+ } else if (high_us > DEBUG_SHIFT_CLOCK_US_MAX) {
+ high_us = DEBUG_SHIFT_CLOCK_US_MAX;
+ }
+ if (low_us < DEBUG_SHIFT_CLOCK_US_MIN) {
+ low_us = DEBUG_SHIFT_CLOCK_US_MIN;
+ } else if (low_us > DEBUG_SHIFT_CLOCK_US_MAX) {
+ low_us = DEBUG_SHIFT_CLOCK_US_MAX;
+ }
+
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);
+ esp_rom_delay_us(high_us);
set_gpio_level_if_valid(CONFIG_TQ_SPI_SHARED_CLK_PIN, 0);
- esp_rom_delay_us(1);
+ esp_rom_delay_us(low_us);
}
}
}
@@ -423,17 +476,18 @@ esp_err_t platform_direct_printer_init(void) {
if (s_state.lock == NULL) {
return ESP_ERR_NO_MEM;
}
+ set_debug_defaults_locked();
esp_err_t err = ESP_OK;
- err = config_output_pin(CONFIG_TQ_PRINT_STB_12_PIN, 0, "print_stb12");
+ err = config_output_pin(CONFIG_TQ_PRINT_STB_12_PIN, strobe_idle_level_locked(), "print_stb12");
if (err != ESP_OK) {
return err;
}
- err = config_output_pin(CONFIG_TQ_PRINT_STB_34_PIN, 0, "print_stb34");
+ err = config_output_pin(CONFIG_TQ_PRINT_STB_34_PIN, strobe_idle_level_locked(), "print_stb34");
if (err != ESP_OK) {
return err;
}
- err = config_output_pin(CONFIG_TQ_PRINT_STB_56_PIN, 0, "print_stb56");
+ err = config_output_pin(CONFIG_TQ_PRINT_STB_56_PIN, strobe_idle_level_locked(), "print_stb56");
if (err != ESP_OK) {
return err;
}
@@ -466,7 +520,7 @@ esp_err_t platform_direct_printer_init(void) {
if (err != ESP_OK) {
return err;
}
- err = config_output_pin(CONFIG_TQ_KEY_PRINT_PIN, BOOST_OFF_LEVEL, "print_boost");
+ err = config_output_pin(CONFIG_TQ_KEY_PRINT_PIN, boost_off_level_locked(), "print_boost");
if (err != ESP_OK) {
return err;
}
@@ -527,7 +581,7 @@ esp_err_t platform_direct_printer_connect(uint32_t timeout_ms) {
}
if (!s_state.connected) {
- set_gpio_level_if_valid(CONFIG_TQ_KEY_PRINT_PIN, BOOST_ON_LEVEL);
+ set_gpio_level_if_valid(CONFIG_TQ_KEY_PRINT_PIN, boost_on_level_locked());
vTaskDelay(pdMS_TO_TICKS(10));
safe_drive_off_locked(true);
s_state.connected = true;
@@ -577,6 +631,103 @@ esp_err_t platform_direct_printer_get_sensors(platform_printer_sensors_t *out_se
return ESP_OK;
}
+esp_err_t platform_direct_printer_get_debug_config(platform_direct_debug_config_t *out_config) {
+#if !CONFIG_TQ_DIRECT_PRINTER_ENABLE
+ (void)out_config;
+ return ESP_ERR_NOT_SUPPORTED;
+#else
+ if (out_config == NULL) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ esp_err_t init_rc = platform_direct_printer_init();
+ if (init_rc != ESP_OK) {
+ return init_rc;
+ }
+ if (xSemaphoreTake(s_state.lock, pdMS_TO_TICKS(500)) != pdTRUE) {
+ return ESP_ERR_TIMEOUT;
+ }
+ *out_config = s_state.debug;
+ xSemaphoreGive(s_state.lock);
+ return ESP_OK;
+#endif
+}
+
+esp_err_t platform_direct_printer_set_debug_config(const platform_direct_debug_config_t *config,
+ bool reset_defaults,
+ char *err,
+ size_t err_len) {
+#if !CONFIG_TQ_DIRECT_PRINTER_ENABLE
+ (void)config;
+ (void)reset_defaults;
+ write_err(err, err_len, "direct backend disabled");
+ return ESP_ERR_NOT_SUPPORTED;
+#else
+ if (config == NULL && !reset_defaults) {
+ write_err(err, err_len, "invalid args");
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ esp_err_t init_rc = platform_direct_printer_init();
+ if (init_rc != ESP_OK) {
+ write_err(err, err_len, "direct backend not initialized");
+ return init_rc;
+ }
+ if (xSemaphoreTake(s_state.lock, pdMS_TO_TICKS(1000)) != pdTRUE) {
+ write_err(err, err_len, "direct printer lock timeout");
+ return ESP_ERR_TIMEOUT;
+ }
+
+ if (reset_defaults) {
+ set_debug_defaults_locked();
+ }
+
+ if (config != NULL) {
+ if (config->shift_clock_high_us < DEBUG_SHIFT_CLOCK_US_MIN ||
+ config->shift_clock_high_us > DEBUG_SHIFT_CLOCK_US_MAX ||
+ config->shift_clock_low_us < DEBUG_SHIFT_CLOCK_US_MIN ||
+ config->shift_clock_low_us > DEBUG_SHIFT_CLOCK_US_MAX) {
+ xSemaphoreGive(s_state.lock);
+ write_err(err, err_len, "shift clock us out of range (1..50)");
+ return ESP_ERR_INVALID_ARG;
+ }
+ if (config->latch_pulse_us < DEBUG_LATCH_PULSE_US_MIN ||
+ config->latch_pulse_us > DEBUG_LATCH_PULSE_US_MAX) {
+ xSemaphoreGive(s_state.lock);
+ write_err(err, err_len, "latch pulse us out of range (1..50)");
+ return ESP_ERR_INVALID_ARG;
+ }
+ if (config->override_strobe_on_us != 0 &&
+ (config->override_strobe_on_us < 100 || config->override_strobe_on_us > 10000)) {
+ xSemaphoreGive(s_state.lock);
+ write_err(err, err_len, "strobe_on_us must be 0 or 100..10000");
+ return ESP_ERR_INVALID_ARG;
+ }
+ if (config->override_strobe_interval_us > 10000) {
+ xSemaphoreGive(s_state.lock);
+ write_err(err, err_len, "strobe_interval_us must be 0..10000");
+ return ESP_ERR_INVALID_ARG;
+ }
+ if (config->override_motor_step_us != 0 &&
+ (config->override_motor_step_us < 100 || config->override_motor_step_us > 20000)) {
+ xSemaphoreGive(s_state.lock);
+ write_err(err, err_len, "motor_step_us must be 0 or 100..20000");
+ return ESP_ERR_INVALID_ARG;
+ }
+ if (config->override_steps_per_line > 8) {
+ xSemaphoreGive(s_state.lock);
+ write_err(err, err_len, "steps_per_line must be 0..8");
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ s_state.debug = *config;
+ }
+
+ safe_drive_off_locked(s_state.connected);
+ xSemaphoreGive(s_state.lock);
+ return ESP_OK;
+#endif
+}
+
esp_err_t platform_direct_printer_print(const platform_direct_print_request_t *request,
char *err,
size_t err_len) {
@@ -610,19 +761,6 @@ esp_err_t platform_direct_printer_print(const platform_direct_print_request_t *r
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;
@@ -634,6 +772,32 @@ esp_err_t platform_direct_printer_print(const platform_direct_print_request_t *r
return ESP_ERR_INVALID_STATE;
}
+ uint16_t strobe_on_us = (request->strobe_on_time_us > 0)
+ ? request->strobe_on_time_us
+ : runtime_policy_direct_printer_strobe_on_us();
+ uint16_t strobe_interval_us = (request->strobe_interval_us > 0)
+ ? request->strobe_interval_us
+ : runtime_policy_direct_printer_strobe_interval_us();
+ uint16_t motor_step_us = (request->motor_step_delay_us > 0)
+ ? request->motor_step_delay_us
+ : runtime_policy_direct_printer_motor_step_us();
+ uint8_t steps_per_line = (request->motor_steps_per_line > 0)
+ ? request->motor_steps_per_line
+ : runtime_policy_direct_printer_steps_per_line();
+
+ if (s_state.debug.override_strobe_on_us > 0) {
+ strobe_on_us = s_state.debug.override_strobe_on_us;
+ }
+ if (s_state.debug.override_strobe_interval_us > 0) {
+ strobe_interval_us = s_state.debug.override_strobe_interval_us;
+ }
+ if (s_state.debug.override_motor_step_us > 0) {
+ motor_step_us = s_state.debug.override_motor_step_us;
+ }
+ if (s_state.debug.override_steps_per_line > 0) {
+ steps_per_line = s_state.debug.override_steps_per_line;
+ }
+
esp_err_t rc = ESP_OK;
if (!request->ignore_precheck) {
rc = precheck_before_print_locked(err, err_len);
@@ -725,6 +889,9 @@ esp_err_t platform_direct_printer_gap_move(uint32_t timeout_ms, char *err, size_
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();
+ if (s_state.debug.override_motor_step_us > 0) {
+ step_delay_us = s_state.debug.override_motor_step_us;
+ }
platform_printer_sensors_t sensors = {0};
sample_sensors_locked(&sensors);