diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index 32c4339530..98ad9e2b10 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -1,19 +1,68 @@ import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.components import sensor, text_sensor +from esphome.const import ( + CONF_ID, + CONF_DEVICE, + CONF_FREE, + CONF_FRAGMENTATION, + CONF_BLOCK, + CONF_LOOP_TIME, + UNIT_MILLISECOND, + UNIT_PERCENT, + UNIT_BYTES, + ICON_COUNTER, + ICON_TIMER, +) CODEOWNERS = ["@OttoWinter"] DEPENDENCIES = ["logger"] debug_ns = cg.esphome_ns.namespace("debug") -DebugComponent = debug_ns.class_("DebugComponent", cg.Component) +DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent) + + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(DebugComponent), + cv.Optional(CONF_DEVICE): text_sensor.TEXT_SENSOR_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(text_sensor.TextSensor)} + ), + cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), + cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), + cv.Optional(CONF_FRAGMENTATION): cv.All( + cv.only_on_esp8266, + cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)), + sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1), + ), + cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema( + UNIT_MILLISECOND, ICON_TIMER, 1 + ), } -).extend(cv.COMPONENT_SCHEMA) +).extend(cv.polling_component_schema("60s")) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + + if CONF_DEVICE in config: + sens = cg.new_Pvariable(config[CONF_DEVICE][CONF_ID]) + await text_sensor.register_text_sensor(sens, config[CONF_DEVICE]) + cg.add(var.set_device_info_sensor(sens)) + + if CONF_FREE in config: + sens = await sensor.new_sensor(config[CONF_FREE]) + cg.add(var.set_free_sensor(sens)) + + if CONF_BLOCK in config: + sens = await sensor.new_sensor(config[CONF_BLOCK]) + cg.add(var.set_block_sensor(sens)) + + if CONF_FRAGMENTATION in config: + sens = await sensor.new_sensor(config[CONF_FRAGMENTATION]) + cg.add(var.set_fragmentation_sensor(sens)) + + if CONF_LOOP_TIME in config: + sens = await sensor.new_sensor(config[CONF_LOOP_TIME]) + cg.add(var.set_loop_time_sensor(sens)) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index f3d0bded13..41bf5f50c7 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -1,21 +1,23 @@ #include "debug_component.h" + +#include #include "esphome/core/log.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" -#include "esphome/core/defines.h" #include "esphome/core/version.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 + #include #include -#endif -#ifdef USE_ESP32 #if ESP_IDF_VERSION_MAJOR >= 4 #include #else #include #endif -#endif + +#endif // USE_ESP32 #ifdef USE_ARDUINO #include @@ -26,19 +28,36 @@ namespace debug { static const char *const TAG = "debug"; +static uint32_t get_free_heap() { +#if defined(USE_ESP8266) + return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP32) + return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); +#endif +} + void DebugComponent::dump_config() { + std::string device_info; + device_info.reserve(256); + #ifndef ESPHOME_LOG_HAS_DEBUG ESP_LOGE(TAG, "Debug Component requires debug log level!"); this->status_set_error(); return; #endif - ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); -#ifdef USE_ARDUINO - this->free_heap_ = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) -#elif defined(USE_ESP_IDF) - this->free_heap_ = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); + ESP_LOGCONFIG(TAG, "Debug component:"); + LOG_TEXT_SENSOR(" ", "Device info", this->device_info_); + LOG_SENSOR(" ", "Free space on heap", this->free_sensor_); + LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_); +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_); #endif + + ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); + device_info += ESPHOME_VERSION; + + this->free_heap_ = get_free_heap(); ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); #ifdef USE_ARDUINO @@ -67,9 +86,12 @@ void DebugComponent::dump_config() { default: flash_mode = "UNKNOWN"; } - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", ESP.getFlashChipSize() / 1024, - ESP.getFlashChipSpeed() / 1000000, flash_mode); + 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; #endif // USE_ARDUINO #ifdef USE_ESP32 @@ -104,10 +126,21 @@ void DebugComponent::dump_config() { 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); ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); + device_info += "|ESP-IDF: "; + device_info += esp_get_idf_version(); - ESP_LOGD(TAG, "EFuse MAC: %s", get_mac_address_pretty().c_str()); + std::string mac = get_mac_address_pretty(); + ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); + device_info += "|EFuse MAC: "; + device_info += mac; const char *reset_reason; switch (rtc_get_reset_reason(0)) { @@ -160,6 +193,8 @@ void DebugComponent::dump_config() { reset_reason = "Unknown Reset Reason"; } ESP_LOGD(TAG, "Reset Reason: %s", reset_reason); + device_info += "|Reset: "; + device_info += reset_reason; const char *wakeup_reason; switch (rtc_get_wakeup_cause()) { @@ -203,6 +238,8 @@ void DebugComponent::dump_config() { wakeup_reason = "Unknown"; } ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason); + device_info += "|Wakeup: "; + device_info += wakeup_reason; #endif #if defined(USE_ESP8266) && !defined(CLANG_TIDY) @@ -214,20 +251,75 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "Flash Chip ID=0x%08X", ESP.getFlashChipId()); ESP_LOGD(TAG, "Reset Reason: %s", ESP.getResetReason().c_str()); ESP_LOGD(TAG, "Reset Info: %s", 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 += ESP.getResetReason().c_str(); + device_info += "|"; + device_info += ESP.getResetInfo().c_str(); #endif + + if (this->device_info_ != nullptr) { + if (device_info.length() > 255) + device_info.resize(255); + this->device_info_->publish_state(device_info); + } } + void DebugComponent::loop() { -#ifdef USE_ARDUINO - uint32_t new_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) -#elif defined(USE_ESP_IDF) - uint32_t new_free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); -#endif + // log when free heap space has halved + uint32_t new_free_heap = get_free_heap(); if (new_free_heap < this->free_heap_ / 2) { this->free_heap_ = new_free_heap; ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); this->status_momentary_warning("heap", 1000); } + + // calculate loop time - from last call to this one + if (this->loop_time_sensor_ != nullptr) { + uint32_t now = millis(); + uint32_t loop_time = now - this->last_loop_timetag_; + this->max_loop_time_ = std::max(this->max_loop_time_, loop_time); + this->last_loop_timetag_ = now; + } } + +void DebugComponent::update() { + if (this->free_sensor_ != nullptr) { + this->free_sensor_->publish_state(get_free_heap()); + } + + if (this->block_sensor_ != nullptr) { +#if defined(USE_ESP8266) + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->block_sensor_->publish_state(ESP.getMaxFreeBlockSize()); +#elif defined(USE_ESP32) + this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)); +#endif + } + +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + if (this->fragmentation_sensor_ != nullptr) { + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->fragmentation_sensor_->publish_state(ESP.getHeapFragmentation()); + } +#endif + + if (this->loop_time_sensor_ != nullptr) { + this->loop_time_sensor_->publish_state(this->max_loop_time_); + this->max_loop_time_ = 0; + } +} + float DebugComponent::get_setup_priority() const { return setup_priority::LATE; } } // namespace debug diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index e3d3d8f810..a362c52617 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -1,18 +1,42 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/macros.h" +#include "esphome/core/helpers.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/sensor/sensor.h" namespace esphome { namespace debug { -class DebugComponent : public Component { +class DebugComponent : public PollingComponent { public: void loop() override; + void update() override; float get_setup_priority() const override; void dump_config() override; + void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; } + void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; } + void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; } +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; } +#endif + void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; } + protected: uint32_t free_heap_{}; + + uint32_t last_loop_timetag_{0}; + uint32_t max_loop_time_{0}; + + text_sensor::TextSensor *device_info_{nullptr}; + sensor::Sensor *free_sensor_{nullptr}; + sensor::Sensor *block_sensor_{nullptr}; +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + sensor::Sensor *fragmentation_sensor_{nullptr}; +#endif + sensor::Sensor *loop_time_sensor_{nullptr}; }; } // namespace debug diff --git a/esphome/const.py b/esphome/const.py index a008c5ab7f..136d88173c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -62,6 +62,7 @@ CONF_BINARY_SENSORS = "binary_sensors" CONF_BINDKEY = "bindkey" CONF_BIRTH_MESSAGE = "birth_message" CONF_BIT_DEPTH = "bit_depth" +CONF_BLOCK = "block" CONF_BLUE = "blue" CONF_BOARD = "board" CONF_BOARD_FLASH_MODE = "board_flash_mode" @@ -239,7 +240,9 @@ CONF_FORCE_UPDATE = "force_update" CONF_FORMALDEHYDE = "formaldehyde" CONF_FORMAT = "format" CONF_FORWARD_ACTIVE_ENERGY = "forward_active_energy" +CONF_FRAGMENTATION = "fragmentation" CONF_FRAMEWORK = "framework" +CONF_FREE = "free" CONF_FREQUENCY = "frequency" CONF_FROM = "from" CONF_FULL_SPECTRUM = "full_spectrum" @@ -335,6 +338,7 @@ CONF_LOG_TOPIC = "log_topic" CONF_LOGGER = "logger" CONF_LOGS = "logs" CONF_LONGITUDE = "longitude" +CONF_LOOP_TIME = "loop_time" CONF_LOW = "low" CONF_LOW_VOLTAGE_REFERENCE = "low_voltage_reference" CONF_MAC_ADDRESS = "mac_address" @@ -806,6 +810,7 @@ ICON_WIFI = "mdi:wifi" UNIT_AMPERE = "A" UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³" +UNIT_BYTES = "B" UNIT_CELSIUS = "°C" UNIT_COUNT_DECILITRE = "/dL" UNIT_COUNTS_PER_CUBIC_METER = "#/m³" @@ -834,6 +839,7 @@ UNIT_MICROMETER = "µm" UNIT_MICROSIEMENS_PER_CENTIMETER = "µS/cm" UNIT_MICROTESLA = "µT" UNIT_MILLIGRAMS_PER_CUBIC_METER = "mg/m³" +UNIT_MILLISECOND = "ms" UNIT_MINUTE = "min" UNIT_OHM = "Ω" UNIT_PARTS_PER_BILLION = "ppb"