一个具备科研可验证性的 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

@@ -13,13 +13,39 @@ from typing import Optional
from dataclasses import dataclass
from sim.core.packet import Packet, PacketType
from sim.routing.gradient_routing import GradientRouting
from sim.routing import (
GradientRouting,
FloodingRouting,
RandomForwardRouting,
BROADCAST,
)
from sim.mac.reliable_mac import ReliableMAC
from sim.radio.channel import Channel, ReceivedPacket
from sim.core.metrics import MetricsCollector
from sim import config
def create_routing(node_id: int, is_sink: bool, routing_type: str = "gradient"):
"""
Factory function to create routing protocol.
Args:
node_id: Node ID
is_sink: Whether this is the sink
routing_type: Type of routing ("gradient", "flooding", "random")
Returns:
Routing protocol instance
"""
routing_type = routing_type.lower()
if routing_type == "flooding":
return FloodingRouting(node_id, is_sink)
elif routing_type == "random":
return RandomForwardRouting(node_id, is_sink)
else: # default to gradient
return GradientRouting(node_id, is_sink)
@dataclass
class NodeStats:
"""Node statistics."""
@@ -53,6 +79,7 @@ class Node:
channel: Channel,
is_sink: bool = False,
metrics_collector: MetricsCollector = None,
routing_type: str = "gradient",
):
"""
Initialize node.
@@ -65,6 +92,7 @@ class Node:
channel: Wireless channel
is_sink: Whether this is the sink node
metrics_collector: Metrics collector for observability
routing_type: Type of routing ("gradient", "flooding", "random")
"""
self.env = env
self.node_id = node_id
@@ -76,11 +104,14 @@ class Node:
# Metrics collector for hop tracking
self.metrics_collector = metrics_collector
# Routing type
self.routing_type = routing_type
# Register position with channel
self.channel.register_node(node_id, x, y)
# Layers
self.routing = GradientRouting(node_id, is_sink)
# Layers - use factory to create routing
self.routing = create_routing(node_id, is_sink, routing_type)
self.mac = ReliableMAC(env, node_id)
# Sequence numbers
@@ -208,7 +239,6 @@ class Node:
# If we're the sink, receive the packet
if self.is_sink:
self.stats.data_received += 1
# print(f"[DEBUG] Sink received DATA from node {packet.src}, hop={packet.hop}, seq={packet.seq}")
# Record unique packet received (for PDR)
if self.metrics_collector:
@@ -219,16 +249,36 @@ class Node:
self._send_ack(packet.src, packet.seq)
return
# If not sink, check if we should forward
# Don't forward if we've already forwarded this packet (check path)
if self.node_id in packet.path:
# We've already seen and forwarded this packet, skip it
return
# For flooding: check if we've seen this packet before
if hasattr(self.routing, "should_forward"):
if not self.routing.should_forward(packet):
return # Already forwarded
# Forward to parent
next_hop = self.routing.get_next_hop()
if next_hop is not None and next_hop != self.node_id:
self._forward_data(packet)
# Get next hop and handle flooding
if hasattr(self.routing, "get_next_hop"):
next_hop = self.routing.get_next_hop(packet)
# Handle flooding (BROADCAST)
if next_hop == BROADCAST:
# Forward to all neighbors
neighbors = self.routing.get_all_neighbors()
for neighbor in neighbors:
if neighbor != packet.src: # Don't send back to sender
self._forward_data_to_neighbor(packet, neighbor)
return
# Handle regular unicast routing
if next_hop is not None and next_hop != self.node_id:
self._forward_data(packet)
def _forward_data_to_neighbor(self, packet: Packet, neighbor: int):
"""Forward a data packet to a specific neighbor (for flooding)."""
# Record this node in the path and increment hop count
packet.add_hop(self.node_id)
# Send to specific neighbor
self.mac.enqueue(packet, neighbor)
self.stats.data_forwarded += 1
def _send_ack(self, dst: int, seq: int):
"""Send ACK packet to destination."""
@@ -261,9 +311,15 @@ class Node:
self.data_seq += 1
self.stats.data_sent += 1
# Send to parent
next_hop = self.routing.get_next_hop()
if next_hop is not None:
# Get next hop and send
next_hop = self.routing.get_next_hop(packet)
# Handle flooding (broadcast to all)
if next_hop == BROADCAST:
neighbors = self.routing.get_all_neighbors()
for neighbor in neighbors:
self.mac.enqueue(packet, neighbor)
elif next_hop is not None:
self.mac.enqueue(packet, next_hop)
def _forward_data(self, packet: Packet):
@@ -271,22 +327,26 @@ class Node:
# Record this node in the path and increment hop count
packet.add_hop(self.node_id)
# Send to parent
next_hop = self.routing.get_next_hop()
if next_hop is not None:
# Get next hop
next_hop = self.routing.get_next_hop(packet)
# Handle flooding
if next_hop == BROADCAST:
neighbors = self.routing.get_all_neighbors()
for neighbor in neighbors:
if neighbor != packet.src:
self._forward_data_to_neighbor(packet, neighbor)
elif next_hop is not None:
self.mac.enqueue(packet, next_hop)
self.stats.data_forwarded += 1
def _check_forward(self):
"""Check if there's data to forward."""
# In a more complex implementation, nodes might buffer data
# For now, we rely on the MAC queue
pass
def _check_convergence(self):
"""Check if routing has converged."""
if not self._converged:
# For now, just signal that we have a route
if self.routing.is_route_valid():
self._converged = True
self.converged.succeed()
@@ -294,15 +354,11 @@ class Node:
def mac_task(self):
"""
MAC layer task - handles sending queue and retries.
Simplified: No ACK waiting for DATA packets to improve throughput.
ACK is still sent from sink but sender doesn't wait for it.
This is more realistic for LoRa mesh where end-to-end ACK is problematic.
"""
while True:
# Check if there's something to send
if self.mac.has_pending():
# Get next packet
item = self.mac.dequeue()
if item:
packet, dst = item
@@ -315,25 +371,11 @@ class Node:
self.channel.transmit(packet, self.node_id)
self.mac.record_send()
# For DATA packets, we don't wait for ACK
# This is a simplification - in production, you'd want some form of
# local ACK or implicit reliability through lower layers
# The packet is either received or lost - no retry for simplicity
pass
# Small wait to prevent busy loop
yield self.env.timeout(0.1)
def send_packet(self, packet: Packet, dst: int):
"""
Send a packet (called by upper layers).
Corresponds to STM32's Radio.Send.
Args:
packet: Packet to send
dst: Destination node ID
"""
"""Send a packet (called by upper layers)."""
self.channel.transmit(packet, self.node_id)
def get_stats(self) -> dict: