[modbus] Rename send_pdu() to queue_pdu() (#18196)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Bonne Eggleston
2026-08-09 17:00:44 -05:00
committed by GitHub
co-authored by Claude J. Nick Koston
parent e9f428983e
commit ab12e5490f
8 changed files with 260 additions and 195 deletions
+7 -7
View File
@@ -883,8 +883,8 @@ void ModbusClientHub::sweep_() {
}
// Raw send for client: pushes to tx queue. Everything except the CRC must be contained in payload.
bool ModbusClientHub::send_pdu(uint8_t address, std::span<const uint8_t> pdu, ModbusClientDevice *device,
CommandOptions options) {
bool ModbusClientHub::queue_pdu(uint8_t address, std::span<const uint8_t> pdu, ModbusClientDevice *device,
CommandOptions options) {
// Requests refused here never enter the machine and get no callback - the false return is it.
if (pdu.empty()) {
ESP_LOGW(TAG, "Empty PDU refused for address %" PRIu8, address);
@@ -995,7 +995,7 @@ void ModbusClientHub::send_raw(const std::vector<uint8_t> &payload, ModbusClient
ESP_LOGW(TAG, "send_raw() payload too short to contain a PDU, refused");
return;
}
this->send_pdu(payload[0], std::span<const uint8_t>(payload).subspan(1), device);
this->queue_pdu(payload[0], std::span<const uint8_t>(payload).subspan(1), device);
}
// Send raw command for server replies immediately. Except CRC everything must be contained in payload
@@ -1077,7 +1077,7 @@ void ModbusClientDevice::dispatch_response_(std::span<const uint8_t> request_pdu
// - On failure (status engaged) the response is empty by design (see on_error()), so only the request
// is validated.
bool custom = !helpers::is_client_pdu_standard(request_pdu.data(), request_pdu.size());
if (!custom && !status.has_value()) {
if (!custom && succeeded(status)) {
custom = !helpers::is_server_pdu_standard(response_pdu.data(), response_pdu.size());
if (!custom && helpers::is_function_code_read(static_cast<uint8_t>(function_code))) {
const bool bits =
@@ -1104,7 +1104,7 @@ void ModbusClientDevice::dispatch_response_(std::span<const uint8_t> request_pdu
// capacity of RegisterValues); a mismatch was diverted to on_custom_response(), never clamped. On
// failure the registers span is empty.
RegisterValues registers;
if (!status.has_value()) {
if (succeeded(status)) {
for (size_t i = 0; i != count_or_value; i++) {
registers.push_back(helpers::get_data<uint16_t>(response_pdu.data(), 2 + 2 * i));
}
@@ -1124,7 +1124,7 @@ void ModbusClientDevice::dispatch_response_(std::span<const uint8_t> request_pdu
// PackedBits::operator[] is unchecked, so size() must never promise bits with no bytes behind them.
std::span<const uint8_t> packed_bytes;
uint16_t count = 0;
if (!status.has_value()) {
if (succeeded(status)) {
packed_bytes = response_pdu.subspan(2);
count = count_or_value;
}
@@ -1141,7 +1141,7 @@ void ModbusClientDevice::dispatch_response_(std::span<const uint8_t> request_pdu
// copy. On an exception the response has no value and the request copy is the only one.
case FunctionCode::WRITE_SINGLE_REGISTER:
case FunctionCode::WRITE_SINGLE_COIL: {
const uint16_t value = (!status.has_value() && response_pdu.size() >= WRITE_SINGLE_PDU_SIZE)
const uint16_t value = (succeeded(status) && response_pdu.size() >= WRITE_SINGLE_PDU_SIZE)
? helpers::get_data<uint16_t>(response_pdu.data(), 3)
: count_or_value;
if (function_code == FunctionCode::WRITE_SINGLE_REGISTER) {
+63 -36
View File
@@ -262,19 +262,31 @@ class ModbusClientHub : public Modbus {
void set_turnaround_time(uint16_t time_in_ms) { this->turnaround_delay_ms_ = time_in_ms; }
bool tx_buffer_empty();
bool tx_blocked() override;
ESPDEPRECATED("Use send_pdu() with create_client_pdu() instead. Removed in 2026.10.0", "2026.4.0")
ESPDEPRECATED("Use queue_pdu() with create_client_pdu() instead. Removed in 2026.10.0", "2026.4.0")
void send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities,
uint8_t payload_len = 0, const uint8_t *payload = nullptr, ModbusClientDevice *device = nullptr) {
this->send_pdu(address,
helpers::create_client_pdu((FunctionCode) function_code, start_address, number_of_entities, payload,
payload_len),
device);
this->queue_pdu(address,
helpers::create_client_pdu((FunctionCode) function_code, start_address, number_of_entities, payload,
payload_len),
device);
};
// Queue a request; true once it is a live entry (resolving in one terminal), false if it never
// entered the machine (empty/oversize PDU, full queue, anonymous or over-cap duplicate) - no callback.
bool send_pdu(uint8_t address, std::span<const uint8_t> pdu, ModbusClientDevice *device = nullptr,
CommandOptions options = {});
ESPDEPRECATED("Use send_pdu(payload[0], <pdu bytes>, device) instead. Removed in 2027.2.0", "2026.8.0")
/// Queue a request. The name says queue, not send: the frame is appended to the transmit queue and
/// goes out later from loop(), so a true return means accepted into the machine (it will resolve in
/// exactly one terminal callback), NOT that anything reached the wire - that is on_sent(). False means
/// it never entered the machine at all (empty or oversize PDU, full queue, anonymous or over-cap
/// duplicate) and no callback of any kind will follow; the false return is the whole story.
bool queue_pdu(uint8_t address, std::span<const uint8_t> pdu, ModbusClientDevice *device = nullptr,
CommandOptions options = {});
// Remove before 2027.2.0. Deliberately the signature 2026.7.4 shipped - void, and no CommandOptions:
// the bool return and the options argument arrived after that release, so nothing external can be
// relying on them under this name. Callers who want the queued/refused answer move to queue_pdu().
ESPDEPRECATED("Use queue_pdu() instead - the call queues a request, it does not send one, and it "
"reports whether the request was accepted. Removed in 2027.2.0",
"2026.8.0")
void send_pdu(uint8_t address, std::span<const uint8_t> pdu, ModbusClientDevice *device = nullptr) {
this->queue_pdu(address, pdu, device);
}
ESPDEPRECATED("Use queue_pdu(payload[0], <pdu bytes>, device) instead. Removed in 2027.2.0", "2026.8.0")
void send_raw(const std::vector<uint8_t> &payload, ModbusClientDevice *device = nullptr);
// Clear an address's commands; each un-run request resolves via on_not_sent(), but a frame on the
// wire still runs to its usual terminal. clear_tx_queue_for_device() instead discards silently.
@@ -315,6 +327,12 @@ class ModbusClientHub : public Modbus {
// Transaction status: std::nullopt on success, otherwise a Modbus exception code
using ResponseStatus = std::optional<ExceptionCode>;
/// True when a transaction carried no exception. The optional holds the exception, so has_value() means
/// the request FAILED - the inverse of how "status" usually reads. Prefer this at the call site; the
/// bare !status.has_value() has already been mistaken for a failure check more than once. Where the code
/// is going to unwrap the exception anyway, status.has_value() followed by status.value() stays clearer.
inline bool succeeded(ResponseStatus status) { return !status.has_value(); }
// Register values exchanged with server handlers, in host byte order. Sized at the larger of the two protocol
// maxima (read = 125 / 0x7D, write = 123 / 0x7B); the per-direction count limit is enforced by the hub, not by
// the capacity of this type.
@@ -373,7 +391,7 @@ class ModbusServerHub : public Modbus {
/// Callback contract. Each accepted request ends in exactly ONE terminal: on_response() (data),
/// on_error() (exception), on_no_response() (timeout/interruption), or on_not_sent() (dropped by
/// clear_tx_queue_for_address before transmission). A request refused at send_pdu() (false return)
/// clear_tx_queue_for_address before transmission). A request refused at queue_pdu() (false return)
/// gets none. on_sent() is additional, once per transmission, never for an on_not_sent() request.
/// on_response()/on_error() fire at parse time and on_no_response() at the send-wait watchdog, all
/// from a quiescent hub; only on_not_sent() is delivered by the sweep. Sending or clearing from
@@ -383,7 +401,7 @@ class ModbusServerHub : public Modbus {
/// merges into it).
///
/// Invariants:
/// - Public entry points (send_pdu/clear_tx_queue_*) only append to the queue or mutate an existing
/// - Public entry points (queue_pdu/clear_tx_queue_*) only append to the queue or mutate an existing
/// entry through its callback-free transition methods.
/// - Public entry points can never trigger a callback synchronously.
/// - Callbacks are delivered only from within loop().
@@ -485,66 +503,75 @@ class ModbusClientDevice {
/// to handle custom traffic (which also silences the warning).
virtual void on_custom_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu,
ResponseStatus status);
ESPDEPRECATED("Use the typed read_*/write_* helpers or send_pdu() instead. Removed in 2027.2.0", "2026.8.0")
ESPDEPRECATED("Use the typed read_*/write_* helpers or queue_pdu() instead. Removed in 2027.2.0", "2026.8.0")
void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len = 0,
const uint8_t *payload = nullptr) {
this->parent_->send_pdu(
this->parent_->queue_pdu(
this->address_,
helpers::create_client_pdu((FunctionCode) function, start_address, number_of_entities, payload, payload_len),
this);
}
/// See ModbusClientHub::send_pdu(): true = accepted (a terminal callback will follow),
/// false = refused at the door (no callback).
bool send_pdu(std::span<const uint8_t> pdu, CommandOptions options = {}) {
return this->parent_->send_pdu(this->address_, pdu, this, options);
/// See ModbusClientHub::queue_pdu(): true = accepted into the queue and a terminal callback will
/// follow, false = refused at the door and nothing further happens. Neither means the frame is on
/// the wire; on_sent() reports that.
bool queue_pdu(std::span<const uint8_t> pdu, CommandOptions options = {}) {
return this->parent_->queue_pdu(this->address_, pdu, this, options);
}
ESPDEPRECATED("Use send_pdu() instead (the device address is prepended for you). Removed in 2027.2.0", "2026.8.0")
bool send_raw(const std::vector<uint8_t> &payload) {
// Remove before 2027.2.0. As on the hub, this is the signature 2026.7.4 shipped: void, no options.
ESPDEPRECATED("Use queue_pdu() instead - the call queues a request, it does not send one, and it "
"reports whether the request was accepted. Removed in 2027.2.0",
"2026.8.0")
void send_pdu(std::span<const uint8_t> pdu) { this->queue_pdu(pdu); }
ESPDEPRECATED("Use queue_pdu() instead (the device address is prepended for you). Removed in 2027.2.0", "2026.8.0")
void send_raw(const std::vector<uint8_t> &payload) {
if (payload.empty())
return false; // too short to contain a PDU; refused at the door like any invalid send
return this->parent_->send_pdu(payload[0], std::span<const uint8_t>(payload).subspan(1), this);
return; // too short to contain a PDU; refused at the door like any invalid send
this->parent_->queue_pdu(payload[0], std::span<const uint8_t>(payload).subspan(1), this);
}
// The typed request builders below all queue through queue_pdu(), so they share its contract: true
// means the request is queued and will resolve in exactly one terminal callback, false means it was
// refused outright with no callback. Neither says the frame has been transmitted - on_sent() does.
// Reads via the table-appropriate function code; an unreadable entity type maps to INVALID, which
// create_read_pdu() rejects into an empty PDU and send_pdu() refuses with a false return.
// create_read_pdu() rejects into an empty PDU and queue_pdu() refuses with a false return.
bool read_entities(EntityType entity_type, uint16_t start_address, uint16_t number_of_entities,
CommandOptions options = {}) {
return this->send_pdu(helpers::create_read_pdu(helpers::modbus_register_read_function(entity_type), start_address,
number_of_entities),
options);
return this->queue_pdu(helpers::create_read_pdu(helpers::modbus_register_read_function(entity_type), start_address,
number_of_entities),
options);
}
bool read_input_registers(uint16_t start_address, uint16_t number_of_registers, CommandOptions options = {}) {
return this->send_pdu(
return this->queue_pdu(
helpers::create_read_pdu(FunctionCode::READ_INPUT_REGISTERS, start_address, number_of_registers), options);
}
bool read_holding_registers(uint16_t start_address, uint16_t number_of_registers, CommandOptions options = {}) {
return this->send_pdu(
return this->queue_pdu(
helpers::create_read_pdu(FunctionCode::READ_HOLDING_REGISTERS, start_address, number_of_registers), options);
}
bool read_coils(uint16_t start_address, uint16_t number_of_coils, CommandOptions options = {}) {
return this->send_pdu(helpers::create_read_pdu(FunctionCode::READ_COILS, start_address, number_of_coils), options);
return this->queue_pdu(helpers::create_read_pdu(FunctionCode::READ_COILS, start_address, number_of_coils), options);
}
bool read_discrete_inputs(uint16_t start_address, uint16_t number_of_inputs, CommandOptions options = {}) {
return this->send_pdu(helpers::create_read_pdu(FunctionCode::READ_DISCRETE_INPUTS, start_address, number_of_inputs),
options);
return this->queue_pdu(
helpers::create_read_pdu(FunctionCode::READ_DISCRETE_INPUTS, start_address, number_of_inputs), options);
}
bool write_single_register(uint16_t start_address, uint16_t value) {
return this->send_pdu(helpers::create_write_single_register_pdu(start_address, value));
return this->queue_pdu(helpers::create_write_single_register_pdu(start_address, value));
}
bool write_single_coil(uint16_t address, bool value) {
return this->send_pdu(helpers::create_write_single_coil_pdu(address, value));
return this->queue_pdu(helpers::create_write_single_coil_pdu(address, value));
}
bool write_multiple_registers(uint16_t start_address, std::span<const uint16_t> values) {
return this->send_pdu(helpers::create_write_registers_pdu(start_address, values));
return this->queue_pdu(helpers::create_write_registers_pdu(start_address, values));
}
/// Note: std::vector<bool> cannot bind to std::span<const bool>; use a contiguous bool container or the packed
/// overload.
bool write_multiple_coils(uint16_t start_address, std::span<const bool> values) {
return this->send_pdu(helpers::create_write_coils_pdu(start_address, values));
return this->queue_pdu(helpers::create_write_coils_pdu(start_address, values));
}
/// Packed variant: a PackedBits view (the same layout on_read_coils() delivers), so
/// read-modify-write needs no unpack/repack.
bool write_multiple_coils(uint16_t start_address, PackedBits bits) {
return this->send_pdu(helpers::create_write_coils_pdu(start_address, bits));
return this->queue_pdu(helpers::create_write_coils_pdu(start_address, bits));
}
inline void clear_tx_queue_for_address() { this->parent_->clear_tx_queue_for_address(this->address_); }
inline void clear_tx_queue_for_device() { this->parent_->clear_tx_queue_for_device(this); }
@@ -33,7 +33,11 @@ template<typename... Ts> class ClientActionBase : public Action<Ts...>, public m
/// The frame was written to the wire: fires once per transmission, before any reply, and never for a
/// send that ended in on_not_sent. request_pdu is the PDU sent (function code + data).
void on_sent(std::span<const uint8_t> request_pdu) override { this->sent_trigger_.trigger(request_pdu); }
/// Never reached the wire (tx queue full, cleared, or a duplicate write dropped by the hub's dedup).
/// Never reached the wire, from either of two sources. The hub calls this for a request it accepted
/// and then dropped, which happens only when clear_tx_queue_for_address() retires it - a modbus
/// device going offline, say. Everything the hub refuses at the door instead returns false from
/// queue_pdu() with no callback at all, so send_or_resolve_() below turns those into this same
/// callback: a full queue, a duplicate write, or an empty PDU from a rejecting builder.
void on_not_sent(std::span<const uint8_t> request_pdu) override { this->not_sent_trigger_.trigger(request_pdu); }
/// A Modbus exception reply. Lives here beside its trigger so every action subclass gets the pairing:
/// register_client_action() wires on_error for all of them, so a derived class must not have to
@@ -64,7 +68,7 @@ template<typename... Ts> class ClientActionBase : public Action<Ts...>, public m
/// Takes a span, not a PduBuffer: the builders return right-sized buffers (a read PDU is 5 bytes), and
/// a PduBuffer parameter would widen each one to the 253-byte maximum just to cross the call.
void send_or_resolve_(std::span<const uint8_t> pdu) {
if (!this->send_pdu(pdu))
if (!this->queue_pdu(pdu))
this->on_not_sent(pdu);
}
@@ -107,7 +111,9 @@ template<typename... Ts> class ModbusClientSendAction : public ClientActionBase<
/// valid for the duration of the trigger. (For a typed-built request the gate can only divert on the
/// response, never with an exception status - real device exceptions arrive via on_error, which
/// ClientActionBase already routes straight to its trigger, so the typed callbacks below only ever see a
/// success status.)
/// success status.) Each typed callback still checks succeeded() before firing its trigger: that branch
/// is unreachable today, and is kept so a future change to that interception cannot silently deliver an
/// exception as a successful reply.
template<typename... Ts> class TypedClientActionBase : public ClientActionBase<Ts...> {
public:
Trigger<std::span<const uint8_t>, std::span<const uint8_t>> *get_custom_response_trigger() {
@@ -127,11 +133,6 @@ template<typename... Ts> class TypedClientActionBase : public ClientActionBase<T
}
protected:
/// Defensive assertion, not a live branch: ClientActionBase::on_error intercepts every exception reply
/// before the dispatch runs, so a typed callback below is only ever reached with a success status. Kept
/// so a future change to that interception cannot silently deliver an exception as a successful reply.
bool is_success_(modbus::ResponseStatus status) { return !status.has_value(); }
Trigger<std::span<const uint8_t>, std::span<const uint8_t>> custom_response_trigger_;
bool custom_response_handled_{false};
};
@@ -154,7 +155,7 @@ template<typename... Ts> class ReadRegistersAction : public TypedClientActionBas
}
void on_read_registers(modbus::EntityType entity_type, uint16_t start_address, std::span<const uint16_t> registers,
modbus::ResponseStatus status) override {
if (this->is_success_(status))
if (modbus::succeeded(status))
this->response_trigger_.trigger(registers);
}
@@ -181,7 +182,7 @@ template<typename... Ts> class ReadBitsAction : public TypedClientActionBase<Ts.
}
void on_read_bits(modbus::EntityType entity_type, uint16_t start_address, modbus::PackedBits bits,
modbus::ResponseStatus status) override {
if (this->is_success_(status))
if (modbus::succeeded(status))
this->response_trigger_.trigger(bits);
}
@@ -204,7 +205,7 @@ template<typename... Ts> class WriteSingleRegisterAction : public TypedClientAct
modbus::helpers::create_write_single_register_pdu(this->start_address_.value(x...), this->value_.value(x...)));
}
void on_write_single_register(uint16_t address, uint16_t value, modbus::ResponseStatus status) override {
if (this->is_success_(status))
if (modbus::succeeded(status))
this->response_trigger_.trigger();
}
@@ -226,7 +227,7 @@ template<typename... Ts> class WriteSingleCoilAction : public TypedClientActionB
modbus::helpers::create_write_single_coil_pdu(this->start_address_.value(x...), this->value_.value(x...)));
}
void on_write_single_coil(uint16_t address, bool value, modbus::ResponseStatus status) override {
if (this->is_success_(status))
if (modbus::succeeded(status))
this->response_trigger_.trigger();
}
@@ -270,7 +271,7 @@ template<typename... Ts> class WriteMultipleRegistersAction : public TypedClient
}
void on_write_multiple_registers(uint16_t start_address, std::span<const uint16_t> registers,
modbus::ResponseStatus status) override {
if (this->is_success_(status))
if (modbus::succeeded(status))
this->response_trigger_.trigger();
}
@@ -318,7 +319,7 @@ template<typename... Ts> class WriteMultipleCoilsAction : public TypedClientActi
}
void on_write_multiple_coils(uint16_t start_address, modbus::PackedBits bits,
modbus::ResponseStatus status) override {
if (this->is_success_(status))
if (modbus::succeeded(status))
this->response_trigger_.trigger();
}
@@ -160,10 +160,11 @@ void ModbusController::queue_command(ModbusCommandItem command) {
}
void ModbusController::unqueue_command(const ModbusCommandItem *command) {
// Called as the last action of the command's own callback, and from send() after send_pdu (which may
// synchronously call on_not_sent). Destroying `command` here would leave send() and the hub touching a
// freed object, so we only FLAG it; sweep_completed_one_shots_() erases it later at a safe point. No-op
// for polling commands (they persist and are not in the one-shot list).
// Called as the last action of the command's own callback (on_response/on_error/on_not_sent/
// on_no_response), which the hub runs from inside its sweep while this entry is still live.
// Destroying `command` here would leave the hub touching a freed object, so we only FLAG it;
// sweep_completed_one_shots_() erases it later at a safe point. No-op for polling commands
// (they persist and are not in the one-shot list).
for (auto &item : this->one_shot_command_items_) {
if (item.get() == command) {
item->pending_removal = true;
@@ -494,13 +495,13 @@ ModbusCommandItem ModbusCommandItem::create_custom_command(
bool ModbusCommandItem::send() {
bool accepted;
if (this->function_code_ != FunctionCode::CUSTOM) {
accepted = this->send_pdu(modbus::helpers::create_client_pdu(
accepted = this->queue_pdu(modbus::helpers::create_client_pdu(
this->function_code_, this->start_address_, this->register_count_,
this->payload.empty() ? nullptr : this->payload.data(), this->payload.size()));
} else {
// Custom command: the bytes are a complete raw frame (address + PDU). Send the PDU to the frame's own
// address (which may differ from this controller's); the hub appends the CRC and routes the response
// back to this item by pointer. (send_raw() is deprecated, so send_pdu() is called with the extracted
// back to this item by pointer. (send_raw() is deprecated, so queue_pdu() is called with the extracted
// address. Raw-frame semantics are kept here; the custom_pdu migration is a later step.)
std::span<const uint8_t> frame =
this->custom_data_ != nullptr ? std::span<const uint8_t>(*this->custom_data_) : this->payload;
@@ -508,7 +509,7 @@ bool ModbusCommandItem::send() {
ESP_LOGW(TAG, "Empty custom command frame, not sent");
accepted = false;
} else {
accepted = this->parent_->send_pdu(frame[0], frame.subspan(1), this);
accepted = this->parent_->queue_pdu(frame[0], frame.subspan(1), this);
}
}
// The on_command_sent trigger fires from on_sent() when the frame actually reaches the wire.
+1 -1
View File
@@ -77,7 +77,7 @@ void PZEMAC::dump_config() {
void PZEMAC::reset_energy_() {
const uint8_t pdu[] = {PZEM_CMD_RESET_ENERGY};
this->send_pdu(pdu);
this->queue_pdu(pdu);
}
} // namespace esphome::pzemac
+1 -1
View File
@@ -65,7 +65,7 @@ void PZEMDC::dump_config() {
void PZEMDC::reset_energy() {
const uint8_t pdu[] = {PZEM_CMD_RESET_ENERGY};
this->send_pdu(pdu);
this->queue_pdu(pdu);
}
} // namespace esphome::pzemdc
+4 -4
View File
@@ -134,7 +134,7 @@ TEST(HeapProbe, QueueingTypicalCommandsIsAllocationFree) {
size_t total = 0;
for (int i = 0; i != n; i++) {
req[2] = static_cast<uint8_t>(i); // distinct start addresses: identical frames would dedup, not enqueue
total += sample([&] { device.send_pdu(req); }).count;
total += sample([&] { device.queue_pdu(req); }).count;
}
printf("HEAPPROBE queue_%d_typical_commands total_allocs=%zu\n", n, total);
EXPECT_EQ(total, 0u);
@@ -151,11 +151,11 @@ TEST(HeapProbe, WriteBehindQueuedReadsAppendsAllocationFree) {
req.assign(read_pdu, read_pdu + sizeof(read_pdu));
for (int i = 0; i != 3; i++) {
req[2] = static_cast<uint8_t>(i); // distinct start addresses: identical frames would dedup, not enqueue
device.send_pdu(req);
device.queue_pdu(req);
}
const uint8_t write_pdu[] = {0x06, 0x00, 0x10, 0xBE, 0xEF};
Sample append = sample([&] { device.send_pdu(write_pdu); });
Sample append = sample([&] { device.queue_pdu(write_pdu); });
printf("HEAPPROBE write_append count=%zu bytes=%zu\n", append.count, append.bytes);
EXPECT_EQ(append.count, 0u);
}
@@ -180,7 +180,7 @@ TEST(HeapProbe, ResponseHandlingIsAllocationFreeAfterWarmup) {
const uint8_t small_resp[] = {0x03, 0x04, 0x00, 0x2A, 0x01, 0x00};
auto round_trip = [&](std::span<const uint8_t> response_pdu) {
device.send_pdu(req);
device.queue_pdu(req);
hub.loop(); // transmit; the tx queue is empty during the measured receive below
uart.inject_frame(0x02, response_pdu);
return sample([&] { hub.loop(); }); // receive + parse + match + dispatch
File diff suppressed because it is too large Load Diff