mirror of
https://github.com/meshtastic/firmware.git
synced 2026-05-19 14:25:28 -04:00
* 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>
84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
"""Mesh: explicit two-way communication, single pass/fail.
|
|
|
|
Opens a ReceiveCollector on EVERY role, sends a uniquely-tagged broadcast
|
|
from each role in turn, and asserts every OTHER role saw it. One atomic
|
|
test that answers "is the mesh actually working both directions?".
|
|
|
|
Not parametrized — it inherently involves the full hub.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from ._receive import ReceiveCollector
|
|
|
|
|
|
@pytest.mark.timeout(300)
|
|
def test_bidirectional_mesh_communication(
|
|
baked_mesh: dict[str, Any],
|
|
) -> None:
|
|
"""Requires ≥2 baked roles.
|
|
|
|
For each role, broadcast a unique tag. Assert every other role's
|
|
ReceiveCollector saw that tag within a 120s window per direction.
|
|
"""
|
|
roles = sorted(baked_mesh.keys())
|
|
if len(roles) < 2:
|
|
pytest.skip(f"need ≥2 roles; have {roles!r}")
|
|
|
|
# Open receive collectors on every role BEFORE sending anything.
|
|
collectors: dict[str, ReceiveCollector] = {}
|
|
try:
|
|
for role in roles:
|
|
rx = ReceiveCollector(
|
|
baked_mesh[role]["port"], topic="meshtastic.receive.text"
|
|
)
|
|
rx.__enter__()
|
|
collectors[role] = rx
|
|
|
|
# Let the meshtastic interfaces stabilize before the first send
|
|
time.sleep(2.0)
|
|
|
|
# From each role, send a uniquely-tagged broadcast. We MUST send through
|
|
# the already-open collector — opening a new SerialInterface here would
|
|
# race the collector's exclusive lock on the port.
|
|
tags: dict[str, str] = {}
|
|
for sender in roles:
|
|
tag = f"bidi-{sender}-{int(time.time() * 1000) % 100_000}"
|
|
tags[sender] = tag
|
|
collectors[sender].send_text(tag)
|
|
# Small gap so airtime doesn't overlap
|
|
time.sleep(4.0)
|
|
|
|
# Every OTHER role must see every sender's tag within 120s each
|
|
missing: list[str] = []
|
|
for sender, tag in tags.items():
|
|
for receiver in roles:
|
|
if receiver == sender:
|
|
continue
|
|
got = collectors[receiver].wait_for(
|
|
lambda pkt, t=tag: pkt.get("decoded", {}).get("text") == t,
|
|
timeout=120,
|
|
)
|
|
if got is None:
|
|
observed = [
|
|
p.get("decoded", {}).get("text")
|
|
for p in collectors[receiver].snapshot()
|
|
]
|
|
missing.append(
|
|
f"{sender}->{receiver}: tag {tag!r} not seen; "
|
|
f"receiver got {observed!r}"
|
|
)
|
|
|
|
assert not missing, "bidirectional comms incomplete:\n " + "\n ".join(missing)
|
|
finally:
|
|
for rx in collectors.values():
|
|
try:
|
|
rx.__exit__(None, None, None)
|
|
except Exception:
|
|
pass
|