Add LoRa support with background sync and settings UI

- Progressive retry in rns_client.py: fast timeout (15s) then slow (60s+)
  for LoRa/multi-hop links, with automatic fallback
- Background sync threads so subscriptions page returns immediately
  with syncing/error status indicators per subscription
- LoRa RNode configuration in settings page with serial port and
  expandable advanced radio settings (frequency, bandwidth, etc.)
- Internet transport now toggleable alongside LoRa — users can
  enable one, the other, or both
- Reticulum config auto-generated from settings on startup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Derick Phan 2026-04-22 08:47:09 -07:00
parent 6ffd38d58c
commit ce50150363
No known key found for this signature in database
3 changed files with 227 additions and 96 deletions

70
app.py
View file

@ -85,16 +85,32 @@ def start_gateway(reticulum):
thread.start()
def _transport_settings_match(config_file, desired_host, desired_port):
"""Check if existing config transport settings match desired values."""
def _config_settings_match(config_file, desired_host, desired_port):
"""Check if existing config transport and LoRa settings match desired values."""
import configparser
try:
config = configparser.ConfigParser()
config.read(config_file)
if config.has_section("TCP Transport"):
existing_host = config.get("TCP Transport", "target_host")
existing_port = config.get("TCP Transport", "target_port")
return existing_host == desired_host and existing_port == str(desired_port)
# Check TCP transport
tcp_enabled = get_setting("tcp_enabled", "1") == "1"
has_tcp = config.has_section("TCP Transport")
if tcp_enabled != has_tcp:
return False
if tcp_enabled and has_tcp:
if (config.get("TCP Transport", "target_host") != desired_host or
config.get("TCP Transport", "target_port") != str(desired_port)):
return False
# Check LoRa
lora_enabled = get_setting("lora_enabled", "0") == "1"
has_lora = config.has_section("RNode LoRa")
if lora_enabled != has_lora:
return False
if lora_enabled and has_lora:
if config.get("RNode LoRa", "port", fallback="") != get_setting("lora_port", ""):
return False
if config.get("RNode LoRa", "frequency", fallback="") != get_setting("lora_frequency", "867200000"):
return False
return True
except Exception:
pass
return False
@ -111,9 +127,41 @@ def ensure_rns_config(config_dir, transport_host=None, transport_port=None):
transport_port = int(get_setting("transport_port", str(DEFAULT_TRANSPORT_PORT)))
if os.path.exists(config_file):
if _transport_settings_match(config_file, transport_host, transport_port):
if _config_settings_match(config_file, transport_host, transport_port):
return
# Build optional interface blocks
tcp_block = ""
if get_setting("tcp_enabled", "1") == "1":
tcp_block = f"""
[[TCP Transport]]
type = TCPClientInterface
enabled = yes
target_host = {transport_host}
target_port = {transport_port}
"""
lora_block = ""
if get_setting("lora_enabled", "0") == "1":
lora_port = get_setting("lora_port", "")
if lora_port:
lora_frequency = get_setting("lora_frequency", "867200000")
lora_bandwidth = get_setting("lora_bandwidth", "125000")
lora_txpower = get_setting("lora_txpower", "7")
lora_sf = get_setting("lora_sf", "8")
lora_cr = get_setting("lora_cr", "5")
lora_block = f"""
[[RNode LoRa]]
type = RNodeInterface
enabled = yes
port = {lora_port}
frequency = {lora_frequency}
bandwidth = {lora_bandwidth}
txpower = {lora_txpower}
spreadingfactor = {lora_sf}
codingrate = {lora_cr}
"""
os.makedirs(config_dir, exist_ok=True)
with open(config_file, "w") as f:
f.write(f"""[reticulum]
@ -127,13 +175,7 @@ def ensure_rns_config(config_dir, transport_host=None, transport_port=None):
[[Default Interface]]
type = AutoInterface
enabled = Yes
[[TCP Transport]]
type = TCPClientInterface
enabled = yes
target_host = {transport_host}
target_port = {transport_port}
""")
{tcp_block}{lora_block}""")
print(f"Created Reticulum config at {config_file}")