OnecardDemo 变更记录
源码仓库:/Users/liang/AndroidStudioProjects/OnecardDemo
提示词仓库:/Users/liang/AndroidStudioProjects/zhct/zhctprompt
2026-08-25 用户全量同步分阶段耗时日志
- “获取全部用户信息(分段)”新增结构化
CUSTOMER_SYNC_*本地日志,覆盖用户总数请求、每批分页请求、XML解析、SQLite保存、单批总耗时和整轮总耗时;计时使用System.nanoTime()单调时钟,单位为毫秒。 - 成功事件包括
CUSTOMER_SYNC_START、COUNT_SUCCESS、BATCH_START、REQUEST_SUCCESS、PARSE_SUCCESS、SAVE_SUCCESS和SUCCESS;失败统一记录CUSTOMER_SYNC_FAILURE,用stage=count/request/parse/save/complete、stageMs和已完成批次/数量定位。 - 新增
SyncLogger和ReaderLogSyncLogger,生产日志复用现有files/reader_logs/、按天文件、单文件最大5MB和保留7天规则;ServerOperationsActivity主动初始化ReaderLog,不依赖用户先进入主页面。 - 不修改
onecard.db数据库版本、表或字段,不增加页面控件。日志只记录同步ID、批次、起始账号、数量、响应字节数和耗时,不记录SOAP原文、姓名、卡号、证件号、PWD、QUERYPWD或其他用户字段值。 - 日志调用通过隔离包装执行;日志器自身抛出运行时异常时,同步结果和原有“每批提交、失败保留前批、成功清理旧用户”语义不变。
- TDD RED:同步单测先因
SyncLogger和注入构造器不存在而编译失败;服务器页面测试确认直接打开页面时ReaderLog.getLogDirectory()为空而失败。 - TDD GREEN:同步测试覆盖成功阶段事件、请求/解析/保存耗时、响应字节数、五类失败阶段、非负失败耗时、敏感内容不进入日志,以及日志器失败不影响同步,定向测试通过。
- 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64个任务全部重跑;67项 JVM 测试通过、0 failure、0 error、0 skipped;Lint 0 error、96 warnings。 - 真机与安装状态:实现后的页面 GREEN 复测和最终 APK 安装暂未执行,原因是 YF_029E 在 RED 测试结束后从 ADB 和 macOS USB 列表中消失;重启 ADB daemon 后仍未枚举到设备。代码编译和 AndroidTest APK 构建已通过,待设备重新连接后补测和安装。
- 文档:新增设计与实施计划 Markdown/HTML;
Change单点功能与字段说明升级至1.10,并同步本条变更记录 Markdown/HTML。 - Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-24 全部用户 SQLite 逐批持久化
- “获取全部用户信息(分段)”从内存聚合全部 SOAP XML 改为逐批持久化:每批最多1000条,解析成功后立即在独立事务中写入应用私有
onecard.db,提交成功后才更新页面进度并请求下一批。 - 新增
CustomerRecord、CustomerBatchParser、CustomerPageSource、CustomerSyncService和CustomerSyncResult;按原始元素名和值解析PROXY_BASE_CUSTOMERS,兼容未知字段,现场观察到的51个字段全部覆盖,包括用户明确要求保存的PWD、QUERYPWD、IDCARDNO。 - 新增
customers、customer_fields、customer_sync_runs三表。CUSTOMERID映射为customers.customer_id主键,重复账号执行更新;原始字段表使用(customer_id, field_name)联合主键并在更新时整体替换,保证字段唯一且不残留旧字段。 - 每批使用 SQLite 事务保证原子性;后续网络、解析或保存失败只把本轮同步标记为
FAILED,此前成功批次保留。只有全部批次完成,才在同一完成事务中清理本轮未出现的旧用户并标记SUCCESS。 - 页面不再展示全量用户 XML;同步中显示本批和累计保存数量,成功后显示同步 ID、服务器总数、本轮保存数、成功批次、数据库用户数和耗时,失败时显示已经成功保存的批次/数量及错误。
- 安全边界:数据库只写应用私有目录;敏感字段值不进入页面、日志或文档;Manifest 已设置
android:allowBackup="false"。当前使用系统明文 SQLite,尚未实现数据库加密、用户列表、搜索、导出、定时同步或自动重试。 - TDD RED/GREEN:解析器、SQLite 仓库和同步编排分别先以缺少目标类型确认失败,再实现转为通过;覆盖51字段与未知字段、敏感字段保留、账号/字段重复拒绝、主键更新、整批回滚、失败保留前批和成功清理旧用户。
- 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64个任务全部重跑;64项 JVM 测试通过、0 failure、0 error、0 skipped;Lint 0 error、96 warnings。 - 真机验证:YF_029E 上
SQLiteCustomerRepositoryTest3项和ServerOperationsUiTest4项,共7项相关测试全部通过。完整设备套件另有2项既有读卡器测试因测试时未识别到 HID 读卡器而失败,与本次服务器/SQLite 功能无关。 - 安装状态:最终 Debug APK 已通过
adb install -r覆盖安装成功。现场约2.6万用户的全量同步未自动触发,需用户连接服务器后点击按钮验证服务器总数、本轮保存数和数据库用户数一致。 - 文档:新增 SQLite 持久化设计与实施计划 Markdown/HTML;
Change单点功能与字段说明升级至1.9,并同步本条变更记录 Markdown/HTML。 - Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-24 全部用户每批 1000 条与常量化
Constants新增ONE_CARD_CUSTOMER_BATCH_SIZE = 1000,作为 Android 端获取全部用户信息的统一分页默认值;当前仍为编译期常量,后续可配置化时只替换取值来源。OnecardWebServiceClient删除内部CUSTOMER_BATCH_SIZE = 200,全量下载通过Constants.ONE_CARD_CUSTOMER_BATCH_SIZE计算Math.min(1000, remaining);最后一批按剩余数量请求,原有最大 CUSTOMERID + 1游标规则不变。- “获取部分用户信息”继续使用页面输入的数量,不强制限制为 1000;1000 是当前 Android 分页策略,不是厂家文档规定的服务端硬上限。
- TDD RED:新增
ConstantsTest.customerBatchSizeDefaultsToOneThousand后,测试因ONE_CARD_CUSTOMER_BATCH_SIZE不存在而按预期编译失败;增加常量后定向测试转为 GREEN。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug lintDebug --rerun-tasks成功,42 个任务全部执行;58 项 JVM 测试通过、0 failure、0 error、0 skipped,Lint 0 error、89 warnings。 - 真机状态:Debug APK 已覆盖安装到 YF_029E;连接现场 WebService 后执行“获取全部用户信息”的 1000 条分页验证待用户操作。
- 文档:新增本次设计和实施计划 Markdown/HTML;
Change单点功能与字段说明Markdown/HTML 升级至 1.8,并明确常量位置、当前分页值和非厂家硬上限边界。 - Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-24 一键服务器稳定性测试
- 服务器单点页面新增“一键服务器稳定性测试”区域,固定执行 100 轮,每轮完成后间隔 1000 毫秒;运行中显示轮次、成功/失败和本轮耗时,按钮可请求停止,其他服务器操作暂时禁用。
- 每轮新建独立
OnecardWebServiceClient调用GetCustomerCount(appId),并在finally清除该轮内存连接状态。单轮 HTTP、超时、XML、SOAP Fault 或返回值异常会记录为失败并继续,不影响后续轮次。 - 新增
ServerStabilityRound、ServerStabilityResult和ServerStabilityRunner,统一计划/实际轮数、成功率、最短/最长/平均/P95 成功耗时、停止状态和失败继续语义;P95 使用 nearest-rank。 - 新增
ServerStabilityReportFormatter和ServerStabilityReportStore。完整逐轮报告使用 UTF-8 保存到应用专属server_stability目录,文件名精确到毫秒;页面展示摘要、错误分组和绝对路径,报告不记录注册码或用户/卡片敏感信息。 - TDD RED:统计测试先因
ServerStabilityRound/Result不存在而编译失败;执行器测试先因ServerStabilityRunner不存在而编译失败;报告测试先因 Formatter/Store 不存在而编译失败;UI 测试先因三个稳定性控件 ID 不存在而编译失败。对应最小实现后四组定向测试全部转为 GREEN。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑;57 项 JVM 测试通过、0 failure、0 error、0 skipped,Lint 为 0 error、89 warnings。 - 真机验证:YF_029E 上
ServerOperationsUiTest3 项通过;最终 Debug APK 覆盖安装成功。启动服务器页面后确认“开始稳定性测试”“等待开始:0/100”“尚未执行稳定性测试”均可见且按钮可用,没有自动发起真实请求。 - 现场验收:用户在 YF_029E 连续执行三轮真实测试,共 300 次全部成功、0 失败,综合成功率 100.00%;综合平均 107.09 ms、P95 129 ms、最大 336 ms,300 次用户总数均为
25965,错误汇总为空。第三轮最大值发生在首请求且成功,去掉三轮首请求后 297 次平均 105.53 ms、P95 127 ms。 - 文档:新增设计、实施计划、三轮独立测试记录和 200/300 次对比报告 Markdown/HTML,归档三份设备原始报告;
Change单点功能与字段说明Markdown/HTML 升级至 1.7,本条变更记录同步生成 Markdown/HTML。 - Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-24 SOAP AppID 大小写与单用户结果解析修复
- Windows Demo 使用同一地址和 AppID 6 获取用户总数成功,真实结果为
25965;对照请求进一步确认<appId>6</appId>返回25965,而 Android 原实现的<appid>6</appid>稳定返回1900009-在加载应用认证信息时,应用配置信息=null!。根因是 SOAP 元素名大小写错误,不是 Android 缺少SysInit.dat。 - 查询请求统一通过
SoapRequestFactory.createWithAppId()发送 WSDL 要求的appId;GetInfoByCustID的账号参数由custid更正为CustomerID。 UploadBusinessRec移除 WSDL 中不存在的顶层appid,改为只发送顶层dto,并在 DTO 内追加AppID。- 单个用户查询不再显示原始 SOAP XML。新增
CustomerInfoFormatter,解析账号、姓名、外部编号、卡号、持卡序号、卡序列号、状态、卡类型、部门、现金/补贴余额、消费计数、开卡时间和有效期;明确不展示PWD、QUERYPWD、IDCARDNO。 - TDD:请求契约测试先因
createWithAppId不存在而编译失败;单用户格式化测试先因CustomerInfoFormatter不存在而编译失败。实现后定向测试及完整testDebugUnitTest均通过。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug lintDebug成功,Lint 0 error;修正后的真实 SOAP 请求返回25965,Debug APK 覆盖安装到 YF_029E 成功。 - 真机验证:YF_029E 自动执行连接服务器和账号
29373的单用户查询,结果区显示结构化中文字段,不包含原始 XML 或认证/证件敏感字段。 - 文档:
Change单点功能与字段说明Markdown/HTML 升级至 1.5;本条变更记录同步生成 Markdown/HTML。 - Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-24 Android SOAP XML 解析兼容与 XXE 防护
- 现场错误确认:YF_029E / Android 11 连接真实
.asmx时,请求已经收到非空 SOAP 响应,但org.apache.harmony.xml.parsers.DocumentBuilderFactoryImpl不支持http://apache.org/xml/features/disallow-doctype-decl,在SoapResponse.parseDocument()初始化阶段抛出ParserConfigurationException,遮蔽了服务器真实 SOAP Fault。 SoapResponse不再把 Apache 专用 feature 作为解析前置条件;改为解析前主动拒绝任何包含DOCTYPE的 SOAP XML,对平台支持的安全 feature 继续关闭外部通用/参数实体,并配置拒绝外部实体的EntityResolver。兼容 Android XML 实现的同时不放宽 XXE 边界。- TDD:JVM 安全回归测试先因旧实现返回平台错误文本而失败;Android 真机兼容测试先精确失败于
SoapResponse.java:99 / ParserConfigurationException。实现后 JVM 正常 SOAP、Fault、分段账号和DOCTYPE拒绝测试通过,Android 正常 SOAP 解析与DOCTYPE拒绝 2 项测试通过、0 失败、0 跳过。 - 真实接口复测:默认地址
http://10.128.1.19/WebService/Newcapec.eCard.ProxyWebService.asmx的 XML feature 错误已消失,页面现在准确显示服务端SOAP Fault: 1900009-在加载应用认证信息时,应用配置信息=null!。本次不修改 AppID;appid=6仍需服务端配置或替换为有效 AppID。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑,46 项单元测试通过、0 failure、0 error、0 skipped,Lint 0 error;最新 Debug APK 已覆盖安装。 - 文档:
Change单点功能与字段说明Markdown/HTML 升级至 1.4,补充 Android XML 兼容策略、XXE 防护与真实服务器验收结果;本条变更记录同步生成 Markdown/HTML。 - Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-24 长时间在线后 USB OUT 卡死自动恢复(协议 1.4)
- 第二次自然故障发生在读卡器长时间在线约 1 小时 38 分后:设备
0610:2010仍可枚举,权限、openDevice、claimInterface均成功,但第一条连接初始化SET_REPORT返回-1;之前增加的 Interrupt OUT0x02备用写路径也返回-1。因此撤销“只有控制写路径失效”的结论,并从生产代码移除 Interrupt OUT 降级。 - 恢复试验依次排除了
setConfiguration、setInterface、USBDEVFS_RESET、sysfsauthorized 0→1和仅重绑 USB 设备节点;这些操作均没有恢复 OUT 通讯。重绑 F700-HW 上的xhci-hcd.1.auto后,读卡器无需物理插拔即重新枚举,连接初始化恢复成功。 - 新增
F700UsbControllerRecovery:只在Build.MODEL=F700-HW、connect()第一次初始化写入为IO_WRITE_FAILED、任何卡片操作尚未开始时执行一次。恢复前固定核验/sys/bus/usb/devices/5-1为0610:2010且控制器节点存在,再通过本机/system/xbin/su重绑控制器,等待设备和 USB 权限恢复后完整重连。 - 权限缺失、open/claim 失败、读超时、协议错误、寻卡、读卡、消费以及第二次写失败都不会触发控制器重绑;消费防重复扣款边界没有变化。恢复命令失败、超时或设备未带权限返回时映射为
DEVICE_RECOVERY_FAILED,提示物理插拔。 - 新增
USB_CONTROLLER_RECOVERY_START/SUCCESS/FAILURE、CONNECT_CONTROLLER_RECOVERY_REQUESTED日志;保留CONNECT_SUCCESS recovered=true/false,记录旧/新设备 ID、控制器和毫秒耗时。 - TDD:恢复策略测试先因
shouldRebindF700Controller尚不存在而编译失败;实现后覆盖型号、错误类型和仅一次恢复边界。真机恢复测试实际执行控制器重绑约 2 秒,设备重新出现且权限保留,随后完成协议初始化连接并正常关闭,1 项通过、0 失败、0 跳过。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑,45 项单元测试通过、0 failure、0 error、0 skipped,Lint 0 error;最终 Debug APK 已覆盖安装。页面实测“连接 → 关闭 → 再连接”均成功,日志确认后一次连接仍固定走CONTROL_SET_REPORT,三次写入均为 64 字节,CONNECT_SUCCESS recovered=false。 - 适配边界:该恢复依赖 F700-HW 当前 USB 拓扑、userdebug/root 能力和 sysfs 节点,不能直接移植到标准 Android 或其他主控。下一次长时间在线自然故障仍需做一次应用端自动触发验收。
- 文档:通讯协议 Markdown/HTML 升级至 1.4,明确被证伪方案、恢复命令边界、新错误码和待验收项;本条变更记录同步生成 Markdown/HTML。
- Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-24 服务器页返回按钮与日志页统一
ServerOperationsActivity删除内容区顶部的普通“返回”按钮,改为与LogViewerActivity一致的 ActionBar 左上角返回箭头。- 页面标题统一使用
action_server_operations;onCreate()启用setDisplayHomeAsUpEnabled(true),onSupportNavigateUp()调用finish()并返回true。 - 不修改服务器连接、断开、用户/黑名单/部门查询、消费记录上传及其按钮启用逻辑。
- TDD:先将页面测试改为要求 ActionBar 存在、开启
DISPLAY_HOME_AS_UP、标题正确以及返回后进入isFinishing();旧实现因没有 Home-As-Up 标志而失败,实现后通过。 - 真机验证:F700_HW 上
ServerOperationsUiTest3 项全部通过;页面控件、21 个输入框“完成”动作和 ActionBar 返回行为均通过。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑,45 项单元测试通过、0 failure、0 skipped,Lint 0 error。 - 文档:
Change单点功能与字段说明Markdown/HTML 升级至 1.3,页面返回方式与实现保持一致。 - Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-24 长时间连接后重新打开恢复(已由协议 1.4 撤销)
本条记录保留当时的试验过程;其“Interrupt OUT 可恢复自然故障”的判断已被同日第二次自然故障证伪,生产实现已移除。当前结论与实现以顶部“USB OUT 卡死自动恢复(协议 1.4)”为准。
现场日志确认:
08:44:09关闭时releaseInterface/close成功;08:44:12和08:44:25两次重新打开均完成设备查找、权限、openDevice和claimInterface,但第一条通道 2 初始化0x6B的 HIDSET_REPORT返回-1。物理拔插后设备 ID 从5005变为5006,相同初始化立即成功。根因边界是该读卡器与 F700_HW 长时间空闲后同一 USB 枚举实例的控制写路径失效,不是按钮状态、权限、USB 口变化、旧
UsbDevice缓存、open 失败或 claim 失败。Android 每次连接本来就会重新枚举并创建新的UsbDeviceConnection,仅重复获取同一个deviceId=5005不能恢复。连接初始化默认继续使用抓包一致的 HID
SET_REPORT;只有第一次初始化写入为IO_WRITE_FAILED时,才切换到读卡器公开的 Interrupt OUT0x02,重新执行一次控制通道初始化,并在该次连接内持续使用备用写路径。恢复严格限制在
connect()、任何寻卡/读卡/消费之前;第二次初始化失败立即停止。寻卡、读卡、消费和扣款结果不确定均不自动换通道或重试,原防重复扣款边界不变。新增
writePath、USB_WRITE_PATH_CHANGED、CONNECT_INTERRUPT_OUT_FALLBACK_START和CONNECT_SUCCESS recovered=true/false日志,便于下一次长时间空闲自然触发时核验恢复链路。TDD:恢复策略测试先因目标类不存在而编译失败;实现后覆盖“仅首次
IO_WRITE_FAILED可恢复”和“权限、协议、超时及第二次写失败不得恢复”,共新增 2 项单元测试。真机验证:F700_HW 强制使用 Interrupt OUT 完成控制通道 ON、OFF 和关闭初始化,设备测试 1 项通过;最终 APK 正常控制写连接成功,关闭后同一
deviceId=5006再次连接成功。长时间空闲后的自动切换仍待下一次自然触发验收。自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑,45 项单元测试通过、0 failure、0 skipped,Lint 0 error。文档:通讯协议 Markdown/HTML 升级至 1.3,补充故障证据、备用写路径、日志和验收条件。
Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 本地消费动态识别 PSAM 槽位
- 第二次现场失败推翻“通道 2 固定为控制、通道 3 固定为 PSAM”的假设:本机当前设备在
0x62 / channel 2返回 20 字节响应,与 Windows 成功样本的 PSAM ATR 长度一致;旧代码却强制要求通道 2 返回0101,因此在槽位选择阶段提前失败。 - Windows 抓包中的通道 2 返回
0101、通道 3 返回 ATR,只能证明该样本的 PSAM 位于槽 3;当前 Android 设备的证据表明 PSAM 位于槽 2。通道 2、3应视为两个候选 PSAM 槽位,不能固定槽号。 - 消费前依次探测通道 2、3;响应长度大于 2 且以
9000结尾时选为本次交易的 PSAM 通道,后续选择文件、读取 PSAM ID、80 70计算和80 72确认统一使用所选通道。 0101表示当前候选槽未发现可用 PSAM,直接检查下一槽;其他异常响应仍只在任何扣款 APDU 前关闭当前槽并重试一次。两槽均失败才返回PSAM_ERROR,并列出各槽原始响应与长度。- 第二次失败仍发生在
80 50、80 70、80 54、80 72之前,没有发送扣款指令;失败前读卡余额为603分。 - TDD:先增加“PSAM 位于通道 2”回归用例,旧实现以
ReaderException失败;动态选择实现后,通道 2、通道 3、通道 3短响应后恢复和两槽失败四条路径均通过。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑,43 项单元测试通过,Lint 0 error;最新 Debug APK 已覆盖安装并启动。真实扣款待用户手动验证。 - 文档:通讯协议与 Change 单点字段说明的 Markdown/HTML 均升级至 1.2,纠正固定 PSAM 槽位的旧结论。
- Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 本地消费 PSAM 激活短响应恢复
- 现场日志确认:账号
29373、主钱包金额2分的消费请求已完成寻卡、读卡、账号/余额校验;控制通道 2 激活成功,PSAM 通道 3 激活只返回 2 字节,随后以PSAM_ERROR停止。 - 失败发生在用户卡
80 50初始化、PSAM80 70计算、用户卡80 54扣款和 PSAM80 72确认之前;该次失败没有发送卡片扣款指令,主钱包仍以读卡结果603分为准。 - 增加写卡前的单次恢复:首次 PSAM 激活响应不以
9000结尾时,记录原始响应,关闭通道 3 并重新激活一次;第二次成功才继续消费,第二次仍失败则立即停止,不无限重试。 - 最终
PSAM_ERROR增加原始响应十六进制和长度;新增PSAM_ACTIVATION_RETRY、PSAM_ACTIVATION_RECOVERED日志,并明确cardDebitSent=false。 - 不修改
80 54发出后的安全边界:扣款结果不确定时仍禁止自动重试,必须先重新读卡核账。 - TDD:恢复用例在原实现中以
PSAM_ERROR失败;实现后覆盖“首次短响应、第二次恢复成功”和“两次均失败、原始响应可见”两种路径。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑,42 项单元测试通过,Lint 0 error;最新 Debug APK 已覆盖安装并启动。真实 PSAM 恢复与扣款结果待用户手动执行小额消费验证。 - 文档:通讯协议与 Change 单点字段说明的 Markdown/HTML 均升级至 1.1,并同步限次重试及禁止重复扣款边界。
- Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 输入框完成动作与本地消费当前时间
- 主页面 3 个本地消费输入框和服务器单点页面 21 个输入框全部改为单行输入,并统一配置软键盘回车动作为“完成”。
- 本地消费时间由页面打开时的 13 位毫秒值改为
yyyy-MM-dd HH:mm:ss;点击“本地消费”时重新获取当下系统时间并更新页面,手工修改值不会参与交易。 - 卡片 APDU 与 Java 交易对象继续使用
long毫秒值,按设备默认时区生成 7 字节 BCD;服务端消费记录上传时间是历史交易字段,仍由用户按yyyy-MM-dd HH:mm:ss填写,上传时不会覆盖为当前时间。 - TDD:实现前,时间格式化单测因目标类不存在而编译失败;F700_HW 上输入框动作测试以实际值
0不等于IME_ACTION_DONE=6失败。实现后固定时区格式化测试和 6 项页面测试全部通过。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑,40 项单元测试通过,Lint 0 error;F700_HW 上主页面和服务器页面共 6 项测试通过,最新 Debug APK 已覆盖安装并启动。 - 文档:同步更新
protocols/Change单点功能与字段说明.md与同名 HTML,页面行为、时间格式和服务器上传边界与实现保持一致。 - Git:源码目录当前不是 Git 工作树;未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 服务器单点页面增加顶部返回按钮
- 在
ServerOperationsActivity页面内容最顶部增加左侧“← 返回”按钮,标题和原连接、查询、上传区域整体顺延。 - 点击按钮直接调用
finish()返回主页面;不发起服务器请求,不修改连接、查询、上传或读卡器逻辑。 - TDD:先新增按钮存在和点击后 Activity 进入
DESTROYED的页面测试,确认资源不存在时编译失败;实现按钮和监听后 Android 测试编译通过。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑,39 项单元测试通过,Lint 0 error;F700_HW 上返回按钮显示与点击关闭页面 2 项测试通过,最新 Debug APK 已覆盖安装。 - Git:未执行暂存、提交、推送、切分支或其他写操作。
2026-08-23 读卡成功后自动填入消费账号
- 分步“读卡”、手动“一键寻卡并读卡”和自动读卡三条成功路径统一把卡内
CustomerID写入本地消费区的账号输入框,减少用户重复输入。 - 明确不使用读卡结果中的
CardNO:当前样本会填入账号29373,不会误填卡号1705827;输入框仍可人工修改,消费执行前的账号一致性校验保持不变。 - 新增纯 Java 映射测试和 Android 页面写入测试;先分别确认目标映射类不存在、页面方法不可访问时测试失败,再做最小实现并恢复通过。
- 未修改寻卡、读卡、消费 APDU、钱包分账、PSAM、USB HID 或服务器接口。
- 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑,39 项单元测试通过,Lint 0 error;F700_HW 上本地消费区域及账号写入 2 项页面测试通过,最新 Debug APK 已覆盖安装。 - Git:未执行暂存、提交、推送、切分支或其他写操作。
2026-08-23 本地消费与服务器单点功能
- 根据 Windows 本地消费 1 分 Bus Hound 样本补齐 HID 长请求分包:单片最多 24 字节,offset 为
0/24,中间片只执行SET_REPORT,最后一片发送后读取响应;传输层新增只写接口,原连接、寻卡、读卡流程保持兼容。 - 新增控制、用户卡和 PSAM 三通道本地消费链路,按
80 50初始化、PSAM80 70计算、80 54扣卡、PSAM80 72确认执行;Windows 成功样本为主钱包余额604→603分、计数43→44、PSAM 序号62、TACA4441B33。 - 新增主钱包
0/EC11、补助钱包1/EC12和“补贴优先”策略;补贴优先最多拆成先补助、后主钱包两笔独立交易,总余额不足时写卡前拒绝,后续交易失败时保留已完成交易且不自动回滚。 - 增加交易安全边界:账号不一致、卡状态异常、金额异常和余额不足均在写卡前拒绝;
80 54已发送但响应不可信时返回TRANSACTION_OUTCOME_UNKNOWN并禁止自动重试;卡片已返回 TAC 而 PSAM 确认失败时返回已扣款结果并标记psamConfirmed=false。 - 主页面新增账号、消费额、钱包策略、消费时间毫秒值、消费按钮和独立结果区;结果展示钱包、金额、扣款前后余额/计数、格式化时间、PSAM 十进制/十六进制卡号、PSAM 序号、TAC 和确认状态。
- 新增服务器单点页面和 SOAP 1.1 客户端:连接/断开、单个/部分/全部用户、用户数量、黑名单数量/全部、部门数量/全部,以及
UploadBusinessRec消费记录上传;全部用户按最多 200 条分段读取。 - 上传页面实现文档中的 16 个必填输入,并 1:1 映射到
TermID、CustomerID、CardNO、Cardsnr、TradeCardType、CardSN、SumFare、MngFare、PurseSector、OpFare、OddFare、Opcount、OPDT、SAMCardNO、SAMTradeNO、CardMac;数值允许为 0 但不得留空,rtn仍作为服务端输出。 - 新增/更新协议与字段文档:
protocols/新开普HID读卡器通讯协议.md升级至 1.0;新增protocols/Change单点功能与字段说明.md,两份均提供同名 HTML,并明确“已实现”和“已现场验收”的差异。 - TDD 覆盖 HID 分包、四条消费 APDU、现金钱包成功、账号不一致、交易结果不确定、PSAM 确认失败、补贴优先计划、SOAP XML/Fault/分段账号解析和 16 字段上传;最终执行
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重跑,38 项单元测试全部通过,Lint 0 error(保留既有兼容性/样式类 warning)。 - F700_HW 安全页面验证只检查本地消费控件、服务器查询和 16 个上传输入是否存在,不连接服务器、不执行消费;Debug APK 已覆盖安装并启动。首次仪器测试因 ScrollView 屏幕外控件使用
isDisplayed()产生假失败,改为验证布局VISIBLE后 2/2 通过,生产页面代码无需为该假失败调整。 - 验收门禁:Android 主钱包 1 分扣款待用户真卡确认;补助钱包和跨钱包分账需有补助余额的 CPU 卡;服务器查询和上传需真实
.asmx地址、AppID 及服务端登记数据。以上未验证项未标记为通过。 - Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 应用启动与返回前台时刷新读卡器状态
MainActivity.onStart()在注册 USB 广播后主动重新枚举VID=0610 / PID=2010读卡器,覆盖设备早于应用启动插入、应用在后台期间拔插以及重新枚举后没有收到动态广播的场景;保持手动点击“连接读卡器”,不自动打开或初始化设备。- 新增
ReaderDeviceState,明确区分未发现设备、检测到但待授权、检测到且可连接、有效连接、旧连接失效五种状态;启动页面按实际设备存在和权限状态显示提示。 - HID 传输层在成功占用接口时保存当前逻辑
deviceId,关闭时清空;返回前台时不仅检查连接对象是否非空,还比对原deviceId是否仍在系统设备列表中,能够识别后台拔插后旧连接对象仍残留的情况。 - 检测到旧连接失效时停止自动读卡、释放旧 USB 连接并清空读卡结果;若设备已经重新枚举则提示重新连接,若设备已拔出则显示未发现读卡器。
- 每次前台刷新写入
USB_STATE_REFRESH日志,包含状态、当前deviceId和权限结果;未修改初始化指令、HID 报文、寻卡、读卡、自动轮询或协议超时。 - TDD:
ReaderDeviceStateTest在实现前因目标状态类不存在而编译失败,实现后新增 5 项状态测试全部通过;MainActivityQuickReadUiTest新增启动设备检测用例。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重新执行,24 项单元测试通过,Lint 0 error;F700_HW 上 3 项页面仪器测试通过。 - 真机启动验证:读卡器已提前插入且权限有效时,页面显示“检测到读卡器,请点击连接”,仅连接按钮启用;日志记录
state=DETECTED_READY deviceId=5003 permission=true,没有自动建立通信连接。 - 待用户验收:应用处于后台时拔插读卡器,返回应用后应释放旧连接并显示当前设备状态;随后手动连接、寻卡、读卡和关闭。
- Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 读卡器全链路本地日志与应用内查看
- 新增统一 Logcat 标签
OnecardReader和应用私有目录files/reader_logs/双写日志;写文件失败只降级到 Logcat,不中断连接、寻卡或读卡业务。 - 每条日志包含
yyyy-MM-dd HH:mm:ss.SSS绝对时间、会话编号、级别、事件和详情;连接、USB传输、HID交换、寻卡、读卡各阶段及关闭同时记录elapsedMs。 - 覆盖连接请求、设备与权限、HID接口/端点、open/claim、初始化、激活、寻卡、基础信息、每条APDU、主钱包、补助钱包、分片、通道关闭、release/close、自动读卡事件和全部
ReaderError异常。 - 正常协议日志只记录 opcode、channel、请求/响应长度、状态和耗时,不保存每个64字节补零报告;短响应等协议异常继续在错误信息中保留必要的原始逻辑字节。
- 日志按天生成,单文件最大5MB并自动按序号滚动,只保留最近7个自然日;动态文本转义为单行,避免并发事件破坏日志结构。
- 主页面新增“查看读卡日志”入口;新增独立日志页面,可选择最近7天的日志文件、刷新并横向/纵向滚动查看,日志文本支持选择复制。
- TDD:
ReaderLogStoreTest实现前因缺少存储类编译失败,实现后覆盖毫秒时间、单行转义、文件滚动和过期清理;MainActivityQuickReadUiTest实现前因页面资源与 Activity 不存在编译失败,实现后2项真机测试通过。 - 真机文件验证:F700_HW 已在
/data/user/0/com.cpt.onecarddemo/files/reader_logs/reader-2026-08-23.log生成日志;实测一次连接失败完整记录到设备发现、权限、接口、端点、open、claim、初始化、USB写失败、错误传播和连接释放,时间均精确到毫秒。 - 通讯协议文档同步升级到0.9;设计和计划归档于
designs/2026-08-23-读卡器全链路本地日志与查看功能设计.md、plans/2026-08-23-读卡器全链路本地日志与查看功能实施计划.md及同名HTML。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64个任务全部重新执行;19项单元测试全部通过,Lint 0 error。F700_HW 上日志入口和查看页2项仪器测试通过。 - 待用户真卡验收:重新插拔读卡器后执行连接、寻卡、读卡和关闭,在应用内确认完整成功链路日志。
- Git:未执行暂存、提交、推送、切分支或其他Git写操作。
2026-08-23 一键与自动读卡重复寻卡去除
- 保留
readCard()原有行为,单独“读卡”按钮仍会在内部先执行完整寻卡,确保调用方无需预先取得 UID。 - 新增
readCard(CardUid expectedUid)接口;已经完成显式寻卡的一键操作和自动读卡流程复用该 UID,直接进入基础信息、主钱包和补助钱包读取。 - 组合流程由“外层
findCard→readCard内层再次findCard”改为“外层findCard→readCard(expectedUid)”,每轮只发送一次完整0x62 → 0x6B → 0x63寻卡序列。 - 三段数据读取前原有的
0x62重新激活和 UID 一致性校验继续保留,因此移卡和换卡保护没有降低。 - TDD:先增加
readsWithPreviouslyFoundUidWithoutRepeatingFindCard,实现前因缺少readCard(CardUid)编译失败;实现后验证请求依次进入激活、APDU 和关闭通道,没有出现0x6B寻卡请求。 - 同步更新通讯协议 Markdown/HTML 至 0.8,明确两个读卡入口及组合流程的去重边界。
- 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重新执行;17 项单元测试全部通过,Lint 0 error。 - 真机验证:待用户安装后验证一次手动一键读卡及自动放卡读卡。
- Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 自动读卡模式 20 轮放卡/移卡正式复测
- 在 F700-HW / Android 11、New-CAP HID RF Picc Reader(VID
0610/ PID2010)上执行单次连接、自动开关持续开启的 20 轮人工放卡/拿卡正式复测。 - 业务日志记录 1 次
SESSION_START、20 次CARD_FOUND、20 次READ_SUCCESS、60 次REMOVAL_CHECK和 20 次REMOVAL_CONFIRMED;没有READ_INTERRUPTED或FATAL_ERROR。 - 每轮均形成“寻卡 → 完整读卡 → 三次无卡确认 → 移卡完成”的闭环,轮次 1–20 连续无缺号;用户确认全过程无异常、无报警。
- 20 轮 UID
92071FC9、卡号1705827、余额604分、消费计数43完全一致。 - 完整读卡耗时 566–667 ms,平均 587.75 ms;首次无卡至第三次确认耗时 1211–1252 ms,平均 1227.85 ms。
- 系统日志未发现应用崩溃、ANR、USB 拔出、
PROTOCOL_ERROR、CRC_ERROR、IO_TIMEOUT或CARD_REMOVED。 - 开始截图确认开关默认关闭且一键区域隐藏;结束截图确认第 20 轮移卡后开关仍开启、状态恢复等待、一键结果已清空。
- 新增 Markdown/HTML 正式测试记录、逐轮 CSV、业务日志、系统日志及两张现场截图;首次缺少逐轮业务证据的 20 轮操作不计入本次正式复测结果。
- 结论限定为同一设备、同一读卡器、同一卡片的 20 轮人工放卡/拿卡;长时间持续放卡、多卡交替、USB 异常和数小时耐久仍待独立测试。
- Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 自动读卡多轮测试逐轮诊断日志
- 为自动读卡模式新增 Logcat 标签
OnecardAutoRead,用于下一次人工放卡/拿卡测试形成可审计的逐轮记录。 - 每次自动会话记录
SESSION_START和SESSION_STOP;每轮记录CARD_FOUND、READ_SUCCESS、三次REMOVAL_CHECK以及REMOVAL_CONFIRMED。 READ_SUCCESS包含轮次、UID、卡号、余额和消费计数,便于校验同一卡片跨轮稳定;读卡中移卡记录READ_INTERRUPTED,真实异常记录FATAL_ERROR及ReaderError。- 等待放卡阶段的正常
NO_CARD不写日志,避免 500ms 轮询产生无意义刷屏;日志仅增加可观测性,不修改状态机、USB I/O、HID、APDU、数据解析、超时或重试策略。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug lintDebug成功。 - 样机验证:用户完成 20 轮放卡、读卡、拿卡和结果清空测试;日志完整记录全部 20 轮事件链,无中断或致命错误。正式结果归档于
test_records/2026-08-23-自动读卡模式20轮复测.md及同名 HTML。 - Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 自动读卡模式开关与移卡清空
- 在普通结果区下方新增“开启自动寻卡并读卡”开关,默认关闭且不持久化;关闭时
layout_find_and_read使用GONE隐藏一键按钮和一键结果区。 - 默认手动模式下,连接成功后不启动自动轮询,并启用原有“寻卡”“读卡”分步按钮;状态显示“读卡器已连接,请手动寻卡或读卡”。
- 打开开关后显示一键区域;若读卡器已连接则立即启动自动监听,否则连接成功后启动。自动监听期间禁用手动寻卡、读卡,避免混用流程。
- 关闭开关会立即取消自动监听会话、隐藏并清空一键结果;已经发出的迟到 USB 回调继续通过会话编号丢弃。
- 等待移卡阶段连续第 3 次收到
NO_CARD后清空一键结果,并恢复“读卡器已连接,等待放卡…”;读卡过程中被拿走也清空结果并恢复等待。 - 自动监听因真实异常停止时保留错误信息,一键按钮恢复为可用的单次诊断入口;切换到关闭状态后手动寻卡、读卡恢复可用。
- TDD:先更新
MainActivityQuickReadUiTest,实现前因switch_auto_read、layout_find_and_read不存在而编译失败;实现后 F700_HW 真机仪器测试 1/1 通过。 - 测试中曾因错误断言
Switch.performClick()返回值导致一次假失败;AndroidCompoundButton即使完成切换也可能返回false,改为验证isChecked()和区域可见性后通过,生产代码无需为该假失败调整。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks成功,64 个任务全部重新执行;Debug APK 已覆盖安装并启动到 F700_HW。 - 未修改
AutoReadStateMachine、CardReader、NewcapecUsbCardReader、NewcapecProtocol、HID、APDU、数据解析、超时或重试策略,通讯协议文档无需变更。 - 设计与计划归档于
designs/2026-08-23-自动读卡开关与移卡清空功能设计.md和plans/2026-08-23-自动读卡开关与移卡清空功能实施计划.md。 - 待用户真卡验收:关闭模式手动寻卡/读卡;打开模式自动读卡;移卡后结果清空并重新等待;再次放卡再次读出。
- Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 连接成功后自动等待寻卡与读卡
- 读卡器连接成功后自动进入等待放卡状态,每 500ms 执行一次离散寻卡探测;等待阶段的
NO_CARD作为正常状态处理,不再要求用户点击“一键寻卡并读卡”。 - 寻卡成功后自动完整读卡一次并在既有一键操作结果区显示 UID 和卡片信息;读卡成功后进入等待移卡状态,同一卡片持续放置时不会重复执行完整读卡。
- 连续 3 次
NO_CARD才确认卡片已经拿走,以降低设备偶发无卡响应造成同一卡片重复读取的风险;确认移卡后自动等待下一张卡。 - 新增纯 Java
AutoReadStateMachine,覆盖未连接、等待放卡、读卡、等待移卡和异常停止状态;主线程Handler只调度离散任务,所有 USB I/O 继续串行运行在现有ioExecutor。 - 主动关闭改为排入同一 I/O 队列,自动探测会话先取消,迟到回调通过会话编号丢弃;USB 拔出和 Activity 销毁同样停止自动监听。
- 读卡过程中主动移卡的
CARD_REMOVED/NO_CARD会显示本次错误并恢复等待;CRC、协议、I/O、设备等其他异常会停止自动监听并保留原始错误,现有手动按钮随即恢复用于诊断。 - TDD:新增
AutoReadStateMachineTest;实现前目标测试因AutoReadStateMachine不存在而编译失败,实现后 6 项状态机测试通过。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug成功;Debug APK 已覆盖安装并启动到 F700_HW。 - 未修改
CardReader、NewcapecUsbCardReader、NewcapecProtocol、HID、APDU、数据解析、超时或重试策略,现有通讯协议文档无需变更。 - 设计与计划归档于
designs/2026-08-23-连接后自动寻卡读卡功能设计.md和plans/2026-08-23-连接后自动寻卡读卡功能实施计划.md。 - 待用户真机验收:连接后等待、首次放卡自动读出、卡片未拿走时不重复读、拿走后恢复等待、再次放卡再次读出。
- Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 一键寻卡并读卡按钮与独立结果区
- 在现有
text_result下方新增全宽button_find_and_read和同风格text_find_and_read_result,保留原连接、寻卡、读卡、关闭按钮及现有结果区。 - 新按钮仅在读卡器已连接且页面空闲时启用;不自动连接、不自动关闭,通过现有单线程执行器顺序调用
findCard()和readCard()。 - 组合操作开始时清除旧状态并显示执行中;成功结果区同时展示显式寻卡 UID 和完整卡信息;失败时状态区与独立结果区都展示
ReaderError和原始错误信息。 - 关闭读卡器时同时清空原结果区和一键操作结果区;未修改
CardReader、NewcapecProtocol、HID、APDU、超时或重试策略。 - TDD:新增
MainActivityQuickReadUiTest;实现前compileDebugAndroidTestJavaWithJavac因三个新资源不存在而失败,实现后编译通过,真机 UI 仪器测试 1 项通过。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug成功,Lint 0 error;Debug APK 和 AndroidTest APK 已安装到 F700_HW。 - 真机页面验证:未连接时新按钮禁用,连接成功后启用;成功点击时状态显示“寻卡并读卡成功”,独立结果区同时展示寻卡 UID
92071FC9和完整卡信息;后续再次返回NO_CARD时,状态区和独立结果区均准确显示错误,按钮恢复可用且应用无崩溃。 - 新增成功结果与
NO_CARD错误结果两张 F700_HW 现场截图,归档于test_records/evidence/2026-08-23-一键寻卡并读卡-成功结果.png和test_records/evidence/2026-08-23-一键寻卡并读卡-NO_CARD错误.png。 - 设计与计划归档于
designs/2026-08-23-一键寻卡并读卡功能设计.md和plans/2026-08-23-一键寻卡并读卡功能实施计划.md。 - Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 单次连接 100 轮寻卡读卡测试
- 在
CardReaderStabilityInstrumentedTest中新增独立入口connectOnceThenFindAndReadForUpTo100Cycles:开始时连接一次,循环执行寻卡和完整读卡,每轮使用参数化间隔,完成或首次失败后在 finally 中只关闭一次。 - 以
mode=singleConnection、interCycleDelayMs=200在 F700_HW 上正式执行;第 1–36 轮通过,第 37 轮失败,第 38–100 轮未执行。 - 第 37 轮外层寻卡成功,随后在
readCard → readBaseData → activateExpected重新激活卡片时返回无卡,上层错误为CARD_REMOVED / The card was removed before the read completed。 - 全场仅一次
CONNECTED、一次UsbDeviceConnectionJNI: close和一次DISCONNECTED;测试后 Android 设备在线且 USB 读卡器0610:2010仍可枚举。 - 结论:取消逐轮 USB 打开/关闭后仍出现射频失联,频繁打开/关闭不是故障必要条件或完整根因;是否提高故障概率仍待多组对照。
- 新增独立 Markdown/HTML 测试记录、CSV 和原始日志,通讯协议升级到 0.7;生产读卡协议未修改。
- 构建验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest成功。 - Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 读卡器 500ms 间隔第二次正式复测
- 以相同
interCycleDelayMs=500、首次失败立即停止且不重试的口径再次独立执行最多 100 轮,不覆盖上一次记录。 - 第 1–16 轮连接、寻卡、完整读卡和关闭全部通过;UID
92071FC9、卡号1705827、余额604分及消费计数保持一致。 - 第 17 轮连接成功后在
findCard阶段返回NO_CARD,测试停止,第 18–100 轮未执行;该失败不同于前次第 30 轮的0301APDU 短响应。 - 用户确认失败前卡片一直放置,读卡器随后持续报警,用户报警后才拿走卡片;现场检查 F700-HW 在线且 USB 读卡器
0610:2010仍可枚举,排除用户提前移卡和 USB 物理断开。 - 诊断结论:本次更支持持续放卡/报警与射频失联存在时间关联,但因相同条件曾 100/100 通过且报警后即时读卡曾成功,仍不能判定持续放卡为根因。
- 新增独立 Markdown/HTML 测试记录、CSV 和原始日志,通讯协议升级到 0.6;未修改生产代码。
- Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 读卡器 100 轮 500ms 间隔正式测试
- 为
CardReaderStabilityInstrumentedTest增加可审计的interCycleDelayMs参数,限制为 0–60000ms;每轮关闭成功并确认连接释放后才等待,最后一轮和失败轮不等待。 - 在 F700_HW 上以
interCycleDelayMs=500正式执行最多 100 轮,遵循首次失败立即停止且不重试:第 1–29 轮通过,第 30 轮失败,第 31–100 轮未执行。 - 成功轮次 UID
92071FC9、卡号1705827、余额604分、主钱包消费计数43、补助余额/计数0/0始终一致;成功轮次平均耗时 738.55ms,不含轮间等待。 - 第 30 轮在
readCard → readBaseData执行00 B0 96 80 35时收到原始逻辑响应03 01(2 字节),以PROTOCOL_ERROR停止;该响应短于正常 APDU 响应所需的最小 4 字节,厂商语义未知。 - 结论:500ms 轮间等待不能保证 100 轮稳定;异常不固定在原第 33 轮,也不固定在补助钱包阶段。未加入重试或吞错,没有改变生产读卡协议行为。
- 新增独立 Markdown/HTML 测试记录、CSV 逐轮数据和 Logcat 原始日志;通讯协议升级到 0.5。
- 构建验证:
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest成功。 - Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 第 33 轮短响应与读卡器报警关联诊断
- 在
NewcapecProtocol.transmitApdu的短响应错误中增加 APDU 命令、原始响应十六进制和长度,便于下次偶发问题直接取证;未改变协议时序、超时、重试或业务逻辑。 - 使用同一 F700_HW、同一卡片、零轮间延迟重新执行最多 100 轮诊断循环,100 轮全部通过,仪器测试耗时 71.702 秒,原
APDU response is too short未复现。 - 用户观察到卡片长时间放置会触发读卡器报警;报警后保持卡片不动立即执行连接、寻卡、完整读卡和关闭,结果成功,数据仍为 UID
92071FC9、余额604分、消费计数43。 - 诊断结论:报警不是失败的充分条件,第 33 轮也不是固定计数溢出;报警触发瞬间是否可能产生偶发时序碰撞仍待下一次带原始响应字节的失败样本确认。
- 同步更新循环测试记录和通讯协议 0.4 的 Markdown/HTML 版本。
- Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 读卡器最多 100 轮循环稳定性测试
- 新增 Android 仪器测试
CardReaderStabilityInstrumentedTest,逐轮执行连接、寻卡、完整读卡和关闭;任一阶段失败立即停止,不做自动重试。 - 每轮记录 UID、卡号、余额、主/补助钱包消费计数和耗时,并校验寻卡/读卡 UID 及业务字段跨轮次稳定。
- F700_HW 实测第 1–32 轮全部通过;UID
92071FC9、卡号1705827、余额604分、主钱包消费计数43、补助余额/计数0/0始终一致。 - 第 33 轮在补助钱包阶段重新激活后选择根应用
EC01时失败:PROTOCOL_ERROR / APDU response is too short;该轮耗时 832 ms,随后关闭读卡器并停止,未执行第 34–100 轮。 - 成功轮次耗时范围 692–801 ms,平均 732.81 ms;前 10 轮平均 698.5 ms,后 10 轮平均 780.0 ms。耗时上升为观察结果,具体原因待原始短响应日志确认。
- 新增同名 Markdown/HTML 测试记录、CSV 逐轮数据和 Logcat 原始日志;通讯协议升级到 0.3 并同步标记该稳定性结果。
- 本次只新增测试代码和记录,没有修改生产读卡协议实现;未执行任何 Git 写操作。
2026-08-23 Android 64 字节 HID 长响应解析修复
- 真机现象:连接读卡器和寻卡均成功,完整读卡报
PROTOCOL_ERROR:Invalid HID response chunk length: 57。 - 根因:64 字节 Input Report 的私有响应头占 7 字节,合法单片数据容量为 57;代码虽然已把 Report 长度从 32 修复为 64,但仍沿用 Windows 32 字节视图下的 25 字节响应上限,因此只在读卡遇到首个长响应时失败。
- TDD:新增
acceptsFull57BytePayloadFrom64ByteInputReport;修复前以ReaderException失败,修复后通过。 - 最小修复:
HidFrameCodec.MAX_RESPONSE_PAYLOAD改为REPORT_SIZE - 7,不改操作码、APDU、CRC-A、请求载荷或业务字段解析。 - 自动验证:
./gradlew testDebugUnitTest assembleDebug lintDebug执行成功。 - 样机验证:Debug APK 已安装到 F700_HW;连接成功,完整读卡成功,读取 UID
92071FC9、CPU 卡类型 8、卡号1705827、状态0xF1、余额604分、消费计数43等信息。 - 同步文档:通讯协议升级到 0.2,区分 Windows 25 字节分片和 Android 57 字节分片,并将连接、寻卡、单卡完整读取标记为 F700_HW 已验证。
- Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-23 新开普 HID 读卡器通讯协议独立归档
- 新增
protocols/新开普HID读卡器通讯协议.md及同名 HTML 浏览版,独立汇总当前已解析的 USB HID、私有帧、续包、初始化、寻卡、APDU、CRC-A、读卡指令、字段偏移和异常信息。 - 文档将结论区分为抓包确认、真机确认、代码实现、语义推定和待确认,避免把单卡抓包样本或当前 Java 常量误写成厂商正式协议。
- 范围限定为连接、寻卡和读卡;未纳入消费、写卡、WebService 地址、注册码等不属于当前交付范围或涉及现场配置的内容。
- 明确记录 Windows 33 字节视图(含 Report ID 占位)与 Android 64 字节原始 HID Report 的差异,以及 64 字节修复后的真机连接仍待验证。
- 本次仅新增/更新协议文档与变更记录,未修改 Android 源码,也未执行任何 Git 写操作。
2026-08-21 Android 真机 HID 报告长度修复
- 真机现象:F700_HW 已识别
VID=0x0610 / PID=0x2010并授予应用 USB 权限,但连接初始化在等待首个响应时显示IO_TIMEOUT。 - 根因证据:真机
/sys/bus/hid/devices/0003:0610:2010.000A/report_descriptor中 Input Report 与 Output Report 的Report Count均为0x40,即 64 字节;原实现按 Windows Bus Hound 应用层视图固定使用 32 字节,Android 原始 USB 端点收发长度与设备描述符不一致。 - 修复范围:仅将
HidFrameCodec.REPORT_SIZE从 32 调整为 64。原协议头、有效载荷长度、CRC-A、分片和命令内容不变,抓包中的 32 字节内容保持为报告前缀,后 32 字节补零。 - TDD:先新增
usesDeviceDeclared64ByteHidReports,修复前目标测试以expected 64 but was 32失败;修改后目标测试通过。 - 待样机验证:安装新 Debug APK 后先验证“连接读卡器”;连接通过后再验证寻卡与读卡。若仍无响应,下一步单独验证设备暴露的 Interrupt OUT 端点,不与本次长度修复混改。
- Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。
2026-08-21 新开普一卡通 USB HID 连接、寻卡与读卡封装
- 需求来源:将 Windows Change 测试程序已验证的“连接读卡器 → 寻卡 → 读卡”流程封装为 Java Android Demo,暂不实现消费。
- 适配对象:
HID RF Picc Reader,抓包和本机识别值为VID=0x0610、PID=0x2010;目标项目minSdk=29。Android 真机端点枚举与实际读卡结果待样机验证。 - 协议证据:依据 Bus Hound 的成功读取、重复读取、无卡读取和移卡样本,实现 32 字节 HID 报告、控制通道初始化、卡片通道激活、寻卡、
0x50续包、APDU、CRC-A 与9000状态校验。 - 代码改动:
- 新增
com.cpt.onecarddemo.readerJava 封装,公开connect()、findCard()、readCard()、disconnect()。 - 写报告使用抓包确认的 HID
SET_REPORT (0x21/0x09/0x0200)控制传输;读报告使用 32 字节 Interrupt IN。 - 解析 UID、CPU 卡类型、应用序列号、账号、卡号、持卡序号、状态、卡类别、总额、余额、消费计数、补助余额及补助消费计数。
- 增加 USB 权限申请、设备过滤、插拔广播处理和连接/寻卡/读卡演示页面。
- 新增
- 异常范围:已编码未连接、设备不存在、权限缺失、接口/端点/占用失败、写失败、超时、短包、分片异常、CRC 错误、无卡、移卡、换卡及不支持卡片状态。设备拔出、Android 权限拒绝等为 Android 侧防御;其真机表现待验证。
- 自动验证:
./gradlew testDebugUnitTest assembleDebug lintDebug执行成功;本地单元测试 8 项、0 失败,Debug APK 生成;Lint 为 0 error、7 个项目模板/依赖版本 warning。 - 待样机验证:当前没有 ADB 在线设备。需在目标 Android 设备上验证 USB 授权、连接、无卡寻卡、同卡连续读取、读卡中移卡、读卡中拔读卡器及重新插入恢复。
- Git:未执行暂存、提交、推送、切分支或其他 Git 写操作。