From ac631711ab47019f31b31f9dbb5bf9062a899251 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:30:23 +1100 Subject: [PATCH 01/13] [qspi_dbi] Bugfix and new features (#7979) --- esphome/components/lvgl/__init__.py | 6 +- esphome/components/qspi_dbi/__init__.py | 3 + esphome/components/qspi_dbi/display.py | 123 ++++++----- esphome/components/qspi_dbi/models.py | 247 ++++++++++++++++++++++- esphome/components/qspi_dbi/qspi_dbi.cpp | 25 +-- esphome/components/qspi_dbi/qspi_dbi.h | 4 +- 6 files changed, 339 insertions(+), 69 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 8fdd03f647..b858e8df01 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -23,7 +23,7 @@ from esphome.helpers import write_file_if_changed from . import defines as df, helpers, lv_validation as lvalid from .automation import disp_update, focused_widgets, update_to_code -from .defines import add_define +from .defines import CONF_DRAW_ROUNDING, add_define from .encoders import ( ENCODERS_CONFIG, encoders_to_code, @@ -205,6 +205,10 @@ def final_validation(configs): raise cv.Invalid( "Using auto_clear_enabled: true in display config not compatible with LVGL" ) + if draw_rounding := display.get(CONF_DRAW_ROUNDING): + config[CONF_DRAW_ROUNDING] = max( + draw_rounding, config[CONF_DRAW_ROUNDING] + ) buffer_frac = config[CONF_BUFFER_SIZE] if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config: LOGGER.warning("buffer_size: may need to be reduced without PSRAM") diff --git a/esphome/components/qspi_dbi/__init__.py b/esphome/components/qspi_dbi/__init__.py index c58ce8a01e..a4b833f6d7 100644 --- a/esphome/components/qspi_dbi/__init__.py +++ b/esphome/components/qspi_dbi/__init__.py @@ -1 +1,4 @@ CODEOWNERS = ["@clydebarrow"] + +CONF_DRAW_FROM_ORIGIN = "draw_from_origin" +CONF_DRAW_ROUNDING = "draw_rounding" diff --git a/esphome/components/qspi_dbi/display.py b/esphome/components/qspi_dbi/display.py index 71ae31f182..ab6dd66cf2 100644 --- a/esphome/components/qspi_dbi/display.py +++ b/esphome/components/qspi_dbi/display.py @@ -24,6 +24,7 @@ from esphome.const import ( ) from esphome.core import TimePeriod +from . import CONF_DRAW_FROM_ORIGIN, CONF_DRAW_ROUNDING from .models import DriverChip DEPENDENCIES = ["spi"] @@ -41,7 +42,6 @@ COLOR_ORDERS = { } DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema -CONF_DRAW_FROM_ORIGIN = "draw_from_origin" DELAY_FLAG = 0xFF @@ -78,56 +78,81 @@ def _validate(config): return config -CONFIG_SCHEMA = cv.All( - display.FULL_DISPLAY_SCHEMA.extend( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(QSPI_DBI), - cv.Required(CONF_MODEL): cv.one_of( - *DriverChip.chips.keys(), upper=True - ), - cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence), - cv.Required(CONF_DIMENSIONS): cv.Any( - cv.dimensions, - cv.Schema( - { - cv.Required(CONF_WIDTH): validate_dimension, - cv.Required(CONF_HEIGHT): validate_dimension, - cv.Optional( - CONF_OFFSET_HEIGHT, default=0 - ): validate_dimension, - cv.Optional( - CONF_OFFSET_WIDTH, default=0 - ): validate_dimension, - } - ), - ), - cv.Optional(CONF_TRANSFORM): cv.Schema( +def power_of_two(value): + value = cv.int_range(1, 128)(value) + if value & (value - 1) != 0: + raise cv.Invalid("value must be a power of two") + return value + + +BASE_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(QSPI_DBI), + cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence), + cv.Required(CONF_DIMENSIONS): cv.Any( + cv.dimensions, + cv.Schema( { - cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, - cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, - cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, + cv.Required(CONF_WIDTH): validate_dimension, + cv.Required(CONF_HEIGHT): validate_dimension, + cv.Optional(CONF_OFFSET_HEIGHT, default=0): validate_dimension, + cv.Optional(CONF_OFFSET_WIDTH, default=0): validate_dimension, } ), - cv.Optional(CONF_COLOR_ORDER, default="RGB"): cv.enum( - COLOR_ORDERS, upper=True - ), - cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range( - 0, 0xFF, min_included=True, max_included=True - ), - cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean, - } - ).extend( - spi.spi_device_schema( - cs_pin_required=False, - default_mode="MODE0", - default_data_rate=10e6, - quad=True, - ) + ), + cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range( + 0, 0xFF, min_included=True, max_included=True + ), + } + ).extend( + spi.spi_device_schema( + cs_pin_required=False, + default_mode="MODE0", + default_data_rate=10e6, + quad=True, ) + ) +) + + +def model_property(name, defaults, fallback): + return cv.Optional(name, default=defaults.get(name, fallback)) + + +def model_schema(defaults): + transform = cv.Schema( + { + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + } + ) + if defaults.get(CONF_SWAP_XY, True): + transform = transform.extend( + { + cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, + } + ) + return BASE_SCHEMA.extend( + { + model_property(CONF_INVERT_COLORS, defaults, False): cv.boolean, + model_property(CONF_COLOR_ORDER, defaults, "RGB"): cv.enum( + COLOR_ORDERS, upper=True + ), + model_property(CONF_DRAW_ROUNDING, defaults, 2): power_of_two, + cv.Optional(CONF_TRANSFORM): transform, + } + ) + + +CONFIG_SCHEMA = cv.All( + cv.typed_schema( + {k.upper(): model_schema(v.defaults) for k, v in DriverChip.chips.items()}, + upper=True, + key=CONF_MODEL, ), cv.only_with_esp_idf, ) @@ -152,6 +177,7 @@ async def to_code(config): cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) cg.add(var.set_model(config[CONF_MODEL])) cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN])) + cg.add(var.set_draw_rounding(config[CONF_DRAW_ROUNDING])) if enable_pin := config.get(CONF_ENABLE_PIN): enable = await cg.gpio_pin_expression(enable_pin) cg.add(var.set_enable_pin(enable)) @@ -163,7 +189,8 @@ async def to_code(config): if transform := config.get(CONF_TRANSFORM): cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) - cg.add(var.set_swap_xy(transform[CONF_SWAP_XY])) + # swap_xy is not implemented for some chips + cg.add(var.set_swap_xy(transform.get(CONF_SWAP_XY, False))) if CONF_DIMENSIONS in config: dimensions = config[CONF_DIMENSIONS] diff --git a/esphome/components/qspi_dbi/models.py b/esphome/components/qspi_dbi/models.py index c1fe434853..7ae1a10ec0 100644 --- a/esphome/components/qspi_dbi/models.py +++ b/esphome/components/qspi_dbi/models.py @@ -1,5 +1,10 @@ # Commands +from esphome.const import CONF_INVERT_COLORS, CONF_SWAP_XY + +from . import CONF_DRAW_ROUNDING + SW_RESET_CMD = 0x01 +SLEEP_IN = 0x10 SLEEP_OUT = 0x11 NORON = 0x13 INVERT_OFF = 0x20 @@ -24,11 +29,12 @@ PAGESEL = 0xFE class DriverChip: chips = {} - def __init__(self, name: str): + def __init__(self, name: str, defaults=None): name = name.upper() self.name = name self.chips[name] = self self.initsequence = [] + self.defaults = defaults or {} def cmd(self, c, *args): """ @@ -59,9 +65,246 @@ chip.cmd(TEON, 0x00) chip.cmd(PIXFMT, 0x55) chip.cmd(NORON) -chip = DriverChip("AXS15231") +chip = DriverChip("AXS15231", {CONF_DRAW_ROUNDING: 8, CONF_SWAP_XY: False}) chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) chip.cmd(0xC1, 0x33) chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) +chip = DriverChip( + "JC4832W535", + { + CONF_DRAW_ROUNDING: 8, + CONF_SWAP_XY: False, + }, +) +chip.cmd(DISPLAY_OFF) +chip.delay(20) +chip.cmd(SLEEP_IN) +chip.delay(80) +chip.cmd(SLEEP_OUT) +chip.cmd(INVERT_OFF) +# A magic sequence to enable the windowed drawing mode +chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) +chip.cmd(0xC1, 0x33) +chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + +chip = DriverChip("JC3636W518", {CONF_INVERT_COLORS: True}) +chip.cmd(0xF0, 0x08) +chip.cmd(0xF2, 0x08) +chip.cmd(0x9B, 0x51) +chip.cmd(0x86, 0x53) +chip.cmd(0xF2, 0x80) +chip.cmd(0xF0, 0x00) +chip.cmd(0xF0, 0x01) +chip.cmd(0xF1, 0x01) +chip.cmd(0xB0, 0x54) +chip.cmd(0xB1, 0x3F) +chip.cmd(0xB2, 0x2A) +chip.cmd(0xB4, 0x46) +chip.cmd(0xB5, 0x34) +chip.cmd(0xB6, 0xD5) +chip.cmd(0xB7, 0x30) +chip.cmd(0xBA, 0x00) +chip.cmd(0xBB, 0x08) +chip.cmd(0xBC, 0x08) +chip.cmd(0xBD, 0x00) +chip.cmd(0xC0, 0x80) +chip.cmd(0xC1, 0x10) +chip.cmd(0xC2, 0x37) +chip.cmd(0xC3, 0x80) +chip.cmd(0xC4, 0x10) +chip.cmd(0xC5, 0x37) +chip.cmd(0xC6, 0xA9) +chip.cmd(0xC7, 0x41) +chip.cmd(0xC8, 0x51) +chip.cmd(0xC9, 0xA9) +chip.cmd(0xCA, 0x41) +chip.cmd(0xCB, 0x51) +chip.cmd(0xD0, 0x91) +chip.cmd(0xD1, 0x68) +chip.cmd(0xD2, 0x69) +chip.cmd(0xF5, 0x00, 0xA5) +chip.cmd(0xDD, 0x3F) +chip.cmd(0xDE, 0x3F) +chip.cmd(0xF1, 0x10) +chip.cmd(0xF0, 0x00) +chip.cmd(0xF0, 0x02) +chip.cmd( + 0xE0, + 0x70, + 0x09, + 0x12, + 0x0C, + 0x0B, + 0x27, + 0x38, + 0x54, + 0x4E, + 0x19, + 0x15, + 0x15, + 0x2C, + 0x2F, +) +chip.cmd( + 0xE1, + 0x70, + 0x08, + 0x11, + 0x0C, + 0x0B, + 0x27, + 0x38, + 0x43, + 0x4C, + 0x18, + 0x14, + 0x14, + 0x2B, + 0x2D, +) +chip.cmd(0xF0, 0x10) +chip.cmd(0xF3, 0x10) +chip.cmd(0xE0, 0x08) +chip.cmd(0xE1, 0x00) +chip.cmd(0xE2, 0x00) +chip.cmd(0xE3, 0x00) +chip.cmd(0xE4, 0xE0) +chip.cmd(0xE5, 0x06) +chip.cmd(0xE6, 0x21) +chip.cmd(0xE7, 0x00) +chip.cmd(0xE8, 0x05) +chip.cmd(0xE9, 0x82) +chip.cmd(0xEA, 0xDF) +chip.cmd(0xEB, 0x89) +chip.cmd(0xEC, 0x20) +chip.cmd(0xED, 0x14) +chip.cmd(0xEE, 0xFF) +chip.cmd(0xEF, 0x00) +chip.cmd(0xF8, 0xFF) +chip.cmd(0xF9, 0x00) +chip.cmd(0xFA, 0x00) +chip.cmd(0xFB, 0x30) +chip.cmd(0xFC, 0x00) +chip.cmd(0xFD, 0x00) +chip.cmd(0xFE, 0x00) +chip.cmd(0xFF, 0x00) +chip.cmd(0x60, 0x42) +chip.cmd(0x61, 0xE0) +chip.cmd(0x62, 0x40) +chip.cmd(0x63, 0x40) +chip.cmd(0x64, 0x02) +chip.cmd(0x65, 0x00) +chip.cmd(0x66, 0x40) +chip.cmd(0x67, 0x03) +chip.cmd(0x68, 0x00) +chip.cmd(0x69, 0x00) +chip.cmd(0x6A, 0x00) +chip.cmd(0x6B, 0x00) +chip.cmd(0x70, 0x42) +chip.cmd(0x71, 0xE0) +chip.cmd(0x72, 0x40) +chip.cmd(0x73, 0x40) +chip.cmd(0x74, 0x02) +chip.cmd(0x75, 0x00) +chip.cmd(0x76, 0x40) +chip.cmd(0x77, 0x03) +chip.cmd(0x78, 0x00) +chip.cmd(0x79, 0x00) +chip.cmd(0x7A, 0x00) +chip.cmd(0x7B, 0x00) +chip.cmd(0x80, 0x48) +chip.cmd(0x81, 0x00) +chip.cmd(0x82, 0x05) +chip.cmd(0x83, 0x02) +chip.cmd(0x84, 0xDD) +chip.cmd(0x85, 0x00) +chip.cmd(0x86, 0x00) +chip.cmd(0x87, 0x00) +chip.cmd(0x88, 0x48) +chip.cmd(0x89, 0x00) +chip.cmd(0x8A, 0x07) +chip.cmd(0x8B, 0x02) +chip.cmd(0x8C, 0xDF) +chip.cmd(0x8D, 0x00) +chip.cmd(0x8E, 0x00) +chip.cmd(0x8F, 0x00) +chip.cmd(0x90, 0x48) +chip.cmd(0x91, 0x00) +chip.cmd(0x92, 0x09) +chip.cmd(0x93, 0x02) +chip.cmd(0x94, 0xE1) +chip.cmd(0x95, 0x00) +chip.cmd(0x96, 0x00) +chip.cmd(0x97, 0x00) +chip.cmd(0x98, 0x48) +chip.cmd(0x99, 0x00) +chip.cmd(0x9A, 0x0B) +chip.cmd(0x9B, 0x02) +chip.cmd(0x9C, 0xE3) +chip.cmd(0x9D, 0x00) +chip.cmd(0x9E, 0x00) +chip.cmd(0x9F, 0x00) +chip.cmd(0xA0, 0x48) +chip.cmd(0xA1, 0x00) +chip.cmd(0xA2, 0x04) +chip.cmd(0xA3, 0x02) +chip.cmd(0xA4, 0xDC) +chip.cmd(0xA5, 0x00) +chip.cmd(0xA6, 0x00) +chip.cmd(0xA7, 0x00) +chip.cmd(0xA8, 0x48) +chip.cmd(0xA9, 0x00) +chip.cmd(0xAA, 0x06) +chip.cmd(0xAB, 0x02) +chip.cmd(0xAC, 0xDE) +chip.cmd(0xAD, 0x00) +chip.cmd(0xAE, 0x00) +chip.cmd(0xAF, 0x00) +chip.cmd(0xB0, 0x48) +chip.cmd(0xB1, 0x00) +chip.cmd(0xB2, 0x08) +chip.cmd(0xB3, 0x02) +chip.cmd(0xB4, 0xE0) +chip.cmd(0xB5, 0x00) +chip.cmd(0xB6, 0x00) +chip.cmd(0xB7, 0x00) +chip.cmd(0xB8, 0x48) +chip.cmd(0xB9, 0x00) +chip.cmd(0xBA, 0x0A) +chip.cmd(0xBB, 0x02) +chip.cmd(0xBC, 0xE2) +chip.cmd(0xBD, 0x00) +chip.cmd(0xBE, 0x00) +chip.cmd(0xBF, 0x00) +chip.cmd(0xC0, 0x12) +chip.cmd(0xC1, 0xAA) +chip.cmd(0xC2, 0x65) +chip.cmd(0xC3, 0x74) +chip.cmd(0xC4, 0x47) +chip.cmd(0xC5, 0x56) +chip.cmd(0xC6, 0x00) +chip.cmd(0xC7, 0x88) +chip.cmd(0xC8, 0x99) +chip.cmd(0xC9, 0x33) +chip.cmd(0xD0, 0x21) +chip.cmd(0xD1, 0xAA) +chip.cmd(0xD2, 0x65) +chip.cmd(0xD3, 0x74) +chip.cmd(0xD4, 0x47) +chip.cmd(0xD5, 0x56) +chip.cmd(0xD6, 0x00) +chip.cmd(0xD7, 0x88) +chip.cmd(0xD8, 0x99) +chip.cmd(0xD9, 0x33) +chip.cmd(0xF3, 0x01) +chip.cmd(0xF0, 0x00) +chip.cmd(0xF0, 0x01) +chip.cmd(0xF1, 0x01) +chip.cmd(0xA0, 0x0B) +chip.cmd(0xA3, 0x2A) +chip.cmd(0xA5, 0xC3) +chip.cmd(PIXFMT, 0x55) + + DriverChip("Custom") diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index f8fd5dd374..380c93c400 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -33,19 +33,12 @@ void QspiDbi::update() { this->do_update_(); if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_) return; - // Start addresses and widths/heights must be divisible by 2 (CASET/RASET restriction in datasheet) - if (this->x_low_ % 2 == 1) { - this->x_low_--; - } - if (this->x_high_ % 2 == 0) { - this->x_high_++; - } - if (this->y_low_ % 2 == 1) { - this->y_low_--; - } - if (this->y_high_ % 2 == 0) { - this->y_high_++; - } + // Some chips require that the drawing window be aligned on certain boundaries + auto dr = this->draw_rounding_; + this->x_low_ = this->x_low_ / dr * dr; + this->y_low_ = this->y_low_ / dr * dr; + this->x_high_ = (this->x_high_ + dr) / dr * dr - 1; + this->y_high_ = (this->y_high_ + dr) / dr * dr - 1; if (this->draw_from_origin_) { this->x_low_ = 0; this->y_low_ = 0; @@ -175,10 +168,9 @@ void QspiDbi::write_to_display_(int x_start, int y_start, int w, int h, const ui this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4); } else { auto stride = x_offset + w + x_pad; - uint16_t cmd = 0x2C00; + this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, nullptr, 0, 4); for (int y = 0; y != h; y++) { - this->write_cmd_addr_data(8, 0x32, 24, cmd, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); - cmd = 0x3C00; + this->write_cmd_addr_data(0, 0, 0, 0, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); } } this->disable(); @@ -220,6 +212,7 @@ void QspiDbi::dump_config() { ESP_LOGCONFIG("", "Model: %s", this->model_); ESP_LOGCONFIG(TAG, " Height: %u", this->height_); ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + ESP_LOGCONFIG(TAG, " Draw rounding: %u", this->draw_rounding_); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); diff --git a/esphome/components/qspi_dbi/qspi_dbi.h b/esphome/components/qspi_dbi/qspi_dbi.h index ebb65a8a05..2c555f115e 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.h +++ b/esphome/components/qspi_dbi/qspi_dbi.h @@ -4,12 +4,10 @@ #pragma once #ifdef USE_ESP_IDF -#include "esphome/core/component.h" #include "esphome/components/spi/spi.h" #include "esphome/components/display/display.h" #include "esphome/components/display/display_buffer.h" #include "esphome/components/display/display_color_utils.h" -#include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_rgb.h" @@ -105,6 +103,7 @@ class QspiDbi : public display::DisplayBuffer, int get_height_internal() override { return this->height_; } bool can_proceed() override { return this->setup_complete_; } void add_init_sequence(const std::vector &sequence) { this->init_sequences_.push_back(sequence); } + void set_draw_rounding(unsigned rounding) { this->draw_rounding_ = rounding; } protected: void check_buffer_() { @@ -161,6 +160,7 @@ class QspiDbi : public display::DisplayBuffer, bool mirror_x_{}; bool mirror_y_{}; bool draw_from_origin_{false}; + unsigned draw_rounding_{2}; uint8_t brightness_{0xD0}; const char *model_{"Unknown"}; std::vector> init_sequences_{}; From b33b4481ea031c19280e3148148be40d99484f56 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:40:08 +1100 Subject: [PATCH 02/13] [helpers] Provide calls to get free heap and largest available block. (#7978) --- esphome/components/json/json_util.cpp | 34 ++-------- .../components/online_image/online_image.cpp | 10 +-- esphome/core/helpers.h | 63 ++++++++++++++++--- 3 files changed, 61 insertions(+), 46 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 89ec13fe5b..87b1cc6d2d 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -1,38 +1,20 @@ #include "json_util.h" #include "esphome/core/log.h" -#ifdef USE_ESP8266 -#include -#endif -#ifdef USE_ESP32 -#include -#endif -#ifdef USE_RP2040 -#include -#endif - namespace esphome { namespace json { static const char *const TAG = "json"; static std::vector global_json_build_buffer; // NOLINT +static const auto ALLOCATOR = RAMAllocator(RAMAllocator::ALLOC_INTERNAL); std::string build_json(const json_build_t &f) { // Here we are allocating up to 5kb of memory, // with the heap size minus 2kb to be safe if less than 5kb // as we can not have a true dynamic sized document. // The excess memory is freed below with `shrinkToFit()` -#ifdef USE_ESP8266 - const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) -#elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); -#elif defined(USE_RP2040) - const size_t free_heap = rp2040.getFreeHeap(); -#elif defined(USE_LIBRETINY) - const size_t free_heap = lt_heap_get_free(); -#endif - + auto free_heap = ALLOCATOR.get_max_free_block_size(); size_t request_size = std::min(free_heap, (size_t) 512); while (true) { ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size); @@ -67,20 +49,12 @@ bool parse_json(const std::string &data, const json_parse_t &f) { // with the heap size minus 2kb to be safe if less than that // as we can not have a true dynamic sized document. // The excess memory is freed below with `shrinkToFit()` -#ifdef USE_ESP8266 - const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) -#elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); -#elif defined(USE_RP2040) - const size_t free_heap = rp2040.getFreeHeap(); -#elif defined(USE_LIBRETINY) - const size_t free_heap = lt_heap_get_free(); -#endif + auto free_heap = ALLOCATOR.get_max_free_block_size(); size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); while (true) { DynamicJsonDocument json_document(request_size); if (json_document.capacity() == 0) { - ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, + ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %zu bytes, free heap: %zu", request_size, free_heap); return false; } diff --git a/esphome/components/online_image/online_image.cpp b/esphome/components/online_image/online_image.cpp index 1786809dfa..8c4669cba5 100644 --- a/esphome/components/online_image/online_image.cpp +++ b/esphome/components/online_image/online_image.cpp @@ -80,15 +80,7 @@ bool OnlineImage::resize_(int width_in, int height_in) { this->width_ = width; ESP_LOGD(TAG, "New size: (%d, %d)", width, height); } else { -#if defined(USE_ESP8266) - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - int max_block = ESP.getMaxFreeBlockSize(); -#elif defined(USE_ESP32) - int max_block = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); -#else - int max_block = -1; -#endif - ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %d Bytes", max_block); + ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %zu Bytes", this->allocator_.get_max_free_block_size()); this->end_connection_(); return false; } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index fcbd8d8683..c823439fb3 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -11,6 +11,14 @@ #include "esphome/core/optional.h" +#ifdef USE_ESP8266 +#include +#endif + +#ifdef USE_RP2040 +#include +#endif + #ifdef USE_ESP32 #include #endif @@ -684,20 +692,23 @@ template class RAMAllocator { }; RAMAllocator() = default; - RAMAllocator(uint8_t flags) : flags_{flags} {} + RAMAllocator(uint8_t flags) { + // default is both external and internal + flags &= ALLOC_INTERNAL | ALLOC_EXTERNAL; + if (flags != 0) + this->flags_ = flags; + } template constexpr RAMAllocator(const RAMAllocator &other) : flags_{other.flags_} {} T *allocate(size_t n) { size_t size = n * sizeof(T); T *ptr = nullptr; #ifdef USE_ESP32 - // External allocation by default or if explicitely requested - if ((this->flags_ & Flags::ALLOC_EXTERNAL) || ((this->flags_ & Flags::ALLOC_INTERNAL) == 0)) { + if (this->flags_ & Flags::ALLOC_EXTERNAL) { ptr = static_cast(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); } - // Fallback to internal allocation if explicitely requested or no flag is specified - if (ptr == nullptr && ((this->flags_ & Flags::ALLOC_INTERNAL) || (this->flags_ & Flags::ALLOC_EXTERNAL) == 0)) { - ptr = static_cast(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) + if (ptr == nullptr && this->flags_ & Flags::ALLOC_INTERNAL) { + ptr = static_cast(heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); } #else // Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported @@ -710,8 +721,46 @@ template class RAMAllocator { free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) } + /** + * Return the total heap space available via this allocator + */ + size_t get_free_heap_size() const { +#ifdef USE_ESP8266 + return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP32) + auto max_internal = + this->flags_ & ALLOC_INTERNAL ? heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL) : 0; + auto max_external = + this->flags_ & ALLOC_EXTERNAL ? heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM) : 0; + return max_internal + max_external; +#elif defined(USE_RP2040) + return ::rp2040.getFreeHeap(); +#elif defined(USE_LIBRETINY) + return lt_heap_get_free(); +#else + return 100000; +#endif + } + + /** + * Return the maximum size block this allocator could allocate. This may be an approximation on some platforms + */ + size_t get_max_free_block_size() const { +#ifdef USE_ESP8266 + return ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP32) + auto max_internal = + this->flags_ & ALLOC_INTERNAL ? heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL) : 0; + auto max_external = + this->flags_ & ALLOC_EXTERNAL ? heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM) : 0; + return std::max(max_internal, max_external); +#else + return this->get_free_heap_size(); +#endif + } + private: - uint8_t flags_{Flags::ALLOW_FAILURE}; + uint8_t flags_{ALLOC_INTERNAL | ALLOC_EXTERNAL}; }; template using ExternalRAMAllocator = RAMAllocator; From 7da07303c934287837797a40b05526460a6734aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:42:29 +1300 Subject: [PATCH 03/13] Bump actions/upload-artifact from 4.4.3 to 4.5.0 (#7981) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6cd86854b2..0304cd2304 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -141,7 +141,7 @@ jobs: echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT - name: Upload digests - uses: actions/upload-artifact@v4.4.3 + uses: actions/upload-artifact@v4.5.0 with: name: digests-${{ steps.sanitize.outputs.name }} path: /tmp/digests From 434879ea0464ed65f24f3446c80412c90cd37963 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Wed, 18 Dec 2024 21:07:07 -0500 Subject: [PATCH 04/13] [core] Bugfix: Implement ring buffer with xRingbuffer (#7973) --- esphome/core/ring_buffer.cpp | 90 +++++++++++++++++++++++++++++------- esphome/core/ring_buffer.h | 13 ++++-- 2 files changed, 82 insertions(+), 21 deletions(-) diff --git a/esphome/core/ring_buffer.cpp b/esphome/core/ring_buffer.cpp index 6152ada314..f779531263 100644 --- a/esphome/core/ring_buffer.cpp +++ b/esphome/core/ring_buffer.cpp @@ -13,8 +13,8 @@ static const char *const TAG = "ring_buffer"; RingBuffer::~RingBuffer() { if (this->handle_ != nullptr) { - vStreamBufferDelete(this->handle_); - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + vRingbufferDelete(this->handle_); + RAMAllocator allocator(RAMAllocator::ALLOW_FAILURE); allocator.deallocate(this->storage_, this->size_); } } @@ -22,26 +22,49 @@ RingBuffer::~RingBuffer() { std::unique_ptr RingBuffer::create(size_t len) { std::unique_ptr rb = make_unique(); - rb->size_ = len + 1; + rb->size_ = len; - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + RAMAllocator allocator(RAMAllocator::ALLOW_FAILURE); rb->storage_ = allocator.allocate(rb->size_); if (rb->storage_ == nullptr) { return nullptr; } - rb->handle_ = xStreamBufferCreateStatic(rb->size_, 1, rb->storage_, &rb->structure_); + rb->handle_ = xRingbufferCreateStatic(rb->size_, RINGBUF_TYPE_BYTEBUF, rb->storage_, &rb->structure_); ESP_LOGD(TAG, "Created ring buffer with size %u", len); + return rb; } size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { - if (ticks_to_wait > 0) - xStreamBufferSetTriggerLevel(this->handle_, len); + size_t bytes_read = 0; - size_t bytes_read = xStreamBufferReceive(this->handle_, data, len, ticks_to_wait); + void *buffer_data = xRingbufferReceiveUpTo(this->handle_, &bytes_read, ticks_to_wait, len); - xStreamBufferSetTriggerLevel(this->handle_, 1); + if (buffer_data == nullptr) { + return 0; + } + + std::memcpy(data, buffer_data, bytes_read); + + vRingbufferReturnItem(this->handle_, buffer_data); + + if (bytes_read < len) { + // Data may have wrapped around, so read a second time to receive the remainder + size_t follow_up_bytes_read = 0; + size_t bytes_remaining = len - bytes_read; + + buffer_data = xRingbufferReceiveUpTo(this->handle_, &follow_up_bytes_read, 0, bytes_remaining); + + if (buffer_data == nullptr) { + return bytes_read; + } + + std::memcpy((void *) ((uint8_t *) (data) + bytes_read), buffer_data, follow_up_bytes_read); + + vRingbufferReturnItem(this->handle_, buffer_data); + bytes_read += follow_up_bytes_read; + } return bytes_read; } @@ -49,22 +72,55 @@ size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { size_t RingBuffer::write(const void *data, size_t len) { size_t free = this->free(); if (free < len) { - size_t needed = len - free; - uint8_t discard[needed]; - xStreamBufferReceive(this->handle_, discard, needed, 0); + // Free enough space in the ring buffer to fit the new data + this->discard_bytes_(len - free); } - return xStreamBufferSend(this->handle_, data, len, 0); + return this->write_without_replacement(data, len, 0); } size_t RingBuffer::write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait) { - return xStreamBufferSend(this->handle_, data, len, ticks_to_wait); + if (!xRingbufferSend(this->handle_, data, len, ticks_to_wait)) { + // Couldn't fit all the data, so only write what will fit + size_t free = std::min(this->free(), len); + if (xRingbufferSend(this->handle_, data, free, 0)) { + return free; + } + return 0; + } + return len; } -size_t RingBuffer::available() const { return xStreamBufferBytesAvailable(this->handle_); } +size_t RingBuffer::available() const { + UBaseType_t ux_items_waiting = 0; + vRingbufferGetInfo(this->handle_, nullptr, nullptr, nullptr, nullptr, &ux_items_waiting); + return ux_items_waiting; +} -size_t RingBuffer::free() const { return xStreamBufferSpacesAvailable(this->handle_); } +size_t RingBuffer::free() const { return xRingbufferGetCurFreeSize(this->handle_); } -BaseType_t RingBuffer::reset() { return xStreamBufferReset(this->handle_); } +BaseType_t RingBuffer::reset() { + // Discards all the available data + return this->discard_bytes_(this->available()); +} + +bool RingBuffer::discard_bytes_(size_t discard_bytes) { + size_t bytes_read = 0; + + void *buffer_data = xRingbufferReceiveUpTo(this->handle_, &bytes_read, 0, discard_bytes); + if (buffer_data != nullptr) + vRingbufferReturnItem(this->handle_, buffer_data); + + if (bytes_read < discard_bytes) { + size_t wrapped_bytes_read = 0; + buffer_data = xRingbufferReceiveUpTo(this->handle_, &wrapped_bytes_read, 0, discard_bytes - bytes_read); + if (buffer_data != nullptr) { + vRingbufferReturnItem(this->handle_, buffer_data); + bytes_read += wrapped_bytes_read; + } + } + + return (bytes_read == discard_bytes); +} } // namespace esphome diff --git a/esphome/core/ring_buffer.h b/esphome/core/ring_buffer.h index aade1b5f49..bad96d3181 100644 --- a/esphome/core/ring_buffer.h +++ b/esphome/core/ring_buffer.h @@ -3,7 +3,7 @@ #ifdef USE_ESP32 #include -#include +#include #include #include @@ -82,9 +82,14 @@ class RingBuffer { static std::unique_ptr create(size_t len); protected: - StreamBufferHandle_t handle_; - StaticStreamBuffer_t structure_; - uint8_t *storage_; + /// @brief Discards data from the ring buffer. + /// @param discard_bytes amount of bytes to discard + /// @return True if all bytes were successfully discarded, false otherwise + bool discard_bytes_(size_t discard_bytes); + + RingbufHandle_t handle_{nullptr}; + StaticRingbuffer_t structure_; + uint8_t *storage_{nullptr}; size_t size_{0}; }; From d28cf011d116c0cfb5f93130d13da131c62f97fb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:07:43 +1300 Subject: [PATCH 05/13] Bump version to 2024.12.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index e9d7612e4a..5b87cad8e1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.12.0" +__version__ = "2024.12.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From f33b4a714ecd4e43d9f2f27f457d1dc8bad70a29 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Fri, 20 Dec 2024 01:45:40 +0100 Subject: [PATCH 06/13] [esp32_ble] do not skip events if queue is blocked (#7960) --- esphome/components/esp32_ble/queue.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_ble/queue.h b/esphome/components/esp32_ble/queue.h index 5b31b97ae2..c98477e121 100644 --- a/esphome/components/esp32_ble/queue.h +++ b/esphome/components/esp32_ble/queue.h @@ -26,10 +26,10 @@ template class Queue { void push(T *element) { if (element == nullptr) return; - if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { - q_.push(element); - xSemaphoreGive(m_); - } + // It is not called from main loop. Thus it won't block main thread. + xSemaphoreTake(m_, portMAX_DELAY); + q_.push(element); + xSemaphoreGive(m_); } T *pop() { From ba2edbc18909953e0de22ee95e57726d476e8f05 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 20 Dec 2024 01:28:08 -0600 Subject: [PATCH 07/13] [esp32] Fix flash size warning when using IDF (#7983) --- esphome/components/esp32/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index b0bde75451..98db45831a 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -602,6 +602,9 @@ async def to_code(config): cg.add_platformio_option( "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] ) + add_idf_sdkconfig_option( + f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True + ) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) add_idf_sdkconfig_option( From f3cb179f54e7b86133e9707b6ad5dcd41eeda989 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 20 Dec 2024 14:16:18 -0600 Subject: [PATCH 08/13] [esp32_ble] Fix for Improv (#7984) --- esphome/components/esp32_ble/ble_advertising.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 92b7c60368..1d340c76d9 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() { esp_err_t err; this->advertising_data_.set_scan_rsp = false; - this->advertising_data_.include_name = true; + this->advertising_data_.include_name = !this->scan_response_; this->advertising_data_.include_txpower = !this->scan_response_; err = esp_ble_gap_config_adv_data(&this->advertising_data_); if (err != ESP_OK) { From 37fcccbb1c28507b89c70b015b3e119f60181f88 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 20 Dec 2024 01:28:08 -0600 Subject: [PATCH 09/13] [esp32] Fix flash size warning when using IDF (#7983) --- esphome/components/esp32/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index b0bde75451..98db45831a 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -602,6 +602,9 @@ async def to_code(config): cg.add_platformio_option( "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] ) + add_idf_sdkconfig_option( + f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True + ) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) add_idf_sdkconfig_option( From 69f1a81e1ddaa97ef4444cb551784787dd97fb17 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 20 Dec 2024 14:16:18 -0600 Subject: [PATCH 10/13] [esp32_ble] Fix for Improv (#7984) --- esphome/components/esp32_ble/ble_advertising.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 92b7c60368..1d340c76d9 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() { esp_err_t err; this->advertising_data_.set_scan_rsp = false; - this->advertising_data_.include_name = true; + this->advertising_data_.include_name = !this->scan_response_; this->advertising_data_.include_txpower = !this->scan_response_; err = esp_ble_gap_config_adv_data(&this->advertising_data_); if (err != ESP_OK) { From 499953e3f4627b146e69fd1f3c8ab6dda2de7a5f Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 20 Dec 2024 14:34:11 -0600 Subject: [PATCH 11/13] Bump version to 2024.12.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5b87cad8e1..10ad9454fd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.12.1" +__version__ = "2024.12.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 45beea68eb5c8c5b663b979515e37a0cffedc65c Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Mon, 23 Dec 2024 06:49:04 +0100 Subject: [PATCH 12/13] [ble_client, bluetooth_proxy, esp32_ble_client, esp32_ble_tracker] fix ble proxy stop working (#7901) Co-authored-by: J. Nick Koston --- esphome/components/ble_client/ble_client.cpp | 3 +- .../bluetooth_proxy/bluetooth_connection.cpp | 5 ++ .../bluetooth_proxy/bluetooth_connection.h | 1 + .../esp32_ble_client/ble_client_base.cpp | 46 +++++++++++++++++++ .../esp32_ble_client/ble_client_base.h | 2 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 43 +++++++++++++---- .../esp32_ble_tracker/esp32_ble_tracker.h | 12 +++-- 7 files changed, 96 insertions(+), 16 deletions(-) diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 19cf2bc1f3..5cf096c9d4 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -25,8 +25,7 @@ void BLEClient::loop() { void BLEClient::dump_config() { ESP_LOGCONFIG(TAG, "BLE Client:"); - ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str()); - ESP_LOGCONFIG(TAG, " Auto-Connect: %s", TRUEFALSE(this->auto_connect_)); + BLEClientBase::dump_config(); } bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 543752853e..b63f7ccde9 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -13,6 +13,11 @@ namespace bluetooth_proxy { static const char *const TAG = "bluetooth_proxy.connection"; +void BluetoothConnection::dump_config() { + ESP_LOGCONFIG(TAG, "BLE Connection:"); + BLEClientBase::dump_config(); +} + bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { if (!BLEClientBase::gattc_event_handler(event, gattc_if, param)) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index e6ab3cbccc..fd83f8dd00 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -11,6 +11,7 @@ class BluetoothProxy; class BluetoothConnection : public esp32_ble_client::BLEClientBase { public: + void dump_config() override; bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 98e7792792..53c430350c 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -44,6 +44,50 @@ void BLEClientBase::loop() { float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } +void BLEClientBase::dump_config() { + ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str()); + ESP_LOGCONFIG(TAG, " Auto-Connect: %s", TRUEFALSE(this->auto_connect_)); + std::string state_name; + switch (this->state()) { + case espbt::ClientState::INIT: + state_name = "INIT"; + break; + case espbt::ClientState::DISCONNECTING: + state_name = "DISCONNECTING"; + break; + case espbt::ClientState::IDLE: + state_name = "IDLE"; + break; + case espbt::ClientState::SEARCHING: + state_name = "SEARCHING"; + break; + case espbt::ClientState::DISCOVERED: + state_name = "DISCOVERED"; + break; + case espbt::ClientState::READY_TO_CONNECT: + state_name = "READY_TO_CONNECT"; + break; + case espbt::ClientState::CONNECTING: + state_name = "CONNECTING"; + break; + case espbt::ClientState::CONNECTED: + state_name = "CONNECTED"; + break; + case espbt::ClientState::ESTABLISHED: + state_name = "ESTABLISHED"; + break; + default: + state_name = "UNKNOWN_STATE"; + break; + } + ESP_LOGCONFIG(TAG, " State: %s", state_name.c_str()); + if (this->status_ == ESP_GATT_NO_RESOURCES) { + ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config."); + } else if (this->status_ != ESP_GATT_OK) { + ESP_LOGW(TAG, " Failed due to error code %d", this->status_); + } +} + bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { if (!this->auto_connect_) return false; @@ -129,6 +173,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } else { ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_, this->address_str_.c_str(), param->reg.app_id, param->reg.status); + this->status_ = param->reg.status; + this->mark_failed(); } break; } diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index fca66c0b3c..84c35c4633 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -26,6 +26,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { void setup() override; void loop() override; float get_setup_priority() const override; + void dump_config() override; void run_later(std::function &&f); // NOLINT bool parse_device(const espbt::ESPBTDevice &device) override; @@ -103,6 +104,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { bool paired_{false}; espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; std::vector services_; + esp_gatt_status_t status_{ESP_GATT_OK}; void log_event_(const char *name); }; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 6d051e3d4a..5fff9dbcad 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -58,7 +58,6 @@ void ESP32BLETracker::setup() { global_esp32_ble_tracker = this; this->scan_result_lock_ = xSemaphoreCreateMutex(); this->scan_end_lock_ = xSemaphoreCreateMutex(); - this->scanner_idle_ = true; #ifdef USE_OTA ota::get_global_ota_callback()->add_on_state_callback( @@ -107,6 +106,15 @@ void ESP32BLETracker::loop() { break; } } + if (connecting != connecting_ || discovered != discovered_ || searching != searching_ || + disconnecting != disconnecting_) { + connecting_ = connecting; + discovered_ = discovered; + searching_ = searching; + disconnecting_ = disconnecting; + ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_, + searching_, disconnecting_); + } bool promote_to_connecting = discovered && !searching && !connecting; if (!this->scanner_idle_) { @@ -183,8 +191,9 @@ void ESP32BLETracker::loop() { } if (this->scan_start_failed_ || this->scan_set_param_failed_) { - if (this->scan_start_fail_count_ == 255) { - ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after 255 attempts, rebooting to restore BLE stack..."); + if (this->scan_start_fail_count_ == std::numeric_limits::max()) { + ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after %d attempts, rebooting to restore BLE stack...", + std::numeric_limits::max()); App.reboot(); } if (xSemaphoreTake(this->scan_end_lock_, 0L)) { @@ -282,6 +291,12 @@ void ESP32BLETracker::start_scan_(bool first) { this->scan_params_.scan_interval = this->scan_interval_; this->scan_params_.scan_window = this->scan_window_; + // Start timeout before scan is started. Otherwise scan never starts if any error. + this->set_timeout("scan", this->scan_duration_ * 2000, []() { + ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack..."); + App.reboot(); + }); + esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err); @@ -293,11 +308,6 @@ void ESP32BLETracker::start_scan_(bool first) { return; } this->scanner_idle_ = false; - - this->set_timeout("scan", this->scan_duration_ * 2000, []() { - ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack..."); - App.reboot(); - }); } void ESP32BLETracker::end_of_scan_() { @@ -371,6 +381,7 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga } void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { + ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status); if (param.status == ESP_BT_STATUS_DONE) { this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS; } else { @@ -379,20 +390,25 @@ void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t: } void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) { + ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status); this->scan_start_failed_ = param.status; if (param.status == ESP_BT_STATUS_SUCCESS) { this->scan_start_fail_count_ = 0; } else { - this->scan_start_fail_count_++; + if (this->scan_start_fail_count_ != std::numeric_limits::max()) { + this->scan_start_fail_count_++; + } xSemaphoreGive(this->scan_end_lock_); } } void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) { + ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status); xSemaphoreGive(this->scan_end_lock_); } void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { + ESP_LOGV(TAG, "gap_scan_result - event %d", param.search_evt); if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { if (xSemaphoreTake(this->scan_result_lock_, 0L)) { if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { @@ -663,7 +679,14 @@ void ESP32BLETracker::dump_config() { ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); - ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", this->scan_continuous_ ? "True" : "False"); + ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", YESNO(this->scan_continuous_)); + ESP_LOGCONFIG(TAG, " Scanner Idle: %s", YESNO(this->scanner_idle_)); + ESP_LOGCONFIG(TAG, " Scan End: %s", YESNO(xSemaphoreGetMutexHolder(this->scan_end_lock_) == nullptr)); + ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_, + searching_, disconnecting_); + if (this->scan_start_fail_count_) { + ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_); + } } void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 2fc5da829d..52b091619e 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -178,7 +178,7 @@ class ESPBTClient : public ESPBTDeviceListener { int app_id; protected: - ClientState state_; + ClientState state_{ClientState::INIT}; }; class ESP32BLETracker : public Component, @@ -229,7 +229,7 @@ class ESP32BLETracker : public Component, /// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received. void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m); - int app_id_; + int app_id_{0}; /// Vector of addresses that have already been printed in print_bt_device_info std::vector already_discovered_; @@ -242,10 +242,10 @@ class ESP32BLETracker : public Component, uint32_t scan_duration_; uint32_t scan_interval_; uint32_t scan_window_; - uint8_t scan_start_fail_count_; + uint8_t scan_start_fail_count_{0}; bool scan_continuous_; bool scan_active_; - bool scanner_idle_; + bool scanner_idle_{true}; bool ble_was_disabled_{true}; bool raw_advertisements_{false}; bool parse_advertisements_{false}; @@ -260,6 +260,10 @@ class ESP32BLETracker : public Component, esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_; esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; + int connecting_{0}; + int discovered_{0}; + int searching_{0}; + int disconnecting_{0}; }; // NOLINTNEXTLINE From 387bde665e3ae5d4b8d5a68299abd6032c4291b8 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 24 Dec 2024 04:15:40 -0500 Subject: [PATCH 13/13] [esp32_rmt] IDF 5+ update fixes (#8002) Co-authored-by: Jonathan Swoboda Co-authored-by: Keith Burzinski --- .../remote_receiver/remote_receiver_esp32.cpp | 8 ++--- .../components/remote_transmitter/__init__.py | 9 +++++ .../remote_transmitter/remote_transmitter.h | 3 ++ .../remote_transmitter_esp32.cpp | 34 ++++++++++++++++--- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index 48b84d61d4..8a36971e36 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -154,16 +154,16 @@ void RemoteReceiverComponent::setup() { void RemoteReceiverComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Receiver:"); LOG_PIN(" Pin: ", this->pin_); - if (this->pin_->digital_read()) { - ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to " - "invert the signal using 'inverted: True' in the pin schema!"); - } #if ESP_IDF_VERSION_MAJOR >= 5 ESP_LOGCONFIG(TAG, " Clock resolution: %" PRIu32 " hz", this->clock_resolution_); ESP_LOGCONFIG(TAG, " RMT symbols: %" PRIu32, this->rmt_symbols_); ESP_LOGCONFIG(TAG, " Filter symbols: %" PRIu32, this->filter_symbols_); ESP_LOGCONFIG(TAG, " Receive symbols: %" PRIu32, this->receive_symbols_); #else + if (this->pin_->digital_read()) { + ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to " + "invert the signal using 'inverted: True' in the pin schema!"); + } ESP_LOGCONFIG(TAG, " Channel: %d", this->channel_); ESP_LOGCONFIG(TAG, " RMT memory blocks: %d", this->mem_block_num_); ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_); diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index ea29751671..e3462fb246 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_CLOCK_DIVIDER, CONF_CLOCK_RESOLUTION, CONF_ID, + CONF_INVERTED, CONF_PIN, CONF_RMT_CHANNEL, CONF_RMT_SYMBOLS, @@ -16,6 +17,7 @@ from esphome.core import CORE AUTO_LOAD = ["remote_base"] +CONF_EOT_LEVEL = "eot_level" CONF_ON_TRANSMIT = "on_transmit" CONF_ON_COMPLETE = "on_complete" CONF_ONE_WIRE = "one_wire" @@ -41,6 +43,7 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_CLOCK_DIVIDER): cv.All( cv.only_on_esp32, cv.only_with_arduino, cv.int_range(min=1, max=255) ), + cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_with_esp_idf, cv.boolean), cv.Optional(CONF_ONE_WIRE): cv.All(cv.only_with_esp_idf, cv.boolean), cv.Optional(CONF_USE_DMA): cv.All(cv.only_with_esp_idf, cv.boolean), cv.SplitDefault( @@ -73,6 +76,12 @@ async def to_code(config): cg.add(var.set_with_dma(config[CONF_USE_DMA])) if CONF_ONE_WIRE in config: cg.add(var.set_one_wire(config[CONF_ONE_WIRE])) + if CONF_EOT_LEVEL in config: + cg.add(var.set_eot_level(config[CONF_EOT_LEVEL])) + elif CONF_ONE_WIRE in config and config[CONF_ONE_WIRE]: + cg.add(var.set_eot_level(True)) + elif CONF_INVERTED in config[CONF_PIN] and config[CONF_PIN][CONF_INVERTED]: + cg.add(var.set_eot_level(True)) else: if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel) diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 20b98d5488..fd1d182063 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -41,6 +41,8 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } void set_one_wire(bool one_wire) { this->one_wire_ = one_wire; } + void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; } + void digital_write(bool value); #endif Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; @@ -68,6 +70,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, std::vector rmt_temp_; bool with_dma_{false}; bool one_wire_{false}; + bool eot_level_{false}; rmt_channel_handle_t channel_{NULL}; rmt_encoder_handle_t encoder_{NULL}; #else diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index efb1735bfd..cd7f366373 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -11,6 +11,7 @@ static const char *const TAG = "remote_transmitter"; void RemoteTransmitterComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Remote Transmitter..."); + this->inverted_ = this->pin_->is_inverted(); this->configure_rmt_(); } @@ -37,6 +38,31 @@ void RemoteTransmitterComponent::dump_config() { } } +#if ESP_IDF_VERSION_MAJOR >= 5 +void RemoteTransmitterComponent::digital_write(bool value) { + rmt_symbol_word_t symbol = { + .duration0 = 1, + .level0 = value, + .duration1 = 0, + .level1 = value, + }; + rmt_transmit_config_t config; + memset(&config, 0, sizeof(config)); + config.loop_count = 0; + config.flags.eot_level = value; + esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config); + if (error != ESP_OK) { + ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error)); + this->status_set_warning(); + } + error = rmt_tx_wait_all_done(this->channel_, -1); + if (error != ESP_OK) { + ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); + this->status_set_warning(); + } +} +#endif + void RemoteTransmitterComponent::configure_rmt_() { #if ESP_IDF_VERSION_MAJOR >= 5 esp_err_t error; @@ -83,6 +109,7 @@ void RemoteTransmitterComponent::configure_rmt_() { this->mark_failed(); return; } + this->digital_write(this->one_wire_ || this->inverted_); this->initialized_ = true; } @@ -120,13 +147,12 @@ void RemoteTransmitterComponent::configure_rmt_() { } c.tx_config.idle_output_en = true; - if (!this->pin_->is_inverted()) { + if (!this->inverted_) { c.tx_config.carrier_level = RMT_CARRIER_LEVEL_HIGH; c.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; } else { c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; - this->inverted_ = true; } esp_err_t error = rmt_config(&c); @@ -210,7 +236,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen rmt_transmit_config_t config; memset(&config, 0, sizeof(config)); config.loop_count = 0; - config.flags.eot_level = this->inverted_; + config.flags.eot_level = this->eot_level_; esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(), this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config); if (error != ESP_OK) { @@ -223,8 +249,6 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (error != ESP_OK) { ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); this->status_set_warning(); - } else { - this->status_clear_warning(); } if (i + 1 < send_times) delayMicroseconds(send_wait);