一个具备科研可验证性的 LoRa 多跳算法评估基线。

This commit is contained in:
sinlatansen
2026-02-25 20:14:45 +08:00
parent 8537331c6f
commit 5ee1a16574
18 changed files with 1704 additions and 47 deletions

View File

@@ -1,5 +1,7 @@
"""Routing module."""
from sim.routing.gradient_routing import GradientRouting
from sim.routing.flooding import FloodingRouting, BROADCAST
from sim.routing.random_forward import RandomForwardRouting
__all__ = ["GradientRouting"]
__all__ = ["GradientRouting", "FloodingRouting", "RandomForwardRouting", "BROADCAST"]

188
sim/routing/flooding.py Normal file
View File

@@ -0,0 +1,188 @@
"""
Flooding-based routing protocol.
Simple flooding: when a node receives a DATA packet it hasn't seen,
it forwards to ALL neighbors (except the sender).
This is a baseline algorithm for comparison with gradient routing.
"""
import random
from typing import Dict, Optional, Set
from dataclasses import dataclass, field
from sim.core.packet import Packet, PacketType
from sim import config
# Marker for broadcast forwarding
BROADCAST = -1
@dataclass
class NeighborInfo:
"""Information about a neighbor node."""
node_id: int
rssi: float
last_hello_time: float
class FloodingRouting:
"""
Flooding routing protocol.
Each node maintains:
- seen_packets: Set of (src, seq) tuples to prevent duplicate forwarding
- neighbors: Dict of known neighbors
Forwarding logic:
- If packet not seen before, forward to ALL neighbors
- Use seen_packets cache with TTL to prevent infinite loops
"""
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.parent: Optional[int] = None # Not used in flooding
self.neighbors: Dict[int, NeighborInfo] = {}
# Flooding state
self.seen_packets: Set[tuple] = set()
self.max_seen_cache = 1000 # Limit cache size
# Sequence number for HELLO messages
self.hello_seq = 0
# Cost (for compatibility with metrics)
self.cost = 0 if is_sink else 1
def reset(self):
"""Reset routing state."""
self.seen_packets.clear()
self.neighbors.clear()
self.hello_seq = 0
self.cost = 0 if self.is_sink else 1
def create_hello_packet(self) -> Packet:
"""
Create a HELLO packet for neighbor discovery.
Returns:
HELLO packet with node ID
"""
packet = Packet(
type=PacketType.HELLO,
src=self.node_id,
dst=-1, # Broadcast
seq=self.hello_seq,
hop=0,
payload=str(self.node_id), # Just send our ID
)
self.hello_seq += 1
return packet
def process_hello(self, packet: Packet, rssi: float) -> bool:
"""
Process received HELLO packet.
For flooding, we just track neighbors - no cost calculation.
Args:
packet: Received HELLO packet
rssi: RSSI of received signal
Returns:
True if neighbor list changed
"""
# Update neighbor info
old_neighbors = len(self.neighbors)
self.neighbors[packet.src] = NeighborInfo(
node_id=packet.src,
rssi=rssi,
last_hello_time=rssi, # Store time in rssi field
)
return len(self.neighbors) != old_neighbors
def get_next_hop(self, packet: Packet = None) -> int:
"""
Get next hops for forwarding.
For flooding, returns BROADCAST to signal all neighbors.
Args:
packet: The packet to forward (for checking seen status)
Returns:
BROADCAST (-1) to forward to all neighbors
"""
return BROADCAST
def should_forward(self, packet: Packet) -> bool:
"""
Check if this packet should be forwarded (not seen before).
Args:
packet: The packet to check
Returns:
True if packet should be forwarded
"""
packet_id = (packet.src, packet.seq)
if packet_id in self.seen_packets:
return False
# Add to seen set
self.seen_packets.add(packet_id)
# Limit cache size
if len(self.seen_packets) > self.max_seen_cache:
# Remove oldest entries
to_remove = len(self.seen_packets) - self.max_seen_cache + 100
self.seen_packets = set(list(self.seen_packets)[to_remove:])
return True
def get_all_neighbors(self) -> list:
"""Get list of all neighbor IDs."""
return list(self.neighbors.keys())
def is_route_valid(self) -> bool:
"""Check if routing is valid."""
# Flooding is always "valid" - we can always forward
return len(self.neighbors) > 0
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]
def get_routing_table(self) -> dict:
"""Get routing table for debugging/visualization."""
return {
"node_id": self.node_id,
"is_sink": self.is_sink,
"cost": self.cost,
"parent": self.parent,
"neighbors": {
nid: {"rssi": round(info.rssi, 2)}
for nid, info in self.neighbors.items()
},
"algorithm": "flooding",
}

