The 1.0 Python client, mirroring the Rust client 1.0 API and the DongLoRa Protocol v2 wire format. * Module split: monolithic protocol.py / client.py replaced with dongle.py (high-level async Dongle), session.py (reader task + tag routing), frame.py (streaming FrameDecoder + encode_frame), commands.py / events.py / errors.py / modulation.py / info.py / crc.py / chip_id (re-export) matching the donglora-protocol Rust crate. * Async API throughout. Serial I/O via asyncio-serial; mux connections via asyncio streams. The blocking client surface is gone. * thiserror-equivalent error hierarchy (DongloraError base with Timeout, TransportClosed, ReaderExited, BadFrame, ErrClientError variants mirroring the Rust ClientError). * Tag-aware dispatch with concurrent in-flight commands: TagAllocator hands out 16-bit device tags, Session routes each response back to the originating coroutine. * Auto-recovery from ERR(ENOTCONFIGURED): the Dongle caches the last SET_CONFIG and silently re-applies on timeout. * 500 ms background keepalive task per PROTOCOL.md §3.4. * Tests rewritten: test_dongle, test_session, test_frame, test_events, test_modulation, test_crc, test_ergonomics; old test_client / test_protocol removed. * Sticky mux reconnect (from the prior fix commit) is preserved: once a mux socket is in use, reconnects commit to it instead of falling back to USB.
38 lines
1.1 KiB
Python
38 lines
1.1 KiB
Python
"""COBS helper tests — the thin re-export layer.
|
|
|
|
Frame-level codec tests live in ``test_frame.py``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from cobs import cobs
|
|
|
|
from donglora.codec import cobs_encode
|
|
|
|
|
|
class TestCobsEncode:
|
|
def test_basic(self) -> None:
|
|
data = b"hello"
|
|
encoded = cobs_encode(data)
|
|
assert encoded[-1:] == b"\x00"
|
|
assert cobs.decode(encoded[:-1]) == data
|
|
|
|
def test_empty(self) -> None:
|
|
encoded = cobs_encode(b"")
|
|
assert encoded[-1:] == b"\x00"
|
|
assert cobs.decode(encoded[:-1]) == b""
|
|
|
|
def test_zeros(self) -> None:
|
|
data = b"\x00\x00\x00"
|
|
encoded = cobs_encode(data)
|
|
assert b"\x00" not in encoded[:-1]
|
|
assert cobs.decode(encoded[:-1]) == data
|
|
|
|
def test_roundtrip(self) -> None:
|
|
for length in [0, 1, 10, 100, 254, 255, 256]:
|
|
data = bytes(range(length % 256)) * (length // 256 + 1)
|
|
data = data[:length]
|
|
encoded = cobs_encode(data)
|
|
decoded = cobs.decode(encoded[:-1])
|
|
assert decoded == data
|