完成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/routing/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
"""Routing module."""
from sim.routing.gradient_routing import GradientRouting
__all__ = ["GradientRouting"]

View File

@@ -0,0 +1,178 @@
"""
Gradient-based routing protocol.
Implements:
- Cost-based routing (gradient routing)
- HELLO message handling for neighbor discovery
- Parent selection based on cost + link penalty
- Data forwarding to parent node
"""
from typing import Dict, Optional
from dataclasses import dataclass, field
from sim.core.packet import Packet, PacketType
from sim.radio import propagation
from sim import config
@dataclass
class NeighborInfo:
"""Information about a neighbor node."""
node_id: int
cost: int
rssi: float
last_hello_time: float
class GradientRouting:
"""
Gradient routing protocol.
Each node maintains:
- cost: Distance to sink (in hops + penalty)
- parent: Next hop towards sink
- neighbors: Dict of known neighbors with their costs
"""
def __init__(self, node_id: int, is_sink: bool = False):
"""
Initialize routing.
Args:
node_id: This node's ID
is_sink: Whether this node is the sink
"""
self.node_id = node_id
self.is_sink = is_sink
# Routing state
self.cost = 0 if is_sink else float("inf")
self.parent: Optional[int] = None
self.neighbors: Dict[int, NeighborInfo] = {}
# Sequence number for HELLO messages
self.hello_seq = 0
def reset(self):
"""Reset routing state."""
self.cost = 0 if self.is_sink else float("inf")
self.parent = None
self.neighbors.clear()
self.hello_seq = 0
def create_hello_packet(self) -> Packet:
"""
Create a HELLO packet for neighbor discovery.
Returns:
HELLO packet with current cost
"""
packet = Packet(
type=PacketType.HELLO,
src=self.node_id,
dst=-1, # Broadcast
seq=self.hello_seq,
hop=0,
payload=str(int(self.cost)) if self.cost != float("inf") else "inf",
)
self.hello_seq += 1
return packet
def process_hello(self, packet: Packet, rssi: float) -> bool:
"""
Process received HELLO packet.
Args:
packet: Received HELLO packet
rssi: RSSI of received signal
Returns:
True if routing state changed (cost/parent updated)
"""
# Parse cost from payload
try:
neighbor_cost = int(packet.payload) if packet.payload else 0
except ValueError:
neighbor_cost = 0
# Calculate link penalty based on RSSI
link_penalty = propagation.calculate_link_penalty(rssi)
# Calculate new cost to sink through this neighbor
new_cost = neighbor_cost + 1 + int(link_penalty)
# Update neighbor info
old_neighbor = self.neighbors.get(packet.src)
self.neighbors[packet.src] = NeighborInfo(
node_id=packet.src,
cost=neighbor_cost,
rssi=rssi,
last_hello_time=packet.rssi, # Use rssi field to store time
)
# Check if we should update our route
# Update condition: new_cost < cost - 1
old_cost = self.cost
if new_cost < self.cost - config.ROUTE_UPDATE_THRESHOLD:
self.cost = new_cost
self.parent = packet.src
return True
# Also update if we have no route yet
if self.parent is None and not self.is_sink:
if new_cost < float("inf"):
self.cost = new_cost
self.parent = packet.src
return True
return old_cost != self.cost
def get_next_hop(self) -> Optional[int]:
"""
Get next hop towards sink.
Returns:
Parent node ID, or None if no route
"""
return self.parent
def is_route_valid(self) -> bool:
"""Check if current route is valid."""
if self.is_sink:
return True
return self.parent is not None and self.cost < float("inf")
def cleanup_stale_neighbors(self, current_time: float, timeout: float = 30.0):
"""Remove neighbors that haven't sent HELLO recently."""
stale = [
nid
for nid, info in self.neighbors.items()
if current_time - info.last_hello_time > timeout
]
for nid in stale:
del self.neighbors[nid]
# If our parent is stale, we need to find a new one
if self.parent in stale:
self.parent = None
self.cost = float("inf")
# Try to find new parent
for nid, info in self.neighbors.items():
if info.cost < self.cost:
self.cost = info.cost + 1
self.parent = nid
def get_routing_table(self) -> dict:
"""Get routing table for debugging/visualization."""
return {
"node_id": self.node_id,
"is_sink": self.is_sink,
"cost": int(self.cost) if self.cost != float("inf") else -1,
"parent": self.parent,
"neighbors": {
nid: {"cost": info.cost, "rssi": round(info.rssi, 2)}
for nid, info in self.neighbors.items()
},
}