Major changes: - Organized Python v1 implementation into v1/ subdirectory - Created Rust workspace with 9 modular crates: - wifi-densepose-core: Core types, traits, errors - wifi-densepose-signal: CSI processing, phase sanitization, FFT - wifi-densepose-nn: Neural network inference (ONNX/Candle/tch) - wifi-densepose-api: Axum-based REST/WebSocket API - wifi-densepose-db: SQLx database layer - wifi-densepose-config: Configuration management - wifi-densepose-hardware: Hardware abstraction - wifi-densepose-wasm: WebAssembly bindings - wifi-densepose-cli: Command-line interface Documentation: - ADR-001: Workspace structure - ADR-002: Signal processing library selection - ADR-003: Neural network inference strategy - DDD domain model with bounded contexts Testing: - 69 tests passing across all crates - Signal processing: 45 tests - Neural networks: 21 tests - Core: 3 doc tests Performance targets: - 10x faster CSI processing (~0.5ms vs ~5ms) - 5x lower memory usage (~100MB vs ~500MB) - WASM support for browser deployment
712 lines
25 KiB
Python
712 lines
25 KiB
Python
"""
|
|
Hardware simulation mocks for testing.
|
|
|
|
Provides realistic hardware behavior simulation for routers and sensors.
|
|
"""
|
|
|
|
import asyncio
|
|
import numpy as np
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, Any, List, Optional, Callable, AsyncGenerator
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
import json
|
|
import random
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
|
|
|
|
class RouterStatus(Enum):
|
|
"""Router status enumeration."""
|
|
OFFLINE = "offline"
|
|
CONNECTING = "connecting"
|
|
ONLINE = "online"
|
|
ERROR = "error"
|
|
MAINTENANCE = "maintenance"
|
|
|
|
|
|
class SignalQuality(Enum):
|
|
"""Signal quality levels."""
|
|
POOR = "poor"
|
|
FAIR = "fair"
|
|
GOOD = "good"
|
|
EXCELLENT = "excellent"
|
|
|
|
|
|
@dataclass
|
|
class RouterConfig:
|
|
"""Router configuration."""
|
|
router_id: str
|
|
frequency: float = 5.8e9 # 5.8 GHz
|
|
bandwidth: float = 80e6 # 80 MHz
|
|
num_antennas: int = 4
|
|
num_subcarriers: int = 64
|
|
tx_power: float = 20.0 # dBm
|
|
location: Dict[str, float] = field(default_factory=lambda: {"x": 0, "y": 0, "z": 0})
|
|
firmware_version: str = "1.2.3"
|
|
|
|
|
|
class MockWiFiRouter:
|
|
"""Mock WiFi router with CSI capabilities."""
|
|
|
|
def __init__(self, config: RouterConfig):
|
|
self.config = config
|
|
self.status = RouterStatus.OFFLINE
|
|
self.signal_quality = SignalQuality.GOOD
|
|
self.is_streaming = False
|
|
self.connected_devices = []
|
|
self.csi_data_buffer = []
|
|
self.error_rate = 0.01 # 1% error rate
|
|
self.latency_ms = 5.0
|
|
self.throughput_mbps = 100.0
|
|
self.temperature_celsius = 45.0
|
|
self.uptime_seconds = 0
|
|
self.last_heartbeat = None
|
|
self.callbacks = {
|
|
"on_status_change": [],
|
|
"on_csi_data": [],
|
|
"on_error": []
|
|
}
|
|
self._streaming_task = None
|
|
self._heartbeat_task = None
|
|
|
|
async def connect(self) -> bool:
|
|
"""Connect to router."""
|
|
if self.status != RouterStatus.OFFLINE:
|
|
return False
|
|
|
|
self.status = RouterStatus.CONNECTING
|
|
await self._notify_status_change()
|
|
|
|
# Simulate connection delay
|
|
await asyncio.sleep(0.1)
|
|
|
|
# Simulate occasional connection failures
|
|
if random.random() < 0.05: # 5% failure rate
|
|
self.status = RouterStatus.ERROR
|
|
await self._notify_error("Connection failed")
|
|
return False
|
|
|
|
self.status = RouterStatus.ONLINE
|
|
self.last_heartbeat = datetime.utcnow()
|
|
await self._notify_status_change()
|
|
|
|
# Start heartbeat
|
|
self._heartbeat_task = asyncio.create_task(self._heartbeat_loop())
|
|
|
|
return True
|
|
|
|
async def disconnect(self):
|
|
"""Disconnect from router."""
|
|
if self.status == RouterStatus.OFFLINE:
|
|
return
|
|
|
|
# Stop streaming if active
|
|
if self.is_streaming:
|
|
await self.stop_csi_streaming()
|
|
|
|
# Stop heartbeat
|
|
if self._heartbeat_task:
|
|
self._heartbeat_task.cancel()
|
|
try:
|
|
await self._heartbeat_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
self.status = RouterStatus.OFFLINE
|
|
await self._notify_status_change()
|
|
|
|
async def start_csi_streaming(self, sample_rate: int = 1000) -> bool:
|
|
"""Start CSI data streaming."""
|
|
if self.status != RouterStatus.ONLINE:
|
|
return False
|
|
|
|
if self.is_streaming:
|
|
return False
|
|
|
|
self.is_streaming = True
|
|
self._streaming_task = asyncio.create_task(self._csi_streaming_loop(sample_rate))
|
|
|
|
return True
|
|
|
|
async def stop_csi_streaming(self):
|
|
"""Stop CSI data streaming."""
|
|
if not self.is_streaming:
|
|
return
|
|
|
|
self.is_streaming = False
|
|
|
|
if self._streaming_task:
|
|
self._streaming_task.cancel()
|
|
try:
|
|
await self._streaming_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
async def _csi_streaming_loop(self, sample_rate: int):
|
|
"""CSI data streaming loop."""
|
|
interval = 1.0 / sample_rate
|
|
|
|
try:
|
|
while self.is_streaming:
|
|
# Generate CSI data
|
|
csi_data = self._generate_csi_sample()
|
|
|
|
# Add to buffer
|
|
self.csi_data_buffer.append(csi_data)
|
|
|
|
# Keep buffer size manageable
|
|
if len(self.csi_data_buffer) > 1000:
|
|
self.csi_data_buffer = self.csi_data_buffer[-1000:]
|
|
|
|
# Notify callbacks
|
|
await self._notify_csi_data(csi_data)
|
|
|
|
# Simulate processing delay and jitter
|
|
actual_interval = interval * random.uniform(0.9, 1.1)
|
|
await asyncio.sleep(actual_interval)
|
|
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
async def _heartbeat_loop(self):
|
|
"""Heartbeat loop to maintain connection."""
|
|
try:
|
|
while self.status == RouterStatus.ONLINE:
|
|
self.last_heartbeat = datetime.utcnow()
|
|
self.uptime_seconds += 1
|
|
|
|
# Simulate temperature variations
|
|
self.temperature_celsius += random.uniform(-1, 1)
|
|
self.temperature_celsius = max(30, min(80, self.temperature_celsius))
|
|
|
|
# Check for overheating
|
|
if self.temperature_celsius > 75:
|
|
self.signal_quality = SignalQuality.POOR
|
|
await self._notify_error("High temperature warning")
|
|
|
|
await asyncio.sleep(1.0)
|
|
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
def _generate_csi_sample(self) -> Dict[str, Any]:
|
|
"""Generate realistic CSI sample."""
|
|
# Base amplitude and phase matrices
|
|
amplitude = np.random.uniform(0.2, 0.8, (self.config.num_antennas, self.config.num_subcarriers))
|
|
phase = np.random.uniform(-np.pi, np.pi, (self.config.num_antennas, self.config.num_subcarriers))
|
|
|
|
# Add signal quality effects
|
|
if self.signal_quality == SignalQuality.POOR:
|
|
noise_level = 0.3
|
|
elif self.signal_quality == SignalQuality.FAIR:
|
|
noise_level = 0.2
|
|
elif self.signal_quality == SignalQuality.GOOD:
|
|
noise_level = 0.1
|
|
else: # EXCELLENT
|
|
noise_level = 0.05
|
|
|
|
# Add noise
|
|
amplitude += np.random.normal(0, noise_level, amplitude.shape)
|
|
phase += np.random.normal(0, noise_level * np.pi, phase.shape)
|
|
|
|
# Clip values
|
|
amplitude = np.clip(amplitude, 0, 1)
|
|
phase = np.mod(phase + np.pi, 2 * np.pi) - np.pi
|
|
|
|
# Simulate packet errors
|
|
if random.random() < self.error_rate:
|
|
# Corrupt some data
|
|
corruption_mask = np.random.random(amplitude.shape) < 0.1
|
|
amplitude[corruption_mask] = 0
|
|
phase[corruption_mask] = 0
|
|
|
|
return {
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"router_id": self.config.router_id,
|
|
"amplitude": amplitude.tolist(),
|
|
"phase": phase.tolist(),
|
|
"frequency": self.config.frequency,
|
|
"bandwidth": self.config.bandwidth,
|
|
"num_antennas": self.config.num_antennas,
|
|
"num_subcarriers": self.config.num_subcarriers,
|
|
"signal_quality": self.signal_quality.value,
|
|
"temperature": self.temperature_celsius,
|
|
"tx_power": self.config.tx_power,
|
|
"sequence_number": len(self.csi_data_buffer)
|
|
}
|
|
|
|
def register_callback(self, event: str, callback: Callable):
|
|
"""Register event callback."""
|
|
if event in self.callbacks:
|
|
self.callbacks[event].append(callback)
|
|
|
|
def unregister_callback(self, event: str, callback: Callable):
|
|
"""Unregister event callback."""
|
|
if event in self.callbacks and callback in self.callbacks[event]:
|
|
self.callbacks[event].remove(callback)
|
|
|
|
async def _notify_status_change(self):
|
|
"""Notify status change callbacks."""
|
|
for callback in self.callbacks["on_status_change"]:
|
|
try:
|
|
if asyncio.iscoroutinefunction(callback):
|
|
await callback(self.status)
|
|
else:
|
|
callback(self.status)
|
|
except Exception:
|
|
pass # Ignore callback errors
|
|
|
|
async def _notify_csi_data(self, data: Dict[str, Any]):
|
|
"""Notify CSI data callbacks."""
|
|
for callback in self.callbacks["on_csi_data"]:
|
|
try:
|
|
if asyncio.iscoroutinefunction(callback):
|
|
await callback(data)
|
|
else:
|
|
callback(data)
|
|
except Exception:
|
|
pass
|
|
|
|
async def _notify_error(self, error_message: str):
|
|
"""Notify error callbacks."""
|
|
for callback in self.callbacks["on_error"]:
|
|
try:
|
|
if asyncio.iscoroutinefunction(callback):
|
|
await callback(error_message)
|
|
else:
|
|
callback(error_message)
|
|
except Exception:
|
|
pass
|
|
|
|
def get_status(self) -> Dict[str, Any]:
|
|
"""Get router status information."""
|
|
return {
|
|
"router_id": self.config.router_id,
|
|
"status": self.status.value,
|
|
"signal_quality": self.signal_quality.value,
|
|
"is_streaming": self.is_streaming,
|
|
"connected_devices": len(self.connected_devices),
|
|
"temperature": self.temperature_celsius,
|
|
"uptime_seconds": self.uptime_seconds,
|
|
"last_heartbeat": self.last_heartbeat.isoformat() if self.last_heartbeat else None,
|
|
"error_rate": self.error_rate,
|
|
"latency_ms": self.latency_ms,
|
|
"throughput_mbps": self.throughput_mbps,
|
|
"firmware_version": self.config.firmware_version,
|
|
"location": self.config.location
|
|
}
|
|
|
|
def set_signal_quality(self, quality: SignalQuality):
|
|
"""Set signal quality for testing."""
|
|
self.signal_quality = quality
|
|
|
|
def set_error_rate(self, error_rate: float):
|
|
"""Set error rate for testing."""
|
|
self.error_rate = max(0, min(1, error_rate))
|
|
|
|
def simulate_interference(self, duration_seconds: float = 5.0):
|
|
"""Simulate interference for testing."""
|
|
async def interference_task():
|
|
original_quality = self.signal_quality
|
|
self.signal_quality = SignalQuality.POOR
|
|
await asyncio.sleep(duration_seconds)
|
|
self.signal_quality = original_quality
|
|
|
|
asyncio.create_task(interference_task())
|
|
|
|
def get_csi_buffer(self) -> List[Dict[str, Any]]:
|
|
"""Get CSI data buffer."""
|
|
return self.csi_data_buffer.copy()
|
|
|
|
def clear_csi_buffer(self):
|
|
"""Clear CSI data buffer."""
|
|
self.csi_data_buffer.clear()
|
|
|
|
|
|
class MockRouterNetwork:
|
|
"""Mock network of WiFi routers."""
|
|
|
|
def __init__(self):
|
|
self.routers = {}
|
|
self.network_topology = {}
|
|
self.interference_sources = []
|
|
self.global_callbacks = {
|
|
"on_router_added": [],
|
|
"on_router_removed": [],
|
|
"on_network_event": []
|
|
}
|
|
|
|
def add_router(self, config: RouterConfig) -> MockWiFiRouter:
|
|
"""Add router to network."""
|
|
if config.router_id in self.routers:
|
|
raise ValueError(f"Router {config.router_id} already exists")
|
|
|
|
router = MockWiFiRouter(config)
|
|
self.routers[config.router_id] = router
|
|
|
|
# Register for router events
|
|
router.register_callback("on_status_change", self._on_router_status_change)
|
|
router.register_callback("on_error", self._on_router_error)
|
|
|
|
# Notify callbacks
|
|
for callback in self.global_callbacks["on_router_added"]:
|
|
callback(router)
|
|
|
|
return router
|
|
|
|
def remove_router(self, router_id: str) -> bool:
|
|
"""Remove router from network."""
|
|
if router_id not in self.routers:
|
|
return False
|
|
|
|
router = self.routers[router_id]
|
|
|
|
# Disconnect if connected
|
|
if router.status != RouterStatus.OFFLINE:
|
|
asyncio.create_task(router.disconnect())
|
|
|
|
del self.routers[router_id]
|
|
|
|
# Notify callbacks
|
|
for callback in self.global_callbacks["on_router_removed"]:
|
|
callback(router_id)
|
|
|
|
return True
|
|
|
|
def get_router(self, router_id: str) -> Optional[MockWiFiRouter]:
|
|
"""Get router by ID."""
|
|
return self.routers.get(router_id)
|
|
|
|
def get_all_routers(self) -> Dict[str, MockWiFiRouter]:
|
|
"""Get all routers."""
|
|
return self.routers.copy()
|
|
|
|
async def connect_all_routers(self) -> Dict[str, bool]:
|
|
"""Connect all routers."""
|
|
results = {}
|
|
tasks = []
|
|
|
|
for router_id, router in self.routers.items():
|
|
task = asyncio.create_task(router.connect())
|
|
tasks.append((router_id, task))
|
|
|
|
for router_id, task in tasks:
|
|
try:
|
|
result = await task
|
|
results[router_id] = result
|
|
except Exception:
|
|
results[router_id] = False
|
|
|
|
return results
|
|
|
|
async def disconnect_all_routers(self):
|
|
"""Disconnect all routers."""
|
|
tasks = []
|
|
|
|
for router in self.routers.values():
|
|
if router.status != RouterStatus.OFFLINE:
|
|
task = asyncio.create_task(router.disconnect())
|
|
tasks.append(task)
|
|
|
|
if tasks:
|
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
async def start_all_streaming(self, sample_rate: int = 1000) -> Dict[str, bool]:
|
|
"""Start CSI streaming on all routers."""
|
|
results = {}
|
|
|
|
for router_id, router in self.routers.items():
|
|
if router.status == RouterStatus.ONLINE:
|
|
result = await router.start_csi_streaming(sample_rate)
|
|
results[router_id] = result
|
|
else:
|
|
results[router_id] = False
|
|
|
|
return results
|
|
|
|
async def stop_all_streaming(self):
|
|
"""Stop CSI streaming on all routers."""
|
|
tasks = []
|
|
|
|
for router in self.routers.values():
|
|
if router.is_streaming:
|
|
task = asyncio.create_task(router.stop_csi_streaming())
|
|
tasks.append(task)
|
|
|
|
if tasks:
|
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
def get_network_status(self) -> Dict[str, Any]:
|
|
"""Get overall network status."""
|
|
total_routers = len(self.routers)
|
|
online_routers = sum(1 for r in self.routers.values() if r.status == RouterStatus.ONLINE)
|
|
streaming_routers = sum(1 for r in self.routers.values() if r.is_streaming)
|
|
|
|
return {
|
|
"total_routers": total_routers,
|
|
"online_routers": online_routers,
|
|
"streaming_routers": streaming_routers,
|
|
"network_health": online_routers / max(total_routers, 1),
|
|
"interference_sources": len(self.interference_sources),
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
def simulate_network_partition(self, router_ids: List[str], duration_seconds: float = 10.0):
|
|
"""Simulate network partition for testing."""
|
|
async def partition_task():
|
|
# Disconnect specified routers
|
|
affected_routers = [self.routers[rid] for rid in router_ids if rid in self.routers]
|
|
|
|
for router in affected_routers:
|
|
if router.status == RouterStatus.ONLINE:
|
|
router.status = RouterStatus.ERROR
|
|
await router._notify_status_change()
|
|
|
|
await asyncio.sleep(duration_seconds)
|
|
|
|
# Reconnect routers
|
|
for router in affected_routers:
|
|
if router.status == RouterStatus.ERROR:
|
|
await router.connect()
|
|
|
|
asyncio.create_task(partition_task())
|
|
|
|
def add_interference_source(self, location: Dict[str, float], strength: float, frequency: float):
|
|
"""Add interference source."""
|
|
interference = {
|
|
"id": f"interference_{len(self.interference_sources)}",
|
|
"location": location,
|
|
"strength": strength,
|
|
"frequency": frequency,
|
|
"active": True
|
|
}
|
|
|
|
self.interference_sources.append(interference)
|
|
|
|
# Affect nearby routers
|
|
for router in self.routers.values():
|
|
distance = self._calculate_distance(router.config.location, location)
|
|
if distance < 50: # Within 50 meters
|
|
if strength > 0.5:
|
|
router.set_signal_quality(SignalQuality.POOR)
|
|
elif strength > 0.3:
|
|
router.set_signal_quality(SignalQuality.FAIR)
|
|
|
|
def _calculate_distance(self, loc1: Dict[str, float], loc2: Dict[str, float]) -> float:
|
|
"""Calculate distance between two locations."""
|
|
dx = loc1.get("x", 0) - loc2.get("x", 0)
|
|
dy = loc1.get("y", 0) - loc2.get("y", 0)
|
|
dz = loc1.get("z", 0) - loc2.get("z", 0)
|
|
return np.sqrt(dx**2 + dy**2 + dz**2)
|
|
|
|
async def _on_router_status_change(self, status: RouterStatus):
|
|
"""Handle router status change."""
|
|
for callback in self.global_callbacks["on_network_event"]:
|
|
await callback("router_status_change", {"status": status})
|
|
|
|
async def _on_router_error(self, error_message: str):
|
|
"""Handle router error."""
|
|
for callback in self.global_callbacks["on_network_event"]:
|
|
await callback("router_error", {"error": error_message})
|
|
|
|
def register_global_callback(self, event: str, callback: Callable):
|
|
"""Register global network callback."""
|
|
if event in self.global_callbacks:
|
|
self.global_callbacks[event].append(callback)
|
|
|
|
|
|
class MockSensorArray:
|
|
"""Mock sensor array for environmental monitoring."""
|
|
|
|
def __init__(self, sensor_id: str, location: Dict[str, float]):
|
|
self.sensor_id = sensor_id
|
|
self.location = location
|
|
self.is_active = False
|
|
self.sensors = {
|
|
"temperature": {"value": 22.0, "unit": "celsius", "range": (15, 35)},
|
|
"humidity": {"value": 45.0, "unit": "percent", "range": (30, 70)},
|
|
"pressure": {"value": 1013.25, "unit": "hPa", "range": (980, 1050)},
|
|
"light": {"value": 300.0, "unit": "lux", "range": (0, 1000)},
|
|
"motion": {"value": False, "unit": "boolean", "range": (False, True)},
|
|
"sound": {"value": 35.0, "unit": "dB", "range": (20, 80)}
|
|
}
|
|
self.reading_history = []
|
|
self.callbacks = []
|
|
|
|
async def start_monitoring(self, interval_seconds: float = 1.0):
|
|
"""Start sensor monitoring."""
|
|
if self.is_active:
|
|
return False
|
|
|
|
self.is_active = True
|
|
asyncio.create_task(self._monitoring_loop(interval_seconds))
|
|
return True
|
|
|
|
def stop_monitoring(self):
|
|
"""Stop sensor monitoring."""
|
|
self.is_active = False
|
|
|
|
async def _monitoring_loop(self, interval: float):
|
|
"""Sensor monitoring loop."""
|
|
try:
|
|
while self.is_active:
|
|
reading = self._generate_sensor_reading()
|
|
self.reading_history.append(reading)
|
|
|
|
# Keep history manageable
|
|
if len(self.reading_history) > 1000:
|
|
self.reading_history = self.reading_history[-1000:]
|
|
|
|
# Notify callbacks
|
|
for callback in self.callbacks:
|
|
try:
|
|
if asyncio.iscoroutinefunction(callback):
|
|
await callback(reading)
|
|
else:
|
|
callback(reading)
|
|
except Exception:
|
|
pass
|
|
|
|
await asyncio.sleep(interval)
|
|
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
def _generate_sensor_reading(self) -> Dict[str, Any]:
|
|
"""Generate realistic sensor reading."""
|
|
reading = {
|
|
"sensor_id": self.sensor_id,
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"location": self.location,
|
|
"readings": {}
|
|
}
|
|
|
|
for sensor_name, config in self.sensors.items():
|
|
if sensor_name == "motion":
|
|
# Motion detection with some randomness
|
|
reading["readings"][sensor_name] = random.random() < 0.1 # 10% chance of motion
|
|
else:
|
|
# Continuous sensors with drift
|
|
current_value = config["value"]
|
|
min_val, max_val = config["range"]
|
|
|
|
# Add small random drift
|
|
drift = random.uniform(-0.1, 0.1) * (max_val - min_val)
|
|
new_value = current_value + drift
|
|
|
|
# Keep within range
|
|
new_value = max(min_val, min(max_val, new_value))
|
|
|
|
config["value"] = new_value
|
|
reading["readings"][sensor_name] = {
|
|
"value": round(new_value, 2),
|
|
"unit": config["unit"]
|
|
}
|
|
|
|
return reading
|
|
|
|
def register_callback(self, callback: Callable):
|
|
"""Register sensor callback."""
|
|
self.callbacks.append(callback)
|
|
|
|
def unregister_callback(self, callback: Callable):
|
|
"""Unregister sensor callback."""
|
|
if callback in self.callbacks:
|
|
self.callbacks.remove(callback)
|
|
|
|
def get_latest_reading(self) -> Optional[Dict[str, Any]]:
|
|
"""Get latest sensor reading."""
|
|
return self.reading_history[-1] if self.reading_history else None
|
|
|
|
def get_reading_history(self, limit: int = 100) -> List[Dict[str, Any]]:
|
|
"""Get sensor reading history."""
|
|
return self.reading_history[-limit:]
|
|
|
|
def simulate_event(self, event_type: str, duration_seconds: float = 5.0):
|
|
"""Simulate environmental event."""
|
|
async def event_task():
|
|
if event_type == "motion_detected":
|
|
self.sensors["motion"]["value"] = True
|
|
await asyncio.sleep(duration_seconds)
|
|
self.sensors["motion"]["value"] = False
|
|
|
|
elif event_type == "temperature_spike":
|
|
original_temp = self.sensors["temperature"]["value"]
|
|
self.sensors["temperature"]["value"] = min(35, original_temp + 10)
|
|
await asyncio.sleep(duration_seconds)
|
|
self.sensors["temperature"]["value"] = original_temp
|
|
|
|
elif event_type == "loud_noise":
|
|
original_sound = self.sensors["sound"]["value"]
|
|
self.sensors["sound"]["value"] = min(80, original_sound + 20)
|
|
await asyncio.sleep(duration_seconds)
|
|
self.sensors["sound"]["value"] = original_sound
|
|
|
|
asyncio.create_task(event_task())
|
|
|
|
|
|
# Utility functions for creating test hardware setups
|
|
def create_test_router_network(num_routers: int = 3) -> MockRouterNetwork:
|
|
"""Create test router network."""
|
|
network = MockRouterNetwork()
|
|
|
|
for i in range(num_routers):
|
|
config = RouterConfig(
|
|
router_id=f"router_{i:03d}",
|
|
location={"x": i * 10, "y": 0, "z": 2.5}
|
|
)
|
|
network.add_router(config)
|
|
|
|
return network
|
|
|
|
|
|
def create_test_sensor_array(num_sensors: int = 2) -> List[MockSensorArray]:
|
|
"""Create test sensor array."""
|
|
sensors = []
|
|
|
|
for i in range(num_sensors):
|
|
sensor = MockSensorArray(
|
|
sensor_id=f"sensor_{i:03d}",
|
|
location={"x": i * 5, "y": 5, "z": 1.0}
|
|
)
|
|
sensors.append(sensor)
|
|
|
|
return sensors
|
|
|
|
|
|
async def setup_test_hardware_environment() -> Dict[str, Any]:
|
|
"""Setup complete test hardware environment."""
|
|
# Create router network
|
|
router_network = create_test_router_network(3)
|
|
|
|
# Create sensor arrays
|
|
sensor_arrays = create_test_sensor_array(2)
|
|
|
|
# Connect all routers
|
|
router_results = await router_network.connect_all_routers()
|
|
|
|
# Start sensor monitoring
|
|
sensor_tasks = []
|
|
for sensor in sensor_arrays:
|
|
task = asyncio.create_task(sensor.start_monitoring(1.0))
|
|
sensor_tasks.append(task)
|
|
|
|
sensor_results = await asyncio.gather(*sensor_tasks)
|
|
|
|
return {
|
|
"router_network": router_network,
|
|
"sensor_arrays": sensor_arrays,
|
|
"router_connection_results": router_results,
|
|
"sensor_start_results": sensor_results,
|
|
"setup_timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
|
|
async def teardown_test_hardware_environment(environment: Dict[str, Any]):
|
|
"""Teardown test hardware environment."""
|
|
# Stop sensor monitoring
|
|
for sensor in environment["sensor_arrays"]:
|
|
sensor.stop_monitoring()
|
|
|
|
# Disconnect all routers
|
|
await environment["router_network"].disconnect_all_routers() |