[tcp] Keep the receive buffer in static memory

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>
This commit is contained in:
clydebarrow
2026-10-20 09:35:39 +11:00
co-authored by Claude Opus 5.5
parent b8795fcd33
commit 8edaf1f53c
4 changed files with 17 additions and 10 deletions
@@ -8,6 +8,7 @@ from esphome.components.const import (
CONF_ROLE,
)
from esphome.components.packet_interface import (
CONF_MAX_PACKET_SIZE,
new_packet_interface,
packet_interface_schema,
)
@@ -81,7 +82,12 @@ CONFIG_SCHEMA = cv.typed_schema(
async def to_code(config: ConfigType) -> None:
var = await new_packet_interface(config)
# The receive buffer's size is known here, so it is a static array rather than allocated at run time
rx_buffer = f"{config[CONF_ID].id}__rx_buffer"
cg.add_global(
cg.RawStatement(f"static uint8_t {rx_buffer}[{config[CONF_MAX_PACKET_SIZE]}];")
)
var = await new_packet_interface(config, cg.RawExpression(rx_buffer))
if config[CONF_ROLE] == "server":
socket.require_tcp_listener()
cg.add(var.set_server(True))
@@ -15,13 +15,12 @@ static constexpr size_t LENGTH_SIZE = 2;
// The peer is gone when nothing has been received for this many keep-alive intervals
static constexpr uint32_t KEEP_ALIVE_MISSES = 3;
TcpPacketInterface::TcpPacketInterface() {
TcpPacketInterface::TcpPacketInterface(uint8_t *rx_buffer) : rx_buffer_(rx_buffer) {
this->capabilities_ = packet_interface::CAPABILITY_RELIABILITY | packet_interface::CAPABILITY_SESSION |
packet_interface::CAPABILITY_ADDRESSING;
}
void TcpPacketInterface::setup() {
this->rx_buffer_ = std::make_unique<uint8_t[]>(this->max_packet_size_);
this->link_.begin(TAG);
#ifdef USE_SOCKET_TCP_LISTENER
this->listener_.begin(TAG);
@@ -108,7 +107,7 @@ void TcpPacketInterface::read_() {
if (this->rx_have_ < LENGTH_SIZE) {
count = this->link_.read(this->rx_header_ + this->rx_have_, LENGTH_SIZE - this->rx_have_);
} else {
count = this->link_.read(this->rx_buffer_.get() + this->rx_have_ - LENGTH_SIZE,
count = this->link_.read(this->rx_buffer_ + this->rx_have_ - LENGTH_SIZE,
this->rx_length_ + LENGTH_SIZE - this->rx_have_);
}
// Nothing more for now; a dropped link is seen on the next loop
@@ -134,7 +133,7 @@ void TcpPacketInterface::read_() {
this->rx_have_ = 0;
// A zero length packet is a keep-alive
if (this->rx_length_ != 0)
this->on_receive_from_interface_(PacketBuffer(this->rx_buffer_.get(), this->rx_length_), {});
this->on_receive_from_interface_(PacketBuffer(this->rx_buffer_, this->rx_length_), {});
}
}
}
@@ -1,7 +1,6 @@
#pragma once
#include <cstdint>
#include <memory>
#include "esphome/components/packet_interface/packet_interface.h"
#include "esphome/components/socket/tcp_client_link.h"
@@ -31,7 +30,8 @@ namespace esphome::tcp {
*/
class TcpPacketInterface : public packet_interface::PacketInterface {
public:
TcpPacketInterface();
/// @param rx_buffer Holds max_packet_size bytes of the packet being received; must outlive the link
explicit TcpPacketInterface(uint8_t *rx_buffer);
void setup() override;
void loop() override;
@@ -69,8 +69,7 @@ class TcpPacketInterface : public packet_interface::PacketInterface {
#ifdef USE_BINARY_SENSOR
binary_sensor::BinarySensor *connected_sensor_{nullptr};
#endif
// The packet being received, allocated in setup()
std::unique_ptr<uint8_t[]> rx_buffer_;
uint8_t *const rx_buffer_;
uint32_t keep_alive_ms_{10000};
uint32_t last_rx_ms_{0};
uint32_t last_tx_ms_{0};
@@ -27,12 +27,15 @@ using Packet = std::vector<uint8_t>;
class TestTcp : public TcpPacketInterface {
public:
TestTcp() {
TestTcp() : TcpPacketInterface(this->rx_buffer_storage) {
this->set_max_packet_size(64);
this->set_host("peer");
this->set_port(1);
}
socket::TcpClientLink &link() { return this->link_; }
// The base class only keeps the pointer, so the storage can be a member set up after it
uint8_t rx_buffer_storage[64]{};
};
// A layer stacked on the link, to check that the session state reaches it