Reduce modbus heap alloc

This commit is contained in:
J. Nick Koston 2025-07-04 21:51:52 -05:00
parent 58b4e7dab2
commit ab9287e959
No known key found for this signature in database
5 changed files with 64 additions and 37 deletions

View File

@ -204,49 +204,82 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
return; return;
} }
std::vector<uint8_t> data; // Calculate the expected message size
data.push_back(address); size_t msg_size = 4; // address + function + CRC(2)
data.push_back(function_code);
if (this->role == ModbusRole::CLIENT) { if (this->role == ModbusRole::CLIENT) {
data.push_back(start_address >> 8); msg_size += 2; // start_address
data.push_back(start_address >> 0);
if (function_code != 0x5 && function_code != 0x6) { if (function_code != 0x5 && function_code != 0x6) {
data.push_back(number_of_entities >> 8); msg_size += 2; // number_of_entities
data.push_back(number_of_entities >> 0); }
}
if (payload != nullptr) {
if (this->role == ModbusRole::SERVER || function_code == 0xF || function_code == 0x10) {
msg_size += 1 + payload_len; // byte count + payload
} else {
msg_size += 2; // single register value
}
}
// Use stack buffer for small messages (most common case)
static constexpr size_t STACK_BUFFER_SIZE = 64;
uint8_t stack_buffer[STACK_BUFFER_SIZE];
std::vector<uint8_t> heap_buffer;
uint8_t *data;
if (msg_size <= STACK_BUFFER_SIZE) {
data = stack_buffer;
} else {
heap_buffer.resize(msg_size);
data = heap_buffer.data();
}
// Build the message
size_t pos = 0;
data[pos++] = address;
data[pos++] = function_code;
if (this->role == ModbusRole::CLIENT) {
data[pos++] = start_address >> 8;
data[pos++] = start_address >> 0;
if (function_code != 0x5 && function_code != 0x6) {
data[pos++] = number_of_entities >> 8;
data[pos++] = number_of_entities >> 0;
} }
} }
if (payload != nullptr) { if (payload != nullptr) {
if (this->role == ModbusRole::SERVER || function_code == 0xF || function_code == 0x10) { // Write multiple if (this->role == ModbusRole::SERVER || function_code == 0xF || function_code == 0x10) { // Write multiple
data.push_back(payload_len); // Byte count is required for write data[pos++] = payload_len; // Byte count is required for write
} else { } else {
payload_len = 2; // Write single register or coil payload_len = 2; // Write single register or coil
} }
for (int i = 0; i < payload_len; i++) { for (int i = 0; i < payload_len; i++) {
data.push_back(payload[i]); data[pos++] = payload[i];
} }
} }
auto crc = crc16(data.data(), data.size()); auto crc = crc16(data, pos);
data.push_back(crc >> 0); data[pos++] = crc >> 0;
data.push_back(crc >> 8); data[pos++] = crc >> 8;
if (this->flow_control_pin_ != nullptr) if (this->flow_control_pin_ != nullptr)
this->flow_control_pin_->digital_write(true); this->flow_control_pin_->digital_write(true);
this->write_array(data); this->write_array(data, pos);
this->flush(); this->flush();
if (this->flow_control_pin_ != nullptr) if (this->flow_control_pin_ != nullptr)
this->flow_control_pin_->digital_write(false); this->flow_control_pin_->digital_write(false);
waiting_for_response = address; waiting_for_response = address;
last_send_ = millis(); last_send_ = millis();
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str()); ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data, pos).c_str());
} }
// Helper function for lambdas // Helper function for lambdas
// Send raw command. Except CRC everything must be contained in payload // Send raw command. Except CRC everything must be contained in payload
void Modbus::send_raw(const std::vector<uint8_t> &payload) { void Modbus::send_raw(const std::vector<uint8_t> &payload) { send_raw(std::span<const uint8_t>(payload)); }
void Modbus::send_raw(std::span<const uint8_t> payload) {
if (payload.empty()) { if (payload.empty()) {
return; return;
} }
@ -255,14 +288,14 @@ void Modbus::send_raw(const std::vector<uint8_t> &payload) {
this->flow_control_pin_->digital_write(true); this->flow_control_pin_->digital_write(true);
auto crc = crc16(payload.data(), payload.size()); auto crc = crc16(payload.data(), payload.size());
this->write_array(payload); this->write_array(payload.data(), payload.size());
this->write_byte(crc & 0xFF); this->write_byte(crc & 0xFF);
this->write_byte((crc >> 8) & 0xFF); this->write_byte((crc >> 8) & 0xFF);
this->flush(); this->flush();
if (this->flow_control_pin_ != nullptr) if (this->flow_control_pin_ != nullptr)
this->flow_control_pin_->digital_write(false); this->flow_control_pin_->digital_write(false);
waiting_for_response = payload[0]; waiting_for_response = payload[0];
ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str()); ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload.data(), payload.size()).c_str());
last_send_ = millis(); last_send_ = millis();
} }

