ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
modbus_output.cpp
Go to the documentation of this file.
1#include "modbus_output.h"
3#include "esphome/core/log.h"
4
5#include <array>
6
8
9static const char *const TAG = "modbus_controller.output";
10
11// Maximum bytes to log in verbose hex output
12static constexpr size_t MODBUS_OUTPUT_MAX_LOG_BYTES = 64;
13
18 this->clear_dispatched_();
19 // A new write supersedes this entity's own not-yet-sent writes: drop them (and detach any in-flight one)
20 // so a rapidly-changing value writes the latest, not every intermediate.
23 auto original_value = value;
24 if (this->write_transform_func_.has_value()) {
25 // The lambda may drive the write itself via item->write_*(), override the value (return a value), or
26 // (deprecated) fill `data` with the register words to write.
27 auto val = (*this->write_transform_func_)(this, value, data);
28 if (this->dispatched()) {
29 return;
30 }
31 if (!data.empty()) {
32 // Deprecated buffer path (frozen): the lambda supplied the register words for the shared write below.
33 this->warn_write_buffer_deprecated_(LOG_STR("float output"), this->start_address);
34 } else if (!val.has_value()) {
35 ESP_LOGV(TAG, "Communication handled by lambda - exiting control");
36 return;
37 } else {
38 ESP_LOGV(TAG, "Value overwritten by lambda");
39 value = val.value();
40 }
41 } else {
42 value = this->multiply_by_ * value;
43 }
44
45 if (data.empty()) {
47 }
48
49 ESP_LOGD(TAG, "Updating register: start address=0x%X register count=%u new value=%.02f (val=%.02f)",
50 this->start_address, this->entity_count(), value, original_value);
51
52 // float_to_payload() appends nothing for RAW, so an empty payload must be caught before data[0].
53 if (data.empty()) {
54 ESP_LOGW(TAG, "No payload was created for updating output");
55 return;
56 }
57
58 // The value type sets the write width, so a wider payload means the config and the lambda disagree.
59 if (data.size() > this->entity_count()) {
60 ESP_LOGE(TAG, "Payload has %zu registers but the value type only spans %u; dropping write", data.size(),
61 this->entity_count());
62 return;
63 }
64
65 bool queued;
66 if (this->entity_count() == 1 && !this->use_write_multiple_) {
67 queued = this->write_single_register(this->write_address(), data[0]);
68 } else {
69 queued = this->write_multiple_registers(this->write_address(), data);
70 }
71 if (!queued) {
72 ESP_LOGW(TAG, "Modbus output write (address 0x%X) was refused by the hub", this->write_address());
73 }
74}
75
77 ESP_LOGCONFIG(TAG, "Modbus Float Output:");
78 LOG_FLOAT_OUTPUT(this);
79 ESP_LOGCONFIG(TAG,
80 " Device start address: 0x%X\n"
81 " Register count: %d\n"
82 " Value type: %d",
83 this->start_address, this->entity_count(), static_cast<int>(this->sensor_value_type));
84}
85
86// ModbusBinaryOutput
88 this->clear_dispatched_();
89 // A new write supersedes this entity's own not-yet-sent writes: drop them (and detach any in-flight one)
90 // so a rapidly-changing value writes the latest, not every intermediate.
93
94 if (this->write_transform_func_.has_value()) {
95 // The lambda may drive the write itself via item->write_*/queue_pdu(), override the value (return a value),
96 // or (deprecated) fill `data` with a custom PDU.
97 auto val = (*this->write_transform_func_)(this, state, data);
98 if (this->dispatched()) {
99 return;
100 }
101 if (!data.empty()) {
102 this->warn_write_buffer_deprecated_(LOG_STR("binary output"), this->start_address);
103#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
104 char hex_buf[format_hex_pretty_size(MODBUS_OUTPUT_MAX_LOG_BYTES)];
105#endif
106 ESP_LOGV(TAG, "Modbus binary output write raw: %s",
107 format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
108 // The lambda filled a legacy raw frame (device address + function code + data).
109 if (!this->send_raw_frame_deprecated_(data)) {
110 ESP_LOGW(TAG, "Modbus output write (address 0x%X) was refused by the hub", this->write_address());
111 }
112 return;
113 }
114 if (!val.has_value()) {
115 ESP_LOGV(TAG, "Communication handled by lambda - exiting control");
116 return;
117 }
118 ESP_LOGV(TAG, "Value overwritten by lambda");
119 state = val.value();
120 }
121 ESP_LOGV(TAG, "Write new state: value is %s, type is %d address = %X, offset = %x", ONOFF(state),
122 (int) this->register_type, this->start_address, this->offset);
123 // offset for coil and discrete inputs is the coil/register number not bytes
124 bool queued;
125 if (this->use_write_multiple_) {
126 std::array<bool, 1> states{state};
127 queued = this->write_multiple_coils(this->write_address(), states);
128 } else {
129 queued = this->write_single_coil(this->write_address(), state);
130 }
131 if (!queued) {
132 ESP_LOGW(TAG, "Modbus output write (address 0x%X) was refused by the hub", this->write_address());
133 }
134}
135
137 ESP_LOGCONFIG(TAG, "Modbus Binary Output:");
138 LOG_BINARY_OUTPUT(this);
139 ESP_LOGCONFIG(TAG,
140 " Device start address: 0x%X\n"
141 " Register count: %d\n"
142 " Value type: %d",
143 this->start_address, this->entity_count(), static_cast<int>(this->sensor_value_type));
144}
145
146} // namespace esphome::modbus_controller
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:227
bool empty() const
Definition helpers.h:294
optional< write_transform_func_t > write_transform_func_
void write_state(float value) override
Write a value to the device.
optional< write_transform_func_t > write_transform_func_
virtual uint16_t entity_count() const
Entities this item spans: one bit for bit-addressed types, ceil(bytes / 2) registers for RAW with a r...
uint8_t offset
Position of this sensor's data within its range's response - a byte offset for registers,...
uint16_t write_address() const
Address a write entity (switch/number/select) targets, derived from its resolved position within the ...
void warn_write_buffer_deprecated_(const LogString *platform, uint16_t address)
bool dispatched() const
Whether the lambda called a request helper since the last clear_dispatched_().
bool write_single_coil(uint16_t address, bool value)
bool write_single_register(uint16_t address, uint16_t value)
bool send_raw_frame_deprecated_(std::span< const uint8_t > frame)
bool write_multiple_coils(uint16_t address, std::span< const bool > values)
bool write_multiple_registers(uint16_t address, std::span< const uint16_t > values)
bool state
Definition fan.h:2
mopeka_std_values val[3]
void float_to_payload(Container &data, float value, SensorValueType value_type)
Append a float converted to register words to any push_back container (heap-free with StaticVector).
const std::vector< uint8_t > & data
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:425
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1438