ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
modbus_controller.h
Go to the documentation of this file.
1#pragma once
2
4
8
9#include <list>
10#include <queue>
11#include <set>
12#include <utility>
13#include <vector>
14
16
17class ModbusController;
18
23
24// Remove before 2026.10.0 — these helpers have moved to modbus::helpers
25ESPDEPRECATED("Use modbus::helpers::value_type_is_float() instead. Removed in 2026.10.0", "2026.4.0")
26inline bool value_type_is_float(SensorValueType v) { return modbus::helpers::value_type_is_float(v); }
27
28ESPDEPRECATED("Use modbus::helpers::modbus_register_read_function() instead. Removed in 2026.10.0", "2026.4.0")
29inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
31}
32
33ESPDEPRECATED("Use modbus::helpers::modbus_register_write_function() instead. Removed in 2026.10.0", "2026.4.0")
34inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_type) {
36}
37
38ESPDEPRECATED("Use modbus::helpers::c_to_hex() instead. Removed in 2026.10.0", "2026.4.0")
39inline uint8_t c_to_hex(char c) { return modbus::helpers::c_to_hex(c); }
40
41ESPDEPRECATED("Use modbus::helpers::byte_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
42inline uint8_t byte_from_hex_str(const std::string &value, uint8_t pos) {
44}
45
46ESPDEPRECATED("Use modbus::helpers::word_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
47inline uint16_t word_from_hex_str(const std::string &value, uint8_t pos) {
49}
50
51ESPDEPRECATED("Use modbus::helpers::dword_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
52inline uint32_t dword_from_hex_str(const std::string &value, uint8_t pos) {
54}
55
56ESPDEPRECATED("Use modbus::helpers::qword_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
57inline uint64_t qword_from_hex_str(const std::string &value, uint8_t pos) {
59}
60
61template<typename T>
62ESPDEPRECATED("Use modbus::helpers::get_data() instead. Removed in 2026.10.0", "2026.4.0")
63T get_data(const std::vector<uint8_t> &data, size_t buffer_offset) {
64 return modbus::helpers::get_data<T>(data, buffer_offset);
65}
66
67// Remove before 2027.2.0 (window restarted when the migration target changed to bit_from_packed())
68ESPDEPRECATED("Use modbus::helpers::bit_from_packed() instead. Removed in 2027.2.0", "2026.4.0")
69inline bool coil_from_vector(int coil, const std::vector<uint8_t> &data) {
71}
72
73template<typename N>
74ESPDEPRECATED("Use modbus::helpers::mask_and_shift_by_rightbit() instead. Removed in 2026.10.0", "2026.4.0")
75N mask_and_shift_by_rightbit(N data, uint32_t mask) {
77}
78
79ESPDEPRECATED("Use modbus::helpers::number_to_payload() instead. Removed in 2026.10.0", "2026.4.0")
80inline void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
81 modbus::helpers::number_to_payload(data, value, value_type);
82}
83
84ESPDEPRECATED("Use modbus::helpers::payload_to_number() instead. Removed in 2026.10.0", "2026.4.0")
85inline int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
86 uint32_t bitmask) {
87 return modbus::helpers::payload_to_number(std::span<const uint8_t>(data), sensor_value_type, offset, bitmask)
88 .value_or(0);
89}
90
91ESPDEPRECATED("Use modbus::helpers::float_to_payload() instead. Removed in 2026.10.0", "2026.4.0")
92inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
93 return modbus::helpers::float_to_payload(value, value_type);
94}
95
96class ModbusController;
97
99 public:
100 virtual void parse_and_publish(const std::vector<uint8_t> &data) = 0;
101
102 void set_custom_data(const std::vector<uint8_t> &data) { custom_data = data; }
103 size_t virtual get_register_size() const {
105 return 1;
106 } else { // if CONF_RESPONSE_BYTES is used override the default
108 }
109 }
110 // Override register size for modbus devices not using 1 register for one dword
111 void set_register_size(uint8_t register_size) { response_bytes = register_size; }
114 uint16_t start_address{0};
116 uint8_t offset{0};
117 uint8_t register_count{0};
118 uint8_t response_bytes{0};
119 uint16_t skip_updates{0};
120 std::vector<uint8_t> custom_data{};
121 bool force_new_range{false};
122};
123
124// ModbusController::create_register_ranges_ tries to optimize register range
125// for this the sensors must be ordered by register_type, start_address and bitmask
127 public:
128 bool operator()(const SensorItem *lhs, const SensorItem *rhs) const {
129 // first sort according to register type
130 if (lhs->register_type != rhs->register_type) {
131 return lhs->register_type < rhs->register_type;
132 }
133
134 // ensure that sensor with force_new_range set are before the others
135 if (lhs->force_new_range != rhs->force_new_range) {
136 return lhs->force_new_range > rhs->force_new_range;
137 }
138
139 // sort by start address
140 if (lhs->start_address != rhs->start_address) {
141 return lhs->start_address < rhs->start_address;
142 }
143
144 // sort by offset (ensures update of sensors in ascending order)
145 if (lhs->offset != rhs->offset) {
146 return lhs->offset < rhs->offset;
147 }
148
149 // The pointer to the sensor is used last to ensure that
150 // multiple sensors with the same values can be added with a stable sort order.
151 return lhs < rhs;
152 }
153};
154
155using SensorSet = std::set<SensorItem *, SensorItemsComparator>;
156
161 uint16_t skip_updates; // the config value
162 SensorSet sensors; // all sensors of this range
163 uint16_t skip_updates_counter; // the running value
164};
165
167 public:
168 static const size_t MAX_PAYLOAD_BYTES = 240;
170 uint16_t register_address{0};
171 uint16_t register_count{0};
174 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
176 std::vector<uint8_t> payload = {};
177 bool send();
179 bool should_retry(uint8_t max_retries) { return this->send_count_ <= max_retries; };
180
182
193 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
194 &&handler);
204 uint16_t start_address, uint16_t register_count);
215 uint16_t register_count, const std::vector<uint16_t> &values);
225 uint16_t value);
234
243 const std::vector<bool> &values);
252 ModbusController *modbusdevice, const std::vector<uint8_t> &values,
253 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
254 &&handler = nullptr);
255
264 ModbusController *modbusdevice, const std::vector<uint16_t> &values,
265 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
266 &&handler = nullptr);
267
268 bool is_equal(const ModbusCommandItem &other);
269
270 protected:
271 // wrong commands (esp. custom commands) can block the send queue, limit the number of repeats.
273 uint8_t send_count_{0};
274};
275
285 public:
286 void dump_config() override;
287 void loop() override;
288 void setup() override;
289 void update() override;
290
292 void queue_command(const ModbusCommandItem &command);
294 void add_sensor_item(SensorItem *item) { sensorset_.insert(item); }
296 void on_modbus_data(const std::vector<uint8_t> &data) override;
298 void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
300 void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
303 void on_write_register_response(ModbusRegisterType register_type, uint16_t start_address,
304 const std::vector<uint8_t> &data);
306 void set_allow_duplicate_commands(bool allow_duplicate_commands) {
307 this->allow_duplicate_commands_ = allow_duplicate_commands;
308 }
312 void set_command_throttle(uint16_t command_throttle) { this->command_throttle_ = command_throttle; }
314 void set_offline_skip_updates(uint16_t offline_skip_updates) { this->offline_skip_updates_ = offline_skip_updates; }
316 size_t get_command_queue_length() { return command_queue_.size(); }
320 template<typename F> void add_on_command_sent_callback(F &&callback) {
321 this->command_sent_callback_.add(std::forward<F>(callback));
322 }
324 template<typename F> void add_on_online_callback(F &&callback) {
325 this->online_callback_.add(std::forward<F>(callback));
326 }
328 template<typename F> void add_on_offline_callback(F &&callback) {
329 this->offline_callback_.add(std::forward<F>(callback));
330 }
332 void set_max_cmd_retries(uint8_t max_cmd_retries) { this->max_cmd_retries_ = max_cmd_retries; }
334 uint8_t get_max_cmd_retries() { return this->max_cmd_retries_; }
335
336 protected:
339 // find register in sensormap. Returns iterator with all registers having the same start address
340 SensorSet find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const;
344 void process_modbus_data_(const ModbusCommandItem *response);
346 bool send_next_command_();
348 void dump_sensors_();
352 std::vector<RegisterRange> register_ranges_{};
354 std::list<std::unique_ptr<ModbusCommandItem>> command_queue_;
356 std::queue<std::unique_ptr<ModbusCommandItem>> incoming_queue_;
362 uint16_t command_throttle_{0};
364 bool module_offline_{false};
375};
376
382inline float payload_to_float(std::span<const uint8_t> data, const SensorItem &item) {
383 int64_t number =
385
386 float float_value;
388 float_value = bit_cast<float>(static_cast<uint32_t>(number));
389 } else {
390 float_value = static_cast<float>(number);
391 }
392
393 return float_value;
394}
395
396} // namespace esphome::modbus_controller
uint8_t address
Definition bl0906.h:4
This class simplifies creating components that periodically check a state.
Definition component.h:510
static ModbusCommandItem create_custom_command(ModbusController *modbusdevice, const std::vector< uint8_t > &values, std::function< void(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)> &&handler=nullptr)
Create custom modbus command.
bool should_retry(uint8_t max_retries)
Check if the command should be retried based on the max_retries parameter.
bool is_equal(const ModbusCommandItem &other)
static ModbusCommandItem create_write_multiple_coils(ModbusController *modbusdevice, uint16_t start_address, const std::vector< bool > &values)
Create modbus write multiple registers command Function 15 (0Fhex) Write Multiple Coils.
static ModbusCommandItem create_write_single_coil(ModbusController *modbusdevice, uint16_t address, bool value)
Create modbus write single registers command Function 05 (05hex) Write Single Coil.
uint8_t send_count_
How many times this command has been sent.
static ModbusCommandItem create_write_single_command(ModbusController *modbusdevice, uint16_t start_address, uint16_t value)
Create modbus write multiple registers command Function 16 (10hex) Write Multiple Registers.
static ModbusCommandItem create_read_command(ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count, std::function< void(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)> &&handler)
factory methods
static ModbusCommandItem create_write_multiple_command(ModbusController *modbusdevice, uint16_t start_address, uint16_t register_count, const std::vector< uint16_t > &values)
Create modbus read command Function code 02-04.
std::function< void(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)> on_data_func
void add_sensor_item(SensorItem *item)
Registers a sensor with the controller. Called by esphomes code generator.
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)
default delegate called by process_modbus_data when a response has retrieved from the incoming queue
std::queue< std::unique_ptr< ModbusCommandItem > > incoming_queue_
modbus response data waiting to get processed
void set_command_throttle(uint16_t command_throttle)
called by esphome generated code to set the command_throttle period
void set_offline_skip_updates(uint16_t offline_skip_updates)
called by esphome generated code to set the offline_skip_updates
uint16_t command_throttle_
min time in ms between sending modbus commands
void on_write_register_response(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)
default delegate called by process_modbus_data when a response for a write response has retrieved fro...
bool allow_duplicate_commands_
if duplicate commands can be sent
bool get_allow_duplicate_commands()
get if a duplicate command can be sent
CallbackManager< void(int, int)> command_sent_callback_
Command sent callback.
std::vector< RegisterRange > register_ranges_
Continuous range of modbus registers.
uint32_t last_command_timestamp_
when was the last send operation
CallbackManager< void(int, int)> offline_callback_
Server offline callback.
void add_on_command_sent_callback(F &&callback)
Set callback for commands.
void dump_sensors_()
dump the parsed sensormap for diagnostics
SensorSet sensorset_
Collection of all sensors for this component.
uint8_t max_cmd_retries_
How many times we will retry a command if we get no response.
bool send_next_command_()
send the next modbus command from the send queue
std::list< std::unique_ptr< ModbusCommandItem > > command_queue_
Hold the pending requests to be sent.
size_t get_command_queue_length()
get the number of queued modbus commands (should be mostly empty)
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override
called when a modbus error response was received
void process_modbus_data_(const ModbusCommandItem *response)
parse incoming modbus data
void set_allow_duplicate_commands(bool allow_duplicate_commands)
Allow a duplicate command to be sent.
void update_range_(RegisterRange &r)
submit the read command for the address range to the send queue
uint8_t get_max_cmd_retries()
get how many times a command will be (re)sent if no response is received
void set_max_cmd_retries(uint8_t max_cmd_retries)
called by esphome generated code to set the max_cmd_retries.
bool module_offline_
if module didn't respond the last command
size_t create_register_ranges_()
parse sensormap_ and create range of sequential addresses
uint16_t offline_skip_updates_
how many updates to skip if module is offline
bool get_module_offline()
get if the module is offline, didn't respond the last command
void add_on_online_callback(F &&callback)
Set callback for online changes.
void add_on_offline_callback(F &&callback)
Set callback for offline changes.
void on_modbus_data(const std::vector< uint8_t > &data) override
called when a modbus response was parsed without errors
void queue_command(const ModbusCommandItem &command)
queues a modbus command in the send queue
SensorSet find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const
CallbackManager< void(int, int)> online_callback_
Server online callback.
void set_custom_data(const std::vector< uint8_t > &data)
virtual void parse_and_publish(const std::vector< uint8_t > &data)=0
void set_register_size(uint8_t register_size)
bool operator()(const SensorItem *lhs, const SensorItem *rhs) const
ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type)
bool value_type_is_float(SensorValueType v)
ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_type, bool multiple=false)
void number_to_payload(Container &data, int64_t value, SensorValueType value_type)
Append the Modbus register words for value to data.
T get_data(const uint8_t *data, size_t buffer_offset)
Extract data from modbus response buffer.
std::vector< uint16_t > float_to_payload(float value, SensorValueType value_type)
uint64_t qword_from_hex_str(const std::string &value, uint8_t pos)
Get a qword from a hex string.
N mask_and_shift_by_rightbit(N data, uint32_t mask)
Extract bits from value and shift right according to the bitmask if the bitmask is 0x00F0 we want the...
uint32_t dword_from_hex_str(const std::string &value, uint8_t pos)
Get a dword from a hex string.
uint8_t byte_from_hex_str(const std::string &value, uint8_t pos)
Get a byte from a hex string byte_from_hex_str("1122", 1) returns uint_8 value 0x22 == 34 byte_from_h...
uint16_t word_from_hex_str(const std::string &value, uint8_t pos)
Get a word from a hex string.
bool bit_from_packed(int bit, std::span< const uint8_t > data)
Extract coil data from modbus response buffer Responses for coil are packed into bytes .
std::optional< int64_t > payload_to_number(const uint8_t *data, size_t size, SensorValueType sensor_value_type, uint8_t offset, uint32_t bitmask)
Convert a raw response payload to a number.
ESPDEPRECATED("Use modbus::helpers::value_type_is_float() instead. Removed in 2026.10.0", "2026.4.0") inline bool value_type_is_float(SensorValueType v)
std::set< SensorItem *, SensorItemsComparator > SensorSet
const std::vector< uint8_t > & data
float payload_to_float(std::span< const uint8_t > data, const SensorItem &item)
Convert vector<uint8_t> response payload to float.
size_t size_t pos
Definition helpers.h:1052
To bit_cast(const From &src)
Convert data between types, without aliasing issues or undefined behaviour.
Definition helpers.h:84
STL namespace.
static void uint32_t