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