ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
modbus_number.cpp
Go to the documentation of this file.
1#include "modbus_number.h"
3#include "esphome/core/log.h"
4
6
7static const char *const TAG = "modbus.number";
8
9// Maximum uint16_t registers to log in verbose hex output
10static constexpr size_t MODBUS_NUMBER_MAX_LOG_REGISTERS = 32;
11
12void ModbusNumber::parse_and_publish(std::span<const uint8_t> data) {
13 float result = payload_to_float(data, *this, this->offset) / this->multiply_by_;
14
15 // Is there a lambda registered
16 // call it with the pre converted value and the raw data array
17 if (this->transform_func_.has_value()) {
18 // the lambda can parse the response itself
19 auto val = (*this->transform_func_)(this, result, data);
20 if (val.has_value()) {
21 ESP_LOGV(TAG, "Value overwritten by lambda");
22 result = val.value();
23 }
24 }
25 ESP_LOGD(TAG, "Number new state : %.02f", result);
26 // this->sensor_->raw_state = result;
27 this->publish_state(result);
28}
29
30void ModbusNumber::control(float value) {
31 this->clear_dispatched_();
32 // A new write supersedes this entity's own not-yet-sent writes: drop them (and detach any in-flight one)
33 // so a rapidly-changing value writes the latest, not every intermediate.
36 float write_value = value;
37 if (this->write_transform_func_.has_value()) {
38 // The lambda may drive the write itself via item->write_*(), override the value (return a value), or
39 // (deprecated) fill `data` with the register words to write.
40 auto val = (*this->write_transform_func_)(this, value, data);
41 if (this->dispatched()) {
42 this->publish_state(value);
43 return;
44 }
45 if (!data.empty()) {
46 // Deprecated buffer path (frozen): the lambda filled a legacy raw frame as words; pack it big-endian.
47 this->warn_write_buffer_deprecated_(LOG_STR("number"), this->start_address);
48#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
49 char hex_buf[format_hex_pretty_uint16_size(MODBUS_NUMBER_MAX_LOG_REGISTERS)];
50#endif
51 ESP_LOGV(TAG, "Modbus Number write raw: %s",
52 format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
53 // Sized to hold RegisterValues at capacity, so a full buffer can never truncate into a valid frame.
55 for (uint16_t word : data) {
56 const auto word_bytes = decode_value(word);
57 bytes.push_back(word_bytes[0]);
58 bytes.push_back(word_bytes[1]);
59 }
60 if (!this->send_raw_frame_deprecated_(std::span<const uint8_t>(bytes.data(), bytes.size()))) {
61 ESP_LOGW(TAG, "Modbus write for '%s' was refused by the hub; state not published", this->get_name().c_str());
62 return;
63 }
64 this->publish_state(value);
65 return;
66 }
67 if (!val.has_value()) {
68 ESP_LOGV(TAG, "Communication handled by lambda - exiting control");
69 return;
70 }
71 ESP_LOGV(TAG, "Value overwritten by lambda");
72 write_value = val.value();
73 } else {
74 write_value = this->multiply_by_ * write_value;
75 }
76
78 // float_to_payload() appends nothing for RAW, so an empty payload must be caught before data[0] below.
79 if (data.empty()) {
80 ESP_LOGW(TAG, "No payload was created for updating number");
81 return;
82 }
83
84 ESP_LOGD(TAG,
85 "Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)",
86 this->get_name().c_str(), this->start_address, this->entity_count(), value, write_value);
87
88 bool queued;
89 if (this->entity_count() == 1 && !this->use_write_multiple_) {
90 queued = this->write_single_register(this->write_address(), data[0]);
91 } else {
92 queued = this->write_multiple_registers(this->write_address(), data);
93 }
94 if (!queued) {
95 ESP_LOGW(TAG, "Modbus write for '%s' was refused by the hub; state not published", this->get_name().c_str());
96 return;
97 }
98 this->publish_state(value);
99}
100void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); }
101
102} // namespace esphome::modbus_controller
const StringRef & get_name() const
Definition entity_base.h:71
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 parse_and_publish(std::span< const uint8_t > data) override
optional< transform_func_t > 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_register(uint16_t address, uint16_t value)
bool send_raw_frame_deprecated_(std::span< const uint8_t > frame)
bool write_multiple_registers(uint16_t address, std::span< const uint16_t > values)
void publish_state(float state)
Definition number.cpp:22
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
float payload_to_float(std::span< const uint8_t > data, const SensorItem &item, uint8_t offset)
Convert vector<uint8_t> response payload to float.
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 std::array< uint8_t, sizeof(T)> decode_value(T val)
Decode a value into its constituent bytes (from most to least significant).
Definition helpers.h:913
constexpr size_t format_hex_pretty_uint16_size(size_t count)
Calculate buffer size needed for format_hex_pretty_to with uint16_t data: "XXXX:XXXX:....
Definition helpers.h:1473