11 KiB
设备适配器
入口是 scripts/device_adapter.py,仅依赖 Python 标准库。所有设备操作必须由本次任务提供目标;分发包没有默认 IP、完整 SN、设备型号、密码或 ROM 路径。
先用 capabilities 查看实际支持范围,按 协议范围 确定设备协议。网络子命令接受 --protocol v1/v2,不传仍默认 V2。V1 搜索、信息查询、版本比较和 TCP4941/2170/MD5+ROM 下发见 V1 接口。下文的 894x/EPTP/EPFP 仅属于 V2。
操作边界
| 命令 | 网络及写入范围 | 必要输入 | 完成含义 |
|---|---|---|---|
capabilities |
不联网 | 无 | 列出已实现协议和缺失依赖,不证明设备在线 |
offline |
只读取本地 ROM,不联网 | --rom |
文件有效且已计算摘要;没有验证设备 |
discover |
V1 UDP 4942;V2 UDP 8941,只读广播 | 按需要指定 --interface、身份筛选 |
候选发现,不能代替升级前 TCP 身份校验 |
identify |
当前协议的只读信息查询 | --host --expected-sn |
SN 精确匹配并观察上报型号;model_verified=false,无需预先猜型号 |
info |
V1 TCP 4941/2104;V2 TCP 8942/EPTP/30101,只读 | --host --expected-sn --expected-model |
完整 SN 与至少一个设备型号字段精确匹配;无需 ROM |
upgrade |
默认本地 ROM 校验及当前协议的 TCP 在线预检 | 数字设备 IP、身份参数、--rom --target-version --version-field |
PREFLIGHT_ONLY,未传包;V1还报告所选版本字段是否存在 |
upgrade --execute |
预检后最多一次写入:V1 TCP4941/2170;V2 TCP8943/EPFP/4001 | 以上参数及 --execute --rom-sha256 |
协议步骤结果;始终要求后续版本回读 |
verify-version --response |
只读取已有响应 JSON,不联网 | 响应文件、身份及版本参数 | 验证文件记录中的身份和指定固件字段,不证明当前在线状态 |
verify-version --host |
当前协议,只读回读一次 | 设备地址、身份及版本参数 | 当前协议响应中的身份和指定固件字段匹配 |
upgrade 默认是在线预检。完全不联网时使用 offline 或 verify-version --response。V2 discover 转调固定版本的发现函数,保留本地 8951、设备 8941;V1 discover 使用独立 ff00/ff01 实现,默认本地/设备 4942,参数见 discover --protocol v1 --help。同一完整 SN 对应冲突网络身份时禁止自动选择。
upgrade --host 必须是明确的数字 IPv4 或 IPv6 地址,包括未加 --execute 的在线预检。主机名会在联网前拒绝;适配器把地址规范化后用于当前协议的身份查询及受支持的写入,避免两次 DNS 解析选择不同目标。结果中的 bound_ip 与 upgrade_target_bound 事件保存实际绑定地址;预检后失败时也保留它。程序拒绝可确定的多播地址、未指定地址和 IPv4 有限广播地址 255.255.255.255,IPv4 映射 IPv6 地址执行相同检查。没有网段前缀时不能判断任意子网定向广播地址,应提供已确认的设备单播 IP。info 与只读在线版本回读仍可使用主机名。
--expected-sn 必须是完整 SN,按完整字符串比较,不接受末尾数位或模糊匹配。--expected-model 在 info、upgrade 和 verify-version 中必填:V1 只匹配 dev_type;V2 与 data.inner.type、data.outer.type、data.hardware.type 至少一个值完全相等。型号未知时先用 identify 观察,再带返回型号校验。产品简称不一定是协议型号;出现不匹配应审阅返回的原始身份,不能通过关闭校验继续写入。
常用命令
下面使用占位符。替换成本次任务已确认的目标后执行;不要把现场设备值保存回技能模板。
python scripts/device_adapter.py capabilities
python scripts/device_adapter.py offline --rom "<ROM路径>"
python scripts/device_adapter.py discover --protocol v2 --expected-sn "<完整SN>" --expected-model "<协议完整型号>"
python scripts/device_adapter.py identify --protocol v2 --host "<设备地址>" --expected-sn "<完整SN>"
python scripts/device_adapter.py info --host "<设备地址>" --expected-sn "<完整SN>" --expected-model "<协议完整型号>"
python scripts/device_adapter.py upgrade --host "<数字设备IP>" --expected-sn "<完整SN>" --expected-model "<协议完整型号>" --rom "<ROM路径>" --target-version "<明确目标版本>" --version-field inner.version
写入还必须提供审阅过的 ROM SHA-256,并显式加 --execute:
python scripts/device_adapter.py upgrade --host "<数字设备IP>" --expected-sn "<完整SN>" --expected-model "<协议完整型号>" --rom "<ROM路径>" --target-version "<明确目标版本>" --version-field inner.version --rom-sha256 "<64位SHA256>" --execute
摘要不匹配、缺少 --rom-sha256、身份不匹配都会阻断写入。适配器在在线预检后、调用写入函数前重新校验摘要;写入期间不要替换或修改 ROM。ROM 路径中的版本号仅保留为诊断线索,不能代替明确的 --target-version,也不能证明 ROM 与设备型号兼容。
升级协议报告成功后,等待任务规定的设备恢复条件,再执行独立的只读回读:
python scripts/device_adapter.py verify-version --host "<设备地址>" --expected-sn "<完整SN>" --expected-model "<协议完整型号>" --target-version "<明确目标版本>" --version-field inner.version
python scripts/device_adapter.py verify-version --response "<已有30101响应JSON路径>" --expected-sn "<完整SN>" --expected-model "<协议完整型号>" --target-version "<明确目标版本>" --version-field inner.version
V2 响应文件可以是原始 30101 JSON;V1 必须是带 protocol=v1、cmd=0x5a0e2104、code=0 及原生 data 的适配器封套。两者均可直接使用 info 输出(读取 response 字段),不能跨协议冒充。不从任意日志中猜选“最后一条成功”。只读查询与升级均不自动重试;写入断开、超时或失败后先收集状态,不自动重新烧录。
结果与验收
除 V2 discover 保留原 JSONL 事件输出外,每次调用输出一个 UTF-8 JSON 对象,events 保留协议记录。导入模块不会修改调用方 stdout/stderr;执行 CLI 才统一配置 UTF-8。运行时记录可能包含现场完整身份与响应,应保存在任务证据目录,不纳入可分发技能。
退出码 0 表示本次命令完成,不能统一解释为设备验收通过。错误、BLOCKED、版本不符和数据不足返回 1;参数错误返回 2;V2 发现命令沿用其原退出码约定。
status |
含义 |
|---|---|
CAPABILITIES |
离线列出协议实现范围;不是设备验收结果 |
BLOCKED |
协议不受支持、搜索身份冲突或未唯一找到目标;不能自动更换目标或协议 |
IDENTITY_OBSERVED |
identify 中完整 SN 已核实,型号仅作为实测值返回 |
OFFLINE_READY |
ROM 本地校验完成,无设备证据 |
IDENTITY_CONFIRMED |
所选协议 SN/型号匹配,未做目标固件版本比较 |
PREFLIGHT_ONLY |
在线预检完成,write_executed=false |
PROTOCOL_SUCCESS_POSTCHECK_REQUIRED |
V1收到2170/整数code=0,或V2收到状态2,且客户端发送ROM字节数严格等于文件大小;firmware_verified=false,postcheck_required=true |
PASS |
仅在 verify-version 中:指定身份及固件字段完全匹配 |
FAIL |
指定固件字段存在,但与明确目标版本不符 |
INCOMPLETE |
指定固件字段缺失、为空或类型不正确,不能通过 |
ERROR |
文件、身份、协议或传输错误;firmware_verified=false |
V2 版本字段只允许 inner.version 或 outer.version;V1 原生字段为 firmware、dev_ver,须由任务明确选择。V1响应如果直接上报字面 inner.version/outer.version,保留该值供指定字段比较;没有 V1→V2 字段映射依据,不能静默换名。指定缺失的 outer.version 得到 INCOMPLETE,已经授权的传包仍可执行。hardware/hardware.version 不作为固件验收,也不替补缺失字段。
PASS 的 acceptance_scope 固定为 identity_and_selected_firmware_version_only,不表示全部业务测试已通过。evidence_source=recorded_response、live_readback=false 表示只验证已有文件;evidence_source=live_30101(V2)或 live_v1_2104(V1)、live_readback=true 表示本次只读回读。已有响应的采集时间与来源需由任务证据链说明。
写入调用开始后发生错误,返回 write_may_have_started=true 与 postcheck_required=true,并设置 automatic_retry=false。这代表写入结果未确定,不能因命令失败而自动重试。
如果零字节或部分 ROM 上传就得到成功状态,适配器返回 ERROR。只有 transfer.bytes_sent 是整数且与 ROM 大小完全一致,并满足所选协议的应答条件,才设置 protocol_success=true。V1 的 v1_upgrade_acknowledged、V2 的 device_reported_status_2 只记录设备应答;transfer_result 记录客户端实际计数,不能代替独立版本回读。
来源与维护
scripts/vendor/source-manifest.json 固定上游文件及分发副本的 SHA-256。导入前重新计算分发副本摘要,不匹配就拒绝加载;维护代码时须审阅差异并同步更新清单,不能从临时下载目录动态加载。
| 上游文件 | 上游 SHA-256 |
|---|---|
embedded-app-pc-debug/scripts/device_upgrade.py |
ce72435ff4a3dbe3cc0c28dd81c2a7873b60e55bb64f09055a64900b8f921578 |
embedded-app-pc-debug/scripts/v2_discover.py |
f6634bfc3b86abb6d379d28b679f2e209694f9d856e343157e24b9ff629ea894 |
升级副本只提取协议、身份、ROM 校验与传输函数,去掉原 CLI、日志类与导入时输出重配置。发现副本只把输出重配置移入脚本入口,发现逻辑未改动。没有复制 module_debug.py、串口脚本、原技能说明、历史密钥或历史授权。
实际验证记录
2026-10-19,全部118项离线 unittest 通过,其中设备适配器28项、V1报文/身份/发现24项、V1升级14项。运行实际 V1/V2 编解码与传输函数,仅替换socket/select边界;使用合成身份及保留文档地址,不访问真机。覆盖默认不写入、摘要/身份门槛、实际发送计数、文件变化、错误不重试、时限、版本字段不足及明确协议分流。
同日在用户授权 V1 设备上完成单次19,420,244字节ROM下发,收到2170/code=0/update success。连续两次恢复查询与原SN/型号一致,firmware=2.0.2、dev_ver=2.0.20260908.6;升级前为2.0.1/2.0.20260908.5。任务指定outer.version缺失,验收保留INCOMPLETE,原生版本观测单独通过。现场证据独立保存,不能据此泛化所有V1设备。