network: 台架 IP 迁到 192.168.31.100,并把重复写死的值收进单一真源
CI / 静态门禁 (pre-commit) (push) Failing after 25s
CI / 主机侧单测 (MSVC) (push) Successful in 48s

分两段(用户裁定 2026-11-02):先做地址迁移,再把同类字面量收敛成
单一真源,以后再改地址只动一处。

一、IP 迁移 192.168.1.100 → 192.168.31.100(gw 同步 → .31.1)
- 工厂默认:Drivers/BSP/NETCFG/net_cfg.c 的 NET_CFG_DEF_IP/_GW
- 注释/文档示例、主机与板级测试、台架 py 脚本、两份 sim_config.ini
- 字节序教学点同步换算 hex:0x6401A8C0 → 0x641FA8C0(内存
  C0 A8 1F 64),旧版示例引文与历史 Trap 记录按冻结纪律不动
- UI 黄金基准:网口设置 4 态 + shot_info 共 5 张 .bin 由
  check_content_pages.py --update 重刷(渲染值 IP 变了,属预期变更)
- 保留判定:客户点表引用(IEC104/继电保护规划)、芯片手册摘录、
  日期冻结的变更记录行、已退役台架档案 MDK-ARM/retracted_bench_*

二、单一真源收口(A+B+C+D,用户批准全做)
- 新建 test/host/bench_defaults.py:台架 py 默认值唯一定义点
  (DEVICE_IP/DEVICE_GW/PC_IP/NET_BCAST),22 个脚本改为引用;
  MDK-ARM 两探针与 scripts/board_ui_nav.py 经 sys.path shim 引入
- test/board/net_test_task.h 新增 TEST_PC_PEER_IP,板测三处
  PHASE*_REMOTE_IP 改引该宏(保留 -D 覆盖);Phase1 断言改
  memcmp 数组比较,数组各只出现一次
- simulator/tests/check_content_pages.py 从 net_cfg.c 正则解析
  NET_CFG_DEF_* 推导期望值(解析失败即报错、不静默兜底),
  脚本内台架 IP 字面量清零
- 装饰性注释去具体值(net_cfg.h/net_socket.h/net_socket.c/
  net_socket_sim.c);字节序教学 hex 对刻意保留为唯一写值点
- 刻意不动:主机单测夹具 IP(逐文件钉住的任意值是正确测试
  实践)、文档正文 IP(那是文档内容本身)

顺带:scripts/gen_task_table.py 规范串清掉「,B5」——编号清退提交
72b4b094 改了受管区没重跑生成器,task-table 门禁因此一直红

