XCOM:一个 4 MB 的串口调试工具

XCOM:一个 4 MB 的串口调试工具

XCOM 是 Windows 上的一个串口调试工具,压缩包 3.93 MB。

核心特性概览

你关心的 XCOM 的答案
多小 压缩包 3.93 MB,绿色版解压即用
内存占用 固定大小,不随数据量增长(接收池恒为 512 KB)
会不会丢数据 只要有日志在,字节流完整;真丢了会亮横幅告诉你,不隐瞒
改功能要编译吗 不用。界面和逻辑都是 Lua,改脚本保存即生效
能扩展吗 scripts/ 下放一个 .lua 文件就是插件,几行代码自动应答设备
支持什么系统 Windows 10/11 x64

下面按这个顺序展开:先看它的组成,再看两个最核心的设计------不丢数据用脚本扩展

系统组成:三层进程结构

XCOM 启动后有三个部分在同一个进程里协作,各自只做一件事:
#mermaid-svg-tbaaVM205Tfn1LS6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tbaaVM205Tfn1LS6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tbaaVM205Tfn1LS6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tbaaVM205Tfn1LS6 .error-icon{fill:#552222;}#mermaid-svg-tbaaVM205Tfn1LS6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tbaaVM205Tfn1LS6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tbaaVM205Tfn1LS6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tbaaVM205Tfn1LS6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tbaaVM205Tfn1LS6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tbaaVM205Tfn1LS6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tbaaVM205Tfn1LS6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tbaaVM205Tfn1LS6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tbaaVM205Tfn1LS6 .marker.cross{stroke:#333333;}#mermaid-svg-tbaaVM205Tfn1LS6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tbaaVM205Tfn1LS6 p{margin:0;}#mermaid-svg-tbaaVM205Tfn1LS6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tbaaVM205Tfn1LS6 .cluster-label text{fill:#333;}#mermaid-svg-tbaaVM205Tfn1LS6 .cluster-label span{color:#333;}#mermaid-svg-tbaaVM205Tfn1LS6 .cluster-label span p{background-color:transparent;}#mermaid-svg-tbaaVM205Tfn1LS6 .label text,#mermaid-svg-tbaaVM205Tfn1LS6 span{fill:#333;color:#333;}#mermaid-svg-tbaaVM205Tfn1LS6 .node rect,#mermaid-svg-tbaaVM205Tfn1LS6 .node circle,#mermaid-svg-tbaaVM205Tfn1LS6 .node ellipse,#mermaid-svg-tbaaVM205Tfn1LS6 .node polygon,#mermaid-svg-tbaaVM205Tfn1LS6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tbaaVM205Tfn1LS6 .rough-node .label text,#mermaid-svg-tbaaVM205Tfn1LS6 .node .label text,#mermaid-svg-tbaaVM205Tfn1LS6 .image-shape .label,#mermaid-svg-tbaaVM205Tfn1LS6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-tbaaVM205Tfn1LS6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tbaaVM205Tfn1LS6 .rough-node .label,#mermaid-svg-tbaaVM205Tfn1LS6 .node .label,#mermaid-svg-tbaaVM205Tfn1LS6 .image-shape .label,#mermaid-svg-tbaaVM205Tfn1LS6 .icon-shape .label{text-align:center;}#mermaid-svg-tbaaVM205Tfn1LS6 .node.clickable{cursor:pointer;}#mermaid-svg-tbaaVM205Tfn1LS6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tbaaVM205Tfn1LS6 .arrowheadPath{fill:#333333;}#mermaid-svg-tbaaVM205Tfn1LS6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tbaaVM205Tfn1LS6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tbaaVM205Tfn1LS6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tbaaVM205Tfn1LS6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tbaaVM205Tfn1LS6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tbaaVM205Tfn1LS6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tbaaVM205Tfn1LS6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tbaaVM205Tfn1LS6 .cluster text{fill:#333;}#mermaid-svg-tbaaVM205Tfn1LS6 .cluster span{color:#333;}#mermaid-svg-tbaaVM205Tfn1LS6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tbaaVM205Tfn1LS6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tbaaVM205Tfn1LS6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-tbaaVM205Tfn1LS6 .icon-shape,#mermaid-svg-tbaaVM205Tfn1LS6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tbaaVM205Tfn1LS6 .icon-shape p,#mermaid-svg-tbaaVM205Tfn1LS6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tbaaVM205Tfn1LS6 .icon-shape .label rect,#mermaid-svg-tbaaVM205Tfn1LS6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tbaaVM205Tfn1LS6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tbaaVM205Tfn1LS6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tbaaVM205Tfn1LS6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-tbaaVM205Tfn1LS6 .proc>*{fill:#E8F0FE!important;stroke:#2E5AAC!important;color:#111!important;stroke-width:1.5px!important;}#mermaid-svg-tbaaVM205Tfn1LS6 .proc span{fill:#E8F0FE!important;stroke:#2E5AAC!important;color:#111!important;stroke-width:1.5px!important;}#mermaid-svg-tbaaVM205Tfn1LS6 .proc tspan{fill:#111!important;}#mermaid-svg-tbaaVM205Tfn1LS6 .dll>*{fill:#FDE9D9!important;stroke:#C55A11!important;color:#111!important;}#mermaid-svg-tbaaVM205Tfn1LS6 .dll span{fill:#FDE9D9!important;stroke:#C55A11!important;color:#111!important;}#mermaid-svg-tbaaVM205Tfn1LS6 .dll tspan{fill:#111!important;}#mermaid-svg-tbaaVM205Tfn1LS6 .th>*{fill:#E2F0D9!important;stroke:#548235!important;color:#111!important;}#mermaid-svg-tbaaVM205Tfn1LS6 .th span{fill:#E2F0D9!important;stroke:#548235!important;color:#111!important;}#mermaid-svg-tbaaVM205Tfn1LS6 .th tspan{fill:#111!important;} 隐藏控制台
版本化 C ABI
固定签名
OVERLAPPED
OVERLAPPED
xcom.exe 49 KB

