完成py_plan.md
This commit is contained in:
170
sim/radio/airtime.py
Normal file
170
sim/radio/airtime.py
Normal 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()
|
||||
Reference in New Issue
Block a user