mirror of
https://github.com/esphome/esphome.git
synced 2026-09-25 14:00:25 +00:00
[modbus_controller] Poll through PollingDevice; deprecate ModbusCommandItem (#18071)
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
co-authored by
Claude
J. Nick Koston
parent
9598449b6b
commit
25d5cd3e14
@@ -23,60 +23,106 @@ void WriterDevice::warn_write_buffer_deprecated(const LogString *platform, uint1
|
||||
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) {
|
||||
void ControllerDevice::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));
|
||||
// A request whose layout carries no start address (a custom PDU) reports -1; 0 stays a real address.
|
||||
static int trigger_address(std::span<const uint8_t> request_pdu) {
|
||||
const auto addr = modbus::helpers::client_pdu_start_address(request_pdu);
|
||||
return addr.has_value() ? *addr : -1;
|
||||
}
|
||||
|
||||
void ControllerDevice::notify_online_(std::span<const uint8_t> request_pdu) {
|
||||
if (this->controller_ != nullptr) {
|
||||
this->controller_->set_online(true, modbus::helpers::pdu_function_code(request_pdu), trigger_address(request_pdu));
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerDevice::on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) {
|
||||
this->notify_online_(request_pdu);
|
||||
}
|
||||
|
||||
void ControllerDevice::on_error(std::span<const uint8_t> request_pdu, modbus::ExceptionCode exception_code) {
|
||||
ESP_LOGW(TAG, "Modbus error function code: 0x%X register %d exception: %d",
|
||||
modbus::helpers::pdu_function_code(request_pdu), trigger_address(request_pdu),
|
||||
static_cast<uint8_t>(exception_code));
|
||||
this->notify_online_(request_pdu); // an exception is still a legitimate reply -> device is online
|
||||
}
|
||||
|
||||
void WriterDevice::on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) {
|
||||
this->notify_online_(request_pdu);
|
||||
ControllerDevice::on_response(request_pdu, response_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
|
||||
ControllerDevice::on_error(request_pdu, exception_code);
|
||||
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));
|
||||
void ControllerDevice::on_sent(std::span<const uint8_t> request_pdu) {
|
||||
if (this->controller_ != nullptr) {
|
||||
this->controller_->command_sent(modbus::helpers::pdu_function_code(request_pdu), trigger_address(request_pdu));
|
||||
}
|
||||
}
|
||||
|
||||
bool WriterDevice::on_no_response(std::span<const uint8_t> request_pdu) {
|
||||
void ControllerDevice::on_not_sent(std::span<const uint8_t> request_pdu) {
|
||||
const uint8_t fc = modbus::helpers::pdu_function_code(request_pdu);
|
||||
const int addr = trigger_address(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)) {
|
||||
ESP_LOGW(TAG, "Write not sent: function 0x%X register %d", fc, addr);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Request not sent: function 0x%X register %d", fc, addr);
|
||||
}
|
||||
}
|
||||
|
||||
bool ControllerDevice::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));
|
||||
this->controller_->set_online(false, modbus::helpers::pdu_function_code(request_pdu), trigger_address(request_pdu));
|
||||
return false;
|
||||
}
|
||||
|
||||
PollingDevice::PollingDevice(ModbusController &controller, RegisterRange &&range)
|
||||
: ControllerDevice(&controller), range_(std::move(range)) {}
|
||||
|
||||
bool PollingDevice::queue(modbus::CommandOptions options) {
|
||||
bool accepted;
|
||||
if (this->range_.custom_pdu != nullptr) {
|
||||
accepted = this->queue_pdu(std::span<const uint8_t>(*this->range_.custom_pdu), options);
|
||||
} else {
|
||||
accepted = this->read_entities(this->range_.register_type, this->range_.start_address, this->range_.register_count,
|
||||
options);
|
||||
}
|
||||
if (accepted) {
|
||||
ESP_LOGV(TAG, "Poll queued type=%u 0x%X %d", static_cast<uint8_t>(this->range_.register_type),
|
||||
this->range_.start_address, this->range_.register_count);
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
void PollingDevice::on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) {
|
||||
this->notify_online_(request_pdu);
|
||||
auto data = modbus::helpers::server_pdu_payload(response_pdu);
|
||||
for (auto *sensor : this->range_.sensors)
|
||||
sensor->parse_and_publish(data);
|
||||
}
|
||||
|
||||
// ModbusCommandItem's machinery stays as-is until its removal in 2027.3.0; silence its self-references.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
ModbusCommandItem::ModbusCommandItem(ModbusController &controller, modbus::ModbusClientHub *parent, uint8_t address,
|
||||
RegisterRange &&range)
|
||||
: modbus::ModbusClientDevice(parent, address),
|
||||
@@ -207,6 +253,8 @@ bool ModbusCommandItem::on_no_response(std::span<const uint8_t> request_pdu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
void ModbusController::set_online(bool online, int function_code, int register_address) {
|
||||
if (online) {
|
||||
this->cmd_non_responses_ = 0;
|
||||
@@ -228,6 +276,8 @@ void ModbusController::set_online(bool online, int function_code, int register_a
|
||||
}
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
void ModbusController::queue_command(ModbusCommandItem command) {
|
||||
this->sweep_completed_one_shots_(); // reclaim finished one-shots before adding a new one
|
||||
// Duplicates are the caller's to manage; the controller only holds the item until its terminal callback.
|
||||
@@ -262,6 +312,8 @@ void ModbusController::sweep_completed_one_shots_() {
|
||||
[](const std::unique_ptr<ModbusCommandItem> &item) { return item->pending_removal; });
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
void ModbusController::update() {
|
||||
this->sweep_completed_one_shots_(); // reclaim one-shots deferred out of their own callbacks
|
||||
if (this->module_offline_) {
|
||||
@@ -270,11 +322,11 @@ void ModbusController::update() {
|
||||
if (offline_retry_due(this->update_counter_, this->module_offline_at_, this->offline_skip_updates_)) {
|
||||
ESP_LOGV(TAG, "Module offline - retrying");
|
||||
this->cmd_non_responses_ = 0; // allow the probe through can_send()
|
||||
for (auto &cmd : this->polling_command_items_) {
|
||||
for (auto &poll : this->polling_devices_) {
|
||||
// Probes carry the read-side options too, so a recovering device resumes streaming on the
|
||||
// probe itself rather than waiting for the next update_interval.
|
||||
if (!cmd.send(this->read_options_)) {
|
||||
ESP_LOGD(TAG, "Probe refused by hub for range 0x%X", cmd.register_address());
|
||||
if (!poll.queue(this->read_options_)) {
|
||||
ESP_LOGD(TAG, "Probe refused by hub for range 0x%X", poll.register_address());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -285,12 +337,12 @@ void ModbusController::update() {
|
||||
}
|
||||
|
||||
if (this->can_send()) {
|
||||
for (auto &cmd : this->polling_command_items_) {
|
||||
ESP_LOGVV(TAG, "Updating range 0x%X", cmd.register_address());
|
||||
for (auto &poll : this->polling_devices_) {
|
||||
ESP_LOGVV(TAG, "Updating range 0x%X", poll.register_address());
|
||||
// read_options_ carries the controller's continuous flag (the offline probe above sends it too).
|
||||
// A refusal is already logged by the hub; note the affected range for controller-level diagnostics.
|
||||
if (!cmd.send(this->read_options_)) {
|
||||
ESP_LOGD(TAG, "Poll refused by hub for range 0x%X", cmd.register_address());
|
||||
if (!poll.queue(this->read_options_)) {
|
||||
ESP_LOGD(TAG, "Poll refused by hub for range 0x%X", poll.register_address());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +360,10 @@ void ModbusController::create_polling_commands_() {
|
||||
// force_new_range ahead of the rest, then address - so the walk is not purely address-ordered.
|
||||
// Each keeps the address it was configured with; what is resolved here is its `offset`, the position
|
||||
// of its data within the response of whichever range it ends up in.
|
||||
// One range per sensor is a strict upper bound: each walk step closes at most one range, plus one
|
||||
// closed after the walk. Sized to that bound so no push is ever silently dropped, then handed on by move.
|
||||
FixedVector<RegisterRange> ranges;
|
||||
ranges.init(this->sensorset_.size());
|
||||
RegisterRange r = {};
|
||||
bool have_range = false;
|
||||
// Set while the open range belongs to a force_new_range sensor: a range the user asked to keep
|
||||
@@ -390,7 +446,7 @@ void ModbusController::create_polling_commands_() {
|
||||
if (!join) {
|
||||
if (have_range) {
|
||||
ESP_LOGV(TAG, "Add range 0x%X %d", r.start_address, r.register_count);
|
||||
this->create_polling_command_(std::move(r));
|
||||
ranges.push_back(std::move(r));
|
||||
}
|
||||
r = {};
|
||||
range_bytes = curr->get_register_size();
|
||||
@@ -401,6 +457,8 @@ void ModbusController::create_polling_commands_() {
|
||||
r.start_address = curr->start_address;
|
||||
r.register_count = curr->register_count;
|
||||
r.register_type = curr->register_type;
|
||||
if (curr->register_type == modbus::EntityType::CUSTOM)
|
||||
r.custom_pdu = &curr->custom_pdu;
|
||||
have_range = true;
|
||||
}
|
||||
|
||||
@@ -412,11 +470,13 @@ void ModbusController::create_polling_commands_() {
|
||||
}
|
||||
if (have_range) {
|
||||
ESP_LOGV(TAG, "Add last range 0x%X %d", r.start_address, r.register_count);
|
||||
this->create_polling_command_(std::move(r));
|
||||
ranges.push_back(std::move(r));
|
||||
}
|
||||
// Staged in a setup-time vector so the device storage can be sized exactly (see polling_devices_).
|
||||
this->polling_devices_.init(ranges.size());
|
||||
for (auto &range : ranges) {
|
||||
this->polling_devices_.emplace_back(*this, std::move(range));
|
||||
}
|
||||
// Reclaim growth slack; safe here because nothing has registered with the hub yet (see the
|
||||
// lifetime note on polling_command_items_).
|
||||
this->polling_command_items_.shrink_to_fit();
|
||||
}
|
||||
|
||||
void ModbusController::dump_config() {
|
||||
@@ -435,13 +495,15 @@ void ModbusController::dump_config() {
|
||||
it->get_register_size());
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "ranges");
|
||||
for (auto &it : this->polling_command_items_) {
|
||||
for (auto &it : this->polling_devices_) {
|
||||
ESP_LOGCONFIG(TAG, " Range type=%u start=0x%X count=%d", static_cast<uint8_t>(it.register_type()),
|
||||
it.register_address(), it.register_count());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
void ModbusController::on_write_register_response(EntityType register_type, uint16_t start_address,
|
||||
std::span<const uint8_t> data) {
|
||||
// A well-formed write ACK echoes address and value, but a truncated PDU yields a short/empty span.
|
||||
@@ -598,5 +660,6 @@ bool ModbusCommandItem::send(modbus::CommandOptions options) {
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
} // namespace esphome::modbus_controller
|
||||
|
||||
Reference in New Issue
Block a user