10static const char *
const TAG =
"modbus_controller";
19 "Modbus %s (address 0x%X): filling the write_lambda buffer parameter is deprecated; call a write helper / "
20 "queue_pdu() on the entity (item) instead. The buffer parameter is removed in 2027.3.0",
21 LOG_STR_ARG(platform),
address);
37static int trigger_address(std::span<const uint8_t> request_pdu) {
39 return addr.has_value() ? *addr : -1;
53 ESP_LOGW(TAG,
"Modbus error function code: 0x%X register %d exception: %d",
55 static_cast<uint8_t
>(exception_code));
61 this->dispatch_response_(request_pdu, response_pdu, std::nullopt);
66 this->dispatch_response_(request_pdu, {}, exception_code);
79 const int addr = trigger_address(request_pdu);
83 ESP_LOGW(TAG,
"Write not sent: function 0x%X register %d", fc, addr);
85 ESP_LOGD(TAG,
"Request not sent: function 0x%X register %d", fc, addr);
105 accepted = this->queue_pdu(std::span<const uint8_t>(*this->
range_.
custom_pdu), options);
107 accepted = this->read_entities(this->
range_.
register_type, this->range_.start_address, this->range_.register_count,
111 ESP_LOGV(TAG,
"Poll queued type=%u 0x%X %d",
static_cast<uint8_t
>(this->
range_.
register_type),
112 this->range_.start_address, this->range_.register_count);
121 sensor->parse_and_publish(
data);
125#pragma GCC diagnostic push
126#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
129 : modbus::ModbusClientDevice(parent,
address),
131 register_type_(
range.register_type),
132 start_address_(
range.start_address),
133 register_count_(
range.register_count),
134 function_code_(modbus::helpers::modbus_register_read_function(
range.register_type)),
135 controller_(&controller) {}
139 : modbus::ModbusClientDevice(parent,
address),
140 start_address_(sensor->start_address),
141 register_count_(sensor->entity_count()),
142 custom_pdu_(&sensor->custom_pdu),
143 controller_(&controller) {
146 if (!sensor->custom_pdu.empty())
147 this->function_code_ =
static_cast<FunctionCode>(sensor->custom_pdu.data()[0]);
148 this->sensors.insert(sensor);
155ModbusCommandItem::ModbusCommandItem(
const ModbusCommandItem &other)
156 : modbus::ModbusClientDevice(other.parent_, other.address_),
157 sensors(other.sensors),
158 on_data_func(other.on_data_func),
159 register_type_(other.register_type_),
160 start_address_(other.start_address_),
161 register_count_(other.register_count_),
162 function_code_(other.function_code_),
163 custom_pdu_(other.custom_pdu_),
164 controller_(other.controller_) {
166 this->
payload.set(other.payload.data(), other.payload.size());
169ModbusCommandItem::ModbusCommandItem(ModbusCommandItem &&other) noexcept
170 : modbus::ModbusClientDevice(other.parent_, other.address_),
171 sensors(std::move(other.sensors)),
172 on_data_func(std::move(other.on_data_func)),
173 payload(std::move(other.payload)),
174 register_type_(other.register_type_),
175 start_address_(other.start_address_),
176 register_count_(other.register_count_),
177 function_code_(other.function_code_),
178 custom_pdu_(other.custom_pdu_),
179 controller_(other.controller_) {
180 other.parent_ =
nullptr;
184void ModbusCommandItem::on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) {
185 if (this->controller_ !=
nullptr)
186 this->controller_->set_online(
true,
static_cast<int>(this->function_code_), this->start_address_);
188 if (this->on_data_func) {
189 this->on_data_func(this->register_type_, this->start_address_,
data);
190 }
else if (!this->sensors.empty()) {
194 for (
auto *sensor : this->sensors)
195 sensor->parse_and_publish(
data);
199 if (this->controller_ !=
nullptr)
200 this->controller_->unqueue_command(
this);
204void ModbusCommandItem::on_error(std::span<const uint8_t> request_pdu,
modbus::ExceptionCode exception_code) {
205 const uint8_t function_code = request_pdu.empty() ? 0 : request_pdu[0];
206 ESP_LOGW(TAG,
"Modbus error function code: 0x%X register 0x%X exception: %d", function_code, this->start_address_,
207 static_cast<uint8_t
>(exception_code));
208 if (this->controller_ !=
nullptr) {
209 this->controller_->set_online(
true, function_code, this->start_address_);
210 this->controller_->unqueue_command(
this);
215void ModbusCommandItem::on_not_sent(std::span<const uint8_t> request_pdu) {
218 ESP_LOGW(TAG,
"Write not sent: function 0x%X register 0x%X",
static_cast<uint8_t
>(this->function_code_),
219 this->start_address_);
221 if (this->controller_ !=
nullptr)
222 this->controller_->unqueue_command(
this);
227void ModbusCommandItem::on_sent(std::span<const uint8_t> request_pdu) {
228 if (this->controller_ ==
nullptr)
230 this->controller_->command_sent(
static_cast<int>(this->function_code_), this->start_address_);
237 uint8_t wire_address = this->address_;
238 if (this->function_code_ == FunctionCode::CUSTOM && !this->payload.empty())
239 wire_address = this->payload.data()[0];
240 if (wire_address == modbus::BROADCAST_ADDRESS)
241 this->controller_->unqueue_command(
this);
244bool ModbusCommandItem::on_no_response(std::span<const uint8_t> request_pdu) {
245 if (this->controller_ ==
nullptr)
247 this->controller_->increment_non_response_count();
248 if (this->controller_->can_send()) {
252 this->controller_->set_online(
false,
static_cast<int>(this->function_code_), this->start_address_);
253 this->controller_->unqueue_command(
this);
257#pragma GCC diagnostic pop
263 ESP_LOGW(TAG,
"Modbus device=%d back online", this->
address_);
272 ESP_LOGW(TAG,
"Modbus device=%d set offline", this->
address_);
280#pragma GCC diagnostic push
281#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
282void ModbusController::queue_command(ModbusCommandItem command) {
291 ESP_LOGW(TAG,
"Command refused by hub: type=0x%X address=0x%X",
static_cast<uint8_t
>(item->register_type()),
292 item->register_address());
293 item->pending_removal =
true;
297void ModbusController::unqueue_command(
const ModbusCommandItem *command) {
304 if (item.get() == command) {
305 item->pending_removal =
true;
312 this->one_shot_command_items_.remove_if(
313 [](
const std::unique_ptr<ModbusCommandItem> &item) {
return item->pending_removal; });
316#pragma GCC diagnostic pop
324 ESP_LOGV(TAG,
"Module offline - retrying");
329 if (!poll.queue(this->read_options_)) {
330 ESP_LOGD(TAG,
"Probe refused by hub for range 0x%X", poll.register_address());
334 ESP_LOGV(TAG,
"Module offline - skipping update");
342 ESP_LOGVV(TAG,
"Updating range 0x%X", poll.register_address());
345 if (!poll.queue(this->read_options_)) {
346 ESP_LOGD(TAG,
"Poll refused by hub for range 0x%X", poll.register_address());
360 bool can_join(
const SensorItem *curr)
const {
366 bool try_reuse_register(SensorItem *curr) {
367 const uint32_t range_end = this->range_end_();
368 if (curr->start_address != range_end - this->prev_->entity_count() ||
369 this->prev_->start_address + this->prev_->entity_count() != range_end ||
370 curr->entity_count() != this->prev_->entity_count() ||
371 curr->get_register_size() != this->prev_->get_register_size()) {
374 if (!place_offset(curr,
static_cast<uint32_t>(this->prev_->offset) + curr->offset_from_start_address))
376 ESP_LOGV(TAG,
"Re-use previous register 0x%X", curr->start_address);
380 bool try_extend(SensorItem *curr) {
381 const uint32_t range_end = this->range_end_();
382 const bool reachable =
384 ? curr->start_address >= range_end
385 : curr->start_address == range_end && (curr->addresses_bits() || !this->range_custom_size_);
388 const uint16_t gap =
static_cast<uint16_t
>(curr->start_address - range_end);
389 const uint32_t new_count = this->r_.register_count + gap + curr->entity_count();
390 const uint16_t max_quantity =
391 curr->addresses_bits() ? modbus::MAX_NUM_OF_COILS_TO_READ : modbus::MAX_NUM_OF_REGISTERS_TO_READ;
393 (curr->addresses_bits() ?
static_cast<uint32_t>(curr->start_address - this->r_.start_address)
394 :
static_cast<uint32_t>(this->range_bytes_) + gap * 2) +
395 curr->offset_from_start_address;
396 if (new_count > max_quantity || !place_offset(curr, prospective_offset)) {
399 if (!curr->addresses_bits())
400 this->range_bytes_ +=
static_cast<size_t>(gap) * 2;
401 this->range_bytes_ += curr->get_register_size();
402 this->range_custom_size_ = this->range_custom_size_ || has_custom_size(curr);
403 this->r_.register_count =
static_cast<uint16_t
>(new_count);
404 ESP_LOGV(TAG,
"Extend range to include 0x%X", curr->start_address);
408 bool try_cover(SensorItem *curr) {
409 if (!this->range_shared_ || this->range_forced_ || curr->start_address < this->r_.start_address ||
410 curr->start_address + curr->entity_count() > this->range_end_() || this->range_custom_size_ ||
411 has_custom_size(curr)) {
414 const uint32_t addr_delta = curr->start_address - this->r_.start_address;
415 if (!place_offset(curr, (curr->addresses_bits() ? addr_delta : addr_delta * 2) + curr->offset_from_start_address))
417 ESP_LOGV(TAG,
"Register 0x%X already covered by range 0x%X", curr->start_address, this->r_.start_address);
423 bool try_share(SensorItem *curr) {
424 if (!this->have_range_ || this->r_.register_type != curr->register_type ||
425 this->r_.start_address != curr->start_address) {
428 curr->offset = curr->offset_from_start_address;
429 this->r_.register_count = std::max(this->r_.register_count, curr->entity_count());
430 this->range_bytes_ = std::max(this->range_bytes_, curr->get_register_size());
431 this->range_custom_size_ = this->range_custom_size_ || has_custom_size(curr);
432 this->range_shared_ =
true;
433 this->range_forced_ = this->range_forced_ || curr->reuse_previous_range ==
RangeReuse::NEVER;
434 ESP_LOGV(TAG,
"Share range start 0x%X", curr->start_address);
438 bool always_declined(
const SensorItem *curr)
const {
440 this->r_.register_type == curr->register_type && curr->start_address != this->r_.start_address;
443 void open(SensorItem *curr) {
446 this->range_bytes_ = curr->get_register_size();
447 this->range_custom_size_ = has_custom_size(curr);
449 this->range_shared_ =
false;
450 curr->offset = curr->offset_from_start_address;
451 this->r_.start_address = curr->start_address;
452 this->r_.register_count = curr->entity_count();
453 this->r_.register_type = curr->register_type;
455 this->r_.custom_pdu = &curr->custom_pdu;
456 this->have_range_ =
true;
459 void record(SensorItem *curr) {
460 curr->range_start_address = this->r_.start_address;
461 this->r_.sensors.insert(curr);
466 if (!this->have_range_)
468 ESP_LOGV(TAG,
"Add range 0x%X %d", this->r_.start_address, this->r_.register_count);
469 this->ranges_.push_back(std::move(this->r_));
470 this->have_range_ =
false;
474 uint32_t range_end_()
const {
return this->r_.start_address + this->r_.register_count; }
476 static bool place_offset(SensorItem *curr,
uint32_t offset) {
477 if (offset > std::numeric_limits<uint8_t>::max())
479 curr->offset =
static_cast<uint8_t
>(offset);
482 static bool has_custom_size(
const SensorItem *item) {
483 return item->get_register_size() !=
static_cast<size_t>(item->entity_count()) * 2;
485 FixedVector<RegisterRange> &ranges_;
486 RegisterRange r_ = {};
487 bool have_range_ =
false;
488 bool range_forced_ =
false;
489 bool range_shared_ =
false;
490 size_t range_bytes_ = 0;
491 bool range_custom_size_ =
false;
492 SensorItem *prev_ =
nullptr;
499 ESP_LOGW(TAG,
"No sensors registered");
507 RangeBuilder builder(ranges);
509 ESP_LOGV(TAG,
"Register: 0x%X width=%u size=%zu offset=%u addr=%p", curr->start_address, curr->entity_count(),
510 curr->get_register_size(), curr->offset, curr);
511 bool join = builder.can_join(curr) &&
512 (builder.try_reuse_register(curr) || builder.try_extend(curr) || builder.try_cover(curr));
513 if (!join && builder.always_declined(curr)) {
514 ESP_LOGW(TAG,
"reuse_previous_range on 0x%X cannot join the previous range; starting a new range",
515 curr->start_address);
517 join = join || builder.try_share(curr);
520 builder.record(curr);
525 for (
auto &
range : ranges) {
532 "ModbusController:\n"
534 " Max Command Retries: %d\n"
535 " Offline Skip Updates: %d\n",
538#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
539 ESP_LOGCONFIG(TAG,
"sensormap");
541 ESP_LOGCONFIG(TAG,
" Sensor type=%u start=0x%X offset=0x%X width=%u size=%zu",
542 static_cast<uint8_t
>(it->register_type), it->start_address, it->offset, it->entity_count(),
543 it->get_register_size());
545 ESP_LOGCONFIG(TAG,
"ranges");
547 ESP_LOGCONFIG(TAG,
" Range type=%u start=0x%X count=%d",
static_cast<uint8_t
>(it.register_type()),
548 it.register_address(), it.register_count());
553#pragma GCC diagnostic push
554#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
555void ModbusController::on_write_register_response(
EntityType register_type, uint16_t start_address,
556 std::span<const uint8_t>
data) {
558 if (
data.size() >= 3) {
562 ESP_LOGV(TAG,
"Command ACK (short payload, %zu bytes)",
data.size());
566ModbusCommandItem ModbusCommandItem::create_read_command(
567 ModbusController *modbusdevice,
EntityType register_type, uint16_t start_address, uint16_t register_count,
568 std::function<
void(
EntityType register_type, uint16_t start_address, std::span<const uint8_t>
data)> &&handler) {
569 ModbusCommandItem cmd(*modbusdevice, modbusdevice->hub(), modbusdevice->device_address());
572 cmd.on_data_func = std::move(handler);
576ModbusCommandItem ModbusCommandItem::create_write_multiple_command(ModbusController *modbusdevice,
577 uint16_t start_address, uint16_t register_count,
578 const std::vector<uint16_t> &values) {
579 ModbusCommandItem cmd(*modbusdevice, modbusdevice->hub(), modbusdevice->device_address());
581 cmd.on_data_func = [modbusdevice](
EntityType register_type, uint16_t start_address, std::span<const uint8_t>
data) {
582 modbusdevice->on_write_register_response(register_type, start_address,
data);
584 uint8_t *p = cmd.payload.init(values.size() * 2);
585 for (
auto v : values) {
587 *p++ = decoded_value[0];
588 *p++ = decoded_value[1];
593ModbusCommandItem ModbusCommandItem::create_write_single_coil(ModbusController *modbusdevice, uint16_t
address,
595 ModbusCommandItem cmd(*modbusdevice, modbusdevice->hub(), modbusdevice->device_address());
597 cmd.on_data_func = [modbusdevice](
EntityType register_type, uint16_t start_address, std::span<const uint8_t>
data) {
598 modbusdevice->on_write_register_response(register_type, start_address,
data);
600 uint8_t *p = cmd.payload.init(2);
601 p[0] = value ? 0xFF : 0;
606ModbusCommandItem ModbusCommandItem::create_write_multiple_coils(ModbusController *modbusdevice, uint16_t start_address,
607 const std::vector<bool> &values) {
608 ModbusCommandItem cmd(*modbusdevice, modbusdevice->hub(), modbusdevice->device_address());
610 cmd.on_data_func = [modbusdevice](
EntityType register_type, uint16_t start_address, std::span<const uint8_t>
data) {
611 modbusdevice->on_write_register_response(register_type, start_address,
data);
617 uint8_t *p = cmd.payload.init(byte_count);
618 memset(p, 0, byte_count);
619 modbus::MutablePackedBits bits(std::span<uint8_t>(p, byte_count),
static_cast<uint16_t
>(values.size()));
620 for (
size_t i = 0; i != values.size(); i++) {
627ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusController *modbusdevice, uint16_t start_address,
629 ModbusCommandItem cmd(*modbusdevice, modbusdevice->hub(), modbusdevice->device_address());
631 cmd.on_data_func = [modbusdevice](
EntityType register_type, uint16_t start_address, std::span<const uint8_t>
data) {
632 modbusdevice->on_write_register_response(register_type, start_address,
data);
636 uint8_t *p = cmd.payload.init(2);
637 p[0] = decoded_value[0];
638 p[1] = decoded_value[1];
642ModbusCommandItem ModbusCommandItem::create_custom_command(
643 ModbusController *modbusdevice,
const std::vector<uint8_t> &values,
644 std::function<
void(
EntityType register_type, uint16_t start_address, std::span<const uint8_t>
data)> &&handler) {
645 ModbusCommandItem cmd(*modbusdevice, modbusdevice->hub(), modbusdevice->device_address());
646 cmd.function_code_ = FunctionCode::CUSTOM;
647 if (handler ==
nullptr) {
648 cmd.on_data_func = [](
EntityType register_type, uint16_t start_address, std::span<const uint8_t>
data) {
649 ESP_LOGI(TAG,
"Custom Command sent");
652 cmd.on_data_func = handler;
654 cmd.payload.set(values.data(), values.size());
659ModbusCommandItem ModbusCommandItem::create_custom_command(
660 ModbusController *modbusdevice,
const std::vector<uint16_t> &values,
661 std::function<
void(
EntityType register_type, uint16_t start_address, std::span<const uint8_t>
data)> &&handler) {
662 ModbusCommandItem cmd(*modbusdevice, modbusdevice->hub(), modbusdevice->device_address());
663 cmd.function_code_ = FunctionCode::CUSTOM;
664 if (handler ==
nullptr) {
665 cmd.on_data_func = [](
EntityType register_type, uint16_t start_address, std::span<const uint8_t>
data) {
666 ESP_LOGI(TAG,
"Custom Command sent");
669 cmd.on_data_func = handler;
671 uint8_t *p = cmd.payload.init(values.size() * 2);
672 for (
auto v : values) {
673 *p++ = (v >> 8) & 0xFF;
680bool ModbusCommandItem::send(modbus::CommandOptions
options) {
683 if (this->custom_pdu_ !=
nullptr) {
686 accepted = modbus::ModbusClientDevice::queue_pdu(std::span<const uint8_t>(*this->custom_pdu_),
options);
687 }
else if (this->function_code_ != FunctionCode::CUSTOM) {
689 this->function_code_, this->start_address_, this->register_count_,
690 this->payload.empty() ?
nullptr : this->payload.data(), this->payload.size()),
696 std::span<const uint8_t> frame = this->payload;
698 ESP_LOGW(TAG,
"Empty custom command frame, not sent");
701 accepted = this->parent_->queue_pdu(frame[0], frame.subspan(1),
this,
options);
706 ESP_LOGV(TAG,
"Command queued %d 0x%X %d", uint8_t(this->function_code_), this->start_address_,
707 this->register_count_);
711#pragma GCC diagnostic pop
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
void set_parent(ModbusClientHub *parent)
void set_address(uint8_t address)
ModbusClientHub * parent_
uint16_t uint16_t uint8_t const uint8_t * payload
bool queue_pdu(uint8_t address, std::span< const uint8_t > pdu, ModbusClientDevice *device=nullptr, CommandOptions options={})
Queue a request.
void clear_tx_queue_for_address(uint8_t address)
The shared feedback half of a controller-owned hub device: online/offline tracking,...
ModbusController * controller_
void set_controller(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 write_buffer_deprecated_warned_
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).
uint8_t device_address() const
std::list< std::unique_ptr< ModbusCommandItem > > one_shot_command_items_
Dynamically queued one-shot commands (writes, custom commands).
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.
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.
bool can_send()
Whether more retries are allowed before the device is considered offline.
void sweep_completed_one_shots_()
Erases one-shot commands flagged by unqueue_command().
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)
bool module_offline_
if module didn't respond the last command
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.
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 dump_config() override
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.
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)
void on_response(std::span< const uint8_t > request_pdu, std::span< const uint8_t > response_pdu) override
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.
T get_data(const uint8_t *data, size_t buffer_offset)
Extract data from modbus response buffer.
bool is_function_code_write(uint8_t function_code)
PduBuffer create_client_pdu(FunctionCode function_code, uint16_t start_address, uint16_t number_of_entities, const uint8_t *values, size_t values_len)
Create a modbus client pdu for reading/writing single/multiple coils/register/inputs.
std::span< const uint8_t > server_pdu_payload(std::span< const uint8_t > pdu)
Returns the payload portion of a server response PDU: the bytes after the function code,...
uint8_t pdu_function_code(std::span< const uint8_t > pdu)
Function code of a PDU, exception flag masked; 0 for an empty PDU.
FunctionCode modbus_register_read_function(EntityType reg_type)
std::optional< uint16_t > client_pdu_start_address(std::span< const uint8_t > pdu)
Start address of a standard client request PDU ([fc, addr_hi, addr_lo, ...]).
const std::vector< uint8_t > & data
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.
@ WRITE_MULTIPLE_REGISTERS
constexpr size_t packed_bit_bytes(size_t bits)
Bits pack 8 per data byte, rounded up to whole bytes.
constexpr std::array< uint8_t, sizeof(T)> decode_value(T val)
Decode a value into its constituent bytes (from most to least significant).
const SmallInlineBuffer< 8 > * custom_pdu
A custom range polls this PDU, referenced from the sensor that opened the range.
modbus::EntityType register_type