- Reserve the link's sockets, so the socket pool is sized for them
- Test the server role: accepting a client, receiving from it, and
accepting the next after it disconnects
- Test a frame assembled across several reads, after a keep-alive
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code generation declares the TCP link's receive buffer as a static array
of max_packet_size bytes, so it is not allocated at run time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Packets go over one TCP connection, as a client that connects to a host
or a server that accepts one client at a time. TCP resends what is lost
and joins exactly two devices, so the link provides the reliability,
addressing and session layers itself, and those cannot be configured on
it. A link platform names such layers with the new provides argument.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The preamble flag stays set when no sync word follows, so a packet from
another network held every send back until max_busy ran out. A link
without LoRa now needs a sync_value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without one the radio cannot tell that a packet is arriving, so listen
before talk does nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Without LoRa, require the SX127x radio to have a sync word or preamble
detection, as listen before talk cannot see a packet arriving otherwise
- Warn once when a send finds the radio failed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Warn when a send goes ahead over a busy period at its limit, as that
means a stuck busy indication or a jammed channel
- Log an empty packet at verbose level, and say why it counts as sent
- Name the radio id if code generation cannot find its configuration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Reject a radio with rx_start false when the link or a peer has a layer
that needs packets back from the peer, as the radio sleeps after sending
- Log a send over a long busy period at debug level, so a lost reception
can be traced
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Make both links one RadioLink template in packet_interface, with shared
code generation in radio_link.py, so a fix applies to both radios
- While sending is held back, watch the radio from loop() and end the
busy period once it is free, so a later reception starts a new period
instead of being sent over
- Reject an SX127x link without LoRa unless the radio is in packet mode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The radio links have no buffer of their own, so code generation passes
the configured max_packet_size to the constructor.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- 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>