410 型优卡特称重台 · 2026.09.09

扫码识别中断:SDK 与代码审计

硬件类型澄清(用户2026-09-09确认):本项目使用扫码器扫描二维码。rfidData / rfidLen 是厂商SDK字段名,不代表此设备采用射频识别;原字节码证据和变量名保留。

SDK 找对了,Mac 上的对应代码也已找到。会议所说的连续空码计数确实存在,但“300ms 与 100ms 不一致”还不足以解释误离盘;SDK 解析与应用空码分类中已发现可复现的问题,现场根因仍需用故障时间线确认。

1. 我们真正要解决什么

目标是:餐盘未离开时,保持同一餐盘、同一次取餐会话;餐盘真正离开时,在明确的时限内完成一次收尾;通信异常时,给出设备异常状态,避免把异常输入当作餐盘被拿走。

当前尚未闭环的问题有四层:

  1. 解释层:会议用“盘已拿走、旧码继续返回”的例子解释中断,这只能证明离盘识别有延迟,不能证明“盘仍在却误判离盘”。
  2. SDK 层:合并输入多帧时留有积压;部分长度异常帧会输出空码;校验字节为 0x3C 的构造帧通过 SDK 外层自检,却在组帧时被截断。这些行为已经通过实际 SDK Java 字节码隔离验证,构造样本在真实硬件中的可达性仍需确认。
  3. 应用层:空码与部分不合格非空码共用离盘计数;实际采用回调次数,不是时间窗口;新装、升级和已有配置的默认策略不同。
  4. 验证层:尚无一条把“盘是否在位、原始串口输入、SDK 输出、计数、界面与取餐记录”对齐的现场故障时间线。会议中的“九十几”没有报告、分母或测试集,不能作为覆盖率证据。

本轮已完成审计与隔离验证,没有修改业务代码、安装 APK 或验证现场修复。

2. 找到的 SDK 和 Mac 代码

核验对象 实际结果
业务项目 410 型优卡特适配单秤 ZhctWeightingTableYoukate
Mac 真实路径 /Users/jack/code/010-cpt/008-zhct/zhctproject/android/ZhctWeightingTableYoukate
当前检出 master,版本 2.6.0 / 21,工作树干净
当前提交 2f759f9415b46a7166215fa670d5b4fafeef615e
对照开发分支 origin/zymy_ongoing,21a5d3d956f0a2dabec036a947e315e957a86997,版本 2.6.2 / 23
SDK 位置 项目 app/libs/DeviceSDK-80.1.10.12.20260129.aar
SDK 一致性 用户提供的企业微信附件与项目包均为 90,728 字节,SHA256 完全相同
SDK SHA256 adabb1f0f4750081abe131a3baa6f76057ff931e05d7264c56bf6fba5d99aeec

已在线回读两个远端分支的 SHA,与本地所审快照一致。两分支关键 HomeActivity、硬件封装、离盘配置/策略文件相同;因此以下核心判断适用于这两个快照。不能据此推断现场安装 APK、固件或开发者未提交代码也相同。

截图里的 ~/AndroidStudioProjects/... 是来源机器的目录提示;Jack 本机没有该目录。本次依据正式项目登记找到上述代码,不靠截图目录名猜测。

首页实际识别链路为:

扫码头 / 秤体控制板
  → 串口复合帧
  → SDK PC681ScaleManager
  → ScaleData.rfidData + 重量/状态
  → YoukateHardwareManager
  → HomeActivity.handleYoukateScanCode
  → 连续空计数 / 餐盘识别 / 取餐收尾

ScanCodeUtils 与 SDK 的 SerialScanCodeImpl 是另一条独立扫码调试路径,不能拿它们的行为解释首页 PC681 链路。特别是独立扫码器里的 3000ms 去重不等于会议里的 300ms 采样。

3. 会议思路哪些成立,哪些仍缺证据

