From e9f2d75aab3a8e31f82e82e30ac7d0bc811e617d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Dec 2025 08:34:45 -1000 Subject: [PATCH] [core] Add format_hex_to helper for zero-allocation hex formatting (#12670) --- esphome/components/one_wire/one_wire_bus.cpp | 6 ++++-- esphome/components/sx126x/sx126x.cpp | 4 +++- esphome/components/sx127x/sx127x.cpp | 4 +++- esphome/core/helpers.cpp | 13 +++++++++++++ esphome/core/helpers.h | 18 ++++++++++++++++++ 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/esphome/components/one_wire/one_wire_bus.cpp b/esphome/components/one_wire/one_wire_bus.cpp index c2542177cf..27b7d58a0f 100644 --- a/esphome/components/one_wire/one_wire_bus.cpp +++ b/esphome/components/one_wire/one_wire_bus.cpp @@ -49,7 +49,8 @@ void OneWireBus::search() { break; auto *address8 = reinterpret_cast(&address); if (crc8(address8, 7) != address8[7]) { - ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str()); + char hex_buf[17]; + ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex_to(hex_buf, address)); } else { this->devices_.push_back(address); } @@ -82,8 +83,9 @@ void OneWireBus::dump_devices_(const char *tag) { ESP_LOGW(tag, " Found no devices!"); } else { ESP_LOGCONFIG(tag, " Found devices:"); + char hex_buf[17]; // uint64_t = 16 hex chars + null for (auto &address : this->devices_) { - ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff))); + ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex_to(hex_buf, address), LOG_STR_ARG(get_model_str(address & 0xff))); } } } diff --git a/esphome/components/sx126x/sx126x.cpp b/esphome/components/sx126x/sx126x.cpp index bb59f26b79..707d6f1fbf 100644 --- a/esphome/components/sx126x/sx126x.cpp +++ b/esphome/components/sx126x/sx126x.cpp @@ -527,7 +527,9 @@ void SX126x::dump_config() { this->spreading_factor_, cr, this->preamble_size_); } if (!this->sync_value_.empty()) { - ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str()); + char hex_buf[17]; // 8 bytes max = 16 hex chars + null + ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", + format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size())); } if (this->is_failed()) { ESP_LOGE(TAG, "Configuring SX126x failed"); diff --git a/esphome/components/sx127x/sx127x.cpp b/esphome/components/sx127x/sx127x.cpp index 8e6db5dc9e..3185574b1a 100644 --- a/esphome/components/sx127x/sx127x.cpp +++ b/esphome/components/sx127x/sx127x.cpp @@ -476,7 +476,9 @@ void SX127x::dump_config() { ESP_LOGCONFIG(TAG, " Payload Length: %" PRIu32, this->payload_length_); } if (!this->sync_value_.empty()) { - ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str()); + char hex_buf[17]; // 8 bytes max = 16 hex chars + null + ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", + format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size())); } if (this->preamble_size_ > 0 || this->preamble_detect_ > 0) { ESP_LOGCONFIG(TAG, diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index d7d32ea28f..5e361ecce2 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -297,6 +297,19 @@ std::string format_hex(const uint8_t *data, size_t length) { } std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } +char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) { + size_t max_bytes = (buffer_size - 1) / 2; + if (length > max_bytes) { + length = max_bytes; + } + for (size_t i = 0; i < length; i++) { + buffer[2 * i] = format_hex_char(data[i] >> 4); + buffer[2 * i + 1] = format_hex_char(data[i] & 0x0F); + } + buffer[length * 2] = '\0'; + return buffer; +} + // Shared implementation for uint8_t and string hex formatting static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) { if (data == nullptr || length == 0) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 48a2313e2c..4319e32510 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -730,6 +730,24 @@ inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) { output[12] = '\0'; } +/// Format byte array as lowercase hex to buffer (base implementation). +char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length); + +/// Format byte array as lowercase hex to buffer. Automatically deduces buffer size. +/// Truncates output if data exceeds buffer capacity. Returns pointer to buffer. +template inline char *format_hex_to(char (&buffer)[N], const uint8_t *data, size_t length) { + static_assert(N >= 3, "Buffer must hold at least one hex byte (3 chars)"); + return format_hex_to(buffer, N, data, length); +} + +/// Format an unsigned integer in lowercased hex to buffer, starting with the most significant byte. +template::value, int> = 0> +inline char *format_hex_to(char (&buffer)[N], T val) { + static_assert(N >= sizeof(T) * 2 + 1, "Buffer too small for type"); + val = convert_big_endian(val); + return format_hex_to(buffer, reinterpret_cast(&val), sizeof(T)); +} + /// Format the six-byte array \p mac into a MAC address. std::string format_mac_address_pretty(const uint8_t mac[6]); /// Format the byte array \p data of length \p len in lowercased hex.