Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/pymc_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Clean, simple API for building mesh network applications.
"""

__version__ = "1.0.1"
__version__ = "1.0.2"

# Core mesh functionality
from .node.node import MeshNode
Expand Down
49 changes: 37 additions & 12 deletions src/pymc_core/hardware/sx1262_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def __init__(

logger.info(
f"SX1262Radio configured: freq={frequency/1e6:.1f}MHz, "
f"power={tx_power}dBm, sf={spreading_factor}, pre={preamble_length}"
f"power={tx_power}dBm, sf={spreading_factor}, bw={bandwidth/1000:.1f}kHz, pre={preamble_length}"
)
# Register this instance as the active radio for IRQ callback access
SX1262Radio._active_instance = self
Expand Down Expand Up @@ -315,7 +315,7 @@ def set_rx_callback(self, callback):

async def _rx_irq_background_task(self):
"""Background task: waits for RX_DONE IRQ and processes received packets automatically."""
logger.info("[RX] Starting RX IRQ background task")
logger.debug("[RX] Starting RX IRQ background task")
rx_check_count = 0
last_preamble_time = 0
preamble_timeout = 5.0 # 5 seconds timeout for incomplete preamble detection
Expand Down Expand Up @@ -456,14 +456,14 @@ async def _rx_irq_background_task(self):
raw_rssi = self.lora.getRssiInst()
if raw_rssi is not None:
noise_floor_dbm = -(float(raw_rssi) / 2)
logger.info(
logger.debug(
f"[RX Task] Status check #{rx_check_count}, "
f"Noise: {noise_floor_dbm:.1f}dBm"
)
else:
logger.info(f"[RX Task] Status check #{rx_check_count}")
logger.debug(f"[RX Task] Status check #{rx_check_count}")
except Exception:
logger.info(f"[RX Task] Status check #{rx_check_count}")
logger.debug(f"[RX Task] Status check #{rx_check_count}")
else:
await asyncio.sleep(0.1) # Longer delay when interrupts not set up

Expand All @@ -484,7 +484,7 @@ def begin(self) -> bool:
self.irq_pin = Button(self.irq_pin_number, pull_up=False)
self.irq_pin.when_activated = self._handle_interrupt
self._interrupt_setup = True
logger.info(f"[RX] IRQ setup successful on pin {self.irq_pin_number}")
logger.debug(f"[RX] IRQ setup successful on pin {self.irq_pin_number}")
except Exception as e:
logger.error(f"IRQ setup failed: {e}")
raise RuntimeError(f"Failed to set up IRQ pin {self.irq_pin_number}: {e}")
Expand Down Expand Up @@ -525,7 +525,11 @@ def begin(self) -> bool:

self.lora.setBufferBaseAddress(0x00, 0x80) # TX=0x00, RX=0x80

self.lora.setLoRaModulation(self.spreading_factor, self.bandwidth, self.coding_rate)
# Enable LDRO if symbol duration > 16ms (SF11/62.5kHz = 32.768ms)
symbol_duration_ms = (2 ** self.spreading_factor) / (self.bandwidth / 1000)
ldro = symbol_duration_ms > 16.0
logger.info(f"LDRO {'enabled' if ldro else 'disabled'} (symbol duration: {symbol_duration_ms:.3f}ms)")
self.lora.setLoRaModulation(self.spreading_factor, self.bandwidth, self.coding_rate, ldro)

self.lora.setLoRaPacket(
self.lora.HEADER_EXPLICIT,
Expand Down Expand Up @@ -564,7 +568,11 @@ def begin(self) -> bool:
self.lora.setTxParams(self.tx_power, self.lora.PA_RAMP_200U)

# Configure modulation and packet parameters
self.lora.setLoRaModulation(self.spreading_factor, self.bandwidth, self.coding_rate)
# Enable LDRO if symbol duration > 16ms (SF11/62.5kHz = 32.768ms)
symbol_duration_ms = (2 ** self.spreading_factor) / (self.bandwidth / 1000)
ldro = symbol_duration_ms > 16.0
logger.info(f"LDRO {'enabled' if ldro else 'disabled'} (symbol duration: {symbol_duration_ms:.3f}ms)")
self.lora.setLoRaModulation(self.spreading_factor, self.bandwidth, self.coding_rate, ldro)
self.lora.setPacketParamsLoRa(
self.preamble_length,
self.lora.HEADER_EXPLICIT,
Expand Down Expand Up @@ -600,9 +608,9 @@ def begin(self) -> bool:
return True

self._rx_irq_task = loop.create_task(self._rx_irq_background_task())
logger.info("[RX] RX IRQ background task started")
logger.debug("[RX] RX IRQ background task started")
else:
logger.info("[RX] RX IRQ background task already running")
logger.debug("[RX] RX IRQ background task already running")
except Exception as e:
logger.warning(f"Failed to start RX IRQ background handler: {e}")
return True
Expand Down Expand Up @@ -759,13 +767,13 @@ async def _execute_transmission(self, driver_timeout: int) -> bool:

async def _wait_for_transmission_complete(self, timeout_seconds: float) -> bool:
"""Wait for transmission to complete using interrupts. Returns True if successful."""
logger.info(f"[TX] Waiting for TX completion (timeout: {timeout_seconds}s)")
logger.debug(f"[TX] Waiting for TX completion (timeout: {timeout_seconds}s)")
start_time = time.time()

# IRQ setup is required
try:
await asyncio.wait_for(self._tx_done_event.wait(), timeout=timeout_seconds)
logger.info("[TX] TX completion interrupt received!")
logger.debug("[TX] TX completion interrupt received!")
return True
except asyncio.TimeoutError:
logger.error("[TX] TX completion timeout - no interrupt received!")
Expand Down Expand Up @@ -918,6 +926,23 @@ def get_last_snr(self) -> float:
"""Return last received SNR in dB"""
return self.last_snr

def get_noise_floor(self) -> Optional[float]:
"""
Get current noise floor (instantaneous RSSI) in dBm.
Returns None if radio is not initialized or if reading fails.
"""
if not self._initialized or self.lora is None:
return None
try:
raw_rssi = self.lora.getRssiInst()
if raw_rssi is not None:
noise_floor_dbm = -(float(raw_rssi) / 2)
return noise_floor_dbm
return None
except Exception as e:
logger.debug(f"Failed to read noise floor: {e}")
return None

def set_frequency(self, frequency: int) -> bool:
"""Set operating frequency"""

Expand Down
2 changes: 1 addition & 1 deletion tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


def test_version():
assert __version__ == "1.0.1"
assert __version__ == "1.0.2"


def test_import():
Expand Down