启动器
luvjit.exe

LuaJIT 主循环
xcom_core.dll 126 KB

C++17 串口核心
xcom_imgui.dll 859 KB

ImGui 渲染
串口读线程
写线程
COM 口

三块的分工很干净:

  • 启动器xcom.exe,只有 49 KB):唯一作用是隐藏那个黑框控制台,让程序看起来像个正常的 Windows 程序。
  • Lua 层luvjit.exe + 两个 DLL):界面上的一切------按钮、菜单、配置、脚本------都是 Lua 写的。
  • C++ 核心xcom_core.dll):跟硬件打交道的部分,包括串口的打开、读写、超时、断线重连,全部在这里。

技术选型:Lua 界面与 C++ 核心

一句话:改起来快,但不牺牲性能。

串口工具真正的痛点是改动成本。时间戳想换个格式、某种报文想自动解析、某类数据想标红------在传统纯 C++ 工具里,这些都意味着改代码、重新编译、重新发给同事。XCOM 把这些压缩成"改一个文本文件,保存,立刻生效"。

但"用脚本写"通常让人担心变慢。这正是 XCOM 选 LuaJIT 而不是普通 Lua 的原因,也是它敢把整个界面放在脚本层的底气:

  1. Lua 调用 C++ 不经过层层接口封装,而是直接调用,开销和 C 调用同级。
  2. 收数据时整批字节一次性交给渲染层,不会在 Lua 里逐字节拼接
  3. 整个界面只有一个线程,所有状态天然顺序,不需要加锁------也就没有锁带来的开销和卡顿。

代价也清楚:界面和主循环同线程,Lua 里写了慢代码,界面就会卡。所以重活全部推给 C++。下面这条接收链路就是最典型的例子。

接收完整性设计:零拷贝共享内存池

这是整个工具最花力气的地方。先澄清一个常见误解:"不丢数据"不是靠开一个很大的缓冲区硬扛。缓冲区总有满的时候,内存开得越大占用就越高,和"轻量"矛盾。

XCOM 的做法是一次拷贝,两个使用者共享

固定大小的接收内存池

