Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e3f0c7a

Browse files
committed
fix: Eliminate remaining mock data paths, isolate test infrastructure
- core/router_interface.py: Replace placeholder _collect_real_csi_data() with explicit RuntimeError directing users to hardware setup docs - hardware/router_interface.py: Replace np.random.rand() in _parse_csi_response() with RouterConnectionError requiring real parser - testing/: New isolated module for mock data generation (moved out of production code paths per ADR-011) - sensing/: Initialize commodity sensing module (ADR-013) No production code path returns random data. Mock mode requires explicit opt-in via WIFI_DENSEPOSE_MOCK=true environment variable. https://claude.ai/code/session_01Ki7pvEZtJDvqJkmyn6B714
1 parent fd493e5 commit e3f0c7a

5 files changed

Lines changed: 286 additions & 19 deletions

File tree

v1/src/core/router_interface.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,23 @@ def _generate_mock_csi_data(self) -> np.ndarray:
195195
return csi_data
196196

197197
async def _collect_real_csi_data(self) -> Optional[np.ndarray]:
198-
"""Collect real CSI data from router (placeholder implementation)."""
199-
# This would implement the actual CSI data collection
200-
# For now, return None to indicate no real implementation
201-
self.logger.warning("Real CSI data collection not implemented")
202-
return None
198+
"""Collect real CSI data from the router.
199+
200+
Raises:
201+
RuntimeError: Always in the current state, because real CSI
202+
data collection requires hardware setup that has not been
203+
configured. This method must never silently return random
204+
or placeholder data.
205+
"""
206+
raise RuntimeError(
207+
f"Real CSI data collection from router '{self.router_id}' requires "
208+
"hardware setup that is not configured. You must: "
209+
"(1) install CSI-capable firmware (e.g., Atheros CSI Tool, Nexmon CSI) on the router, "
210+
"(2) configure the SSH connection to the router, and "
211+
"(3) implement the CSI extraction command for your specific firmware. "
212+
"For development/testing, use mock_mode=True. "
213+
"See docs/hardware-setup.md for complete setup instructions."
214+
)
203215

204216
async def check_health(self) -> bool:
205217
"""Check if the router connection is healthy.

v1/src/hardware/router_interface.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -197,25 +197,25 @@ async def health_check(self) -> bool:
197197

198198
def _parse_csi_response(self, response: str) -> CSIData:
199199
"""Parse CSI response data.
200-
200+
201201
Args:
202202
response: Raw response from router
203-
203+
204204
Returns:
205205
Parsed CSI data
206+
207+
Raises:
208+
RouterConnectionError: Always in current state, because real CSI
209+
parsing from router command output requires hardware-specific
210+
format knowledge that must be implemented per router model.
206211
"""
207-
# Mock implementation for testing
208-
# In real implementation, this would parse actual router CSI format
209-
return CSIData(
210-
timestamp=datetime.now(timezone.utc),
211-
amplitude=np.random.rand(3, 56),
212-
phase=np.random.rand(3, 56),
213-
frequency=2.4e9,
214-
bandwidth=20e6,
215-
num_subcarriers=56,
216-
num_antennas=3,
217-
snr=15.0,
218-
metadata={'source': 'router', 'raw_response': response}
212+
raise RouterConnectionError(
213+
"Real CSI data parsing from router responses is not yet implemented. "
214+
"Collecting CSI data from a router requires: "
215+
"(1) a router with CSI-capable firmware (e.g., Atheros CSI Tool, Nexmon), "
216+
"(2) proper hardware setup and configuration, and "
217+
"(3) a parser for the specific binary/text format produced by the firmware. "
218+
"See docs/hardware-setup.md for instructions on configuring your router for CSI collection."
219219
)
220220

221221
def _parse_status_response(self, response: str) -> Dict[str, Any]:

v1/src/sensing/__init__.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""
2+
Commodity WiFi Sensing Module (ADR-013)
3+
=======================================
4+
5+
RSSI-based presence and motion detection using standard Linux WiFi metrics.
6+
This module provides real signal processing from commodity WiFi hardware,
7+
extracting presence and motion features from RSSI time series.
8+
9+
Components:
10+
- rssi_collector: Data collection from Linux WiFi interfaces
11+
- feature_extractor: Time-domain and frequency-domain feature extraction
12+
- classifier: Presence and motion classification from features
13+
- backend: Common sensing backend interface
14+
15+
Capabilities:
16+
- PRESENCE: Detect whether a person is present in the sensing area
17+
- MOTION: Classify motion level (absent / still / active)
18+
19+
Note: This module uses RSSI only. For higher-fidelity sensing (respiration,
20+
pose estimation), CSI-capable hardware and the full DensePose pipeline
21+
are required.
22+
"""
23+
24+
from v1.src.sensing.rssi_collector import (
25+
LinuxWifiCollector,
26+
SimulatedCollector,
27+
WifiSample,
28+
)
29+
from v1.src.sensing.feature_extractor import (
30+
RssiFeatureExtractor,
31+
RssiFeatures,
32+
)
33+
from v1.src.sensing.classifier import (
34+
PresenceClassifier,
35+
SensingResult,
36+
MotionLevel,
37+
)
38+
from v1.src.sensing.backend import (
39+
SensingBackend,
40+
CommodityBackend,
41+
Capability,
42+
)
43+
44+
__all__ = [
45+
"LinuxWifiCollector",
46+
"SimulatedCollector",
47+
"WifiSample",
48+
"RssiFeatureExtractor",
49+
"RssiFeatures",
50+
"PresenceClassifier",
51+
"SensingResult",
52+
"MotionLevel",
53+
"SensingBackend",
54+
"CommodityBackend",
55+
"Capability",
56+
]

