# handy_serial **Repository Path**: Bryan_He/handy_serial ## Basic Information - **Project Name**: handy_serial - **Description**: 原生 Win32 串口控制台 + 日志高亮:逐行时间戳、时间差测量、词条高亮、多词条搜索,单文件免安装。 - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-08 - **Last Updated**: 2026-10-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HandySerial 串口控制台 / 日志高亮工具 基于 **C++ / Win32** 原生 Windows 窗口应用(无 Electron、无运行时依赖、单个约 0.8MB 可执行文件)。 ## 功能特性 - **串口连接、命令行登录 Linux**:自动枚举 COM 口(刷新),可配置数据位/校验位/停止位/换行符/编码(UTF-8、GBK、**HEX 直显**)。连接后即可通过串口登录 Linux(输入用户名→回车→密码→命令行操作)。 - **波特率支持自定义输入并自动记忆**:波特率下拉框可直接键入任意数值(如 1500000),程序退出/连接时会保存到 `handyserial.ini`;每次启动自动加载上次的串口、波特率(含自定义值)、数据位、校验位、停止位、换行、编码、时间戳开关/格式、本地回显、自动滚动等全部配置。 - **高亮规则一并保存**:右键设置/修改/清除的词条高亮(词条文字、颜色、区分大小写、整词匹配)都会自动写入 `handyserial.ini`,下次启动自动恢复并作用于新日志。 - **每行日志自动加时间戳**:`[HH:mm:ss.fff]` 等 4 种格式可选,可随时开关。 - **时间差测量(两点间隔)**:**Ctrl+左键**点日志行左侧的**时间戳** → 该时间戳填充**亮紫**(起点),再 **Ctrl+左键**点**另一个时间戳** → 它填充**青绿**(终点),状态栏立即显示 `Δ时间差: 1.234 s (1234 ms) [12:00:01.123 → 12:00:02.357] 行 12→317`;起点保持不动,因此可以用同一个基准反复比多行(终点早于起点时差值为负);再次点同一个起点、**或直接按 Esc** 即清除标记与结果(两处填充一起撤掉)。**右键只在菜单里操作、不会一点就测量**,而且时间差项**直接就在一级菜单里**(不套子菜单,少点一层):右击时间戳弹出的菜单里依次是「复制该行(无时间戳)」「复制该行(含时间戳)」「复制该时间戳」,以及「标记该时间戳(时间差起点)」「计算与该起点的时间差」「清除时间差标记」(抬头一行灰显显示当前起点与上次差值,用不上的项自动置灰),其余复制/高亮/搜索/清屏菜单项一个都不少。正文上 Ctrl+左键仍是原来的文字选择;行号栏与导入的日志行(无时间戳)不参与测量。 - **词条颜色高亮**:鼠标选中日志中的词条 → 右键 → 选择颜色,日志中所有相同词条自动用该颜色填充;支持“区分大小写 / 整词匹配”;再次右键该词条可改色或“移除此词条的高亮”,也可“清除全部高亮”。 - **词条搜索(支持一次搜多个词条)**:顶部「搜索」框输入关键词即高亮日志中全部匹配词条(大小写不敏感、输入即搜、与颜色高亮互不干扰);多个词条**只用 `|` 分隔**(如 `error|fail|timeout`),其余字符(空格、逗号、分号、引号…)都按普通字符处理 —— 所以**含空格的短语可以直接按短语搜**(如 `kernel panic`),词条首尾空格会被忽略、空词条与重复词条自动去重,最多 16 个;「上一个 / 下一个」或 回车 / Shift+回车 会在**所有词条**的命中之间按行序(同一行内按列)逐条定位,到底自动回绕;搜索框内 ↑/↓ 可回看/前进最近搜索过的表达式(最多 50 条,宏 `SEARCH_HIST_MAX`);「清除」或 Esc 取消;右键选中词条可「搜索该词条」或「**追加该词条到搜索**」(把多个词条逐个并进同一个搜索);状态栏实时显示总匹配个数与词条数,下方结果预览窗列出命中任一词条的行(并集去重、行内同样高亮全部词条、点击可定位);在日志里选中文字后按 **Ctrl+F** 也能把选中的词条追加进这个表达式(见下文「日志查看」)。 - **深色 / 浅色主题**:第一行行首「主题」下拉框随时切换、立即生效并写入 `handyserial.ini`;浅色主题对齐 **VS Code 默认浅色主题 Light Modern**(日志底 `#ffffff`、工具栏/状态栏 `#f8f8f8`、描边 `#e5e5e5`、焦点框 `#0090f1`、主按钮 `#0078d4`(hover `#026ec1`)、次级按钮 `#e5e5e5`、行选中 `#0060c0`+白字、正文 `#3b3b3b`、行号/时间戳 `#6e7681`、搜索命中 `#a8ac94`、警告 `#bf8803`、错误 `#e51400`、ANSI 绿 `#00bc00`)。 - 发送区:回车发送命令、↑/↓ 回看发送历史、Ctrl+回车 = 无换行发送;**Tab 键 = 远端补全**(把命令框里已输入的内容发给板子上的 shell 去补全,回显解析后自动写回输入框;之后回车只补发新增的字符,同一段文字绝不发两遍;连按 Tab 即换下一个候选);本地回显开关;特殊按键(Ctrl+C/D/Z/A/U、Esc、Tab、回车等)快捷发送;DTR/RTS 电平控制。 - **发送文件(分包发送任意文件)**:右下角「发送文件」→「浏览…」选任意文件;可设置 **每包数据长度**(1~65535 字节)、**每包间隔**(0~60000 ms,0 = 连着发)、**包头标识 0~4 字节**(先选字节数,再逐字节填 2 位十六进制,如 `AA`/`0x5A`)。有包头时的帧格式是 `[包头][数据长度 2 字节,大端][数据 ≤ 每包长度]`;**包头长度选 0 字节时没有这两个字段**,按每包长度一包包发原始数据。发送中窗口与状态栏都显示进度、可随时「停止」,关闭窗口不影响发送(日志与状态栏照常显示进度);窗口里**「包头长度」是与主界面同款的下拉框**(扁平底色 + 细箭头,展开列表随主题)、**「帧格式」一行用醒目颜色实时预览当前帧结构**(改包长/改包头立即更新),发送中整片设置区连同下拉框一起置灰;文件、每包长度、间隔、包头都记进 `handyserial.ini`,下次打开自动恢复。 - 暂停显示、自动滚动(向上翻页自动停止跟随,回到底部自动恢复)、清屏、保存日志、复制(带/不带时间戳、复制整行)。**Ctrl+A 全选当前日志文本**(含时间戳前缀,接着 Ctrl+C 即可整段复制),日志区内还支持 Ctrl+C 复制选中。**Ctrl+F = 把选中的文字追加到搜索框**:在日志区(或结果预览窗)里选中词条后按 Ctrl+F,选中的文字会以 `|` 并入当前搜索表达式 —— 多行框选时**每行算一个词条**、只取正文不含时间戳前缀、已在表达式里的词条不会重复加入,然后焦点跳到搜索框末尾,接着选中下一个词条再按 Ctrl+F 就能把 `error|timeout|CRC fail` 这样一串关键字一点点凑齐(一次最多 8 个、整体仍受 16 个词条上限约束)。**超长日志行不会被裁剪**:比窗口宽的行会出现底部水平滚动条,可拖动、Shift+滚轮或 ←/→ 键左右查看(Ctrl+←/→ 直达两端)。 - **导入/查看别人的日志**:点工具栏「导入日志」打开 .log/.txt 文件,内容按原样载入查看区(**不自动附加时间戳**,文件自带的时间原样显示),并按当前高亮/关键字配色规则渲染;「关于」查看版本、作者与开源地址。 - 实时行(尚未回车结束的行,如 `login:`、Shell 提示符)实时显示在日志末尾,回车后自动成为带时间戳的日志行。 - 自动剥离 ANSI 控制序列、支持退格擦除;自动过滤空行;**缓存大小按内存设置(单位 MB,直接输入)**:第三行「内存上限」框内填数字(默认 64,可填 4~4096),回车或移开焦点即生效,缓存占用超过上限就**自动丢掉最旧的数据**(先进先出);状态栏实时显示 `行:当前行数` 与 `缓存≈实际占用/上限`,方便按自己电脑的内存资源调整;该值写入 `handyserial.ini`(`maxmemorymb`)。行数上限(旧版 `maxlines`)已随界面隐藏并不再读写,避免它悄悄盖过内存上限。被丢掉的行无法再搜索/复制/保存,重要会话请先「保存日志」;状态栏匹配数与搜索结果预览窗采用**增量匹配缓存**,内存上限调大后每次刷新也不会全表重扫。 - **多开(同时看多块板子)**:可以同时打开多个窗口 —— 每个窗口都是独立进程,各自连接自己的串口、各自维护日志/高亮/时间差标记,互不干扰;**标题栏会显示「实例名 + 已连接串口」**,任务栏 / Alt-Tab 里一眼分清是哪个窗口。默认多个窗口共用 `handyserial.ini`(谁后退出谁覆盖),想让每个窗口各自记住设置:`HandySerial.exe --instance 板子A`(自动用 `handyserial-板子A.ini`,标题栏加“- 板子A”)或 `HandySerial.exe --ini D:\cfg\a.ini`(完全指定该窗口的配置文件)。同一路串口由系统独占,两个窗口不能同时打开同一个口。 - `--demo`:无需串口硬件即可演示全部核心功能;`--selftest`:自动回归测试核心链路,写出报告后退出(退出码 0=通过)。 ## 界面布局 - 第一行:**主题(深色/浅色,行首)** | 时间戳(开关) | 本地回显 | 自动滚动 | 时间格式 | 暂停显示 | 清屏 | 保存日志 | 导入日志 | 特殊按键 | 按键 | 关于 | 帮助 - 第二行:串口 | 刷新 | 波特率 | 数据位 | 校验位 | 停止位 | 换行 | 编码 | DTR | RTS | 连接串口 - 第三行:搜索(词条,可多词条) | 内存上限(MB) | 上一个 | 下一个 | 清除 - 中部:日志显示区(深/浅色主题,词条按规则填充颜色,时间戳灰显) - 底部:指令输入框 + 发送按钮(发送 / 无换行发送 / **发送文件**);状态栏显示 连接状态 / RX / TX / 行数 / **时间差(右击时间戳测量时)** / 缓存实际占用与内存上限 / 高亮规则 / 搜索匹配数(词条数) / **远端补全提示(Tab)** / **发送文件进度** / 暂停等 - 「发送文件」窗口(非模态,可一边发一边看日志):文件(+浏览…) / 每包数据长度 / 每包间隔 / **包头长度(下拉框,与主界面同款)** / 包头字节×4(十六进制) / **帧格式(醒目色实时预览)**,底部为 开始发送 / 停止 / 关闭;一项一行、按当前字号实测宽度排布 ## 编译(Windows,需 MinGW-w64 g++ / windres) ```bat build.cmd ``` `build.cmd` 自己会找工具链:先看环境变量 `MINGW_BIN` 指向的 `bin` 目录,再看 `PATH`,最后探测 `C:\msys64\mingw64\bin`、`C:\mingw64\bin` 等常见安装位置;一个都找不到时会打印安装提示并返回错误码 1 (不再像以前那样只在 windres 处报一句 “windres FAILED”,看不出真正原因)。也可以手动指定: ```bat set MINGW_BIN=D:\mingw64\bin build.cmd ``` 或手动编译: ```bat windres appicon.rc -O coff -o appicon.o g++ -O2 -std=c++17 -DUNICODE -D_UNICODE -D_WIN32_WINNT=0x0601 -DWINVER=0x0601 ^ -municode main.cpp serial.cpp logview.cpp appicon.o -o HandySerial.exe ^ -mwindows -static -lgdi32 -luser32 -lcomctl32 -lcomdlg32 -lshell32 ^ -lole32 -ldwmapi del appicon.o ``` > `-D_WIN32_WINNT=0x0601 -DWINVER=0x0601` 不能省:较老的 MinGW-w64(如随板卡 SDK 分发的 > GCC 8.1.0)默认目标是 `0x0502`,那样 `serial.cpp` 里用的 `CancelIoEx`(Vista+ API)不会被 > 声明,编译会报 `'CancelIoEx' was not declared in this scope`。 > `appicon.rc` + `icon.ico` 会将图标嵌入 exe(文件资源管理器、标题栏、任务栏都显示该图标);替换 `icon.ico` 后重新 `build.cmd` 即可生效。 ### 打包发行 ```bat package.cmd ``` 先调用 `build.cmd` 做一次 release 编译,再按 `main.cpp` 里的 `kVersion`(单一版本来源)生成: - `dist\HandySerial-v<版本>-win64.zip` —— **命名与既有发行包一致**(V1.2.0 / V1.3.4),内层同名文件夹 `HandySerial-v<版本>-win64\` 里是:`HandySerial.exe`、`handyserial.ini`(干净默认值)、`icon.ico`、 `README.md`、`README.en.md`、`LICENSE`、`CHANGELOG.md`(上一个 tag 以来的提交) - `dist\SHA256SUMS.txt` —— exe 与 zip 的 SHA256 校验值 把 `main.cpp` 里的 `kVersion` 改成新版本号后重新跑 `package.cmd`,压缩包名字与 `CHANGELOG.md` 标题都会跟着变。 ### 上传到 Gitee 发行版 ```bat set GITEE_TOKEN=你的私人令牌 publish_gitee.cmd ``` 令牌在 Gitee → 设置 → 安全设置 → 私人令牌(勾选 `projects`)。脚本按 `kVersion` 自动创建发行版 `HandySerial V<版本> 正式发行`(正文 = 更新内容 + 下载 + SHA256 校验)并上传 zip 与 `SHA256SUMS.txt`, 已存在同 tag 发行版时自动复用;不设 `GITEE_TOKEN` 时会先尝试 Git Credential Manager 里缓存的 gitee 凭据。 ## 运行 ```bat HandySerial.exe :: 正常使用 HandySerial.exe --demo :: 无硬件演示核心功能 HandySerial.exe --selftest :: 自动回归测试(退出码 0 = 通过) HandySerial.exe --instance 板子A :: 多开:该窗口用 handyserial-板子A.ini,标题栏显示“- 板子A” HandySerial.exe --ini D:\cfg\a.ini :: 多开:完全指定该窗口的配置文件 ``` ## 使用提示(串口登录 Linux) 1. 点“刷新”选择串口(如 COM3),波特率选 115200,点“连接串口”。 2. 出现 `login:` 提示后,在下部输入框输入 `root` 回车;提示 `Password:` 时输入密码回车,即进入 Shell。 3. 需要中断程序可点“特殊按键”发送 Ctrl+C;操作可用 ↑/↓ 回看命令。 4. 选中日志中的词条(如 `error`)→ 右键选颜色 → 所有 `error` 都会被该颜色填充,便于在大量日志中快速定位。 5. 想固定读取历史时点“暂停显示”,或直接向上滚动(自动跟随会暂停,滚动到底部后恢复)。 6. 波特率可下拉选择,也可直接在框内输入自定义数值(如 1500000);设置会在程序退出后自动保存,下次打开自动恢复。 7. 搜索可一次搜多个词条:在「搜索」框里**只用 `|` 分隔**(如 `error|fail|timeout`);空格/逗号/分号/引号都按普通字符处理,所以含空格的短语直接输入即可(如 `kernel panic` 按短语匹配),词条首尾空格会被忽略、空词条与重复词条自动去重。回车 / Shift+回车 或按钮在所有词条的命中之间逐条定位,状态栏显示总匹配数与词条数;也可以在日志里右键选中词条 →「追加该词条到搜索」把多个词条并进同一个搜索。 8. 缓存大小按内存设置(单位 MB,直接输入):「内存上限」框内填数字(默认 64,4~4096),回车或移开焦点即生效;缓存占用超过上限就自动丢掉最旧的数据(先进先出)。状态栏实时显示 `行:当前行数` 与 `缓存≈实际占用/上限`,照着调即可(8GB 内存的机器给 32~64MB 很宽裕)。注意被丢掉的行无法再搜索/复制/保存,重要会话请及时「保存日志」。 9. 量两点之间的时间差:在日志区 **Ctrl+左键**点某行的**时间戳**(不是正文)→ 该时间戳填充**亮紫**(起点);再 **Ctrl+左键**点**另一个时间戳** → 它填充**青绿**(终点),状态栏显示差值(如 `Δ时间差: 308 ms [22:50:13.386 → 22:50:13.694] 行 1→317`),便于看「两条日志之间隔了多久 / 某步操作耗时」。起点会一直保留(紫色不动),继续 Ctrl+左键点别的时间戳都是与该起点比,每次新点的那一行变青色;**按 Esc 键**或再次点同一个起点即可清除标记与结果(两处填充一起撤掉)。**右击只在菜单里操作**(不会一点就生效),时间差项**直接在一级菜单里**:右击时间戳后可直接点「标记该时间戳(时间差起点)」「计算与该起点的时间差」「清除时间差标记」(顶部还有一行灰显信息告诉你当前起点与上次差值,用不上的项置灰),复制项里另有「复制该时间戳」「复制该行(含时间戳)」,其余菜单项照旧。Esc 在任何位置都生效(日志区、命令输入框、工具栏控件上均可;只有搜索框例外——那里的 Esc 仍专用于清除搜索)。 10. 命令补全按 **Tab 键**(和真机 UART 终端一样,补全是**板子上的 shell** 做的):程序把命令框里已输入的内容(只发板子还不知道的那些字符)连同 TAB 一起发给板子,等它把结果回显回来再写回输入框,你可以接着改;回车时只补发新增的字符,所以同一段文字不会发两遍。如果回头改了前面的字符(或刚用过「无换行发送」),下次发送前会先发 Ctrl+U 清掉板子上的那一行再整行重发,保证板子上的行与命令框一致。补出来的就是板子上真实存在的命令/路径;多候选时 shell 会把候选列表打印出来并重画当前行,列表会以普通日志行的形式留在日志里。状态栏会显示最近一次 Tab 的结果(等待回显 / 已写回 / 无匹配 / 板子似乎不做补全)。 11. 往板子里灌文件(固件、配置、音频数据…)用 **「发送文件」**(右下角按钮):选文件 → 填每包数据长度与每包间隔 →(可选)选包头字节数并逐字节填十六进制 → 「开始发送」。帧格式:**包头长度 ≥1 字节**时每包 = `自定义包头` + `数据长度(2 字节,大端)` + `数据`;**包头长度 = 0 字节**时**不带包头也不带长度字段**,就按你设定的每包长度一包包发原始数据。发送过程中界面照常可用(定时器按间隔发包,单趟最多占用约 20ms):可以看日志、发命令,也可以点「停止」随时中止(日志里会记录已发送的字节数与包数);一包数据本身要按波特率发完(115200 下 1KB 约 90ms),这段发送时间里界面会有轻微顿挫,把「每包长度」调小或把「每包间隔」调大就更跟手;**关掉这个窗口不会中断发送**,状态栏里有进度(`发送文件:1234/5678B (21%)`)。窗口里「包头长度」是下拉框(与主界面下拉框同款:扁平底色 + 细箭头,展开列表跟随主题),「帧格式」一行用**醒目颜色**实时显示当前帧结构,改包长/包头立即更新;发送中整片设置区连同下拉框一起置灰,防止半路改设置。这套设置连文件路径都会记住,下次直接点「开始发送」即可。 ## 源码结构 | 文件 | 说明 | | --- | --- | | `main.cpp` | 主窗口、日志模型/时间戳解析、词条搜索与命中索引、右键颜色菜单、命令发送 | | `logview.h` / `logview.cpp` | 自绘虚拟化日志视图(只画可见行、行式滚动、增量匹配缓存) | | `serial.h` / `serial.cpp` | Win32 串口封装(overlapped IO + 读取线程,支持安全关闭) | | `build.cmd` | MinGW-w64 一键编译脚本 | | `package.cmd` | 一键打包发行包(`dist\HandySerial-v<版本>-win64.zip` + `SHA256SUMS.txt`) | | `publish_gitee.cmd` / `.ps1` | 一键创建 Gitee 发行版并上传 zip 与 `SHA256SUMS.txt` | ## 说明与限制 - **发送文件(分包发送)**:每包数据长度上限 65535 字节(2 字节长度字段的天然上限,输入其它值会被拒绝);大包在慢波特率下程序会把一次 Write 自动拆成多次(按当前波特率折算约 2 秒一片、64~4096 字节),避免撞上串口层 3 秒的写超时;「每包间隔」是“上一包发完 → 下一包开始”的等待时间,0 = 不等(连续发,界面仍可用)。发送中若串口断开或写入失败会立即中止并在日志/弹窗给出原因;发送过程中改设置无效(设置区会置灰),避免半路换包头/包长。 - 本工具按“命令行式”串口会话设计(逐行发送命令),适用于 Linux 串口登录与常规 Shell 操作;不适用于依赖完整终端仿真的全屏程序(如 vi/top 界面模式)。 - **可以同时打开多个窗口(多开)**:每个窗口独立连自己的串口,互不干扰;默认共用 `handyserial.ini`(后退出者覆盖),需要各记各的用 `--instance` / `--ini`;同一路串口由系统独占,不能两个窗口同时打开同一个口。 - **波特率设错导致的乱码洪流不会拖死界面,更不会把程序撑崩**:如果实际波特率远高于这里所设(例如板子跑 1M 而这里设 115200),串口会源源不断送进“乱码”。程序按 **8 ms / 1000 行**为一趟时间片解析,每一趟之间都回到消息循环(窗口照常重绘、滚动、点击),接收队列上限 **8 MB** —— 顶到上限后**丢掉最旧的数据并把丢弃量记下来**:状态栏常驻显示 `丢弃:N B`,日志里同时给出一条系统行提示(洪流下最多每 2 秒一条,不会刷屏)。显示侧同样有硬上界:一趟里逐行刷新合并成一次(**批量喂入**)、超长行测宽只测前 1024 字再按字数线性外推、重绘按可见宽度分片画且只扫前 8192 字做配色 —— 所以“一条几十万字的乱码行”(乱码里长期没有换行时会出现)也不会把界面按住,按「断开串口」照样立刻响应。**界面到底有没有卡过、卡了多久,看状态栏的 `停顿:max Nms`**:只有真的卡过(> 700 ms)才出现,同时在日志里留一条 `界面停顿 …ms(当时:…)` 说明那一下在干什么(正常会话永远是空的)。看到提示请先把波特率改成板子上的真实值,然后重新连接(重连即清零计数)。断开/退出时会先丢掉还没解析的数据、主动取消未完成的读取,且读取线程内部所有等待都有超时上限,所以即使驱动不理会 `CancelIoEx` 也不会把界面卡住。 - 日志默认按 UTF-8 解码,可切换 GBK 以显示中文日志;**编码下拉的第三项「HEX」为十六进制直显**:收到的数据按 `41 42 43`(大写、空格分隔)逐字节显示,成行日志与实时行都适用,退格按一个字节删除。**切换编码会立即重渲染已有日志**(每行都留了一份原始字节,按新编码重新解码并重建视图,实时行同步刷新),所以 UTF-8 / GBK / HEX 可以随时来回切着看;系统提示行、本地回显、导入的 `.log` 天生是文本,不受 HEX 影响。暂停显示时视图是冻结快照,模型已按新编码更新,点「继续显示」即为新编码。 - **Tab 补全(远端补全)依赖板子上 shell 的行编辑**:bash 的 readline、busybox ash 的 lineedit 都支持,补出来的就是板子上真实存在的命令/路径;少数极小系统的 `sh` 没有行编辑,TAB 会被当作普通字符收下(状态栏会提示「板子似乎不做补全」),这时请改用「特殊按键 → Tab」或直接输入完整命令。补全请求会让板子重画当前行(`\r` + 提示符 + 整行),本程序按 CR 分行显示,所以多候选的候选列表会各占一行留在日志里(有意保留的信息,不会被吞掉);解析补全用的是**原始字节**,因此 HEX 直显模式下补全照样可用。程序内部只给这一行记账(远端行缓冲里已有的字符不重发),换编码 / 暂停显示 / 导入日志都不影响它。**板子在折行 / 写到终端最后一列时会再补一个 CR/LF**,于是补全后的整行会直接变成一条带时间戳的日志行(末尾实时行变空)—— 这种情况程序会自动改用「整段回显」兜底解析(去掉 ANSI 与控制字符、按退格擦除后再找锚点),照样能补全,状态栏会写明是 `…(回显在换行后结算)`;若兜底解析发现回显里像是一串候选(中间夹着空白,说明板子只打印了候选、没重画整行),则保持输入框不动并在状态栏说明。