- Hold a send back for at most the airtime of the largest packet, worked
out from the radio settings and at least 2 s, instead of a fixed 2 s
that a LoRa packet at a high spreading factor outlasts
- End a busy period when a send finds the radio free, so a busy flag that
never clears is released however rarely sends are tried
- Detect an FSK reception on the SX127x from its preamble and sync word
flags
- Test the radio links' final validation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each packet is one radio packet, with the RSSI and SNR of received
packets in the metadata. A radio cannot hear while it sends, so a link
refuses to send while its radio is receiving a packet, for up to two
seconds, and the layers above send again shortly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Work out each layer's packet size during validation, as the C++ does,
and reject a fragmentation max_packet_size whose fragments do not all
fit in the reliability queue at once, or need more than 128 fragments
- Fit the test peer's largest packet in its queue
- Reuse interface_layers() in final_validate_interface()
- Keep the earlier tests in place in test_packet_interface.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A broadcast from a device with its own port went to that port, whose
layers drop it, so a broadcast port could not reach reliable peers. It now
goes to the broadcast port when there is one.
Fragmentation allocates its buffers with RAMAllocator and fails setup when
they cannot be allocated. A fragment from another source no longer drops
the packet being joined.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AddressingPort derives max_packet_size from the link in its constructor,
and the fragmentation layer passes its configured size to the base
constructor.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- Reject an addressing destination or peer that is this device's own
address, whose packets would be dropped as echoes
- Show 0 fragments, not SIZE_MAX, for a link with no room
- Test the check that applications use a peer's id
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As in the other layers, addressing ports and fragmentation get no room
when max_packet_size is smaller than their header, and fragmentation
fails its setup instead of dividing by zero.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The addressing layer lets several devices share a channel such as an
RS485 bus or a radio, with 16-bit addresses. A link can carry several
peers, each with its own stack of layers and its own id. Layers that work
between two devices only are rejected with broadcast addressing. The
fragmentation layer splits packets larger than the layers below allow,
and joins them again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Set a warning once the interface has accepted no packets for a minute,
and log refused dictionary packets as data packets are
- Clear remote entities that a provider's dictionary does not name also
when the provider goes back to a dictionary already seen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Check a data packet before asking for its dictionary, so a damaged
packet does not make the provider resend everything
- When a provider's complete dictionary does not name a remote entity,
warn and clear that entity's state, as a typo in provider or remote_id,
or an entity the provider no longer sends, was otherwise silent
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dictionary packet now carries its part index and a last-part flag. A dictionary counts as received, and is not
asked for again, only once its last part follows every other part in order. Until then, data under it sends a
request even when some remote entities are already known.
A refused request no longer starts the request interval. A short or malformed packet, or one with another format
version, is logged as a warning at most once a minute.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a session and a reliability layer, the update interval defaults to
never, and unchanged states are not sent again, so a receiver's timeout
cleared them on a working link. The session layer already clears the
states when the other device cannot be reached.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each data record now starts with a 16-bit tag holding the entity's number
and type, and the form of its payload, so that more entity types can be
added and a receiver can skip records of types it does not know. A binary
sensor's state is in the tag; a sensor has a 4 byte state, or none.
Data and dictionary packets are checked in full before anything in them
is used, so a damaged or forged packet changes no state and teaches nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dictionary that names none of the remote entities was asked for again
every 5 seconds while any remote entity was unresolved, and each request
made its provider resend everything. A provider's new dictionary was not
asked for once every remote entity was resolved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mock link takes max_packet_size in its constructor, so a test that
needs a different size builds a local link.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- Compare received ids by length and bytes, so an id with an embedded NUL
cannot read past the configured string
- Fail setup when max_packet_size cannot hold the device name and the
longest entity id, instead of skipping the id at every resend
- Reject two entities of one type sent with the same id
- Avoid an unused variable without remote entities
- Use matching ids in the test configuration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sends sensor and binary sensor states to other devices over a packet
interface. A dictionary sent on connect, on request and at an interval
names the entities, so data packets refer to them by number. The update
interval and the handling of a lost peer follow the layers the interface
has, and remote states can time out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rolling code accepts a packet only once the peer has sent back this
device's boot number, so a device that only receives never gets a packet.
The session's keep-alives make sure packets go both ways.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both layers derive max_packet_size from the layer below in their
constructors instead of assigning it in the body.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- Fail setup, instead of underflowing, when max_packet_size leaves the
encryption or session layer no room
- Warn when the boot number cannot be saved
- Warn once when the peer's packets carry an older boot number, which
happens when its flash is erased
- Test the counter wrapping to a new boot number
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session layer sends keep-alives and reports whether the peer can be
reached, including after it restarts or a reliability layer below loses
data. The encryption layer encrypts and checks each packet with XXTEA,
with an optional rolling code against replays. Components that a layer
needs, such as binary_sensor, are loaded only when it is configured.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Such a type would be missing from the link schema, and selecting it would
fail with an unrelated error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While the peer is away the layers above keep sending, so the message
repeated about once a second until the link came up again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A frame larger than max_packet_size, usually from a peer with a larger
setting, is warned about once. A link reset that the peer does not answer
is warned about after max_retries tries, and a full queue is logged at
debug level while the link is down.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every module in layers/ is imported automatically, so two modules
registering the same name silently replaced one with the other, in an
order decided by pkgutil. Registration now raises, so the mistake is
found when the configuration is loaded.
Co-Authored-By: Claude Code <noreply@anthropic.com>
A peer that has not heard the UA repeats its SABM, and cannot have sent a
numbered frame meanwhile. Treating that as a restart threw away queued
frames and reported the session lost for a single dropped UA. A SABM is
now a restart only if the peer has sent a numbered frame since the link
came up.
Co-Authored-By: Claude Code <noreply@anthropic.com>
max_packet_size is derived from the layer below in the constructor
instead of being assigned in the body.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- Keep App alive across the HDLC tests, as the test main requires
- Run a layer's loop in the tests only while it has asked for it, so
the loop's disable and request bookkeeping is tested, and wait for the
retry timer in the tests of refused sends
- Report no send capacity while the layer has no send queue
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>