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