ESPectre 感知准入契约:classifier-first 20 MHz CSI 接收规范
【免费下载链接】espectreWi-Fi CSI motion sensing for ESP32. C++ SDK, ESPHome, Native, and Matter frontends, browser tools, and a CLI for the full device lifecycle. GPLv3 and commercial licensing.项目地址: https://gitcode.com/GitHub_Trending/es/espectre
导读
本文基于 ESPectre 仓库中的架构决策记录 2026-07-23-adopt-classifier-first-ht20-sensing-contract.md,系统讲解 ESPectre(ESP32 Wi-Fi CSI 运动感知)如何通过"先分类、后归一化"的准入契约,确保进入检测器和训练数据的每一条 CSI 记录都符合生产级 20 MHz 感知契约。你将掌握:为什么字节长度不能证明 CSI 有效、lltf20/ht20/vht20三类采集 profile 的物理含义、五步运行时准入顺序、以及 2.4 GHz / 5 GHz 频段选择与检测验证状态的边界。读完本文,你将能够理解并审查 ESPectre 固件与主机工具共享的 CSI 准入边界,并知道如何在源码中定位与验证这一契约。
背景:为什么字节长度不足以定义有效输入
"Parsing a CSI payload does not prove that it is valid detector input."——ADR 原文
ESPectre 过去存在两个叠加的隐患:
- 固件与主机工具过度依赖字节长度推断格式:一段能解析出 I/Q 对的字节流,未必是检测器认可的物理格式。长度相同不代表布局相同、PHY 相同、子载波网格相同。
- 频段选择被强制为 2.4 GHz:即使在双频目标上,早期实现也一律强制 2.4 GHz 采集。
生产契约必须把三件事独立定义:
- PHY 准入(
HT/VHT/ legacy LLTF 是否被允许); - 布局归一化(载荷布局能否映射到内部 64 子载波网格);
- Wi-Fi 频段选择(
2g/5g/auto由集成方显式决定)。
从源码结构看,这套契约在仓库中被拆分为多个职责单一的头文件与实现:csi_phy_filter.h 负责 PHY 元数据判定,csi_format_classifier.h 负责在归一化之前完成分类,csi_payload_normalizer.h 负责把已识别的布局归一化到 64 子载波网格。运行时与主机工具共用同一套准入语义(tools/lib/csi_io.py 中的assess_ht20_sensing_record与 C++ 侧assess_ht20_sensing_format相互对齐)。
决策:唯一的 classifier-first 感知契约
ADR 明确采纳一条显式的 classifier-first 感知契约,其核心规则如下:
| 规则 | 内容 |
|---|---|
| 命名 profile 白名单 | 生产感知仅接受lltf20、ht20、vht20三种采集 profile |
| 布局识别 | 只接受能映射到内部 64 子载波网格的已识别布局 |
| 分类先行 | 每个数据包或数据集行在归一化之前必须先分类 |
| 归一化范围 | 只归一化命名的 20 MHz 布局,包括精确的 64 子载波载荷以及显式支持的短估计与双倍估计 |
| 丢弃策略 | 不支持或歧义的格式直接丢弃,并携带 reason 遥测 |
| 训练校验 | 当过滤移除全部有效感知数据时,主机侧训练与校验必须显式失败 |
| 历史数据保留 | 没有 PHY 元数据的历史采集,仅当其存储布局本身能证明符合受支持的 20 MHz 契约时才保留 |
| 频段与带宽 | 由前端或 SDK 集成方选择2g、5g或auto;同时强制 20 MHz 带宽、2.4 GHz 上使用 HT、5 GHz 上使用 VHT、禁止 HE 采集 |
验证语料与频段的边界
ADR 特别强调一个容易被误解的边界:
- 经过验证的检测器语料仍是 2.4 GHz;
- 5 GHz 与自动频段模式在受支持的双频目标上可用,但可用性不等于在 5 GHz 语料上的检测性能验证;
- HE20、HT40、VHT40 及更宽布局需要各自的显式推广(promotion)流程。
在 ALGORITHMS.md 中可以看到一致的表述:"The current detection corpus validates 2.4 GHz HT20 with HT-LTF; 5 GHz VHT20 detection quality remains uncharacterized."(当前检测语料验证的是 2.4 GHz HT20 + HT-LTF;5 GHz VHT20 检测质量尚未刻画。)
五步运行时准入顺序
ADR 给出了明确的运行时准入顺序,这也是理解整个契约的主干:
- validate structure—— 校验载荷结构;
- validate PHY, LTF, and width metadata—— 校验 PHY、LTF 与带宽元数据;
- recognize the payload layout—— 识别载荷布局;
- normalize a named 20 MHz variant—— 归一化命名的 20 MHz 变体;
- route to sensing or an explicit drop path—— 路由到感知路径或显式丢弃路径。
源码中的实现证据
这一顺序在 csi_format_classifier.h 的assess_ht20_sensing_format中得到了完整落地(src/cpp/runtime/esp_idf/csi_format_classifier.h)。其执行路径与 ADR 的五个步骤一一对应:
- 结构校验:
info == nullptr、info->buf == nullptr、len == 0、奇数长度分别映射到NULL_OR_EMPTY、BAD_LENGTH; - 硬件与 PHY 校验:非零
rx_state优先判定为RX_ERROR(硬件故障优先于畸形载荷,避免无关的坏接收污染格式切换重置计数);在 HE 能力的芯片(C5/C6)上,非零rxend_state判定为RX_END_ERROR,rx_channel_estimate_info_vld == 0判定为INVALID_ESTIMATE;随后通过csi_info_is_ht20_sensing/csi_info_is_vht20_sensing/csi_info_is_legacy_lltf判定 PHY 与带宽; - 布局识别:依据长度常量切换到
HT20_64、HT20_57、HT20_64_DOUBLE、HT20_57_DOUBLE、LLTF20_53等布局 ID; - 归一化标记:短布局与双倍布局被标记为
NORMALIZED视图,并携带对应的NormalizedCSIPayloadTag(如HT57_TO_64、DOUBLE_HT20、LLTF53_TO_64); - 路由或丢弃:
CsiFormatDisposition::SENSE或DROP决定去向,reason_code作为遥测字段。
值得注意的细节是:UNEXPECTED_LTF这个 reason code 在注释中明确标注为 host-only 词汇——ESP-IDF 的rx_ctrl元数据无法把 LTF 与 PHY 决策分开,因此固件分类器在遇到该情形时报告UNSUPPORTED_PHY,保留该词汇只是为了与主机侧 reason 词表对齐(csi_format_classifier.h)。
PHY 元数据判定:两种芯片代际的分支
csi_phy_filter.h 展示了 ESP-IDF 元数据在两种芯片代际上的差异:
- HE 能力芯片(C5/C6):通过
cur_bb_format == RX_BB_FORMAT_HT且second == 0U判定 HT20;VHT20 需要RX_BB_FORMAT_VHT且second == 0U; - 经典芯片(ESP32/S2/S3 等):HT20 需要
sig_mode == 1U且cwb == 0U;VHT 不支持。
该头文件注释明确指出其与主机侧assess_ht20_sensing_record(phy_mode=ht、channel_width=20)对齐,保证固件与工具在 PHY 层面判定一致。
采集 profile 与频段策略的物理含义
三种命名 profile
csi_capture_profile.h 定义了三种物理采集 profile,以及构建期策略到物理 profile 的解析:
HT20:20 MHz 802.11n(HT)载荷,训练字段为 HT-LTF;LLTF20:legacy OFDM 长训练字段(LLTF),用于 ESP32 / ESP32-S2 等经典芯片(这些芯片不支持 HT 的 8 位 CSI 模式);VHT20:20 MHz 802.11ac(VHT)载荷,用于 VHT 能力的 5 GHz 链路。
resolve_csi_capture_profile(csi_capture_profile.h)展示了构建期策略AUTO/LLTF/HT_VHT如何被解析为物理 profile:
- 请求
LLTF,或AUTO且芯片偏好 LLTF20(ESP32/S2)→LLTF20; - 支持 VHT20 且信道号
> 14(即 5 GHz)→VHT20; - 其余情况 →
HT20。
频段策略与构建期配置
频段选择是显式的集成方决策。在 espectre_config/Kconfig.projbuild 中可以看到ESPECTRE_WIFI_BAND_POLICYchoice:
ESPECTRE_WIFI_BAND_2G(2.4 GHz only);ESPECTRE_WIFI_BAND_5G(5 GHz only,depends on SOC_WIFI_SUPPORT_5G);ESPECTRE_WIFI_BAND_AUTO(自动 2.4/5 GHz 选择,同样依赖双频能力)。
默认值按目标能力区分:双频目标默认AUTO,否则默认2G。5 GHz 与自动选择都要求双频硅片。运行时配置侧(runtime_sensing_kconfig.cpp)会把CONFIG_ESPECTRE_WIFI_BAND_5G映射到WifiBandPolicy::BAND_5G,否则落到BAND_2G。
带宽、HT/VHT 与 HE 上限
契约强制在选定频段上固定 20 MHz 带宽:
- 2.4 GHz 上使用HT(802.11n,HT-LTF);
- 5 GHz 上使用VHT(802.11ac,VHT-LTF);
- 禁止 HE(802.11ax / Wi-Fi 6)采集。
这也解释了 csi_format.h 中布局检测逻辑的成因:Wi-Fi 6(HE 能力)部件交付以 DC 为中心的 HT20 CSI(bin = 子载波 + 32),而经典 MAC 部件交付 Espressif 原生0~31, -32~-1顺序(DC 在 bin 0)。两种布局通过 guard 空 bin 区分——bin 0 和 32 在两种约定下都为空,而HT20_CLASSIC_ONLY_NULL_BINS(29/30/31/33/34/35)与HT20_CENTERED_ONLY_NULL_BINS(1/2/3/61/62/63)恰好各只有一种约定下为空(csi_format.h)。
布局归一化:把变体折叠到规范 64 子载波视图
支持的载荷变体
csi_types.h 定义了全部相关长度常量:
| 常量 | 值 | 含义 |
|---|---|---|
HT20_NUM_SUBCARRIERS | 64 | 规范子载波数 |
HT20_CSI_LEN | 128 B | 精确 64 子载波 HT20 载荷 |
HT20_CSI_LEN_DOUBLE | 256 B | 双倍 HT20 载荷(2×64 SC) |
HT20_CSI_LEN_SHORT | 114 B | 短 HT 估计(57 SC) |
HT20_CSI_LEN_SHORT_DOUBLE | 228 B | 双倍短 HT 估计(2×57 SC) |
LLTF20_CSI_LEN_SHORT | 106 B | 紧凑 LLTF 估计(53 SC) |
归一化映射表
CSI.md 给出了完整的映射表,这与分类器中的CsiLayoutId和NormalizedCSIPayloadTag一一对应:
| 输入情形 | 原始布局 | 映射到 HT20 | 输出 |
|---|---|---|---|
| Native HT20 | 128 B = 64 SC | 直通 | 64 SC / 128 B |
| 短 HT 估计 | 114 B = 57 SC | 左补 4 个 SC、复制 57 SC、右补 3 个 SC | 64 SC / 128 B |
| 双倍 HT20 载荷 | 256 B = 2×64 SC | 折叠为 1 个128 B半部 | 64 SC / 128 B |
| 双倍短 HT 估计 | 228 B = 2×57 SC | 折叠为 57 SC 半部,再左补 4、右补 3 | 64 SC / 128 B |
| 紧凑 LLTF 估计 | 106 B = 53 SC | 居中顺序-26..+26(DC 在 pair 26),左补 6 个 bin、右补 5 个 bin,DC 落到 bin 32 | 64 SC / 128 B |
归一化后的分类器结果会记录raw_len、raw_num_subcarriers、normalized_len(恒为HT20_CSI_LEN)与normalized_num_subcarriers(恒为HT20_NUM_SUBCARRIERS),以及requires_normalization()视图标记(csi_format_classifier.h)。
注意:LLTF 紧凑估计的归一化只接受 8 位分量,不解码打包的 12 位采样;C5 的 LLTF 采集选择 8 位模式(CSI.md)。
双布局检测与经典→居中旋转
对于精确的 128 字节载荷,detect_ht20_bin_layout(csi_format.h)要求双向正证据:一组 guard 完全为空且另一组完全有能量,才判定布局;仅凭"无能量"不足以下结论(稀疏或退化载荷在两种约定下都为空)。识别为CLASSIC布局后,rotate_ht20_classic_to_centered通过交换两半(旋转 32 bin 是自身的逆)把0~31, -32~-1映射到-32~+31(csi_format.h)。
检测器视图的进一步准备
归一化后,检测器还消费一个"规范居中 64 子载波视图"(ALGORITHMS.md 中的 "canonical centered 64-subcarrier view")。prepare_ht20_detector_input(csi_format.h)在私有检测器缓冲区上完成两类补齐:
- LLTF 缺失的边缘 tone(±26 之外的 ±27/±28)从最近的活跃 ±26 tone 复制 I/Q;
first_word_invalid且源为 CLASSIC 布局时,把物理子载波 +1(居中 bin 33)从 +2 复制。
选择依据只有采集 profile、硬件标志和原始 bin 顺序——有效的零值永远不被当作缺失数据的证据。原始 CSI 分支在准备之前运行,保留零值与硬件标志。
丢弃路径与 reason 遥测
"不支持或歧义格式直接丢弃,并携带 reason 遥测"是契约的关键。完整的 reason 词表定义在 csi_format_classifier.h:
| Reason code | 含义 | 判定时机(源码依据) |
|---|---|---|
NULL_OR_EMPTY | 空指针或空载荷 | info == nullptr/buf == nullptr/len == 0 |
BAD_LENGTH | 长度非法(奇数、非命名长度) | raw_len % 2 != 0等 |
UNSUPPORTED_PHY | 非 HT/VHT/LLTF 的 PHY | csi_info_is_*判定失败且非选定 PHY |
UNSUPPORTED_WIDTH | 带宽不是 20 MHz | PHY 匹配但second != 0/cwb != 0 |
UNEXPECTED_LTF | 意外的 LTF(host-only 词汇) | 固件侧以UNSUPPORTED_PHY代替 |
UNKNOWN_LAYOUT | 布局无法识别 | 长度不在命名集合,或布局检测证据不足 |
MISSING_METADATA | 元数据缺失(主机侧) | 见assess_ht20_sensing_record |
RX_ERROR/RX_END_ERROR | 硬件接收错误 | rx_state/rxend_state非零 |
INVALID_ESTIMATE | 信道估计无效(HE 芯片) | rx_channel_estimate_info_vld == 0 |
INVALID_FIRST_WORD | 首字无效且无法独立识别布局 | 非全宽或布局检测为UNKNOWN |
reason 遥测被写入分类评估(CsiFormatAssessment),并在运行时与主机工具中用于诊断。丢弃路径与感知路径的区分由CsiFormatDisposition::DROP / SENSE承载,而reset_detector_before_consume标记会在格式转换或长无效序列时要求先清除检测器历史再消费被接受的包(csi_format_classifier.h)。
主机侧的对齐实现
主机工具在 tools/lib/csi_io.py 中实现assess_ht20_sensing_record(tools/lib/csi_io.py),按phy_mode与channel_width元数据执行同一套判定:
- 元数据缺失时,若载荷布局本身能证明 20 MHz 契约(历史采集),则以
METADATA_SOURCE_HISTORICAL标记并放行;否则报告MISSING_METADATA; phy_mode != ht报告UNSUPPORTED_PHY;channel_width != 20报告UNSUPPORTED_WIDTH;- 布局评估失败时报告
UNKNOWN_LAYOUT(或已归一化数据上的UNNORMALIZED_LAYOUT)。
MicroPython 运行时也维护了等价的assess_ht20_sensing_phy与assess_ht20_payload_layout(src/python/micro_espectre/device_utils.py 与 L214-L238),并支持metadata_missing的历史放行路径。
训练与校验侧的强制失败语义
契约要求:"require host training and validation to fail explicitly when filtering removes all valid sensing data."(当过滤移除全部有效感知数据时,主机训练与校验必须显式失败。)
从 csi_io.py 的过滤逻辑看(filter_sensing_rows/filter_ht20_sensing_records等),主机在批量读取数据集时会对每行执行assess_ht20_sensing_record,并以 reason code 为NONE作为准入条件(tools/lib/csi_io.py)。当过滤后有效数据为零时,上层训练/校验流程应显式失败而非静默训练空集——这保证了"不支持格式"永远不会悄悄污染训练数据,与 ADR 的 "fail loudly" 目标一致。
决策历史:契约的演进
ADR 的 Decision History 记录了该契约的三次演进,对理解当前状态至关重要:
| 日期 | 方向 | 结果 |
|---|---|---|
| 2026-07-23 | 让 HT20 准入 classifier-first | 采纳 |
| 2026-08-05 | 在每个目标上强制 2.4 GHz | 被替换为显式的集成方频段选择,同时在所有选定频段上保持 HT20 |
| 2026-09-01 | 在 5 GHz 上保持 802.11n 上限 | 被替换为在 VHT 能力的 5 GHz 目标上采用 VHT20 采集,同时保留规范的 64 子载波检测器视图 |
第二次演进(强制 2.4 GHz → 集成方显式选择)正是本次 ADR 在 Context 中提到的"band selection was initially forced to 2.4 GHz even on dual-band targets"的收尾;第三次演进(HT ceiling → VHT20 on 5 GHz)则回答了"5 GHz 上到底用什么 PHY 采集"的问题,并最终形成了 2026-09-01 起的lltf20 / ht20 / vht20三 profile 格局。
被拒绝的替代方案及其理由
ADR 记录了四个被评估并拒绝的替代方案,这些理由构成了契约的边界条件:
| 替代方案 | 拒绝理由 |
|---|---|
| 保持 length-first(先看长度再归一化) | 结构兼容性弱于感知兼容性,会静默重新解释不支持的输入 |
| 接受一切可解析的 PHY 或带宽 | 仓库对这些感知契约缺乏经过验证的映射、语料与 parity 门禁 |
| 在双频设备上强制 2.4 GHz | 不必要地剥夺集成方选择权;验证状态应独立于受支持的无线配置面记录 |
| 到处使用自动频段选择 | 固定频段部署需要确定性策略,集成方必须能显式选择目标频段 |
其中"接受一切可解析的 PHY 或宽度"被拒绝的理由在源码中得到呼应:UNSUPPORTED_WIDTH与UNSUPPORTED_PHY是两个独立的 reason code,且 HE/40 MHz/更宽布局的映射、语料与 parity 门禁在仓库中均未建立。
后果与影响
ADR 的 Consequences 部分总结了采纳后的影响,这些在源码中均可验证:
运行时与主机工具共享同一条感知准入边界:C++ 的
assess_ht20_sensing_format(csi_format_classifier.h)、MicroPython 的assess_ht20_sensing_phy/assess_ht20_payload_layout(device_utils.py)与主机工具assess_ht20_sensing_record(csi_io.py)实现同一套语义,reason 词表对齐。不支持的 PHY 大声失败而不是污染检测器或训练数据:reason 遥测 + 显式丢弃路径保证坏数据不会流入特征提取。
规范 64 子载波检测器视图保持稳定:运行时可从芯片与关联频段选择 LLTF20、HT20 或 VHT20 采集,但检测器永远看到同一个居中 64 子载波网格。采集侧的差异在进入检测器之前全部被归一化吸收。
新 PHY 支持需要完整证据链:布局映射、代表性数据、检测器验证与 Python/C++ parity 四者缺一不可。这也是 csi_pipeline.cpp 中归一化后
normalization_tag与reset_detector_before_consume标记被消费(csi_pipeline.cpp)的原因——格式转换被当作检测器生命周期边界处理。
测试验证:布局归一化在 LLTF20 下全量覆盖
test_csi_pipeline_lltf20_normalizes_all_ht_layouts_before_detector 直接验证了契约在固件侧的落地:在LLTF20profile 下,依次注入 128 / 114 / 256 / 228 字节四种受支持长度,断言每包都到达检测器(expected_packets递增)、短布局被正确映射到物理 tone -28..+28 的居中网格,且 LLTF 边缘 tone 补齐生效(nearest_edge_tones_copied)。这印证了"分类→归一化→路由"在真实采集链路上的行为。
关联文档导航
- 本 ADR 的前置决策:2026-07-03-unify-raw-csi-collection-over-http.md(统一 raw CSI 采集传输)、2026-07-25-select-the-classic-band-from-channel-coherence.md(经典频段选择,定义了固定 12-tone 频段
DEFAULT_SUBCARRIERS = (4, 8, 13, 18, 23, 28, 36, 41, 46, 51, 56, 60)); - 系统级文档:ALGORITHMS.md(检测算法与语料现状)、ARCHITECTURE.md(运行时架构);
- 采集与归一化细节:CSI.md;
- 其他相关决策:2026-08-15-use-fixed-temporal-csi-admission.md(时间槽准入)、2026-08-28-retain-provenance-filtered-csi-admission.md(来源过滤准入)。
如需在实际设备上配置,可参考 espectre_config/Kconfig.projbuild 中的ESPECTRE_WIFI_BAND_POLICY与ESPECTRE_CSI_CAPTURE_PROFILE选项,或在 ESPHome / Native / Matter 前端对应的 YAML 与 Kconfig 中指定auto、lltf、ht-vht采集策略。
【免费下载链接】espectreWi-Fi CSI motion sensing for ESP32. C++ SDK, ESPHome, Native, and Matter frontends, browser tools, and a CLI for the full device lifecycle. GPLv3 and commercial licensing.项目地址: https://gitcode.com/GitHub_Trending/es/espectre
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考