ESPHome 2025.5.0
Loading...
Searching...
No Matches
modbus.cpp
Go to the documentation of this file.
1#include "modbus.h"
2#include "esphome/core/log.h"
5
6namespace esphome {
7namespace modbus {
8
9static const char *const TAG = "modbus";
10
12 if (this->flow_control_pin_ != nullptr) {
13 this->flow_control_pin_->setup();
14 }
15}
17 const uint32_t now = App.get_loop_component_start_time();
18
19 while (this->available()) {
20 uint8_t byte;
21 this->read_byte(&byte);
22 if (this->parse_modbus_byte_(byte)) {
23 this->last_modbus_byte_ = now;
24 } else {
25 size_t at = this->rx_buffer_.size();
26 if (at > 0) {
27 ESP_LOGV(TAG, "Clearing buffer of %d bytes - parse failed", at);
28 this->rx_buffer_.clear();
29 }
30 }
31 }
32
33 if (now - this->last_modbus_byte_ > 50) {
34 size_t at = this->rx_buffer_.size();
35 if (at > 0) {
36 ESP_LOGV(TAG, "Clearing buffer of %d bytes - timeout", at);
37 this->rx_buffer_.clear();
38 }
39
40 // stop blocking new send commands after sent_wait_time_ ms after response received
41 if (now - this->last_send_ > send_wait_time_) {
42 if (waiting_for_response > 0) {
43 ESP_LOGV(TAG, "Stop waiting for response from %d", waiting_for_response);
44 }
46 }
47 }
48}
49
50bool Modbus::parse_modbus_byte_(uint8_t byte) {
51 size_t at = this->rx_buffer_.size();
52 this->rx_buffer_.push_back(byte);
53 const uint8_t *raw = &this->rx_buffer_[0];
54 ESP_LOGVV(TAG, "Modbus received Byte %d (0X%x)", byte, byte);
55 // Byte 0: modbus address (match all)
56 if (at == 0)
57 return true;
58 uint8_t address = raw[0];
59 uint8_t function_code = raw[1];
60 // Byte 2: Size (with modbus rtu function code 4/3)
61 // See also https://en.wikipedia.org/wiki/Modbus
62 if (at == 2)
63 return true;
64
65 uint8_t data_len = raw[2];
66 uint8_t data_offset = 3;
67
68 // Per https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf Ch 5 User-Defined function codes
69 if (((function_code >= 65) && (function_code <= 72)) || ((function_code >= 100) && (function_code <= 110))) {
70 // Handle user-defined function, since we don't know how big this ought to be,
71 // ideally we should delegate the entire length detection to whatever handler is
72 // installed, but wait, there is the CRC, and if we get a hit there is a good
73 // chance that this is a complete message ... admittedly there is a small chance is
74 // isn't but that is quite small given the purpose of the CRC in the first place
75
76 // Fewer than 2 bytes can't calc CRC
77 if (at < 2)
78 return true;
79
80 data_len = at - 2;
81 data_offset = 1;
82
83 uint16_t computed_crc = crc16(raw, data_offset + data_len);
84 uint16_t remote_crc = uint16_t(raw[data_offset + data_len]) | (uint16_t(raw[data_offset + data_len + 1]) << 8);
85
86 if (computed_crc != remote_crc)
87 return true;
88
89 ESP_LOGD(TAG, "Modbus user-defined function %02X found", function_code);
90
91 } else {
92 // data starts at 2 and length is 4 for read registers commands
93 if (this->role == ModbusRole::SERVER && (function_code == 0x3 || function_code == 0x4)) {
94 data_offset = 2;
95 data_len = 4;
96 }
97
98 // the response for write command mirrors the requests and data starts at offset 2 instead of 3 for read commands
99 if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) {
100 data_offset = 2;
101 data_len = 4;
102 }
103
104 // Error ( msb indicates error )
105 // response format: Byte[0] = device address, Byte[1] function code | 0x80 , Byte[2] exception code, Byte[3-4] crc
106 if ((function_code & 0x80) == 0x80) {
107 data_offset = 2;
108 data_len = 1;
109 }
110
111 // Byte data_offset..data_offset+data_len-1: Data
112 if (at < data_offset + data_len)
113 return true;
114
115 // Byte 3+data_len: CRC_LO (over all bytes)
116 if (at == data_offset + data_len)
117 return true;
118
119 // Byte data_offset+len+1: CRC_HI (over all bytes)
120 uint16_t computed_crc = crc16(raw, data_offset + data_len);
121 uint16_t remote_crc = uint16_t(raw[data_offset + data_len]) | (uint16_t(raw[data_offset + data_len + 1]) << 8);
122 if (computed_crc != remote_crc) {
123 if (this->disable_crc_) {
124 ESP_LOGD(TAG, "Modbus CRC Check failed, but ignored! %02X!=%02X", computed_crc, remote_crc);
125 } else {
126 ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc);
127 return false;
128 }
129 }
130 }
131 std::vector<uint8_t> data(this->rx_buffer_.begin() + data_offset, this->rx_buffer_.begin() + data_offset + data_len);
132 bool found = false;
133 for (auto *device : this->devices_) {
134 if (device->address_ == address) {
135 // Is it an error response?
136 if ((function_code & 0x80) == 0x80) {
137 ESP_LOGD(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]);
138 if (waiting_for_response != 0) {
139 device->on_modbus_error(function_code & 0x7F, raw[2]);
140 } else {
141 // Ignore modbus exception not related to a pending command
142 ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response");
143 }
144 } else if (this->role == ModbusRole::SERVER && (function_code == 0x3 || function_code == 0x4)) {
145 device->on_modbus_read_registers(function_code, uint16_t(data[1]) | (uint16_t(data[0]) << 8),
146 uint16_t(data[3]) | (uint16_t(data[2]) << 8));
147 } else {
148 device->on_modbus_data(data);
149 }
150 found = true;
151 }
152 }
154
155 if (!found) {
156 ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X! ", address);
157 }
158
159 // reset buffer
160 ESP_LOGV(TAG, "Clearing buffer of %d bytes - parse succeeded", at);
161 this->rx_buffer_.clear();
162 return true;
163}
164
166 ESP_LOGCONFIG(TAG, "Modbus:");
167 LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
168 ESP_LOGCONFIG(TAG, " Send Wait Time: %d ms", this->send_wait_time_);
169 ESP_LOGCONFIG(TAG, " CRC Disabled: %s", YESNO(this->disable_crc_));
170}
172 // After UART bus
173 return setup_priority::BUS - 1.0f;
174}
175
176void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities,
177 uint8_t payload_len, const uint8_t *payload) {
178 static const size_t MAX_VALUES = 128;
179
180 // Only check max number of registers for standard function codes
181 // Some devices use non standard codes like 0x43
182 if (number_of_entities > MAX_VALUES && function_code <= 0x10) {
183 ESP_LOGE(TAG, "send too many values %d max=%zu", number_of_entities, MAX_VALUES);
184 return;
185 }
186
187 std::vector<uint8_t> data;
188 data.push_back(address);
189 data.push_back(function_code);
190 if (this->role == ModbusRole::CLIENT) {
191 data.push_back(start_address >> 8);
192 data.push_back(start_address >> 0);
193 if (function_code != 0x5 && function_code != 0x6) {
194 data.push_back(number_of_entities >> 8);
195 data.push_back(number_of_entities >> 0);
196 }
197 }
198
199 if (payload != nullptr) {
200 if (this->role == ModbusRole::SERVER || function_code == 0xF || function_code == 0x10) { // Write multiple
201 data.push_back(payload_len); // Byte count is required for write
202 } else {
203 payload_len = 2; // Write single register or coil
204 }
205 for (int i = 0; i < payload_len; i++) {
206 data.push_back(payload[i]);
207 }
208 }
209
210 auto crc = crc16(data.data(), data.size());
211 data.push_back(crc >> 0);
212 data.push_back(crc >> 8);
213
214 if (this->flow_control_pin_ != nullptr)
215 this->flow_control_pin_->digital_write(true);
216
217 this->write_array(data);
218 this->flush();
219
220 if (this->flow_control_pin_ != nullptr)
221 this->flow_control_pin_->digital_write(false);
223 last_send_ = millis();
224 ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str());
225}
226
227// Helper function for lambdas
228// Send raw command. Except CRC everything must be contained in payload
229void Modbus::send_raw(const std::vector<uint8_t> &payload) {
230 if (payload.empty()) {
231 return;
232 }
233
234 if (this->flow_control_pin_ != nullptr)
235 this->flow_control_pin_->digital_write(true);
236
237 auto crc = crc16(payload.data(), payload.size());
238 this->write_array(payload);
239 this->write_byte(crc & 0xFF);
240 this->write_byte((crc >> 8) & 0xFF);
241 this->flush();
242 if (this->flow_control_pin_ != nullptr)
243 this->flow_control_pin_->digital_write(false);
244 waiting_for_response = payload[0];
245 ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str());
246 last_send_ = millis();
247}
248
249} // namespace modbus
250} // namespace esphome
uint8_t address
Definition bl0906.h:4
uint8_t raw[35]
Definition bl0939.h:0
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
virtual void setup()=0
virtual void digital_write(bool value)=0
bool parse_modbus_byte_(uint8_t byte)
Definition modbus.cpp:50
void send_raw(const std::vector< uint8_t > &payload)
Definition modbus.cpp:229
void setup() override
Definition modbus.cpp:11
uint16_t send_wait_time_
Definition modbus.h:47
uint8_t waiting_for_response
Definition modbus.h:37
uint32_t last_modbus_byte_
Definition modbus.h:50
GPIOPin * flow_control_pin_
Definition modbus.h:44
std::vector< ModbusDevice * > devices_
Definition modbus.h:52
void loop() override
Definition modbus.cpp:16
float get_setup_priority() const override
Definition modbus.cpp:171
void dump_config() override
Definition modbus.cpp:165
std::vector< uint8_t > rx_buffer_
Definition modbus.h:49
void send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len=0, const uint8_t *payload=nullptr)
Definition modbus.cpp:176
bool read_byte(uint8_t *data)
Definition uart.h:29
void write_byte(uint8_t data)
Definition uart.h:19
void write_array(const uint8_t *data, size_t len)
Definition uart.h:21
const float BUS
For communication buses like i2c/spi.
Definition component.cpp:16
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t reverse_poly, bool refin, bool refout)
Calculate a CRC-16 checksum of data with size len.
Definition helpers.cpp:112
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27
Application App
Global storage of Application pointer - only one Application can exist.
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
Definition helpers.cpp:372