外接 USB 扫码枪偶尔"滴滴"两声,应用没有任何反应。刚插入时容易出现,休眠后再次使用时也容易出现。
这种现象很容易让开发者怀疑监听没启动、首条数据被丢弃,或者休眠恢复后需要重新初始化 SDK。这次我们沿着日志往下查,换过工位机,又更换了连接线,最终发现:故障高度指向原来的线材或接头连接问题。换线后,密集掉线停止,休眠激活后的第一次扫码也完整到达了业务页面。
这篇文章记录排查过程。结论来自现场对照和日志,没有对旧线做电气测量,也没有把它重新接回去进行反向复现,因此不把"旧线内部具体哪里坏了"写成已经确认的事实。
1. 先把"扫不到"拆成几段
现场使用 React Native Android 应用,同时支持外接键盘模拟扫码枪和厂商 POS HID 扫码设备。本次异常的外接设备在系统中显示为:
text
Manufacturer: LWTEK
Product: Barcode Scanner
VID:PID: 0581:0115
Input device: LWTEK Barcode Scanner
这把枪实际走的是 USB HID Keyboard 链路:
text
扫码枪识别条码
→ 通过 USB 发送键盘输入
→ Android 输入系统产生 KeyEvent
→ MainActivity.dispatchKeyEvent()
→ ScanKeyEventHelper 拼出完整条码
→ UidBroadcastScanModule 发出 RN 事件
→ React Native 业务页面处理
扫码枪发声,只能说明设备发出了提示。没有对应型号说明书时,不能仅凭"两声"确定它表示识别成功、唤醒,还是传输失败,更不能据此认定应用一定收到了数据。
排查的第一个问题应该是:这条链路在哪一段停止了?
2. 第一台工位机:USB 还没建立好,应用就无从接收
第一台工位机是 RK3576 平台。日志中最关键的内容来自 USB 内核层:
text
Device not responding to setup address.
device not accepting address 55, error -71
device descriptor read/all, error -32
attempt power cycle
unable to enumerate USB device
系统在尝试读取设备信息、分配地址时就失败了,并反复尝试重新枚举设备。此时还没有稳定的键盘输入设备,继续调整 JavaScript 监听或者条码缓存,无法补回设备没有成功传上来的数据。
后来直接读取工位机内核日志,还看到了:
text
usb 1-1-port7: Cannot enable. Maybe the USB cable is bad?
这条提示让线材成为重点怀疑对象,但它本身不是检测报告。线材、接头、设备固件、供电以及主机侧问题,仍需要对照验证。
Linux 官方文档指出,EPROTO 一类错误可能与设备、固件或线缆有关;EPIPE 在传输完成状态中可以表示端点停滞。应结合错误发生的操作和上下文分析,不能把 -71 或 -32 直接翻译成"线坏了"。参考:USB Error codes。
同一份日志也出现过完整条码接收和业务处理记录。这说明接收链路至少在连接稳定时可以工作,问题具有间歇性;它不能证明应用的所有场景都没有问题。
3. 换了一台工位机,问题仍然出现
接下来把扫码枪接到另一台工位机:RK3288,Android 7.1.2。
如果问题只来自第一台机器的某个 USB 口或者特定系统实现,换机可能会改变表现。但新机器依旧出现密集的设备移除、重新接入,并且内核仍报 error -32。
在 16:59:23~17:00:49 的日志窗口里,Android 输入系统记录到这把 LWTEK 扫码枪:
| 事件 | 次数 |
|---|---|
| Device removed | 16 |
| Device added | 16 |
这是窗口内的事件计数,不代表每次移除和接入都能一一配对,也不等于发生了 16 次人工插拔。现场操作人员明确表示没有反复插拔。
其中一段时序尤其直观。以下为日志节选,条码统一替换为 [BARCODE]:
text
17:00:24.535 InputReader: Device added: LWTEK Barcode Scanner
17:00:24.705 ScanKeyEventHelper: scan success [BARCODE]
17:00:24.705 UidBroadcastScanModule: emit [BARCODE]
17:00:24.839 ReactNativeJS: [QualityTaskList] 扫码: [BARCODE]
17:00:25.220 InputReader: Device removed: LWTEK Barcode Scanner
设备刚连接,应用收到了完整条码,随后系统又把设备移除了。
这把定位范围进一步缩小了:故障跨两台工位机复现,扫码枪和连接线成为共同的重点排查对象。 两台机器仍使用同一套应用,因此换机也不能单独排除全部软件因素。
4. "休眠"要区分系统自动挂起和设备自身行为
因为问题经常出现在休眠之后,我们检查了扫码枪对应 USB 设备的电源管理状态:
text
power/control: on
power/runtime_status: active
power/autosuspend_delay_ms: 2000
这里容易误读的是 2000:它不表示设备一定会在两秒后休眠。
根据 Linux USB 电源管理文档,power/control=on 表示不允许该设备自动挂起;auto 才表示允许自动挂起。延迟值需要结合这个开关解释。参考:Power Management for USB。
所以,采样时的状态不支持"扫码枪被系统 USB 自动挂起"这个判断 。但一次状态读取不能代表整个历史过程,on 也不代表禁止整机休眠,更不能阻止扫码枪固件进入自身的省电状态。
系统 USB 电源管理、扫码枪自身休眠、物理连接异常,是需要分别验证的几个方向。
5. 更换连接线后,密集掉线停止了
保持第二台工位机和应用不变,更换扫码枪连接线后,现场反馈连续扫码正常,休眠后再次使用也正常。
最新日志中,之前短时间内反复移除、接入和枚举报错的现象停止了。不过仍能看到个别断开和重连记录。操作人员确认其中一次没有手动插拔,而是在进行休眠、激活测试。
这时不能简单得出"只要有 USB disconnect,就说明换线没用"的结论。需要继续对齐现场动作,观察重连是否顺利,以及之后的第一条扫码是否完整到达。
最后一次测试的记录是:
| 时间 | 观察到的事件 |
|---|---|
| 17:23:23.800 | 输入系统移除扫码枪 |
| 17:23:31.327 | 输入系统重新加入扫码枪 |
| 17:23:32.351 | 原生层拼出完整条码,并发出 RN 事件 |
| 17:23:32.354 | 业务页面收到完整扫码结果 |
现场确认:扫码枪休眠后,按一下激活,随后第一次扫码成功。
这一次,从原生层发出完整条码,到业务页面打印接收日志,相隔约 3 毫秒。这个数字只描述本次 RN 事件到达业务层的日志间隔,不是扫码枪的识别耗时,也不是完整唤醒耗时。
同样,日志里约 7.5 秒的"设备移除到重新加入"间隔,也不能直接当作唤醒耗时:我们没有记录用户按下激活键的准确时间。
结合现场操作,这些记录支持"休眠测试期间发生断开,激活后顺利重连并完成首扫"的解释;它们不能证明所有扫码枪都应当这样休眠,也不能单独证明断开一定由固件休眠触发。
6. 最终能下什么结论?
把事实和推断分开,排查记录才有复用价值。
| 结论 | 证据与边界 |
|---|---|
| 异常时存在真实的 USB 掉线和枚举失败 | 内核及 Android 输入系统都有对应记录 |
| 这次故障在两台不同平台工位机上复现 | RK3576、RK3288 均有记录 |
| 换线后连接表现明显改善 | 密集掉线停止,连续扫码及休眠激活后首扫成功 |
| 旧线或接头连接问题高度可疑 | 来自换线对照;未测量旧线,也未接回旧线反向验证 |
| 不能确定旧线的具体失效方式 | 没有证据区分内部断续、接头接触、电压降或信号质量问题 |
| 本次无需靠修改扫码接收代码解决已观察到的现象 | 排查期间未修改扫码逻辑,换线后完成了上述验证 |
更准确的处理记录是:更换连接线后故障未在本轮验证中复现,原线材或接头高度可疑,后续继续观察。
如果还需要进一步确认因果,可以在受控测试环境中把旧线接回去,检查同一故障是否重新出现。这次现场没有执行这个步骤。
7. 下次可以直接复用的排查方法
先保留完整日志,再筛选
下面是本次用到的只读诊断命令。先通过 adb devices -l 确认目标设备,再设置序列号:
bash
adb devices -l
SCANNER_STATION_SERIAL='替换为目标工位机序列号'
adb -s "$SCANNER_STATION_SERIAL" shell dmesg > usb-kernel.log
adb -s "$SCANNER_STATION_SERIAL" logcat -d -v threadtime > usb-logcat.log
adb -s "$SCANNER_STATION_SERIAL" shell cat /proc/bus/input/devices
部分 Android 系统会限制读取内核日志,需要使用设备允许的调试方式。以下 rg 命令在电脑上执行:
bash
rg -n 'USB disconnect|error -71|error -32|unable to enumerate|Barcode Scanner' usb-kernel.log
rg -n 'LWTEK|dispatchKeyEvent|scan success|UidBroadcastScanModule|扫码:' usb-logcat.log
这次有一个实际教训:一开始只看 dmesg 的末尾几百行,其他系统日志把关键 USB 记录挤出了显示范围。保存完整日志后再筛选,才看到了连续掉线的全貌。
日志应尽量在复现后立即采集;保存文件也无法找回已经从系统环形缓冲区中被覆盖的内容。不要在采集前清空日志。
用 VID/PID 找设备,不要固定 USB 路径
同一把枪在两台工位机上的路径分别是 1-1.7 和 1-1.6。排查脚本不能默认端口路径永远不变。
本次可以按 0581:0115 查找,然后读取电源状态:
bash
adb -s "$SCANNER_STATION_SERIAL" shell '
for d in /sys/bus/usb/devices/*; do
[ -f "$d/idVendor" ] || continue
[ -f "$d/idProduct" ] || continue
if [ "$(cat "$d/idVendor")" = "0581" ] &&
[ "$(cat "$d/idProduct")" = "0115" ]; then
echo "$d"
cat "$d/power/control"
cat "$d/power/runtime_status"
fi
done
'
这是本次设备的 VID/PID,其他型号需要替换。如果接入多把相同型号的设备,还需要结合序列号、物理端口等信息区分。
按最早失败的那一层继续查
| 日志表现 | 优先排查方向 |
|---|---|
| 描述符读取失败、无法枚举 | 线材、接头、供电、设备固件、主机或 Hub |
| USB 设备稳定,但应用没有 KeyEvent | 输入焦点、窗口、输入模式、事件拦截 |
| 有 KeyEvent,但没有完整条码 | 结束符、字符间隔、解析和缓冲策略 |
| 原生层已有完整条码,JS 没收到 | RN 事件发送、订阅生命周期、线程拥堵 |
| 业务层收到条码,但页面没有结果 | 去重、业务状态、接口和页面处理 |
这些是调查入口,不是仅凭一行日志就能确定的根因。尤其不要把一次成功扫码,扩大成"所有软件路径都没有问题"。
做对照时一次只改一项
如果同时换线、换口、升级 SDK、增加重试,即使最后恢复,也很难知道哪项起了作用。
这次先换工位机,确认故障仍存在;再换连接线,观察到明显改善;最后单独验证休眠激活后的首扫。这样的过程,比直接给代码增加延迟或重连循环更容易留下可判断的证据。
8. 留下的经验
"应用没收到"是用户看到的结果,故障位置要靠逐层证据确定。 先确认 USB 是否稳定,再看 KeyEvent、条码组包、RN 事件和业务处理,可以避免在错误的层次修改代码。
换线是有效的对照实验,但要记录它改变了什么。 它既换了线材,也重新插合了接头,所以这次能高度怀疑原连接组件,不能凭现象区分内部断线、接触或供电问题。
休眠相关问题要验到第一条业务数据。 "设备重新连接"只是中间状态;用户激活后第一枪能否完整到达业务页面,才是这次真正需要验证的结果。
这次最终没有修改扫码接收逻辑。通过保留日志、换机对照、换线以及休眠首扫验证,我们把一个看起来像软件监听失效的问题,收敛到了原线材或接头这一侧,也避免把一次有限的现场验证写成永久保证。