Sends a binary channel datagram: flood by default, or along a given path. Validates data_type (1..0xFFFF), payload length (<= 163) and channel index (one byte), like send_raw_data.
The companion protocol lists PACKET_CHANNEL_DATA_RECV (0x1B) as an
answer to GET_MESSAGE (0x0A). PR #88 added the CHANNEL_DATA_RECV
event and parser, but get_msg() still waited only for
CONTACT_MSG_RECV, CHANNEL_MSG_RECV, ERROR and NO_MORE_MSGS. When the
next queued message is a channel datagram, get_msg() therefore ignored
the device's answer and returned ERROR (reason "timeout") after
default_timeout, which also stops the auto-fetch loop.
Add CHANNEL_DATA_RECV to the expected events and cover it with a test
that runs a real CHANNEL_DATA_RECV frame through MessageReader and
EventDispatcher.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Use packet type 0x16 and include the four-byte timestamp so the
fixture exercises ADVERT_PATH parsing instead of PRIVATE_KEY handling.
- Preserve the embedded zero byte and existing path assertions.
send_msg_with_retry could return None for a message the recipient received and ACKed:
- The ACK subscription was registered only after send_msg returned. If the ACK was queued right behind MSG_SENT (both frames in one read, or the event loop busy for the round trip), it was dispatched with no listener.
- Each attempt waited only for its own ACK code. A late ACK for an earlier attempt, which the device still reports from its expected-ACK table, was ignored.
The ACK subscription now spans the whole call and accepts the code of any attempt sent so far. All attempts also share one timestamp—the attempt byte already makes each packet unique—so receivers can tell a retry from a new message.
Both commands already existed in commands/device.py but had no entry in
the README's command reference table, so users had no way to discover
them (reported as "missing"). Add rows under Advanced Configuration
explaining that mode controls how many bytes of each hop's node ID are
stored per hop in advertised/logged paths (bytes per hop = mode + 1),
matching the plen >> 6 / hash_mode + 1 logic in reader.py and
commands/base.py.
Fixes#84
logging.basicConfig() at import time in __init__.py silently clobbered the
embedding application's own logging setup. Replace it with a NullHandler so
the library stays silent by default without touching global config.
Also stop MeshCore.__init__ from unconditionally forcing the "meshcore"
logger to INFO when neither debug= nor only_error= is passed - only set a
level when the caller explicitly asks for one, otherwise leave whatever the
app already configured alone.
Document the idiomatic logging setup for consumers in README.md.
Fixes#58
parsePacketPayload copies message/msg_hash/sender_timestamp/attempt/txt_type from a
prior channels_log entry matched by pkt_hash. When that prior entry was logged while
the channel could not be decrypted it has none of those keys, so a later duplicate of
the same transmission raises KeyError: 'message' and aborts handle_rx for the whole
packet, dropping its RX_LOG_DATA. It triggers when a channel-table refresh lands
between two copies of a flooded channel message.
Copy with .get() so the fields are simply absent instead of raising. Adds a regression
test that reproduces the crash (fails on main, passes here).