验证:产品 @build 0/0;CH395F 套件开关构建 0/0 后还原产品态;
6 套主机单测 rc=0;content_pages(含宏推导路径)/菜单 8 态/
推流位比对全 PASS;bom-drift 99 无漂移;doc-links/coding-standard/
cppcheck 绿;py_compile 23 文件 + --help 冒烟(含 shim 绝对路径)
This commit is contained in:
2026-11-02 14:36:36 +08:00
parent 9a71265f41
commit 7efa58d9a1
62 changed files with 338 additions and 224 deletions
+1 -1
View File
@@ -147,7 +147,7 @@
- 所有 `net_*` socket 操作经 `netMsgQueue` 在 netTask 串行执行;非 netTask 任务直接调 `net_*` API,勿直接碰 CH395F 驱动
- **`net_*` 公开 API 的错误码经"任务通知值"回传**:`net_msg_t` 是**按值拷入队列**的,netTask 改的是自己那份副本,调用方永远看不到 → 错误码搭 `xTaskNotify()` 通知值回来。**勿给 `net_msg_t` 加"回传用"字段**(调用方只会读到栈垃圾)
- **单连接 socket 的断开语义**:EOF 时 net 层已 `auto_relisten`,应用层**不得**在 EOF 时 `net_close` 该 fd(会连监听一起毁掉 → 端口"连接被拒");仅当数据 fd ≠ 监听 fd(模拟器多连接后端)时才关
- 网口配置:`/config.ini` [network] 段(工厂默认 192.168.1.100/24、gw .1;MAC 全零 = 芯片内置;UI 保存立即生效;**旧档 `/cfg/netcfg.bin` 迁移已于 2026-10-10 删除**,旧档存在则被忽略);同文件 **[ftp] 段 = FTP 鉴权**(`user` / `pwd_hash` = SHA-256(user‖password) / `write`,出厂默认 admin/admin,默认口令在用有启动告警;连续 5 次失败锁 60 s)→ `docs/子系统设计/网络配置说明.md`
- 网口配置:`/config.ini` [network] 段(工厂默认 192.168.31.100/24、gw .1;MAC 全零 = 芯片内置;UI 保存立即生效;**旧档 `/cfg/netcfg.bin` 迁移已于 2026-10-10 删除**,旧档存在则被忽略);同文件 **[ftp] 段 = FTP 鉴权**(`user` / `pwd_hash` = SHA-256(user‖password) / `write`,出厂默认 admin/admin,默认口令在用有启动告警;连续 5 次失败锁 60 s)→ `docs/子系统设计/网络配置说明.md`
- **新 .c 文件必须手动注册到两处**才能双端编译:`MDK-ARM/STM32F407-Demo.uvprojx` + `simulator/CMakeLists.txt`(用 `new-source-file` skill);⚠ **新文件落在还没有的新目录,还要把该目录加进 `uvprojx` 的 `<IncludePath>`**——否则报错落在**消费者 TU** 上(`cannot open source input file`),极易误判
- 测试构建:`test/board/test_config.h` 的 `TEST_SUITE_CH395F/STORAGE/GD5F/RTC` **互斥**(一次只能一个,`#error`);全部注释 = 产品构建,**受 `test-suite-switch` 钩子门禁**;`ENABLE_DESTRUCTIVE_TESTS` **默认关闭**(关闭时套件只读模式,可随时在在用设备上跑)——⚠ 跑一轮破坏性 GD5F 即让 dhara 多记一个坏块且该计数持久化,普通恢复清不掉 → `docs/测试说明/专项验证/存储维护与容量恢复.md`;板级套件运行用 **`board-test-suite` skill**
- **改驱动前先读对应 `docs/问题记录/*_Trap_Records.md`**(CH395F / GD5F2GQ5UE / SD2506);各模块测试指南见 `docs/测试说明/`
+11 -11
View File
@@ -268,9 +268,9 @@ static bool s_mac_is_zero(const uint8_t *p_mac) {
/*
* 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式)
* 入口参数:ip - IP 地址字符串 "192.168.1.100",NULL 使用 DHCP 自动获取 const char *
* mask - 子网掩码字符串 "255.255.255.0",NULL 使用默认值 const char *
* gateway - 网关地址字符串 "192.168.1.1",NULL 使用默认值 const char *
* 入口参数:ip - IP 地址字符串(点分十进制),NULL 使用 DHCP 自动获取 const char *
* mask - 子网掩码字符串,NULL 使用默认值 const char *
* gateway - 网关地址字符串,NULL 使用默认值 const char *
* mac - MAC 地址(6 字节,低字节在前),NULL/全零使用芯片内置 const uint8_t *
* 返回值:0 - 初始化成功 int
* -1 - 初始化失败(芯片检测/复位/协议栈初始化失败) int
@@ -2930,7 +2930,7 @@ int net_sock_state(int sockfd) {
/*
* 函数功能:将点分十进制 IP 地址字符串转换为网络字节序的 32 位地址
* 入口参数:cp - 点分十进制 IP 字符串(如 "192.168.1.100") const char *
* 入口参数:cp - 点分十进制 IP 字符串 const char *
* 出口参数:无
* 返回值:网络字节序的 32 位 IP 地址(0 表示参数无效) uint32_t
* 限定条件:无
@@ -2939,9 +2939,9 @@ int net_sock_state(int sockfd) {
* 本文件内 s_addr 只有这一族(2026-10-24:两套 IP 字节序约定统一为 POSIX 网络序):net_inet_ntoa 的
* 「先 NTOHL、再高段在前」正是本函数的解析逆运算;net_getsockname、
* net_getpeername、net_recvfrom 填的 s_addr 也同族,故三者可直接相等比较。
* 2. 小端主机(STM32F407 与 PC 模拟器均是)上 "192.168.1.100" 的数值为
* 0x6401A8C0(内存字节 C0 A8 01 64)。旧实现在小端累加后又多做一次
* HTONL,得到 0xC0A80164,与 ntoa 不互逆——那是同一文件里的第二套约定,
* 2. 小端主机(STM32F407 与 PC 模拟器均是)上 "192.168.31.100" 的数值为
* 0x641FA8C0(内存字节 C0 A8 1F 64)。旧实现在小端累加后又多做一次
* HTONL,得到 0xC0A81F64,与 ntoa 不互逆——那是同一文件里的第二套约定,
* 已连同其消费者(connect/sendto 的 des_ip 拆装)一起改为本族。
* 3. 段值超过 255 时按低 8 位截断(旧实现会把溢出位溅进相邻段,更不可预期);
* 非法字符仍按 0 处理,失败返回值维持 0,不改 NET_INADDR_NONE 的语义。
@@ -2988,9 +2988,9 @@ uint32_t net_inet_addr(const char *cp) {
* 函数说明:1. 先经 NET_NTOHL 转为主机字节序再格式化输出
* 2. ⚠ 段序(E13 定案 修复):NTOHL 后主机序数值的高字节才是
* 点分第一段。**入参必须是网络序整数**(&addr 的第 0 字节 = 第一段,
* 小端上 192.168.1.100 = 0x6401A8C0,见 net_inet_addr 的约定说明),
* 于是 0x6401A8C0 → NTOHL → 0xC0A80164 → 高字节 0xC0 = 192 首段 ✓。
* 原实现低段在前,同一入参会显示成字节反转的 "100.1.168.192"。
* 小端上 192.168.31.100 = 0x641FA8C0,见 net_inet_addr 的约定说明),
* 于是 0x641FA8C0 → NTOHL → 0xC0A81F64 → 高字节 0xC0 = 192 首段 ✓。
* 原实现低段在前,同一入参会显示成字节反转的 "100.31.168.192"。
* 3. ⚠ 本注释旧版举的例子写成「192.168.1.100 → 0xC0A80164 → 同串」,
* 那个 0xC0A80164 取的是**已废弃的另一族**(当时 net_inet_addr 的返回
* 值),按本函数的真实算法它打印出来恰是 "100.1.168.192"。两套 s_addr
@@ -3100,7 +3100,7 @@ static int alloc_socket(void) {
/*
* 函数功能:将点分十进制 IP 地址字符串解析为 4 字节数组
* 入口参数:str - 点分十进制 IP 字符串(如 "192.168.1.100") const char *
* 入口参数:str - 点分十进制 IP 字符串 const char *
* ip_arr - 输出:4 字节 IP 数组(网络序,低字节在前) uint8_t *
* 出口参数:ip_arr - 填充解析后的 IP 地址
* 返回值:无
+5 -5
View File
@@ -85,9 +85,9 @@ int net_msg_queue_adopt(osMessageQueueId_t queue_id);
/*
* 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式)
* 入口参数:ip - 本地 IP 地址字符串 "192.168.1.100",NULL 使用 DHCP
* mask - 子网掩码字符串 "255.255.255.0",NULL 使用默认
* gateway - 网关地址字符串 "192.168.1.1",NULL 使用默认
* 入口参数:ip - 本地 IP 地址字符串(点分十进制),NULL 使用 DHCP
* mask - 子网掩码字符串,NULL 使用默认
* gateway - 网关地址字符串,NULL 使用默认
* mac - MAC 地址(6 字节,低字节在前),NULL 或全零使用芯片内置
* 返回值:0 成功,-1 失败
* 限定条件:SPI2 已正确初始化
@@ -465,14 +465,14 @@ int net_sock_state(int sockfd);
/*
* 函数功能:将 IP 地址字符串转为网络字节序
* 入口参数:cp - 点分十进制字符串 "192.168.1.100"
* 入口参数:cp - 点分十进制字符串
* 返回值:网络字节序 IP,失败返回 0
* 限定条件:cp 指针非空
* 函数说明:不支持域名解析
*
* s_addr 的**唯一约定**(2026-10-24 定案:两套 IP 字节序约定统一为 POSIX 网络序;全仓只此一族,勿再引入第二套):
* 网络字节序整数 —— &s_addr 的第 0 个内存字节 == 点分第一段。
* 小端主机(STM32F407 与 PC 模拟器都是)上 "192.168.1.100" 的数值 = 0x6401A8C0。
* 小端主机(STM32F407 与 PC 模拟器都是)上 "192.168.31.100" 的数值 = 0x641FA8C0。
* 与 net_inet_ntoa 互为逆运算;net_getsockname / net_getpeername / net_recvfrom
* 填出来的 s_addr、以及 net_bind / net_connect / net_sendto 收进去的 s_addr
* 全在同一族,故可直接做相等比较(旧实现里 inet_addr 与其余四个不互逆,
+2 -2
View File
@@ -41,9 +41,9 @@
/* 私有宏定义区 */
/* 工厂默认值(单一来源:s_cfg 初值与 s_defaults 共用,避免两处漂移) */
#define NET_CFG_DEF_IP { 192u, 168u, 1u, 100u }
#define NET_CFG_DEF_IP { 192u, 168u, 31u, 100u }
#define NET_CFG_DEF_MASK { 255u, 255u, 255u, 0u }
#define NET_CFG_DEF_GW { 192u, 168u, 1u, 1u }
#define NET_CFG_DEF_GW { 192u, 168u, 31u, 1u }
#define NET_CFG_DEF_MAC { 0u, 0u, 0u, 0u, 0u, 0u }
/* 静态变量定义区 */
+3 -3
View File
@@ -36,9 +36,9 @@ extern "C" {
* mac 全零哨兵:不设置芯片 MAC(用芯片内置值,存于芯片 EEPROM)
*/
typedef struct {
uint8_t ip[4]; /* IP 地址(工厂默认 192.168.1.100) */
uint8_t mask[4]; /* 子网掩码(工厂默认 255.255.255.0) */
uint8_t gw[4]; /* 默认网关(工厂默认 192.168.1.1) */
uint8_t ip[4]; /* IP 地址(工厂默认 = net_cfg.c 的 NET_CFG_DEF_IP) */
uint8_t mask[4]; /* 子网掩码(工厂默认 = net_cfg.c 的 NET_CFG_DEF_MASK) */
uint8_t gw[4]; /* 默认网关(工厂默认 = net_cfg.c 的 NET_CFG_DEF_GW) */
uint8_t mac[6]; /* MAC 地址(全零 = 芯片内置 MAC) */
} net_cfg_t;
+9 -2
View File
@@ -11,7 +11,7 @@
# * Only the device's own production logs are used for the device-side timeline
# (window them by the per-round "TS <epoch>" line printed here).
#
# Usage: ftp_freeze_probe.py [--ip 192.168.1.100] [--port 21] [--size 65536]
# Usage: ftp_freeze_probe.py [--ip <设备IP>] [--port 21] [--size 65536]
# [--rounds 10] [--gap 3] [--keep]
# Output lines (machine-auditable):
# TS <epoch>
@@ -22,6 +22,13 @@
import argparse, hashlib, io, os, random, socket, sys, time
from ftplib import FTP, error_reply, error_temp, error_perm, all_errors
# 台架连接默认值单一真源在 test/host/bench_defaults.py(跨目录:补 sys.path 后 import)
_sys_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "test", "host")
if _sys_path not in sys.path:
sys.path.insert(0, _sys_path)
import bench_defaults # noqa: E402
# Device-side FTP auth: lftpd validates against /config.ini [ftp] (user / pwd_hash).
# This device has NO anonymous channel (USER anonymous -> 530), so the probe must log
# in with the factory account. Override with --user/--password if it was changed.
@@ -48,7 +55,7 @@ def liveness(ip, port, timeout=3.0):
def main():
global FTP_USER, FTP_PASS # 两处登录点都读模块常量,命令行覆盖后即刻生效
ap = argparse.ArgumentParser()
ap.add_argument("--ip", default="192.168.1.100")
ap.add_argument("--ip", default=bench_defaults.DEVICE_IP)
ap.add_argument("--port", type=int, default=21)
ap.add_argument("--size", type=int, default=65536)
ap.add_argument("--rounds", type=int, default=10)
+10 -3
View File
@@ -11,7 +11,7 @@
# * on failure the probe keeps retrying for --recover-s to distinguish
# "transient" from "permanently dead listener"
#
# Usage: tcp_reconnect_probe.py [--host 192.168.1.100] [--port 21] [--rounds 50]
# Usage: tcp_reconnect_probe.py [--host <设备IP>] [--port 21] [--rounds 50]
# [--gap-ms 600] [--timeout 3] [--recover-s 10]
# [--banner] (read one line after connect, e.g. FTP 220)
# Output:
@@ -19,7 +19,14 @@
# ROUND <n> gap=<ms> connect=<OK|FAIL:reason> recovered=<na|yes|no>
# SUMMARY rounds=<n> ok=<n> fail=<n> unrecovered=<n>
# Exit: 0 all rounds connected / 1 any failure / 2 usage
import argparse, socket, sys, time
import argparse, os, socket, sys, time
# 台架连接默认值单一真源在 test/host/bench_defaults.py(跨目录:补 sys.path 后 import)
_sys_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "test", "host")
if _sys_path not in sys.path:
sys.path.insert(0, _sys_path)
import bench_defaults # noqa: E402
def log(m):
sys.stdout.write(m + "\n"); sys.stdout.flush()
@@ -36,7 +43,7 @@ def try_connect(host, port, timeout, banner):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="192.168.1.100")
ap.add_argument("--host", default=bench_defaults.DEVICE_IP)
ap.add_argument("--port", type=int, default=21)
ap.add_argument("--rounds", type=int, default=50)
ap.add_argument("--gap-ms", type=int, default=600)
@@ -1276,18 +1276,18 @@ for (i = 1; i < LED_COUNT; i++) /* LE
; DTU 通用配置文件
[network]
; IP 地址
ip=192.168.1.100
ip=192.168.31.100
; 子网掩码
mask=255.255.255.0
; 默认网关
gateway=192.168.1.1
gateway=192.168.31.1
; MAC 地址 (全零 = 使用芯片内置 MAC)
mac=00:00:00:00:00:00
```
**值类型**:`IP4`(`a.b.c.d`)、`MAC`(`aa:bb:cc:dd:ee:ff`)、`UINT`(十进制,兼容 `0x`)、`STR`(≤31 字符)。
**工厂默认**:IP `192.168.1.100` / 掩码 `255.255.255.0` / 网关 `192.168.1.1` / MAC 全零(= 芯片内置)。
**工厂默认**:IP `192.168.31.100` / 掩码 `255.255.255.0` / 网关 `192.168.31.1` / MAC 全零(= 芯片内置)。
> MAC 全零是"使用芯片内置 MAC"的**哨兵**,不是"MAC 为 0"。恢复出厂:FTP `DELE /config.ini` → 下次上电重新生成模板。
+6 -6
View File
@@ -15,7 +15,7 @@
| **入口** | 屏幕:`通讯参数 → 网口设置`(修改密码 `123` 可编辑;查看密码 `000` 只读) |
| **存储** | NAND Flash 文件 `/config.ini`(INI `[network]` 段,通用配置文件,掉电不丢);旧版 `/cfg/netcfg.bin` **不再读取**(存在则忽略,见 §3.2) |
| **生效** | 按「保存」后**立即生效**(网络重建约 1~3s,既有 TCP 连接会断开,无需重启) |
| **工厂默认** | `192.168.1.100` / `255.255.255.0` / `192.168.1.1` / 芯片内置 MAC |
| **工厂默认** | `192.168.31.100` / `255.255.255.0` / `192.168.31.1` / 芯片内置 MAC |
**典型操作(改 IP)**:
@@ -32,9 +32,9 @@
| 参数 | 字段 | 工厂默认 | 说明 |
|---|---|---|---|
| IP 地址 | `ip[4]` | **192.168.1.100** | 静态 IP(不启用 DHCP) |
| IP 地址 | `ip[4]` | **192.168.31.100** | 静态 IP(不启用 DHCP) |
| 子网掩码 | `mask[4]` | **255.255.255.0** | /24 |
| 默认网关 | `gw[4]` | **192.168.1.1** | |
| 默认网关 | `gw[4]` | **192.168.31.1** | |
| MAC 地址 | `mac[6]` | **全零** | 全零 = 不覆盖芯片内置 MAC(见 §2) |
工厂默认值是**单一来源**:`net_cfg.c` 顶部 `NET_CFG_DEF_*` 宏,同时用于 `s_cfg` 静态初始化与 `s_defaults`(引擎注册的 `p_defaults` 目标),避免两处漂移。
@@ -71,9 +71,9 @@
; ============================================================
[network]
ip=192.168.1.100
ip=192.168.31.100
mask=255.255.255.0
gw=192.168.1.1
gw=192.168.31.1
mac=00:00:00:00:00:00
```
@@ -96,7 +96,7 @@ mac=00:00:00:00:00:00
| 存在 | — | 正常加载现有文件 |
- **删除理由**:该一次性路径把整条配置**写**链(`net_cfg_save → cfg_file_save → cfg_store_write_atomic → f_rename → FatFS → dhara FTL`)无条件挂进启动期调用图,使 defaultTask 的 armlink 静态最坏栈链 **+600 B**;而本仓库不存在携带旧档的设备。
- **对使用方的影响**:卷上仍残留旧档的设备升级本版本后,网口配置**回落工厂默认**(`192.168.1.100/24`、gw `.1`),需经 UI「网口设置」或 FTP 上传 `/config.ini` 重新配置。
- **对使用方的影响**:卷上仍残留旧档的设备升级本版本后,网口配置**回落工厂默认**(`192.168.31.100/24`、gw `.1`),需经 UI「网口设置」或 FTP 上传 `/config.ini` 重新配置。
### 3.3 存储后端(双端)
@@ -104,11 +104,11 @@
[network]
; IP 地址
ip=192.168.1.100
ip=192.168.31.100
; 子网掩码
mask=255.255.255.0
; 默认网关
gateway=192.168.1.1
gateway=192.168.31.1
; MAC 地址 (全零 = 使用芯片内置 MAC)
mac=00:00:00:00:00:00
```
@@ -117,7 +117,7 @@ mac=00:00:00:00:00:00
| 类型 | 语法 | 示例 | 解析失败处理 |
|---|---|---|---|
| `CFG_VAL_IP4` | `a.b.c.d`(兼容前导零) | `192.168.1.100` | 该键取工厂默认 + `DBG_ERROR` |
| `CFG_VAL_IP4` | `a.b.c.d`(兼容前导零) | `192.168.31.100` | 该键取工厂默认 + `DBG_ERROR` |
| `CFG_VAL_MAC` | `aa:bb:cc:dd:ee:ff`(十六进制) | `a4:94:b6:12:34:56` | 同上 |
| `CFG_VAL_UINT` | 十进制(兼容 `0x` 前缀) | `7003` | 同上 |
| `CFG_VAL_STR` | 原始字符串(≤31 字符) | `hello` | 同上 |
@@ -330,7 +330,7 @@ TPAFE 是双极性、二进制补码,可以注负压;STM32 是单极性,
| 板子 | 产品构建(`test/board/test_config.h` 四个 `TEST_SUITE_*` 全注释)+ `/keil-build` 0 错误 0 警告 + 烧录 `-Rst -Run` 与回读校验 | → `stlink-flash` skill 与 AGENTS.md「构建与验证」 |
| `[adc_cal]` 状态 | **保持未标定**(`eng=0`),别为"读数好看"先写标定值 | 现在 `meas:` / `phasor` 的量纲是 **ADC 引脚电压 V**(`App/algo/meas.h` 文件头的 A-lite 口径);标定一生效就换成二次值,本节全部期望值作废 |
| 串口 | 全程抓 RX 落盘:用 `serial-assistant` skill(`open` → `clear` → 跑 → 读日志文件或 `log --tail`) | 日志文件就是证据本体,§8.10 归档归档的就是它;**长跑别用 `/serial-boot-test`**——它绑定一轮启动语义(静默 8 s、总超时 30 s、`max_boot_count` = 1,且 forbidden 含 `[ERR]`,而削顶轮故意造 `[ERR]`),约 135 min 的 session 必然超时或误判;daemon 全量写 `state/serial.log`(20 MB 上限自动截头保尾 10 MB)→ `docs/测试说明/流程与规范/自动化测试流程.md` 长时间采集一节 |
| 网口(可选) | 要读 104 那一路就插网线、PC 与板同网段(出厂 192.168.1.100),跑现成主站 `test/host/iec104_master.py`(`--ip` / `--port` / `--addr`) | 104 是浮点无量化 + 带品质,是 `meas:` 之外的第二条腿 |
| 网口(可选) | 要读 104 那一路就插网线、PC 与板同网段(出厂 192.168.31.100),跑现成主站 `test/host/iec104_master.py`(`--ip` / `--port` / `--addr`) | 104 是浮点无量化 + 带品质,是 `meas:` 之外的第二条腿 |
| FTP 客户端 | 走现成 21 端口取段文件(出厂 `[ftp]` 账号,连续 5 次失败锁 60 s) | 段文件是相位 / 谐波轮定量档的样本出口;段文件名 `/wave/WAVE<NN>.BIN`(00~99 轮转),**取完立刻改名归档**,否则下一次触发把它盖掉 |
| 录波触发路径 | 产品构建里唯一**免改代码**的路径 = UI 菜单第 26 叶「手动录波」确认页(`Drivers/BSP/UI/ui_page_wave_manual.c` → `App/task/ui_task.c:605`) | 保护启动上升沿也算一次触发,但保护判据现处**未标定闭锁**态(AGENTS.md「现状」),别指望它自动来段;bench 定时自抓在双层编译门 `DTU_BENCH` 且 `WAVE_BENCH_AUTOCAP` 非 0 之内(默认 0,`App/task/adc_task.c:153`),开它就走 §8.9 的还原判据 |
| 共地 | 发生器 LO/GND 与板 AGND **单点**相连 | 不共地量到的是两地之差;现象见 §8.8 |
@@ -29,7 +29,7 @@ flowchart LR
## 2. 到场检查清单(人到板边,打开这份清单逐条打勾)
- [ ] 板上电、网灯亮:会话里让 agent 跑 `ping 192.168.1.100`——通了就行(这台装置一直在线,串口 COM3、网线、ST-LINK 都常年插在这台电脑上,正常**都不用动**)
- [ ] 板上电、网灯亮:会话里让 agent 跑 `ping 192.168.31.100`——通了就行(这台装置一直在线,串口 COM3、网线、ST-LINK 都常年插在这台电脑上,正常**都不用动**)
- [ ] 信号发生器:和 2026-10-27 那次已知电压注入用的**是同一台**(波形档正弦、幅值 rms 直读;接线与端子位置照 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.3 那四条,本文不重抄)
- [ ] 万用表一块(并接监视 UA 端子,接线照上一条引的那四条)
- [ ] 打开这个仓库的 agent 会话,对它说一句:**"按故障录波到场轮手册,先做第 X 幕"**——后面每步它自己跑,该你出手时它会说"到你接线了"
@@ -258,7 +258,7 @@ DTU-RemoteLCD 客户端;输出每帧 `req2first / total / recv 批次到达时
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # 必须 0 错 0 警
# 1) 先 clear 再起 peer(顺序不可反:wait-idle 需要看到会话活跃期)
& '.venv\Scripts\python.exe' .dsh\skills\serial-assistant\scripts\serial_tool.py clear
& '.venv\Scripts\python.exe' test\latency_peer.py --host 192.168.1.100 --port 8080 `
& '.venv\Scripts\python.exe' test\host\latency_peer.py --host 192.168.31.100 --port 8080 `
--json "docs\测试说明\测试数据\m2_data\m2_quiet.json" --wait 170 # 后台作业
# 2) 烧录(Reset&Run 不可省)
& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash
@@ -284,16 +284,16 @@ DTU-RemoteLCD 客户端;输出每帧 `req2first / total / recv 批次到达时
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # 0 错 0 警(Phase 14 构建)
& '.venv\Scripts\python.exe' .dsh\skills\serial-assistant\scripts\serial_tool.py clear
& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash # 先烧录,再起 peer(顺序不可反!)
& '.venv\Scripts\python.exe' test\latency_peer.py --mode remo --host 192.168.1.100 --port 7003 `
& '.venv\Scripts\python.exe' test\host\latency_peer.py --mode remo --host 192.168.31.100 --port 7003 `
--samples 45 --stress-from 15 --ftp-stress --json "docs\测试说明\测试数据\m3_data\m3_dev_stress.json"
# 模拟器基线(Δ 的对照端)
& "simulator\build_sim.bat"; .\simulator\build\Debug\lvgl_sim.exe # 等 7003 就绪
& '.venv\Scripts\python.exe' test\latency_peer.py --mode remo --host 127.0.0.1 --port 7003 `
& '.venv\Scripts\python.exe' test\host\latency_peer.py --mode remo --host 127.0.0.1 --port 7003 `
--samples 50 --json "docs\测试说明\测试数据\m3_data\m3_sim_quiet.json"
# 尾批定性/解法(都不需要抓包工具)
& '.venv\Scripts\python.exe' test\latency_peer.py --mode remo --host 192.168.1.100 --port 7003 `
& '.venv\Scripts\python.exe' test\host\latency_peer.py --mode remo --host 192.168.31.100 --port 7003 `
--samples 30 --ack-probe --json "docs\测试说明\测试数据\m3_data\m3_tail_ackprobe.json" # 带内补 ACK
& '.venv\Scripts\python.exe' test\latency_peer.py --mode remo --host 192.168.1.100 --port 7003 `
& '.venv\Scripts\python.exe' test\host\latency_peer.py --mode remo --host 192.168.31.100 --port 7003 `
--samples 30 --ack-fast --json "docs\测试说明\测试数据\m3_data\m3_tail_ackfast.json" # 关延迟 ACK
```
@@ -249,7 +249,7 @@
|---|---|---|---|---|---|
| **FTP 一套** | `lftpd.c`(694)、`lftpd_io.c`(207)、`lftpd_inet.c`(171)、`lftpd_string.c`(11) = 1,083 | 0.99/0.90/0.93 | 真实 `ff.h` + 假 diskio;复用 `net_socket.h` | `docs/测试说明/板级测试/FTP_Test_Guide.md`、`docs/子系统设计/网络配置说明.md` §3.4(`pwd_hash`=SHA-256(user‖password)、连续 5 次失败锁 60 s、默认口令启动告警) | **20 次提交里 6 次是 fix**,最近两条都是"静默型":LIST/NLST 满块被截断回 550、对端行为误打 `[ERR]`(2026-10-23)——覆盖率最擅长照的就是这类"日志看不出根因" |
| **NET 拆出来的四块** | `net_pending.c`(287)、`net_events.c`(263)、`net_listener.c`(191)、`net_select.c`(162) = 903 | 0.90/0.78/–/0.93 | **新建 `stub/ch395f.h`**(芯片寄存器只到函数边界)+ 假任务通知;`net_socket.h` 已有 | `docs/网络/网络自愈架构设计.md` §4.10(socket 读清点审计 + 新增一处读清的三条准入判据)、`docs/网络/Net层阻塞模型架构设计.md` | 挂起 recv、监听按代次重建、退避这些**语义在真机上极难复现**;这一族的 网络自愈("顺手全量清一遍把别的 socket 门焊死")与 N34 E14 都是 fix 出来的真事 |
| **配置与工具(最便宜,建议第一个落地)** | `net_cfg.c`(167)、`task_prof.c`(160)、`ftp_cfg.c`(59)、`prot_set_store.c`(43) = 429 | 1.00/0.97/–/– | 真实头即可(`cfg_file.h` 已被现有单测证明能在 PC 上编),最多补 `dbg_log.h` | `docs/子系统设计/网络配置说明.md`(工厂默认 192.168.1.100/24、MAC 全零=芯片内置、保存立即生效)、`docs/算法/采集与算法层设计说明.md` | 硬件符号 0~2 处,纯解析/编解码/格式化。**一天内能出第一份绿**,用来把"新增 `files_tested`"这条路先跑通 |
| **配置与工具(最便宜,建议第一个落地)** | `net_cfg.c`(167)、`task_prof.c`(160)、`ftp_cfg.c`(59)、`prot_set_store.c`(43) = 429 | 1.00/0.97/–/– | 真实头即可(`cfg_file.h` 已被现有单测证明能在 PC 上编),最多补 `dbg_log.h` | `docs/子系统设计/网络配置说明.md`(工厂默认 192.168.31.100/24、MAC 全零=芯片内置、保存立即生效)、`docs/算法/采集与算法层设计说明.md` | 硬件符号 0~2 处,纯解析/编解码/格式化。**一天内能出第一份绿**,用来把"新增 `files_tested`"这条路先跑通 |
| **104 服务层** | `iec104_serv.c`(202) | 0.96 | 复用 `time_sync`/stub 的 `cmsis_os2.h` + 已有 `net_socket.h` | 与 `test/host/iec104/`(97 用例)同一套脚手架 | 同族的 `iec104_pts.c` 现在只有 **67.2%**(全仓最低之一);服务层进来才能把 YK/YX 编排的分支照全 |
**落地进度(2026-10-23,同日推进)**:
@@ -74,7 +74,7 @@ UDP 路径 `net_socket()` 中对 Socket 4~7 的兜底重设使用同一布局。
### 测试环境
```
PC (192.168.1.2) ──── 交换机 ──── CH395F (192.168.1.100)
PC (192.168.31.2) ──── 交换机 ──── CH395F (192.168.31.100)
MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
```
@@ -120,8 +120,8 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
|----|------|----------|----------|
| 1 | `ch395f_check_exist()`<br>SPI写: CMD=0x06, DATA=0x57<br>回读: ~0x57 = 0xA8 | 返回 `0x00`(`CH395F_ERR_SUCCESS`)<br>= 回读字节 `0xA8` 匹配 `~0x57` | 返回 `0xFF`(`CH395F_STATUS_NOT_DETECTED`)<br>= 回读字节 ≠ `0xA8`,回显取反不匹配 |
| 2 | `ch395f_get_version()` | 版本值 == `0x4A`(CH395F 芯片版本) | 版本值 ≠ `0x4A`(芯片不匹配或未响应) |
| 3 | `ch395f_set_ip_addr(192.168.1.100)` → `ch395f_get_ip_inf()` 回读 IP | `ip_info[0..3] == 192.168.1.100` | 任意字节不匹配(写入/回读不一致) |
| 4 | `ch395f_set_gwip_addr(192.168.1.1)` → `ch395f_get_ip_inf()` 回读 GW | `ip_info[4..7] == 192.168.1.1` | 任意字节不匹配 |
| 3 | `ch395f_set_ip_addr(192.168.31.100)` → `ch395f_get_ip_inf()` 回读 IP | `ip_info[0..3] == 192.168.31.100` | 任意字节不匹配(写入/回读不一致) |
| 4 | `ch395f_set_gwip_addr(192.168.31.1)` → `ch395f_get_ip_inf()` 回读 GW | `ip_info[4..7] == 192.168.31.1` | 任意字节不匹配 |
| 5 | `ch395f_set_mask_addr(255.255.255.0)` → `ch395f_get_ip_inf()` 回读 MASK | `ip_info[8..11] == 255.255.255.0` | 任意字节不匹配 |
| 6 | `ch395f_get_glob_int_status_all()`(16 位)<br>注:测试入口处已预读一次清除 `PHY_CHANGE` 残留(`net_stack_bring_up()` 强制 PHY 重协商 + 等待链路期间锁存,硬件层模式无 netTask 消费) | 返回值 == `0x0000`(无待处理中断) | 返回值 ≠ `0x0000`(有残留中断未清除) |
| 7 | `ch395f_open_socket(0)` → `ch395f_close_socket(0)` → `ch395f_get_cmd_status()` | `CMD_STATUS == 0x00`(`CH395F_ERR_SUCCESS`) | `CMD_STATUS` 非 `0x00`(Socket 操作失败) |
@@ -161,7 +161,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `ch395f_set_proto_type(0, CH395F_PROTO_TYPE_TCP)` + 设置目标 IP `192.168.1.2`、端口 `8081`、本地端口 `50000` | 配置写入成功(CMD_STATUS == `0x00`) | 配置失败(SPI 异常或寄存器未更新) |
| 1 | `ch395f_set_proto_type(0, CH395F_PROTO_TYPE_TCP)` + 设置目标 IP `192.168.31.2`、端口 `8081`、本地端口 `50000` | 配置写入成功(CMD_STATUS == `0x00`) | 配置失败(SPI 异常或寄存器未更新) |
| 2 | `ch395f_open_socket(0)` 打开 Socket 0 | 返回 `CH395F_ERR_SUCCESS`(`0x00`) | 返回值非 `0x00`(Socket 硬件异常或 SPI 通信失败) |
| 3 | `ch395f_tcp_connect(0)` 发起 TCP 连接 | 返回 `CH395F_ERR_SUCCESS`(`0x00`) | 返回值非 `0x00`(连接命令下发失败) |
| 4 | 轮询 `GET_GLOB_INT_STATUS` 的 `(sock+4)` 位 + `SINT_STAT` 等待 `CH395F_SINT_STAT_CONNECT`,超时 10s | 检测到 CONNECT 中断(连接已建立,进入 ESTABLISHED) | 超时未建立连接(目标不可达 / 链路异常) |
@@ -210,13 +210,13 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
| **优先级** | P1 |
| **类型** | 负向测试(硬件层) |
| **标题** | 目标端口无服务时连接超时 |
| **前置条件** | 1. PC **未**运行 TCP Server(`192.168.1.2:8081` 无监听)<br>2. TC-NET-201 通过 |
| **前置条件** | 1. PC **未**运行 TCP Server(`192.168.31.2:8081` 无监听)<br>2. TC-NET-201 通过 |
**测试步骤与判定标准**:
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `ch395f_set_proto_type(0, CH395F_PROTO_TYPE_TCP)` + 设置目标 IP `192.168.1.2`、端口 `8081` | 配置写入成功(CMD_STATUS == `0x00`) | 配置失败(SPI 异常或寄存器未更新) |
| 1 | `ch395f_set_proto_type(0, CH395F_PROTO_TYPE_TCP)` + 设置目标 IP `192.168.31.2`、端口 `8081` | 配置写入成功(CMD_STATUS == `0x00`) | 配置失败(SPI 异常或寄存器未更新) |
| 2 | `ch395f_open_socket(0)` → `ch395f_tcp_connect(0)` | OPEN / CONNECT 均返回 `CH395F_ERR_SUCCESS`(`0x00`),命令异步下发不立即报错 | 命令返回值非 `0x00`(Socket 硬件异常) |
| 3 | 轮询 `GET_GLOB_INT_STATUS` 的 `(sock+4)` 位,等待负向结果,超时窗 30s;同时扫全局 `UNREACH` 位作备注 | 收到 `SOCK_TIMEOUT` 或 `DISCONNECT`,且期间未收到 `CH395F_SINT_STAT_CONNECT`。按耗时细分:<br>**<3s** = RST 快速拒绝(端口关闭,实测主路径——芯片毫秒级上报 SOCK_TIMEOUT)<br>**≥3s** = 真无应答(离线/防火墙丢包)。全局 `UNREACH`(ARP 失败)仅备注不影响判定 | 误收到 `CONNECT`(PC 服务未关闭,环境错误——串口给出明确指引)或 30s 内无任何中断(状态机挂死) |
| 4 | `ch395f_close_socket(0)` 并轮询 `GET_SOCKET_STATUS` 等待 `CH395F_SOCKET_CLOSED` | Socket 正常关闭、系统无崩溃 / 死锁 | 关闭异常或任务挂死 |
@@ -277,7 +277,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
| **优先级** | P0 |
| **类型** | 功能测试(硬件层) |
| **标题** | 30 轮 64B UDP 数据报回显 |
| **前置条件** | PC: `python test/host/ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 60000` |
| **前置条件** | PC: `python test/host/ch395f_socket_test.py udp_client --ip 192.168.31.100 --port 60000` |
> **会话结构**(固件单会话,与 udp_client 脚本约定):累计 **60 个有效数据包**后关闭 Socket——第 1~30 轮对应本用例(PC 发 64B),第 31~60 轮对应 TC-NET-302(变长);脚本 `"HELLO"` 就绪探针只回显、**不计数**,不占用用例预算。
@@ -341,7 +341,7 @@ CH395F PC (dhcp_server)
- **xid/chaddr 回填**:OFFER/ACK 必须原样回传 DISCOVER 的 `xid([4:8])` 与 `chaddr([28:34])`(TRAP-NET-03:早期 xid 偏移错误导致芯片丢弃响应)
- **option53 位置**:报文类型在 `data[242]`(236B 固定头 + magic `63825363` + option 偏移),`len<=250` 视为无效包
- **双广播发送**:OFFER/ACK 同时发 `255.255.255.255` 与 `192.168.1.255`(规避 Windows 定向广播路由限制)
- **双广播发送**:OFFER/ACK 同时发 `255.255.255.255` 与 `192.168.31.255`(规避 Windows 定向广播路由限制)
- **REQUEST 重试**:PC 最多等 8×5s;期间收到重复 DISCOVER 则重发 OFFER
**固件会话结构**(phase4_run):
@@ -364,7 +364,7 @@ CH395F PC (dhcp_server)
| **优先级** | P0 |
| **类型** | 功能测试(硬件层) |
| **标题** | DISCOVER → OFFER → REQUEST → ACK 全流程获取 IP |
| **前置条件** | 管理员 PowerShell: `python test/host/ch395f_socket_test.py dhcp_server --ip 192.168.1.77 --dhcp-iface 192.168.1.2`(`--ip` 建议 ≠ 静态 IP,以证明确为动态获取) |
| **前置条件** | 管理员 PowerShell: `python test/host/ch395f_socket_test.py dhcp_server --ip 192.168.31.77 --dhcp-iface 192.168.31.2`(`--ip` 建议 ≠ 静态 IP,以证明确为动态获取) |
**测试步骤与判定标准**:
@@ -551,7 +551,7 @@ MCU (TCP Client, ch395f_* 裸命令) PC (tcp_bulk --port 8081)
```mermaid
flowchart LR
PC["PC(Python)<br/>tcp_client / tcp_server"] -->|"TCP 8080 · 以太网"| CH["CH395F(192.168.1.100)<br/>Socket 0(单连接复用)"]
PC["PC(Python)<br/>tcp_client / tcp_server"] -->|"TCP 8080 · 以太网"| CH["CH395F(192.168.31.100)<br/>Socket 0(单连接复用)"]
CH -->|"SPI2(分频=8)"| MCU["MCU(STM32F407)<br/>netTask: net_poll() 10ms<br/>testTask: net_layer_test_main()<br/>└─ phase6_run() 原样回显"]
```
@@ -593,7 +593,7 @@ sequenceDiagram
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | Socket 0:OPEN → TCP_LISTEN → ESTABLISHED(单连接) |
| **前置条件** | 1. `net_init("192.168.1.100")` 成功、SYS_EVT_NET_READY 已广播(netTask 周期 `net_poll()` 运行)<br>2. `net_layer_test_main()` 已 `net_socket`+`net_bind(8080)`+`net_listen`(进入 LISTEN)<br>3. PC: `python .\test\ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080` |
| **前置条件** | 1. `net_init("192.168.31.100")` 成功、SYS_EVT_NET_READY 已广播(netTask 周期 `net_poll()` 运行)<br>2. `net_layer_test_main()` 已 `net_socket`+`net_bind(8080)`+`net_listen`(进入 LISTEN)<br>3. PC: `python .\test\ch395f_socket_test.py tcp_client --ip 192.168.31.100 --port 8080` |
**测试步骤与判定标准**:
@@ -751,7 +751,7 @@ net_socket(NET_AF_INET, NET_SOCK_STREAM) // 取空闲 Socket(非监听 So
```mermaid
flowchart LR
MCU["MCU(STM32F407)<br/>netTask: net_poll() 10ms<br/>testTask: net_layer_client_test_main()<br/>└─ net_connect → 收发 → close"] -->|"SPI2(分频=8)"| CH["CH395F(192.168.1.100)<br/>Socket N(非 0 空闲)"]
MCU["MCU(STM32F407)<br/>netTask: net_poll() 10ms<br/>testTask: net_layer_client_test_main()<br/>└─ net_connect → 收发 → close"] -->|"SPI2(分频=8)"| CH["CH395F(192.168.31.100)<br/>Socket N(非 0 空闲)"]
CH -->|"TCP 8081 · 以太网"| PC["PC(Python)<br/>tcp_server(回显服务器)"]
```
@@ -796,7 +796,7 @@ sequenceDiagram
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `net_socket(NET_AF_INET, NET_SOCK_STREAM)` 创建 TCP socket | 返回 sockfd(`0~7`),非 `-1` | 返回 `-1`(无空闲 Socket 或 net 层未初始化) |
| 2 | 构造 `struct net_sockaddr_in`(sin_family=`NET_AF_INET`,sin_port=`net_htons(8081)`,sin_addr=`net_inet_addr("192.168.1.2")`)后 `net_connect(sockfd, &addr, len)` | 返回 `0`(连接命令已异步下发) | 返回 `-1`(地址非法或协议栈异常) |
| 2 | 构造 `struct net_sockaddr_in`(sin_family=`NET_AF_INET`,sin_port=`net_htons(8081)`,sin_addr=`net_inet_addr("192.168.31.2")`)后 `net_connect(sockfd, &addr, len)` | 返回 `0`(连接命令已异步下发) | 返回 `-1`(地址非法或协议栈异常) |
| 3 | 在 netTask 周期 `net_poll()` 推进下,调用 `net_select(sockfd+1, NULL, &wfds, NULL, &tv)` 等待可写事件,超时 10s | 返回正数且 `NET_FD_ISSET(sockfd)` 指示连接建立(ESTABLISHED) | 超时未连接,`net_errno == NET_ERR_WOULDBLOCK` 持续 |
| 4 | `net_send(sockfd, buf, 64, 0)` 发送 64 字节递增模式数据(`0x00..0x3F`) | 返回值 == `64`(全部发出) | 返回值 `< 0`(发送失败)或 `≠ 64`(未完全发出) |
| 5 | `net_recv(sockfd, buf, 64, 0)` 阻塞接收回显,超时 10s | 返回值 == `64`、与原数据逐字节一致、且固件字节级比对 + 累加和 `TEST_CHECK` 通过 | 返回值 `< 0`(超时/断开)或 `≠ 64`(数据不完整)或任意字节不符 或 固件 `TEST_CHECK` 失败 |
@@ -837,7 +837,7 @@ sequenceDiagram
| **优先级** | P1 |
| **类型** | 负向测试(NET 层) |
| **标题** | 目标端口无服务时 net_connect 超时 |
| **前置条件** | 1. PC **未**运行 TCP Server(`192.168.1.2:8081` 无监听)<br>2. TC-NET-701 通过 |
| **前置条件** | 1. PC **未**运行 TCP Server(`192.168.31.2:8081` 无监听)<br>2. TC-NET-701 通过 |
**测试步骤与判定标准**:
@@ -867,7 +867,7 @@ sequenceDiagram
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | 循环 i=0..4:`net_socket()` 创建 5 个 TCP socket(本地端口 51000+i),`net_connect(192.168.1.2:8081)` | 5 个 `net_connect` 均返回 `0` | 任一返回 `-1` |
| 1 | 循环 i=0..4:`net_socket()` 创建 5 个 TCP socket(本地端口 51000+i),`net_connect(192.168.31.2:8081)` | 5 个 `net_connect` 均返回 `0` | 任一返回 `-1` |
| 2 | netTask `net_poll()` 推进下,`net_select` 等待全部 5 路可写 | 5 路均在 10s 内 ESTABLISHED | 任一超时未建立 |
| 3 | 每路 `net_send(sockfd, 64B)` → `net_recv(sockfd, 64B)` 校验 | 5 路收发各 64B、字节级比对 + 累加和 `TEST_CHECK` 通过 | 任一收发失败 / 数据不符 / `TEST_CHECK` 失败 |
@@ -889,7 +889,7 @@ sequenceDiagram
| **耗时** | 取决于文件大小与速率(100KB 约数秒) |
| **入口** | `ENABLE_PHASE8_TESTS` |
| **配置** | `PHASE8_FILE_SIZE`(默认 102400 = 100KB);`PHASE8_DIRECTION`(SEND / RECV / ECHO,默认 ECHO) |
| **PC 配合** | `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op recv --file rx_100k.bin --size 102400`(文件发送)<br>`... --op send --file tx_100k.bin --size 102400`(文件接收) |
| **PC 配合** | `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.31.100 --port 8080 --op recv --file rx_100k.bin --size 102400`(文件发送)<br>`... --op send --file tx_100k.bin --size 102400`(文件接收) |
> 架构:MCU 为 TCP Server(`net_test_task` 监听 8080,`net_accept` 后 `phase8_run(acc)`);PC 用 `tcp_file` 子命令作 Client 连接 MCU:8080。一次 `phase8_run` 对应一次 PC 连接。
@@ -912,7 +912,7 @@ sequenceDiagram
| **优先级** | P0 |
| **类型** | 功能测试(文件发送) |
| **标题** | MCU 流式发送 100KB 至 PC 并落盘校验 |
| **前置条件** | 1. PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op recv --file rx_100k.bin --size 102400`(先连接等待数据)<br>2. `ENABLE_PHASE8_TESTS`,`PHASE8_FILE_SIZE=102400`,`PHASE8_DIRECTION=SEND`<br>3. `net_init` 完成,MSS=1024 |
| **前置条件** | 1. PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.31.100 --port 8080 --op recv --file rx_100k.bin --size 102400`(先连接等待数据)<br>2. `ENABLE_PHASE8_TESTS`,`PHASE8_FILE_SIZE=102400`,`PHASE8_DIRECTION=SEND`<br>3. `net_init` 完成,MSS=1024 |
| **测试步骤** | 1. MCU `net_accept` 建链(PC 已连接)<br>2. 循环填充一个滑动窗口(≤ s_tx_buf)并调用 `net_send`;`net_send` 内部按硬件发送 FIFO(`NET_SEND_CHUNK_MAX`=1KB,见 TRAP-NET-16)自动拆分写入,直至 100KB 发完<br>3. PC 接收全部字节写入 `rx_100k.bin`,按递增模式或预置 CRC32 校验 |
| **通过标准** | PC 收满 102400B 且逐字节/CRC32 校验一致,MCU 无发送错误 |
| **覆盖原则** | DMA 边界安全(分块发送、DMA 边界)、缓冲架构(1KB 发送缓冲) |
@@ -926,7 +926,7 @@ sequenceDiagram
| **优先级** | P0 |
| **类型** | 功能测试(文件接收) |
| **标题** | PC 流式发送 100KB 文件,MCU 接收并校验 |
| **前置条件** | 1. PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op send --file tx_100k.bin --size 102400`(先连接并发送;无文件则生成 100KB 递增模式)<br>2. 同 TC-NET-801(`PHASE8_DIRECTION=RECV`) |
| **前置条件** | 1. PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.31.100 --port 8080 --op send --file tx_100k.bin --size 102400`(先连接并发送;无文件则生成 100KB 递增模式)<br>2. 同 TC-NET-801(`PHASE8_DIRECTION=RECV`) |
| **测试步骤** | 1. MCU `net_accept` 建链(PC 已连接)<br>2. 循环 `net_recv`(≤2KB/次,保守一次读空)→ 按 offset 字节校验 + 累计 CRC32,直至收满 100KB<br>3. PC 在文件末尾附 4 字节 CRC32(小端);MCU 收齐后比对 CRC32 |
| **通过标准** | MCU 收满 102400B,字节校验全过且 CRC32 匹配,无溢出/截断 |
| **覆盖原则** | DMA 边界安全(接收缓冲 2KB 一次读空) |
@@ -940,7 +940,7 @@ sequenceDiagram
| **优先级** | P1 |
| **类型** | 恢复/压力测试 |
| **标题** | MCU 发 100KB 经 PC 回显原样返回,MCU 收齐校验 |
| **前置条件** | MCU: `PHASE8_DIRECTION=ECHO`(net_accept 监听 8080);PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op echo`(PC 作 Client 连 MCU 并原样回显) |
| **前置条件** | MCU: `PHASE8_DIRECTION=ECHO`(net_accept 监听 8080);PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.31.100 --port 8080 --op echo`(PC 作 Client 连 MCU 并原样回显) |
| **测试步骤** | MCU 发 100KB → PC 回显 → MCU 收 100KB 字节校验 |
| **通过标准** | 100KB 全过,验证 100KB 经回显链路完整 |
| **覆盖原则** | 单连接 socket、DMA 边界安全 |
@@ -1140,7 +1140,7 @@ WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `--ip` | 192.168.1.100 | 目标 IP |
| `--ip` | 192.168.31.100 | 目标 IP |
| `--port` | 因模式而异 | 端口 |
| `--count` | 10 | 测试次数 |
| `--timeout` | 30 | 超时秒数 |
@@ -25,7 +25,7 @@
### 2.1 硬件与网络
- CH395F 网口经以太网连接到 PC 或同一台交换机。
- PC 网卡与固件 IP **同网段**。固件默认静态 IP **192.168.1.100**(掩码 255.255.255.0);PC 可设 192.168.1.x(如 192.168.1.77)。
- PC 网卡与固件 IP **同网段**。固件默认静态 IP **192.168.31.100**(掩码 255.255.255.0);PC 可设 192.168.31.x(如 192.168.31.77)。
- **启动时序(2026-09-21 现行实现,取代旧文档的 `osDelay(7000)`)**:`netTask` 完成 `net_init()` + PHY 稳定后置 `SYS_EVT_NET_READY`;`defaultTask` 挂载卷 + 加载配置后置 `SYS_EVT_STORAGE_READY`;`ftpTask` 以 `osEventFlagsWait(两位, osFlagsWaitAll | osFlagsNoClear)` 等**两位同时就绪**再 `lftpd_start`。串口出现 `[FTP] waiting for connection...` 即可连接(实测:上电后约 0.5~1s,取决于存储挂载耗时)。
- ⚠ **就绪位等待必须带 `osFlagsNoClear`**(2026-09-21 实证,详见 §8.6):`osEventFlagsWait` 默认**消费**所等待的位,若被其它等待者清掉,`ftpTask` 的 `WaitAll(两位)` 永不成立 → FTP 静默不监听。
- **单客户端**:监听 socket 与数据通道复用,同一时刻只服务 1 个会话;上一个会话未 `QUIT` 就断开时,服务端约 30s 后才丢弃旧会话并重新 listen,期间新连接被拒(`WinError 10061`)。脚本已内置连接重试;**不要用裸 TCP 工具探测 21 端口**(探测本身会占掉唯一会话,见 §8.7)。
@@ -51,14 +51,14 @@
## 4. PC 端脚本手测(主要手段)
所有脚本支持 `--host`(默认 `192.168.1.100`)、`--port`(默认 21)、`--user` / `--password`
所有脚本支持 `--host`(默认 `192.168.31.100`)、`--port`(默认 21)、`--user` / `--password`
(默认出厂账号 `admin` / `admin`;设备口令改过就要显式传,**没有匿名通道**),在 `test/` 目录运行;`ftp_up_dl_test.py` / `ftp_speed_test.py` 内置**连接重试**(单客户端:旧会话未释放时自动等待重试,默认 6 次 × 3s)。
推荐入口:`& '.venv\Scripts\python.exe' test/host/ftp_up_dl_test.py`(功能闭环:登录/TYPE I/PASV/STOR/LIST/RETR/比对/DELE/QUIT)与 `& '.venv\Scripts\python.exe' test/host/ftp_speed_test.py --size 128`(大文件 + 吞吐);后者同时是 **ftpTask 栈水位测量**的负载(见 `docs/内存管理/RAM使用分析与优化方案.md` §4.0.6)。
### 4.1 冒烟测试:`ftp_test.py`
- 用途:验证连接、登录、`PWD`、`PASV`、`LIST` 基本链路。
- 命令:`python ./test/host/ftp_test.py --host 192.168.1.100`
- 命令:`python ./test/host/ftp_test.py --host 192.168.31.100`
- 预期:
- 连接成功并读到 `220` 欢迎;
- `USER admin` 返回 `331`(需要口令)、`PASS admin` 返回 `230`;
@@ -70,7 +70,7 @@
### 4.2 上传 / 下载 / 比对:`ftp_up_dl_test.py`
- 用途:验证 `STOR`/`RETR`/`DELE` 与数据一致性(`TYPE I` 二进制 + `PASV`)。
- 命令:`python ./test/host/ftp_up_dl_test.py --host 192.168.1.100`
- 命令:`python ./test/host/ftp_up_dl_test.py --host 192.168.31.100`
- 流程:登录 → `TYPE I` → `STOR up_test.dat` → `LIST` 校验存在 → `RETR up_test.dat` → 逐字节比对 → `DELE` 清理 → `QUIT`。
- 预期:`*** MATCH! Upload/Download success. ***`,且 LIST 中出现 `up_test.dat`。
- 判据:上传/下载字节数相等且内容逐字节一致。
@@ -78,7 +78,7 @@
### 4.3 速度与大数据量:`ftp_speed_test.py`
- 用途:测量上传/下载吞吐,作回归基线,同时验证大文件不丢字节。
- 命令:`python ./test/host/ftp_speed_test.py --host 192.168.1.100 --size 512`(默认 512KB,`--size` 可调)。
- 命令:`python ./test/host/ftp_speed_test.py --host 192.168.31.100 --size 512`(默认 512KB,`--size` 可调)。
- 预期:上传/下载均返回 `226`,回读 `MATCH!`,打印 KB/s。
- 注意:脚本使用 8.3 文件名 `SPEEDTST.DAT`(**FTP 仅支持 8.3 短文件名**,见 §8.3);以回读比对(MATCH)为最终判据,中间打印的 Sent 字段仅供参考。
- 判据:size 一致 + MATCH + 速度稳定(受 SPI2 分频 8 限制,通常约 300~450 KB/s,参见 `CH395F_Test_Guide.md` 阶段 5 吞吐基线)。
@@ -106,7 +106,7 @@
## 5. GUI 客户端手测(可选)
- **FileZilla**:主机 `192.168.1.100`,端口 21,用户 `admin`、密码 `admin`(**匿名会被 530 拒**);可拖拽上传/下载、浏览目录。
- **FileZilla**:主机 `192.168.31.100`,端口 21,用户 `admin`、密码 `admin`(**匿名会被 530 拒**);可拖拽上传/下载、浏览目录。
- 要点:使用二进制模式传输;文件名遵守 8.3;**一次只连一个客户端**。
---
@@ -71,7 +71,7 @@
```bash
# 一键闭环(selftest.log 自动 FTP 抓取;无故障记录 = PASS)
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py --require-selftest-pass --device-ip 192.168.1.100
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py --require-selftest-pass --device-ip 192.168.31.100
# 或手动联合判定(本轮串口日志 + selftest.log 本地副本)
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/verify_log.py \
@@ -208,7 +208,7 @@ D:/Code/DTU Code/
- 串口 `COM6` @115200
- ST-LINK SN `0B250D12A218303030303032`
- 设备 IP `192.168.1.100`
- 设备 IP `192.168.31.100`
机制:`_wt/.state/locks/{build,bench}.lock` 记录持有者(任务 ID + pid + 时间戳 + 备注 + 自报 TTL)。
@@ -553,9 +553,9 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 参数 | 工厂默认 | 修改方式 |
|---|---|---|
| IP | **192.168.1.100**(静态,无 DHCP) | 屏幕 `通讯参数 → 网口设置`(修改密码 `123`)保存后**立即生效**(网络重建约 1~3 s);或预置 `/config.ini`(INI;旧版 `/cfg/netcfg.bin` **不再读取**——存在则忽略、不迁移,配置回落工厂默认,2026-10-10 起) |
| IP | **192.168.31.100**(静态,无 DHCP) | 屏幕 `通讯参数 → 网口设置`(修改密码 `123`)保存后**立即生效**(网络重建约 1~3 s);或预置 `/config.ini`(INI;旧版 `/cfg/netcfg.bin` **不再读取**——存在则忽略、不迁移,配置回落工厂默认,2026-10-10 起) |
| 子网掩码 | 255.255.255.0(/24) | 同上 |
| 默认网关 | 192.168.1.1 | 同上 |
| 默认网关 | 192.168.31.1 | 同上 |
| MAC | 全零 = 芯片内置 MAC | 同上(全零是"使用芯片内置 MAC"的哨兵,不是"MAC 为 0") |
| PHY | 强制 100M 全双工 | 固件固定,不可配 |
@@ -583,7 +583,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| # | 项目 | 方法 | 通过标准 |
|---|---|---|---|
| 1 | 上电自启动 | 断电上电 + 串口采集 | `/serial-boot-test` PASS:Required `App init OK;Network Init;net init: OK;PHY: 100M FULL`,无 `FAIL/ERROR/HardFault` |
| 2 | 网络连通 | PC ping 192.168.1.100 | 通(PHY 100M FULL) |
| 2 | 网络连通 | PC ping 192.168.31.100 | 通(PHY 100M FULL) |
| 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 |
| 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 |
| 5 | UI 功能 | 屏幕 / 远端操作 | 菜单 7 项导航、内容页、密码(查看 `000` / 修改 `123`)、网口设置保存后立即生效 |
+10 -10
View File
@@ -222,7 +222,7 @@ int main(void)
* 本项目中由 netTask 调用(网口配置已由 defaultTask 在就绪屏障前
* 加载进 RAM 副本,netTask 只读副本组网)
*/
int ret = net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
int ret = net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL);
if (ret != 0) {
printf("网络初始化失败!\r\n");
return -1;
@@ -258,7 +258,7 @@ int main(void)
/* 外设初始化... */
/* BSD Socket API 初始化 */
net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL);
/* 创建 Socket、绑定、监听... */
@@ -395,7 +395,7 @@ void tcp_client(void)
/* 2. 设置服务器地址 */
addr.sin_family = AF_INET;
addr.sin_port = net_htons(8080);
addr.sin_addr.s_addr = net_inet_addr("192.168.1.200");
addr.sin_addr.s_addr = net_inet_addr("192.168.31.200");
/* 3. 连接服务器 */
printf("正在连接服务器...\r\n");
@@ -473,7 +473,7 @@ void udp_client(void)
/* 3. 设置目标地址 */
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = net_htons(8888);
dest_addr.sin_addr.s_addr = net_inet_addr("192.168.1.200");
dest_addr.sin_addr.s_addr = net_inet_addr("192.168.31.200");
/* 4. 发送数据 */
const char *msg = "UDP Hello!";
@@ -726,7 +726,7 @@ void event_callback_example(void)
### 9.0 `s_addr` 的唯一约定(2026-10-24 统一;全仓只此一族,勿再引入第二套)
**定义**:`sin_addr.s_addr` 一律存**网络字节序整数**,判据是 `&s_addr` 的**第 0 个内存字节 == 点分第一段**。
小端主机(STM32F407 与 PC 模拟器都是)上 `"192.168.1.100"` 的数值 = **`0x6401A8C0`**(内存里是 `C0 A8 01 64`)。
小端主机(STM32F407 与 PC 模拟器都是)上 `"192.168.31.100"` 的数值 = **`0x641FA8C0`**(内存里是 `C0 A8 1F 64`)。
| 方向 | 用哪个 API | 要点 |
|---|---|---|
@@ -759,11 +759,11 @@ uint16_t host_port = net_ntohs(net_port);
```c
/* 字符串转网络序 IP */
uint32_t ip = net_inet_addr("192.168.1.100");
uint32_t ip = net_inet_addr("192.168.31.100");
/* 网络序 IP 转字符串 */
char ip_str[16];
net_inet_ntoa(ip, ip_str); /* ip_str = "192.168.1.100" */
net_inet_ntoa(ip, ip_str); /* ip_str = "192.168.31.100" */
```
### 9.3 填充地址结构体
@@ -773,7 +773,7 @@ struct net_sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = net_htons(8080); /* 端口转换 */
addr.sin_addr.s_addr = net_inet_addr("192.168.1.100"); /* IP 转换 */
addr.sin_addr.s_addr = net_inet_addr("192.168.31.100"); /* IP 转换 */
/* 如果要绑定任意地址 */
addr.sin_addr.s_addr = net_inet_addr("0.0.0.0");
@@ -1180,7 +1180,7 @@ NET_FD_ISSET(fd, fdset) /* 检查 fd 是否在集合中 */
#include <stdio.h>
#include <string.h>
#define SERVER_IP "192.168.1.200"
#define SERVER_IP "192.168.31.200"
#define SERVER_PORT 8080
void tcp_client_reconnect(void)
@@ -1191,7 +1191,7 @@ void tcp_client_reconnect(void)
uint32_t reconnect_interval = 5000; /* 5 秒重连间隔 */
/* 初始化网络 */
net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL);
while (1)
{
+8 -1
View File
@@ -49,6 +49,13 @@ import subprocess
import sys
import time
# 台架连接默认值单一真源在 test/host/bench_defaults.py(跨目录:补 sys.path 后 import)
_sys_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "test", "host")
if _sys_path not in sys.path:
sys.path.insert(0, _sys_path)
import bench_defaults # noqa: E402
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
@@ -500,7 +507,7 @@ class BoardDevice(object):
CMD_KEY = 2
CMD_LCDMEM = 3
def __init__(self, ip="192.168.1.100", port=7003):
def __init__(self, ip=bench_defaults.DEVICE_IP, port=7003):
self.s = socket.create_connection((ip, port), timeout=5)
self.s.settimeout(3)
+1 -1
View File
@@ -77,7 +77,7 @@ ROLE = {
"timeSyncTask": "墙钟时间同步",
"adcTask": "TPAFE5160 采样(TIM3 1kHz 事件驱动 + 原子快照 + STM32 ADC DMA,纯消费者)",
"iec104Task": "IEC 60870-5-104 上行(工厂默认 :2404 监听,遥信/遥测/定值区)",
"protectTask": "保护判据 20ms 节拍(保护判据元件 + 采样故障闭锁,B5)",
"protectTask": "保护判据 20ms 节拍(保护判据元件 + 采样故障闭锁)",
}
+4 -4
View File
@@ -1,15 +1,15 @@
{
"built_at": "2026-10-30T18:28:23+08:00",
"fingerprint": "80504df8fdee0a2565df3636eebbb366c615866c93f41e27e8940521fe566f0c",
"built_at": "2026-11-02T14:32:47+08:00",
"fingerprint": "c5294354ba3e05e51882676c7edddf995852d114c6f6413016e49adc7f6ac2a6",
"fingerprint_files": 1466,
"fingerprint_source": "git-index",
"gated_regions": [
"RW_IRAM1",
"RW_CCM"
],
"git_head": "51ce59006be80de59d312df97dce9ed78b0eeaea",
"git_head": "e63abb919a5934511ffc7e8fea2c5b86dbfc70fc",
"map": "MDK-ARM/STM32F407-Demo/STM32F407-Demo.map",
"map_mtime": 1793356087,
"map_mtime": 1793600487,
"pct": {
"ER_IROM1": 26.37,
"RW_CCM": 87.89,
+1 -1
View File
@@ -809,7 +809,7 @@ int net_get_errno(void) {
/*
* 函数功能:将点分十进制 IP 字符串转为网络字节序 32 位地址
* 入口参数:cp - 点分十进制字符串(如 "192.168.1.100")
* 入口参数:cp - 点分十进制字符串
* 出口参数:无
* 返回值:网络字节序 IP(参数无效返回 0)
* 限定条件:无
+2 -2
View File
@@ -7,7 +7,7 @@
; ============================================================
[network]
ip=192.168.1.100
ip=192.168.31.100
mask=255.255.255.0
gw=192.168.1.1
gw=192.168.31.1
mac=00:00:00:00:00:00
+38 -9
View File
@@ -35,6 +35,35 @@ from pathlib import Path
EXE = Path(__file__).resolve().parents[1] / "build" / "Debug" / "lvgl_sim.exe"
REF_DIR = Path(__file__).resolve().parent / "ref"
# 工厂默认 = 黄金参考的真源:Drivers/BSP/NETCFG/net_cfg.c 的 NET_CFG_DEF_* 宏。
# run_sim 一律在空临时目录跑(无 ini → 模拟器按编译期宏显示),故断言值必须与该宏
# 一致;这里现读现算,改默认 IP 只动 C 宏,本脚本零同步(2026-11-02 教训:本文件
# 曾钉死 7 处补零字面量,IP 迁移时逐处爆红)。
_NETCFG_C = Path(__file__).resolve().parents[2] / "Drivers" / "BSP" / "NETCFG" / "net_cfg.c"
def _read_net_cfg_default(name):
"""抽取 `#define name { a, b, c, d }` 四字节(容忍 u 后缀/任意空白)。
解析失败立即报错——绝不回退到写死旧值(宁可红在明处)。
"""
pat = r"#define\s+%s\s*\{\s*(\d+)u?\s*,\s*(\d+)u?\s*,\s*(\d+)u?\s*,\s*(\d+)u?\s*\}" % name
m = re.search(pat, _NETCFG_C.read_text(encoding="utf-8-sig"))
if not m:
raise RuntimeError("net_cfg.c 的 %s 解析失败:宏形态变了就同步本函数的正则(勿改回写死值)" % name)
return tuple(int(g) for g in m.groups())
def _fmt_ip03(octets):
"""net_cfg_fmt_ipv4 的既有口径:后三段补零成 3 位、首段原样(同一格式化真源)。"""
return "%d.%03d.%03d.%03d" % octets
EXP_IP = _fmt_ip03(_read_net_cfg_default("NET_CFG_DEF_IP"))
EXP_MASK = _fmt_ip03(_read_net_cfg_default("NET_CFG_DEF_MASK"))
EXP_GW = _fmt_ip03(_read_net_cfg_default("NET_CFG_DEF_GW"))
EXP_IP_TAIL = EXP_IP.split(".", 1)[1] # 编辑态首字节被光标反显覆盖,只断言尾部
# (页名, 注入键序):与 ui_menu.c 节点映射/路由一致(u=上 d=下 l=左 r=右 e=回车 s=ESC);
# 密码页 = 受保护叶子(参数定值/整定/参数 node 8,默认密码 123)
PAGES = [
@@ -316,7 +345,7 @@ TEXTS = {
("r5名", "MAC地址", (103, 116), False),
("r5值", "00:00:00:00:00:00", (103, 116), False),
# net_cfg_fmt_ipv4 的既有口径是三段补零(与网口设置页同一格式化真源),照实断言
("r6名", "IP地址", (116, 129), False), ("r6值", "192.168.001.100", (116, 129), False),
("r6名", "IP地址", (116, 129), False), ("r6值", EXP_IP, (116, 129), False),
("r7名", "运行时长", (129, 142), False), ("r7值", "00:00:00", (129, 142), False),
("页脚", "第 1 页,共 1 页", (140, 160), False),
],
@@ -480,18 +509,18 @@ TEXTS = {
],
"netcfg": [
("标题", "网口设置", (0, 24), False),
("r0名", "IP地址", (24, 38), True), ("r0值", "192.168.001.100", (24, 38), True),
("r1名", "子网掩码", (38, 52), False), ("r1值", "255.255.255.000", (38, 52), False),
("r2名", "默认网关", (52, 66), False), ("r2值", "192.168.001.001", (52, 66), False),
("r0名", "IP地址", (24, 38), True), ("r0值", EXP_IP, (24, 38), True),
("r1名", "子网掩码", (38, 52), False), ("r1值", EXP_MASK, (38, 52), False),
("r2名", "默认网关", (52, 66), False), ("r2值", EXP_GW, (52, 66), False),
("r3名", "MAC地址", (66, 80), False), ("r3值", "00:00:00:00:00:00", (66, 80), False),
("r4名", "保存", (80, 95), False), ("r4值", "ENTER", (80, 95), False),
("页脚", "第 1 页,共 1 页", (140, 160), False),
],
"netcfg_ro": [
("标题", "网口设置", (0, 24), False),
("r0名", "IP地址", (24, 38), True), ("r0值", "192.168.001.100", (24, 38), True),
("r1名", "子网掩码", (38, 52), False), ("r1值", "255.255.255.000", (38, 52), False),
("r2名", "默认网关", (52, 66), False), ("r2值", "192.168.001.001", (52, 66), False),
("r0名", "IP地址", (24, 38), True), ("r0值", EXP_IP, (24, 38), True),
("r1名", "子网掩码", (38, 52), False), ("r1值", EXP_MASK, (38, 52), False),
("r2名", "默认网关", (52, 66), False), ("r2值", EXP_GW, (52, 66), False),
("r3名", "MAC地址", (66, 80), False), ("r3值", "00:00:00:00:00:00", (66, 80), False),
("r4名", "保存", (80, 95), False), ("r4值", "ENTER", (80, 95), False),
("页脚", "第 1 页,共 1 页", (140, 160), False),
@@ -499,7 +528,7 @@ TEXTS = {
"netcfg_edit": [
("标题", "网口设置", (0, 24), False),
("r0名", "IP地址", (24, 38), True),
("r0值尾", "168.001.100", (24, 38), True), # 首字节 192 被光标反显覆盖
("r0值尾", EXP_IP_TAIL, (24, 38), True), # 首字节(192 那段)被光标反显覆盖
("r1名", "子网掩码", (38, 52), False),
("r4名", "保存", (80, 95), False),
],
@@ -578,7 +607,7 @@ TEXTS = {
],
"netcfg_save": [
("标题", "网口设置", (0, 24), False),
("r0名", "IP地址", (24, 38), False), ("r0值", "192.168.001.100", (24, 38), False),
("r0名", "IP地址", (24, 38), False), ("r0值", EXP_IP, (24, 38), False),
("r3名", "MAC地址", (66, 80), False),
("r4名", "保存", (80, 95), True), ("r4值", "OK", (80, 95), True),
],
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+23 -20
View File
@@ -122,41 +122,44 @@ static void phase1_run(void) {
* 原理:ch395f_set_ip_addr() 将 IP 写入 CH395F 内部寄存器,
* 然后通过 ch395f_get_ip_inf() 回读完整网络信息(IP+GW+MASK)。
* 前 4 字节为 IP 地址。
* 通过:回读 IP[0..3] == 192.168.1.100(与写入值一致)。
* 通过:回读 IP[0..3] == 写入的 set_ip(回读与写入一致;值本身任意合法,
* 不校验工厂默认——工厂默认断言在 net_cfg 主机单测里钉)。
* 失败:任意字节不匹配(SPI 写入/回读路径异常或寄存器未更新)。
*/
{
uint8_t set_ip[4] = {192, 168, 1, 100};
uint8_t set_ip[4] = {192, 168, 31, 100};
ch395f_set_ip_addr(set_ip);
memset(ip_info, 0, sizeof(ip_info));
ch395f_get_ip_inf(ip_info);
int32_t match = (ip_info[0] == 192 && ip_info[1] == 168 &&
ip_info[2] == 1 && ip_info[3] == 100);
DBG_INFO(" ch395f_set_ip_addr(192.168.1.100)");
DBG_INFO(" expect: IP[0..3] = 192.168.1.100");
int32_t match = (memcmp(ip_info, set_ip, sizeof(set_ip)) == 0);
DBG_INFO(" ch395f_set_ip_addr(%d.%d.%d.%d)",
set_ip[0], set_ip[1], set_ip[2], set_ip[3]);
DBG_INFO(" expect: IP[0..3] = 写入值(上一行)");
DBG_INFO(" actual: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]);
TEST_CHECK(match, "IP = %d.%d.%d.%d (expect 192.168.1.100)",
ip_info[0], ip_info[1], ip_info[2], ip_info[3]);
TEST_CHECK(match, "IP = %d.%d.%d.%d (expect %d.%d.%d.%d)",
ip_info[0], ip_info[1], ip_info[2], ip_info[3],
set_ip[0], set_ip[1], set_ip[2], set_ip[3]);
}
/* ---- 设置网关 → 回读验证 ----
* 原理:同「设置 IP → 回读」项,通过 ch395f_set_gwip_addr() 写入,ch395f_get_ip_inf()
* 回读的第 5-8 字节为网关地址。
* 通过:回读 GW[0..3] == 192.168.1.1。
* 通过:回读 GW[0..3] == 写入的 set_gw(同「设置 IP → 回读」项,值任意合法)。
* 失败:任意字节不匹配。
*/
{
uint8_t set_gw[4] = {192, 168, 1, 1};
uint8_t set_gw[4] = {192, 168, 31, 1};
ch395f_set_gwip_addr(set_gw);
memset(ip_info, 0, sizeof(ip_info));
ch395f_get_ip_inf(ip_info);
int32_t match = (ip_info[4] == 192 && ip_info[5] == 168 &&
ip_info[6] == 1 && ip_info[7] == 1);
DBG_INFO(" ch395f_set_gwip_addr(192.168.1.1)");
DBG_INFO(" expect: GW[0..3] = 192.168.1.1");
int32_t match = (memcmp(ip_info + 4, set_gw, sizeof(set_gw)) == 0);
DBG_INFO(" ch395f_set_gwip_addr(%d.%d.%d.%d)",
set_gw[0], set_gw[1], set_gw[2], set_gw[3]);
DBG_INFO(" expect: GW[0..3] = 写入值(上一行)");
DBG_INFO(" actual: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]);
TEST_CHECK(match, "GW = %d.%d.%d.%d (expect 192.168.1.1)",
ip_info[4], ip_info[5], ip_info[6], ip_info[7]);
TEST_CHECK(match, "GW = %d.%d.%d.%d (expect %d.%d.%d.%d)",
ip_info[4], ip_info[5], ip_info[6], ip_info[7],
set_gw[0], set_gw[1], set_gw[2], set_gw[3]);
}
/* ---- 设置掩码 → 回读验证 ----
@@ -269,7 +272,7 @@ static void phase1_run(void) {
#ifdef ENABLE_PHASE2_TESTS
#ifndef PHASE2_REMOTE_IP
#define PHASE2_REMOTE_IP "192.168.1.2"
#define PHASE2_REMOTE_IP TEST_PC_PEER_IP
#endif
#ifndef PHASE2_REMOTE_PORT
#define PHASE2_REMOTE_PORT 8081
@@ -854,7 +857,7 @@ static void phase2_tc204(void)
/* TC-NET-203(硬件层):TCP Client 连接超时(负向测试,P1)
*
* 前置环境:PC 未在 192.168.1.2:8081 提供服务(与 TC-NET-201/202 互斥,
* 前置环境:PC 未在 PHASE2_REMOTE_IP:8081 提供服务(与 TC-NET-201/202 互斥,
* 需先关闭 PC 端 tcp_server)。固件自行辨别环境错误:
* 若等待期间误收到 CONNECT,说明 PC 服务未关,判 FAIL 并给出指引。
*
@@ -1027,7 +1030,7 @@ static void phase2_run(void)
* 4. 向对应源地址回显数据
* 5. SENDBUF_FREE 中断等待(确保发送缓冲可用)
*
* 前置条件:PC 运行 udp_client 发包到 192.168.1.100:60000
* 前置条件:PC 运行 udp_client 发包到板 IP:60000(板 IP = config.ini [network] 设置值)
* 会话结构:累计 PHASE3_TOTAL_ROUNDS(60) 个有效数据包后关闭 Socket
* 第 1~30 轮 = TC-NET-301(PC 发 64B);第 31~60 轮 = TC-NET-302(1B/64B/200B)
* "HELLO"(5B) 为 PC 就绪探针:照常回显但不计入轮次
@@ -1317,7 +1320,7 @@ phase4_end:
/* 回传参数(TC-NET-502 合规测试时按档位修改重编译) */
#ifndef PHASE5_REMOTE_IP
#define PHASE5_REMOTE_IP "192.168.1.2"
#define PHASE5_REMOTE_IP TEST_PC_PEER_IP
#endif
#ifndef PHASE5_REMOTE_PORT
#define PHASE5_REMOTE_PORT 8081 /* PC tcp_server 监听端口 */
+2 -2
View File
@@ -112,7 +112,7 @@ static void phase6_run(int32_t sockfd) {
*
* 测试目的:验证 BSD Socket API 的 TCP Client 路径(net_socket + net_bind
* + net_connect + net_send + net_recv):
* 1. MCU 主动连接 PC 回显服务器(192.168.1.2:8080)
* 1. MCU 主动连接 PC 回显服务器(默认 PHASE7_REMOTE_IP:8080)
* 2. 64B 递增数据回显后,固件侧逐字节比对 + 32 位累加和校验
* 3. 关闭并重新连接(规避 TIME_WAIT:每轮换本地端口)
* 4. 连接超时负向用例(PC 服务关闭时 connect 不应成功)
@@ -128,7 +128,7 @@ static void phase6_run(int32_t sockfd) {
#include "net_select.h"
#ifndef PHASE7_REMOTE_IP
#define PHASE7_REMOTE_IP "192.168.1.2"
#define PHASE7_REMOTE_IP TEST_PC_PEER_IP
#endif
#ifndef PHASE7_REMOTE_PORT
#define PHASE7_REMOTE_PORT 8081
+4
View File
@@ -5,6 +5,10 @@
extern "C" {
#endif
/* 台架 PC 对端 IP 单一真源(PHASE2/5/7_REMOTE_IP 的缺省值;改台架只动这里,
* 个别构建也可在编译行 -DPHASE*_REMOTE_IP="..." 临时覆盖)。 */
#define TEST_PC_PEER_IP "192.168.31.2"
/* NET 层测试事件循环入口(定义于 net_test_task.c)。
* 由 testTask 在 NET 层构建模式下调用,内部创建单连接 TCP 监听
* 并驱动 Phase 6/8~11 依次执行,连接断开后自动重新 listen。 */
+19
View File
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
"""台架连接默认值单一真源(test/host/*.py 直接 `import bench_defaults`)。
2026-11-02 设备 IP 由 192.168.1.100 迁到 192.168.31.100 时,25+ 个脚本的
argparse 缺省值/模块常量逐处爆红——收口到本文件后,换台架只改这里。
对应关系(同一事实一处详述,勿在别处复制值):
- 设备 IP/网关:编译期工厂默认在 Drivers/BSP/NETCFG/net_cfg.c 的 NET_CFG_DEF_*,
本文件是**台架脚本侧**的连接缺省,两者当前一致但语义不同(工厂默认 vs 台架约定)。
- PC 对端 IP:板测固件侧单一真源是 test/board/net_test_task.h 的 TEST_PC_PEER_IP,
本文件 PC_IP 与其保持同值(改台架两处都要动——C 与 Python 无共享编译单元)。
"""
DEVICE_IP = "192.168.31.100" # DTU 板子(台架标准地址,用户裁定 2026-11-02)
DEVICE_GW = "192.168.31.1" # 台架网关(PC 热点/路由侧)
PC_IP = "192.168.31.2" # 台架 PC 自身网卡地址(= TEST_PC_PEER_IP 同值)
# /24 子网定向广播(从 DEVICE_IP 现算,不再单独钉一个 .255 字面量)
NET_BCAST = DEVICE_IP.rsplit(".", 1)[0] + ".255"
+2 -2
View File
@@ -474,8 +474,8 @@ static void tc_ip4(void) {
CHECK(s_decode_ip4(d, "0.0.0.0") == 0 && d[0] == 0u && d[3] == 0u, "all-zero");
CHECK(s_decode_ip4(d, "255.255.255.255") == 0 &&
d[0] == 255u && d[3] == 255u, "all-255");
CHECK(s_decode_ip4(d, "192.168.1.100") == 0 &&
d[0] == 192u && d[1] == 168u && d[2] == 1u && d[3] == 100u, "typical");
CHECK(s_decode_ip4(d, "192.168.31.100") == 0 &&
d[0] == 192u && d[1] == 168u && d[2] == 31u && d[3] == 100u, "typical");
CHECK(s_decode_ip4(d, "1.2.3") == -1, "too few octets rejected");
CHECK(s_decode_ip4(d, "1.2.3.4.5") == -1, "too many octets rejected");
CHECK(s_decode_ip4(d, "256.1.1.1") == -1, "octet >255 rejected");
@@ -268,9 +268,9 @@ static void tc_read(void)
/* 001 成功 + 只读模式 */
reset_all();
(void)ff_fake_mkfile(P_CFG, (const uint8_t *)"net ip=192.168.1.100\n", 20u);
ok = (cfg_store_read(P_CFG, buf, sizeof(buf), &n) == 0) && (n == 20u)
&& (memcmp(buf, "net ip=192.168.1.100\n", 20u) == 0)
(void)ff_fake_mkfile(P_CFG, (const uint8_t *)"net ip=192.168.31.100\n", 21u);
ok = (cfg_store_read(P_CFG, buf, sizeof(buf), &n) == 0) && (n == 21u)
&& (memcmp(buf, "net ip=192.168.31.100\n", 21u) == 0)
&& (g_ff_obs.last_open_mode == (BYTE)FA_READ)
&& (s_lock_waits == 1) && (s_lock_releases == 1) && (s_lock_seq_err == 0);
CHECK(ok, "TC-CFGFST-001 read ok: content and length correct, opened with FA_READ only (no write bit)");
+3 -1
View File
@@ -11,6 +11,8 @@ import struct
import sys
import time
import bench_defaults # 台架连接默认值单一真源
# {idx: (tx_len, close_mode)} 与固件表一致
VARS = {
0: (100, 0), # 0ms control
@@ -125,7 +127,7 @@ def e4_round(host, rg, rounds_per_variant, first_budget):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="192.168.1.100")
ap.add_argument("--host", default=bench_defaults.DEVICE_IP)
ap.add_argument("--cycles", type=int, default=3)
ap.add_argument("--mode", choices=["legacy", "e4"], default="legacy")
ap.add_argument("--rounds", type=int, default=10, help="e4: rounds per variant")
+18 -16
View File
@@ -19,7 +19,7 @@ CH395F BSD Socket API 测试程序
tcp_file - 文件收发:PC 作 Client 连 MCU:8080(阶段 8),--op send/recv 收发 100KB 文件(含 CRC32)
选项:
--ip 目标 IP 地址(默认: 192.168.1.100)
--ip 目标 IP 地址(默认见 bench_defaults.DEVICE_IP)
--port 端口号(默认: 8080)
--count 测试次数(默认: 10)
--size 数据包大小(默认: 1024)
@@ -28,7 +28,7 @@ CH395F BSD Socket API 测试程序
示例:
# 测试 CH395F TCP Server
python ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080
python ch395f_socket_test.py tcp_client --port 8080
# 测试 CH395F TCP Client
python ch395f_socket_test.py tcp_server --port 8080
@@ -37,18 +37,18 @@ CH395F BSD Socket API 测试程序
python ch395f_socket_test.py udp_server --port 8888
# 测试 CH395F UDP Server
python ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 8888
python ch395f_socket_test.py udp_client --port 8888
# 压力测试(多连接)
python ch395f_socket_test.py stress --ip 192.168.1.100 --port 8080 --count 7
python ch395f_socket_test.py stress --port 8080 --count 7
# 阶段 5 双向大块(先收 256KB 再回放 256KB)
python ch395f_socket_test.py tcp_bulk --port 8081 --bulk 262144 --chunk 4096
# 阶段 8 文件收发(PC 连 MCU:8080)
python ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op recv --file rx_100k.bin --size 102400
python ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op send --file tx_100k.bin
python ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op echo # TC-NET-803 回显(MCU 为 server)
python ch395f_socket_test.py tcp_file --port 8080 --op recv --file rx_100k.bin --size 102400
python ch395f_socket_test.py tcp_file --port 8080 --op send --file tx_100k.bin
python ch395f_socket_test.py tcp_file --port 8080 --op echo # TC-NET-803 回显(MCU 为 server)
"""
import socket
@@ -63,6 +63,8 @@ import string
import zlib
from datetime import datetime
import bench_defaults # 台架连接默认值单一真源
# TCP 回显服务器(tcp_server 模式)参数
INTER_ACCEPT_TIMEOUT = 60 # 首个连接后,等待下一个连接的超时秒数
ECHO_IDLE_TIMEOUT = 30 # 连接内无数据的空闲超时秒数
@@ -963,7 +965,7 @@ def dhcp_server_test(args):
options)
# Try both global broadcast and subnet broadcast
for bcast in ['255.255.255.255', '192.168.1.255']:
for bcast in ['255.255.255.255', bench_defaults.NET_BCAST]:
sock.sendto(offer, (bcast, DHCP_CLIENT_PORT))
log_success("发送 DHCP OFFER")
@@ -994,7 +996,7 @@ def dhcp_server_test(args):
_ip_to_bytes(offer_ip),
_ip_to_bytes(server_ip),
ack_options)
for bcast in ['255.255.255.255', '192.168.1.255']:
for bcast in ['255.255.255.255', bench_defaults.NET_BCAST]:
sock.sendto(ack, (bcast, DHCP_CLIENT_PORT))
log_success(f"发送 DHCP ACK → {offer_ip}")
log_success("DHCP 分配完成!")
@@ -1016,7 +1018,7 @@ def dhcp_server_test(args):
_ip_to_bytes(offer_ip),
_ip_to_bytes(server_ip),
options)
for bcast in ['255.255.255.255', '192.168.1.255']:
for bcast in ['255.255.255.255', bench_defaults.NET_BCAST]:
sock.sendto(offer, (bcast, DHCP_CLIENT_PORT))
log_info(f"重发 DHCP OFFER (retry={retry+1})")
except socket.timeout:
@@ -1245,10 +1247,10 @@ def main():
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
%(prog)s tcp_client --ip 192.168.1.100 --port 8080
%(prog)s tcp_client --port 8080
%(prog)s tcp_server --port 8080
%(prog)s stress --ip 192.168.1.100 --port 8080 --count 5
%(prog)s dhcp_server --dhcp-iface 192.168.1.2 --ip 192.168.1.100
%(prog)s stress --port 8080 --count 5
%(prog)s dhcp_server --dhcp-iface <PC 网卡 IP,见 bench_defaults.PC_IP> --ip <设备 IP>
%(prog)s tcp_bulk --port 8081 --bulk 262144 --chunk 4096
"""
)
@@ -1257,7 +1259,7 @@ def main():
'tcp_echo', 'udp_echo', 'stress', 'packet_size',
'dhcp_server', 'tcp_bulk', 'tcp_file'],
help='测试模式')
parser.add_argument('--ip', default='192.168.1.100', help='目标 IP 地址 / DHCP 待分配 IP')
parser.add_argument('--ip', default=bench_defaults.DEVICE_IP, help='目标 IP 地址 / DHCP 待分配 IP')
parser.add_argument('--port', type=int, default=8080, help='端口号')
parser.add_argument('--count', type=int, default=10, help='测试次数')
parser.add_argument('--size', type=int, default=1024, help='数据包大小')
@@ -1268,9 +1270,9 @@ def main():
help='tcp_file 方向: send=读文件发往 MCU, recv=从 MCU 收文件落盘, echo=连 MCU 回显')
parser.add_argument('--file', default=None, help='tcp_file 文件路经(send: 读取; recv: 写入)')
parser.add_argument('--max-conn', type=int, default=0, help='最大并发连接数(默认0=不限;按并发计数,非累计,TC-NET-704 并发测试用 8)')
parser.add_argument('--dhcp-iface', default='192.168.1.2', help='DHCP 服务器网卡 IP')
parser.add_argument('--dhcp-iface', default=bench_defaults.PC_IP, help='DHCP 服务器网卡 IP')
parser.add_argument('--dhcp-mask', default='255.255.255.0', help='DHCP 分配的子网掩码')
parser.add_argument('--dhcp-gw', default='192.168.1.1', help='DHCP 分配的网关')
parser.add_argument('--dhcp-gw', default=bench_defaults.DEVICE_GW, help='DHCP 分配的网关')
args = parser.parse_args()
+3 -1
View File
@@ -27,7 +27,9 @@ import socket
import sys
import time
DEV = "192.168.1.100"
import bench_defaults # 台架连接默认值单一真源
DEV = bench_defaults.DEVICE_IP
tag = sys.argv[1] if len(sys.argv) > 1 else "run"
t0 = time.time()
+3 -1
View File
@@ -30,6 +30,8 @@ import subprocess
import sys
import time
import bench_defaults # 台架连接默认值单一真源
TAG_C, TAG_D = 0xAA, 0xBB
CMD_KEEPLIVE = 0
RULE_NAME = "SELFHEAL-E11-DROP"
@@ -88,7 +90,7 @@ def connect(host: str, port: int, timeout: float = 5.0):
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="192.168.1.100")
ap.add_argument("--host", default=bench_defaults.DEVICE_IP)
ap.add_argument("--port", type=int, default=7003)
ap.add_argument("--mode", choices=["idle", "block"], required=True)
ap.add_argument("--hold-s", type=float, default=90.0)
+3 -1
View File
@@ -19,6 +19,8 @@
import argparse, hashlib, io, random, re, socket, struct, sys, time
from ftplib import FTP, all_errors
import bench_defaults # 台架连接默认值单一真源
def log(m):
sys.stdout.write(m + "\n"); sys.stdout.flush()
@@ -66,7 +68,7 @@ def followup_ok(ip, port, size=4096):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--ip", default="192.168.1.100")
ap.add_argument("--ip", default=bench_defaults.DEVICE_IP)
ap.add_argument("--port", type=int, default=21)
ap.add_argument("--rounds", type=int, default=10)
ap.add_argument("--send", type=int, default=8192, help="bytes sent before the RST")
+4 -2
View File
@@ -16,7 +16,7 @@
注意:CH395F 单连接——每步用完必须 quit() 释放 socket,否则下一步连不上;
跑之前确保 UI 远程显示(7003)/对时(8888)客户端未占用 socket。
用法(设备默认 192.168.1.100,出厂口令 admin/admin):
用法(设备默认见 bench_defaults.DEVICE_IP,出厂口令 admin/admin):
& '.venv\\Scripts\\python.exe' test/host/ftp_auth_bench.py
改过口令: ... --pwd <新口令>
跳过 60s 锁定等待(只验锁定触发不验解锁): ... --skip-unlock
@@ -32,6 +32,8 @@ import sys
import time
from ftplib import FTP, error_perm
import bench_defaults # 台架连接默认值单一真源
LOCK_WAIT_S = 62 # 固件锁定 60 s + 余量
@@ -66,7 +68,7 @@ def _try_login(host, port, timeout, user, pwd):
def main(argv=None):
ap = argparse.ArgumentParser(description="FTP auth bench matrix")
ap.add_argument("--host", default="192.168.1.100")
ap.add_argument("--host", default=bench_defaults.DEVICE_IP)
ap.add_argument("--port", type=int, default=21)
ap.add_argument("--timeout", type=int, default=15)
ap.add_argument("--user", default="admin")
+3 -1
View File
@@ -26,6 +26,8 @@ import sys
import time
from ftplib import FTP, error_perm
import bench_defaults # 台架连接默认值单一真源
NAME = "e12a.bin" # 8.3 短名(FatFs FF_USE_LFN=0)
@@ -45,7 +47,7 @@ def first_diff(a: bytes, b: bytes):
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="192.168.1.100")
ap.add_argument("--host", default=bench_defaults.DEVICE_IP)
ap.add_argument("--port", type=int, default=21)
ap.add_argument("--user", default="admin")
ap.add_argument("--pwd", default="admin")
+4 -2
View File
@@ -16,7 +16,7 @@ break 并回 -1,cmd_list 据此回 **550**——目录一大,客户端拿到
3. LIST 的完成回包是 226 而不是 550(ftplib 在非 2xx 完成时会抛异常,正是这条)
清理:无论成败都逐个 DELE;删除失败会打 WARN(不影响判定,但会留残留文件要手清)。
用法:python ftp_list_fill_test.py [--host 192.168.1.100] [--files 26]
用法:python ftp_list_fill_test.py [--host <设备IP>] [--files 26]
[--user admin] [--password admin]
注:--files 26 是按每行约 63 B 反推的下限(26×63 ≈ 1638 > 1536);目录本来就有
条目时更小也能触发,取 26 保证空目录也能越过一块。
@@ -27,7 +27,9 @@ import io
import sys
from ftplib import FTP, all_errors
HOST = "192.168.1.100"
import bench_defaults # 台架连接默认值单一真源
HOST = bench_defaults.DEVICE_IP
PORT = 21
# 设备无匿名通道:账号取 /config.ini 的 [ftp],出厂默认 admin/admin
USER = "admin"
+4 -2
View File
@@ -12,7 +12,7 @@ net 层当作"刚建立就断开"释放。修复见 net_socket.c handle_disconne
内容校验;任一环节失败即退出码 1。
用法:
& '.venv\\Scripts\\python.exe' test/host/ftp_regression.py --host 192.168.1.100
& '.venv\\Scripts\\python.exe' test/host/ftp_regression.py --host <设备IP>
"""
import argparse
@@ -21,13 +21,15 @@ import sys
import time
from ftplib import FTP
import bench_defaults # 台架连接默认值单一真源
SMALL = b"D20-small-" + b"A" * 1017 # 1024B
BIG = bytes(((i * 7 + 3) & 0xFF) for i in range(32768)) # 32KB 模式数据
def main(argv=None):
ap = argparse.ArgumentParser(description="FTP data-connection regression (D20)")
ap.add_argument("--host", default="192.168.1.100", help="device IP")
ap.add_argument("--host", default=bench_defaults.DEVICE_IP, help="device IP")
ap.add_argument("--port", type=int, default=21, help="FTP control port")
ap.add_argument("--timeout", type=int, default=20, help="socket timeout (s)")
# 2026-10-11 起 FTP 强制鉴权:匿名登录被 530 拒;默认口令 admin/admin
+4 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
FTP 速度测试 — 上传/下载大文件,测量传输速度
用法:python ftp_speed_test.py --host 192.168.1.100 [--size 512] [--port 21]
用法:python ftp_speed_test.py --host <设备IP> [--size 512] [--port 21]
[--user admin] [--password admin]
"""
@@ -12,7 +12,9 @@ import re
import argparse
import os
HOST = "192.168.1.100"
import bench_defaults # 台架连接默认值单一真源
HOST = bench_defaults.DEVICE_IP
PORT = 21
# 设备侧 FTP 鉴权走 /config.ini 的 [ftp](user / pwd_hash),出厂默认 admin/admin;
# **没有匿名通道**(USER anonymous 一律回 530)。
+4 -2
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
"""
FTP Server 测试脚本 — 连接 STM32F4 上的 lftpd 服务器
用法:python ftp_test.py [--host 192.168.1.100] [--port 21] [--user admin] [--password admin]
用法:python ftp_test.py [--host <设备IP>] [--port 21] [--user admin] [--password admin]
"""
import socket
@@ -10,7 +10,9 @@ import sys
import time
import argparse
HOST = "192.168.1.100"
import bench_defaults # 台架连接默认值单一真源
HOST = bench_defaults.DEVICE_IP
PORT = 21
# 设备侧 FTP 鉴权走 /config.ini 的 [ftp](user / pwd_hash),出厂默认 admin/admin;
# **没有匿名通道**(USER anonymous 一律回 530)。
+4 -2
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
"""
FTP 上传+下载测试脚本
用法:python ftp_up_dl_test.py --host 192.168.1.100 [--user admin] [--password admin]
用法:python ftp_up_dl_test.py --host <设备IP> [--user admin] [--password admin]
"""
import socket
@@ -12,7 +12,9 @@ import re
import argparse
import os
HOST = "192.168.1.100"
import bench_defaults # 台架连接默认值单一真源
HOST = bench_defaults.DEVICE_IP
PORT = 21
# 设备侧 FTP 鉴权走 /config.ini 的 [ftp](user / pwd_hash = SHA-256(user‖password)),
# 出厂默认 admin/admin;**没有匿名通道**(USER anonymous 一律回 530)。
+4 -2
View File
@@ -30,7 +30,7 @@ round-trip log as bench evidence).
RX decode fix (2026-10-25, task-21/m104fix): cause-byte semantics above;
offline sample-frame selftest: python test/host/test_iec104_master_rx.py
Usage: python iec104_master.py [--ip 192.168.1.100] [--port 2404]
Usage: python iec104_master.py [--ip <设备IP>] [--port 2404]
[--addr 0x8858] [--low 0.05]
[--restore 10.0]
@@ -49,6 +49,8 @@ bench leg, firmware side App/cfg/prot_set_store.c):
"""
import argparse
import collections
import bench_defaults # 台架连接默认值单一真源
import socket
import struct
import sys
@@ -469,7 +471,7 @@ def b7_check(m, ck, args):
def main():
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("--ip", default="192.168.1.100")
ap.add_argument("--ip", default=bench_defaults.DEVICE_IP)
ap.add_argument("--port", type=int, default=2404)
ap.add_argument("--addr", default="0x8858",
help="setpoint comm addr to drive COV (OC1 current)")
+4 -2
View File
@@ -18,7 +18,7 @@ per-batch arrival curve / >=500ms spikes (TCP retransmission fingerprint).
Both the serial log and this JSON carry HOST timestamps -> direct correlation.
Usage:
python latency_peer.py --host 192.168.1.100 --port 8080 --json m2_net.json
python latency_peer.py --port 8080 --json m2_net.json
python latency_peer.py --mode net --ftp-stress ... (head-of-line repro)
Pure stdlib (socket/json/time/argparse/threading). ASCII output only.
@@ -31,6 +31,8 @@ import sys
import threading
import time
import bench_defaults # 台架连接默认值单一真源
# --- Protocol constants (mirror test/board/net_latency_test.c Phase 13 defines) ---
HELLO = b"\x5a\xa5\x58\x01"
OP_QUEUE_RT_RECV = 0x01
@@ -783,7 +785,7 @@ def main(argv=None):
ap.add_argument("--mode", default="net", choices=["net", "remo"],
help="net = Phase 13 test protocol :8080; "
"remo = production RemoDispBus client :7003 (Phase 14 / simulator)")
ap.add_argument("--host", default="192.168.1.100", help="device IP")
ap.add_argument("--host", default=bench_defaults.DEVICE_IP, help="device IP")
ap.add_argument("--port", type=int, default=None,
help="listen port (default: 8080 for net, 7003 for remo)")
ap.add_argument("--wait", type=int, default=180,
+2 -2
View File
@@ -346,7 +346,7 @@ int net_getsockname(int sockfd, struct net_sockaddr *addr, int *addrlen)
(void)sockfd;
(void)addrlen;
a->sin_addr.s_addr = 0x0101A8C0u; /* 192.168.1.1 的宿主序表示(假件自定) */
a->sin_addr.s_addr = 0x011FA8C0u; /* 192.168.31.1 的宿主序表示(假件自定) */
a->sin_port = net_htons(21u);
return g_sockname_rc;
}
@@ -512,7 +512,7 @@ static void tc_passive(void)
/* 直调处理器:走 handle_control_channel 的话,会话退出时 cleanup 一定把被动监听
关掉(那正是 TC-LFTPD-006 要验的行为),这里要看的是 PASV 自己留下的状态。 */
(void)cmd_pasv(&g_cli, NULL);
CHECK((strcmp(rep(0), "227 Entering Passive Mode (192,168,1,1,19,136).\r\n") == 0) &&
CHECK((strcmp(rep(0), "227 Entering Passive Mode (192,168,31,1,19,136).\r\n") == 0) &&
(g_cli.data_socket == 20) && (g_n.listen_calls == 1),
"TC-LFTPD-011 the 227 tuple: ip through htonl then high byte to low byte (dotted order), port high/low, and the passive listen handle is recorded");
+3 -1
View File
@@ -6,10 +6,12 @@ import io
import sys
from ftplib import FTP, error_temp, error_perm
import bench_defaults # 台架连接默认值单一真源
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="192.168.1.100")
ap.add_argument("--host", default=bench_defaults.DEVICE_IP)
ap.add_argument("--port", type=int, default=21)
ap.add_argument("--user", default=None, help="None=anonymous")
ap.add_argument("--pwd", default=None)
+6 -6
View File
@@ -32,9 +32,9 @@
*
* 覆盖点:
* —— 出厂默认(load 之前也必须可用,TC-NETCFG-001..004)——
* TC-NETCFG-001 未 load 时 ip = 工厂默认 192.168.1.100
* TC-NETCFG-001 未 load 时 ip = 工厂默认 192.168.31.100
* TC-NETCFG-002 未 load 时 mask = 255.255.255.0
* TC-NETCFG-003 未 load 时 gw = 192.168.1.1
* TC-NETCFG-003 未 load 时 gw = 192.168.31.1
* TC-NETCFG-004 未 load 时 mac = 全零(哨兵 = 不覆盖芯片内置 MAC)
* —— 注册与 load(TC-NETCFG-005..011)——
* TC-NETCFG-005 首次 load 注册 [network] 段恰好一次(且 load 返回 0)
@@ -257,15 +257,15 @@ static void save_hook_probe(void)
static void tc_defaults(void)
{
const net_cfg_t *p = net_cfg_get();
static const uint8_t def_ip[4] = { 192u, 168u, 1u, 100u };
static const uint8_t def_ip[4] = { 192u, 168u, 31u, 100u };
static const uint8_t def_mask[4] = { 255u, 255u, 255u, 0u };
static const uint8_t def_gw[4] = { 192u, 168u, 1u, 1u };
static const uint8_t def_gw[4] = { 192u, 168u, 31u, 1u };
static const uint8_t zero[6] = { 0u, 0u, 0u, 0u, 0u, 0u };
printf("-- TC-NETCFG-001..004 defaults before load --\n");
CHECK(bytes_eq(p->ip, def_ip, 4u), "TC-NETCFG-001 ip = 192.168.1.100");
CHECK(bytes_eq(p->ip, def_ip, 4u), "TC-NETCFG-001 ip = 192.168.31.100");
CHECK(bytes_eq(p->mask, def_mask, 4u), "TC-NETCFG-002 mask = 255.255.255.0");
CHECK(bytes_eq(p->gw, def_gw, 4u), "TC-NETCFG-003 gw = 192.168.1.1");
CHECK(bytes_eq(p->gw, def_gw, 4u), "TC-NETCFG-003 gw = 192.168.31.1");
CHECK(mac_eq(p->mac, zero), "TC-NETCFG-004 mac = all zero (chip built-in sentinel)");
}
+3 -3
View File
@@ -209,13 +209,13 @@ static void tc_connect(void)
printf("-- TC-NETEV-001..006 CONNECT --\n");
reset_all();
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
ch395f_fake_set_peer(0, 192u, 168u, 1u, 50u, 51234u);
ch395f_fake_set_peer(0, 192u, 168u, 31u, 50u, 51234u);
handle_connect_event(p);
/* remote_ip 是**低字节在前**拼的(remote[0] 落在最低 8 位),与网络序相反:
* 192.168.1.50 -> 0x3201A8C0。这是既有实现的字节序,钉住它是防止有人"顺手
* 192.168.31.50 -> 0x321FA8C0。这是既有实现的字节序,钉住它是防止有人"顺手
* 改成大端"导致所有按 IP 比对/白名单的判断静默错位。 */
CHECK((p->remote_ip_arr[0] == 192u) && (p->remote_ip_arr[3] == 50u) &&
(p->remote_ip == ((192u) | (168u << 8) | (1u << 16) | (50u << 24))) &&
(p->remote_ip == ((192u) | (168u << 8) | (31u << 16) | (50u << 24))) &&
(p->remote_port == 51234u),
"TC-NETEV-001 peer tuple parsing: ip assembled little-endian into 32 bits, port from bytes 5/6");
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->session_dead == 0u) &&
+15 -15
View File
@@ -121,7 +121,7 @@ static void bring_up(void)
* 调用的(App/task/net_task.c:92),而本层执行任务句柄(UDP 上下文守卫的判据)
* 正是在这里捕获——这里若不换身份,整个 UDP 守卫就只能测到"自己判自己" */
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
(void)net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
g_peers.pending_capture_calls = 0; /* bring_up 内部的登记动作不该算进场景 */
g_ns_error_count = 0;
@@ -160,13 +160,13 @@ static int mk_sock(int type, net_sock_state_t state)
/* TC-NETSOCK-100 点分十进制 ↔ 32 位:**正向断言**(字节序统一修复后)
* 全仓唯一约定:s_addr 是网络字节序整数,&s_addr 的第 0 个内存字节 == 点分第一段;
* 小端主机上 "192.168.1.100" 的数值 = 0x6401A8C0。本组断言的落点是「互逆」与
* 小端主机上 "192.168.31.100" 的数值 = 0x641FA8C0。本组断言的落点是「互逆」与
* 「同族可比」,不是某个中间数值——数值只是这条约定的一个投影。 */
static void tc_100_inet_helpers(void)
{
/* 往返不变量的样本集:两个全零/全一边界 + 三个非回文地址(非回文才能考出顺位) */
static const char *const k_ips[5] = {
"0.0.0.0", "255.255.255.255", "1.1.1.1", "192.168.1.100", "10.0.0.0"
"0.0.0.0", "255.255.255.255", "1.1.1.1", "192.168.31.100", "10.0.0.0"
};
char buf[16];
int i;
@@ -174,12 +174,12 @@ static void tc_100_inet_helpers(void)
reset_all();
CHECK(net_inet_addr(NULL) == 0U, "TC-NETSOCK-100a inet_addr(NULL) returns 0 (invalid)");
CHECK(net_inet_addr("192.168.1.100") == 0x6401A8C0U,
"TC-NETSOCK-100b inet_addr returns network-order int (LE value 0x6401A8C0 = bytes C0 A8 01 64)");
CHECK(net_inet_ntoa(net_inet_addr("192.168.1.100"), buf) == buf &&
strcmp(buf, "192.168.1.100") == 0,
CHECK(net_inet_addr("192.168.31.100") == 0x641FA8C0U,
"TC-NETSOCK-100b inet_addr returns network-order int (LE value 0x641FA8C0 = bytes C0 A8 1F 64)");
CHECK(net_inet_ntoa(net_inet_addr("192.168.31.100"), buf) == buf &&
strcmp(buf, "192.168.31.100") == 0,
"TC-NETSOCK-100c ntoa(inet_addr(s)) == s: the two are exact inverses");
CHECK(net_inet_ntoa(0x6401A8C0U, buf) == buf && strcmp(buf, "192.168.1.100") == 0,
CHECK(net_inet_ntoa(0x641FA8C0U, buf) == buf && strcmp(buf, "192.168.31.100") == 0,
"TC-NETSOCK-100d ntoa segment order is MSB-first (fixed by E13/TRAP-NET-21, unchanged here)");
CHECK(net_inet_ntoa(0U, NULL) == NULL, "TC-NETSOCK-100e ntoa(NULL buf) returns NULL without crashing");
CHECK(net_inet_addr("10.0.0.1") == 0x0100000AU,
@@ -237,8 +237,8 @@ static void tc_102_parse_ip(void)
(void)memset(a, 0xEE, sizeof(a));
parse_ip_string(NULL, a);
CHECK(a[0] == 0xEEU, "TC-NETSOCK-102a parse_ip_string(NULL) returns at once, target buffer untouched");
parse_ip_string("192.168.1.100", a);
CHECK(a[0] == 192U && a[1] == 168U && a[2] == 1U && a[3] == 100U,
parse_ip_string("192.168.31.100", a);
CHECK(a[0] == 192U && a[1] == 168U && a[2] == 31U && a[3] == 100U,
"TC-NETSOCK-102b parse_ip_string puts segment 0 at [0] (the order chip set_ip_addr wants)");
parse_ip_string("10.20.30.40", NULL); /* 只验不崩 */
CHECK(1, "TC-NETSOCK-102c parse_ip_string with NULL target does not crash");
@@ -333,7 +333,7 @@ static void tc_105_bind(void)
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(2404);
in.sin_addr.s_addr = net_inet_addr("192.168.1.100");
in.sin_addr.s_addr = net_inet_addr("192.168.31.100");
CHECK(net_bind(-1, (struct net_sockaddr *)&in, sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-105a out of range fd -> BADF");
@@ -442,7 +442,7 @@ static void tc_107_peer_and_localname(void)
int fd;
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
(void)net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL);
CHECK(net_getsockname(-1, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107a getsockname out of range -> BADF");
@@ -460,10 +460,10 @@ static void tc_107_peer_and_localname(void)
addrlen == (int)sizeof(in),
"TC-NETSOCK-107e fills family, port (network order) and length");
/* 钉**具体顺位**而不是二选一:getsockname 走的是「首段在最低字节」这一族
* (与 net_inet_ntoa、net_events 的 remote_ip 同族),所以 192.168.1.100 =
* 0x6401A8C0。net_inet_addr 返回的是另一族(首段在最高字节,0xC0A80164),
* (与 net_inet_ntoa、net_events 的 remote_ip 同族),所以 192.168.31.100 =
* 0x641FA8C0。net_inet_addr 返回的是另一族(首段在最高字节,0xC0A81F64),
* 两族不互逆——已作为疑似缺陷报出,这里刻意不写成「两个都行」。 */
CHECK(in.sin_addr.s_addr == 0x6401A8C0U,
CHECK(in.sin_addr.s_addr == 0x641FA8C0U,
"TC-NETSOCK-107f local IP byte order = first octet in low byte (ntoa family)");
addrlen = 1;
+12 -12
View File
@@ -33,7 +33,7 @@ static void tc_400_net_init_downlink(void)
/* 1) 芯片不在位:直接失败,且不得继续下发任何参数 */
reset_all();
g_fx.exist_rc = (int)CH395F_STATUS_NOT_DETECTED;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == -1,
CHECK(net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL) == -1,
"TC-NETSOCK-400a chip not present -> -1");
CHECK(g_fxchip.reset_calls == 0 && g_fxchip.init_calls == 0,
"TC-NETSOCK-400b when absent it must not reset or initialise (do not drag the error into the stack)");
@@ -42,21 +42,21 @@ static void tc_400_net_init_downlink(void)
/* 2) 协议栈初始化失败 */
reset_all();
g_fx.init_rc = (int)CH395F_STATUS_ERROR;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == -1,
CHECK(net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL) == -1,
"TC-NETSOCK-400d ch395f_init failure -> -1");
CHECK(g_fxchip.ping_calls == 0, "TC-NETSOCK-400e after an init failure PING must not be enabled (half-configured state)");
/* 3) 版本门控:低于 0x46 必须**拒置** bit3(SOCKET_CLOSE),退回芯片自动关断 */
reset_all();
g_fx.version = 0x45U;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == 0,
CHECK(net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL) == 0,
"TC-NETSOCK-400f a too-old version is not fatal: bring-up continues");
CHECK(g_fx.fun_para == 0U && g_ns_error_count == 1,
"TC-NETSOCK-400g ver<0x46 -> FUN_PARA actually sends 0 (bit3 refused) plus [ERR], never silently degraded");
/* 4) 版本够新:FUN_PARA=0x08;bit1(TCP_SERVER 多连接) 与 bit0(保留) 都必须为 0 */
reset_all();
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == 0,
CHECK(net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL) == 0,
"TC-NETSOCK-400h normal initialisation succeeds");
/* bit3=SOCKET_CLOSE 要置;bit1=TCP_SERVER 多连接(本项目单连接模型)与
* bit0=保留位都必须为 0——旧实现把 0x09 当「多 Socket 模式」下发,2026-10-11
@@ -76,7 +76,7 @@ static void tc_400_net_init_downlink(void)
"TC-NETSOCK-400l SET-class commands (MSS/KeepAlive) come before INIT_CH395 (manual hard requirement)");
/* 5) IP/掩码/网关三样都按点分顺位下发,并缓存本机 IP 供 getsockname 用 */
CHECK(g_fx.ip[0] == 192U && g_fx.ip[1] == 168U && g_fx.ip[2] == 1U &&
CHECK(g_fx.ip[0] == 192U && g_fx.ip[1] == 168U && g_fx.ip[2] == 31U &&
g_fx.ip[3] == 100U && g_fx.gw[3] == 1U && g_fx.mask[0] == 255U,
"TC-NETSOCK-400m IP, gateway and mask follow the parse_ip_string order (segment 0 lands at [0])");
CHECK(g_fxchip.set_dhcp_calls == 0 && g_fx.dhcp_enable == 0U,
@@ -92,10 +92,10 @@ static void tc_400_net_init_downlink(void)
/* 7) MAC:全零=用芯片内置,不下发;非零必须下发(写 EEPROM,须在 INIT 之前) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", mac_zero);
(void)net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", mac_zero);
CHECK(g_fxchip.set_mac_calls == 0, "TC-NETSOCK-400q all-zero MAC -> keep the chip built-in one, do not issue it");
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", mac_real);
(void)net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", mac_real);
CHECK(g_fxchip.set_mac_calls == 1 && g_fx.mac[0] == 0x02U && g_fx.mac[5] == 0x55U,
"TC-NETSOCK-400r a non-zero MAC is issued (device online identity stays consistent with the UI)");
CHECK(ch395f_fake_log_find('M') < ch395f_fake_log_find('n') ||
@@ -104,7 +104,7 @@ static void tc_400_net_init_downlink(void)
/* 8) 收发缓冲块布局:6 块/Socket,先配数据 Socket 再配监听 Socket 0(TRAP-NET-01) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
(void)net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL);
CHECK(g_fxchip.set_recv_buf_calls == NET_MAX_SOCKETS &&
g_fxchip.set_send_buf_calls == NET_MAX_SOCKETS,
"TC-NETSOCK-400t all 8 sockets are explicitly reallocated (power-on defaults only cover 0-3)");
@@ -122,7 +122,7 @@ static void tc_400_net_init_downlink(void)
/* 9) net_init 的第一件事是取消挂起并通知调用方(顺序错了 = 调用方永久挂死) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
(void)net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL);
CHECK(g_peers.pending_cancel_all_calls == 1 &&
g_peers.send_cancel_all_calls == 1,
"TC-NETSOCK-400w both pending tables are cancelled before a rebuild (recv and send each one, missing one strands a task)");
@@ -358,7 +358,7 @@ static void tc_430_connect(void)
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(8888);
in.sin_addr.s_addr = net_inet_addr("192.168.1.200");
in.sin_addr.s_addr = net_inet_addr("192.168.31.200");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
"TC-NETSOCK-430e connect command issued (asynchronous, the interrupt decides success)");
@@ -367,7 +367,7 @@ static void tc_430_connect(void)
CHECK(s_net_socks[fd].remote_port == 8888U,
"TC-NETSOCK-430g peer port stored in bookkeeping as host order");
CHECK(g_fx.des_ip[fd][0] == 192U && g_fx.des_ip[fd][1] == 168U &&
g_fx.des_ip[fd][2] == 1U && g_fx.des_ip[fd][3] == 200U,
g_fx.des_ip[fd][2] == 31U && g_fx.des_ip[fd][3] == 200U,
"TC-NETSOCK-430h destination IP issued as a big-endian array (the order set_des_ip needs, reversed means no link)");
CHECK(g_chip.set_port_calls == 1 && g_fx.des_port[fd] == 8888U,
"TC-NETSOCK-430i source port issued once (SET_SOUR_PORT), the peer port goes via SET_DES_PORT=8888");
@@ -455,7 +455,7 @@ static void tc_440_udp_datagram(void)
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(8888);
in.sin_addr.s_addr = net_inet_addr("192.168.1.9");
in.sin_addr.s_addr = net_inet_addr("192.168.31.9");
CHECK(net_sendto(fd, "hello", 5, 0, sa, (int)sizeof(in)) == 5,
"TC-NETSOCK-440f sendto returns the number of bytes sent");
CHECK(g_fx.des_ip[fd][3] == 9U && g_fx.des_port[fd] == 8888U,
+7 -7
View File
@@ -402,17 +402,17 @@ static void tc_550_family_roundtrip(void)
(void)memset(&in, 0, sizeof(in));
CHECK(net_getsockname(0, sa, &addrlen) == 0,
"TC-NETSOCK-550a getsockname precondition ok");
CHECK(in.sin_addr.s_addr == net_inet_addr("192.168.1.100"),
CHECK(in.sin_addr.s_addr == net_inet_addr("192.168.31.100"),
"TC-NETSOCK-550b getsockname s_addr is the same family as inet_addr (direct equality)");
CHECK(strcmp(net_inet_ntoa(in.sin_addr.s_addr, txt), "192.168.1.100") == 0,
"TC-NETSOCK-550c local address closes parse-store-print (old code printed 100.1.168.192)");
CHECK(strcmp(net_inet_ntoa(in.sin_addr.s_addr, txt), "192.168.31.100") == 0,
"TC-NETSOCK-550c local address closes parse-store-print (old code printed 100.31.168.192)");
/* 2) connect 的对端簿记同样闭合(lftpd:864 与 104 显示走的就是这条链) */
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(2404U);
in.sin_addr.s_addr = net_inet_addr("192.168.1.200");
in.sin_addr.s_addr = net_inet_addr("192.168.31.200");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
CHECK(net_connect_locked(fd, sa, (int)sizeof(in)) == 0,
"TC-NETSOCK-550d connect precondition ok");
@@ -420,13 +420,13 @@ static void tc_550_family_roundtrip(void)
(void)memset(&in, 0, sizeof(in));
CHECK(net_getpeername(fd, sa, &addrlen) == 0,
"TC-NETSOCK-550e getpeername ok (reads event cache, zero SPI)");
CHECK(in.sin_addr.s_addr == net_inet_addr("192.168.1.200"),
CHECK(in.sin_addr.s_addr == net_inet_addr("192.168.31.200"),
"TC-NETSOCK-550f getpeername peer equals the s_addr fed into net_connect");
CHECK(strcmp(net_inet_ntoa(in.sin_addr.s_addr, txt), "192.168.1.200") == 0,
CHECK(strcmp(net_inet_ntoa(in.sin_addr.s_addr, txt), "192.168.31.200") == 0,
"TC-NETSOCK-550g peer address prints right (lftpd connection-received log depends on it)");
CHECK(s_net_socks[fd].remote_ip_arr[0] == 192U &&
s_net_socks[fd].remote_ip_arr[3] == 200U,
"TC-NETSOCK-550h chip des_ip array stays wire order [192,168,1,200]: on-wire behaviour unchanged");
"TC-NETSOCK-550h chip des_ip array stays wire order [192,168,31,200]: on-wire behaviour unchanged");
reset_all();
}
+3 -1
View File
@@ -20,7 +20,9 @@ import socket
import sys
import time
IP_DEFAULT = "192.168.1.100"
import bench_defaults # 台架连接默认值单一真源
IP_DEFAULT = bench_defaults.DEVICE_IP
def ftp_stor_round(ip, user, pwd, fname, payload):
+3 -1
View File
@@ -16,10 +16,12 @@ import socket
import sys
import time
import bench_defaults # 台架连接默认值单一真源
def main():
ap = argparse.ArgumentParser(description="listener reconnect stress")
ap.add_argument("--host", default="192.168.1.100")
ap.add_argument("--host", default=bench_defaults.DEVICE_IP)
ap.add_argument("--port", type=int, required=True)
ap.add_argument("--n", type=int, default=50)
ap.add_argument("--gap-ms", type=int, default=100)
+4 -2
View File
@@ -3,7 +3,7 @@
"""RemoDispBus(7003) 拉帧压力客户端:「双通道并发回归」的网络侧激励。
做什么:
连 192.168.1.100:7003,先发 INIT 查询屏幕参数(应得 8 B:宽u16/高u16/帧字节数u32,
连设备 IP(默认见 bench_defaults.DEVICE_IP):7003,先发 INIT 查询屏幕参数(应得 8 B:宽u16/高u16/帧字节数u32,
大端),随后按固定节拍持续发 LCDMEM(偏移 0) 拉整帧 3200 B 显存,逐帧校验
XOR 校验和与长度,统计请求数 / 应答数 / CRC 错 / 超时 / 往返延迟 min-avg-max。
可 --dump last_frame.pbm 存最后一帧(1bpp 布局与屏板帧 B 点阵段逐字节同构)。
@@ -39,7 +39,9 @@ import struct
import sys
import time
DEFAULT_IP = "192.168.1.100"
import bench_defaults # 台架连接默认值单一真源
DEFAULT_IP = bench_defaults.DEVICE_IP
DEFAULT_PORT = 7003
TAG_CLIENT = 0xAA
TAG_DEVICE = 0xBB
+6 -4
View File
@@ -13,9 +13,9 @@
字节 4..7 : 本地 Unix 时间戳,uint32,大端(主机字节序转换后由板子分解)
用法:
python time_sync_server.py # 默认推送到 192.168.1.100:8888
python time_sync_server.py 192.168.1.100 # 指定板子 IP
python time_sync_server.py 192.168.1.100 9000 3 # 指定端口与间隔(秒)
python time_sync_server.py # 默认推送到设备 IP(bench_defaults.DEVICE_IP):8888
python time_sync_server.py <设备IP> # 指定板子 IP
python time_sync_server.py <设备IP> 9000 3 # 指定端口与间隔(秒)
按 Ctrl+C 停止。
"""
@@ -26,7 +26,9 @@ import sys
import time
import calendar
DEFAULT_MCU_IP = "192.168.1.100"
import bench_defaults # 台架连接默认值单一真源
DEFAULT_MCU_IP = bench_defaults.DEVICE_IP
DEFAULT_PORT = 8888
DEFAULT_INTERVAL = 5.0