mirror of
https://github.com/meshcore-dev/meshcore_py.git
synced 2026-11-01 02:02:00 +00:00
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:
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user