添加热敏打印机调试功能

This commit is contained in:
admin
2026-02-27 16:39:54 +08:00
parent 2b6bcc83dc
commit 46a800c499
10 changed files with 951 additions and 35 deletions

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ __pycache__/
.pytest_cache/
managed_components
JX_2R_01测试程序_STM32F103RF

View File

@@ -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"
],

View File

@@ -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);

View File

@@ -78,6 +78,57 @@ static const char *s_web_index =
" </div>\n"
" <div class='small'>Only effective on Direct backend. Use for sensor bring-up/debug.</div>\n"
"\n"
" <label>Direct Debug</label>\n"
" <div class='small'>参数标签中的字段名与 <code>/v1/printer/direct/debug</code> 的 JSON 键保持一致。</div>\n"
" <div class='row'>\n"
" <div style='min-width:220px;max-width:260px;flex:1;'>\n"
" <label style='margin:0 0 4px 0;'>shift_clock_high_us (CLK高电平时长, us)</label>\n"
" <input id='dbgShiftHighUs' type='number' min='1' max='50' value='5' placeholder='1..50' style='max-width:100%'>\n"
" </div>\n"
" <div style='min-width:220px;max-width:260px;flex:1;'>\n"
" <label style='margin:0 0 4px 0;'>shift_clock_low_us (CLK低电平时长, us)</label>\n"
" <input id='dbgShiftLowUs' type='number' min='1' max='50' value='5' placeholder='1..50' style='max-width:100%'>\n"
" </div>\n"
" <div style='min-width:220px;max-width:260px;flex:1;'>\n"
" <label style='margin:0 0 4px 0;'>latch_pulse_us (LAT脉宽, us)</label>\n"
" <input id='dbgLatchUs' type='number' min='1' max='50' value='1' placeholder='1..50' style='max-width:100%'>\n"
" </div>\n"
" </div>\n"
" <div class='row'>\n"
" <div style='min-width:220px;max-width:260px;flex:1;'>\n"
" <label style='margin:0 0 4px 0;'>override_strobe_on_us (加热点火时长覆盖, us)</label>\n"
" <input id='dbgHotUs' type='number' min='0' max='10000' value='0' placeholder='0 或 100..10000' style='max-width:100%'>\n"
" </div>\n"
" <div style='min-width:220px;max-width:260px;flex:1;'>\n"
" <label style='margin:0 0 4px 0;'>override_strobe_interval_us (加热间隔覆盖, us)</label>\n"
" <input id='dbgStrobeGapUs' type='number' min='0' max='10000' value='0' placeholder='0..10000' style='max-width:100%'>\n"
" </div>\n"
" <div style='min-width:220px;max-width:260px;flex:1;'>\n"
" <label style='margin:0 0 4px 0;'>override_motor_step_us (电机步进延时覆盖, us)</label>\n"
" <input id='dbgMotorUs' type='number' min='0' max='20000' value='0' placeholder='0 或 100..20000' style='max-width:100%'>\n"
" </div>\n"
" <div style='min-width:220px;max-width:260px;flex:1;'>\n"
" <label style='margin:0 0 4px 0;'>override_steps_per_line (每行步进数覆盖)</label>\n"
" <input id='dbgStepsPerLine' type='number' min='0' max='8' value='0' placeholder='0..8' style='max-width:100%'>\n"
" </div>\n"
" </div>\n"
" <div class='row'>\n"
" <label style='margin:0;display:flex;align-items:center;gap:6px;'>\n"
" <input id='dbgStbActiveHigh' type='checkbox' checked style='width:auto;'>\n"
" strobe_active_high (STB有效电平为高)\n"
" </label>\n"
" <label style='margin:0;display:flex;align-items:center;gap:6px;'>\n"
" <input id='dbgBoostActiveHigh' type='checkbox' checked style='width:auto;'>\n"
" boost_active_high (BOOST有效电平为高)\n"
" </label>\n"
" <button id='btnDebugLoad' class='alt'>Load Debug</button>\n"
" <button id='btnDebugApply' class='alt'>Apply Debug</button>\n"
" <button id='btnDebugReset' class='warn'>Reset Debug</button>\n"
" <button id='btnDebugAutoScan' class='alt'>Auto Diagnose (Direct)</button>\n"
" </div>\n"
" <div class='small'>override_* 参数填 0 表示不覆盖,恢复为运行时默认策略。</div>\n"
" <div class='small'>Auto Diagnose 使用低功耗测试图案若点击后仍重启Brownout请先改善供电或降低热头功率。</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"
@@ -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,

View File

@@ -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");

View File

@@ -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);

View File

@@ -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);

View File

@@ -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:

View File

@@ -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);

View File

@@ -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);