From 4ef7e94a9f847ba96f39ca6e31d27a0732903ec0 Mon Sep 17 00:00:00 2001 From: Jos van Eijndhoven Date: Tue, 2 Jun 2026 22:30:18 +0200 Subject: [PATCH] Modify the message decoder to avoid std::string --- esphome/components/hdmi_cec/cec_decoder.cpp | 17 ++++++----- esphome/components/hdmi_cec/cec_decoder.h | 18 ++++++++--- esphome/components/hdmi_cec/hdmi_cec.cpp | 34 ++++++++++++--------- esphome/components/hdmi_cec/hdmi_cec.h | 10 +++++- 4 files changed, 52 insertions(+), 27 deletions(-) diff --git a/esphome/components/hdmi_cec/cec_decoder.cpp b/esphome/components/hdmi_cec/cec_decoder.cpp index 19bea47275e..35830589863 100644 --- a/esphome/components/hdmi_cec/cec_decoder.cpp +++ b/esphome/components/hdmi_cec/cec_decoder.cpp @@ -153,7 +153,7 @@ template bool Decoder::do_operand_() { } } -#define APPEND_LINE(...) (length_ = buf_append_printf(line_.data(), line_.size(), length_, __VA_ARGS__)) +#define APPEND_LINE(...) (length_ = buf_append_printf(line_, size_, length_, __VA_ARGS__)) /** * List of specialised operand decode functions, one function per operand type. @@ -441,16 +441,19 @@ const char *Decoder::find_opcode_name_(uint32_t opcode) const { bool Decoder::append_operand_(const char *word, uint8_t offset_incr /* default 1 */) { APPEND_LINE("[%s]", word); offset_ += offset_incr; - return (length_ < line_.size()) && (offset_ < frame_.size()); + return (length_ < size_) && (offset_ < frame_.size()); } /** * Entry function 'decode' to call for full decode of a CEC frame */ -const char *Decoder::decode() { - // prepare text buffer as empty - length_ = 0; // currently accumulated length of text of operands - line_[length_] = 0; // initialise operand text to empty string +int Decoder::decode() { + // prepare text buffer state variables for an empty line + length_ = 0; // currently accumulated length of text of operands + line_[length_] = '\0'; // initialise output text to empty string + + if (!line_ || !size_) + return 0; // print src and dest fields into 'line_' address_decode_(); @@ -473,7 +476,7 @@ const char *Decoder::decode() { OperandDecode_f f = frametype->decode_f_; (this->*f)(); // call one of the above 'do_operand_' methods } - return line_.data(); + return length_; } } // namespace hdmi_cec diff --git a/esphome/components/hdmi_cec/cec_decoder.h b/esphome/components/hdmi_cec/cec_decoder.h index 96893abe0ff..c17ff4f9076 100644 --- a/esphome/components/hdmi_cec/cec_decoder.h +++ b/esphome/components/hdmi_cec/cec_decoder.h @@ -53,8 +53,15 @@ template class LookupTable { */ class Decoder { public: - Decoder(const Frame &frame) : frame_(frame), length_(0), offset_(2) {} - const char *decode(); + Decoder(const Frame &frame, char *line, unsigned int size) + : frame_(frame), line_(line), size_(size), length_(0), offset_(2) {} + + /** + * Create a one-line textual representation of the Frame content for logging and debugging + * Store the text in the earlier provided 'line' buffer + * @return the length of the written text, excluding the terminating null character + */ + int decode(); protected: const char *find_opcode_name_(uint32_t opcode) const; @@ -80,9 +87,10 @@ class Decoder { using VendorIdTable = LookupTable; const Frame &frame_; - std::array line_; // to hold the text of the decoded frame - unsigned int length_; // currently accumulated length of output text in 'line' - unsigned int offset_; // current offset in frame to process next operand byte(s) (frame[0] and [1] are skipped) + char *const line_; // buffer to hold the text of the decoded frame + const unsigned int size_; // size of the 'line_' buffer + unsigned int length_; // currently accumulated length of output text in 'line' + unsigned int offset_; // current offset in frame to process next operand byte(s) (frame[0] and [1] are skipped) /** * The HDMI CEC standard specifies a set of distinct operand (parameter) types, diff --git a/esphome/components/hdmi_cec/hdmi_cec.cpp b/esphome/components/hdmi_cec/hdmi_cec.cpp index ebd317c3f04..e31ffd37eaf 100644 --- a/esphome/components/hdmi_cec/hdmi_cec.cpp +++ b/esphome/components/hdmi_cec/hdmi_cec.cpp @@ -46,25 +46,22 @@ void Frame::set_header(uint8_t initiator_addr, uint8_t target_addr) { size_ = 1; } -std::string Frame::to_string() const { - static constexpr unsigned int HEX_LINE_SIZE = Frame::MAX_LENGTH * 3 + 1; - char hex_line[HEX_LINE_SIZE]; +int Frame::to_text(char *line, unsigned int size) const { unsigned int offset = 0; for (int i = 0; i < size_; i++) { - offset = buf_append_printf(hex_line, HEX_LINE_SIZE, offset, "%02X:", data_[i]); + offset = buf_append_printf(line, size, offset, "%02X:", data_[i]); } // clear last printed ':' if (offset > 0) - hex_line[offset - 1] = '\0'; - std::string result{hex_line}; + line[--offset] = '\0'; #ifdef HDMI_CEC_USE_DECODER - Decoder decoder(*this); - result += " => "; - result += decoder.decode(); + offset = buf_append_printf(line, size, offset, " => "); + Decoder decoder(*this, line + offset, size - offset); + offset += decoder.decode(); #endif - return result; + return offset; } void HDMICEC::setup() { @@ -110,8 +107,11 @@ void HDMICEC::handle_received_message_(const Frame *frame) { return; } - auto frame_str = frame->to_string(); - ESP_LOGD(TAG, "Received: %s", frame_str.c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG + std::array line; + frame->to_text(line.data(), (unsigned int) (line.size())); + ESP_LOGD(TAG, "Received: %s", line.data()); +#endif std::vector data(frame->size() - 1); for (int i = 1; i < frame->size(); i++) @@ -362,9 +362,15 @@ void CECTransmit::transmit_message() { // Launch the transmit of the frame that is on the front of the queue transmit_state_ = TransmitState::BUSY; transmit_attempts_++; + +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG if (transmit_attempts_ <= 1) { - ESP_LOGD(TAG, "Sending: %s", (frame->to_string()).c_str()); + std::array line; + frame->to_text(line.data(), (unsigned int) (line.size())); + ESP_LOGD(TAG, "Sending: %s", line.data()); } +#endif + // Reset info from receiver on progress of this transmit // (The receiver will see this transmit and keep track of acknowledge bits.) n_bytes_received_ = 0; @@ -573,7 +579,7 @@ void IRAM_ATTR CECTransmit::got_byte_eom_ack(bool eom, bool ack) { if (eom) { // some bus transfer ended. When the bus is free for a little while, we are allowed to transmit. required_idle_period_ = SIGNAL_FREE_TIME_AFTER_RECEIVE; - ATOMIC_SET(receiver_is_busy_, false); + ATOMIC_SET(receiver_is_busy_, false); // unblock the transmitter ATOMIC_SET(eom_received_, true); // atomic write to synchronize/commit above increments to the transmitter thread } } diff --git a/esphome/components/hdmi_cec/hdmi_cec.h b/esphome/components/hdmi_cec/hdmi_cec.h index 39cd33cc807..7085e1d77ef 100644 --- a/esphome/components/hdmi_cec/hdmi_cec.h +++ b/esphome/components/hdmi_cec/hdmi_cec.h @@ -57,7 +57,15 @@ class Frame { data_[size_++] = data; } } - std::string to_string() const; // NOLINT + + /** + * Create a one-line textual representation of the Frame content for logging and debugging + * @param line: a buffer in which the text is to be written + * @param size: The size of this buffer not to exceed with text + * @return the length of the written text, excluding the terminating null character + */ + int to_text(char *line, unsigned int size) const; + constexpr static unsigned int MAX_LENGTH = 16; constexpr static unsigned int MAX_PAYLOAD_LENGTH = MAX_LENGTH - 1; // subtract 1 byte for the src&dst field