30 minutos | Nivel Intermedio-Avanzado | Domine los principios de estimación de postura WiFi CSI, despliegue versiones en Python/Rust y conecte nodos de sensores ESP32 a un pipeline de extremo a extremo
Lectores objetivo
- Desarrolladores interesados en la detección WiFi y computación ubicua.
- Ingenieros con bases en Python que deseen explorar la inferencia de alto rendimiento con Rust.
- Investigadores que busquen alternativas a la percepción humana basada en cámaras.
Requisitos mínimos: Capacidad para comprender clases en Python y sintaxis básica de Rust, contar con un entorno Linux/macOS o WSL2.
Dependencias principales y entorno
| Dependencia | Versión requerida | Función |
|---|---|---|
| Python | ≥ 3.9 | Base de código principal v1 |
| Rust | ≥ 1.75 | Puerto de inferencia de alto rendimiento |
| PyTorch | ≥ 2.1 | Inferencia de redes neuronales |
| NumPy + SciPy | Última versión estable | Procesamiento de señales CSI |
| OpenCV | ≥ 4.8 | Visualización de posturas |
| Placa ESP32-S3 | 8MB Flash | Nodo de hardware WiFi CSI (opcional) |
| Router con soporte AP | 802.11n o superior | Fuente de datos CSI |
WARNING
El ESP32 original y el ESP32-C3 no admiten la captura de CSI. El proyecto requiere explícitamente ESP32-S3 (Dual-core Xtensa) o ESP32-C6 + módulo mmWave de 60GHz.
Estructura completa del proyecto
RuView/
├── v1/ # Base de código principal en Python
│ ├── src/
│ │ ├── core/
│ │ │ ├── csi_processor.py # Estructura de datos y procesamiento CSI
│ │ │ ├── phase_sanitizer.py # Corrección de ruido de fase
│ │ │ └── router_interface.py # Comunicación Router/ESP32
│ │ ├── hardware/
│ │ │ ├── csi_extractor.py # Extracción de datos serie ESP32
│ │ │ └── router_interface.py # Captura de tramas WiFi CSI
│ │ └── services/
│ │ └── ... # Pipeline de procesamiento de señales
│ ├── data/
│ │ └── proof/
│ │ ├── sample_csi_data.json # 1000 tramas de CSI sintético (seed=42)
│ │ └── verify.py # Script de verificación SHA-256 del pipeline
│ └── tests/ # Suite de pruebas pytest
├── rust-port/wifi-densepose-rs/ # Adaptación a Rust
│ └── crates/
│ ├── wifi-densepose-core/ # Tipos principales, primitivas de tramas CSI
│ ├── wifi-densepose-signal/ # Procesamiento de señal SOTA RuvSense (14 módulos)
│ ├── wifi-densepose-nn/ # Backend de inferencia ONNX/PyTorch/Candle
│ ├── wifi-densepose-train/ # Pipeline de entrenamiento RuVector
│ ├── wifi-densepose-hardware/ # Protocolo TDM para ESP32
│ └── wifi-densepose-api/ # API REST con Axum
├── firmware/esp32-csi-node/ # Código fuente del firmware ESP32-S3
├── docs/adr/ # 43 Registros de Decisiones de Arquitectura (ADR)
└── pyproject.toml # Definición de dependencias Python
Guía paso a paso
Paso 1: Comprender el principio de WiFi CSI (Simulación en Python sin hardware)
La esencia de WiFi CSI es: utilizar los cambios de amplitud y fase causados por el reflejo del cuerpo humano cuando las señales inalámbricas se propagan en el espacio para lograr una percepción sin contacto.
Cuando una persona se mueve entre un dispositivo WiFi (router) y el receptor, el reporte CSI registra los valores complejos de cada subportadora, incluyendo la amplitud y la fase. Aunque estos cambios son minúsculos, bastan para deducir la postura y el movimiento del cuerpo.
La genialidad de RuView reside en que convierte la señal CSI en una estimación de postura de 17 puntos clave (keypoints) a través de un pipeline completo (corrección de fase → supresión de trayectorias múltiples → inferencia de red neuronal).
Ejecute el proceso completo con datos sintéticos antes de conectar el hardware:
# Clonar el proyecto
git clone https://github.com/ruvnet/RuView.git
cd RuView
# Instalar dependencias de Python
cd v1
pip install -e ".[dev]"
# Ejecutar la verificación determinista del pipeline (sin hardware)
python data/proof/verify.py
Si todo es correcto, verá:
===== WiFi-DensePose Pipeline Verification =====
Generator: generate_reference_signal.py v1.0.0
Seed: 42
Frames: 1000 | Antennas: 3 | Subcarriers: 56
Loading sample CSI data... done
Running pipeline...
[1/4] Phase sanitization ............ OK
[2/4] Multistatic signal processing .. OK
[3/4] Neural network inference ....... OK
[4/4] Keypoint extraction ........... OK
Computing reference hash (SHA-256)...
VERDICT: PASS
TIP
Este script de verificación utiliza datos CSI sintéticos generados con seed=42, por lo que todos los resultados son reproducibles. Si supera este paso, su entorno Python está listo.
Paso 2: Preparación del entorno (Python + Rust + WSL2)
Entorno Python (Recomendado venv)
# Se recomienda Python 3.10 o 3.11 por compatibilidad
python3.10 -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows PowerShell
pip install --upgrade pip
pip install -e ".[gpu]" # Si tiene GPU NVIDIA
pip install -e ".[dev]" # Dependencias de desarrollo (incluye pytest)
Entorno Rust (Para puerto de inferencia de alto rendimiento)
# Instalar Rust (si no lo tiene)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Verificar
rustc --version # Debe ser 1.75+
cargo --version # Debe ser 1.75+
# Soporte de toolchain Rust para desarrollo ESP32 (opcional)
rustup target add xtensa-esp32-elf riscv32imc-esp-elf
Usuarios de Windows: WSL2 es obligatorio
# Ejecutar en PowerShell como administrador
wsl --install
# Entrar en WSL tras reiniciar
wsl -d Ubuntu-22.04
WARNING
ESP-IDF tiene conflictos con Git Bash nativo de Windows (la variable MSYSTEM interfiere con ESP-IDF v5.4). Se recomienda usar WSL2 o PowerShell puro para compilar el firmware.
Paso 3: Ejecución del pipeline de estimación en Python
Ahora utilizaremos código real para procesar datos CSI en el pipeline de inferencia.
3.1 Estructura de datos CSI
# v1/src/core/csi_processor.py
from __future__ import annotations
import numpy as np
from dataclasses import dataclass
from typing import List
@dataclass
class CSIFrame:
"""Trama CSI individual — corresponde a una observación de canal WiFi"""
timestamp_s: float
amplitude: np.ndarray # shape: (num_antennas, num_subcarriers)
phase: np.ndarray # shape: (num_antennas, num_subcarriers)
subcarrier_indices: np.ndarray # Índices de subportadora
@classmethod
def from_json(cls, data: dict) -> CSIFrame:
return cls(
timestamp_s=data["timestamp_s"],
amplitude=np.array(data["amplitude"], dtype=np.float32),
phase=np.zeros_like(data["amplitude"], dtype=np.float32), # Inicialización manual
subcarrier_indices=np.arange(56), # 56 subportadoras por defecto
)
@dataclass
class PoseKeypoints:
"""Resultados de postura de 17 puntos clave COCO"""
keypoints: List[np.ndarray] # (x, y, confianza) para cada punto
pose_id: int
def get_skeleton(self) -> np.ndarray:
"""Retorna array de coordenadas (N, 2) para dibujo"""
return np.array([p[:2] for p in self.keypoints])
3.2 Corrección de fase (Eliminación de desviación LO)
La fase CSI original está muy contaminada por el ruido del oscilador local (LO) y debe corregirse:
# v1/src/core/phase_sanitizer.py
import numpy as np
def sanitize_phase(csi: np.ndarray) -> np.ndarray:
"""
Estimación iterativa de desviación de fase LO + corrección de media circular
Ref: ADR-014 SOTA Signal Processing
"""
num_antennas, num_subcarriers = csi.shape
phase = np.angle(csi)
for _ in range(3):
phase_offset = np.zeros(num_antennas)
for a in range(1, num_antennas):
delta = phase[a] - phase[0]
delta_mean = np.arctan2(
np.mean(np.sin(delta)),
np.mean(np.cos(delta))
)
phase_offset[a] = delta_mean
phase[a] -= phase_offset[a]
return phase
3.3 Flujo de inferencia de extremo a extremo
Ejecute el script de demostración:
python v1/demo_pipeline.py
# Ejemplo de salida:
# Loaded 1000 CSI frames
# Frame 0: 17 keypoints, avg confidence=0.901
# ...
# Pipeline run complete.
Paso 4: Compilación y pruebas en Rust (Inferencia ultrarrápida)
La versión de Rust es el camino de producción capaz de alcanzar latencias de milisegundos.
cd rust-port/wifi-densepose-rs
# Verificar compilación (sin GPU)
cargo check -p wifi-densepose-core --no-default-features
# Ejecutar suite de pruebas (más de 1,000 pruebas)
cargo test --workspace --no-default-features
Paso 5: Grabación de firmware ESP32-S3 y conexión WiFi
5.1 Compilación de firmware
cd firmware/esp32-csi-node
cp sdkconfig.defaults.8mb sdkconfig.defaults
# Activar entorno ESP-IDF y compilar
idf.py build
5.2 Grabación y provisión
idf.py -p /dev/ttyUSB0 flash monitor
# Configurar WiFi y destino de datos
python provision.py --ssid "SuSSID" --password "SuPassword" --target-ip 192.168.1.20
Paso 6: Verificación final — De CSI a Postura
Conecte el flujo de datos del ESP32 con el extractor y la API de Rust:
# Terminal 1: Extractor CSI
python -m src.hardware.csi_extractor --port /dev/ttyUSB0
# Terminal 2: API REST (Servicio Axum en Rust)
cd rust-port/wifi-densepose-rs
cargo run -p wifi-densepose-api
Consulte los resultados mediante curl:
curl http://localhost:8080/api/v1/poses/current
Solución de problemas comunes
P1: El ESP32 no envía datos CSI Verifique que tanto el router como el ESP32 soporten 802.11n. Algunos routers requieren activar manualmente la captura de CSI.
P2: Error numpy.linalg.LinAlgError
Suele deberse a incompatibilidad de versiones. Asegúrese de usar numpy >= 1.24 y scipy >= 1.11.
P3: La postura salta mucho o hay mucho ruido
Es común por interferencia de trayectorias múltiples. Intente rotar las antenas 45° o aumente los parámetros de supresión en field_model.rs.
Lecturas adicionales / Direcciones avanzadas
- Fusión de atención multi-vista RuvSense: Utilice el mecanismo
CrossViewpointAttentionpara combinar perspectivas de múltiples nodos ESP32. - Arquitectura ADR: Lea ADR-014 para entender la selección del procesamiento de señales SOTA.
- Entrenamiento con RuVector: Use el crate
wifi-densepose-trainpara ajustar el modelo ONNX con sus propios conjuntos de datos CSI.