ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
debug_esp32.cpp
Go to the documentation of this file.
1#include "debug_component.h"
2
3#ifdef USE_ESP32
5#include "esphome/core/log.h"
6#include "esphome/core/hal.h"
8#include <esp_sleep.h>
9#include <esp_idf_version.h>
10
11#include <esp_heap_caps.h>
12#include <esp_system.h>
13#include <esp_chip_info.h>
14#include <esp_partition.h>
15
16#ifdef USE_ARDUINO
17#include <Esp.h>
18#endif
19
20namespace esphome::debug {
21
22static const char *const TAG = "debug";
23
24// index by values returned by esp_reset_reason
25
26static const char *const RESET_REASONS[] = {
27 "unknown source",
28 "power-on event",
29 "external pin",
30 "software via esp_restart",
31 "exception/panic",
32 "interrupt watchdog",
33 "task watchdog",
34 "other watchdogs",
35 "exiting deep sleep mode",
36 "brownout",
37 "SDIO",
38 "USB peripheral",
39 "JTAG",
40 "efuse error",
41 "power glitch detected",
42 "CPU lock up",
43};
44
45static const char *const REBOOT_KEY = "reboot_source";
46static const size_t REBOOT_MAX_LEN = 24;
47
48// on shutdown, store the source of the reboot request
51 char buffer[REBOOT_MAX_LEN]{};
52 auto pref = global_preferences->make_preference(REBOOT_MAX_LEN,
53 fnv1_hash_extend(fnv1_hash(REBOOT_KEY), App.get_name().c_str()));
54 if (component != nullptr) {
55 strncpy(buffer, LOG_STR_ARG(component->get_component_log_str()), REBOOT_MAX_LEN - 1);
56 buffer[REBOOT_MAX_LEN - 1] = '\0';
57 }
58 ESP_LOGD(TAG, "Storing reboot source: %s", buffer);
59 pref.save(&buffer);
61}
62
63const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
64 char *buf = buffer.data();
65 const size_t size = RESET_REASON_BUFFER_SIZE;
66
67 unsigned reason = esp_reset_reason();
68 if (reason < sizeof(RESET_REASONS) / sizeof(RESET_REASONS[0])) {
69 if (reason == ESP_RST_SW || reason == ESP_RST_WDT) {
70 // On some ESP32-S3 configurations (e.g. SPIRAM with fetch-instructions/rodata),
71 // esp_restart() intermittently produces RTCWDT_RTC_RST (ESP_RST_WDT) instead of
72 // ESP_RST_SW. Check the stored reboot source for both reset reasons so a software
73 // reboot that ends up as WDT still reports the correct source.
74 auto pref = global_preferences->make_preference(REBOOT_MAX_LEN,
75 fnv1_hash_extend(fnv1_hash(REBOOT_KEY), App.get_name().c_str()));
76 char reboot_source[REBOOT_MAX_LEN]{};
77 if (pref.load(&reboot_source) && reboot_source[0] != '\0') {
78 reboot_source[REBOOT_MAX_LEN - 1] = '\0';
79 snprintf(buf, size, "Reboot request from %s", reboot_source);
80 } else {
81 snprintf(buf, size, "%s", RESET_REASONS[reason]);
82 }
83 } else {
84 snprintf(buf, size, "%s", RESET_REASONS[reason]);
85 }
86 } else {
87 snprintf(buf, size, "unknown source");
88 }
89 return buf;
90}
91
92#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
93static const char *const WAKEUP_CAUSES[] = {
94 "undefined", // ESP_SLEEP_WAKEUP_UNDEFINED (0)
95 "undefined", // ESP_SLEEP_WAKEUP_ALL (1)
96 "external signal using RTC_IO", // ESP_SLEEP_WAKEUP_EXT0 (2)
97 "external signal using RTC_CNTL", // ESP_SLEEP_WAKEUP_EXT1 (3)
98 "timer", // ESP_SLEEP_WAKEUP_TIMER (4)
99 "touchpad", // ESP_SLEEP_WAKEUP_TOUCHPAD (5)
100 "ULP program", // ESP_SLEEP_WAKEUP_ULP (6)
101 "GPIO", // ESP_SLEEP_WAKEUP_GPIO (7)
102 "UART", // ESP_SLEEP_WAKEUP_UART (8)
103 "UART1", // ESP_SLEEP_WAKEUP_UART1 (9)
104 "UART2", // ESP_SLEEP_WAKEUP_UART2 (10)
105 "WIFI", // ESP_SLEEP_WAKEUP_WIFI (11)
106 "COCPU int", // ESP_SLEEP_WAKEUP_COCPU (12)
107 "COCPU crash", // ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG (13)
108 "BT", // ESP_SLEEP_WAKEUP_BT (14)
109 "VAD", // ESP_SLEEP_WAKEUP_VAD (15)
110 "VBAT under voltage", // ESP_SLEEP_WAKEUP_VBAT_UNDER_VOLT (16)
111};
112#else
113static const char *const WAKEUP_CAUSES[] = {
114 "undefined", // ESP_SLEEP_WAKEUP_UNDEFINED (0)
115 "undefined", // ESP_SLEEP_WAKEUP_ALL (1)
116 "external signal using RTC_IO", // ESP_SLEEP_WAKEUP_EXT0 (2)
117 "external signal using RTC_CNTL", // ESP_SLEEP_WAKEUP_EXT1 (3)
118 "timer", // ESP_SLEEP_WAKEUP_TIMER (4)
119 "touchpad", // ESP_SLEEP_WAKEUP_TOUCHPAD (5)
120 "ULP program", // ESP_SLEEP_WAKEUP_ULP (6)
121 "GPIO", // ESP_SLEEP_WAKEUP_GPIO (7)
122 "UART", // ESP_SLEEP_WAKEUP_UART (8)
123 "WIFI", // ESP_SLEEP_WAKEUP_WIFI (9)
124 "COCPU int", // ESP_SLEEP_WAKEUP_COCPU (10)
125 "COCPU crash", // ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG (11)
126 "BT", // ESP_SLEEP_WAKEUP_BT (12)
127};
128#endif
129
130const char *DebugComponent::get_wakeup_cause_(std::span<char, WAKEUP_CAUSE_BUFFER_SIZE> buffer) {
131 static constexpr auto NUM_CAUSES = sizeof(WAKEUP_CAUSES) / sizeof(WAKEUP_CAUSES[0]);
132#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
133 // IDF 6.0+ returns a bitmap of all wakeup sources
134 uint32_t causes = esp_sleep_get_wakeup_causes();
135 if (causes == 0) {
136 return WAKEUP_CAUSES[0]; // "undefined"
137 }
138 char *p = buffer.data();
139 char *end = p + buffer.size();
140 *p = '\0';
141 const char *sep = "";
142 for (unsigned i = 0; i < NUM_CAUSES && p < end; i++) {
143 if (causes & (1U << i)) {
144 size_t needed = strlen(sep) + strlen(WAKEUP_CAUSES[i]);
145 if (p + needed >= end) {
146 break;
147 }
148 p += snprintf(p, end - p, "%s%s", sep, WAKEUP_CAUSES[i]);
149 sep = ", ";
150 }
151 }
152 return buffer.data();
153#else
154 unsigned reason = esp_sleep_get_wakeup_cause();
155 if (reason < NUM_CAUSES) {
156 return WAKEUP_CAUSES[reason];
157 }
158 return "unknown source";
159#endif
160}
161
163 ESP_LOGCONFIG(TAG,
164 "Partition table:\n"
165 " %-12s %-4s %-8s %-10s %-10s",
166 "Name", "Type", "Subtype", "Address", "Size");
167 esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
168 while (it != NULL) {
169 const esp_partition_t *partition = esp_partition_get(it);
170 ESP_LOGCONFIG(TAG, " %-12s %-4d %-8d 0x%08" PRIX32 " 0x%08" PRIX32, partition->label, partition->type,
171 partition->subtype, partition->address, partition->size);
172 it = esp_partition_next(it);
173 }
174 esp_partition_iterator_release(it);
175}
176
177uint32_t DebugComponent::get_free_heap_() { return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); }
178
179struct ChipFeature {
180 int bit;
181 const char *name;
182};
183
184static constexpr ChipFeature CHIP_FEATURES[] = {
185 {CHIP_FEATURE_BLE, "BLE"},
186 {CHIP_FEATURE_BT, "BT"},
187 {CHIP_FEATURE_EMB_FLASH, "EMB Flash"},
188 {CHIP_FEATURE_EMB_PSRAM, "EMB PSRAM"},
189 {CHIP_FEATURE_WIFI_BGN, "2.4GHz WiFi"},
190};
191
192size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
193 constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
194 char *buf = buffer.data();
195
196#if defined(USE_ARDUINO)
197 const char *flash_mode;
198 switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
199 case FM_QIO:
200 flash_mode = "QIO";
201 break;
202 case FM_QOUT:
203 flash_mode = "QOUT";
204 break;
205 case FM_DIO:
206 flash_mode = "DIO";
207 break;
208 case FM_DOUT:
209 flash_mode = "DOUT";
210 break;
211 case FM_FAST_READ:
212 flash_mode = "FAST_READ";
213 break;
214 case FM_SLOW_READ:
215 flash_mode = "SLOW_READ";
216 break;
217 default:
218 flash_mode = "UNKNOWN";
219 }
220 uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
221 uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
222 pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
223 flash_mode);
224#endif
225
226 esp_chip_info_t info;
227 esp_chip_info(&info);
228 const char *model = ESPHOME_VARIANT;
229
230 // Build features string
231 pos = buf_append_str(buf, size, pos, "|Chip: ");
232 pos = buf_append_str(buf, size, pos, model);
233 pos = buf_append_str(buf, size, pos, " Features:");
234 bool first_feature = true;
235 for (const auto &feature : CHIP_FEATURES) {
236 if (info.features & feature.bit) {
237 pos = buf_append_str(buf, size, pos, first_feature ? "" : ", ");
238 pos = buf_append_str(buf, size, pos, feature.name);
239 first_feature = false;
240 info.features &= ~feature.bit;
241 }
242 }
243 if (info.features != 0) {
244 pos = buf_append_str(buf, size, pos, first_feature ? "" : ", ");
245 pos = buf_append_printf(buf, size, pos, "Other:0x%" PRIx32, info.features);
246 }
247 pos = buf_append_printf(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision);
248
249 uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000;
250 pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
251
252 char reset_buffer[RESET_REASON_BUFFER_SIZE];
253 char wakeup_buffer[WAKEUP_CAUSE_BUFFER_SIZE];
254 const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reset_buffer));
255 const char *wakeup_cause = get_wakeup_cause_(std::span<char, WAKEUP_CAUSE_BUFFER_SIZE>(wakeup_buffer));
256
257 uint8_t mac[MAC_ADDRESS_SIZE];
259
260 ESP_LOGD(TAG,
261 "ESP32 debug info:\n"
262 " Chip: %s\n"
263 " Cores: %u\n"
264 " Revision: %u\n"
265 " CPU Frequency: %" PRIu32 " MHz\n"
266 " ESP-IDF Version: %s\n"
267 " EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X\n"
268 " Reset Reason: %s\n"
269 " Wakeup Cause: %s",
270 model, info.cores, info.revision, cpu_freq_mhz, esp_get_idf_version(), mac[0], mac[1], mac[2], mac[3],
271 mac[4], mac[5], reset_reason, wakeup_cause);
272#if defined(USE_ARDUINO)
273 ESP_LOGD(TAG, " Flash: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
274#endif
275 // Framework detection
276#ifdef USE_ARDUINO
277 ESP_LOGD(TAG, " Framework: Arduino");
278 pos = buf_append_str(buf, size, pos, "|Framework: Arduino");
279#else
280 ESP_LOGD(TAG, " Framework: ESP-IDF");
281 pos = buf_append_str(buf, size, pos, "|Framework: ESP-IDF");
282#endif
283
284 pos = buf_append_str(buf, size, pos, "|ESP-IDF: ");
285 pos = buf_append_str(buf, size, pos, esp_get_idf_version());
286 pos = buf_append_printf(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3],
287 mac[4], mac[5]);
288 pos = buf_append_str(buf, size, pos, "|Reset: ");
289 pos = buf_append_str(buf, size, pos, reset_reason);
290 pos = buf_append_str(buf, size, pos, "|Wakeup: ");
291 pos = buf_append_str(buf, size, pos, wakeup_cause);
292
293 return pos;
294}
295
297#ifdef USE_SENSOR
298 uint32_t max_alloc = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
299 if (this->block_sensor_ != nullptr) {
300 this->block_sensor_->publish_state(max_alloc);
301 }
302 if (this->min_free_sensor_ != nullptr) {
303 this->min_free_sensor_->publish_state(heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
304 }
305 if (this->fragmentation_sensor_ != nullptr) {
306 uint32_t free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
307 if (free_heap > 0) {
308 float fragmentation = 100.0f - (100.0f * max_alloc / free_heap);
309 this->fragmentation_sensor_->publish_state(fragmentation);
310 }
311 }
312 if (this->psram_sensor_ != nullptr) {
313 this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
314 }
315#endif
316}
317
318} // namespace esphome::debug
319
320#endif // USE_ESP32
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
Component * get_current_component()
constexpr const char * c_str() const
Definition string_ref.h:73
const char * get_wakeup_cause_(std::span< char, WAKEUP_CAUSE_BUFFER_SIZE > buffer)
void log_partition_info_()
Logs information about the device's partition table.
size_t get_device_info_(std::span< char, DEVICE_INFO_BUFFER_SIZE > buffer, size_t pos)
sensor::Sensor * fragmentation_sensor_
const char * get_reset_reason_(std::span< char, RESET_REASON_BUFFER_SIZE > buffer)
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
const Component * component
Definition component.cpp:34
size_t buf_append_str(char *buf, size_t size, size_t pos, const char *str)
Safely append a string to buffer, returning new position (capped at size).
Definition helpers.h:1141
constexpr uint32_t fnv1_hash_extend(uint32_t hash, T value)
Extend a FNV-1 hash with an integer (hashes each byte).
Definition helpers.h:814
uint16_t size
Definition helpers.cpp:25
ESPPreferences * global_preferences
uint32_t fnv1_hash(const char *str)
Calculate a FNV-1 hash of str.
Definition helpers.cpp:160
uint32_t arch_get_cpu_freq_hz()
Definition hal.cpp:63
size_t size_t pos
Definition helpers.h:1092
void get_mac_address_raw(uint8_t *mac)
Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
Definition helpers.cpp:87
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
ESPPreferenceObject make_preference(size_t, uint32_t, bool)
Definition preferences.h:24
bool sync()
Commit pending writes to flash.
Definition preferences.h:33
uint8_t end[39]
Definition sun_gtil2.cpp:17