XCOM 的核心取舍是:串口收发与内存管理放在 C++,界面和业务扩展放在 LuaJIT;接收数据只复制一次,显示和文件通道共享固定大小的内存池。压缩包为 3.93 MB,Windows 10/11 x64 绿色版解压即用,接收池固定为 512 KB,不随数据量增长。

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

进程结构与技术取舍

XCOM 启动后在同一进程中协作三个部分:

flowchart LR
    EXE["xcom.exe 49 KB
启动器"]:::proc LJ["luvjit.exe
LuaJIT 主循环"]:::proc CORE["xcom_core.dll 126 KB
C++17 串口核心"]:::dll IMG["xcom_imgui.dll 859 KB
ImGui 渲染"]:::dll RD["串口读线程"]:::th WR["写线程"]:::th COM("COM 口") EXE --> LJ LJ --> CORE LJ --> IMG CORE --> RD CORE --> WR RD --> COM WR --> COM classDef proc fill:#E8F0FE,stroke:#2E5AAC,stroke-width:1.5px classDef dll fill:#FDE9D9,stroke:#C55A11 classDef th fill:#E2F0D9,stroke:#548235

xcom.exe 只有 49 KB,作用是隐藏控制台窗口。Lua 层负责按钮、菜单、配置和脚本;xcom_core.dll 大小为 126 KB,负责串口打开、读写、超时和断线重连;xcom_imgui.dll 大小为 859 KB,负责 ImGui 渲染。

这种划分让界面改动不需要重新编译 C++:时间戳格式、报文解析和数据标色都可在文本脚本中调整。Lua 调用 C++ 直接进行,批量字节一次性交给渲染层,不在 Lua 中逐字节拼接;单线程界面也不需要为共享状态加锁。

Lua 与主循环同线程,脚本中的慢代码会阻塞界面。因此耗时工作必须放回 C++,接收链路尤其不能在 Lua 中逐字节处理。

固定接收池与双通道背压

串口数据只在读线程复制一次,写入固定内存池:128 块、每块 4 KB,共 512 KB。显示通道和存文件通道各自持有同一块内存的引用,最后使用者归还池块。

flowchart LR
    RD["串口读线程
只拷贝这一次"]:::cpp POOL["内存池
128 × 4 KB = 512 KB"]:::cpp DISP["显示通道"]:::cpp FILE["存文件通道"]:::cpp LW["Lua 每 10ms 取走
画到界面"]:::lua LOG["写线程
落盘"]:::cpp RD --> POOL POOL --> DISP POOL --> FILE DISP --> LW FILE --> LOG DISP -.-> POOL FILE -.-> POOL classDef cpp fill:#FDE9D9,stroke:#C55A11 classDef lua fill:#D6E4FF,stroke:#2E5AAC

这种方式有两点收益:内存占用固定,收取更多数据也不会膨胀;两个使用者共享同一块内存,不需要分别复制,避免显示复制成功而文件复制失败。

两条通道采用不同背压策略:

  • 存文件通道宁可等待。池子暂时没有空闲块时,读线程等待磁盘写完,文件流不丢失;界面提示“存储跟不上”。
  • 显示通道允许滞后。拿不到块时画面慢一拍,但完整字节流已经安全写入文件。
  • 池子为文件通道保留 96 块,即 384 KB。显示通道只有在剩余块数多于 96 时才能取块,不能耗尽文件通道的保底。

按最高支持速率,一块 4 KB 约装 44 ms 数据,96 块约提供 4 秒多余量,显示侧另有 1 秒多。按 10 ms 一轮的处理节奏,偶发调度延迟不会立即造成阻塞。

丢失可观测性与扩展边界

flowchart TB
    Q{"读线程取块"}:::ok
    OK["显示和文件各持一份引用
文件流完整"]:::ok BP["等一下磁盘
文件流仍然完整"]:::warn LOSS["计入丢失账本
界面显示 DATA LOSS"]:::bad DRV{"驱动层是否溢出"}:::warn OVR["计数可见
但阻止不了"]:::bad CLEAN["字节流完整"]:::ok Q --> OK Q --> BP Q --> LOSS OK --> DRV BP --> DRV DRV --> OVR DRV --> CLEAN classDef ok fill:#E2F0D9,stroke:#548235 classDef warn fill:#FDE9D9,stroke:#C55A11 classDef bad fill:#FBE0E0,stroke:#B03030

只要存在文件日志,应用层接收到的字节流就是完整的。若发生驱动层串口缓冲溢出,XCOM 无法阻止,但会计数并在界面显示 DATA LOSS 横幅,同时记录缺口在数据流中的位置。软件不把“未观测到丢失”当成“绝对不会丢失”。

/scripts 目录下的每个 .lua 文件都可作为插件。下面的示例在收到 AT 后延迟 10 ms 回复 OK:

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)

接口按职责分成几类:

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

保存脚本后自动热重载,不需要重启。若新版本存在语法错误,程序保留原来可用的版本;每个插件独立隔离,连续出错 3 次后自动停用并在日志面板提示,不影响其他插件和主程序。