mirror of
https://github.com/esphome/esphome.git
synced 2026-08-31 01:56:01 +00:00
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: J. Nick Koston <nick@koston.org>
147 lines
5.6 KiB
C++
147 lines
5.6 KiB
C++
#include "modbus_output.h"
|
|
#include "esphome/core/helpers.h"
|
|
#include "esphome/core/log.h"
|
|
|
|
#include <array>
|
|
|
|
namespace esphome::modbus_controller {
|
|
|
|
static const char *const TAG = "modbus_controller.output";
|
|
|
|
// Maximum bytes to log in verbose hex output
|
|
static constexpr size_t MODBUS_OUTPUT_MAX_LOG_BYTES = 64;
|
|
|
|
/** Write a value to the device
|
|
*
|
|
*/
|
|
void ModbusFloatOutput::write_state(float value) {
|
|
this->clear_dispatched_();
|
|
// A new write supersedes this entity's own not-yet-sent writes: drop them (and detach any in-flight one)
|
|
// so a rapidly-changing value writes the latest, not every intermediate.
|
|
this->clear_tx_queue_for_device();
|
|
modbus::RegisterValues data;
|
|
auto original_value = value;
|
|
if (this->write_transform_func_.has_value()) {
|
|
// The lambda may drive the write itself via item->write_*(), override the value (return a value), or
|
|
// (deprecated) fill `data` with the register words to write.
|
|
auto val = (*this->write_transform_func_)(this, value, data);
|
|
if (this->dispatched()) {
|
|
return;
|
|
}
|
|
if (!data.empty()) {
|
|
// Deprecated buffer path (frozen): the lambda supplied the register words for the shared write below.
|
|
this->warn_write_buffer_deprecated_(LOG_STR("float output"), this->start_address);
|
|
} else if (!val.has_value()) {
|
|
ESP_LOGV(TAG, "Communication handled by lambda - exiting control");
|
|
return;
|
|
} else {
|
|
ESP_LOGV(TAG, "Value overwritten by lambda");
|
|
value = val.value();
|
|
}
|
|
} else {
|
|
value = this->multiply_by_ * value;
|
|
}
|
|
|
|
if (data.empty()) {
|
|
modbus::helpers::float_to_payload(data, value, this->sensor_value_type);
|
|
}
|
|
|
|
ESP_LOGD(TAG, "Updating register: start address=0x%X register count=%u new value=%.02f (val=%.02f)",
|
|
this->start_address, this->entity_count(), value, original_value);
|
|
|
|
// float_to_payload() appends nothing for RAW, so an empty payload must be caught before data[0].
|
|
if (data.empty()) {
|
|
ESP_LOGW(TAG, "No payload was created for updating output");
|
|
return;
|
|
}
|
|
|
|
// The value type sets the write width, so a wider payload means the config and the lambda disagree.
|
|
if (data.size() > this->entity_count()) {
|
|
ESP_LOGE(TAG, "Payload has %zu registers but the value type only spans %u; dropping write", data.size(),
|
|
this->entity_count());
|
|
return;
|
|
}
|
|
|
|
bool queued;
|
|
if (this->entity_count() == 1 && !this->use_write_multiple_) {
|
|
queued = this->write_single_register(this->write_address(), data[0]);
|
|
} else {
|
|
queued = this->write_multiple_registers(this->write_address(), data);
|
|
}
|
|
if (!queued) {
|
|
ESP_LOGW(TAG, "Modbus output write (address 0x%X) was refused by the hub", this->write_address());
|
|
}
|
|
}
|
|
|
|
void ModbusFloatOutput::dump_config() {
|
|
ESP_LOGCONFIG(TAG, "Modbus Float Output:");
|
|
LOG_FLOAT_OUTPUT(this);
|
|
ESP_LOGCONFIG(TAG,
|
|
" Device start address: 0x%X\n"
|
|
" Register count: %d\n"
|
|
" Value type: %d",
|
|
this->start_address, this->entity_count(), static_cast<int>(this->sensor_value_type));
|
|
}
|
|
|
|
// ModbusBinaryOutput
|
|
void ModbusBinaryOutput::write_state(bool state) {
|
|
this->clear_dispatched_();
|
|
// A new write supersedes this entity's own not-yet-sent writes: drop them (and detach any in-flight one)
|
|
// so a rapidly-changing value writes the latest, not every intermediate.
|
|
this->clear_tx_queue_for_device();
|
|
modbus::helpers::PduBuffer data;
|
|
|
|
if (this->write_transform_func_.has_value()) {
|
|
// The lambda may drive the write itself via item->write_*/queue_pdu(), override the value (return a value),
|
|
// or (deprecated) fill `data` with a custom PDU.
|
|
auto val = (*this->write_transform_func_)(this, state, data);
|
|
if (this->dispatched()) {
|
|
return;
|
|
}
|
|
if (!data.empty()) {
|
|
this->warn_write_buffer_deprecated_(LOG_STR("binary output"), this->start_address);
|
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
|
char hex_buf[format_hex_pretty_size(MODBUS_OUTPUT_MAX_LOG_BYTES)];
|
|
#endif
|
|
ESP_LOGV(TAG, "Modbus binary output write raw: %s",
|
|
format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
|
|
// The lambda filled a legacy raw frame (device address + function code + data).
|
|
if (!this->send_raw_frame_deprecated_(data)) {
|
|
ESP_LOGW(TAG, "Modbus output write (address 0x%X) was refused by the hub", this->write_address());
|
|
}
|
|
return;
|
|
}
|
|
if (!val.has_value()) {
|
|
ESP_LOGV(TAG, "Communication handled by lambda - exiting control");
|
|
return;
|
|
}
|
|
ESP_LOGV(TAG, "Value overwritten by lambda");
|
|
state = val.value();
|
|
}
|
|
ESP_LOGV(TAG, "Write new state: value is %s, type is %d address = %X, offset = %x", ONOFF(state),
|
|
(int) this->register_type, this->start_address, this->offset);
|
|
// offset for coil and discrete inputs is the coil/register number not bytes
|
|
bool queued;
|
|
if (this->use_write_multiple_) {
|
|
std::array<bool, 1> states{state};
|
|
queued = this->write_multiple_coils(this->write_address(), states);
|
|
} else {
|
|
queued = this->write_single_coil(this->write_address(), state);
|
|
}
|
|
if (!queued) {
|
|
ESP_LOGW(TAG, "Modbus output write (address 0x%X) was refused by the hub", this->write_address());
|
|
}
|
|
}
|
|
|
|
void ModbusBinaryOutput::dump_config() {
|
|
ESP_LOGCONFIG(TAG, "Modbus Binary Output:");
|
|
LOG_BINARY_OUTPUT(this);
|
|
ESP_LOGCONFIG(TAG,
|
|
" Device start address: 0x%X\n"
|
|
" Register count: %d\n"
|
|
" Value type: %d",
|
|
this->start_address, this->entity_count(), static_cast<int>(this->sensor_value_type));
|
|
}
|
|
|
|
} // namespace esphome::modbus_controller
|