Overhaul frontend organization and pause message polling during repeater operations

This commit is contained in:
Jack Kingsman
2026-01-10 16:27:15 -08:00
parent e559d6cd47
commit b0ab2bcb32
15 changed files with 1817 additions and 1131 deletions
+16 -1
View File
@@ -12,6 +12,7 @@ don't work reliably.
import asyncio
import logging
import time
from contextlib import asynccontextmanager
from meshcore import EventType
@@ -28,6 +29,20 @@ _message_poll_task: asyncio.Task | None = None
# Message poll interval in seconds
MESSAGE_POLL_INTERVAL = 5
# Flag to pause polling during repeater operations
_polling_paused: bool = False
@asynccontextmanager
async def pause_polling():
"""Context manager to pause message polling during repeater operations."""
global _polling_paused
_polling_paused = True
try:
yield
finally:
_polling_paused = False
# Background task handle
_sync_task: asyncio.Task | None = None
@@ -276,7 +291,7 @@ async def _message_poll_loop():
try:
await asyncio.sleep(MESSAGE_POLL_INTERVAL)
if radio_manager.is_connected:
if radio_manager.is_connected and not _polling_paused:
await poll_for_messages()
except asyncio.CancelledError:
+1 -1
View File
@@ -15,7 +15,7 @@ class ContactRepository:
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(public_key) DO UPDATE SET
name = COALESCE(excluded.name, contacts.name),
type = excluded.type,
type = CASE WHEN excluded.type = 0 THEN contacts.type ELSE excluded.type END,
flags = excluded.flags,
last_path = COALESCE(excluded.last_path, contacts.last_path),
last_path_len = excluded.last_path_len,
+42 -39
View File
@@ -23,6 +23,7 @@ ACL_PERMISSION_NAMES = {
3: "Admin",
}
from app.radio import radio_manager
from app.radio_sync import pause_polling
from app.repository import ContactRepository
logger = logging.getLogger(__name__)
@@ -382,50 +383,52 @@ async def send_repeater_command(public_key: str, request: CommandRequest) -> Com
detail=f"Contact is not a repeater (type={contact.type}, expected {CONTACT_TYPE_REPEATER})"
)
# Send the command
logger.info("Sending command to repeater %s: %s", contact.public_key[:12], request.command)
# Pause message polling to prevent it from stealing our response
async with pause_polling():
# Send the command
logger.info("Sending command to repeater %s: %s", contact.public_key[:12], request.command)
send_result = await mc.commands.send_cmd(contact.public_key, request.command)
send_result = await mc.commands.send_cmd(contact.public_key, request.command)
if send_result.type == EventType.ERROR:
raise HTTPException(
status_code=500,
detail=f"Failed to send command: {send_result.payload}"
)
# Wait for response (MESSAGES_WAITING event, then get_msg)
try:
wait_result = await mc.wait_for_event(EventType.MESSAGES_WAITING, timeout=10.0)
if wait_result is None:
# Timeout - no response received
logger.warning("No response from repeater %s for command: %s", contact.public_key[:12], request.command)
return CommandResponse(
command=request.command,
response="(no response - command may have been processed)"
if send_result.type == EventType.ERROR:
raise HTTPException(
status_code=500,
detail=f"Failed to send command: {send_result.payload}"
)
response_event = await mc.commands.get_msg()
# Wait for response (MESSAGES_WAITING event, then get_msg)
try:
wait_result = await mc.wait_for_event(EventType.MESSAGES_WAITING, timeout=10.0)
if wait_result is None:
# Timeout - no response received
logger.warning("No response from repeater %s for command: %s", contact.public_key[:12], request.command)
return CommandResponse(
command=request.command,
response="(no response - command may have been processed)"
)
response_event = await mc.commands.get_msg()
if response_event.type == EventType.ERROR:
return CommandResponse(
command=request.command,
response=f"(error: {response_event.payload})"
)
# Extract the response text and timestamp from the payload
response_text = response_event.payload.get("text", str(response_event.payload))
sender_timestamp = response_event.payload.get("timestamp")
logger.info("Received response from %s: %s", contact.public_key[:12], response_text)
if response_event.type == EventType.ERROR:
return CommandResponse(
command=request.command,
response=f"(error: {response_event.payload})"
response=response_text,
sender_timestamp=sender_timestamp,
)
except Exception as e:
logger.error("Error waiting for response: %s", e)
return CommandResponse(
command=request.command,
response=f"(error waiting for response: {e})"
)
# Extract the response text and timestamp from the payload
response_text = response_event.payload.get("text", str(response_event.payload))
sender_timestamp = response_event.payload.get("timestamp")
logger.info("Received response from %s: %s", contact.public_key[:12], response_text)
return CommandResponse(
command=request.command,
response=response_text,
sender_timestamp=sender_timestamp,
)
except Exception as e:
logger.error("Error waiting for response: %s", e)
return CommandResponse(
command=request.command,
response=f"(error waiting for response: {e})"
)