Merge branch 'dev' into esp8266-crash-handler

This commit is contained in:
J. Nick Koston
2026-04-05 13:40:11 -10:00
committed by GitHub
16 changed files with 293 additions and 26 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ void log_button(const char *tag, const char *prefix, const char *type, Button *o
}
void Button::press() {
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
ESP_LOGV(TAG, "'%s' Pressed.", this->get_name().c_str());
this->press_action();
this->press_callback_.call();
}
+14 -9
View File
@@ -399,8 +399,17 @@ void ESP32BLE::loop() {
return;
}
#ifdef USE_ESP32_BLE_ADVERTISING
if (this->advertising_ != nullptr) {
this->advertising_->loop();
}
#endif
BLEEvent *ble_event = this->ble_events_.pop();
while (ble_event != nullptr) {
if (ble_event == nullptr)
return;
do {
switch (ble_event->type_) {
#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
case BLEEvent::GATTS: {
@@ -488,15 +497,11 @@ void ESP32BLE::loop() {
}
// Return the event to the pool
this->ble_event_pool_.release(ble_event);
ble_event = this->ble_events_.pop();
}
#ifdef USE_ESP32_BLE_ADVERTISING
if (this->advertising_ != nullptr) {
this->advertising_->loop();
}
#endif
} while ((ble_event = this->ble_events_.pop()) != nullptr);
// Log dropped events periodically
// Log dropped events - only reachable when events were processed.
// Drops only occur when the queue is full, and only this loop drains it,
// so if pop() returned nullptr above we can skip this check (saves a memw).
uint16_t dropped = this->ble_events_.get_and_reset_dropped_count();
if (dropped > 0) {
ESP_LOGW(TAG, "Dropped %u BLE events due to buffer overflow", dropped);
+8 -2
View File
@@ -392,6 +392,9 @@ async def to_code(configs):
} & styles_used:
df.add_define("LV_COLOR_SCREEN_TRANSP", "1")
if configs[0].get(df.CONF_THEME, {}).get(df.CONF_DARK_MODE):
df.add_define("LV_THEME_DEFAULT_DARK", "1")
# Currently always need RGB565 for the display buffer, and ARGB8888 is used for layer blending
lv_image_formats = {"RGB565", "ARGB8888"}
if {
@@ -459,8 +462,11 @@ def add_hello_world(config):
def _theme_schema(value):
return cv.Schema(
{
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
for name, w in WIDGET_TYPES.items()
cv.Optional(df.CONF_DARK_MODE, default=False): cv.boolean,
**{
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
for name, w in WIDGET_TYPES.items()
},
}
)(value)
+1
View File
@@ -598,6 +598,7 @@ CONF_FLEX_ALIGN_CROSS = "flex_align_cross"
CONF_FLEX_ALIGN_TRACK = "flex_align_track"
CONF_FLEX_GROW = "flex_grow"
CONF_FREEZE = "freeze"
CONF_DARK_MODE = "dark_mode"
CONF_FULL_REFRESH = "full_refresh"
CONF_GRADIENTS = "gradients"
CONF_GRID_CELL_ROW_POS = "grid_cell_row_pos"
+1 -1
View File
@@ -3,7 +3,7 @@
- obj:
id: hello_world_card_
pad_all: 12
bg_color: white
bg_opa: cover
height: 100%
width: 100%
scrollable: false
+2 -2
View File
@@ -7,7 +7,7 @@ from esphome.core import ID
from .defines import CONF_STYLE_DEFINITIONS, CONF_THEME, LValidator, literal
from .helpers import add_lv_use
from .lvcode import LambdaContext, lv
from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, remap_property
from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, WIDGET_TYPES, remap_property
from .types import ObjUpdateAction, lv_style_t
from .widgets import collect_parts, theme_widget_map, wait_for_widgets
@@ -85,7 +85,7 @@ async def style_update_to_code(config, action_id, template_arg, args):
async def theme_to_code(config):
if theme := config.get(CONF_THEME):
add_lv_use(CONF_THEME)
for w_name, style in theme.items():
for w_name, style in ((k, v) for k, v in theme.items() if k in WIDGET_TYPES):
# Work around Python 3.10 bug with nested async comprehensions
# With Python 3.11 this could be simplified
# TODO: Now that we require Python 3.11+, this can be updated to use nested comprehensions
@@ -37,6 +37,11 @@ void MCP23X08Base::pin_mode(uint8_t pin, gpio::Flags flags) {
if (this->interrupt_pin_ != nullptr && (flags & gpio::FLAG_INPUT)) {
this->pin_interrupt_mode(pin, mcp23xxx_base::MCP23XXX_CHANGE);
}
// Enable polling loop for input pins (not needed for interrupt-driven mode
// where the ISR handles re-enabling loop)
if (this->interrupt_pin_ == nullptr && (flags & gpio::FLAG_INPUT)) {
this->enable_loop();
}
}
void MCP23X08Base::pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) {
@@ -49,6 +49,11 @@ void MCP23X17Base::pin_mode(uint8_t pin, gpio::Flags flags) {
if (this->interrupt_pin_ != nullptr && (flags & gpio::FLAG_INPUT)) {
this->pin_interrupt_mode(pin, mcp23xxx_base::MCP23XXX_CHANGE);
}
// Enable polling loop for input pins (not needed for interrupt-driven mode
// where the ISR handles re-enabling loop)
if (this->interrupt_pin_ == nullptr && (flags & gpio::FLAG_INPUT)) {
this->enable_loop();
}
}
void MCP23X17Base::pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) {
@@ -35,8 +35,11 @@ template<uint8_t N> class MCP23XXXBase : public Component, public gpio_expander:
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&MCP23XXXBase::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
this->set_invalidate_on_read_(false);
this->disable_loop();
}
// Disable loop until an input pin is configured via pin_mode()
// For interrupt-driven mode, loop is re-enabled by the ISR
// For polling mode, loop is re-enabled when pin_mode() registers an input pin
this->disable_loop();
}
static void IRAM_ATTR gpio_intr(MCP23XXXBase *arg) { arg->enable_loop_soon_any_context(); }
@@ -41,8 +41,11 @@ void PI4IOE5V6408Component::setup() {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&PI4IOE5V6408Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
this->set_invalidate_on_read_(false);
this->disable_loop();
}
// Disable loop until an input pin is configured via pin_mode()
// For interrupt-driven mode, loop is re-enabled by the ISR
// For polling mode, loop is re-enabled when pin_mode() registers an input pin
this->disable_loop();
}
void IRAM_ATTR PI4IOE5V6408Component::gpio_intr(PI4IOE5V6408Component *arg) { arg->enable_loop_soon_any_context(); }
void PI4IOE5V6408Component::dump_config() {
@@ -67,6 +70,11 @@ void PI4IOE5V6408Component::pin_mode(uint8_t pin, gpio::Flags flags) {
this->pull_up_down_mask_ &= ~(1 << pin);
this->pull_enable_mask_ |= 1 << pin;
}
// Enable polling loop for input pins (not needed for interrupt-driven mode
// where the ISR handles re-enabling loop)
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
}
// Write GPIO to enable input mode
this->write_gpio_modes_();
+1 -1
View File
@@ -116,7 +116,7 @@ void SelectCall::perform() {
auto idx = target_index.value();
// All operations use indices, call control() by index to avoid string conversion
ESP_LOGD(TAG, "'%s' - Set selected option to: %s", name, parent->option_at(idx));
ESP_LOGV(TAG, "'%s' - Set selected option to: %s", name, parent->option_at(idx));
parent->control(idx);
}
+3 -3
View File
@@ -18,15 +18,15 @@ void Switch::control(bool target_state) {
}
}
void Switch::turn_on() {
ESP_LOGD(TAG, "'%s' Turning ON.", this->get_name().c_str());
ESP_LOGV(TAG, "'%s' Turning ON.", this->get_name().c_str());
this->write_state(!this->inverted_);
}
void Switch::turn_off() {
ESP_LOGD(TAG, "'%s' Turning OFF.", this->get_name().c_str());
ESP_LOGV(TAG, "'%s' Turning OFF.", this->get_name().c_str());
this->write_state(this->inverted_);
}
void Switch::toggle() {
ESP_LOGD(TAG, "'%s' Toggling %s.", this->get_name().c_str(), this->state ? "OFF" : "ON");
ESP_LOGV(TAG, "'%s' Toggling %s.", this->get_name().c_str(), this->state ? "OFF" : "ON");
this->write_state(this->inverted_ == this->state);
}
optional<bool> Switch::get_initial_state() {
+1 -3
View File
@@ -15,8 +15,6 @@ from typing import Any
import colorama
from esphome.loader import get_platform
root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, "..", "..")))
basepath = os.path.join(root_path, "esphome")
temp_folder = os.path.join(root_path, ".temp")
@@ -644,7 +642,7 @@ def get_all_dependencies(
PLATFORM_HOST,
)
from esphome.core import CORE
from esphome.loader import get_component
from esphome.loader import get_component, get_platform
all_components: set[str] = set(component_names)
@@ -0,0 +1,235 @@
#include <benchmark/benchmark.h>
#include "esphome/components/api/api_pb2.h"
#include "esphome/components/api/api_buffer.h"
#include "esphome/components/light/color_mode.h"
namespace esphome::api::benchmarks {
static constexpr int kInnerIterations = 2000;
// --- ListEntitiesSensorResponse ---
static ListEntitiesSensorResponse make_sensor_response() {
ListEntitiesSensorResponse msg;
msg.object_id = StringRef::from_lit("living_room_temperature");
msg.key = 0x12345678;
msg.name = StringRef::from_lit("Living Room Temperature");
#ifdef USE_ENTITY_ICON
msg.icon = StringRef::from_lit("mdi:thermometer");
#endif
msg.entity_category = enums::ENTITY_CATEGORY_NONE;
msg.disabled_by_default = false;
msg.unit_of_measurement = StringRef::from_lit("°C");
msg.accuracy_decimals = 1;
msg.force_update = false;
msg.device_class = StringRef::from_lit("temperature");
msg.state_class = enums::STATE_CLASS_MEASUREMENT;
#ifdef USE_DEVICES
msg.device_id = 1;
#endif
return msg;
}
static void CalculateSize_ListEntitiesSensorResponse(benchmark::State &state) {
auto msg = make_sensor_response();
for (auto _ : state) {
uint32_t result = 0;
for (int i = 0; i < kInnerIterations; i++) {
result += msg.calculate_size();
}
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalculateSize_ListEntitiesSensorResponse);
static void Encode_ListEntitiesSensorResponse(benchmark::State &state) {
auto msg = make_sensor_response();
APIBuffer buffer;
uint32_t size = msg.calculate_size();
buffer.resize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Encode_ListEntitiesSensorResponse);
static void CalcAndEncode_ListEntitiesSensorResponse(benchmark::State &state) {
auto msg = make_sensor_response();
APIBuffer buffer;
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
uint32_t size = msg.calculate_size();
buffer.resize(size);
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalcAndEncode_ListEntitiesSensorResponse);
// --- ListEntitiesBinarySensorResponse ---
static ListEntitiesBinarySensorResponse make_binary_sensor_response() {
ListEntitiesBinarySensorResponse msg;
msg.object_id = StringRef::from_lit("front_door_contact");
msg.key = 0xAABBCCDD;
msg.name = StringRef::from_lit("Front Door Contact");
#ifdef USE_ENTITY_ICON
msg.icon = StringRef::from_lit("mdi:door");
#endif
msg.entity_category = enums::ENTITY_CATEGORY_NONE;
msg.disabled_by_default = false;
msg.device_class = StringRef::from_lit("door");
msg.is_status_binary_sensor = false;
#ifdef USE_DEVICES
msg.device_id = 2;
#endif
return msg;
}
static void CalculateSize_ListEntitiesBinarySensorResponse(benchmark::State &state) {
auto msg = make_binary_sensor_response();
for (auto _ : state) {
uint32_t result = 0;
for (int i = 0; i < kInnerIterations; i++) {
result += msg.calculate_size();
}
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalculateSize_ListEntitiesBinarySensorResponse);
static void Encode_ListEntitiesBinarySensorResponse(benchmark::State &state) {
auto msg = make_binary_sensor_response();
APIBuffer buffer;
uint32_t size = msg.calculate_size();
buffer.resize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Encode_ListEntitiesBinarySensorResponse);
static void CalcAndEncode_ListEntitiesBinarySensorResponse(benchmark::State &state) {
auto msg = make_binary_sensor_response();
APIBuffer buffer;
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
uint32_t size = msg.calculate_size();
buffer.resize(size);
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalcAndEncode_ListEntitiesBinarySensorResponse);
// --- ListEntitiesLightResponse ---
static light::ColorModeMask light_color_modes;
static FixedVector<const char *> light_effects;
static ListEntitiesLightResponse make_light_response() {
// Initialize static data on first call
static bool initialized = false;
if (!initialized) {
light_color_modes.insert(light::ColorMode::RGB_WHITE);
light_color_modes.insert(light::ColorMode::COLOR_TEMPERATURE);
light_effects.init(3);
light_effects.push_back("None");
light_effects.push_back("Rainbow");
light_effects.push_back("Strobe");
initialized = true;
}
ListEntitiesLightResponse msg;
msg.object_id = StringRef::from_lit("kitchen_ceiling_light");
msg.key = 0x55667788;
msg.name = StringRef::from_lit("Kitchen Ceiling Light");
#ifdef USE_ENTITY_ICON
msg.icon = StringRef::from_lit("mdi:ceiling-light");
#endif
msg.entity_category = enums::ENTITY_CATEGORY_NONE;
msg.disabled_by_default = false;
msg.supported_color_modes = &light_color_modes;
msg.min_mireds = 153.0f;
msg.max_mireds = 500.0f;
msg.effects = &light_effects;
#ifdef USE_DEVICES
msg.device_id = 3;
#endif
return msg;
}
static void CalculateSize_ListEntitiesLightResponse(benchmark::State &state) {
auto msg = make_light_response();
for (auto _ : state) {
uint32_t result = 0;
for (int i = 0; i < kInnerIterations; i++) {
result += msg.calculate_size();
}
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalculateSize_ListEntitiesLightResponse);
static void Encode_ListEntitiesLightResponse(benchmark::State &state) {
auto msg = make_light_response();
APIBuffer buffer;
uint32_t size = msg.calculate_size();
buffer.resize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Encode_ListEntitiesLightResponse);
static void CalcAndEncode_ListEntitiesLightResponse(benchmark::State &state) {
auto msg = make_light_response();
APIBuffer buffer;
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
uint32_t size = msg.calculate_size();
buffer.resize(size);
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalcAndEncode_ListEntitiesLightResponse);
} // namespace esphome::api::benchmarks
+1
View File
@@ -49,6 +49,7 @@ lvgl:
bg_color: 0x000000
bg_opa: cover
theme:
dark_mode: true
obj:
border_width: 1
+2 -2
View File
@@ -1037,7 +1037,7 @@ def test_get_all_dependencies_platform_component() -> None:
with (
patch("esphome.loader.get_component") as mock_get_component,
patch("helpers.get_platform") as mock_get_platform,
patch("esphome.loader.get_platform") as mock_get_platform,
):
mock_get_platform.return_value = platform_comp
mock_get_component.return_value = None
@@ -1061,7 +1061,7 @@ def test_get_all_dependencies_platform_component_with_dependencies() -> None:
with (
patch("esphome.loader.get_component") as mock_get_component,
patch("helpers.get_platform") as mock_get_platform,
patch("esphome.loader.get_platform") as mock_get_platform,
):
mock_get_platform.return_value = platform_comp
mock_get_component.side_effect = lambda name: (