View File

@ -3,6 +3,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/uart/uart.h" #include "esphome/components/uart/uart.h"
#include <span>
#include <vector> #include <vector>
namespace esphome { namespace esphome {
@ -32,6 +33,7 @@ class Modbus : public uart::UARTDevice, public Component {
void send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities, 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); uint8_t payload_len = 0, const uint8_t *payload = nullptr);
void send_raw(const std::vector<uint8_t> &payload); void send_raw(const std::vector<uint8_t> &payload);
void send_raw(std::span<const uint8_t> payload);
void set_role(ModbusRole role) { this->role = role; } void set_role(ModbusRole role) { this->role = role; }
void set_flow_control_pin(GPIOPin *flow_control_pin) { this->flow_control_pin_ = flow_control_pin; } void set_flow_control_pin(GPIOPin *flow_control_pin) { this->flow_control_pin_ = flow_control_pin; }
uint8_t waiting_for_response{0}; uint8_t waiting_for_response{0};
@ -65,13 +67,10 @@ class ModbusDevice {
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload); this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
} }
void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); } void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); }
void send_raw(std::span<const uint8_t> payload) { this->parent_->send_raw(payload); }
void send_error(uint8_t function_code, uint8_t exception_code) { void send_error(uint8_t function_code, uint8_t exception_code) {
std::vector<uint8_t> error_response; uint8_t error_response[3] = {this->address_, static_cast<uint8_t>(function_code | 0x80), exception_code};
error_response.reserve(3); this->send_raw(std::span<const uint8_t>(error_response, 3));
error_response.push_back(this->address_);
error_response.push_back(function_code | 0x80);
error_response.push_back(exception_code);
this->send_raw(error_response);
} }
// If more than one device is connected block sending a new command before a response is received // If more than one device is connected block sending a new command before a response is received
bool waiting_for_response() { return parent_->waiting_for_response != 0; } bool waiting_for_response() { return parent_->waiting_for_response != 0; }

View File

@ -224,12 +224,11 @@ void ModbusController::on_modbus_write_registers(uint8_t function_code, const st
return; return;
} }
std::vector<uint8_t> response; uint8_t response[6];
response.reserve(6); response[0] = this->address_;
response.push_back(this->address_); response[1] = function_code;
response.push_back(function_code); std::copy(data.begin(), data.begin() + 4, response + 2);
response.insert(response.end(), data.begin(), data.begin() + 4); this->send_raw(std::span<const uint8_t>(response, 6));
this->send_raw(response);
} }
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const { SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {

View File

@ -77,10 +77,8 @@ void PZEMAC::dump_config() {
} }
void PZEMAC::reset_energy_() { void PZEMAC::reset_energy_() {
std::vector<uint8_t> cmd; uint8_t cmd[2] = {this->address_, PZEM_CMD_RESET_ENERGY};
cmd.push_back(this->address_); this->send_raw(std::span<const uint8_t>(cmd, 2));
cmd.push_back(PZEM_CMD_RESET_ENERGY);
this->send_raw(cmd);
} }
} // namespace pzemac } // namespace pzemac

View File

@ -65,10 +65,8 @@ void PZEMDC::dump_config() {
} }
void PZEMDC::reset_energy() { void PZEMDC::reset_energy() {
std::vector<uint8_t> cmd; uint8_t cmd[2] = {this->address_, PZEM_CMD_RESET_ENERGY};
cmd.push_back(this->address_); this->send_raw(std::span<const uint8_t>(cmd, 2));
cmd.push_back(PZEM_CMD_RESET_ENERGY);
this->send_raw(cmd);
} }
} // namespace pzemdc } // namespace pzemdc