一个具备科研可验证性的 LoRa 多跳算法评估基线。
This commit is contained in:
188
sim/routing/flooding.py
Normal file
188
sim/routing/flooding.py
Normal 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",
|
||||
}
|
||||
Reference in New Issue
Block a user