The session flood scope is framed by MeshTerm, so an older meshcore 2.3 can send scoped

reset_flood_scope and force_unscoped arrived late in meshcore 2.3.x. The uConsole's 2.3.7 satisfies meshcore>=2.3 without them, so every scoped channel send would have crashed on restoring the scope. set_flood_scope now sends the firmware's command 54 itself ([54][0][key], [54][0], [54][1]), as the default scope's command 63 already was. Every other library call MeshTerm makes exists in 2.3.7.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 5.5 committed 2026-09-25 02:06:44 -04:00
1 parent b0d89a5ece
commit 01a8f711d6
2 files changed
+28 -8

No files matched your search

+18 -6
View File
@@ -323,6 +323,11 @@ _CMD_SET_AUTOADD_CONFIG = 58
#: :meth:`MeshCoreDevice.set_default_flood_scope` itself rather than the library.
_CMD_SET_DEFAULT_FLOOD_SCOPE = 63
#: Companion command that sets (or clears, or forces off) the session flood scope
#: (firmware 1.10+; the force-unscoped mode 1.16+), framed by
#: :meth:`MeshCoreDevice.set_flood_scope` itself rather than the library.
_CMD_SET_FLOOD_SCOPE_KEY = 54
#: ``RESP_CODE_AUTOADD_CONFIG`` — the reply to a read of the auto-add configuration. Its
#: second payload byte is the hop limit, which the library's parser drops (it keeps only the
#: bitmask), so :meth:`MeshCoreDevice.get_autoadd_config` recognises the raw frame by this.
@@ -3240,16 +3245,23 @@ class MeshCoreDevice(Device):
self._ok(await mc.commands.send(frame, [EventType.OK, EventType.ERROR]))
async def set_flood_scope(self, region: str | None) -> None: # noqa: D102
commands = self._require().commands
from meshcore import EventType
# Framed here, like the default scope, rather than through the library's helpers:
# ``reset_flood_scope`` and ``force_unscoped`` arrived late in meshcore 2.3.x, and
# an install that satisfies ``meshcore>=2.3`` without them (the uConsole's 2.3.7)
# crashed every scoped channel send on the restore. The frame is the firmware's
# own ``CMD_SET_FLOOD_SCOPE_KEY``: ``[54][0][key16]`` sets the session scope,
# ``[54][0]`` alone clears it back to the default, ``[54][1]`` forces unscoped.
bare = normalize_region(region) if region else ""
if not bare:
self._ok(await commands.reset_flood_scope())
frame = bytes([_CMD_SET_FLOOD_SCOPE_KEY, 0])
elif bare == REGION_WILDCARD:
self._ok(await commands.force_unscoped())
frame = bytes([_CMD_SET_FLOOD_SCOPE_KEY, 1])
else:
# The key goes over as bytes, which the library sends as-is — its string path
# would derive the same key, but through its own ``#`` handling.
self._ok(await commands.set_flood_scope(region_key(validate_region(bare))))
frame = bytes([_CMD_SET_FLOOD_SCOPE_KEY, 0]) + region_key(validate_region(bare))
mc = self._require()
self._ok(await mc.commands.send(frame, [EventType.OK, EventType.ERROR]))
async def set_manual_add_contacts(self, enabled: bool) -> None: # noqa: D102
self._ok(await self._require().commands.set_manual_add_contacts(enabled))
+10 -2
View File
@@ -285,13 +285,21 @@ def test_default_scope_frame_is_bare_and_byte_padded() -> None:
def test_session_scope_sends_the_key_the_override_or_the_reset() -> None:
"""A name sends its key; ``*`` forces unscoped; ``None`` falls back to the default."""
"""A name sends its key; ``*`` forces unscoped; ``None`` falls back to the default.
Framed by MeshTerm as the firmware's command 54, never through the library's
``reset_flood_scope``/``force_unscoped``, which an older meshcore 2.3 lacks.
"""
commands = _Commands()
device = _device(commands)
asyncio.run(device.set_flood_scope("#harbour"))
asyncio.run(device.set_flood_scope("*"))
asyncio.run(device.set_flood_scope(None))
assert commands.calls == [("scope", region_key("harbour")), ("unscoped",), ("reset",)]
assert commands.calls == [
("send", bytes([54, 0]) + region_key("harbour")),
("send", bytes([54, 1])),
("send", bytes([54, 0])),
]
def test_mock_device_mirrors_scope_and_answers_regions_from_repeaters() -> None: