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);
36 const uint32_t end_address =
static_cast<uint32_t>(start_address) + number_of_registers;
37 uint32_t current_address = start_address;
38 while (current_address < end_address) {
41 if (server_register ==
nullptr) {
45 ESP_LOGV(TAG,
"No register at 0x%04X; returning courtesy default %" PRIu16
".",
51 ESP_LOGW(TAG,
"No register at 0x%04X and courtesy default not allowed. Sending exception response.",
52 static_cast<uint16_t
>(current_address));
58 ESP_LOGW(TAG,
"Register at 0x%04X is not readable. Sending exception response.", server_register->
address);
65 const uint16_t value_offset =
static_cast<uint16_t
>(current_address - server_register->
address);
66 const uint16_t words_available =
static_cast<uint16_t
>(server_register->
register_count - value_offset);
67 const uint16_t words_wanted =
static_cast<uint16_t
>(end_address - current_address);
68 const uint16_t take = words_available < words_wanted ? words_available : words_wanted;
69 const bool clipped = value_offset != 0 || take != server_register->
register_count;
72 "Read clips the multi-register value at 0x%04X, which does not allow partial reads. "
73 "Sending exception response.",
80 ESP_LOGV(TAG,
"Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.",
88 if (value_offset + take > value_words.
size()) {
90 ESP_LOGE(TAG,
"Register at 0x%04X did not encode to %u registers", server_register->
address,
94 for (uint16_t i = 0; i < take; i++) {
95 registers.
push_back(value_words[value_offset + i]);
97 current_address += take;
106 ESP_LOGV(TAG,
"Received write registers for device 0x%X. Start address: 0x%X. Number of registers: 0x%zX.",
109 auto for_each_register =
110 [
this, start_address,
111 ®isters](
const std::function<bool(
ServerRegister *, uint16_t register_offset)> &callback) ->
bool {
112 uint16_t register_offset = 0;
113 for (
uint32_t current_address = start_address; current_address < start_address + registers.
size();) {
116 if (server_register->address == current_address) {
117 ok = callback(server_register, register_offset);
118 current_address += server_register->register_count;
119 register_offset += server_register->register_count;
136 if (!for_each_register([&precheck, ®isters](
ServerRegister *server_register, uint16_t register_offset) ->
bool {
140 if (!registers_to_number(registers.
data() + register_offset, registers.
size() - register_offset,
148 ESP_LOGW(TAG,
"Write request rejected before applying any register. Sending exception response.");
154 if (!for_each_register([®isters](
ServerRegister *server_register, uint16_t register_offset) {
155 int64_t number = registers_to_number(registers.
data() + register_offset, registers.
size() - register_offset,
160 ESP_LOGW(TAG,
"A register write callback failed mid-sequence; earlier writes were already applied.");
172 " Server Courtesy Response:\n"
174 " Register Last Address: 0x%02X\n"
175 " Register Value: %" PRIu16,
177 this->server_courtesy_response_.register_last_address, this->server_courtesy_response_.register_value);
179#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
180 ESP_LOGCONFIG(TAG,
"server registers");
182 ESP_LOGCONFIG(TAG,
" Address=0x%02X value_type=%u register_count=%u", r->address,
183 static_cast<uint8_t
>(r->value_type), r->register_count);
Minimal static vector - saves memory by avoiding std::vector overhead.
void push_back(const T &value)
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
ServerRegister * find_containing_register_(uint32_t address) const
Find the registered value whose register span contains address, or nullptr if none does.
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
ServerCourtesyResponse server_courtesy_response_
Server courtesy response.
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< ModbusExceptionCode > ResponseStatus
uint16_t register_last_address