9static const char *
const TAG =
"modbus_server";
12static constexpr uint8_t MAX_REGISTERS_PER_VALUE = 4;
17static_assert(MAX_REGISTERS_PER_VALUE ==
sizeof(int64_t) /
sizeof(uint16_t),
18 "MAX_REGISTERS_PER_VALUE must match the register span of the widest encodable value");
22 if (address >= server_register->address &&
23 address <
static_cast<uint32_t>(server_register->address) + server_register->register_count) {
24 return server_register;
33 "Received read holding/input registers for device 0x%X. Start address: 0x%X. Number of registers: 0x%X.",
34 this->
address_, start_address, number_of_registers);
42 const uint32_t end_address =
static_cast<uint32_t>(start_address) + number_of_registers;
43 uint32_t current_address = start_address;
44 while (current_address < end_address) {
47 if (server_register ==
nullptr) {
51 ESP_LOGV(TAG,
"No register at 0x%04X; returning courtesy default %" PRIu16
".",
57 ESP_LOGW(TAG,
"No register at 0x%04X and courtesy default not allowed. Sending exception response.",
58 static_cast<uint16_t
>(current_address));
64 ESP_LOGW(TAG,
"Register at 0x%04X is not readable. Sending exception response.", server_register->
address);
71 const uint16_t value_offset =
static_cast<uint16_t
>(current_address - server_register->
address);
72 const uint16_t words_available =
static_cast<uint16_t
>(server_register->
register_count - value_offset);
73 const uint16_t words_wanted =
static_cast<uint16_t
>(end_address - current_address);
74 const uint16_t take = words_available < words_wanted ? words_available : words_wanted;
75 const bool clipped = value_offset != 0 || take != server_register->
register_count;
78 "Read clips the multi-register value at 0x%04X, which does not allow partial reads. "
79 "Sending exception response.",
84 const optional<int64_t> read_value = server_register->
read_lambda();
85 if (!read_value.has_value()) {
86 ESP_LOGW(TAG,
"Register read at 0x%04X declined to produce a value. Sending exception response.",
90 const int64_t value = *read_value;
92 ESP_LOGV(TAG,
"Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.",
100 if (value_offset + take > value_words.
size()) {
102 ESP_LOGE(TAG,
"Register at 0x%04X did not encode to %u registers", server_register->
address,
106 for (uint16_t i = 0; i < take; i++) {
107 registers.
push_back(value_words[value_offset + i]);
109 current_address += take;
118 ESP_LOGV(TAG,
"Received write registers for device 0x%X. Start address: 0x%X. Number of registers: 0x%zX.",
126 auto for_each_register =
127 [
this, start_address,
128 ®isters](
const std::function<bool(
ServerRegister *, uint16_t register_offset)> &callback) ->
bool {
129 uint16_t register_offset = 0;
130 for (
uint32_t current_address = start_address; current_address < start_address + registers.
size();) {
133 if (server_register->address == current_address) {
134 ok = callback(server_register, register_offset);
135 current_address += server_register->register_count;
136 register_offset += server_register->register_count;
153 if (!for_each_register([&precheck, ®isters](
ServerRegister *server_register, uint16_t register_offset) ->
bool {
157 if (!registers_to_number(registers.
data() + register_offset, registers.
size() - register_offset,
167 ESP_LOGV(TAG,
"Write request rejected before applying any register.");
173 if (!for_each_register([®isters](
ServerRegister *server_register, uint16_t register_offset) {
174 int64_t number = registers_to_number(registers.
data() + register_offset, registers.
size() - register_offset,
179 ESP_LOGW(TAG,
"A register write callback failed mid-sequence; earlier writes were already applied.");
189 if (server_bit->address ==
address) {
197 ESP_LOGV(TAG,
"Received read coils/discrete inputs for device 0x%X. Start address: 0x%X. Count: 0x%X.",
205 for (uint16_t i = 0; i < bits.
size(); i++) {
206 const uint16_t
address =
static_cast<uint16_t
>(start_address + i);
208 if (server_bit ==
nullptr || !server_bit->
read_lambda) {
209 ESP_LOGW(TAG,
"No readable bit at 0x%04X. Sending exception response.",
address);
213 if (!value.has_value()) {
214 ESP_LOGW(TAG,
"Bit read at 0x%04X declined to produce a value. Sending exception response.",
address);
223 ESP_LOGV(TAG,
"Received write coils for device 0x%X. Start address: 0x%X. Count: 0x%X.", this->
address_,
224 start_address, bits.
size());
233 for (uint16_t i = 0; i < bits.
size(); i++) {
234 const uint16_t
address =
static_cast<uint16_t
>(start_address + i);
236 if (server_bit ==
nullptr || !server_bit->
write_lambda) {
239 ESP_LOGV(TAG,
"No writable bit at 0x%04X; write request rejected before applying any bit.",
address);
249 for (uint16_t i = 0; i < bits.
size(); i++) {
250 const uint16_t
address =
static_cast<uint16_t
>(start_address + i);
252 if (server_bit ==
nullptr || !server_bit->
write_lambda) {
253 ESP_LOGE(TAG,
"Bit at 0x%04X unresolved between pre-flight and commit; aborting write.",
address);
257 ESP_LOGW(TAG,
"Bit write callback failed at 0x%04X mid-sequence; earlier writes were already applied.",
address);
268 " Server Courtesy Response:\n"
270 " Register Last Address: 0x%02X\n"
271 " Register Value: %" PRIu16,
273 this->server_courtesy_response_.register_last_address, this->server_courtesy_response_.register_value);
275#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
276 ESP_LOGCONFIG(TAG,
"server registers");
278 ESP_LOGCONFIG(TAG,
" Address=0x%02X value_type=%u register_count=%u", r->address,
279 static_cast<uint8_t
>(r->value_type), r->register_count);
281 ESP_LOGCONFIG(TAG,
"server bits");
283 ESP_LOGCONFIG(TAG,
" Address=0x%04X readable=%s writable=%s", b->address, b->read_lambda ?
"true" :
"false",
284 b->write_lambda ?
"true" :
"false");
Minimal static vector - saves memory by avoiding std::vector overhead.
void push_back(const T &value)
Mutable counterpart of PackedBits: set() writes bits in place (deliberately no proxy operator[]=).
void set(size_t bit, bool value)
Set or clear the given bit.
Read-only view of Modbus-packed bits: bit 0 of byte 0 is the first bit (LSB first),...
uint16_t size() const
Number of bits in the view.
void dump_config() override
modbus::ResponseStatus on_write_registers(uint16_t start_address, const modbus::RegisterValues ®isters) final
called when a modbus request (function code 0x06 or 0x10) was parsed without errors
modbus::ResponseStatus on_read_bits(uint16_t start_address, modbus::MutablePackedBits bits) final
called when a modbus request (function code 0x01 or 0x02) was parsed without errors; both are served ...
ServerRegister * find_containing_register_(uint32_t address) const
Find the registered value whose register span contains address, or nullptr if none does.
std::vector< ServerBit * > server_bits_
Collection of all server bits (coils/discrete inputs) for this component.
std::vector< ServerRegister * > server_registers_
Collection of all server registers for this component.
modbus::ResponseStatus on_read_registers(uint16_t start_address, uint16_t number_of_registers, modbus::RegisterValues ®isters) final
called when a modbus request (function code 0x03 or 0x04) was parsed without errors
ServerBit * find_bit_(uint16_t address) const
Find the registered bit at address, or nullptr if none is.
modbus::ResponseStatus on_write_coils(uint16_t start_address, modbus::PackedBits bits) final
called when a modbus request (function code 0x05 or 0x0F) was parsed without errors
ServerCourtesyResponse server_courtesy_response_
Server courtesy response.
A single bit in the server's coil/discrete-input table.
SensorValueType value_type
const char * format_value(int64_t value, char *buf, size_t buf_size) const
static constexpr size_t FORMAT_VALUE_BUF_SIZE
void number_to_payload(Container &data, int64_t value, SensorValueType value_type)
Append the Modbus register words for value to data.
std::optional< int64_t > registers_to_number(const uint16_t *registers, size_t count, SensorValueType sensor_value_type)
Reconstruct a number from register words (host byte order).
std::optional< ExceptionCode > ResponseStatus
uint16_t register_last_address