fix(windows): 枚举解码误判 UTF-16 为 UTF-8 导致设备路径碎片化——0x80 全假象

用户 Windows 无驱动实测日志破案:PavoApiW_x64 枚举成功(size=256),缓冲是
UTF-16LE 的裸 USB 设备路径(\\?\usb#vid_0d16&pid_0309#serial#{guid}),
但 printableRatio 把 \0 计为可打印字符,UTF-8 误判通过 → 路径被 \0 拆成
单字符碎片(first='\')→ CheckPrinterStatus/PrintOneCard 全部 0x80——
此前所有"离线"均为碎片名所致,设备与通信从未真正尝试过。

结论反转:**Windows 免驱动成立且必须免驱动**——PavoApi 直连裸 USB 设备
(GUID_DEVINTERFACE_USB_DEVICE);装 HiTi 驱动反而接管 USB 接口导致枚举为空
(用户实测:装驱动前枚举到、装后 size=0、卸载后又枚举到)。

修复:
- decodeAuto 增加 UTF-16LE 特征判定(奇数位字节全零)优先于比例启发;
  新增守护测试(设备路径形态整串不碎片化)
- 向导 Windows 分支反转:不再提供驱动安装(mac only),文案明确
  "SDK 直连无需驱动;已装 HiTi 驱动请卸载(驱动占用 USB 接口导致无法直连)"
This commit is contained in:
dzc committed 2026-09-21 17:49:15 +08:00
1 parent e31ed4115c
commit 62909609d1
3 files changed
+44 -16

No files matched your search

+16 -2
View File
@@ -79,8 +79,7 @@ void main() {
// 纯 ASCII:UTF-8 解码即可正确还原(NUL 计入可打印率,两种编码都可能命中 UTF-8 路径)
final ascii = Uint8List.fromList('HiTi CS-200e'.codeUnits);
expect(PavoPrinter.decodeAuto(ascii).startsWith('HiTi CS-200e'), isTrue);
// 纯中文:UTF-8 解码得乱码(可打印率低),走 UTF-16 路径正确还原
// (ASCII 混中文时 NUL 也计入可打印率,可能仍走 UTF-8 路径——与 C# 行为一致)
// 带中文:UTF-8 解码得乱码(可打印率低),走 UTF-16 路径正确还原
final name = '证卡打印机';
final bytes = <int>[];
for (final c in name.codeUnits) {
@@ -89,6 +88,21 @@ void main() {
expect(PavoPrinter.decodeAuto(Uint8List.fromList(bytes)), name);
});
test('decodeAuto 识别 UTF-16LE 设备路径(Windows 实测形态,整串不碎片化)', () {
// Win11 无驱动实测:枚举返回 UTF-16LE 的 \\?\usb#vid_xxxx&pid_xxxx#... 设备路径。
// 奇数位字节全零特征必须命中 UTF-16 路径,否则整串会被 \0 拆成单字符碎片
// (后续 API 拿 '\' 单字符调用全部 0x80)。
const path = r'\\?\usb#vid_0d16&pid_0309#2ec511089913092#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}';
final bytes = <int>[];
for (final c in path.codeUnits) {
bytes..add(c & 0xff)..add(c >> 8);
}
bytes..add(0)..add(0); // UTF-16 NUL 结尾
final decoded = PavoPrinter.decodeAuto(Uint8List.fromList(bytes));
expect(decoded, contains(r'\\?\usb#vid_0d16&pid_0309'));
expect(decoded.split('\x00').where((s) => s.isNotEmpty).first, path);
});
test('statusTextOf 状态文案', () {
expect(PavoPrinter.statusTextOf(0), '就绪');
expect(PavoPrinter.statusTextOf(PavoApi.dsOutOfRibbon), '色带已用尽,请更换色带');