diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 6350f43ef6..48e730d500 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -204,49 +204,82 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address return; } - std::vector data; - data.push_back(address); - data.push_back(function_code); + // Calculate the expected message size + size_t msg_size = 4; // address + function + CRC(2) if (this->role == ModbusRole::CLIENT) { - data.push_back(start_address >> 8); - data.push_back(start_address >> 0); + msg_size += 2; // start_address if (function_code != 0x5 && function_code != 0x6) { - data.push_back(number_of_entities >> 8); - data.push_back(number_of_entities >> 0); + msg_size += 2; // number_of_entities + } + } + 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 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 (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 { payload_len = 2; // Write single register or coil } for (int i = 0; i < payload_len; i++) { - data.push_back(payload[i]); + data[pos++] = payload[i]; } } - auto crc = crc16(data.data(), data.size()); - data.push_back(crc >> 0); - data.push_back(crc >> 8); + auto crc = crc16(data, pos); + data[pos++] = crc >> 0; + data[pos++] = crc >> 8; if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(true); - this->write_array(data); + this->write_array(data, pos); this->flush(); if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); waiting_for_response = address; 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 // Send raw command. Except CRC everything must be contained in payload -void Modbus::send_raw(const std::vector &payload) { +void Modbus::send_raw(const std::vector &payload) { send_raw(std::span(payload)); } + +void Modbus::send_raw(std::span payload) { if (payload.empty()) { return; } @@ -255,14 +288,14 @@ void Modbus::send_raw(const std::vector &payload) { this->flow_control_pin_->digital_write(true); 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 >> 8) & 0xFF); this->flush(); if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); 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(); } diff --git a/esphome/components/modbus/modbus.h b/esphome/components/modbus/modbus.h index ec35612690..9ca3eeceb9 100644 --- a/esphome/components/modbus/modbus.h +++ b/esphome/components/modbus/modbus.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/uart/uart.h" +#include #include 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, uint8_t payload_len = 0, const uint8_t *payload = nullptr); void send_raw(const std::vector &payload); + void send_raw(std::span payload); void set_role(ModbusRole role) { this->role = role; } void set_flow_control_pin(GPIOPin *flow_control_pin) { this->flow_control_pin_ = flow_control_pin; } 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); } void send_raw(const std::vector &payload) { this->parent_->send_raw(payload); } + void send_raw(std::span payload) { this->parent_->send_raw(payload); } void send_error(uint8_t function_code, uint8_t exception_code) { - std::vector error_response; - error_response.reserve(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); + uint8_t error_response[3] = {this->address_, static_cast(function_code | 0x80), exception_code}; + this->send_raw(std::span(error_response, 3)); } // 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; } diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 0f3ddf920d..05ee6bf9dd 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -224,12 +224,11 @@ void ModbusController::on_modbus_write_registers(uint8_t function_code, const st return; } - std::vector response; - response.reserve(6); - response.push_back(this->address_); - response.push_back(function_code); - response.insert(response.end(), data.begin(), data.begin() + 4); - this->send_raw(response); + uint8_t response[6]; + response[0] = this->address_; + response[1] = function_code; + std::copy(data.begin(), data.begin() + 4, response + 2); + this->send_raw(std::span(response, 6)); } SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const { diff --git a/esphome/components/pzemac/pzemac.cpp b/esphome/components/pzemac/pzemac.cpp index 0dbe0e761d..3172b5aefb 100644 --- a/esphome/components/pzemac/pzemac.cpp +++ b/esphome/components/pzemac/pzemac.cpp @@ -77,10 +77,8 @@ void PZEMAC::dump_config() { } void PZEMAC::reset_energy_() { - std::vector cmd; - cmd.push_back(this->address_); - cmd.push_back(PZEM_CMD_RESET_ENERGY); - this->send_raw(cmd); + uint8_t cmd[2] = {this->address_, PZEM_CMD_RESET_ENERGY}; + this->send_raw(std::span(cmd, 2)); } } // namespace pzemac diff --git a/esphome/components/pzemdc/pzemdc.cpp b/esphome/components/pzemdc/pzemdc.cpp index 428bcc1fcf..c297199ca9 100644 --- a/esphome/components/pzemdc/pzemdc.cpp +++ b/esphome/components/pzemdc/pzemdc.cpp @@ -65,10 +65,8 @@ void PZEMDC::dump_config() { } void PZEMDC::reset_energy() { - std::vector cmd; - cmd.push_back(this->address_); - cmd.push_back(PZEM_CMD_RESET_ENERGY); - this->send_raw(cmd); + uint8_t cmd[2] = {this->address_, PZEM_CMD_RESET_ENERGY}; + this->send_raw(std::span(cmd, 2)); } } // namespace pzemdc