完成py_plan.md
This commit is contained in:
223
sim/mac/reliable_mac.py
Normal file
223
sim/mac/reliable_mac.py
Normal file
@@ -0,0 +1,223 @@
|
||||
"""
|
||||
Reliable MAC layer with ACK and retransmission.
|
||||
|
||||
Implements:
|
||||
- Send queue management
|
||||
- Random backoff before transmission
|
||||
- ACK等待 and retransmission
|
||||
- Maximum retry limit
|
||||
"""
|
||||
|
||||
import random
|
||||
from typing import Optional, Dict
|
||||
from dataclasses import dataclass, field
|
||||
from collections import deque
|
||||
|
||||
import simpy
|
||||
|
||||
from sim.core.packet import Packet, PacketType
|
||||
from sim.radio import airtime as airtime_calc
|
||||
from sim import config
|
||||
|
||||
|
||||
@dataclass
|
||||
class PendingAck:
|
||||
"""Tracks a packet waiting for ACK."""
|
||||
|
||||
packet: Packet
|
||||
dst: int
|
||||
retry_count: int = 0
|
||||
send_time: float = 0.0
|
||||
|
||||
|
||||
class ReliableMAC:
|
||||
"""
|
||||
Reliable MAC layer with CSMA-like backoff and ACK.
|
||||
|
||||
Send flow:
|
||||
1. Enqueue packet
|
||||
2. Wait for random backoff
|
||||
3. Transmit
|
||||
4. Wait for ACK
|
||||
5. Retry or success
|
||||
"""
|
||||
|
||||
def __init__(self, env: simpy.Environment, node_id: int):
|
||||
"""
|
||||
Initialize MAC layer.
|
||||
|
||||
Args:
|
||||
env: SimPy environment
|
||||
node_id: This node's ID
|
||||
"""
|
||||
self.env = env
|
||||
self.node_id = node_id
|
||||
|
||||
# Send queue
|
||||
self.queue: deque = deque()
|
||||
|
||||
# Pending ACKs {seq: PendingAck}
|
||||
self.pending_acks: Dict[int, PendingAck] = {}
|
||||
|
||||
# Statistics
|
||||
self.sent_packets = 0
|
||||
self.received_acks = 0
|
||||
self.retries = 0
|
||||
|
||||
# Channel access (set by node)
|
||||
self.channel = None
|
||||
|
||||
def enqueue(self, packet: Packet, dst: int):
|
||||
"""
|
||||
Add packet to send queue.
|
||||
|
||||
Args:
|
||||
packet: Packet to send
|
||||
dst: Destination node ID
|
||||
"""
|
||||
self.queue.append((packet, dst))
|
||||
|
||||
def dequeue(self) -> Optional[tuple]:
|
||||
"""
|
||||
Get next packet from queue.
|
||||
|
||||
Returns:
|
||||
Tuple of (packet, dst) or None if queue empty
|
||||
"""
|
||||
if self.queue:
|
||||
return self.queue.popleft()
|
||||
return None
|
||||
|
||||
def has_pending(self) -> bool:
|
||||
"""Check if there are packets to send."""
|
||||
return len(self.queue) > 0
|
||||
|
||||
def calculate_backoff(self) -> float:
|
||||
"""
|
||||
Calculate random backoff time.
|
||||
|
||||
Returns:
|
||||
Backoff time in seconds
|
||||
"""
|
||||
return random.uniform(config.BACKOFF_MIN, config.BACKOFF_MAX)
|
||||
|
||||
def calculate_ack_timeout(self, packet: Packet) -> float:
|
||||
"""
|
||||
Calculate ACK timeout based on packet airtime.
|
||||
|
||||
Args:
|
||||
packet: The packet waiting for ACK
|
||||
|
||||
Returns:
|
||||
Timeout in seconds
|
||||
"""
|
||||
if packet.is_data:
|
||||
ack_time = airtime_calc.get_ack_airtime()
|
||||
else:
|
||||
ack_time = airtime_calc.get_hello_airtime()
|
||||
|
||||
return ack_time * config.ACK_TIMEOUT_FACTOR
|
||||
|
||||
def start_pending_ack(self, packet: Packet, dst: int):
|
||||
"""
|
||||
Start tracking a packet waiting for ACK.
|
||||
|
||||
Args:
|
||||
packet: The sent packet
|
||||
dst: Destination node ID
|
||||
"""
|
||||
self.pending_acks[packet.seq] = PendingAck(
|
||||
packet=packet, dst=dst, retry_count=0, send_time=self.env.now
|
||||
)
|
||||
|
||||
def ack_received(self, seq: int) -> bool:
|
||||
"""
|
||||
Handle ACK received for a packet.
|
||||
|
||||
Args:
|
||||
seq: Sequence number of acknowledged packet
|
||||
|
||||
Returns:
|
||||
True if ACK was pending (success)
|
||||
"""
|
||||
if seq in self.pending_acks:
|
||||
del self.pending_acks[seq]
|
||||
self.received_acks += 1
|
||||
return True
|
||||
return False
|
||||
|
||||
def should_retry(self, seq: int) -> bool:
|
||||
"""
|
||||
Check if a packet should be retried.
|
||||
|
||||
Args:
|
||||
seq: Sequence number
|
||||
|
||||
Returns:
|
||||
True if should retry
|
||||
"""
|
||||
if seq not in self.pending_acks:
|
||||
return False
|
||||
|
||||
pending = self.pending_acks[seq]
|
||||
if pending.retry_count >= config.MAX_RETRY:
|
||||
# Max retries reached, remove from pending
|
||||
del self.pending_acks[seq]
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def increment_retry(self, seq: int):
|
||||
"""
|
||||
Increment retry count for a packet.
|
||||
|
||||
Args:
|
||||
seq: Sequence number
|
||||
"""
|
||||
if seq in self.pending_acks:
|
||||
self.pending_acks[seq].retry_count += 1
|
||||
self.retries += 1
|
||||
|
||||
def get_retry_packet(self, seq: int) -> Optional[Packet]:
|
||||
"""
|
||||
Get packet for retry.
|
||||
|
||||
Args:
|
||||
seq: Sequence number
|
||||
|
||||
Returns:
|
||||
Packet to retry, or None if max retries reached
|
||||
"""
|
||||
if seq in self.pending_acks:
|
||||
pending = self.pending_acks[seq]
|
||||
if pending.retry_count < config.MAX_RETRY:
|
||||
return pending.packet
|
||||
return None
|
||||
|
||||
def get_pending_count(self) -> int:
|
||||
"""Get number of packets waiting for ACK."""
|
||||
return len(self.pending_acks)
|
||||
|
||||
def get_queue_length(self) -> int:
|
||||
"""Get send queue length."""
|
||||
return len(self.queue)
|
||||
|
||||
def reset_stats(self):
|
||||
"""Reset MAC statistics."""
|
||||
self.sent_packets = 0
|
||||
self.received_acks = 0
|
||||
self.retries = 0
|
||||
|
||||
def record_send(self):
|
||||
"""Record a packet send."""
|
||||
self.sent_packets += 1
|
||||
|
||||
def get_stats(self) -> dict:
|
||||
"""Get MAC statistics."""
|
||||
return {
|
||||
"sent_packets": self.sent_packets,
|
||||
"received_acks": self.received_acks,
|
||||
"retries": self.retries,
|
||||
"pending_acks": len(self.pending_acks),
|
||||
"queue_length": len(self.queue),
|
||||
}
|
||||
Reference in New Issue
Block a user