会议说法 核验判断
秤体约 100ms 回报,扫码头约 300ms 真采样 是会议转述;SDK 可配置称体间隔,但包内不能证明扫码头固定 300ms 或控制板必然重复三次
连续 3 次空码才认为拿盘,中间有效码清零 普通盘码路径确实如此;还有非空短码、特殊码、换盘等分支,不能只看这一句
可配 3~10,未设置默认 7 范围与新装默认属实;升级且缺配置会初始化为 3,需要核对现场有效值
默认 7 与厂商后续 900~1200ms 建议的关系 两者不能等同:7 是次数,当前没有按这段时长计时;会议只称 7 对应此前建议的起点
已排除硬件或外部干扰 原文没有完成排除;纪要结论过强。原文 08:46 优先提出业务理解与代码实现两个核查方向
Codex 测试覆盖率超过 90% 原文 07:36 只是回忆“九十几”;没有报告,不能写成正式覆盖率

会议关键位置:原文第 1 页 00:59–03:23 说明节拍、计数与配置;第 2 页 04:34–07:20 争论旧码与连续空;第 3 页 07:26–09:35 要求反例并核查 SDK/代码;第 4 页 11:42 强调最高优先级。

3.1 Jack 的质疑成立的条件

如果盘始终在、每次真实扫码都成功、控制板一直返回最近一次真实结果,那么 300ms 扫一次、100ms 重复回报只会得到 A、A、A、A…,不会凭空出现空码。仅有频率差,不能推出误离盘。

会议中的“读到 A 后把盘拿走,随后两包还返回 A”,说明的是实际离盘后仍保持旧状态一小段时间。必须另找一个“盘没拿走但输入变为空”的证据,才能解释误中断。

3.2 一个成立的条件反例

以下是假设模型,已做确定性模拟;不是现场抓包。额外条件是:盘仍在,某一次真实扫码漏扫为空,而且控制板把这个空结果保持到下一次扫码。

时刻 物理餐盘 真实扫码 回报给应用 连续空次数
0ms 在 A A 0
100ms 在 无新采样 A 0
200ms 在 无新采样 A 0
300ms 在 本次漏扫 空 1
400ms 在 无新采样 空 2
500ms 在 无新采样 空 3 → 误判离盘
600ms 在 A A 重新识别

这里三次空回报只代表一次真实漏扫。把阈值提高到 7,可以避开这个特定反例,但连续更久的漏扫仍会触发;因此它是容错参数,不是根因证明。

规则还必须区分:空码是收到一包但字段为空;无回包是没有新事件。按回调计数的规则不会因完全无回包而自行累加。

4. SDK 详细结论

S1:一次读取多帧时,仅解析第一帧,余帧可能滞留

实际 PC681ScaleManager.parseCacheData() 一次只处理一个帧;读取线程空闲时调用的 checkPackageTimeout() 没有排空缓存逻辑。隔离验证把 A、B 两帧一次注入缓存并调用解析方法,只收到 A,B 的 35 字节留在缓存;主动调用空闲检查仍不产生 B 回调;再注入 C 并解析,才先回调旧 B,C 继续滞留。测试没有启动真实读取线程。

影响:应用处理的盘码和重量可能落后于输入;尾帧可能等待下一批数据。这可以加重旧码延迟或旧空码延迟,但单独不能证明盘仍在时必然误判离盘。需要现场出现合并读取或积压的证据。

S2:SDK码字段声明长度不一致仍能作为空码上报

构造总长度 35 字节、SDK 外层校验通过的帧:SDK码字段声明长度为 7,实际只有 6 字节。SDK 没有把它送入解析错误回调,而是正常回调一个空 rfidData。

影响:协议异常在接口上变成“没有盘码”。如果业务正在取餐、这些回调达到有效阈值,就会走离盘收尾。这是与会议症状最直接相关的候选链路之一;现场是否出现该种帧仍待证实。

S3:校验字节 0x3C 会被组帧器误认作帧尾

构造完整帧通过 SDK 自己的帧头尾/校验和检查,但其校验字节恰好是 0x3C。组帧器先遇到该字节便截断,随后报告非法帧,该数据没有回调给业务。此人工样本温度字段为 0x00C7,其硬件编码范围和真实可达性尚未得到厂商协议确认。

影响:存在 SDK 外层自检接受、组帧却拒绝的输入;缺帧可能干扰恢复和数据新鲜度。丢包本身不会增加空码计数,还需要与前后空码/恢复时序一起分析。

S4:SDK 没有实现会议中争论的离盘算法

PC681 SDK 直接提取帧中的码字段(SDK命名为 rfidData)。该路径未发现扫码头 300ms 真实采样调度、固定重复旧码三次、3/7/10 次离盘判断;离盘逻辑在应用。扫码头发热、控制板缓存策略、固件周期与“900~1200ms 建议”均不能从此 AAR 单独证实。

现有应用并未旁路或修复这些 SDK 解析方法。历史提交 266709fecdfe27586d505bc9782a242447db84da 的缓存竞态修正是调整暂停监听行为,避免应用直接清 SDK 读线程缓存;它不等于修复本次发现的解析缺陷。

完整方法、字节码定位、输入与验证脚本见 SDK 专项证据。

5. 应用是否按讨论思路实现

A1:有“连续空、有效码归零”,但计数输入不只有真正空码

HomeActivity 的主路径确实对普通有效盘码清零,再处理识别;空码则增加计数,达到阈值调用离盘处理。实际方法隔离测试验证了 7 个连续空回调在第 7 次触发,以及中间恢复同盘有效码后计数归零。

但长度非 6 的非空码也会被清洗成空。例如 SDK 接受并回调 12345,应用把这个非空值变为空码;阈值 3 时,连续三次该输入即可离盘。这说明业务层把“不满足盘码格式”与“确实无盘”合并了。证据:hardware/YoukateHardwareManager.java:316;activity/HomeActivity.java:1032、:1473。

A2:升级安装存在默认 3 的明确路径

安装/配置状态 实际策略
被识别为全新安装,没有保存值 默认 7
升级安装,没有这个新配置项 初始化为 3
安装状态读取失败,没有保存值 按升级处理,初始化为 3
已保存合法值 3 保留 3
已保存合法值 7 或 10 保留保存值
非法配置,例如 11 回落到 7

这是明确的兼容策略,不宜直接判作编码错误;但它与“凡是未设置都默认 7”的会议概括不一致。现场需要查的是最终生效值及其来源,不能只看常量 DEFAULT=7。证据:antiescape/TrayRemovalConfirmationCountConfig.java:18、:41;antiescape/TrayRemovalConfirmationCountPolicy.java:22。

A3:实现的是次数,不是持续时间

实际空码处理方法没有要求两次回调之间间隔 100ms。测试连续立即调用三次即可到达阈值。mFirstEmptyScanElapsedMs 仅用于性能日志,没有参与离盘决策。因此回调聚集、间隔配置变化或缓存延迟都会改变实际等待时间。证据:activity/HomeActivity.java:1473。

在严格 100ms 等间隔的简化模型中,从第一包空到第 N 包空只有 (N−1)×100ms:N=3 为 200ms,N=7 为 600ms,N=10 为 900ms。若从最后一个有效包或物理离盘计时,还要加不同的相位/采样等待;不能直接把 N=7 等同于 900ms,更不能等同于独立扫码 3 次。

A4:特殊码、换盘与通信异常需要单独验收

以上分支是对会议抽象的补充,不等于每条都已被证明触发现场故障。换盘证据:HomeActivity.java:1038、:787、:456,正常旧单收尾在 :611;带盘返回见 :352,迟到人员回包见 :902。上述路径均相对 app/src/main/java/com/cpt/zhct/weighting/。完整调用、行号、已有生命周期处理与测试边界见 应用专项证据。

A5:补充代码定位页已审,A02/A03 不能套用普通离盘结论

用户补充的 离盘确认次数代码定位页 已从源站读取 HTTP 200,且与本地 HTML 字节一致。它的主要代码位置和配置结论准确,是定位入口;它没有证明完整业务链已经正确。

该页补充了重要范围:同一个 N 同时控制普通离盘、A03 原未绑定盘离开、A02 补录盘清空。三者业务含义不同:

路径 源码与补充验证 需要解决的问题
普通离盘 普通有效盘码清零;达到 N 收尾 仍要处理坏码变空、时间、新鲜度、换盘
A02 补录盘 空回调在等待认领时累加;查询进行中,有效码在请求前置条件处被拦下,没进入会清计数的 shouldQuery 空、空、有效、空 在特定查询中状态下仍可达到 N=3 的清盘条件;不能概括为“连续空,中间有码必归零”
A03 原盘移走 等待餐盘认领时,任何非空码都清空原盘移走计数,没有核对是否仍是原盘;等待管理员卡时空码不推进该计数 A换成B但空档不足N,可能不能确认原A已离开;不同报警子状态需分别验收

A02 的清盘动作主要解除同码重复查询限制,不能把它描述为普通取餐结束或已发生重复订单。A03 也不能只凭有一个非空码认定原盘仍在。证据位置:HomeActivity.java:1089、:1242、:1497,antiescape/A02TrayClaimCodePolicy.java:38;完整上下文见专项报告。

该页还需注意两处表达:本地 Markdown 总览“返回首页后清空旧计数”应限定为配置值发生变化时;HTML 中若干代码块省略日志/副作用,应标“节选”,不能当逐字完整方法。没有修改用户给出的定位页,修正意见已汇总在 补充页专项审计。

6. 接下来要完成的任务目标

优先级 / 任务 要解决的问题 明确完成条件
P0 · T1 冻结现场基线 审的是不是故障设备实际在运行的版本,阈值是否真为 7 一台可复现设备的 APK/构建提交/SDK 哈希/固件版本/采样配置/生效阈值对应完整
P0 · T2 取得故障最小时间线 盘没拿走却离盘,究竟从哪一层变成空或丢失 同步记录物理在盘、接收时间与帧摘要、SDK 输出/错误、空计数、会话和收尾事件,至少获得一条真实失败样本
P0 · T3 修正解析与错误分类 SDK 的粘包积压、长度异常转空、帧尾碰撞;应用异常短码转空 当前失败用例先作为回归基线;符合已确认协议的完整帧按序处理一次,异常显式分类;陈旧帧按约定处理
P0 · T4 冻结离盘/续餐规则 到底容忍多久漏扫,何时收尾,同盘恢复和异盘到来如何处理 明确计时起点、窗口、最大离盘延迟及同盘/异盘/失联策略,再决定按时间还是有真实序号的采样计数
P1 · T5 配置与升级可观测 新装默认 7、升级默认 3 与现场认知不一致 设置显示、持久化值、运行值一致;初始化/升级/重启后可回读来源,不默默修改历史值
P1 · T6 回归与真机关闭 不能再以模型通过或调大次数宣称已修复 SDK/普通取餐/A02/A03回归、目标 410 真机、临界时间和生命周期矩阵通过,保留修复版本、视频、日志与结果

建议顺序:T1 与 T2 同时推进;T3 用已复现用例定义修复;T4 的关键规则确认后再进入业务改动;最后完成 T5、T6。责任建议为 Android 开发、SDK/控制板供应商、测试、产品负责人协同,具体人员和日期待安排。

下一阶段详细合同见 AI 可执行 Use Case 与验收矩阵。目前属于审计后的任务草案;关键现场与业务规则未锁定,DoR: BLOCKED,并不影响本次审计完成。

7. 本次验证与尚未证明的部分

验证层次 已执行 证明范围
文件/版本 两份 PDF 全文、关键页渲染;AAR 哈希;本地与远端分支 来源一致性与审计快照
SDK 解析器 11 项实际 AAR Java 字节码构造帧特征验证 解析器在这些输入下的真实行为;未运行串口/JNI/Android 调度
应用逻辑 11 项实际 Config/Policy/Constants 与抽取原始方法特征验证,Android/UI/日志替身 配置选择、清洗和计数分支;不是整 APK 测试
补充 A02/A03 3 项已有 A02 策略测试 + 7 项实际策略/原样路由特征验证 报警子状态、查询进行中和归零路径;不是完整报警订单回归
会议逻辑 确定性 300/100ms 时间线模拟 逻辑假设是否自洽;不是硬件证据或覆盖率
现场闭环 未执行 缺故障设备/APK/原始串口与同步物理证据,不能宣布根因唯一、硬件已排除或故障已修复

合计 32 项 SDK/应用验证的 PASS 表示当前行为与断言一致,其中多项专门用于复现问题;不是产品质量验收通过,也不构成覆盖率。条件模型另行计算,未混入这个数量。

本次资料足以证明:不能把问题简化成“次数太少”。已有解析缺陷、空码语义混合和配置差异需要分别处理;最终是否解决,以餐盘会话连续性和现场回归为准。

来源哈希、范围和计划见 任务契约;会议原文对照与模型详见 会议逻辑复核;验证输出见 验证回执。