View File

@@ -109,7 +109,7 @@ class GradientRouting:
node_id=packet.src,
cost=neighbor_cost,
rssi=rssi,
last_hello_time=packet.rssi, # Use rssi field to store time
last_hello_time=rssi, # Use rssi field to store time
)
# Check if we should update our route
@@ -129,10 +129,13 @@ class GradientRouting:
return old_cost != self.cost
def get_next_hop(self) -> Optional[int]:
def get_next_hop(self, packet: Packet = None) -> Optional[int]:
"""
Get next hop towards sink.
Args:
packet: Optional packet (for compatibility)
Returns:
Parent node ID, or None if no route
"""

View File

@@ -0,0 +1,148 @@
"""
Random forwarding routing protocol.
Baseline algorithm: randomly select ONE neighbor to forward to.
This demonstrates the value of gradient-based routing.
"""
import random
from typing import Dict, Optional
from dataclasses import dataclass
from sim.core.packet import Packet, PacketType
from sim import config
@dataclass
class NeighborInfo:
"""Information about a neighbor node."""
node_id: int
rssi: float
last_hello_time: float
class RandomForwardRouting:
"""
Random forwarding routing protocol.
Each node maintains:
- neighbors: Dict of known neighbors
- For each packet, randomly selects ONE neighbor to forward to
This is a baseline to demonstrate why gradient routing is better.
"""
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.parent: Optional[int] = None # Randomly selected per packet
self.neighbors: Dict[int, NeighborInfo] = {}
# Sequence number for HELLO messages
self.hello_seq = 0
# Cost (for compatibility with metrics)
self.cost = 0 if is_sink else 1
def reset(self):
"""Reset routing state."""
self.neighbors.clear()
self.hello_seq = 0
self.cost = 0 if self.is_sink else 1
self.parent = None
def create_hello_packet(self) -> Packet:
"""
Create a HELLO packet for neighbor discovery.
Returns:
HELLO packet with node ID
"""
packet = Packet(
type=PacketType.HELLO,
src=self.node_id,
dst=-1, # Broadcast
seq=self.hello_seq,
hop=0,
payload=str(self.node_id),
)
self.hello_seq += 1
return packet
def process_hello(self, packet: Packet, rssi: float) -> bool:
"""
Process received HELLO packet.
Track neighbors but don't calculate cost.
Args:
packet: Received HELLO packet
rssi: RSSI of received signal
Returns:
True if neighbor list changed
"""
# Update neighbor info
old_neighbors = len(self.neighbors)
self.neighbors[packet.src] = NeighborInfo(
node_id=packet.src,
rssi=rssi,
last_hello_time=rssi, # Store time in rssi field
)
return len(self.neighbors) != old_neighbors
def get_next_hop(self, packet: Packet = None) -> Optional[int]:
"""
Randomly select ONE neighbor to forward to.
Args:
packet: The packet to forward (unused in random routing)
Returns:
Random neighbor ID, or None if no neighbors
"""
if not self.neighbors:
return None
# Randomly select one neighbor
neighbor_ids = list(self.neighbors.keys())
return random.choice(neighbor_ids)
def is_route_valid(self) -> bool:
"""Check if routing is valid."""
return len(self.neighbors) > 0
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]
def get_routing_table(self) -> dict:
"""Get routing table for debugging/visualization."""
return {
"node_id": self.node_id,
"is_sink": self.is_sink,
"cost": self.cost,
"parent": self.parent,
"neighbors": {
nid: {"rssi": round(info.rssi, 2)}
for nid, info in self.neighbors.items()
},
"algorithm": "random_forward",
}