RuView ADR-117 实践指南:用 PyO3 + maturin 把 pip `wifi-densepose` 从纯 Python 服务重构为 Rust 核心绑定(PIP-PHOENIX)
本文基于 RuView 仓库中的架构决策记录 ADR-117 展开:项目曾把
wifi-densepose以纯 Python 服务的形式发布到 PyPI,而生产能力早已迁移到 Rust 工作区(v2/),形成"PyPI 包误导新用户、生态两套分裂"的窘境。ADR-117 提出代号 PIP-PHOENIX 的方案——采用 PyO3 + maturin 绑定三个高价值 Rust crate,发布带稳定 ABI 的编译型 wheel,并配一层纯 Python 的 WS/MQTT 客户端。读完本文,你将掌握:该方案被选中而另外三种方案被否决的完整理由、首版 wheel 的绑定范围与包布局、abi3-py310多平台构建矩阵、见证链(witness chain)如何重新挂到 Rust 管线之上,以及 P1→P6+ 的分阶段迁移清单和 1.x 老用户的兼容性处理策略。
1. 背景:这个 pip 包今天是什么,为什么必须改
wifi-densepose v1.1.0 于 2025-06-07 发布到 PyPI(同日先后发布 1.0.0 与 1.1.0 两个版本),wheel 标签是 py3-none-any——即没有任何编译扩展、没有任何平台相关代码的纯 Python 服务器应用,源码全部来自 archive/v1/。
从当前仓库的 archive/v1/setup.py 可以核实关键事实:
- 安装时会拉起一套约 40 个硬依赖的栈:FastAPI、PyTorch、SQLAlchemy、Redis、Celery、OpenCV、asyncpg、psycopg2、Scapy 等(见
setup.py中默认 requirements 列表); - 声明的两个控制台入口均为
src.cli:cli:wifi-densepose = src.cli:cli wdp = src.cli:cli python_requires=">=3.9",classifier 覆盖 Python 3.9/3.10/3.11/3.12,状态为 Beta;find_packages(include=["src", "src.*"])说明 wheel 以src.*作为可导入命名空间;- archive/v1/src/init.py 导出的是 FastAPI
app、CSIProcessor、PhaseSanitizer、PoseEstimator、RouterInterface、ServiceOrchestrator、HealthCheckService、MetricsService等服务器应用概念。
换句话说,v1 包是一个面向 postgres + redis 的 FastAPI 服务器,并不是可被脚本化调用的库。
1.1 为什么现在必须处理(现状证据表)
ADR-115(2026-05-23 合入)已从 Rust crate wifi-densepose-sensing-server 交付了 21 个 Home Assistant 实体、10 个语义原语、mTLS、隐私模式与完整见证 bundle;ADR-116 正在把它打包为 Cognitum Seed cog。但这两条能力面在 pip install wifi-densepose 后全都不可达——pip 包既不能 import 一个 CsiFrame、无法解码 edge-vitals 报文、无法调用任何 DSP 阶段、无法校验 witness bundle,也无法订阅 sensing-server 的 MQTT/WebSocket 端点。
ADR 给出的现状证据表:
| Artifact | 值 | 来源 |
|---|---|---|
| 最新 PyPI 版本 | 1.1.0 | pypi.org/pypi/wifi-densepose/json |
| 首次发布日期 | 2025-06-07T13:24:53Z | PyPI JSON 元数据 |
| 最近一次发布日期 | 2025-06-07T17:02:40Z | PyPI JSON 元数据 |
| 距最近发布已过去 | 约 11.5 个月(截至 2026-05-24) | PyPI JSON 元数据 |
| Wheel 标签 | py3-none-any |
PyPI simple index |
| 硬依赖数量 | 40(torch、fastapi、sqlalchemy、redis、celery…) | setup.py |
| Python 要求 | >=3.9 |
setup.py |
| 当前 Rust 工作区版本 | 0.3.0 | v2/Cargo.toml |
| Rust crate 数量 | 20+ | v2/Cargo.toml members |
| ADR-115 交付时间 | 2026-05-23(PR #778) | — |
三个具体的客户痛点被写进决策依据:
- Python 用户
pip install wifi-densepose期望消费实时 pose/vitals 数据,得到的却是一个依赖 postgres + redis 的 FastAPI 服务器,不是可写脚本的库; - 编写 HA 自动化或 Node-RED 流程的集成方,缺少面向 v0.7 遥测面(ADR-115 实体、语义原语)的惯用 Python API;
- ADR-028 见证链本是 Python 实现、经
archive/v1/data/proof/verify.py验证,但它只 import v1 栈——无法见证如今作为生产实现的 Rust 管线。
1.2 这个 ADR 不是什么
ADR 明确划清四条边界:
- 不是删除
archive/v1/:v1 保留为研究归档,其 proof bundle 继续留在archive/v1/data/proof/; - 不是把 Rust crate 移植成 Python:Rust 工作区(
v2/)是权威实现,本 ADR 不改动它; - 不是替换
wifi-densepose-sensing-server二进制:pip 包只是包装或作为其客户端,不重新实现它; - 不与 ADR-116 重叠:ADR-116 交付 Seed 可安装产物;ADR-117 交付面向 Python 开发者、用于对 Rust 栈进行脚本化/自动化/原型验证的库。
1.3 Gap 分析:v1 与 Rust 栈的能力差
| 能力 | Rust crate | pip v1.1.0 状态 | 严重度 |
|---|---|---|---|
CsiFrame / CsiMetadata 核心类型 |
wifi-densepose-core(types.rs) |
缺失——v1 用 CSIData Python 类 |
Critical |
| 从 CSI buffer 提取 HR/BR | wifi-densepose-vitals(4 阶段:preprocessor → breathing → heartrate → anomaly) |
Python stub 无 DSP | Critical |
| 相位净化/去噪 | wifi-densepose-signal(phase_sanitizer、csi_processor、hampel) |
Python stub | Critical |
| 动作检测 + presence 打分 | wifi-densepose-signal(motion.rs、MotionDetector) |
缺失 | Critical |
| RuvSense 多站感知(13 模块) | wifi-densepose-signal/src/ruvsense/ |
缺失(ADR-029 晚于 v1) | Critical |
| 17 关键点姿态估计 | wifi-densepose-nn、wifi-densepose-mat |
需要模型权重的 stub PoseEstimator |
High |
| MQTT 发布(21 个 HA 实体) | wifi-densepose-sensing-server/src/mqtt/ |
缺失(ADR-115 晚于 v1) | High |
| 语义原语(10 种) | wifi-densepose-sensing-server/src/semantic/ |
缺失 | High |
| Matter bridge | wifi-densepose-sensing-server/src/matter/ |
缺失 | High |
| sensing-server 的 WS/REST 客户端 | wifi-densepose-sensing-server(Axum) |
v1 只有独立 FastAPI 服务,无客户端 | High |
| Witness bundle 验证 | ADR-028 / scripts/generate-witness-bundle.sh | verify.py 只证明 v1 管线 |
High |
| ESP32-C6 固件遥测(ADR-110) | wifi-densepose-hardware + wifi-densepose-sensing-server |
缺失 | Medium |
| 跨视角融合(RuVector) | wifi-densepose-ruvector/src/viewpoint/ |
缺失 | Medium |
| 语义原语 MQTT 载荷 | wifi-densepose-sensing-server/src/semantic/bus.rs |
缺失 | Medium |
| PostgreSQL + Redis 服务器模式 | archive/v1/ |
存在(仅 v1) | Low(非 SOTA) |
| FastAPI HTTP REST 服务器 | archive/v1/src/app.py |
存在(仅 v1) | Low(非 SOTA) |
2. 决策:PyO3 + maturin,方案代号 PIP-PHOENIX
ADR-117 采纳 PyO3 + maturin Python 扩展绑定作为主要现代化路径:把 pip 包发布为平台原生 wheel(manylinux、macosx、win-amd64),内含编译好的 Rust 扩展模块,同时附一层与运行中的 wifi-densepose-sensing-server 通信的纯 Python WS/MQTT 客户端。
2.1 为什么赢过另外三种被否决的方案
| 判据 | PyO3 + maturin(入选) | Subprocess 包装 | 仅 REST/WS 客户端 | 纯 Python 重实现 |
|---|---|---|---|---|
| DSP 性能 | 原生 Rust 速度、零拷贝 | 每次调用都有 IPC 开销 | N/A——无本地 DSP | Python 瓶颈 |
| wheel 内二进制体积 | 仅 core + vitals + signal:约 2 MB(strip 后) | 完整 sensing-server 二进制:约 15–30 MB | 极小(约 50 kB) | 极小(约 100 kB) |
| 离线 / 无服务器可用 | 是 | 是(捆绑二进制) | 否——必须有服务器 | 部分 |
| Proof bundle 能否覆盖 Rust 管线 | 能——绑定与服务器调用同一份 Rust 代码 | 部分——服务器是黑盒 | 否 | 否 |
| 安装体验 | pip install wifi-densepose——wheel 无系统依赖 |
pip install 下载 25 MB 二进制 |
pip install——纯 Python |
pip install——纯 Python |
| 维护面 | Python 绑定 + Rust 工作区 | Python 薄垫片 | Python 客户端 | Python 重实现必须追着 Rust 跑 |
| Async / tokio | PyO3 0.28 用 pyo3-asyncio 或 pyo3-async-runtimes 导出 async;DSP 热路径用同步入口 |
N/A | 客户端原生 asyncio | N/A |
| GIL 关切 | DSP 重调用经 py.allow_threads 释放 GIL;按模块管理 tokio runtime |
N/A | 无 | N/A |
| 契合现有架构 | core + vitals + signal 已有干净 public API(lib.rs re-export) |
要求 sensing-server 在跑 | 要求 sensing-server | 分叉领域模型 |
三句话记住淘汰理由:
- Subprocess 包装被否:把约 25 MB 的预编译服务器二进制塞进每个 pip wheel,安装过于沉重,且不启动服务器就无法离线写脚本;
- REST/WS client only 被否:离线时没有任何 DSP 价值,且无法弥合 witness 缺口——proof bundle 必须执行同一份管线代码;
- 纯 Python 重实现被否(明确拒绝):这正是当前 11 个月漂移的根源,任何 Python 重实现都会在 Rust 栈演进时立刻再次漂移。
方案起点刻意做小:只绑定对 Python 最有用处的三个 crate(wifi-densepose-core、wifi-densepose-vitals、wifi-densepose-signal),把 py3-none-any 的纯 Python WS/MQTT 客户端作为独立子模块发布,再逐步扩展。
3. 详细设计
3.1 首版 wheel(v2.0)绑定的 Rust crate
三个 crate 均无重型系统依赖(不带 libtorch、不带 ONNX Runtime),在 lib.rs 有稳定 pub re-export,且直击三个最被要求的缺失能力:
| Crate | 导出的 Python 类型/函数 | 绑定理由 |
|---|---|---|
wifi-densepose-core |
CsiFrame、CsiMetadata、Keypoint、KeypointType、PersonPose、PoseEstimate、Confidence、BoundingBox |
其余 crate 共享的基石类型;没有它们用户连一帧都描述不了 |
wifi-densepose-vitals |
CsiVitalPreprocessor、BreathingExtractor、HeartRateExtractor、VitalAnomalyDetector、VitalSignStore、VitalReading、VitalEstimate、AnomalyAlert |
被问得最多的面:4 行 Python 从 CSI buffer 拿 HR/BR |
wifi-densepose-signal |
CsiProcessor、CsiProcessorConfig、PhaseSanitizer、MotionDetector、MotionScore、FeatureExtractor、HardwareNormalizer |
产出 vitals/pose 所需特征的那条 DSP 管线 |
推迟到 P6+:wifi-densepose-nn(需要 libtorch 或 candle——wheel 体积风险)、wifi-densepose-mat(依赖 nn)、wifi-densepose-ruvector(RuVector GNN 类型价值高但引入 ruvector-gnn 2.0.5 链接依赖)、wifi-densepose-hardware(ESP32 HAL,不适合 Python 脚本)。
3.2 新的工作区成员与 crate 结构
ADR 设计了一个新的 cdylib crate(位于 v2/crates/wifi-densepose-py/),在单个 maturin 模块 wifi_densepose._core 后面统一 re-export 三个被绑定 crate。其 Cargo.toml 要点如下:
# Cargo.toml(sketch)
[package]
name = "wifi-densepose-py"
version.workspace = true
edition.workspace = true
[lib]
name = "_core"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.28", features = ["extension-module", "abi3-py310"] }
wifi-densepose-core = { path = "../wifi-densepose-core", features = ["serde"] }
wifi-densepose-vitals = { path = "../wifi-densepose-vitals" }
wifi-densepose-signal = { path = "../wifi-densepose-signal" }
其中 abi3-py310 锁定了 CPython 3.10+ 的稳定 ABI——同一个二进制 wheel 无需重编译即可覆盖 3.10、3.11、3.12、3.13。这就是后面 wheel 矩阵从 20 个缩到 5 个的关键。
PyO3 绑定模式以 CsiFrame 为例(属性逐一暴露为 getter):
// v2/crates/wifi-densepose-py/src/core_types.rs
use pyo3::prelude::*;
use wifi_densepose_core::CsiFrame as RustCsiFrame;
#[pyclass(name = "CsiFrame")]
#[derive(Clone)]
pub struct PyCsiFrame {
inner: RustCsiFrame,
}
#[pymethods]
impl PyCsiFrame {
#[new]
fn new(amplitudes: Vec<f32>, phases: Vec<f32>, n_subcarriers: usize,
sample_index: u64, sample_rate_hz: f32) -> Self {
Self { inner: RustCsiFrame { amplitudes, phases, n_subcarriers,
sample_index, sample_rate_hz } }
}
#[getter] fn amplitudes(&self) -> Vec<f32> { self.inner.amplitudes.clone() }
#[getter] fn phases(&self) -> Vec<f32> { self.inner.phases.clone() }
#[getter] fn n_subcarriers(&self) -> usize { self.inner.n_subcarriers }
}
执行时间超过 1 ms 的 DSP 调用要释放 GIL(否则 Python 线程会被阻塞在 Rust 侧):
#[pymethods]
impl PyCsiProcessor {
fn process<'py>(&mut self, py: Python<'py>, frame: &PyCsiFrame)
-> PyResult<Option<PyProcessedSignal>>
{
py.allow_threads(|| self.inner.process(&frame.inner))
.map(|opt| opt.map(PyProcessedSignal::from))
.map_err(|e| PyRuntimeError::new_err(e.to_string()))
}
}
3.3 pip 包布局
wifi-densepose/ ← PyPI 包名(不变)
wifi_densepose/ ← 可导入命名空间
__init__.py ← re-export 核心类型 + 版本
_core.pyd / _core.so ← 编译的 PyO3 扩展(maturin 构建产物)
vitals.py ← 覆盖 _core vitals 类型的薄 Python 包装 + docstring
signal.py ← 覆盖 _core signal 类型的薄 Python 包装
client/
__init__.py
ws.py ← sensing-server /ws/sensing 的 asyncio WebSocket 客户端
mqtt.py ← 订阅 ruview/<node_id>/raw/* 主题的 paho-mqtt 包装
ha.py ← HA-DISCO 载荷辅助(只读,对齐 ADR-115 §3.2)
witness/
__init__.py
verify.py ← 可被 Python 调用的 witness 验证器(经 PyO3 绑定
在 Rust 管线上重建 ADR-028 proof,而非 archive/v1/)
compat/
v1.py ← 抛出 MigrationError 的 import 垫片(见 §7)
py.typed ← PEP 561 标记
导入路径刻意与 Rust crate 名一一对应:
from wifi_densepose import CsiFrame # core types
from wifi_densepose.vitals import BreathingExtractor, HeartRateExtractor
from wifi_densepose.signal import CsiProcessor, MotionDetector
from wifi_densepose.client.ws import SensingClient
from wifi_densepose.witness import verify_bundle
3.4 PyPI 分发:wheel 矩阵
以 wifi-densepose==2.0.0 发布,由 cibuildwheel 驱动(建议放独立 workflow,不改动既有 Rust CI):
| 平台 | 架构 | CPython | Tag(稳定 ABI) |
|---|---|---|---|
manylinux_2_28 |
x86_64 | 3.10+ | cp310-abi3-manylinux_2_28_x86_64 |
manylinux_2_28 |
aarch64 | 3.10+ | cp310-abi3-manylinux_2_28_aarch64 |
macosx_11_0 |
x86_64 | 3.10+ | cp310-abi3-macosx_11_0_x86_64 |
macosx_11_0 |
arm64 | 3.10+ | cp310-abi3-macosx_11_0_arm64 |
win |
amd64 | 3.10+ | cp310-abi3-win_amd64 |
| sdist | — | — | 源码兜底 |
abi3-py310 让每个 OS/arch 一个二进制即可覆盖所有受支持 Python 版本——总共 5 个 wheel + 1 个 sdist,而不用稳定 ABI 则需要 20 个 wheel 的矩阵。
# .github/workflows/pip-release.yml(sketch)
- uses: pypa/cibuildwheel@v2
with:
package-dir: v2/crates/wifi-densepose-py
output-dir: dist
env:
CIBW_BUILD: "cp310-*"
CIBW_ARCHS_LINUX: "x86_64 aarch64"
CIBW_ARCHS_MACOS: "x86_64 arm64"
CIBW_ARCHS_WINDOWS: "AMD64"
CIBW_BEFORE_BUILD: "pip install maturin"
CIBW_BUILD_FRONTEND: "build[uv]"
3.5 CLI 对等(但不重实现)
wheel 安装同名 wifi-densepose console script,v2 中它是一个薄 Python 垫片:
- 检查
wifi-densepose-sensing-server二进制是否在PATH上(由独立的平台二进制分发或cargo install安装); - 找到则用
subprocess.run把wifi-densepose serve、wifi-densepose stream等转发给 Rust 二进制; - 找不到则回退到 PyO3 模块执行离线 DSP 命令(如
wifi-densepose vitals --file recording.jsonl)。
这不是对 CLI 的重实现——权威 CLI 仍是 Rust 二进制(wifi-densepose-cli,当前暴露 mat 与 version 子命令),pip 垫片只是发现/便捷层。
3.6 WS/MQTT 客户端层
SensingClient 是包装 sensing-server WebSocket /ws/sensing 的纯 Python asyncio 客户端:
async with SensingClient("ws://localhost:8765/ws/sensing") as client:
async for msg in client.stream():
if msg.type == "edge_vitals":
print(msg.breathing_rate_bpm, msg.heartrate_bpm)
RuViewMqttClient 包装 paho-mqtt,按 ADR-115 §3.2 订阅 ruview/<node_id>/raw/+。
两个客户端都是纯 Python(无 PyO3),属于可选依赖:pip install wifi-densepose[client],分别依赖 websockets>=12 与 paho-mqtt>=2。
3.7 BFLD:向 v2.0 追加的绑定目标
2026-05-24 依维护者反馈在 P3 实现期间新增。 BFLD(Beamforming Feedback Loop Data,波束成形反馈环数据)是信道发射端 / AP-站点环路视角——802.11ac/ax/be 站点每个 sounding 周期发给 AP 的压缩波束成形反馈帧。从感知视角它与接收端 CSI 互补:
| 接收端 CSI(现状) | BFLD(本次新增) | |
|---|---|---|
| 来源 | 无线电 RX 侧(如 Pi 5 的 Nexmon CSI、ESP32 promisc cb) | 空气中嗅探的 BFR 帧或 mac80211 ACK trace |
| 子载波(HE20) | 52(HT-LTF)或 242(HE-LTF) | 最高 996(HE160 压缩 BFR)——更密 |
| 硬件要求 | 需打补丁的 Broadcom/Cypress 或专用 ESP32 | 任意 802.11ac+ 站点-AP 对——无需补丁固件 |
| 隐私模型 | 捕获无线电范围内所有人 | 相同 |
| 仓库内成熟度 | 生产(ADR-014、ADR-018、ADR-039) | 研究;尚无 Rust crate |
| 合适用例 | 穿墙 pose + vitals | 面向 AETHER 类生物特征(ADR-024)与 soul-signature 规范的稠密子载波反射剖面 |
由于 Rust 工作区还没有 wifi-densepose-bfld crate,P3 交付一个向前兼容的 Python trait 面,未来 Rust crate 可直接插入而无需改动 Python API:
from wifi_densepose import BfldFrame, BfldReport
# 今天(P3):从解析好的 BFR 反馈矩阵构造(自带 parser 路径)。
# Pi 5 + Wireshark BFR dissector 的用户可直接把帧灌进来。
frame = BfldFrame.from_compressed_feedback(
timestamp_ms=…,
sounding_index=…,
sta_mac="aa:bb:cc:…",
bandwidth_mhz=80,
n_subcarriers=996,
feedback_matrix=…, # numpy ndarray complex64 [Nr × Nc × Nsc]
)
# 明天(post-v2.0):wifi-densepose-bfld Rust crate(TBD,独立 ADR-1xx)
# 提供 Nexmon nl80211 trace + 内核 mac80211 debugfs hook 的摄取,
# pip wheel 在不改 Python 面前提下透明绑定它。
为什么 BFLD 应进 v2.0 而不是等 Rust core:
- 客户拉动——多位集成方读了 ADR-115 发布说明后就在问 WiFi-6 稠密子载波采集,答案就是 BFLD,API 应在他们搭管线前稳定下来;
- soul-signature 依赖——其研究规范把 "Subcarrier Reflection Profile" 列为七种生物特征信道之一,HE20/HE80 下稠密 BFR 子载波正是正确输入;先暴露
BfldFrame让研究者不必等 Rust 摄取 crate 就能原型验证(见 docs/research/soul/); - 跨厂商可移植——CSI 摄取需要补丁固件,而 BFR 摄取在原生 802.11ac/ax 硬件上即可工作(
tcpdump/Wireshark + BFR dissector 捕获)。先发布 Python 数据结构,等于给社区一条从不受支持设备向 RuView 喂数据的路。
P3 的实现面——新增 bindings/bfld.rs(约 150 行、三个 #[pyclass] 类型):
BfldFrame(frozen)——一份压缩反馈矩阵快照。构造器:from_compressed_feedback(...)与from_uncompressed_v(...)(802.11n V-matrix 形式)。属性:timestamp_ms、sounding_index、sta_mac、bandwidth_mhz、n_subcarriers、n_rows(Nr)、n_cols(Nc)、feedback_matrix(numpy complex64 ndarray);BfldReport(frozen)——一个窗口内多帧BfldFrame的聚合器。属性:n_frames、timestamp_first、timestamp_last、mean_amplitude_per_subcarrier、coherence_score,给用户一个"这 60 秒扫描里的所有 BFR 数据"的稳定句柄而不泄露存储表示;BfldKind(#[pyclass(eq, eq_int, hash, frozen)])——枚举支持的 BFR 变体:CompressedHE20、CompressedHE40、CompressedHE80、CompressedHE160、UncompressedHT20、UncompressedHT40。
在正式 Rust crate 出现之前,stub 实现放 python/src/bfld_stub.rs,刻意不进 v2/crates/;未来由独立的 ADR-1xx 拥有 Rust 摄取 crate。
3.8 Witness 链:重新挂到 Rust 管线
wifi_densepose.witness.verify_bundle(path) 取代 v1 proof 验证,改用经 PyO3 执行 Rust 管线的新链条:
from wifi_densepose.witness import verify_bundle
result = verify_bundle("dist/witness-bundle-ADR028-*/")
assert result.verdict == "PASS", result.detail
内部步骤:
- 从 bundle 加载 1,000 帧参考 JSON;
- 每帧喂给
PyCsiProcessor(RustCsiProcessor的 PyO3 绑定); - 用与 v1
verify.py相同的 SHA-256 方案对输出取哈希; - 与
expected_features.sha256中发布的哈希比对。
关键点:v1 的 proof(archive/v1/data/proof/verify.py)原样保留,继续证明 v1 管线;新的 witness.py 证明 v2/Rust 管线。两者共存,ADR-028 witness bundle 同时携带二者。
4. 分阶段迁移路径
P1 ──► P2 ──► P3 ──► P4 ──► P5 ──► P6+
scaffold core vitals+ client publish deferred
types signal layer v2.0.0
- P1 — Scaffold(1 周):新增
v2/crates/wifi-densepose-py/工作区成员;Cargo.toml配crate-type = ["cdylib"]、pyo3 0.28 +abi3-py310(空模块可编译可导入);根目录python/放pyproject.toml,[build-system] requires = ["maturin>=1.8"]、[tool.maturin] features = ["pyo3/extension-module"];CI 在 ubuntu-latest + Python 3.12 venv 跑maturin develop并验证import wifi_densepose._core;把wifi-densepose==1.99.0作为墓碑版发布到 PyPI(见 §7)。 - P2 — 核心类型绑定(1 周):绑定 core 的
CsiFrame、CsiMetadata、Confidence、Keypoint、KeypointType、BoundingBox、PoseEstimate、PersonPose;所有类型在有意义处实现__repr__/__eq__/__hash__,经pyo3-serde或手写to_dict()/from_dict()完成 serde JSON round-trip;加py.typed+ 由pyo3-stub-gen生成的.pyi;tests/test_core.py覆盖每种类型的构造与 JSON round-trip。 - P3 — Vitals + signal DSP 绑定(2 周):绑定完整 4 阶段 vitals 管线(
CsiVitalPreprocessor、BreathingExtractor、HeartRateExtractor、VitalAnomalyDetector、VitalSignStore、VitalReading、VitalEstimate、AnomalyAlert);绑定 signal DSP 入口(CsiProcessor、CsiProcessorConfig、PhaseSanitizer、MotionDetector、HardwareNormalizer);对 bench 中测得 >0.5 ms 的所有调用执行 GIL 释放(py.allow_threads);集成测试把sample_csi_data.json的 1,000 帧灌进 PyO3 vitals 管线并断言输出确定性;用 P3 绑定重实现witness/verify.py并与 v1 期望哈希比对——注意哈希必然不同(Rust 与 Python 处理器并不逐位一致),因此需生成并发布新的expected_features_v2.sha256。 - P3.5 — BFLD 绑定面(与 P3 并行):
bindings/bfld.rs里BfldFrame/BfldReport/BfldKind的#[pyclass]包装(先以 stub Rust 实现背书,等 v3wifi-densepose-bfldcrate);bfld_stub.rs提供进程内最小存储(压缩反馈矩阵 vec),让 Python API 在摄取 crate 落地前即可用;feedback_matrix(Complex64 ndarray)走与 P3CsiFrame.amplitude相同的 numpy bridge;测试覆盖各带宽构造路径(HE20/HE40/HE80/HE160 + HT20/HT40)、n_subcarriers契约、coherence_score合理性、BfldKind可哈希 + 相等;前向兼容契约测试保证今天从 numpy ndarray 构造的BfldFrame在未来 Rust crate 存在后能经 (de)serialisation 一致 round-trip。 - P4 — WS/MQTT 客户端层(1 周):实现
wifi_densepose.client.ws.SensingClient(asyncio,websockets>=12);实现wifi_densepose.client.mqtt.RuViewMqttClient(paho-mqtt 2.x);加client.ha把 ADR-115 MQTT discovery 载荷解析为 Python dataclass 的辅助;集成测试用 Docker 以--mock-frames拉起 sensing-server,断言SensingClient能收到edge_vitals消息。 - P5 — 首次 cibuildwheel 发布 v2.0.0(1 周):落地
.github/workflows/pip-release.yml的 cibuildwheel 矩阵(5 wheel + sdist);python_requires = ">=3.10";pyproject.toml写最小install_requires(pyo3 是构建期依赖而非运行期;运行时 extras:[client]加websockets>=12,paho-mqtt>=2);各 CI 平台 smoke 测试pip install wifi-densepose==2.0.0;用 Trusted Publisher(OIDC,密钥不存 API token) 发布到 PyPI;公告1.99.0墓碑已在 PyPI、搜索结果的v2.0.0顶替它。 - P6+ — 推迟项:
wifi-densepose-bfldRust crate(Nexmon BFR pcap +mac80211debugfs 摄取,替换 P3.5 stub 而不改 Python API,自持 ADR-1xx);wifi-densepose-nn绑定(libtorch/candle wheel 体积待定);wifi-densepose-ruvector绑定(RuVector attention 类型);MQTT/Matter 集成辅助(client.matter);对wifi-densepose==1.x的弃用公告(PyPI yank);经 pip extra 分发 sensing-server 二进制(pip install wifi-densepose[server]);在 pip 客户端层之上做 HACS Python 集成(ADR-115 后续)。
5. 兼容性与弃用策略
5.1 版本大跳
wifi-densepose==2.0.0 是硬性 major 破坏:1.x 的 src.* 导入命名空间与 2.x 的 wifi_densepose.* 不兼容,不存在透明桥接两者的垫片。
5.2 墓碑版 v1.99.0
发布 v2.0.0 之前,先发布唯一的职责就是把迁移错误抛给用户的 wifi-densepose==1.99.0(纯 Python sdist/wheel):
# wifi_densepose/__init__.py (v1.99.0)
raise ImportError(
"wifi-densepose 1.x has been superseded by v2.0.0 which wraps "
"the Rust-based stack. Run:\n\n"
" pip install wifi-densepose==2.0.0\n\n"
"Migration guide: 见仓库内 pip-migration 说明\n"
"Legacy v1 source: archive/v1/ in the repository"
)
仓库中 python/tombstone/src/wifi_densepose/init.py 正是这份墓碑的实现(含指向仓库内 docs 的说明),确保任何把版本钉在 wifi-densepose>=1 的项目升级到 1.99.0 时得到清晰报错而非静默的坏导入。
5.3 PyPI yank 策略(v2.0.0 稳定后,90 天观察窗口)
- Yank
wifi-densepose==1.0.0——它从未有独立稳定期,发布 4 小时后即被取代; wifi-densepose==1.1.0不 yank 但在描述中标 deprecated;- 把
wifi-densepose==1.99.0作为 1.x 的权威落地页(抛错)。
被 yank 的版本仍可用 pip install wifi-densepose==1.1.0 --force 安装,保证钉住精确版本的可复现构建不被静默破坏。
5.4 Semver 一览
| 版本 | 内容 |
|---|---|
| 1.0.0 – 1.1.0 | 遗留 Python 服务器(archive/v1/) |
| 1.99.0 | 墓碑:ImportError 迁移提示 |
| 2.0.0 | PyO3 Rust 绑定 + WS/MQTT 客户端 |
| 2.x.y | 增量绑定 + 客户端改进 |
| 3.0.0 | 若加入 nn 绑定(libtorch wheel 体积可能迫使拆分独立包) |
6. 落地的仓库状态(源码佐证)
从当前仓库源码结构看,ADR-117 的骨架已在 python/ 目录落地成型,且实现细节与 ADR 原始 sketch 略有演进:
- python/Cargo.toml 定义了 crate
wifi-densepose-py(2.0.0-alpha.1),并注释说明该 crate 刻意位于v2/工作区之外,避免cargo test --workspace连带编译 pyo3;[lib] name = "wifi_densepose_native"、crate-type = ["cdylib", "rlib"];pyo3 采用 0.22 +abi3-py310(而非设计稿中的 0.28); - python/src/lib.rs 是模块注册中心:编译模块名为
_native(module-name = "wifi_densepose._native",见 python/pyproject.toml),导出__rust_version__/__rust_build_tag__/__build_features__与hello()smoke 函数;绑定分模块注册——keypoint、pose(对应 P2 core 类型)、vitals(P3)、bfld(P3.5)、privacy_gate(ADR-118 语义),而 aether/meridian/mat 仅在对应 Cargo feature 打开时编译注册(ADR-185 扩展); - python/wifi_densepose/init.py 就是 §3.3 所画的 Python facade:
__version__ = "2.0.0",从_nativere-exportKeypoint/BoundingBox/PersonPose/PoseEstimate(P2)、VitalStatus/VitalEstimate/VitalReading/BreathingExtractor/HeartRateExtractor(P3)、BfldKind/BfldFrame/BfldReport(P3.5),并带上 PEP 561 的py.typed与.pyi存根; - python/src/bindings/vitals.rs 展示了 P3 的 GIL 释放实践:
BreathingExtractor(0.1–0.5 Hz 带通 → 呼吸率)与HeartRateExtractor(0.8–2.0 Hz 带通 + 自相关 → 心率)的extract都包在py.allow_threads(|| ...)里执行纯同步 DSP,并提供esp32_default()(56 子载波、100 Hz、30 s/15 s 窗口)等便捷静态构造器; - python/wifi_densepose/client/ 已包含 P4 的三个客户端模块(ws/mqtt/ha 以及 ADR-115 语义原语辅助 primitives);python/wifi_densepose/client/ws.py 把 sensing-server 的
connection_established/pose_data/edge_vitals三类 JSON 消息解码为 frozen dataclass,支持Authorization: Bearer(token 取RUVIEW_API_TOKEN环境变量或构造参数),并对websockets>=12内 header kwarg 的改名做了签名自检; - python/pyproject.toml 中
version = "2.0.0"、requires-python = ">=3.10"、maturin 构建后端、strip = true控制 wheel 体积,并配置了[client](websockets>=12.0、paho-mqtt>=2.1)、[aether]/[meridian]/[mat]/[sota]等 optional extras; - python/tombstone/ 存放
1.99.0墓碑包源码与测试,对应 §5.2; - 测试侧,python/tests/ 下
test_smoke.py、test_vitals.py、test_bfld.py、test_client_ws.py、test_client_mqtt.py、test_client_ha.py等对应 P2–P4 各阶段验收;Rust 侧还保留 golden 与 SHA-256 parity 测试来锁定确定性输出。
需要提醒:ADR 文档状态仍标 Proposed,上述目录呈现的是仓库内正在推进的实现快照;个别字段(pyo3 大版本、模块名 _native vs _core、crate 放置位置)与决策稿不完全一致,均以仓库源码为准。
7. 风险登记
| 风险 | 可能性 | 严重度 | 缓解 |
|---|---|---|---|
| 构建矩阵复杂度——5 个目标 triple × cibuildwheel 配置、CI 时间、aarch64 交叉编译需 QEMU | 高 | 中 | 用 abi3-py310(5 个 wheel 而非 20 个);GitHub Actions 自带 QEMU aarch64 模拟;maturin 自动处理 auditwheel |
| 二进制体积——未来 nn/ONNX 绑定可能把 wheel 推到 50 MB 以上 | 中 | 高 | nn 绑定放独立 wifi-densepose-nn PyPI 包;core+vitals+signal wheel 保持精简(约 2 MB stripped) |
GIL / async 问题——用 PyO3 包 tokio crate 需要谨慎管理 runtime;所有阻塞 Rust 调用必须用 py.allow_threads |
高 | 高 | 初始绑定只做同步 Rust API(vitals/signal/core 全为 sync);async 的 sensing-server 客户端留在纯 Python 的 client/ws.py |
| 维护者开销——两种语言、两套构建系统、一个 PyPI 包 | 中 | 中 | maturin 统一构建;CI 负责发布;只从 3 个被绑 crate 起步 |
1.x 用户破坏——钉在 wifi-densepose>=1,<2 的用户会撞上墓碑 |
低 | 中 | 1.99.0 墓碑给出清晰报错;v2 后 1.1.0 在 PyPI 上保持 90 天不 yank |
| Windows Rust 工具链——Windows 上链接 PyO3 需要 MSVC 或 mingw,额外 CI 复杂度 | 中 | 中 | GitHub Actions windows-latest 自带 MSVC;maturin + cibuildwheel 原生处理 |
稳定 ABI 限制——abi3 会排除部分高级 PyO3 特性(如 Buffer 协议) |
低 | 低 | core/vitals/signal 类型是标量/Vec,P2–P3 不需要 buffer 协议 |
PyPI 名字归属——项目拥有 wifi-densepose 这个名字(rUv author 字段已确认) |
低 | 低 | 发布前以 pypi.org/user/ruvnet 复核 |
8. 验收标准(全部通过才视为 Accepted)
pip install wifi-densepose==2.0.0在 Python 3.10/3.11/3.12/3.13 × linux/x86_64、macos/arm64、windows/amd64 的全新 venv 中无需额外构建工具即可成功;python -c "import wifi_densepose; print(wifi_densepose.__version__)"输出2.0.0;python -c "from wifi_densepose import CsiFrame; f = CsiFrame([1.0]*56, [0.0]*56, 56, 0, 100.0); print(f)"产生非报错的 repr;- 4 阶段 vitals 管线在参考机器(CPython 3.12、linux x86_64、无 GPU)上处理 1,000 帧 耗时低于 500 ms;
wifi_densepose.witness.verify_bundle(path)对 scripts/generate-witness-bundle.sh 新生成的 bundle 返回verdict="PASS";SensingClient能在 5 秒内从sensing-server --mock-frames收到至少一条edge_vitals消息;pip install wifi-densepose==1.99.0抛出带迁移指引的ImportError;- 编译出的
_core扩展除 libc/msvcrt 外无未解析动态库依赖(Linux 用auditwheel show、macOS 用delocate-listdeps验证); - 类型存根(
wifi_densepose/*.pyi)存在,且mypy --strict通过示例代码; - core+vitals+signal 每平台 wheel 总大小 ≤ 5 MB。
9. 悬而未决的问题(Open Questions)
- 稳定 ABI 基线版本:
abi3-py310会丢掉 v1.1.0 声明支持的 Python 3.9。3.9 已于 2025-10-05 EOL,是否干净利落地弃用?暂定:是,弃 3.9,用 abi3-py310。 - nn 绑定包名:若
wifi-densepose-nn需要约 30 MB libtorch wheel,应放独立 PyPI 包wifi-densepose-nn,还是作wifi-densepose[nn]可选重 extra?暂定:独立包,避免污染精简 wheel。 - Witness 哈希连续性:Rust 管线对同样的输入帧会产出与 v1 Python 管线不同的 SHA-256,新的
expected_features_v2.sha256必须在 v2.0.0 前生成并提交。谁来生成、生成过程如何被见证?暂定:CI 中生成、哈希提交进 proof 目录并纳入 ADR-028 矩阵。 ruv-neuralcrate:工作区内已有v2/crates/ruv-neural/,它是否适合提前做 Python 绑定(利于训练循环脚本化)?暂定:推迟——它依赖训练后端。- Tokio runtime:sensing-server 基于 tokio,但 P2–P3 绑定的三个 crate 都是同步的。是否存在隐藏 tokio 依赖会逼扩展模块引入 runtime?暂定:P1 scaffold 前逐个检查各 crate Cargo.toml 的 tokio 依赖。
pyo3-stub-gen还是手写存根:对泛型与 newtype 模式自动生成有毛刺。暂定:首版用pyo3-stub-gen搭骨架、公开 API 手工精修。wifi_denseposevswifi-densepose命名空间:PyPI 名用连字符、Python 导入用下划线;v1 包走src.*而非wifi_densepose.*,是否有工具硬编码了src?暂定:src.*是 archive/v1 专属,干净丢弃。- cibuildwheel 版本:现有 GitHub Actions 是
cargo build/build.py模式,是否需为 maturin 构建更新?暂定:加独立pip-release.yml,不改既有 Rust CI。 - RuVector 绑定时间线:
wifi-densepose-ruvector依赖ruvector-gnn = "2.0.5",它是否以预构建静态库分发、还是构建时链接?这直接影响 P6+ wheel 体积。暂定:承诺时间线前先查 ruvector-gnn 链接策略。 client.ha与 ADR-115/116 的冲突:ha.py不应在 Python 侧重复 ADR-115 的 MQTT discovery 逻辑。只读(解析 HA discovery JSON → dataclass)还是也写(发布 discovery JSON)?暂定:v2.0 只读;写路径推迟到 HACS 集成后续(ADR-115 §6.A)。- BFLD Rust crate 归属(2026-05-24 新增):P3.5 绑定以
bfld_stub.rs打底,正式 crate(Nexmon BFR pcap 解析 +mac80211debugfs 摄取)应作新工作区成员wifi-densepose-bfld,还是扩展wifi-densepose-signal?暂定:独立新 crate——BFR 解析代码量大(Wireshark dissector 约 2k 行)会撑爆-signal;且 BFLD 摄取是可选项,独立 crate 让默认-signal保持精简。 - BFLD 各厂商压缩角变体(2026-05-24 新增):802.11 标准化了压缩反馈格式,但 Broadcom/Intel/Qualcomm/MediaTek 在 psi/phi 量化步长与矩阵条目顺序上有差异。多少归一化应放 Python 绑定、多少放未来 Rust crate?暂定:Python 绑定保持"笨"(numpy 进 numpy 出、不解码);未来 Rust crate 拥有按厂商归一化,经构造器上的
Vendor枚举暴露。
10. 关联决策与其他参考
ADR-117 不是孤立的架构决策,它串联了 RuView 生态中一串相邻决策记录,理解这些上下文有助于把握其位置:
- ADR-115 Home Assistant 集成——定义了
client/mqtt.py/client/ha.py消费的 MQTT 主题结构与 discovery 语义;ADR-117 是让 Python 自动化能访问这套面的通道; - ADR-116 HA-COG Seed 打包——Seed 可安装产物,与 ADR-117 面向开发者的库平行而非重叠;
- ADR-185 Python P6+ SOTA 绑定——AETHER/MERIDIAN/MAT 三个 SOTA 子系统的编译 feature 化绑定,正是 §3.2 中"落地演进"分支的直接后续;
- ADR-021(ESP32 vitals)、ADR-110(ESP32-C6 固件扩展)——定义了 vitals 提取管线与固件遥测,是 P3 绑定的上游语义;
- ADR-118 BFLD 检测层——
python/src/lib.rs中privacy_gate绑定(PrivacyClass、身份风险打分)来自 ADR-118; - ADR-024 对比式 CSI embedding、docs/research/soul/ soul-signature 研究——是 §3.7 BFLD 稠密子载波剖面未来消费方的能力佐证;
- v1 证据文件:archive/v1/setup.py(40 依赖、双入口、
src.*命名空间)、archive/v1/src/init.py(FastAPI/服务类导出); - 落地实现:
python/目录(详见 §6 列表),其中 tombstone 位于 python/tombstone/。
结语
ADR-117 用一句话概括:不再让"Python 生态入口"与"Rust 生产实现"长期分叉。通过 PyO3 + maturin 把 wifi-densepose 重塑为"编译型核心 + 纯 Python 客户端 + 独立 CLI 垫片"的三层结构,用 abi3-py310 把构建矩阵压到 5 个 wheel,用墓碑版 + yank 策略护住 1.x 老用户,并用重挂到 Rust 管线的 witness 链保证"证明的就是生产跑的"。对需要在自己的栈里做同类"纯 Python → Rust 绑定"跃迁的开发者而言,本文梳理的选型判据表、GIL 释放写法、稳定 ABI 取舍、灰度发布顺序与验收清单,都是一份可以直接对照执行的地图。仓库内 python/ 目录已经让这份设计有了可读的骨架实现,值得顺着 python/pyproject.toml 与 python/src/lib.rs 继续深读。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00