v1/src/testing/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""
2+
Testing utilities for WiFi-DensePose.
3+
4+
This module contains mock data generators and testing helpers that are
5+
ONLY intended for use in development/testing environments. These generators
6+
produce synthetic data that mimics real CSI and pose data patterns.
7+
8+
WARNING: Code in this module uses random number generation intentionally
9+
for mock/test data. Do NOT import from this module in production code paths
10+
unless behind an explicit mock_mode flag with appropriate logging.
11+
"""
12+
13+
from .mock_csi_generator import MockCSIGenerator
14+
from .mock_pose_generator import generate_mock_poses, generate_mock_keypoints, generate_mock_bounding_box
15+
16+
__all__ = [
17+
"MockCSIGenerator",
18+
"generate_mock_poses",
19+
"generate_mock_keypoints",
20+
"generate_mock_bounding_box",
21+
]
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
"""
2+
Mock CSI data generator for testing and development.
3+
4+
This module provides synthetic CSI (Channel State Information) data generation
5+
for use in development and testing environments ONLY. The generated data mimics
6+
realistic WiFi CSI patterns including multipath effects, human motion signatures,
7+
and noise characteristics.
8+
9+
WARNING: This module uses np.random intentionally for test data generation.
10+
Do NOT use this module in production data paths.
11+
"""
12+
13+
import logging
14+
import numpy as np
15+
from typing import Dict, Any, Optional
16+
17+
logger = logging.getLogger(__name__)
18+
19+
# Banner displayed when mock mode is active
20+
MOCK_MODE_BANNER = """
21+
================================================================================
22+
WARNING: MOCK MODE ACTIVE - Using synthetic CSI data
23+
24+
All CSI data is randomly generated and does NOT represent real WiFi signals.
25+
For real pose estimation, configure hardware per docs/hardware-setup.md.
26+
================================================================================
27+
"""
28+
29+
30+
class MockCSIGenerator:
31+
"""Generator for synthetic CSI data used in testing and development.
32+
33+
This class produces complex-valued CSI matrices that simulate realistic
34+
WiFi channel characteristics including:
35+
- Per-antenna and per-subcarrier amplitude/phase variation
36+
- Simulated human movement signatures
37+
- Configurable noise levels
38+
- Temporal coherence across consecutive frames
39+
40+
This is ONLY for testing. Production code must use real hardware data.
41+
"""
42+
43+
def __init__(
44+
self,
45+
num_subcarriers: int = 64,
46+
num_antennas: int = 4,
47+
num_samples: int = 100,
48+
noise_level: float = 0.1,
49+
movement_freq: float = 0.5,
50+
movement_amplitude: float = 0.3,
51+
):
52+
"""Initialize mock CSI generator.
53+
54+
Args:
55+
num_subcarriers: Number of OFDM subcarriers to simulate
56+
num_antennas: Number of antenna elements
57+
num_samples: Number of temporal samples per frame
58+
noise_level: Standard deviation of additive Gaussian noise
59+
movement_freq: Frequency of simulated human movement (Hz)
60+
movement_amplitude: Amplitude of movement-induced CSI variation
61+
"""
62+
self.num_subcarriers = num_subcarriers
63+
self.num_antennas = num_antennas
64+
self.num_samples = num_samples
65+
self.noise_level = noise_level
66+
self.movement_freq = movement_freq
67+
self.movement_amplitude = movement_amplitude
68+
69+
# Internal state for temporal coherence
70+
self._phase = 0.0
71+
self._frequency = 0.1
72+
self._amplitude_base = 1.0
73+
74+
self._banner_shown = False
75+
76+
def show_banner(self) -> None:
77+
"""Display the mock mode warning banner (once per session)."""
78+
if not self._banner_shown:
79+
logger.warning(MOCK_MODE_BANNER)
80+
self._banner_shown = True
81+
82+
def generate(self) -> np.ndarray:
83+
"""Generate a single frame of mock CSI data.
84+
85+
Returns:
86+
Complex-valued numpy array of shape
87+
(num_antennas, num_subcarriers, num_samples).
88+
"""
89+
self.show_banner()
90+
91+
# Advance internal phase for temporal coherence
92+
self._phase += self._frequency
93+
94+
time_axis = np.linspace(0, 1, self.num_samples)
95+
96+
csi_data = np.zeros(
97+
(self.num_antennas, self.num_subcarriers, self.num_samples),
98+
dtype=complex,
99+
)
100+
101+
for antenna in range(self.num_antennas):
102+
for subcarrier in range(self.num_subcarriers):
103+
# Base amplitude varies with antenna and subcarrier
104+
amplitude = (
105+
self._amplitude_base
106+
* (1 + 0.2 * np.sin(2 * np.pi * subcarrier / self.num_subcarriers))
107+
* (1 + 0.1 * antenna)
108+
)
109+
110+
# Phase with spatial and frequency variation
111+
phase_offset = (
112+
self._phase
113+
+ 2 * np.pi * subcarrier / self.num_subcarriers
114+
+ np.pi * antenna / self.num_antennas
115+
)
116+
117+
# Simulated human movement
118+
movement = self.movement_amplitude * np.sin(
119+
2 * np.pi * self.movement_freq * time_axis
120+
)
121+
122+
signal_amplitude = amplitude * (1 + movement)
123+
signal_phase = phase_offset + movement * 0.5
124+
125+
# Additive complex Gaussian noise
126+
noise = np.random.normal(0, self.noise_level, self.num_samples) + 1j * np.random.normal(
127+
0, self.noise_level, self.num_samples
128+
)
129+
130+
csi_data[antenna, subcarrier, :] = (
131+
signal_amplitude * np.exp(1j * signal_phase) + noise
132+
)
133+
134+
return csi_data
135+
136+
def configure(self, config: Dict[str, Any]) -> None:
137+
"""Update generator parameters.
138+
139+
Args:
140+
config: Dictionary with optional keys:
141+
- sampling_rate: Adjusts internal frequency
142+
- noise_level: Sets noise standard deviation
143+
- num_subcarriers: Updates subcarrier count
144+
- num_antennas: Updates antenna count
145+
- movement_freq: Updates simulated movement frequency
146+
- movement_amplitude: Updates movement amplitude
147+
"""
148+
if "sampling_rate" in config:
149+
self._frequency = config["sampling_rate"] / 1000.0
150+
if "noise_level" in config:
151+
self.noise_level = config["noise_level"]
152+
if "num_subcarriers" in config:
153+
self.num_subcarriers = config["num_subcarriers"]
154+
if "num_antennas" in config:
155+
self.num_antennas = config["num_antennas"]
156+
if "movement_freq" in config:
157+
self.movement_freq = config["movement_freq"]
158+
if "movement_amplitude" in config:
159+
self.movement_amplitude = config["movement_amplitude"]
160+
161+
def get_router_info(self) -> Dict[str, Any]:
162+
"""Return mock router hardware information.
163+
164+
Returns:
165+
Dictionary mimicking router hardware info for testing.
166+
"""
167+
return {
168+
"model": "Mock Router",
169+
"firmware": "1.0.0-mock",
170+
"wifi_standard": "802.11ac",
171+
"antennas": self.num_antennas,
172+
"supported_bands": ["2.4GHz", "5GHz"],
173+
"csi_capabilities": {
174+
"max_subcarriers": self.num_subcarriers,
175+
"max_antennas": self.num_antennas,
176+
"sampling_rate": 1000,
177+
},
178+
}

0 commit comments

Comments
 (0)