mirror of
https://github.com/pdxlocations/contact.git
synced 2026-07-29 04:53:02 +02:00
Merge pull request #295 from pdxlocations:remote-admin
Refactor remote admin handling
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import curses
|
||||
import logging
|
||||
from typing import Optional
|
||||
import time
|
||||
import traceback
|
||||
from numbers import Real
|
||||
@@ -1009,8 +1010,20 @@ def handle_backtick(stdscr: curses.window) -> None:
|
||||
settings_menu(stdscr, interface)
|
||||
elif remote_node_num is not None:
|
||||
wait_win = None
|
||||
|
||||
def remote_status(message: Optional[str]) -> None:
|
||||
nonlocal wait_win
|
||||
if message is None:
|
||||
if wait_win is not None:
|
||||
wait_win.erase()
|
||||
wait_win.refresh()
|
||||
wait_win = None
|
||||
return
|
||||
if wait_win is None:
|
||||
wait_win = show_remote_admin_wait(stdscr, get_name_from_database(remote_node_num))
|
||||
update_remote_admin_wait(wait_win, message)
|
||||
|
||||
try:
|
||||
wait_win = show_remote_admin_wait(stdscr, get_name_from_database(remote_node_num))
|
||||
settings_menu(
|
||||
stdscr,
|
||||
interface,
|
||||
@@ -1018,7 +1031,7 @@ def handle_backtick(stdscr: curses.window) -> None:
|
||||
# implicit channel request racing that setup.
|
||||
node=interface.getNode(remote_node_num, False),
|
||||
remote=True,
|
||||
status_callback=lambda message: update_remote_admin_wait(wait_win, message),
|
||||
status_callback=remote_status,
|
||||
)
|
||||
except SystemExit as exc:
|
||||
logging.warning("Remote admin was rejected for %s: %s", remote_node_num, exc)
|
||||
|
||||
+66
-19
@@ -5,6 +5,7 @@ import logging
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import List
|
||||
from meshtastic.protobuf import admin_pb2
|
||||
|
||||
@@ -298,6 +299,49 @@ def _request_remote_with_timeout(request, *args) -> None:
|
||||
raise failure[0]
|
||||
|
||||
|
||||
def _request_remote_channels_with_timeout(node: object) -> None:
|
||||
"""Retrieve a remote node's channel list without leaving the UI waiting forever."""
|
||||
node.requestChannels()
|
||||
deadline = time.monotonic() + REMOTE_ADMIN_REQUEST_TIMEOUT_SECONDS
|
||||
while getattr(node, "channels", None) is None:
|
||||
if time.monotonic() >= deadline:
|
||||
raise TimeoutError("Timed out waiting for the remote node's channel list.")
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
def _request_remote_section(node: object, menu_path: List[str], selected_option: str, status_callback=None) -> bool:
|
||||
"""Fetch exactly the remote-admin section the user is opening.
|
||||
|
||||
Meshtastic returns radio and module settings one protobuf section at a
|
||||
time. Asking for all of them on menu entry is both slow and unreliable on
|
||||
long paths, so leave the top-level menu immediately available.
|
||||
"""
|
||||
if menu_path == ["Main Menu"] and selected_option == "Channels":
|
||||
if status_callback:
|
||||
status_callback("Requesting channel list…")
|
||||
_request_remote_channels_with_timeout(node)
|
||||
return True
|
||||
|
||||
if len(menu_path) != 2:
|
||||
return False
|
||||
|
||||
category = menu_path[-1]
|
||||
if category == "Radio Settings":
|
||||
config = node.localConfig
|
||||
elif category == "Module Settings":
|
||||
config = node.moduleConfig
|
||||
else:
|
||||
return False
|
||||
|
||||
field = config.DESCRIPTOR.fields_by_name.get(selected_option)
|
||||
if field is None or field.name in {"version", "sessionkey"}:
|
||||
return False
|
||||
if status_callback:
|
||||
status_callback(f"Requesting {selected_option} config…")
|
||||
_request_remote_with_timeout(node.requestConfig, field)
|
||||
return True
|
||||
|
||||
|
||||
def settings_menu(
|
||||
stdscr: object, interface: object, node: object = None, remote: bool = False, status_callback=None
|
||||
) -> None:
|
||||
@@ -309,24 +353,6 @@ def settings_menu(
|
||||
node_name = node_info.get("user", {}).get("longName") if isinstance(node_info, dict) else None
|
||||
admin_target_label = node_name or f"!{node_num:08x}"
|
||||
|
||||
if remote:
|
||||
# Remote nodes do not populate radio config automatically. Request
|
||||
# the standard sections before building the menu. Module requests are
|
||||
# intentionally deferred: firmware may omit modules and never answer
|
||||
# those requests, which would otherwise block the entire UI.
|
||||
for config_type in list(node.localConfig.DESCRIPTOR.fields):
|
||||
if config_type.name in {"version", "sessionkey"}:
|
||||
continue
|
||||
if status_callback:
|
||||
status_callback(f"Requesting {config_type.name} config…")
|
||||
_request_remote_with_timeout(node.requestConfig, config_type)
|
||||
if status_callback:
|
||||
status_callback("Requesting channels…")
|
||||
node.requestChannels()
|
||||
if status_callback:
|
||||
status_callback("Waiting for channel list…")
|
||||
_request_remote_with_timeout(node.waitForConfig, "channels")
|
||||
|
||||
menu = generate_menu_from_protobuf(interface, node=node, include_app_settings=not remote)
|
||||
menu_state.current_menu = menu["Main Menu"]
|
||||
menu_state.menu_path = ["Main Menu"]
|
||||
@@ -782,7 +808,28 @@ def settings_menu(
|
||||
|
||||
menu_state.current_menu[selected_option] = (field, new_value)
|
||||
else:
|
||||
menu_state.current_menu = menu_state.current_menu[selected_option]
|
||||
requested_remote_section = False
|
||||
if remote:
|
||||
try:
|
||||
requested_remote_section = _request_remote_section(
|
||||
node, menu_state.menu_path, selected_option, status_callback
|
||||
)
|
||||
finally:
|
||||
if status_callback:
|
||||
status_callback(None)
|
||||
if requested_remote_section:
|
||||
# The response updates the node's protobufs. Rebuild
|
||||
# the menu before entering the requested section so
|
||||
# the newly received values are what the user sees.
|
||||
menu = generate_menu_from_protobuf(interface, node=node, include_app_settings=False)
|
||||
refreshed_menu = menu["Main Menu"]
|
||||
for path_step in menu_state.menu_path[1:]:
|
||||
refreshed_menu = refreshed_menu[path_step]
|
||||
menu_state.current_menu = refreshed_menu[selected_option]
|
||||
else:
|
||||
menu_state.current_menu = menu_state.current_menu[selected_option]
|
||||
else:
|
||||
menu_state.current_menu = menu_state.current_menu[selected_option]
|
||||
menu_state.menu_path.append(selected_option)
|
||||
menu_state.menu_index.append(menu_state.selected_index)
|
||||
menu_state.selected_index = 0
|
||||
|
||||
Reference in New Issue
Block a user