mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
feat(transport): CompositeTransport for dual Meshtastic+MeshCore (Phase 4) (#6)
* feat(transport): CompositeTransport for dual Meshtastic+MeshCore (Phase 4) Adds CompositeTransport (transport: both) that fans broadcasts to both meshes, sizes to min(children) for uniform messages, and routes DM replies back over the originating mesh via a transport hint threaded from the inbound MeshMessage. Per-child self-filtering; supervisor watchdog now resolves the Meshtastic child inside the composite. Additive/optional throughout; single-transport behavior unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(sizing): fixed universal mesh budget (mesh_max_chars=140) Replace per-transport / min-of-active max_chars with a single fixed universal constant (mesh_max_chars, default 140 = MeshCore LCD). Every message is built once against one deterministic budget regardless of which radios are connected; no runtime variance, no per-transport retooling. Meshtastic sizing intentionally moves 200 -> 140. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
225a5d37df
commit
f3df7a0a6d
16 changed files with 1140 additions and 76 deletions
|
|
@ -1,8 +1,8 @@
|
|||
"""Tests for Phase 3 uniform mesh-packet sizing.
|
||||
"""Tests for uniform mesh-packet sizing.
|
||||
|
||||
Verifies that every size-sensitive code path uses the active transport's
|
||||
max_chars rather than a hardcoded 200. Meshtastic default stays 200
|
||||
(byte-identical); MeshCore uses 140.
|
||||
Verifies that every size-sensitive code path uses the single universal
|
||||
mesh_max_chars constant (140 = MeshCore LCD). All transports — Meshtastic,
|
||||
MeshCore, and the composite — report the same fixed value.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
|
@ -127,59 +127,90 @@ def _minimal_config():
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestTransportMaxChars:
|
||||
def test_meshtastic_default_is_200(self):
|
||||
def test_meshtastic_default_is_140(self):
|
||||
"""Meshtastic now uses the universal mesh_max_chars constant (140)."""
|
||||
cfg = _mt_config()
|
||||
t = MeshtasticTransport(cfg)
|
||||
assert t.max_chars == 200
|
||||
|
||||
def test_meshtastic_respects_config_field(self):
|
||||
cfg = _mt_config(meshtastic_max_chars=160)
|
||||
t = MeshtasticTransport(cfg)
|
||||
assert t.max_chars == 160
|
||||
assert t.max_chars == 140
|
||||
|
||||
def test_meshcore_default_is_140(self):
|
||||
cfg = _mc_config()
|
||||
t = MeshCoreTransport(cfg)
|
||||
assert t.max_chars == 140
|
||||
|
||||
def test_meshcore_respects_config_field(self):
|
||||
cfg = _mc_config(meshcore_max_chars=120)
|
||||
t = MeshCoreTransport(cfg)
|
||||
assert t.max_chars == 120
|
||||
|
||||
def test_build_transport_meshtastic_max_chars(self):
|
||||
"""build_transport(meshtastic) → 140 (universal constant)."""
|
||||
t = build_transport(_mt_config())
|
||||
assert t.max_chars == 200
|
||||
assert t.max_chars == 140
|
||||
|
||||
def test_build_transport_meshcore_max_chars(self):
|
||||
t = build_transport(_mc_config())
|
||||
assert t.max_chars == 140
|
||||
|
||||
def test_mesh_max_chars_config_field_governs_all(self):
|
||||
"""A non-default mesh_max_chars propagates to both transport types."""
|
||||
cfg_mt = _mt_config(mesh_max_chars=120)
|
||||
assert MeshtasticTransport(cfg_mt).max_chars == 120
|
||||
|
||||
cfg_mc = _mc_config(mesh_max_chars=120)
|
||||
assert MeshCoreTransport(cfg_mc).max_chars == 120
|
||||
|
||||
def test_all_three_transports_and_composite_report_140(self):
|
||||
"""Sanity: all transports + CompositeTransport report mesh_max_chars=140.
|
||||
|
||||
This is the canonical single-budget guarantee: Meshtastic, MeshCore,
|
||||
and the composite (transport=both) all resolve to the same constant.
|
||||
"""
|
||||
from meshai.config import ConnectionConfig
|
||||
from meshai.transport.composite_transport import CompositeTransport
|
||||
|
||||
# Meshtastic
|
||||
assert MeshtasticTransport(_mt_config()).max_chars == 140
|
||||
|
||||
# MeshCore
|
||||
assert MeshCoreTransport(_mc_config()).max_chars == 140
|
||||
|
||||
# Composite (built via factory with transport="both")
|
||||
cfg_both = ConnectionConfig(
|
||||
transport="both",
|
||||
type="tcp",
|
||||
tcp_host="127.0.0.1",
|
||||
tcp_port=4403,
|
||||
meshcore_host="127.0.0.1",
|
||||
meshcore_port=5050,
|
||||
meshcore_channel_index=0,
|
||||
)
|
||||
comp = build_transport(cfg_both)
|
||||
assert isinstance(comp, CompositeTransport)
|
||||
assert comp.max_chars == 140
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. MeshRenderer char_limit propagation via channels
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestChannelRendererBudget:
|
||||
def test_broadcast_channel_with_meshcore_connector_uses_140(self):
|
||||
def test_broadcast_channel_propagates_connector_max_chars(self):
|
||||
"""Channel renderer inherits max_chars from whatever the connector reports."""
|
||||
conn = _fake_connector(140)
|
||||
ch = MeshBroadcastChannel(connector=conn, channel_index=0)
|
||||
assert ch._renderer._limit == 140
|
||||
|
||||
def test_broadcast_channel_with_meshtastic_connector_uses_200(self):
|
||||
conn = _fake_connector(200)
|
||||
def test_broadcast_channel_with_meshtastic_connector_uses_140(self):
|
||||
"""After the universal budget refactor, a meshtastic connector reports 140."""
|
||||
conn = _fake_connector(140)
|
||||
ch = MeshBroadcastChannel(connector=conn, channel_index=0)
|
||||
assert ch._renderer._limit == 200
|
||||
assert ch._renderer._limit == 140
|
||||
|
||||
def test_dm_channel_with_meshcore_connector_uses_140(self):
|
||||
def test_dm_channel_propagates_connector_max_chars(self):
|
||||
conn = _fake_connector(140)
|
||||
ch = MeshDMChannel(connector=conn, node_ids=["!aabbccdd"])
|
||||
assert ch._renderer._limit == 140
|
||||
|
||||
def test_dm_channel_with_meshtastic_connector_uses_200(self):
|
||||
conn = _fake_connector(200)
|
||||
def test_dm_channel_with_meshtastic_connector_uses_140(self):
|
||||
conn = _fake_connector(140)
|
||||
ch = MeshDMChannel(connector=conn, node_ids=["!aabbccdd"])
|
||||
assert ch._renderer._limit == 200
|
||||
assert ch._renderer._limit == 140
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -187,11 +218,12 @@ class TestChannelRendererBudget:
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestBuildPipelineMeshCharLimit:
|
||||
def test_no_connector_uses_200(self):
|
||||
def test_no_connector_uses_140(self):
|
||||
"""Without a connector the pipeline falls back to the universal constant (140)."""
|
||||
cfg = _minimal_config()
|
||||
bus = build_pipeline(cfg, llm_backend=None, connector=None)
|
||||
acc = bus._pipeline_components["accumulator"]
|
||||
assert acc._mesh_char_limit == 200
|
||||
assert acc._mesh_char_limit == 140
|
||||
|
||||
def test_meshcore_connector_uses_140(self):
|
||||
cfg = _minimal_config()
|
||||
|
|
@ -200,12 +232,13 @@ class TestBuildPipelineMeshCharLimit:
|
|||
acc = bus._pipeline_components["accumulator"]
|
||||
assert acc._mesh_char_limit == 140
|
||||
|
||||
def test_meshtastic_connector_uses_200(self):
|
||||
def test_meshtastic_connector_uses_140(self):
|
||||
"""After universal budget, a meshtastic connector also reports 140."""
|
||||
cfg = _minimal_config()
|
||||
conn = _fake_connector(200)
|
||||
conn = _fake_connector(140)
|
||||
bus = build_pipeline(cfg, llm_backend=None, connector=conn)
|
||||
acc = bus._pipeline_components["accumulator"]
|
||||
assert acc._mesh_char_limit == 200
|
||||
assert acc._mesh_char_limit == 140
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue