ESPHome 2026.9.1
Loading...
Searching...
No Matches
modbus_controller.h
Go to the documentation of this file.
1#pragma once
2
4
9
10#include <list>
11#include <set>
12#include <span>
13#include <utility>
14#include <vector>
15
17
18class ModbusController;
19
24
25// Remove before 2027.2.0 - deprecated names re-exported so external components keep their warning window
26#pragma GCC diagnostic push
27#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
28using modbus::ModbusExceptionCode;
29using modbus::ModbusFunctionCode;
30using modbus::ModbusRegisterType;
31#pragma GCC diagnostic pop
32
33// Remove before 2026.10.0 — these helpers have moved to modbus::helpers
34ESPDEPRECATED("Use modbus::helpers::value_type_is_float() instead. Removed in 2026.10.0", "2026.4.0")
35inline bool value_type_is_float(SensorValueType v) { return modbus::helpers::value_type_is_float(v); }
36
37ESPDEPRECATED("Use modbus::helpers::modbus_register_read_function() instead. Removed in 2026.10.0", "2026.4.0")
38inline FunctionCode modbus_register_read_function(modbus::EntityType reg_type) {
40}
41
42ESPDEPRECATED("Use modbus::helpers::modbus_register_write_function() instead. Removed in 2026.10.0", "2026.4.0")
43inline FunctionCode modbus_register_write_function(modbus::EntityType reg_type) {
45}
46
47ESPDEPRECATED("Use modbus::helpers::c_to_hex() instead. Removed in 2026.10.0", "2026.4.0")
48inline uint8_t c_to_hex(char c) { return modbus::helpers::c_to_hex(c); }
49
50ESPDEPRECATED("Use modbus::helpers::byte_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
51inline uint8_t byte_from_hex_str(const std::string &value, uint8_t pos) {
53}
54
55ESPDEPRECATED("Use modbus::helpers::word_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
56inline uint16_t word_from_hex_str(const std::string &value, uint8_t pos) {
58}
59
60ESPDEPRECATED("Use modbus::helpers::dword_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
61inline uint32_t dword_from_hex_str(const std::string &value, uint8_t pos) {
63}
64
65ESPDEPRECATED("Use modbus::helpers::qword_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
66inline uint64_t qword_from_hex_str(const std::string &value, uint8_t pos) {
68}
69
70template<typename T>
71ESPDEPRECATED("Use modbus::helpers::get_data() instead. Removed in 2026.10.0", "2026.4.0")
72T get_data(const std::vector<uint8_t> &data, size_t buffer_offset) {
73 return modbus::helpers::get_data<T>(data, buffer_offset);
74}
75
76// Span overloads of the deprecated helpers below: read lambdas receive their payload as a
77// std::span<const uint8_t> (previously a const std::vector<uint8_t> &), and a span does not convert to
78// a vector, so existing lambdas calling these by name need an overload that accepts one. These carry
79// this release's deprecation window, since the span forms only exist from it.
80// payload_to_number() deliberately has no such overload: one of its arguments is a modbus::helpers
81// type, so a span call already reaches the helper by argument-dependent lookup, and a forwarder here
82// would only make that call ambiguous.
83// Remove before 2027.2.0.
84template<typename T>
85ESPDEPRECATED("Use modbus::helpers::get_data() instead. Removed in 2027.2.0", "2026.8.0")
86T get_data(std::span<const uint8_t> data, size_t buffer_offset) {
87 return modbus::helpers::get_data<T>(data.data(), buffer_offset);
88}
89
90// Remove before 2027.2.0 (window restarted when the migration target changed to bit_from_packed())
91ESPDEPRECATED("Use modbus::helpers::bit_from_packed() instead. Removed in 2027.2.0", "2026.4.0")
92inline bool coil_from_vector(int coil, const std::vector<uint8_t> &data) {
94}
95
96// Remove before 2027.2.0
97ESPDEPRECATED("Use modbus::helpers::bit_from_packed() instead. Removed in 2027.2.0", "2026.8.0")
98inline bool coil_from_vector(int coil, std::span<const uint8_t> data) {
100}
101
102template<typename N>
103ESPDEPRECATED("Use modbus::helpers::mask_and_shift_by_rightbit() instead. Removed in 2026.10.0", "2026.4.0")
104N mask_and_shift_by_rightbit(N data, uint32_t mask) {
106}
107
108ESPDEPRECATED("Use modbus::helpers::number_to_payload() instead. Removed in 2026.10.0", "2026.4.0")
109inline void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
110 modbus::helpers::number_to_payload(data, value, value_type);
111}
112
113ESPDEPRECATED("Use modbus::helpers::payload_to_number() instead. Removed in 2026.10.0", "2026.4.0")
114inline int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
115 uint32_t bitmask) {
116 return modbus::helpers::payload_to_number(std::span<const uint8_t>(data), sensor_value_type, offset, bitmask)
117 .value_or(0);
118}
119
120ESPDEPRECATED("Use modbus::helpers::float_to_payload() instead. Removed in 2026.10.0", "2026.4.0")
121inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
122 std::vector<uint16_t> data;
123 modbus::helpers::float_to_payload(data, value, value_type);
124 return data;
125}
126
127class ModbusController;
128
133enum class RangeReuse : uint8_t {
134 AUTO = 0, // join when adjacent and the position in the reply is exact (no non-standard response_size ahead)
135 ALWAYS = 1, // join unconditionally, reading across any address gap
136 NEVER = 2, // never join backward (later items may still extend this item's range)
137};
138
140 public:
144 virtual void parse_and_publish(std::span<const uint8_t> data) = 0;
145
148
151 uint16_t write_address() const {
152 return this->range_start_address + (this->addresses_bits() ? this->offset : this->offset / 2);
153 }
154
160 this->offset = offset;
161 }
162
166 void set_address(uint16_t address) {
167 this->start_address = address;
169 }
170
171 void set_custom_pdu(std::initializer_list<uint8_t> pdu) { this->custom_pdu.set(pdu.begin(), pdu.size()); }
172
175 virtual uint16_t entity_count() const {
177 return 1;
178 }
179 if (this->sensor_value_type == SensorValueType::RAW && this->response_bytes > 0) {
180 return (this->response_bytes + 1) / 2;
181 }
183 }
184
187 size_t virtual get_register_size() const {
188 if (this->addresses_bits()) {
189 return 1;
190 } else { // if CONF_RESPONSE_BYTES is used override the default
191 return response_bytes > 0 ? response_bytes : this->entity_count() * 2;
192 }
193 }
194 // Override register size for modbus devices not using 1 register for one dword
195 void set_register_size(uint8_t register_size) { response_bytes = register_size; }
198 uint16_t start_address{0};
204 uint8_t offset{0};
205 uint8_t response_bytes{0};
216};
217
218// ModbusController::create_polling_commands_ tries to optimize register range
219// for this the sensors must be ordered by register_type, start_address and bitmask
221 public:
222 bool operator()(const SensorItem *lhs, const SensorItem *rhs) const {
223 // first sort according to register type
224 if (lhs->register_type != rhs->register_type) {
225 return lhs->register_type < rhs->register_type;
226 }
227
228 // sort by start address
229 if (lhs->start_address != rhs->start_address) {
230 return lhs->start_address < rhs->start_address;
231 }
232
233 // at the same address: AUTO before ALWAYS before NEVER, so a NEVER item never starts the range
234 // the others at that address are then forced to share (see RangeReuse)
237 }
238
239 // sort by the offset as configured (ensures update of sensors in ascending order). The resolved
240 // `offset` is deliberately not used: ranges are built while iterating this set and assign it, and
241 // a sort key that changed under the iteration would corrupt the set's ordering.
244 }
245
246 // The pointer to the sensor is used last to ensure that
247 // multiple sensors with the same values can be added with a stable sort order.
248 return lhs < rhs;
249 }
250};
251
252using SensorSet = std::set<SensorItem *, SensorItemsComparator>;
253
257 uint16_t register_count; // registers (or bits) the poll command reads; joins across gaps can exceed 255
258 SensorSet sensors; // all sensors of this range
261};
262
267 public:
268 // Public: only the owner can reach this instance, so reachability is the access gate.
269 void set_controller(ModbusController *controller);
270
271 protected:
272 ControllerDevice() = default; // WriterEntity's member is wired later via set_controller()
273 explicit ControllerDevice(ModbusController *controller) { this->set_controller(controller); }
274
275 void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) override;
276 void on_error(std::span<const uint8_t> request_pdu, modbus::ExceptionCode exception_code) override;
277 void on_sent(std::span<const uint8_t> request_pdu) override;
278 void on_not_sent(std::span<const uint8_t> request_pdu) override;
279 bool on_no_response(std::span<const uint8_t> request_pdu) override;
280
281 void notify_online_(std::span<const uint8_t> request_pdu);
282
285 bool dispatched_ : 1 {false};
289};
290
293class WriterDevice final : public ControllerDevice {
294 public:
295 using modbus::ModbusClientDevice::clear_tx_queue_for_device;
296 using modbus::ModbusClientDevice::queue_pdu;
297 using modbus::ModbusClientDevice::write_multiple_coils;
298 using modbus::ModbusClientDevice::write_multiple_registers;
299 using modbus::ModbusClientDevice::write_single_coil;
300 using modbus::ModbusClientDevice::write_single_register;
301
304 bool send_raw_frame_deprecated(std::span<const uint8_t> frame);
305
306 bool dispatched() const { return this->dispatched_; }
307 void set_dispatched() { this->dispatched_ = true; }
308 void clear_dispatched() { this->dispatched_ = false; }
313 void warn_write_buffer_deprecated(const LogString *platform, uint16_t address);
314
315 protected:
316 // Only the write side forwards to the typed callbacks (for item->queue_pdu() replies): a poll parses
317 // its own response, and dispatching its errors would trip the base unhandled-custom-response warning.
318 void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) override;
319 void on_error(std::span<const uint8_t> request_pdu, modbus::ExceptionCode exception_code) override;
320};
321
328 public:
331 bool dispatched() const { return this->device_.dispatched(); }
333 bool write_single_register(uint16_t address, uint16_t value) {
334 this->device_.set_dispatched();
335 return this->device_.write_single_register(address, value, this->device_.write_options());
336 }
337 bool write_single_coil(uint16_t address, bool value) {
338 this->device_.set_dispatched();
339 return this->device_.write_single_coil(address, value, this->device_.write_options());
340 }
341 bool write_multiple_registers(uint16_t address, std::span<const uint16_t> values) {
342 this->device_.set_dispatched();
343 return this->device_.write_multiple_registers(address, values, this->device_.write_options());
344 }
345 bool write_multiple_coils(uint16_t address, std::span<const bool> values) {
346 this->device_.set_dispatched();
347 return this->device_.write_multiple_coils(address, values, this->device_.write_options());
348 }
350 this->device_.set_dispatched();
351 return this->device_.write_multiple_coils(address, bits, this->device_.write_options());
352 }
353 bool queue_pdu(std::span<const uint8_t> pdu) { return this->queue_pdu(pdu, this->device_.write_options()); }
354 bool queue_pdu(std::span<const uint8_t> pdu, modbus::CommandOptions options) {
355 this->device_.set_dispatched();
356 return this->device_.queue_pdu(pdu, options);
357 }
358 void clear_tx_queue_for_device() { this->device_.clear_tx_queue_for_device(); }
359
360 protected:
361 bool send_raw_frame_deprecated_(std::span<const uint8_t> frame) {
362 this->device_.set_dispatched();
363 return this->device_.send_raw_frame_deprecated(frame);
364 }
365 void set_controller_(ModbusController *controller) { this->device_.set_controller(controller); }
366 void clear_dispatched_() { this->device_.clear_dispatched(); }
367 void warn_write_buffer_deprecated_(const LogString *platform, uint16_t address) {
368 this->device_.warn_write_buffer_deprecated(platform, address);
369 }
370
371 private:
372 // Private so a derived entity cannot reach the device except through the recording forwarders above.
373 WriterDevice device_;
374};
375
378class PollingDevice final : public ControllerDevice {
379 public:
381
384
385 uint16_t register_address() const { return this->range_.start_address; }
386 uint16_t register_count() const { return this->range_.register_count; }
388
389 protected:
390 void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) override;
391
393};
394
398// The deprecated class references other deprecated names. Remove before 2027.3.0.
399#pragma GCC diagnostic push
400#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
401class ESPDEPRECATED(
402 "One-shot writes go through the entity write helpers (WriterDevice) or the modbus_client actions, and "
403 "polling runs through PollingDevice. Removed in 2027.3.0",
404 "2026.9.0") ModbusCommandItem : public modbus::ModbusClientDevice {
405 public:
407 ModbusCommandItem(ModbusController &controller, modbus::ModbusClientHub *parent, uint8_t address)
408 : modbus::ModbusClientDevice(parent, address), controller_(&controller) {}
410 ModbusCommandItem(ModbusController &controller, modbus::ModbusClientHub *parent, uint8_t address,
411 RegisterRange &&range);
414 ModbusCommandItem(ModbusController &controller, modbus::ModbusClientHub *parent, uint8_t address, SensorItem *sensor);
415
416 // The base deletes copy/move (its destructor unregisters the device from the hub queue), but command
417 // items are stored in value containers, so copy/move CONSTRUCTION is re-provided (copy only for the
418 // queue_command() path). Assignment stays deleted: the item's address-in-memory is its hub identity.
419 ModbusCommandItem(const ModbusCommandItem &other);
420 ModbusCommandItem(ModbusCommandItem &&other) noexcept;
421 ModbusCommandItem &operator=(ModbusCommandItem &&) = delete;
422
423 SensorSet sensors; // sensors served by this command (empty for factory/write commands)
424 std::function<void(EntityType register_type, uint16_t start_address, std::span<const uint8_t> data)> on_data_func;
430 SmallInlineBuffer<8> payload;
431 // Set by unqueue_command() when this one-shot has completed. The controller erases flagged items at a
432 // safe point (update()/queue_command()), never from inside the command's own callback.
433 bool pending_removal{false};
434
436 void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) override;
438 void on_error(std::span<const uint8_t> request_pdu, modbus::ExceptionCode exception_code) override;
440 void on_not_sent(std::span<const uint8_t> request_pdu) override;
442 void on_sent(std::span<const uint8_t> request_pdu) override;
444 bool on_no_response(std::span<const uint8_t> request_pdu) override;
445
446 uint16_t register_address() const { return this->start_address_; }
447 uint16_t register_count() const { return this->register_count_; }
448 EntityType register_type() const { return this->register_type_; }
449
455 bool send(modbus::CommandOptions options = {});
456
458
467 static ModbusCommandItem create_read_command(
468 ModbusController *modbusdevice, EntityType register_type, uint16_t start_address, uint16_t register_count,
469 std::function<void(EntityType register_type, uint16_t start_address, std::span<const uint8_t> data)> &&handler);
479 static ModbusCommandItem create_write_multiple_command(ModbusController *modbusdevice, uint16_t start_address,
480 uint16_t register_count, const std::vector<uint16_t> &values);
489 static ModbusCommandItem create_write_single_command(ModbusController *modbusdevice, uint16_t start_address,
490 uint16_t value);
498 static ModbusCommandItem create_write_single_coil(ModbusController *modbusdevice, uint16_t address, bool value);
499
507 static ModbusCommandItem create_write_multiple_coils(ModbusController *modbusdevice, uint16_t start_address,
508 const std::vector<bool> &values);
516 static ModbusCommandItem create_custom_command(
517 ModbusController *modbusdevice, const std::vector<uint8_t> &values,
518 std::function<void(EntityType register_type, uint16_t start_address, std::span<const uint8_t> data)> &&handler =
519 nullptr);
520
528 static ModbusCommandItem create_custom_command(
529 ModbusController *modbusdevice, const std::vector<uint16_t> &values,
530 std::function<void(EntityType register_type, uint16_t start_address, std::span<const uint8_t> data)> &&handler =
531 nullptr);
532
533 protected:
534 void set_command_(FunctionCode function_code, EntityType register_type, uint16_t start_address,
535 uint16_t register_count) {
536 this->function_code_ = function_code;
537 this->register_type_ = register_type;
538 this->start_address_ = start_address;
539 this->register_count_ = register_count;
540 }
541 EntityType register_type_{EntityType::CUSTOM};
542 uint16_t start_address_{0};
543 uint16_t register_count_{0};
544 FunctionCode function_code_{FunctionCode::CUSTOM};
546 const SmallInlineBuffer<8> *custom_pdu_{nullptr};
547 ModbusController *controller_{nullptr};
548};
549#pragma GCC diagnostic pop
550
554inline bool offline_retry_due(uint16_t update_counter, uint16_t module_offline_at, uint16_t offline_skip_updates) {
555 return static_cast<uint16_t>(update_counter + 1 - module_offline_at) % (offline_skip_updates + 1) == 0;
556}
557
566class ModbusController final : public PollingComponent {
567 public:
568 // The controller is not itself a modbus device - its commands and writer entities send as their own
569 // devices, built against this hub + address.
571
572 void dump_config() override;
573 // No loop() override: the hub owns transmit/receive timing and each command routes its own
574 // response, so the controller never joins the looping components at all.
575 void setup() override;
576 void update() override;
577
580 modbus::ModbusClientHub *hub() const { return this->hub_; }
581 uint8_t device_address() const { return this->address_; }
582
583#pragma GCC diagnostic push
584#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
587 ESPDEPRECATED("Use the entity write helpers or the modbus_client actions instead. Removed in 2027.3.0", "2026.9.0")
588 void queue_command(ModbusCommandItem command);
592 ESPDEPRECATED("Serves only ModbusCommandItem's own callbacks. Removed in 2027.3.0", "2026.9.0")
593 void unqueue_command(const ModbusCommandItem *command);
594#pragma GCC diagnostic pop
596 void add_sensor_item(SensorItem *item) { sensorset_.insert(item); }
599 ESPDEPRECATED("Write acknowledgements are handled by the writing entity's own device. Removed in 2027.3.0",
600 "2026.9.0")
601 void on_write_register_response(EntityType register_type, uint16_t start_address, std::span<const uint8_t> data);
603 void set_online(bool online, int function_code, int register_address);
605 void command_sent(int function_code, int register_address) {
606 this->command_sent_callback_.call(function_code, register_address);
607 }
612 bool can_send() { return this->cmd_non_responses_ <= this->max_cmd_retries_; }
614 void set_offline_skip_updates(uint16_t offline_skip_updates) { this->offline_skip_updates_ = offline_skip_updates; }
618 template<typename F> void add_on_command_sent_callback(F &&callback) {
619 this->command_sent_callback_.add(std::forward<F>(callback));
620 }
622 template<typename F> void add_on_online_callback(F &&callback) {
623 this->online_callback_.add(std::forward<F>(callback));
624 }
626 template<typename F> void add_on_offline_callback(F &&callback) {
627 this->offline_callback_.add(std::forward<F>(callback));
628 }
630 void set_max_cmd_retries(uint8_t max_cmd_retries) { this->max_cmd_retries_ = max_cmd_retries; }
632 uint8_t get_max_cmd_retries() { return this->max_cmd_retries_; }
636 const modbus::CommandOptions &read_options() const { return this->read_options_; }
637
638 protected:
643 uint8_t address_{0};
651#pragma GCC diagnostic push
652#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
653 std::list<std::unique_ptr<ModbusCommandItem>> one_shot_command_items_;
654#pragma GCC diagnostic pop
661 bool module_offline_{false};
665 uint16_t update_counter_{0};
680};
681
687inline float payload_to_float(std::span<const uint8_t> data, const SensorItem &item, uint8_t offset) {
688 int64_t number = modbus::helpers::payload_to_number(data, item.sensor_value_type, offset, item.bitmask).value_or(0);
689
690 float float_value;
692 float_value = bit_cast<float>(static_cast<uint32_t>(number));
693 } else {
694 float_value = static_cast<float>(number);
695 }
696
697 return float_value;
698}
699
700// Remove before 2027.2.0 (window opened when this helper gained an explicit offset). item.offset is
701// the item's resolved position within its range's response, so this decodes the same bytes as passing
702// that offset explicitly.
703ESPDEPRECATED("Pass the offset explicitly: payload_to_float(data, item, item.offset). Removed in 2027.2.0", "2026.8.0")
704inline float payload_to_float(std::span<const uint8_t> data, const SensorItem &item) {
705 return payload_to_float(data, item, item.offset);
706}
707
708} // namespace esphome::modbus_controller
uint8_t address
Definition bl0906.h:4
Fixed-capacity vector - sized once through init() or try_init(); push_back never reallocates This avo...
Definition helpers.h:558
This class simplifies creating components that periodically check a state.
Definition component.h:510
Small buffer optimization - stores data inline when small, heap-allocates for large data This avoids ...
Definition helpers.h:152
void set(const uint8_t *src, size_t size)
Set buffer contents, allocating heap if needed.
Definition helpers.h:215
Read-only view of Modbus-packed bits: bit 0 of byte 0 is the first bit (LSB first),...
The shared feedback half of a controller-owned hub device: online/offline tracking,...
void set_controller(ModbusController *controller)
ControllerDevice(ModbusController *controller)
void notify_online_(std::span< const uint8_t > request_pdu)
void on_error(std::span< const uint8_t > request_pdu, modbus::ExceptionCode exception_code) override
void on_sent(std::span< const uint8_t > request_pdu) override
bool on_no_response(std::span< const uint8_t > request_pdu) override
void on_not_sent(std::span< const uint8_t > request_pdu) override
void on_response(std::span< const uint8_t > request_pdu, std::span< const uint8_t > response_pdu) override
bool dispatched_
Write-path state for WriterEntity's forwarders, packed into the base's tail padding.
void add_sensor_item(SensorItem *item)
Registers a sensor with the controller. Called by esphomes code generator.
uint8_t cmd_non_responses_
consecutive non-responses; drives can_send() and offline detection
void command_sent(int function_code, int register_address)
Fire the on_command_sent trigger (called when a command's frame reaches the wire).
void set_offline_skip_updates(uint16_t offline_skip_updates)
called by esphome generated code to set the offline_skip_updates
std::list< std::unique_ptr< ModbusCommandItem > > one_shot_command_items_
Dynamically queued one-shot commands (writes, custom commands).
CallbackManager< void(int, int)> command_sent_callback_
Command sent callback.
void increment_non_response_count()
A command timed out; bump the consecutive-timeout counter used by can_send()/offline detection.
CallbackManager< void(int, int)> offline_callback_
Server offline callback.
void add_on_command_sent_callback(F &&callback)
Set callback for commands.
SensorSet sensorset_
Collection of all sensors for this component.
FixedVector< PollingDevice > polling_devices_
One persistent PollingDevice per register range.
uint8_t max_cmd_retries_
How many times we will retry a command if we get no response.
void set_read_options(modbus::CommandOptions options)
called by esphome generated code with the read-side command options applied to every poll
const modbus::CommandOptions & read_options() const
the read-side command options applied to every poll
bool can_send()
Whether more retries are allowed before the device is considered offline.
modbus::CommandOptions read_options_
read-side command options applied to every poll
ModbusController(modbus::ModbusClientHub *hub, uint8_t address)
void sweep_completed_one_shots_()
Erases one-shot commands flagged by unqueue_command().
uint8_t get_max_cmd_retries()
get how many times a command will be (re)sent if no response is received
void create_polling_commands_()
Group the registered sensors into contiguous ranges and create one PollingDevice per range.
uint16_t module_offline_at_
update_counter_ value at which the module went offline (for offline_skip_updates timing)
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
ESPDEPRECATED("Write acknowledgements are handled by the writing entity's own device. Removed in 2027.3.0", "2026.9.0") void on_write_register_response(EntityType register_type
Handles a write command acknowledgement (used by write command on_data_func handlers).
uint16_t offline_skip_updates_
how many updates to skip if module is offline
modbus::ModbusClientHub * hub() const
The hub and modbus address this controller talks to.
bool get_module_offline()
get if the module is offline, didn't respond the last command
ESPDEPRECATED("Use the entity write helpers or the modbus_client actions instead. Removed in 2027.3.0", "2026.9.0") void queue_command(ModbusCommandItem command)
Queues a one-shot modbus command (writes, custom commands); taken by value, so std::move to avoid a c...
void set_online(bool online, int function_code, int register_address)
Update the online/offline state after a response or a run of timeouts, firing the callbacks.
uint16_t update_counter_
counts update() cycles; drives the offline-retry cadence
uint16_t std::span< const uint8_t > data
void add_on_online_callback(F &&callback)
Set callback for online changes.
void add_on_offline_callback(F &&callback)
Set callback for offline changes.
modbus::ModbusClientHub * hub_
The hub this controller's commands/entities send through, and the modbus address they target.
CallbackManager< void(int, int)> online_callback_
Server online callback.
A persistent hub device that polls one register range - the read-side mirror of WriterDevice.
void on_response(std::span< const uint8_t > request_pdu, std::span< const uint8_t > response_pdu) override
bool queue(modbus::CommandOptions options={})
Queue this range's read (or its sensor's custom PDU) on the hub. False = refused, no callback follows...
PollingDevice(ModbusController &controller, RegisterRange &&range)
virtual void parse_and_publish(std::span< const uint8_t > data)=0
Parse this sensor's slice out of its range's response and publish it.
void set_address(uint16_t address)
Sets the configured address, and points the range base at it.
void set_offset_from_start_address(uint8_t offset)
Records the offset as configured, and seeds the resolved position with it.
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...
void set_register_size(uint8_t register_size)
uint16_t range_start_address
First register of the range this sensor is polled in; equals start_address for an unpolled item.
void set_custom_pdu(std::initializer_list< uint8_t > pdu)
uint8_t offset
Position of this sensor's data within its range's response - a byte offset for registers,...
uint8_t offset_from_start_address
The offset exactly as configured: measured from this sensor's own start_address, where offset is meas...
bool addresses_bits() const
Coils and discrete inputs address individual bits; every other type addresses 16-bit registers.
uint16_t write_address() const
Address a write entity (switch/number/select) targets, derived from its resolved position within the ...
virtual size_t get_register_size() const
Bytes this item's registers occupy in a response: one per bit for bit-addressed types; response_size ...
bool operator()(const SensorItem *lhs, const SensorItem *rhs) const
The write side of a ControllerDevice, owned by the writer entities through WriterEntity,...
void on_response(std::span< const uint8_t > request_pdu, std::span< const uint8_t > response_pdu) override
void set_write_options(modbus::CommandOptions options)
modbus::CommandOptions write_options() const
void on_error(std::span< const uint8_t > request_pdu, modbus::ExceptionCode exception_code) override
void warn_write_buffer_deprecated(const LogString *platform, uint16_t address)
Warn once per entity that filling the write_lambda buffer parameter is deprecated (the entity is now ...
bool send_raw_frame_deprecated(std::span< const uint8_t > frame)
Send a legacy raw frame (address + function code + data) to the frame's own address.
Gives a writer entity the write API of the WriterDevice it owns.
void set_write_options(modbus::CommandOptions options)
bool write_multiple_coils(uint16_t address, modbus::PackedBits bits)
void warn_write_buffer_deprecated_(const LogString *platform, uint16_t address)
bool queue_pdu(std::span< const uint8_t > pdu, modbus::CommandOptions options)
bool dispatched() const
Whether the lambda called a request helper since the last clear_dispatched_().
void set_controller_(ModbusController *controller)
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 queue_pdu(std::span< const uint8_t > pdu)
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)
uint8_t options
Range range
Definition msa3xx.h:0
bool value_type_is_float(SensorValueType v)
void number_to_payload(Container &data, int64_t value, SensorValueType value_type)
Append the Modbus register words for value to data.
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).
T get_data(const uint8_t *data, size_t buffer_offset)
Extract data from modbus response buffer.
constexpr uint16_t register_width_for(SensorValueType v)
Number of 16-bit registers a value of this type occupies (RAW counts as one register).
FunctionCode modbus_register_write_function(EntityType reg_type, bool multiple=false)
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.
FunctionCode modbus_register_read_function(EntityType reg_type)
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 .
bool is_entity_type_binary(EntityType type)
Coils and discrete inputs are the bit-addressed entity tables; the other types are 16-bit registers.
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)
const std::vector< uint8_t > & data
std::set< SensorItem *, SensorItemsComparator > SensorSet
RangeReuse
How an item relates to the register range built just before it (same register type,...
float payload_to_float(std::span< const uint8_t > data, const SensorItem &item, uint8_t offset)
Convert vector<uint8_t> response payload to float.
class ESPDEPRECATED("One-shot writes go through the entity write helpers (WriterDevice) or the modbus_client actions, and " "polling runs through PollingDevice. Removed in 2027.3.0", "2026.9.0") ModbusCommandItem bool offline_retry_due(uint16_t update_counter, uint16_t module_offline_at, uint16_t offline_skip_updates)
A single modbus command.
size_t size_t pos
Definition helpers.h:1123
To bit_cast(const From &src)
Convert data between types, without aliasing issues or undefined behaviour.
Definition helpers.h:89
STL namespace.
static void uint32_t
const SmallInlineBuffer< 8 > * custom_pdu
A custom range polls this PDU, referenced from the sensor that opened the range.