Add MCP server for interacting with meshtastic devices and testing framework / TUI (#10194)
* Start of MCP server and test suite * Add MCP server for interacting with meshtastic devices and testing framework / TUI * Update mcp-server/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix mcp-server review feedback from thread Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/91dc128a-ed50-4d07-8bb2-3dc6623a05f7 Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Enhance StreamAPI and PhoneAPI for improved log record handling and concurrency control * Semgrep fixes * Trunk and semgrep fixes * optimize pio streaming tee file writes Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/04e26c6b-6a2b-45be-bbeb-79ae4d0be633 Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * chore: remove redundant log handle assignment Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/04e26c6b-6a2b-45be-bbeb-79ae4d0be633 Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Consolidate type imports and remove placeholder test files * Add tests for config persistence and more exchange messages * Refactor position test to validate on-demand request/reply behavior * Remove position request/reply test and update README for telemetry behavior * Fix transmit history file to get removed on factory reset --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
thebentern
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
parent
aab4cd086f
commit
c8dac10348
@@ -0,0 +1,98 @@
|
||||
"""In-memory registry of active serial monitor sessions and port locks.
|
||||
|
||||
Two things live here so the rest of the package has a single place to reach
|
||||
them:
|
||||
1. `sessions`: `{session_id: SerialSession}` for pio device monitor subprocs.
|
||||
2. `port_locks`: `{port: threading.Lock}` so admin/info tools can fail fast
|
||||
when a serial monitor or another meshtastic client already owns a port.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
from .serial_session import SerialSession, close_session
|
||||
|
||||
_LOCK = threading.Lock()
|
||||
_sessions: dict[str, SerialSession] = {}
|
||||
_port_locks: dict[str, threading.Lock] = {}
|
||||
|
||||
|
||||
def register_session(session: SerialSession) -> None:
|
||||
with _LOCK:
|
||||
_sessions[session.id] = session
|
||||
|
||||
|
||||
def get_session(session_id: str) -> SerialSession:
|
||||
with _LOCK:
|
||||
session = _sessions.get(session_id)
|
||||
if session is None:
|
||||
raise KeyError(f"Unknown session_id: {session_id!r}")
|
||||
return session
|
||||
|
||||
|
||||
def remove_session(session_id: str) -> SerialSession | None:
|
||||
with _LOCK:
|
||||
return _sessions.pop(session_id, None)
|
||||
|
||||
|
||||
def active_session_for_port(port: str) -> SerialSession | None:
|
||||
"""Find any active (non-eof) session owning `port`."""
|
||||
sweep_dead()
|
||||
with _LOCK:
|
||||
for s in _sessions.values():
|
||||
if s.port == port and s.proc.poll() is None:
|
||||
return s
|
||||
return None
|
||||
|
||||
|
||||
def all_sessions() -> list[SerialSession]:
|
||||
with _LOCK:
|
||||
return list(_sessions.values())
|
||||
|
||||
|
||||
def sweep_dead() -> int:
|
||||
"""Remove sessions whose subprocess has exited. Returns count removed."""
|
||||
removed_sessions: list[SerialSession] = []
|
||||
with _LOCK:
|
||||
for sid, s in list(_sessions.items()):
|
||||
if s.proc.poll() is not None:
|
||||
removed_sessions.append(_sessions.pop(sid))
|
||||
for session in removed_sessions:
|
||||
try:
|
||||
close_session(session)
|
||||
except Exception:
|
||||
pass
|
||||
return len(removed_sessions)
|
||||
|
||||
|
||||
def shutdown_all() -> None:
|
||||
"""Close every live session (called on server exit)."""
|
||||
with _LOCK:
|
||||
items = list(_sessions.items())
|
||||
_sessions.clear()
|
||||
for _sid, session in items:
|
||||
try:
|
||||
close_session(session)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def port_lock(port: str) -> threading.Lock:
|
||||
"""Per-port lock for SerialInterface / admin tool serialization."""
|
||||
with _LOCK:
|
||||
lock = _port_locks.get(port)
|
||||
if lock is None:
|
||||
lock = threading.Lock()
|
||||
_port_locks[port] = lock
|
||||
return lock
|
||||
|
||||
|
||||
def snapshot() -> dict[str, Any]:
|
||||
"""Debug dump: session count, port lock count."""
|
||||
with _LOCK:
|
||||
return {
|
||||
"sessions": len(_sessions),
|
||||
"port_locks": len(_port_locks),
|
||||
}
|
||||
Reference in New Issue
Block a user