完成py_plan.md

This commit is contained in:
sinlatansen
2026-02-24 16:21:30 +08:00
parent 6c0526b2ef
commit 375febb4c0
21 changed files with 2041 additions and 4 deletions

5
sim/core/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
"""Core module."""
from sim.core.packet import Packet, PacketType
__all__ = ["Packet", "PacketType"]

156
sim/core/metrics.py Normal file
View File

@@ -0,0 +1,156 @@
"""
Metrics system for simulation evaluation.
Collects and reports:
- sent_packets, received_packets
- delivery_ratio
- avg_delay
- avg_hop
- retransmissions
- collisions
- convergence_time
"""
from typing import Dict, List, Set
from dataclasses import dataclass, field
from sim import config
@dataclass
class SimulationMetrics:
"""Metrics for the entire simulation."""
# Packet counts
total_sent: int = 0 # Data packets generated (all nodes)
total_received: int = 0 # Data packets received at sink
total_forwarded: int = 0 # Data packets forwarded by nodes
total_dropped: int = 0 # Packets dropped due to collision
# Routing
convergence_time: float = 0.0
route_updates: int = 0
# MAC
retries: int = 0
acks_received: int = 0
# Channel
collisions: int = 0
# Hop statistics
hop_counts: List[int] = field(default_factory=list)
# Per-node stats
node_stats: Dict[int, dict] = field(default_factory=dict)
# Track unique packets received at sink
received_packet_ids: Set[tuple] = field(default_factory=set)
def calculate_pdr(self) -> float:
"""Calculate Packet Delivery Ratio (unique packets at sink / sent)."""
unique_received = len(self.received_packet_ids)
if self.total_sent == 0:
return 0.0
return unique_received / self.total_sent
def calculate_avg_hop(self) -> float:
"""Calculate average hop count."""
if not self.hop_counts:
return 0.0
return sum(self.hop_counts) / len(self.hop_counts)
def calculate_avg_retries(self) -> float:
"""Calculate average retries per packet."""
if self.total_sent == 0:
return 0.0
return self.retries / self.total_sent
def get_summary(self) -> dict:
"""Get metrics summary."""
unique_received = len(self.received_packet_ids)
return {
"total_sent": self.total_sent,
"total_received": unique_received,
"total_forwarded": self.total_forwarded,
"total_dropped": self.total_dropped,
"pdr": round(self.calculate_pdr() * 100, 2),
"avg_hop": round(self.calculate_avg_hop(), 2),
"avg_retries": round(self.calculate_avg_retries(), 2),
"convergence_time": round(self.convergence_time, 2),
"collisions": self.collisions,
"route_updates": self.route_updates,
}
class MetricsCollector:
"""Collects metrics from simulation."""
def __init__(self):
self.metrics = SimulationMetrics()
self.start_time = 0.0
def set_start_time(self, time: float):
"""Set simulation start time."""
self.start_time = time
def set_convergence_time(self, time: float):
"""Set convergence time."""
self.metrics.convergence_time = time - self.start_time
def add_node_stats(self, node_id: int, stats: dict, is_sink: bool = False):
"""Add per-node statistics."""
self.metrics.node_stats[node_id] = stats
# Aggregate
node_stats = stats.get("stats", {})
if is_sink:
# For sink, data_received is actual unique packets received
# Track unique (src, seq) pairs
pass # Will handle sink specially below
else:
self.metrics.total_sent += node_stats.get("data_sent", 0)
self.metrics.total_forwarded += node_stats.get("data_forwarded", 0)
self.metrics.total_dropped += node_stats.get("packets_dropped", 0)
self.metrics.route_updates += node_stats.get("route_updates", 0)
# MAC stats
mac_stats = stats.get("mac", {})
self.metrics.retries += mac_stats.get("retries", 0)
self.metrics.acks_received += mac_stats.get("received_acks", 0)
def add_sink_stats(self, node_id: int, stats: dict):
"""Add sink-specific statistics (unique packet delivery tracking)."""
self.metrics.node_stats[node_id] = stats
node_stats = stats.get("stats", {})
# Count how many unique packets the sink received
# This is the actual delivery count for PDR
received = node_stats.get("data_received", 0)
self.metrics.total_received = received
# Also track all packets sent
for nid, nstats in self.metrics.node_stats.items():
if nid != node_id:
self.metrics.total_sent += nstats.get("stats", {}).get("data_sent", 0)
# Update rest of stats
self.metrics.total_dropped += node_stats.get("packets_dropped", 0)
mac_stats = stats.get("mac", {})
self.metrics.retries += mac_stats.get("retries", 0)
self.metrics.acks_received += mac_stats.get("received_acks", 0)
def add_collision(self, count: int = 1):
"""Add collision count."""
self.metrics.collisions += count
def add_hop_count(self, hops: int):
"""Add hop count for a received packet."""
self.metrics.hop_counts.append(hops)
def get_metrics(self) -> SimulationMetrics:
"""Get collected metrics."""
return self.metrics

79
sim/core/packet.py Normal file
View File

@@ -0,0 +1,79 @@
"""
Packet model for LoRa route simulation.
Defines packet types and structure for HELLO, DATA, and ACK packets.
"""
from dataclasses import dataclass
from enum import IntEnum
from typing import Optional
class PacketType(IntEnum):
"""Packet type enumeration."""
HELLO = 1
DATA = 2
ACK = 3
@dataclass
class Packet:
"""
LoRa packet structure.
Attributes:
type: Packet type (HELLO, DATA, or ACK)
src: Source node ID
dst: Destination node ID (-1 for broadcast)
seq: Sequence number
hop: Current hop count
payload: Optional payload data
rssi: Received signal strength indicator (set on receive)
"""
type: PacketType
src: int
dst: int
seq: int
hop: int = 0
payload: Optional[str] = None
rssi: Optional[float] = None # Set by receiver
def __repr__(self) -> str:
return (
f"Packet({self.type.name}, src={self.src}, dst={self.dst}, "
f"seq={self.seq}, hop={self.hop})"
)
@property
def is_broadcast(self) -> bool:
"""Check if packet is broadcast (dst = -1)."""
return self.dst == -1
@property
def is_hello(self) -> bool:
"""Check if packet is a HELLO packet."""
return self.type == PacketType.HELLO
@property
def is_data(self) -> bool:
"""Check if packet is a DATA packet."""
return self.type == PacketType.DATA
@property
def is_ack(self) -> bool:
"""Check if packet is an ACK packet."""
return self.type == PacketType.ACK
def to_dict(self) -> dict:
"""Convert packet to dictionary for serialization."""
return {
"type": self.type.name,
"src": self.src,
"dst": self.dst,
"seq": self.seq,
"hop": self.hop,
"payload": self.payload,
"rssi": self.rssi,
}