数据从串口读进来时,只在读线程这一处拷贝一次,放进固定大小的内存池:一共 512 KB,分成 128 块,每块 4 KB。显示通道和存文件通道各自持有同一块内存的引用------不是各复制一份,谁最后用完谁还回池子。
#mermaid-svg-aJAkP0GTyVyxsfIR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aJAkP0GTyVyxsfIR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aJAkP0GTyVyxsfIR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aJAkP0GTyVyxsfIR .error-icon{fill:#552222;}#mermaid-svg-aJAkP0GTyVyxsfIR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aJAkP0GTyVyxsfIR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aJAkP0GTyVyxsfIR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aJAkP0GTyVyxsfIR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aJAkP0GTyVyxsfIR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aJAkP0GTyVyxsfIR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aJAkP0GTyVyxsfIR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aJAkP0GTyVyxsfIR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aJAkP0GTyVyxsfIR .marker.cross{stroke:#333333;}#mermaid-svg-aJAkP0GTyVyxsfIR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aJAkP0GTyVyxsfIR p{margin:0;}#mermaid-svg-aJAkP0GTyVyxsfIR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aJAkP0GTyVyxsfIR .cluster-label text{fill:#333;}#mermaid-svg-aJAkP0GTyVyxsfIR .cluster-label span{color:#333;}#mermaid-svg-aJAkP0GTyVyxsfIR .cluster-label span p{background-color:transparent;}#mermaid-svg-aJAkP0GTyVyxsfIR .label text,#mermaid-svg-aJAkP0GTyVyxsfIR span{fill:#333;color:#333;}#mermaid-svg-aJAkP0GTyVyxsfIR .node rect,#mermaid-svg-aJAkP0GTyVyxsfIR .node circle,#mermaid-svg-aJAkP0GTyVyxsfIR .node ellipse,#mermaid-svg-aJAkP0GTyVyxsfIR .node polygon,#mermaid-svg-aJAkP0GTyVyxsfIR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aJAkP0GTyVyxsfIR .rough-node .label text,#mermaid-svg-aJAkP0GTyVyxsfIR .node .label text,#mermaid-svg-aJAkP0GTyVyxsfIR .image-shape .label,#mermaid-svg-aJAkP0GTyVyxsfIR .icon-shape .label{text-anchor:middle;}#mermaid-svg-aJAkP0GTyVyxsfIR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aJAkP0GTyVyxsfIR .rough-node .label,#mermaid-svg-aJAkP0GTyVyxsfIR .node .label,#mermaid-svg-aJAkP0GTyVyxsfIR .image-shape .label,#mermaid-svg-aJAkP0GTyVyxsfIR .icon-shape .label{text-align:center;}#mermaid-svg-aJAkP0GTyVyxsfIR .node.clickable{cursor:pointer;}#mermaid-svg-aJAkP0GTyVyxsfIR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aJAkP0GTyVyxsfIR .arrowheadPath{fill:#333333;}#mermaid-svg-aJAkP0GTyVyxsfIR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aJAkP0GTyVyxsfIR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aJAkP0GTyVyxsfIR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aJAkP0GTyVyxsfIR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aJAkP0GTyVyxsfIR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aJAkP0GTyVyxsfIR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aJAkP0GTyVyxsfIR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aJAkP0GTyVyxsfIR .cluster text{fill:#333;}#mermaid-svg-aJAkP0GTyVyxsfIR .cluster span{color:#333;}#mermaid-svg-aJAkP0GTyVyxsfIR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aJAkP0GTyVyxsfIR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aJAkP0GTyVyxsfIR rect.text{fill:none;stroke-width:0;}#mermaid-svg-aJAkP0GTyVyxsfIR .icon-shape,#mermaid-svg-aJAkP0GTyVyxsfIR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aJAkP0GTyVyxsfIR .icon-shape p,#mermaid-svg-aJAkP0GTyVyxsfIR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aJAkP0GTyVyxsfIR .icon-shape .label rect,#mermaid-svg-aJAkP0GTyVyxsfIR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aJAkP0GTyVyxsfIR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aJAkP0GTyVyxsfIR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aJAkP0GTyVyxsfIR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-aJAkP0GTyVyxsfIR .cpp>*{fill:#FDE9D9!important;stroke:#C55A11!important;color:#111!important;}#mermaid-svg-aJAkP0GTyVyxsfIR .cpp span{fill:#FDE9D9!important;stroke:#C55A11!important;color:#111!important;}#mermaid-svg-aJAkP0GTyVyxsfIR .cpp tspan{fill:#111!important;}#mermaid-svg-aJAkP0GTyVyxsfIR .lua>*{fill:#D6E4FF!important;stroke:#2E5AAC!important;color:#111!important;}#mermaid-svg-aJAkP0GTyVyxsfIR .lua span{fill:#D6E4FF!important;stroke:#2E5AAC!important;color:#111!important;}#mermaid-svg-aJAkP0GTyVyxsfIR .lua tspan{fill:#111!important;} 引用 +1
引用 +1
最后用完的人还回池子
最后用完的人还回池子
串口读线程

只拷贝这一次
内存池

128 × 4 KB = 512 KB
显示通道
存文件通道
Lua 每 10ms 取走

画到界面
写线程

落盘

好处有两个:

  • 内存占用固定,收再多数据也不膨胀。
  • 两个使用者共享同一块内存,不存在"复制给显示成功、复制给文件失败"这种丢数据的可能。

池满时的双通道背压策略

两条通道的策略不一样,这是关键的取舍。

存文件的通道,宁可等,也不丢。 池子一时没有空闲块时,读线程会停下来等磁盘写完,而不是把数据丢掉。文件是拿来做分析的,缺一个字节都可能让分析出错。等待期间界面会提示"存储跟不上",用户看得见。

显示通道则可以慢一点。 它拿不到块时界面显示滞后,但这不是数据丢失------完整的字节流已经安全写在文件里。滞后的只是画面,不是数据。

为了不让显示拖累文件,池子里给文件通道留了 96 块(384 KB)的保底:只有池子剩余多于 96 块时显示通道才能拿块,所以显示再怎么积压,也吃不掉文件的保底份额。

按最高支持速率算,一块 4 KB 约装 44 毫秒的数据,96 块保底相当于 4 秒多的余量,显示侧另有 1 秒多。以 10 毫秒一轮的处理节奏,偶尔被系统调度一下,完全来得及。

丢失可观测性:如实计数与报告

有,而且如实标出来:
#mermaid-svg-KJBOom9NAJhgt8u9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KJBOom9NAJhgt8u9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KJBOom9NAJhgt8u9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KJBOom9NAJhgt8u9 .error-icon{fill:#552222;}#mermaid-svg-KJBOom9NAJhgt8u9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KJBOom9NAJhgt8u9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KJBOom9NAJhgt8u9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KJBOom9NAJhgt8u9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KJBOom9NAJhgt8u9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KJBOom9NAJhgt8u9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KJBOom9NAJhgt8u9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KJBOom9NAJhgt8u9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KJBOom9NAJhgt8u9 .marker.cross{stroke:#333333;}#mermaid-svg-KJBOom9NAJhgt8u9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KJBOom9NAJhgt8u9 p{margin:0;}#mermaid-svg-KJBOom9NAJhgt8u9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KJBOom9NAJhgt8u9 .cluster-label text{fill:#333;}#mermaid-svg-KJBOom9NAJhgt8u9 .cluster-label span{color:#333;}#mermaid-svg-KJBOom9NAJhgt8u9 .cluster-label span p{background-color:transparent;}#mermaid-svg-KJBOom9NAJhgt8u9 .label text,#mermaid-svg-KJBOom9NAJhgt8u9 span{fill:#333;color:#333;}#mermaid-svg-KJBOom9NAJhgt8u9 .node rect,#mermaid-svg-KJBOom9NAJhgt8u9 .node circle,#mermaid-svg-KJBOom9NAJhgt8u9 .node ellipse,#mermaid-svg-KJBOom9NAJhgt8u9 .node polygon,#mermaid-svg-KJBOom9NAJhgt8u9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KJBOom9NAJhgt8u9 .rough-node .label text,#mermaid-svg-KJBOom9NAJhgt8u9 .node .label text,#mermaid-svg-KJBOom9NAJhgt8u9 .image-shape .label,#mermaid-svg-KJBOom9NAJhgt8u9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-KJBOom9NAJhgt8u9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KJBOom9NAJhgt8u9 .rough-node .label,#mermaid-svg-KJBOom9NAJhgt8u9 .node .label,#mermaid-svg-KJBOom9NAJhgt8u9 .image-shape .label,#mermaid-svg-KJBOom9NAJhgt8u9 .icon-shape .label{text-align:center;}#mermaid-svg-KJBOom9NAJhgt8u9 .node.clickable{cursor:pointer;}#mermaid-svg-KJBOom9NAJhgt8u9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KJBOom9NAJhgt8u9 .arrowheadPath{fill:#333333;}#mermaid-svg-KJBOom9NAJhgt8u9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KJBOom9NAJhgt8u9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KJBOom9NAJhgt8u9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KJBOom9NAJhgt8u9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KJBOom9NAJhgt8u9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KJBOom9NAJhgt8u9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KJBOom9NAJhgt8u9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KJBOom9NAJhgt8u9 .cluster text{fill:#333;}#mermaid-svg-KJBOom9NAJhgt8u9 .cluster span{color:#333;}#mermaid-svg-KJBOom9NAJhgt8u9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KJBOom9NAJhgt8u9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KJBOom9NAJhgt8u9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-KJBOom9NAJhgt8u9 .icon-shape,#mermaid-svg-KJBOom9NAJhgt8u9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KJBOom9NAJhgt8u9 .icon-shape p,#mermaid-svg-KJBOom9NAJhgt8u9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KJBOom9NAJhgt8u9 .icon-shape .label rect,#mermaid-svg-KJBOom9NAJhgt8u9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KJBOom9NAJhgt8u9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KJBOom9NAJhgt8u9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KJBOom9NAJhgt8u9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-KJBOom9NAJhgt8u9 .ok>*{fill:#E2F0D9!important;stroke:#548235!important;color:#111!important;}#mermaid-svg-KJBOom9NAJhgt8u9 .ok span{fill:#E2F0D9!important;stroke:#548235!important;color:#111!important;}#mermaid-svg-KJBOom9NAJhgt8u9 .ok tspan{fill:#111!important;}#mermaid-svg-KJBOom9NAJhgt8u9 .warn>*{fill:#FDE9D9!important;stroke:#C55A11!important;color:#111!important;}#mermaid-svg-KJBOom9NAJhgt8u9 .warn span{fill:#FDE9D9!important;stroke:#C55A11!important;color:#111!important;}#mermaid-svg-KJBOom9NAJhgt8u9 .warn tspan{fill:#111!important;}#mermaid-svg-KJBOom9NAJhgt8u9 .bad>*{fill:#FBE0E0!important;stroke:#B03030!important;color:#111!important;}#mermaid-svg-KJBOom9NAJhgt8u9 .bad span{fill:#FBE0E0!important;stroke:#B03030!important;color:#111!important;}#mermaid-svg-KJBOom9NAJhgt8u9 .bad tspan{fill:#111!important;} 取到
池空,文件通道在
池空,且没在存文件


读线程取块
显示和文件各持一份引用

文件流完整
等一下磁盘

文件流仍然完整
计入丢失账本

界面显示 DATA LOSS
驱动层是否溢出
计数可见

但阻止不了
字节流完整

有一种丢失 XCOM 阻止不了:操作系统串口驱动自己的缓冲区溢出。这是驱动层的限制,任何用户态程序都拦不住,只能检测并计数。

所以承诺归结为两条:

  • 只要有文件日志在,接收到的字节流就是完整的。池子满最多让显示慢一拍、读线程等一等磁盘,不会让字节凭空消失。
  • 万一真发生丢失(包括驱动溢出),都会记入账本,界面亮出 "DATA LOSS" 横幅,而不是悄悄瞒过去。账本还记录缺口在数据流中的位置,方便定位。

不隐瞒,是这套设计里和"不丢"同等重要的一半。

脚本扩展机制:热重载与故障隔离

界面和逻辑既然都是 Lua,把能力开放给脚本就是顺理成章的事。scripts/ 目录下每个 .lua 文件都是一个插件,写几行就能用:

lua 复制代码
on.receive(function(text)
    if text:match("^AT\r?\n?$") then
        sys.timer_start(10, function() uart.send("OK\r\n") end)
    end
    return text
end)

这段脚本的意思是:收到以 AT 结尾的一行时,延迟 10 毫秒回复 OK。这是一个典型的设备自动应答场景------用传统工具往往要写一个上位机程序,这里只要几行。

脚本接口分类

接口按职责分成几类:

接口 用途
uart 收发串口
on 注册"收到数据"、"发送数据"这类钩子
filter 决定哪些数据要显示、哪些不要
log 往日志面板写字
wave 把数据推给波形图
sys 定时器、文件操作

热重载与故障隔离机制

改完即生效。 保存脚本文件后不需要重启程序,会自动重新加载。新版本写错了,程序保留原来能用的版本,不会因为一个语法错误就打不开。

写错不拖垮程序。 每个插件单独隔离,连续出错 3 次自动停用,并在日志面板提示。一个插件出问题,不影响其他插件,也不影响主程序。

相关推荐
susplus1 天前
【51单片机通信协议】串口
51单片机·串口·嵌入式·通信协议
玖玥拾3 天前
Lua 基础语法(八) Unity Huatuo (HybridCLR)
开发语言·unity·游戏引擎·lua
x10n94 天前
Postman 汉化完整指南
测试工具·lua·postman
AI情绪识别开源5 天前
检信 Allemotion 认知功能障碍矫正软件体验评审意见报告
lua
玖玥拾5 天前
Lua 基础语法(六)xLua Lua 调用 C# 交互
开发语言·unity·c#·lua
玖玥拾6 天前
Lua 基础语法(五)Unity xLua基础配置与 C# 访问 Lua
开发语言·unity·c#·lua
玖玥拾7 天前
Lua 基础语法(二)
开发语言·unity·lua
玖玥拾7 天前
Lua 基础语法(三) 元表 metatable、元方法、模拟面向对象
开发语言·unity·lua
盟道科技8 天前
Redis+Lua 实现滑动窗口限流:从固定窗口踩坑到生产可用的完整方案
redis·junit·lua