ESPHome 2026.5.0b1
Loading...
Searching...
No Matches
modbus_server.cpp
Go to the documentation of this file.
1#include "modbus_server.h"
3#include "esphome/core/log.h"
4
8
9static const char *const TAG = "modbus_server";
10
11void ModbusServer::on_modbus_read_registers(uint8_t function_code, uint16_t start_address,
12 uint16_t number_of_registers) {
13 ESP_LOGV(TAG,
14 "Received read holding/input registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
15 "0x%X.",
16 this->address_, function_code, start_address, number_of_registers);
17
18 if (number_of_registers == 0 || number_of_registers > modbus::MAX_NUM_OF_REGISTERS_TO_READ) {
19 ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers);
21 return;
22 }
23
24 std::vector<uint16_t> sixteen_bit_response;
25 for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
26 bool found = false;
27 for (auto *server_register : this->server_registers_) {
28 if (server_register->address == current_address) {
29 if (!server_register->read_lambda) {
30 break;
31 }
32 int64_t value = server_register->read_lambda();
33 ESP_LOGV(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.",
34 server_register->address, static_cast<size_t>(server_register->value_type),
35 server_register->register_count, server_register->format_value(value).c_str());
36
37 std::vector<uint16_t> payload;
38 payload.reserve(server_register->register_count * 2);
39 modbus::helpers::number_to_payload(payload, value, server_register->value_type);
40 sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
41 current_address += server_register->register_count;
42 found = true;
43 break;
44 }
45 }
46
47 if (!found) {
49 (current_address <= this->server_courtesy_response_.register_last_address)) {
50 ESP_LOGV(TAG,
51 "Could not match any register to address 0x%02X, but default allowed. "
52 "Returning default value: %d.",
53 current_address, this->server_courtesy_response_.register_value);
54 sixteen_bit_response.push_back(this->server_courtesy_response_.register_value);
55 current_address += 1; // Just increment by 1, as the default response is a single register
56 } else {
57 ESP_LOGW(TAG,
58 "Could not match any register to address 0x%02X and default not allowed. Sending exception response.",
59 current_address);
61 return;
62 }
63 }
64 }
65
66 std::vector<uint8_t> response;
67 for (auto v : sixteen_bit_response) {
68 auto decoded_value = decode_value(v);
69 response.push_back(decoded_value[0]);
70 response.push_back(decoded_value[1]);
71 }
72
73 this->send(function_code, start_address, number_of_registers, response.size(), response.data());
74}
75
76void ModbusServer::on_modbus_write_registers(uint8_t function_code, const std::vector<uint8_t> &data) {
77 uint16_t number_of_registers;
78 uint16_t payload_offset;
79
81 if (data.size() < 5) {
82 ESP_LOGW(TAG, "Write multiple registers data too short (%zu bytes)", data.size());
84 return;
85 }
86 number_of_registers = uint16_t(data[3]) | (uint16_t(data[2]) << 8);
87 if (number_of_registers == 0 || number_of_registers > modbus::MAX_NUM_OF_REGISTERS_TO_WRITE) {
88 ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers);
90 return;
91 }
92 uint16_t payload_size = data[4];
93 if (payload_size != number_of_registers * 2) {
94 ESP_LOGW(TAG, "Payload size of %d bytes is not 2 times the number of registers (%d). Sending exception response.",
95 payload_size, number_of_registers);
97 return;
98 }
99 if (data.size() < 5 + payload_size) {
100 ESP_LOGW(TAG, "Write multiple registers payload truncated (%zu bytes, expected %u)", data.size(),
101 5 + payload_size);
103 return;
104 }
105 payload_offset = 5;
106 } else if (function_code == ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
107 if (data.size() < 4) {
108 ESP_LOGW(TAG, "Write single register data too short (%zu bytes)", data.size());
110 return;
111 }
112 number_of_registers = 1;
113 payload_offset = 2;
114 } else {
115 ESP_LOGW(TAG, "Invalid function code 0x%X. Sending exception response.", function_code);
117 return;
118 }
119
120 uint16_t start_address = uint16_t(data[1]) | (uint16_t(data[0]) << 8);
121 ESP_LOGD(TAG,
122 "Received write holding registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
123 "0x%X.",
124 this->address_, function_code, start_address, number_of_registers);
125
126 auto for_each_register = [this, start_address, number_of_registers, payload_offset](
127 const std::function<bool(ServerRegister *, uint16_t offset)> &callback) -> bool {
128 uint16_t offset = payload_offset;
129 for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
130 bool ok = false;
131 for (auto *server_register : this->server_registers_) {
132 if (server_register->address == current_address) {
133 ok = callback(server_register, offset);
134 current_address += server_register->register_count;
135 offset += server_register->register_count * sizeof(uint16_t);
136 break;
137 }
138 }
139
140 if (!ok) {
141 return false;
142 }
143 }
144 return true;
145 };
146
147 // check all registers are writable before writing to any of them:
148 if (!for_each_register([](ServerRegister *server_register, uint16_t offset) -> bool {
149 return server_register->write_lambda != nullptr;
150 })) {
152 return;
153 }
154
155 // Actually write to the registers:
156 if (!for_each_register([&data](ServerRegister *server_register, uint16_t offset) {
157 int64_t number = modbus::helpers::payload_to_number(data, server_register->value_type, offset, 0xFFFFFFFF);
158 return server_register->write_lambda(number);
159 })) {
161 return;
162 }
163
164 std::vector<uint8_t> response;
165 response.reserve(6);
166 response.push_back(this->address_);
167 response.push_back(function_code);
168 response.insert(response.end(), data.begin(), data.begin() + 4);
169 this->send_raw(response);
170}
171
173 ESP_LOGCONFIG(TAG,
174 "ModbusServer:\n"
175 " Address: 0x%02X\n"
176 " Server Courtesy Response:\n"
177 " Enabled: %s\n"
178 " Register Last Address: 0x%02X\n"
179 " Register Value: %" PRIu16,
180 this->address_, this->server_courtesy_response_.enabled ? "true" : "false",
181 this->server_courtesy_response_.register_last_address, this->server_courtesy_response_.register_value);
182
183#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
184 ESP_LOGCONFIG(TAG, "server registers");
185 for (auto &r : this->server_registers_) {
186 ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%u register_count=%u", r->address,
187 static_cast<uint8_t>(r->value_type), r->register_count);
188 }
189#endif
190}
191
192} // namespace esphome::modbus_server
void send_raw(const std::vector< uint8_t > &payload)
Definition modbus.h:103
void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len=0, const uint8_t *payload=nullptr)
Definition modbus.h:99
void send_error(uint8_t function_code, ModbusExceptionCode exception_code)
Definition modbus.h:104
void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final
called when a modbus request (function code 0x03 or 0x04) was parsed without errors
void on_modbus_write_registers(uint8_t function_code, const std::vector< uint8_t > &data) final
called when a modbus request (function code 0x06 or 0x10) was parsed without errors
std::vector< ServerRegister * > server_registers_
Collection of all server registers for this component.
ServerCourtesyResponse server_courtesy_response_
Server courtesy response.
int64_t payload_to_number(const std::vector< uint8_t > &data, SensorValueType sensor_value_type, uint8_t offset, uint32_t bitmask)
Convert vector<uint8_t> response payload to number.
void number_to_payload(std::vector< uint16_t > &data, int64_t value, SensorValueType value_type)
Convert float value to vector<uint16_t> suitable for sending.
const uint8_t MAX_NUM_OF_REGISTERS_TO_READ
const uint8_t MAX_NUM_OF_REGISTERS_TO_WRITE
constexpr std::array< uint8_t, sizeof(T)> decode_value(T val)
Decode a value into its constituent bytes (from most to least significant).
Definition helpers.h:888
uint32_t payload_size()