Merge pull request #114 from maxburto/feat/send-channel-data

Add send_channel_data() for CMD_SEND_CHANNEL_DATA (0x3E)
This commit is contained in:
fdlamotte
2026-10-03 15:10:14 -04:00
committed by GitHub
2 changed files with 106 additions and 0 deletions
+35
View File
@@ -368,6 +368,41 @@ class MessagingCommands(CommandHandlerBase):
data = bytes([0x19, len(path)]) + path + bytes(payload)
return await self.send(data, [EventType.OK, EventType.ERROR])
async def send_channel_data(
self, chan: int, data_type: int, payload: bytes, path: Optional[bytes] = None
) -> Event:
"""Send a binary datagram to a channel via CMD_SEND_CHANNEL_DATA (0x3E).
Command format:
0x3E | channel_idx(1) | path_len(1) | path(path_len bytes) | data_type(2, LE) | payload
Args:
chan: Channel index (0-255, must fit in one byte; the firmware rejects unknown channels).
data_type: Application identifier, 0x0001-0xFFFF (0xFFFF is the developer namespace).
payload: Binary payload, at most 163 bytes (MAX_CHANNEL_DATA_LENGTH).
path: Optional path bytes to send along (default None = flood, path_len 0xFF).
Returns:
Event with OK or ERROR.
"""
if not isinstance(payload, (bytes, bytearray)):
raise TypeError("payload must be bytes-like")
if not 0 <= chan <= 0xFF:
raise ValueError("chan must fit in one byte (0-255)")
if not 0x0001 <= data_type <= 0xFFFF:
raise ValueError("data_type must be between 0x0001 and 0xFFFF")
if len(payload) > 163:
raise ValueError("payload must be at most 163 bytes")
if path is None:
path_bytes = b"\xff"
else:
path = bytes(path)
if len(path) >= 0xFF:
raise ValueError("path must be shorter than 255 bytes")
path_bytes = bytes([len(path)]) + path
data = bytes([0x3E, chan]) + path_bytes + data_type.to_bytes(2, "little") + bytes(payload)
return await self.send(data, [EventType.OK, EventType.ERROR])
async def set_flood_scope(self, scope, force_unscoped=False):
if scope is None:
logger.debug(f"Resetting scope")
+71
View File
@@ -489,6 +489,77 @@ async def test_channel_data_recv_widened_data_type():
assert evt.payload["payload"] == ""
# ---------------------------------------------------------------------------
# Command wrapper: send_channel_data (CMD_SEND_CHANNEL_DATA, 0x3E)
# ---------------------------------------------------------------------------
def _make_channel_data_commands():
"""MessagingCommands whose send() records the written frame and expected events."""
from meshcore.commands.messaging import MessagingCommands
cmd = MessagingCommands.__new__(MessagingCommands)
sent = []
async def mock_send(data, expected_events, timeout=None):
sent.append((bytes(data), expected_events))
return Event(EventType.OK, {})
cmd.send = mock_send
return cmd, sent
@pytest.mark.asyncio
async def test_send_channel_data_flood_matches_protocol_example():
"""Flood frame equals the companion protocol example: 3E 01 FF FF FF A1 B2 C3."""
cmd, sent = _make_channel_data_commands()
evt = await cmd.send_channel_data(1, 0xFFFF, b"\xA1\xB2\xC3")
assert evt.type == EventType.OK
assert sent == [
(bytes.fromhex("3E01FFFFFFA1B2C3"), [EventType.OK, EventType.ERROR]),
]
@pytest.mark.asyncio
async def test_send_channel_data_with_path():
"""A path is sent after its length byte; data_type is little-endian."""
cmd, sent = _make_channel_data_commands()
await cmd.send_channel_data(2, 0x0102, b"\x07", path=b"\xAA\xBB")
assert sent[0][0] == bytes.fromhex("3E02" "02" "AABB" "0201" "07")
@pytest.mark.asyncio
async def test_send_channel_data_max_payload_accepted():
"""A 163-byte payload (MAX_CHANNEL_DATA_LENGTH) is sent."""
cmd, sent = _make_channel_data_commands()
await cmd.send_channel_data(0, 0x0100, b"\x00" * 163)
assert len(sent[0][0]) == 3 + 2 + 163
@pytest.mark.asyncio
async def test_send_channel_data_rejects_invalid_arguments():
"""Reserved data_type, oversized payload, bad chan and non-bytes payload never reach send()."""
cmd, sent = _make_channel_data_commands()
with pytest.raises(ValueError):
await cmd.send_channel_data(1, 0x0000, b"\x01")
with pytest.raises(ValueError):
await cmd.send_channel_data(1, 0x10000, b"\x01")
with pytest.raises(ValueError):
await cmd.send_channel_data(1, 0xFFFF, b"\x00" * 164)
with pytest.raises(ValueError):
await cmd.send_channel_data(256, 0xFFFF, b"\x01")
with pytest.raises(TypeError):
await cmd.send_channel_data(1, 0xFFFF, "text")
assert sent == []
# ---------------------------------------------------------------------------
# Wire-format parity bundle: AUTOADD_CONFIG (max_hops trailing byte)
# ---------------------------------------------------------------------------