|
11 | 11 | import asyncio |
12 | 12 | import logging |
13 | 13 | import time |
| 14 | +import math |
| 15 | +import random |
14 | 16 | from typing import Callable, Optional |
15 | | - |
16 | 17 | from gpiozero import Button, Device, OutputDevice |
17 | 18 |
|
18 | 19 | # Force gpiozero to use LGPIOFactory - no RPi.GPIO fallback |
@@ -671,34 +672,38 @@ def begin(self) -> bool: |
671 | 672 | raise RuntimeError(f"Failed to initialize SX1262 radio: {e}") from e |
672 | 673 |
|
673 | 674 | def _calculate_tx_timeout(self, packet_length: int) -> tuple[int, int]: |
674 | | - """Calculate transmission timeout based on modulation parameters""" |
675 | | - sf = self.spreading_factor |
676 | | - bw = self.bandwidth |
677 | | - |
678 | | - # Realistic timeout calculation based on actual LoRa performance |
679 | | - if sf == 11 and bw == 250000: |
680 | | - # Your specific configuration: SF11/250kHz |
681 | | - base_tx_time_ms = 500 + (packet_length * 8) # ~500ms + 8ms per byte |
682 | | - elif sf == 7 and bw == 125000: |
683 | | - # Standard configuration |
684 | | - base_tx_time_ms = 100 + (packet_length * 2) # ~100ms + 2ms per byte |
| 675 | + """Calculate transmission timeout using C++ MeshCore formula - simple and accurate""" |
| 676 | + |
| 677 | + symbol_time = float(1 << self.spreading_factor) / float(self.bandwidth) |
| 678 | + preamble_time = (self.preamble_length + 4.25) * symbol_time |
| 679 | + tmp = (8 * packet_length) - (4 * self.spreading_factor) + 28 + 16 |
| 680 | + #CRC is enabled |
| 681 | + tmp -= 16 |
| 682 | + |
| 683 | + if tmp > 0: |
| 684 | + payload_symbols = 8.0 + math.ceil(float(tmp) / float(4 * self.spreading_factor)) * (self.coding_rate + 4) |
685 | 685 | else: |
686 | | - # General formula for other configurations |
687 | | - sf_factor = 2 ** (sf - 7) |
688 | | - bw_factor = 125000.0 / bw |
689 | | - base_tx_time_ms = int(100 * sf_factor * bw_factor + (packet_length * sf_factor)) |
690 | | - |
691 | | - # Add reasonable safety margin (2x) for timeout |
692 | | - safety_margin = 2.0 |
693 | | - final_timeout_ms = int(base_tx_time_ms * safety_margin) |
694 | | - |
695 | | - # Reasonable limits: minimum 1 second, maximum 10 seconds |
696 | | - final_timeout_ms = max(1000, min(final_timeout_ms, 10000)) |
697 | | - |
698 | | - # Convert to driver timeout format |
699 | | - driver_timeout = final_timeout_ms * 64 # tOut = timeout * 64 |
700 | | - |
701 | | - return final_timeout_ms, driver_timeout |
| 686 | + payload_symbols = 8.0 |
| 687 | + |
| 688 | + payload_time = payload_symbols * symbol_time |
| 689 | + air_time_ms = (preamble_time + payload_time) * 1000.0 |
| 690 | + timeout_ms = math.ceil(air_time_ms) + 1000 |
| 691 | + driver_timeout = timeout_ms * 64 |
| 692 | + |
| 693 | + logger.debug( |
| 694 | + f"TX timing SF{self.spreading_factor}/{self.bandwidth/1000:.1f}kHz " |
| 695 | + f"CR4/{self.coding_rate} {packet_length}B: " |
| 696 | + f"symbol={symbol_time*1000:.1f}ms, " |
| 697 | + f"preamble={preamble_time*1000:.0f}ms, " |
| 698 | + f"tmp={tmp}, " |
| 699 | + f"payload_syms={payload_symbols:.1f}, " |
| 700 | + f"payload={payload_time*1000:.0f}ms, " |
| 701 | + f"air_time={air_time_ms:.0f}ms, " |
| 702 | + f"timeout={timeout_ms}ms, " |
| 703 | + f"driver_timeout={driver_timeout}" |
| 704 | + ) |
| 705 | + |
| 706 | + return timeout_ms, driver_timeout |
702 | 707 |
|
703 | 708 | def _prepare_packet_transmission(self, data_list: list, length: int) -> None: |
704 | 709 | """Prepare radio for packet transmission""" |
@@ -753,18 +758,17 @@ async def _prepare_radio_for_tx(self) -> bool: |
753 | 758 | else: |
754 | 759 | lbt_attempts += 1 |
755 | 760 | if lbt_attempts < max_lbt_attempts: |
756 | | - # Channel busy, wait random backoff before trying again |
757 | | - # this may conflict with dispatcher will need testing. |
758 | | - # Channel busy, wait backoff before trying again (MeshCore-inspired) |
759 | | - import random |
760 | | - |
761 | | - base_delay = random.randint(120, 240) |
762 | | - backoff_ms = base_delay + ( |
763 | | - lbt_attempts * 50 |
764 | | - ) # Progressive: 120-290ms, 170-340ms, etc. |
| 761 | + |
| 762 | + # Jitter (50-200ms) |
| 763 | + base_delay = random.randint(50, 200) |
| 764 | + # Exponential backoff: base * 2^attempts |
| 765 | + backoff_ms = base_delay * (2 ** (lbt_attempts - 1)) |
| 766 | + # Cap at 5 seconds maximum |
| 767 | + backoff_ms = min(backoff_ms, 5000) |
| 768 | + |
765 | 769 | logger.debug( |
766 | | - f"Channel busy (CAD detected activity), backing off {backoff_ms}ms" |
767 | | - f" - >>>>>>> attempt {lbt_attempts} <<<<<<<", |
| 770 | + f"Channel busy (CAD detected activity), backing off {backoff_ms}ms " |
| 771 | + f"- attempt {lbt_attempts}/{max_lbt_attempts} (exponential backoff)" |
768 | 772 | ) |
769 | 773 | await asyncio.sleep(backoff_ms / 1000.0) |
770 | 774 | else: |
|
0 commit comments