2#ifdef USE_PACKET_INTERFACE_HDLC
10static constexpr uint32_t RETRANSMIT_TIMER_ID = 0;
11static constexpr uint32_t ACK_TIMER_ID = 1;
12static constexpr uint32_t SEND_RETRY_TIMER_ID = 2;
14static constexpr uint32_t SEND_RETRY_MS = 50;
16static constexpr uint32_t RESET_INTERVAL_MS = 1000;
19static constexpr uint8_t POLL_FINAL_BIT = 0x10;
20static constexpr uint8_t SUPERVISORY_FRAME = 0x01;
21static constexpr uint8_t SUPERVISORY_RR = 0x00;
22static constexpr uint8_t SUPERVISORY_REJ = 0x02;
23static constexpr uint8_t UNNUMBERED_SABM = 0x2F;
24static constexpr uint8_t UNNUMBERED_UA = 0x63;
26static uint16_t frame_crc(uint8_t control,
const uint8_t *payload,
size_t length) {
27 uint16_t crc =
crc16(&control, 1);
38 this->
mark_failed(LOG_STR(
"max_packet_size too small for the configured layers"));
44 this->
mark_failed(LOG_STR(
"Not enough memory for the send queue"));
53 "HDLC Reliability on '%s':\n"
54 " Max Packet Size: %zu\n"
55 " Timeout: %" PRIu32
"ms\n"
56 " Ack Delay: %" PRIu32
"ms\n"
57 " Max Queue Length: %u\n"
109 ESP_LOGW(TAG,
"%s: Packet of %zu bytes is larger than %zu", this->
name_, size, this->
max_packet_size_);
115 ESP_LOGV(TAG,
"%s: Send queue full", this->
name_);
119 ESP_LOGD(TAG,
"%s: Send queue full, waiting for the peer to answer the link reset", this->
name_);
126 this->
vs_ = (seq + 1) & 7;
130 if (outstanding == 0)
137 uint16_t crc = frame_crc(control, payload,
length);
138 uint8_t
trailer[2] = {
static_cast<uint8_t
>(crc),
static_cast<uint8_t
>(crc >> 8)};
143 ESP_LOGV(TAG,
"%s: Lower layer did not accept frame 0x%02X", this->
name_, control);
148 if (!this->
send_frame_(SUPERVISORY_FRAME | (
type << 2) | (this->
vr_ << 5),
nullptr, 0))
158 if (this->
vn_ == this->
vs_)
160 uint8_t control = (this->
vn_ << 1) | (this->
vr_ << 5);
167 this->
vn_ = (this->
vn_ + 1) & 7;
168 if (this->
vn_ == this->
vs_)
182 interval = RESET_INTERVAL_MS;
193 if (this->
retries_ == this->max_retries_) {
194 ESP_LOGW(TAG,
"%s: No answer to the link reset after %u tries; check that the peer has a reliability layer",
195 this->
name_, this->max_retries_);
205 ESP_LOGW(TAG,
"%s: No acknowledgement after %u retries, resetting link", this->
name_, this->
max_retries_);
210 ESP_LOGV(TAG,
"%s: Timeout, resending %u frames", this->
name_, this->
outstanding_());
233 ESP_LOGD(TAG,
"%s: Link connected", this->
name_);
246 uint8_t acked = (nr - this->
va_) & 7;
248 ESP_LOGV(TAG,
"%s: Ignoring invalid N(R) %u", this->
name_, nr);
254 if (acked > ((this->
vn_ - this->
va_) & 7))
268 if (!data.is_contiguous()) {
269 ESP_LOGW(TAG,
"%s: Packet split across buffers is not supported", this->
name_);
272 auto frame = data.as_span();
278 uint8_t control = frame[0];
279 const uint8_t *payload = frame.data() + 1;
280 uint16_t crc = frame[
length + 1] | (frame[
length + 2] << 8);
281 if (frame_crc(control, payload,
length) != crc) {
282 ESP_LOGV(TAG,
"%s: CRC error, frame dropped", this->
name_);
291 "%s: Frame of %zu bytes is larger than %zu, dropped; check that both devices use the same "
295 ESP_LOGV(TAG,
"%s: Frame of %zu bytes is larger than %zu, dropped", this->
name_, length, this->
max_packet_size_);
302 uint8_t command = control & ~POLL_FINAL_BIT;
303 if (command == UNNUMBERED_SABM) {
308 ESP_LOGD(TAG,
"%s: Link reset by peer", this->
name_);
318 if (command == UNNUMBERED_UA) {
328 uint8_t nr = control >> 5;
329 if ((control & 1) == 0) {
334 if ((control & 3) != SUPERVISORY_FRAME)
337 if (((control >> 2) & 3) == SUPERVISORY_REJ && this->
outstanding_() != 0) {
338 ESP_LOGV(TAG,
"%s: Peer rejected from %u", this->
name_, nr);
346 uint8_t ns = (control >> 1) & 7;
347 if (ns != this->
vr_) {
357 this->
vr_ = (this->
vr_ + 1) & 7;
void mark_failed()
Mark this component as failed.
bool cancel_timeout(const char *name)
Cancel a timeout function.
void enable_loop()
Enable this component's loop.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
void disable_loop()
Disable this component's loop.
An STL allocator that uses SPI or internal RAM.
void owe_ack_(bool now)
An RR is owed: at once, or after the ack delay unless an I-frame carries it first.
HdlcReliability(PacketInterface *lower)
void dump_config() override
@ LINK_STATE_RESET_PENDING
void resend_outstanding_()
uint8_t * slot_(uint8_t seq)
std::array< uint16_t, MAX_WINDOW > tx_lengths_
void receive_info_(uint8_t control, const uint8_t *payload, size_t length, const PacketMetaData &meta_data)
bool send_frame_(uint8_t control, const uint8_t *payload, size_t length)
uint8_t slot_index_(uint8_t seq) const
bool send_supervisory_(uint8_t type)
void process_ack_(uint8_t nr)
uint8_t max_queue_length_
uint8_t outstanding_() const
bool send_to_interface(const PacketBuffer &data, const PacketMetaData &meta_data) override
void receive_(const PacketBuffer &data, const PacketMetaData &meta_data)
static constexpr size_t FRAME_OVERHEAD
Control byte plus CRC-16.
A buffer class that efficiently represents a composite buffer with three sections:
size_t copy_to(uint8_t *dest, size_t count, size_t offset=0) const
Copy data from the buffer to a destination pointer.
size_t size() const
Get the total size of the buffer.
A class that will transport data to and from an abstract channel.
void note_received_(bool understood)
Called by a layer for each received packet it understood, or could not make sense of.
void stack_on_(PacketInterface *lower, Capability capability)
Called by a layer's constructor to record itself as the layer above lower, adding its own capability.
virtual bool send_to_interface(const PacketBuffer &data, const PacketMetaData &meta_data)=0
Method to be implemented by child classes to send data to the interface.
void add_packet_interface_listener(F &&callback)
Add a listener that will be called when data is received.
LazyCallbackManager< void(bool)> session_callback_
const size_t max_packet_size_
void on_receive_from_interface_(const PacketBuffer &data, const PacketMetaData &meta_data)
This function should be called by implementing classes with received data to be passed to any listene...
constexpr size_t remaining_packet_size(size_t size, size_t used)
The room left for a packet of size bytes after used bytes, or 0 if it is too small.
ESPHOME_LOG_TAG(TAG, "packet_interface.session")
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t reverse_poly, bool refin, bool refout)
Calculate a CRC-16 checksum of data with size len.
uint32_t random_uint32()
Return a random 32-bit unsigned integer.