ESPHome 2026.11.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
7ESPHOME_LOG_TAG(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->publish_state(result);
27}
28
29void ModbusNumber::control(float value) {
30 this->clear_dispatched_();
31 // A new write supersedes this entity's own not-yet-sent writes: drop them (and detach any in-flight one)
32 // so a rapidly-changing value writes the latest, not every intermediate.
35 float write_value = value;
36 if (this->write_transform_func_.has_value()) {
37 // The lambda may drive the write itself via item->write_*(), override the value (return a value), or
38 // (deprecated) fill `data` with the register words to write.
39 auto val = (*this->write_transform_func_)(this, value, data);
40 if (this->dispatched()) {
41 this->publish_state(value);
42 return;
43 }
44 if (!data.empty()) {
45 // Deprecated buffer path (frozen): the lambda filled a legacy raw frame as words; pack it big-endian.
46 this->warn_write_buffer_deprecated_(LOG_STR("number"), this->start_address);
47#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
48 char hex_buf[format_hex_pretty_uint16_size(MODBUS_NUMBER_MAX_LOG_REGISTERS)];
49#endif
50 ESP_LOGV(TAG, "Modbus Number write raw: %s",
51 format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
52 // Sized to hold RegisterValues at capacity, so a full buffer can never truncate into a valid frame.
54 for (uint16_t word : data) {
55 const auto word_bytes = decode_value(word);
56 bytes.push_back(word_bytes[0]);
57 bytes.push_back(word_bytes[1]);
58 }
59 if (!this->send_raw_frame_deprecated_(std::span<const uint8_t>(bytes.data(), bytes.size()))) {
60 ESP_LOGW(TAG, "Modbus write for '%s' was refused by the hub; state not published",
61 LOG_STR_ARG(this->get_log_name()));
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 LOG_STR_ARG(this->get_log_name()), 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",
96 LOG_STR_ARG(this->get_log_name()));
97 return;
98 }
99 this->publish_state(value);
100}
101void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); }
102
103} // namespace esphome::modbus_controller
const LogString * get_log_name() const
Get the name for a "%s" log argument (LOG_STR_ARG).
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:286
bool empty() const
Definition helpers.h:357
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:20
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
ESPHOME_LOG_TAG(TAG, "modbus_controller.binary_sensor")
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:498
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:1056
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:1644