[debug] Use stack buffers with buf_append helper instead of std::string (#13020)

This commit is contained in:
J. Nick Koston
2026-01-07 10:19:10 -10:00
committed by GitHub
parent 050e9b0d4a
commit 44eac36e05
8 changed files with 320 additions and 208 deletions
+7 -8
View File
@@ -28,24 +28,23 @@ void DebugComponent::dump_config() {
#endif // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
#endif // USE_SENSOR
std::string device_info;
device_info.reserve(256);
char device_info_buffer[DEVICE_INFO_BUFFER_SIZE];
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
device_info += ESPHOME_VERSION;
size_t pos = buf_append(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION);
this->free_heap_ = get_free_heap_();
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
get_device_info_(device_info);
pos = get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>(device_info_buffer), pos);
#ifdef USE_TEXT_SENSOR
if (this->device_info_ != nullptr) {
if (device_info.length() > 255)
device_info.resize(255);
this->device_info_->publish_state(device_info);
this->device_info_->publish_state(device_info_buffer, pos);
}
if (this->reset_reason_ != nullptr) {
this->reset_reason_->publish_state(get_reset_reason_());
char reset_reason_buffer[RESET_REASON_BUFFER_SIZE];
this->reset_reason_->publish_state(
get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reset_reason_buffer)));
}
#endif // USE_TEXT_SENSOR
+48 -3
View File
@@ -4,6 +4,13 @@
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/macros.h"
#include <span>
#include <cstdarg>
#include <cstdio>
#include <algorithm>
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
@@ -15,6 +22,44 @@
namespace esphome {
namespace debug {
static constexpr size_t DEVICE_INFO_BUFFER_SIZE = 256;
static constexpr size_t RESET_REASON_BUFFER_SIZE = 128;
#ifdef USE_ESP8266
// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM)
// Format strings must be wrapped with PSTR() macro
inline size_t buf_append_p(char *buf, size_t size, size_t pos, PGM_P fmt, ...) {
if (pos >= size) {
return size;
}
va_list args;
va_start(args, fmt);
int written = vsnprintf_P(buf + pos, size - pos, fmt, args);
va_end(args);
if (written < 0) {
return pos; // encoding error
}
return std::min(pos + static_cast<size_t>(written), size);
}
#define buf_append(buf, size, pos, fmt, ...) buf_append_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__)
#else
/// Safely append formatted string to buffer, returning new position (capped at size)
__attribute__((format(printf, 4, 5))) inline size_t buf_append(char *buf, size_t size, size_t pos, const char *fmt,
...) {
if (pos >= size) {
return size;
}
va_list args;
va_start(args, fmt);
int written = vsnprintf(buf + pos, size - pos, fmt, args);
va_end(args);
if (written < 0) {
return pos; // encoding error
}
return std::min(pos + static_cast<size_t>(written), size);
}
#endif
class DebugComponent : public PollingComponent {
public:
void loop() override;
@@ -81,10 +126,10 @@ class DebugComponent : public PollingComponent {
text_sensor::TextSensor *reset_reason_{nullptr};
#endif // USE_TEXT_SENSOR
std::string get_reset_reason_();
std::string get_wakeup_cause_();
const char *get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer);
const char *get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer);
uint32_t get_free_heap_();
void get_device_info_(std::string &device_info);
size_t get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos);
void update_platform_();
};
+56 -49
View File
@@ -58,24 +58,29 @@ void DebugComponent::on_shutdown() {
global_preferences->sync();
}
std::string DebugComponent::get_reset_reason_() {
std::string reset_reason;
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
char *buf = buffer.data();
const size_t size = RESET_REASON_BUFFER_SIZE;
unsigned reason = esp_reset_reason();
if (reason < sizeof(RESET_REASONS) / sizeof(RESET_REASONS[0])) {
reset_reason = RESET_REASONS[reason];
if (reason == ESP_RST_SW) {
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
char buffer[REBOOT_MAX_LEN]{};
if (pref.load(&buffer)) {
buffer[REBOOT_MAX_LEN - 1] = '\0';
reset_reason = "Reboot request from " + std::string(buffer);
char reboot_source[REBOOT_MAX_LEN]{};
if (pref.load(&reboot_source)) {
reboot_source[REBOOT_MAX_LEN - 1] = '\0';
snprintf(buf, size, "Reboot request from %s", reboot_source);
} else {
snprintf(buf, size, "%s", RESET_REASONS[reason]);
}
} else {
snprintf(buf, size, "%s", RESET_REASONS[reason]);
}
} else {
reset_reason = "unknown source";
snprintf(buf, size, "unknown source");
}
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
return reset_reason;
ESP_LOGD(TAG, "Reset Reason: %s", buf);
return buf;
}
static const char *const WAKEUP_CAUSES[] = {
@@ -94,7 +99,7 @@ static const char *const WAKEUP_CAUSES[] = {
"BT",
};
std::string DebugComponent::get_wakeup_cause_() {
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
const char *wake_reason;
unsigned reason = esp_sleep_get_wakeup_cause();
if (reason < sizeof(WAKEUP_CAUSES) / sizeof(WAKEUP_CAUSES[0])) {
@@ -103,6 +108,7 @@ std::string DebugComponent::get_wakeup_cause_() {
wake_reason = "unknown source";
}
ESP_LOGD(TAG, "Wakeup Reason: %s", wake_reason);
// Return the static string directly - no need to copy to buffer
return wake_reason;
}
@@ -136,7 +142,10 @@ static constexpr ChipFeature CHIP_FEATURES[] = {
{CHIP_FEATURE_WIFI_BGN, "2.4GHz WiFi"},
};
void DebugComponent::get_device_info_(std::string &device_info) {
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
char *buf = buffer.data();
#if defined(USE_ARDUINO)
const char *flash_mode;
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
@@ -161,68 +170,66 @@ void DebugComponent::get_device_info_(std::string &device_info) {
default:
flash_mode = "UNKNOWN";
}
ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s",
ESP.getFlashChipSize() / 1024, // NOLINT
ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT
device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT
"kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT
device_info += flash_mode;
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
flash_mode);
#endif
esp_chip_info_t info;
esp_chip_info(&info);
const char *model = ESPHOME_VARIANT;
std::string features;
// Check each known feature bit
// Build features string
pos = buf_append(buf, size, pos, "|Chip: %s Features:", model);
bool first_feature = true;
for (const auto &feature : CHIP_FEATURES) {
if (info.features & feature.bit) {
features += feature.name;
features += ", ";
pos = buf_append(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name);
first_feature = false;
info.features &= ~feature.bit;
}
}
if (info.features != 0)
features += "Other:" + format_hex(info.features);
ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores,
info.revision);
device_info += "|Chip: ";
device_info += model;
device_info += " Features:";
device_info += features;
device_info += " Cores:" + to_string(info.cores);
device_info += " Revision:" + to_string(info.revision);
device_info += str_sprintf("|CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000);
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000);
if (info.features != 0) {
pos = buf_append(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features);
}
ESP_LOGD(TAG, "Chip: Model=%s, Cores=%u, Revision=%u", model, info.cores, info.revision);
pos = buf_append(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision);
uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000;
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
// Framework detection
device_info += "|Framework: ";
#ifdef USE_ARDUINO
ESP_LOGD(TAG, "Framework: Arduino");
device_info += "Arduino";
pos = buf_append(buf, size, pos, "|Framework: Arduino");
#elif defined(USE_ESP32)
ESP_LOGD(TAG, "Framework: ESP-IDF");
device_info += "ESP-IDF";
pos = buf_append(buf, size, pos, "|Framework: ESP-IDF");
#else
ESP_LOGW(TAG, "Framework: UNKNOWN");
device_info += "UNKNOWN";
pos = buf_append(buf, size, pos, "|Framework: UNKNOWN");
#endif
ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
device_info += "|ESP-IDF: ";
device_info += esp_get_idf_version();
pos = buf_append(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version());
std::string mac = get_mac_address_pretty();
ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str());
device_info += "|EFuse MAC: ";
device_info += mac;
uint8_t mac[6];
get_mac_address_raw(mac);
ESP_LOGD(TAG, "EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
pos = buf_append(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4],
mac[5]);
device_info += "|Reset: ";
device_info += get_reset_reason_();
char reason_buffer[RESET_REASON_BUFFER_SIZE];
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason);
std::string wakeup_reason = this->get_wakeup_cause_();
device_info += "|Wakeup: ";
device_info += wakeup_reason;
const char *wakeup_cause = get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
pos = buf_append(buf, size, pos, "|Wakeup: %s", wakeup_cause);
return pos;
}
void DebugComponent::update_platform_() {
+44 -29
View File
@@ -8,19 +8,31 @@ namespace debug {
static const char *const TAG = "debug";
std::string DebugComponent::get_reset_reason_() {
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
char *buf = buffer.data();
#if !defined(CLANG_TIDY)
return ESP.getResetReason().c_str();
String reason = ESP.getResetReason(); // NOLINT
snprintf_P(buf, RESET_REASON_BUFFER_SIZE, PSTR("%s"), reason.c_str());
return buf;
#else
return "";
buf[0] = '\0';
return buf;
#endif
}
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
// ESP8266 doesn't have detailed wakeup cause like ESP32
return "";
}
uint32_t DebugComponent::get_free_heap_() {
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
}
void DebugComponent::get_device_info_(std::string &device_info) {
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
char *buf = buffer.data();
const char *flash_mode;
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
case FM_QIO:
@@ -38,42 +50,45 @@ void DebugComponent::get_device_info_(std::string &device_info) {
default:
flash_mode = "UNKNOWN";
}
ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s",
ESP.getFlashChipSize() / 1024, // NOLINT
ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT
device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT
"kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT
device_info += flash_mode;
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
flash_mode);
#if !defined(CLANG_TIDY)
auto reset_reason = get_reset_reason_();
char reason_buffer[RESET_REASON_BUFFER_SIZE];
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
uint32_t chip_id = ESP.getChipId();
uint8_t boot_version = ESP.getBootVersion();
uint8_t boot_mode = ESP.getBootMode();
uint8_t cpu_freq = ESP.getCpuFreqMHz();
uint32_t flash_chip_id = ESP.getFlashChipId();
ESP_LOGD(TAG,
"Chip ID: 0x%08X\n"
"Chip ID: 0x%08" PRIX32 "\n"
"SDK Version: %s\n"
"Core Version: %s\n"
"Boot Version=%u Mode=%u\n"
"CPU Frequency: %u\n"
"Flash Chip ID=0x%08X\n"
"Flash Chip ID=0x%08" PRIX32 "\n"
"Reset Reason: %s\n"
"Reset Info: %s",
ESP.getChipId(), ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), ESP.getBootVersion(), ESP.getBootMode(),
ESP.getCpuFreqMHz(), ESP.getFlashChipId(), reset_reason.c_str(), ESP.getResetInfo().c_str());
chip_id, ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), boot_version, boot_mode, cpu_freq, flash_chip_id,
reset_reason, ESP.getResetInfo().c_str());
device_info += "|Chip: 0x" + format_hex(ESP.getChipId());
device_info += "|SDK: ";
device_info += ESP.getSdkVersion();
device_info += "|Core: ";
device_info += ESP.getCoreVersion().c_str();
device_info += "|Boot: ";
device_info += to_string(ESP.getBootVersion());
device_info += "|Mode: " + to_string(ESP.getBootMode());
device_info += "|CPU: " + to_string(ESP.getCpuFreqMHz());
device_info += "|Flash: 0x" + format_hex(ESP.getFlashChipId());
device_info += "|Reset: ";
device_info += reset_reason;
device_info += "|";
device_info += ESP.getResetInfo().c_str();
pos = buf_append(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id);
pos = buf_append(buf, size, pos, "|SDK: %s", ESP.getSdkVersion());
pos = buf_append(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str());
pos = buf_append(buf, size, pos, "|Boot: %u", boot_version);
pos = buf_append(buf, size, pos, "|Mode: %u", boot_mode);
pos = buf_append(buf, size, pos, "|CPU: %u", cpu_freq);
pos = buf_append(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id);
pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason);
pos = buf_append(buf, size, pos, "|%s", ESP.getResetInfo().c_str());
#endif
return pos;
}
void DebugComponent::update_platform_() {
+4 -2
View File
@@ -5,11 +5,13 @@
namespace esphome {
namespace debug {
std::string DebugComponent::get_reset_reason_() { return ""; }
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) { return ""; }
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) { return ""; }
uint32_t DebugComponent::get_free_heap_() { return INT_MAX; }
void DebugComponent::get_device_info_(std::string &device_info) {}
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) { return pos; }
void DebugComponent::update_platform_() {}
+28 -16
View File
@@ -7,31 +7,43 @@ namespace debug {
static const char *const TAG = "debug";
std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_name(lt_get_reboot_reason()); }
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
// Return the static string directly
return lt_get_reboot_reason_name(lt_get_reboot_reason());
}
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) { return ""; }
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
void DebugComponent::get_device_info_(std::string &device_info) {
std::string reset_reason = get_reset_reason_();
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
char *buf = buffer.data();
char reason_buffer[RESET_REASON_BUFFER_SIZE];
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
uint32_t flash_kib = lt_flash_get_size() / 1024;
uint32_t ram_kib = lt_ram_get_size() / 1024;
uint32_t mac_id = lt_cpu_get_mac_id();
ESP_LOGD(TAG,
"LibreTiny Version: %s\n"
"Chip: %s (%04x) @ %u MHz\n"
"Chip ID: 0x%06X\n"
"Chip ID: 0x%06" PRIX32 "\n"
"Board: %s\n"
"Flash: %u KiB / RAM: %u KiB\n"
"Flash: %" PRIu32 " KiB / RAM: %" PRIu32 " KiB\n"
"Reset Reason: %s",
lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), lt_cpu_get_mac_id(),
lt_get_board_code(), lt_flash_get_size() / 1024, lt_ram_get_size() / 1024, reset_reason.c_str());
lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id,
lt_get_board_code(), flash_kib, ram_kib, reset_reason);
device_info += "|Version: ";
device_info += LT_BANNER_STR + 10;
device_info += "|Reset Reason: ";
device_info += reset_reason;
device_info += "|Chip Name: ";
device_info += lt_cpu_get_model_name();
device_info += "|Chip ID: 0x" + format_hex(lt_cpu_get_mac_id());
device_info += "|Flash: " + to_string(lt_flash_get_size() / 1024) + " KiB";
device_info += "|RAM: " + to_string(lt_ram_get_size() / 1024) + " KiB";
pos = buf_append(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10);
pos = buf_append(buf, size, pos, "|Reset Reason: %s", reset_reason);
pos = buf_append(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name());
pos = buf_append(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id);
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib);
pos = buf_append(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib);
return pos;
}
void DebugComponent::update_platform_() {
+12 -4
View File
@@ -7,13 +7,21 @@ namespace debug {
static const char *const TAG = "debug";
std::string DebugComponent::get_reset_reason_() { return ""; }
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) { return ""; }
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) { return ""; }
uint32_t DebugComponent::get_free_heap_() { return rp2040.getFreeHeap(); }
void DebugComponent::get_device_info_(std::string &device_info) {
ESP_LOGD(TAG, "CPU Frequency: %u", rp2040.f_cpu());
device_info += "CPU Frequency: " + to_string(rp2040.f_cpu());
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
char *buf = buffer.data();
uint32_t cpu_freq = rp2040.f_cpu();
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq);
pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq);
return pos;
}
void DebugComponent::update_platform_() {}
File diff suppressed because it is too large Load Diff