[modbus_controller] Writer entities as their own hub device; heap-free, byte-accurate write path (#18082)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Bonne Eggleston
2026-08-26 18:24:09 -05:00
committed by GitHub
co-authored by J. Nick Koston
parent 9508165797
commit 5df1c7f1d3
19 changed files with 598 additions and 235 deletions
@@ -10,6 +10,73 @@ static const char *const TAG = "modbus_controller";
void ModbusController::setup() { this->create_polling_commands_(); }
void WriterDevice::warn_write_buffer_deprecated(const LogString *platform, uint16_t address) {
if (this->write_buffer_deprecated_warned_)
return;
this->write_buffer_deprecated_warned_ = true;
ESP_LOGW(TAG,
"Modbus %s (address 0x%X): filling the write_lambda buffer parameter is deprecated; call a write helper / "
"queue_pdu() on the entity (item) instead. The buffer parameter is removed in 2027.3.0",
LOG_STR_ARG(platform), address);
}
bool WriterDevice::send_raw_frame_deprecated(std::span<const uint8_t> frame) {
if (frame.empty())
return false;
this->dispatched_ = true;
return this->parent_->queue_pdu(frame[0], frame.subspan(1), this);
}
void WriterDevice::set_controller(ModbusController *controller) {
this->controller_ = controller;
this->set_parent(controller->hub());
this->set_address(controller->device_address());
}
void WriterDevice::notify_online_(std::span<const uint8_t> request_pdu) {
if (this->controller_ != nullptr)
this->controller_->set_online(true, fc_of(request_pdu), addr_of(request_pdu));
}
void WriterDevice::on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) {
this->notify_online_(request_pdu);
this->dispatch_response_(request_pdu, response_pdu, std::nullopt);
}
void WriterDevice::on_error(std::span<const uint8_t> request_pdu, modbus::ExceptionCode exception_code) {
ESP_LOGW(TAG, "Modbus error function code: 0x%X register 0x%X exception: %d", fc_of(request_pdu),
addr_of(request_pdu), static_cast<uint8_t>(exception_code));
this->notify_online_(request_pdu); // an exception is still a legitimate reply -> device is online
this->dispatch_response_(request_pdu, {}, exception_code);
}
// Fired once per wire transmission (including hub re-queues from a retry), so the on_command_sent trigger
// reflects when the frame actually went out, not when it was queued.
void WriterDevice::on_sent(std::span<const uint8_t> request_pdu) {
if (this->controller_ != nullptr)
this->controller_->command_sent(fc_of(request_pdu), addr_of(request_pdu));
}
void WriterDevice::on_not_sent(std::span<const uint8_t> request_pdu) {
// Only the offline teardown reaches this (a supersede retires silently), so the frame is genuinely
// lost; a dropped write was already published optimistically, so surface it.
if (modbus::helpers::is_function_code_write(fc_of(request_pdu))) {
ESP_LOGW(TAG, "Write not sent: function 0x%X register 0x%X", fc_of(request_pdu), addr_of(request_pdu));
} else {
ESP_LOGD(TAG, "Request not sent: function 0x%X register 0x%X", fc_of(request_pdu), addr_of(request_pdu));
}
}
bool WriterDevice::on_no_response(std::span<const uint8_t> request_pdu) {
if (this->controller_ == nullptr)
return false;
this->controller_->increment_non_response_count();
if (this->controller_->can_send())
return true; // the hub re-queues the frame it is holding; on_sent fires again on the retry
this->controller_->set_online(false, fc_of(request_pdu), addr_of(request_pdu));
return false;
}
ModbusCommandItem::ModbusCommandItem(ModbusController &controller, modbus::ModbusClientHub *parent, uint8_t address,
RegisterRange &&range)
: modbus::ModbusClientDevice(parent, address),