完成py_plan.md

This commit is contained in:
sinlatansen
2026-02-24 16:21:30 +08:00
parent 6c0526b2ef
commit 375febb4c0
21 changed files with 2041 additions and 4 deletions

170
sim/radio/airtime.py Normal file
View File

@@ -0,0 +1,170 @@
"""
LoRa Airtime Calculation.
Implements the real LoRa airtime formula for accurate simulation.
Reference: Semtech SX1276/77/78/79 Datasheet
"""
import math
from sim import config
def calculate_symbol_time(sf: int, bw: int) -> float:
"""
Calculate symbol time.
T_symbol = 2^SF / BW
Args:
sf: Spreading Factor (7-12)
bw: Bandwidth in Hz
Returns:
Symbol time in seconds
"""
return (2**sf) / bw
def calculate_payload_airtime(
payload_size: int,
sf: int,
bw: int,
cr: int,
use_header: bool = True,
low_data_rate_optimize: bool = None,
) -> float:
"""
Calculate payload airtime.
Args:
payload_size: Payload size in bytes
sf: Spreading Factor (7-12)
bw: Bandwidth in Hz
cr: Coding Rate (5-8, represents 4/5 to 4/8)
use_header: Whether packet header is present
low_data_rate_optimize: Low Data Rate Optimization flag
Set to True if SF >= 11 or BW <= 125kHz
Returns:
Payload airtime in seconds
"""
# Determine DE (Low Data Rate Optimization)
if low_data_rate_optimize is None:
# Auto-detect: DE = 1 if SF >= 11 or BW <= 125 kHz
de = 1 if (sf >= 11 or bw <= 125000) else 0
else:
de = 1 if low_data_rate_optimize else 0
# H = 0 if header is present, 1 if no header
h = 0 if use_header else 1
# Calculate number of payload symbols
# N_payload = 8 + max(ceil((8*PL - 4*SF + 28 - 16 - 20*H) / (4*(SF - 2*DE))) * (CR + 4), 0)
numerator = 8 * payload_size - 4 * sf + 28 - 16 - 20 * h
denominator = 4 * (sf - 2 * de)
if denominator <= 0:
# SF - 2*DE <= 0, use minimum
n_payload = 0
else:
n_payload = 8 + max(math.ceil(numerator / denominator) * (cr + 4), 0)
# Calculate time
symbol_time = calculate_symbol_time(sf, bw)
return n_payload * symbol_time
def calculate_preamble_airtime(sf: int, bw: int, preamble: int = None) -> float:
"""
Calculate preamble airtime.
T_preamble = (PREAMBLE + 4.25) * T_symbol
Args:
sf: Spreading Factor (7-12)
bw: Bandwidth in Hz
preamble: Number of preamble symbols (default from config)
Returns:
Preamble airtime in seconds
"""
if preamble is None:
preamble = config.PREAMBLE
symbol_time = calculate_symbol_time(sf, bw)
return (preamble + 4.25) * symbol_time
def calculate_packet_airtime(
payload_size: int,
sf: int = None,
bw: int = None,
cr: int = None,
preamble: int = None,
) -> float:
"""
Calculate total packet airtime.
T_packet = T_preamble + T_payload
Args:
payload_size: Payload size in bytes
sf: Spreading Factor (default from config)
bw: Bandwidth in Hz (default from config)
cr: Coding Rate (default from config)
preamble: Number of preamble symbols (default from config)
Returns:
Total packet airtime in seconds
"""
if sf is None:
sf = config.SF
if bw is None:
bw = config.BW
if cr is None:
cr = config.CR
preamble_time = calculate_preamble_airtime(sf, bw, preamble)
payload_time = calculate_payload_airtime(payload_size, sf, bw, cr)
return preamble_time + payload_time
def calculate_ack_time(ack_seq: int = 1) -> float:
"""
Calculate ACK packet airtime.
ACK packet structure:
- 1 byte for type
- 1 byte for seq
- 1 byte for dst
- Total: 3 bytes (minimal)
Args:
ack_seq: ACK sequence number (affects total size)
Returns:
ACK airtime in seconds
"""
# ACK = type(1) + seq(1) + dst(1) + reserved(1) = 4 bytes minimum
ack_size = 4
return calculate_packet_airtime(ack_size)
# Convenience function for quick calculations
def get_hello_airtime() -> float:
"""Get airtime for HELLO packet (minimal size)."""
# HELLO = type(1) + src(1) + cost(4) + seq(1) = ~7 bytes
return calculate_packet_airtime(7)
def get_data_airtime(payload_size: int = 16) -> float:
"""Get airtime for DATA packet."""
# DATA = type(1) + src(1) + dst(1) + seq(2) + hop(1) + payload(n)
base_size = 6
return calculate_packet_airtime(base_size + payload_size)
def get_ack_airtime() -> float:
"""Get airtime for ACK packet."""
return calculate_ack_time()