From 6262fb8fcf9538d161d94512a857755c839eb48d Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 14 Jan 2025 08:32:54 +1100 Subject: [PATCH 01/36] [lvgl] fix tests (#8075) --- tests/components/lvgl/lvgl-package.yaml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 234fd78678..7c59cfa171 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -795,6 +795,19 @@ lvgl: color: 0xA0A0A0 r_mod: -20 opa: 0% + - id: page3 + widgets: + - keyboard: + id: lv_keyboard + align: bottom_mid + on_value: + then: + - logger.log: + format: "keyboard value %s" + args: [text.c_str()] + - keyboard: + id: lv_keyboard1 + mode: special font: - file: "gfonts://Roboto" @@ -805,10 +818,13 @@ image: - id: cat_image resize: 256x48 file: $component_dir/logo-text.svg + type: RGB565 + use_transparency: alpha_channel - id: dog_image file: $component_dir/logo-text.svg resize: 256x48 - type: TRANSPARENT_BINARY + type: BINARY + use_transparency: chroma_key color: - id: light_blue From bdb1094b477baad40cfcf9d9bc86665c14069a55 Mon Sep 17 00:00:00 2001 From: Stefan Rado <628587+kroimon@users.noreply.github.com> Date: Tue, 14 Jan 2025 04:20:52 +0100 Subject: [PATCH 02/36] Allow external libraries to use ESP_LOGx macros (#8078) --- esphome/components/lvgl/lvgl_esphome.cpp | 20 +++++--------------- esphome/core/log.h | 14 +++++++------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 5abeead9d8..a9fe56fb32 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -501,9 +501,7 @@ size_t lv_millis(void) { return esphome::millis(); } void *lv_custom_mem_alloc(size_t size) { auto *ptr = malloc(size); // NOLINT if (ptr == nullptr) { -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR - esphome::ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size); -#endif + ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size); } return ptr; } @@ -520,30 +518,22 @@ void *lv_custom_mem_alloc(size_t size) { ptr = heap_caps_malloc(size, cap_bits); } if (ptr == nullptr) { -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR - esphome::ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size); -#endif + ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size); return nullptr; } -#ifdef ESPHOME_LOG_HAS_VERBOSE - esphome::ESP_LOGV(esphome::lvgl::TAG, "allocate %zu - > %p", size, ptr); -#endif + ESP_LOGV(esphome::lvgl::TAG, "allocate %zu - > %p", size, ptr); return ptr; } void lv_custom_mem_free(void *ptr) { -#ifdef ESPHOME_LOG_HAS_VERBOSE - esphome::ESP_LOGV(esphome::lvgl::TAG, "free %p", ptr); -#endif + ESP_LOGV(esphome::lvgl::TAG, "free %p", ptr); if (ptr == nullptr) return; heap_caps_free(ptr); } void *lv_custom_mem_realloc(void *ptr, size_t size) { -#ifdef ESPHOME_LOG_HAS_VERBOSE - esphome::ESP_LOGV(esphome::lvgl::TAG, "realloc %p: %zu", ptr, size); -#endif + ESP_LOGV(esphome::lvgl::TAG, "realloc %p: %zu", ptr, size); return heap_caps_realloc(ptr, size, cap_bits); } #endif diff --git a/esphome/core/log.h b/esphome/core/log.h index 86af534f98..99a68024c5 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -74,7 +74,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE #define esph_log_vv(tag, format, ...) \ - esp_log_printf_(ESPHOME_LOG_LEVEL_VERY_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) + ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_VERY_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) #define ESPHOME_LOG_HAS_VERY_VERBOSE #else @@ -83,7 +83,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE #define esph_log_v(tag, format, ...) \ - esp_log_printf_(ESPHOME_LOG_LEVEL_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) + ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) #define ESPHOME_LOG_HAS_VERBOSE #else @@ -92,9 +92,9 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG #define esph_log_d(tag, format, ...) \ - esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) + ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) #define esph_log_config(tag, format, ...) \ - esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) + ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) #define ESPHOME_LOG_HAS_DEBUG #define ESPHOME_LOG_HAS_CONFIG @@ -105,7 +105,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_INFO #define esph_log_i(tag, format, ...) \ - esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) + ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) #define ESPHOME_LOG_HAS_INFO #else @@ -114,7 +114,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN #define esph_log_w(tag, format, ...) \ - esp_log_printf_(ESPHOME_LOG_LEVEL_WARN, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) + ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_WARN, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) #define ESPHOME_LOG_HAS_WARN #else @@ -123,7 +123,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR #define esph_log_e(tag, format, ...) \ - esp_log_printf_(ESPHOME_LOG_LEVEL_ERROR, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) + ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_ERROR, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__) #define ESPHOME_LOG_HAS_ERROR #else From fc2b15e307833359f079aec46ceffd30b9398abe Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:27:47 +1100 Subject: [PATCH 03/36] [uptime] Add text_sensor (#8028) --- .../uptime/{sensor.py => sensor/__init__.py} | 6 +-- .../{ => sensor}/uptime_seconds_sensor.cpp | 0 .../{ => sensor}/uptime_seconds_sensor.h | 0 .../{ => sensor}/uptime_timestamp_sensor.cpp | 0 .../{ => sensor}/uptime_timestamp_sensor.h | 0 .../components/uptime/text_sensor/__init__.py | 19 ++++++++ .../uptime/text_sensor/uptime_text_sensor.cpp | 46 +++++++++++++++++++ .../uptime/text_sensor/uptime_text_sensor.h | 25 ++++++++++ tests/components/uptime/common.yaml | 4 ++ 9 files changed, 97 insertions(+), 3 deletions(-) rename esphome/components/uptime/{sensor.py => sensor/__init__.py} (100%) rename esphome/components/uptime/{ => sensor}/uptime_seconds_sensor.cpp (100%) rename esphome/components/uptime/{ => sensor}/uptime_seconds_sensor.h (100%) rename esphome/components/uptime/{ => sensor}/uptime_timestamp_sensor.cpp (100%) rename esphome/components/uptime/{ => sensor}/uptime_timestamp_sensor.h (100%) create mode 100644 esphome/components/uptime/text_sensor/__init__.py create mode 100644 esphome/components/uptime/text_sensor/uptime_text_sensor.cpp create mode 100644 esphome/components/uptime/text_sensor/uptime_text_sensor.h diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor/__init__.py similarity index 100% rename from esphome/components/uptime/sensor.py rename to esphome/components/uptime/sensor/__init__.py index 30220751b6..e2a7aee1a2 100644 --- a/esphome/components/uptime/sensor.py +++ b/esphome/components/uptime/sensor/__init__.py @@ -1,14 +1,14 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import sensor, time +import esphome.config_validation as cv from esphome.const import ( CONF_TIME_ID, + DEVICE_CLASS_DURATION, DEVICE_CLASS_TIMESTAMP, ENTITY_CATEGORY_DIAGNOSTIC, + ICON_TIMER, STATE_CLASS_TOTAL_INCREASING, UNIT_SECOND, - ICON_TIMER, - DEVICE_CLASS_DURATION, ) uptime_ns = cg.esphome_ns.namespace("uptime") diff --git a/esphome/components/uptime/uptime_seconds_sensor.cpp b/esphome/components/uptime/sensor/uptime_seconds_sensor.cpp similarity index 100% rename from esphome/components/uptime/uptime_seconds_sensor.cpp rename to esphome/components/uptime/sensor/uptime_seconds_sensor.cpp diff --git a/esphome/components/uptime/uptime_seconds_sensor.h b/esphome/components/uptime/sensor/uptime_seconds_sensor.h similarity index 100% rename from esphome/components/uptime/uptime_seconds_sensor.h rename to esphome/components/uptime/sensor/uptime_seconds_sensor.h diff --git a/esphome/components/uptime/uptime_timestamp_sensor.cpp b/esphome/components/uptime/sensor/uptime_timestamp_sensor.cpp similarity index 100% rename from esphome/components/uptime/uptime_timestamp_sensor.cpp rename to esphome/components/uptime/sensor/uptime_timestamp_sensor.cpp diff --git a/esphome/components/uptime/uptime_timestamp_sensor.h b/esphome/components/uptime/sensor/uptime_timestamp_sensor.h similarity index 100% rename from esphome/components/uptime/uptime_timestamp_sensor.h rename to esphome/components/uptime/sensor/uptime_timestamp_sensor.h diff --git a/esphome/components/uptime/text_sensor/__init__.py b/esphome/components/uptime/text_sensor/__init__.py new file mode 100644 index 0000000000..996d983e71 --- /dev/null +++ b/esphome/components/uptime/text_sensor/__init__.py @@ -0,0 +1,19 @@ +import esphome.codegen as cg +from esphome.components import text_sensor +import esphome.config_validation as cv +from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC, ICON_TIMER + +uptime_ns = cg.esphome_ns.namespace("uptime") +UptimeTextSensor = uptime_ns.class_( + "UptimeTextSensor", text_sensor.TextSensor, cg.PollingComponent +) +CONFIG_SCHEMA = text_sensor.text_sensor_schema( + UptimeTextSensor, + icon=ICON_TIMER, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, +).extend(cv.polling_component_schema("60s")) + + +async def to_code(config): + var = await text_sensor.new_text_sensor(config) + await cg.register_component(var, config) diff --git a/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp b/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp new file mode 100644 index 0000000000..0fa5e199f3 --- /dev/null +++ b/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp @@ -0,0 +1,46 @@ +#include "uptime_text_sensor.h" + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uptime { + +static const char *const TAG = "uptime.sensor"; + +void UptimeTextSensor::setup() { this->last_ms_ = millis(); } + +void UptimeTextSensor::update() { + const auto now = millis(); + // get whole seconds since last update. Note that even if the millis count has overflowed between updates, + // the difference will still be correct due to the way twos-complement arithmetic works. + const uint32_t delta = (now - this->last_ms_) / 1000; + if (delta == 0) + return; + // set last_ms_ to the last second boundary + this->last_ms_ = now - (now % 1000); + this->uptime_ += delta; + auto uptime = this->uptime_; + unsigned days = uptime / (24 * 3600); + unsigned seconds = uptime % (24 * 3600); + unsigned hours = seconds / 3600; + seconds %= 3600; + unsigned minutes = seconds / 60; + seconds %= 60; + if (days != 0) { + this->publish_state(str_sprintf("%dd%dh%dm%ds", days, hours, minutes, seconds)); + } else if (hours != 0) { + this->publish_state(str_sprintf("%dh%dm%ds", hours, minutes, seconds)); + } else if (minutes != 0) { + this->publish_state(str_sprintf("%dm%ds", minutes, seconds)); + } else { + this->publish_state(str_sprintf("%ds", seconds)); + } +} + +float UptimeTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; } +void UptimeTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Uptime Text Sensor", this); } + +} // namespace uptime +} // namespace esphome diff --git a/esphome/components/uptime/text_sensor/uptime_text_sensor.h b/esphome/components/uptime/text_sensor/uptime_text_sensor.h new file mode 100644 index 0000000000..4baf1039b6 --- /dev/null +++ b/esphome/components/uptime/text_sensor/uptime_text_sensor.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/defines.h" + +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace uptime { + +class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent { + public: + void update() override; + void dump_config() override; + void setup() override; + + float get_setup_priority() const override; + + protected: + uint64_t uptime_{0}; + uint64_t last_ms_{0}; +}; + +} // namespace uptime +} // namespace esphome diff --git a/tests/components/uptime/common.yaml b/tests/components/uptime/common.yaml index f63f80b050..d78ef8eca9 100644 --- a/tests/components/uptime/common.yaml +++ b/tests/components/uptime/common.yaml @@ -13,3 +13,7 @@ sensor: - platform: uptime name: Uptime Sensor Timestamp type: timestamp + +text_sensor: + - platform: uptime + name: Uptime Text From c3412df169a0fc742b5bf854245d6a07adef836d Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:29:27 +1100 Subject: [PATCH 04/36] [image] Fix mdi images (#8082) --- esphome/components/image/__init__.py | 39 ++++++++++++++++++++++------ esphome/components/image/image.cpp | 35 +++++++++++++++++++++---- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index 801b05e160..a503e8f471 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -82,11 +82,13 @@ class ImageEncoder: self.dither = dither self.index = 0 self.invert_alpha = invert_alpha + self.path = "" - def convert(self, image): + def convert(self, image, path): """ Convert the image format :param image: Input image + :param path: Path to the image file :return: converted image """ return image @@ -103,6 +105,16 @@ class ImageEncoder: """ +def is_alpha_only(image: Image): + """ + Check if an image (assumed to be RGBA) is only alpha + """ + # Any alpha data? + if image.split()[-1].getextrema()[0] == 0xFF: + return False + return all(b.getextrema()[1] == 0 for b in image.split()[:-1]) + + class ImageBinary(ImageEncoder): allow_config = {CONF_OPAQUE, CONF_INVERT_ALPHA, CONF_CHROMA_KEY} @@ -111,7 +123,9 @@ class ImageBinary(ImageEncoder): super().__init__(self.width8, height, transparency, dither, invert_alpha) self.bitno = 0 - def convert(self, image): + def convert(self, image, path): + if is_alpha_only(image): + image = image.split()[-1] return image.convert("1", dither=self.dither) def encode(self, pixel): @@ -136,7 +150,16 @@ class ImageBinary(ImageEncoder): class ImageGrayscale(ImageEncoder): allow_config = {CONF_ALPHA_CHANNEL, CONF_CHROMA_KEY, CONF_INVERT_ALPHA, CONF_OPAQUE} - def convert(self, image): + def convert(self, image, path): + if is_alpha_only(image): + if self.transparency != CONF_ALPHA_CHANNEL: + _LOGGER.warning( + "Grayscale image %s is alpha only, but transparency is set to %s", + path, + self.transparency, + ) + self.transparency = CONF_ALPHA_CHANNEL + image = image.split()[-1] return image.convert("LA") def encode(self, pixel): @@ -166,7 +189,7 @@ class ImageRGB565(ImageEncoder): invert_alpha, ) - def convert(self, image): + def convert(self, image, path): return image.convert("RGBA") def encode(self, pixel): @@ -204,7 +227,7 @@ class ImageRGB(ImageEncoder): invert_alpha, ) - def convert(self, image): + def convert(self, image, path): return image.convert("RGBA") def encode(self, pixel): @@ -308,7 +331,7 @@ def is_svg_file(file): if not file: return False with open(file, "rb") as f: - return "get_grayscale_pixel_(img_x, img_y); - if (color.w >= 0x80) { - display->draw_pixel_at(x + img_x, y + img_y, color); + const uint32_t pos = (img_x + img_y * this->width_); + const uint8_t gray = progmem_read_byte(this->data_start_ + pos); + Color color = Color(gray, gray, gray, 0xFF); + switch (this->transparency_) { + case TRANSPARENCY_CHROMA_KEY: + if (gray == 1) { + continue; // skip drawing + } + break; + case TRANSPARENCY_ALPHA_CHANNEL: { + auto on = (float) gray / 255.0f; + auto off = 1.0f - on; + // blend color_on and color_off + color = Color(color_on.r * on + color_off.r * off, color_on.g * on + color_off.g * off, + color_on.b * on + color_off.b * off, 0xFF); + break; + } + default: + break; } + display->draw_pixel_at(x + img_x, y + img_y, color); } } break; @@ -179,8 +196,16 @@ Color Image::get_rgb565_pixel_(int x, int y) const { Color Image::get_grayscale_pixel_(int x, int y) const { const uint32_t pos = (x + y * this->width_); const uint8_t gray = progmem_read_byte(this->data_start_ + pos); - uint8_t alpha = (gray == 1 && this->transparency_ == TRANSPARENCY_CHROMA_KEY) ? 0 : 0xFF; - return Color(gray, gray, gray, alpha); + switch (this->transparency_) { + case TRANSPARENCY_CHROMA_KEY: + if (gray == 1) + return Color(0, 0, 0, 0); + return Color(gray, gray, gray, 0xFF); + case TRANSPARENCY_ALPHA_CHANNEL: + return Color(0, 0, 0, gray); + default: + return Color(gray, gray, gray, 0xFF); + } } int Image::get_width() const { return this->width_; } int Image::get_height() const { return this->height_; } From e8d2ad4ce856ab2e2ae9b156c6f0cbe10f069ddf Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:53:44 +1100 Subject: [PATCH 05/36] [ili9xxx] psram and 8 bit changes (#8084) --- esphome/components/ili9xxx/display.py | 40 ++++++++++++++----- .../components/ili9xxx/ili9xxx_display.cpp | 7 +--- esphome/components/ili9xxx/ili9xxx_display.h | 3 +- tests/components/ili9xxx/test.esp32-ard.yaml | 1 + 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 3c9dd2dab9..e3abb7e98c 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -1,9 +1,12 @@ +import logging + from esphome import core, pins import esphome.codegen as cg from esphome.components import display, spi from esphome.components.display import validate_rotation import esphome.config_validation as cv from esphome.const import ( + CONF_AUTO_CLEAR_ENABLED, CONF_COLOR_ORDER, CONF_COLOR_PALETTE, CONF_DC_PIN, @@ -27,17 +30,12 @@ from esphome.const import ( CONF_WIDTH, ) from esphome.core import CORE, HexInt +from esphome.final_validate import full_config DEPENDENCIES = ["spi"] - -def AUTO_LOAD(): - if CORE.is_esp32: - return ["psram"] - return [] - - CODEOWNERS = ["@nielsnl68", "@clydebarrow"] +LOGGER = logging.getLogger(__name__) ili9xxx_ns = cg.esphome_ns.namespace("ili9xxx") ILI9XXXDisplay = ili9xxx_ns.class_( @@ -84,7 +82,7 @@ COLOR_ORDERS = { "BGR": ColorOrder.COLOR_ORDER_BGR, } -COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") +COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE", "8BIT", upper=True) CONF_LED_PIN = "led_pin" CONF_COLOR_PALETTE_IMAGES = "color_palette_images" @@ -195,9 +193,27 @@ CONFIG_SCHEMA = cv.All( _validate, ) -FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( - "ili9xxx", require_miso=False, require_mosi=True -) + +def final_validate(config): + global_config = full_config.get() + # Ideally would calculate buffer size here, but that info is not available on the Python side + needs_buffer = ( + CONF_LAMBDA in config or CONF_PAGES in config or config[CONF_AUTO_CLEAR_ENABLED] + ) + if ( + CORE.is_esp32 + and config[CONF_COLOR_PALETTE] == "NONE" + and "psram" not in global_config + and needs_buffer + ): + LOGGER.info("Consider enabling PSRAM if available for the display buffer") + + return spi.final_validate_device_schema( + "ili9xxx", require_miso=False, require_mosi=True + ) + + +FINAL_VALIDATE_SCHEMA = final_validate async def to_code(config): @@ -283,6 +299,8 @@ async def to_code(config): palette = converted.getpalette() assert len(palette) == 256 * 3 rhs = palette + elif config[CONF_COLOR_PALETTE] == "8BIT": + cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_8)) else: cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_16)) diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index b9664067a9..f056f0a128 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -66,12 +66,9 @@ void ILI9XXXDisplay::setup() { void ILI9XXXDisplay::alloc_buffer_() { if (this->buffer_color_mode_ == BITS_16) { this->init_internal_(this->get_buffer_length_() * 2); - if (this->buffer_ != nullptr) { - return; - } - this->buffer_color_mode_ = BITS_8; + } else { + this->init_internal_(this->get_buffer_length_()); } - this->init_internal_(this->get_buffer_length_()); if (this->buffer_ == nullptr) { this->mark_failed(); } diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index c141739d2a..87d7c86e5c 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -98,7 +98,8 @@ class ILI9XXXDisplay : public display::DisplayBuffer, protected: inline bool check_buffer_() { if (this->buffer_ == nullptr) { - this->alloc_buffer_(); + if (!this->is_failed()) + this->alloc_buffer_(); return !this->is_failed(); } return true; diff --git a/tests/components/ili9xxx/test.esp32-ard.yaml b/tests/components/ili9xxx/test.esp32-ard.yaml index 850273230a..c00c38ce3e 100644 --- a/tests/components/ili9xxx/test.esp32-ard.yaml +++ b/tests/components/ili9xxx/test.esp32-ard.yaml @@ -20,6 +20,7 @@ display: it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ili9xxx invert_colors: false + color_palette: 8bit dimensions: width: 320 height: 240 From dac9768f6ac897d46b093ecee81ee64101b29b59 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:56:52 +1100 Subject: [PATCH 06/36] [spi] Restore ``SPIDelegateDummy`` (#8019) --- esphome/components/spi/spi.cpp | 6 ++++++ esphome/components/spi/spi.h | 21 +++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index f9435b0424..b13826c443 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -7,6 +7,10 @@ namespace spi { const char *const TAG = "spi"; +SPIDelegate *const SPIDelegate::NULL_DELEGATE = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + new SPIDelegateDummy(); +// https://bugs.llvm.org/show_bug.cgi?id=48040 + bool SPIDelegate::is_ready() { return true; } GPIOPin *const NullPin::NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -75,6 +79,8 @@ void SPIComponent::dump_config() { } } +void SPIDelegateDummy::begin_transaction() { ESP_LOGE(TAG, "SPIDevice not initialised - did you call spi_setup()?"); } + uint8_t SPIDelegateBitBash::transfer(uint8_t data) { return this->transfer_(data, 8); } void SPIDelegateBitBash::write(uint16_t data, size_t num_bits) { this->transfer_(data, num_bits); } diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 4cd8d3383c..f581dc3f56 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -163,6 +163,8 @@ class Utility { } }; +class SPIDelegateDummy; + // represents a device attached to an SPI bus, with a defined clock rate, mode and bit order. On Arduino this is // a thin wrapper over SPIClass. class SPIDelegate { @@ -248,6 +250,21 @@ class SPIDelegate { uint32_t data_rate_{1000000}; SPIMode mode_{MODE0}; GPIOPin *cs_pin_{NullPin::NULL_PIN}; + static SPIDelegate *const NULL_DELEGATE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +}; + +/** + * A dummy SPIDelegate that complains if it's used. + */ + +class SPIDelegateDummy : public SPIDelegate { + public: + SPIDelegateDummy() = default; + + uint8_t transfer(uint8_t data) override { return 0; } + void end_transaction() override{}; + + void begin_transaction() override; }; /** @@ -365,7 +382,7 @@ class SPIClient { virtual void spi_teardown() { this->parent_->unregister_device(this); - this->delegate_ = nullptr; + this->delegate_ = SPIDelegate::NULL_DELEGATE; } bool spi_is_ready() { return this->delegate_->is_ready(); } @@ -376,7 +393,7 @@ class SPIClient { uint32_t data_rate_{1000000}; SPIComponent *parent_{nullptr}; GPIOPin *cs_{nullptr}; - SPIDelegate *delegate_{nullptr}; + SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE}; }; /** From 17b88f2e3e5d873b1961b40e4c1fb6acccfc1305 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:29:51 +1100 Subject: [PATCH 07/36] [lvgl] fix lvgl.widget.update and friends (#8087) --- esphome/components/lvgl/automation.py | 15 +++++++++++- esphome/components/lvgl/schemas.py | 27 ++++++++++++++------- esphome/components/lvgl/widgets/dropdown.py | 2 +- esphome/components/lvgl/widgets/keyboard.py | 9 ++++++- esphome/components/lvgl/widgets/msgbox.py | 2 +- esphome/components/lvgl/widgets/obj.py | 13 +--------- esphome/components/lvgl/widgets/tabview.py | 2 +- tests/components/lvgl/lvgl-package.yaml | 15 ++++++++++++ 8 files changed, 59 insertions(+), 26 deletions(-) diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index 7db6e1f045..168fc03cb7 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -15,6 +15,7 @@ from .defines import ( CONF_FREEZE, CONF_LVGL_ID, CONF_SHOW_SNOW, + PARTS, literal, ) from .lv_validation import lv_bool, lv_color, lv_image, opacity @@ -33,7 +34,7 @@ from .lvcode import ( lvgl_comp, static_cast, ) -from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA +from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA, base_update_schema from .types import ( LV_STATE, LvglAction, @@ -41,6 +42,7 @@ from .types import ( ObjUpdateAction, lv_disp_t, lv_group_t, + lv_obj_base_t, lv_obj_t, lv_pseudo_button_t, ) @@ -336,3 +338,14 @@ async def widget_focus(config, action_id, template_arg, args): lv.group_focus_freeze(group, True) var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) return var + + +@automation.register_action( + "lvgl.widget.update", ObjUpdateAction, base_update_schema(lv_obj_base_t, PARTS) +) +async def obj_update_to_code(config, action_id, template_arg, args): + async def do_update(widget: Widget): + await set_obj_properties(widget, config) + + widgets = await get_widgets(config[CONF_ID]) + return await action_to_code(widgets, do_update, action_id, template_arg, args) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 271dbea19f..f0318dd17a 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -199,13 +199,12 @@ FLAG_SCHEMA = cv.Schema({cv.Optional(flag): lvalid.lv_bool for flag in df.OBJ_FL FLAG_LIST = cv.ensure_list(df.LvConstant("LV_OBJ_FLAG_", *df.OBJ_FLAGS).one_of) -def part_schema(widget_type: WidgetType): +def part_schema(parts): """ Generate a schema for the various parts (e.g. main:, indicator:) of a widget type - :param widget_type: The type of widget to generate for - :return: + :param parts: The parts to include in the schema + :return: The schema """ - parts = widget_type.parts return cv.Schema({cv.Optional(part): STATE_SCHEMA for part in parts}).extend( STATE_SCHEMA ) @@ -228,9 +227,15 @@ def automation_schema(typ: LvType): } -def create_modify_schema(widget_type): +def base_update_schema(widget_type, parts): + """ + Create a schema for updating a widgets style properties, states and flags + :param widget_type: The type of the ID + :param parts: The allowable parts to specify + :return: + """ return ( - part_schema(widget_type) + part_schema(parts) .extend( { cv.Required(CONF_ID): cv.ensure_list( @@ -245,7 +250,12 @@ def create_modify_schema(widget_type): } ) .extend(FLAG_SCHEMA) - .extend(widget_type.modify_schema) + ) + + +def create_modify_schema(widget_type): + return base_update_schema(widget_type.w_type, widget_type.parts).extend( + widget_type.modify_schema ) @@ -256,7 +266,7 @@ def obj_schema(widget_type: WidgetType): :return: """ return ( - part_schema(widget_type) + part_schema(widget_type.parts) .extend(FLAG_SCHEMA) .extend(LAYOUT_SCHEMA) .extend(ALIGN_TO_SCHEMA) @@ -341,7 +351,6 @@ FLEX_OBJ_SCHEMA = { cv.Optional(df.CONF_FLEX_GROW): cv.int_, } - DISP_BG_SCHEMA = cv.Schema( { cv.Optional(df.CONF_DISP_BG_IMAGE): cv.Any( diff --git a/esphome/components/lvgl/widgets/dropdown.py b/esphome/components/lvgl/widgets/dropdown.py index a6bfc6bb88..b32b5a2b2e 100644 --- a/esphome/components/lvgl/widgets/dropdown.py +++ b/esphome/components/lvgl/widgets/dropdown.py @@ -37,7 +37,7 @@ DROPDOWN_BASE_SCHEMA = cv.Schema( cv.Exclusive(CONF_SELECTED_INDEX, CONF_SELECTED_TEXT): lv_int, cv.Exclusive(CONF_SELECTED_TEXT, CONF_SELECTED_TEXT): lv_text, cv.Optional(CONF_DIR, default="BOTTOM"): DIRECTIONS.one_of, - cv.Optional(CONF_DROPDOWN_LIST): part_schema(dropdown_list_spec), + cv.Optional(CONF_DROPDOWN_LIST): part_schema(dropdown_list_spec.parts), } ) diff --git a/esphome/components/lvgl/widgets/keyboard.py b/esphome/components/lvgl/widgets/keyboard.py index ba7edb302e..d4a71078d0 100644 --- a/esphome/components/lvgl/widgets/keyboard.py +++ b/esphome/components/lvgl/widgets/keyboard.py @@ -16,6 +16,11 @@ KEYBOARD_SCHEMA = { cv.Optional(CONF_TEXTAREA): cv.use_id(lv_textarea_t), } +KEYBOARD_MODIFY_SCHEMA = { + cv.Optional(CONF_MODE): KEYBOARD_MODES.one_of, + cv.Optional(CONF_TEXTAREA): cv.use_id(lv_textarea_t), +} + lv_keyboard_t = LvType( "LvKeyboardType", parents=(KeyProvider, LvCompound), @@ -32,6 +37,7 @@ class KeyboardType(WidgetType): lv_keyboard_t, (CONF_MAIN, CONF_ITEMS), KEYBOARD_SCHEMA, + modify_schema=KEYBOARD_MODIFY_SCHEMA, ) def get_uses(self): @@ -41,7 +47,8 @@ class KeyboardType(WidgetType): lvgl_components_required.add("KEY_LISTENER") lvgl_components_required.add(CONF_KEYBOARD) add_lv_use("btnmatrix") - await w.set_property(CONF_MODE, await KEYBOARD_MODES.process(config[CONF_MODE])) + if mode := config.get(CONF_MODE): + await w.set_property(CONF_MODE, await KEYBOARD_MODES.process(mode)) if ta := await get_widgets(config, CONF_TEXTAREA): await w.set_property(CONF_TEXTAREA, ta[0].obj) diff --git a/esphome/components/lvgl/widgets/msgbox.py b/esphome/components/lvgl/widgets/msgbox.py index c3393940b6..82b2442378 100644 --- a/esphome/components/lvgl/widgets/msgbox.py +++ b/esphome/components/lvgl/widgets/msgbox.py @@ -51,7 +51,7 @@ MSGBOX_SCHEMA = container_schema( cv.Required(CONF_TITLE): STYLED_TEXT_SCHEMA, cv.Optional(CONF_BODY, default=""): STYLED_TEXT_SCHEMA, cv.Optional(CONF_BUTTONS): cv.ensure_list(BUTTONMATRIX_BUTTON_SCHEMA), - cv.Optional(CONF_BUTTON_STYLE): part_schema(buttonmatrix_spec), + cv.Optional(CONF_BUTTON_STYLE): part_schema(buttonmatrix_spec.parts), cv.Optional(CONF_CLOSE_BUTTON, default=True): lv_bool, cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr), } diff --git a/esphome/components/lvgl/widgets/obj.py b/esphome/components/lvgl/widgets/obj.py index afb4c97f33..ab22a5ce86 100644 --- a/esphome/components/lvgl/widgets/obj.py +++ b/esphome/components/lvgl/widgets/obj.py @@ -1,9 +1,5 @@ -from esphome import automation - -from ..automation import update_to_code from ..defines import CONF_MAIN, CONF_OBJ, CONF_SCROLLBAR -from ..schemas import create_modify_schema -from ..types import ObjUpdateAction, WidgetType, lv_obj_t +from ..types import WidgetType, lv_obj_t class ObjType(WidgetType): @@ -21,10 +17,3 @@ class ObjType(WidgetType): obj_spec = ObjType() - - -@automation.register_action( - "lvgl.widget.update", ObjUpdateAction, create_modify_schema(obj_spec) -) -async def obj_update_to_code(config, action_id, template_arg, args): - return await update_to_code(config, action_id, template_arg, args) diff --git a/esphome/components/lvgl/widgets/tabview.py b/esphome/components/lvgl/widgets/tabview.py index 226fc3f286..1d18ddd259 100644 --- a/esphome/components/lvgl/widgets/tabview.py +++ b/esphome/components/lvgl/widgets/tabview.py @@ -38,7 +38,7 @@ TABVIEW_SCHEMA = cv.Schema( }, ) ), - cv.Optional(CONF_TAB_STYLE): part_schema(buttonmatrix_spec), + cv.Optional(CONF_TAB_STYLE): part_schema(buttonmatrix_spec.parts), cv.Optional(CONF_POSITION, default="top"): DIRECTIONS.one_of, cv.Optional(CONF_SIZE, default="10%"): size, } diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 7c59cfa171..b3227bb96e 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -805,9 +805,24 @@ lvgl: - logger.log: format: "keyboard value %s" args: [text.c_str()] + - lvgl.keyboard.update: + id: lv_keyboard + hidden: true + on_ready: + - lvgl.widget.update: + id: lv_keyboard + - lvgl.keyboard.update: + id: lv_keyboard + hidden: true + - keyboard: id: lv_keyboard1 mode: special + on_ready: + lvgl.keyboard.update: + id: lv_keyboard1 + hidden: true + mode: text_lower font: - file: "gfonts://Roboto" From c43d8460bdbc8d2e3c77986e55dbdc7deee99184 Mon Sep 17 00:00:00 2001 From: Saninn Salas Diaz <5490201+distante@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:14:58 +0100 Subject: [PATCH 08/36] fix(web_server/fan): send speed update values even when fan is off (#8086) --- esphome/components/web_server/web_server.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index ed0cb3db2c..8c09d607a7 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -455,8 +455,9 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } else if (match.method == "toggle") { this->schedule_([obj]() { obj->toggle().perform(); }); request->send(200); - } else if (match.method == "turn_on") { - auto call = obj->turn_on(); + } else if (match.method == "turn_on" || match.method == "turn_off") { + auto call = match.method == "turn_on" ? obj->turn_on() : obj->turn_off(); + if (request->hasParam("speed_level")) { auto speed_level = request->getParam("speed_level")->value(); auto val = parse_number(speed_level.c_str()); @@ -486,9 +487,6 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } this->schedule_([call]() mutable { call.perform(); }); request->send(200); - } else if (match.method == "turn_off") { - this->schedule_([obj]() { obj->turn_off().perform(); }); - request->send(200); } else { request->send(404); } From 98817a5bbfacced643d1046135eea26099a0ac36 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 14 Jan 2025 21:47:22 -0600 Subject: [PATCH 09/36] [es7210] add support for es7210 ADC (#8007) --- CODEOWNERS | 1 + esphome/components/es7210/__init__.py | 67 ++++++ esphome/components/es7210/es7210.cpp | 201 ++++++++++++++++++ esphome/components/es7210/es7210.h | 69 ++++++ esphome/components/es7210/es7210_const.h | 126 +++++++++++ esphome/components/es8311/audio_dac.py | 3 +- esphome/const.py | 1 + tests/components/es7210/common.yaml | 6 + tests/components/es7210/test.esp32-ard.yaml | 5 + .../components/es7210/test.esp32-c3-ard.yaml | 5 + .../components/es7210/test.esp32-c3-idf.yaml | 5 + tests/components/es7210/test.esp32-idf.yaml | 5 + 12 files changed, 492 insertions(+), 2 deletions(-) create mode 100644 esphome/components/es7210/__init__.py create mode 100644 esphome/components/es7210/es7210.cpp create mode 100644 esphome/components/es7210/es7210.h create mode 100644 esphome/components/es7210/es7210_const.h create mode 100644 tests/components/es7210/common.yaml create mode 100644 tests/components/es7210/test.esp32-ard.yaml create mode 100644 tests/components/es7210/test.esp32-c3-ard.yaml create mode 100644 tests/components/es7210/test.esp32-c3-idf.yaml create mode 100644 tests/components/es7210/test.esp32-idf.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 088e350f5d..ba7106e6a3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -131,6 +131,7 @@ esphome/components/ens160_base/* @latonita @vincentscode esphome/components/ens160_i2c/* @latonita esphome/components/ens160_spi/* @latonita esphome/components/ens210/* @itn3rd77 +esphome/components/es7210/* @kahrendt esphome/components/es8311/* @kahrendt @kroimon esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @Rapsssito @jesserockz diff --git a/esphome/components/es7210/__init__.py b/esphome/components/es7210/__init__.py new file mode 100644 index 0000000000..8e63d7f04f --- /dev/null +++ b/esphome/components/es7210/__init__.py @@ -0,0 +1,67 @@ +import esphome.codegen as cg +from esphome.components import i2c +import esphome.config_validation as cv +from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_MIC_GAIN, CONF_SAMPLE_RATE + +CODEOWNERS = ["@kahrendt"] +DEPENDENCIES = ["i2c"] + +es7210_ns = cg.esphome_ns.namespace("es7210") +ES7210 = es7210_ns.class_("ES7210", cg.Component, i2c.I2CDevice) + + +es7210_bits_per_sample = es7210_ns.enum("ES7210BitsPerSample") +ES7210_BITS_PER_SAMPLE_ENUM = { + 16: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_16, + 24: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_24, + 32: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_32, +} + + +es7210_mic_gain = es7210_ns.enum("ES7210MicGain") +ES7210_MIC_GAIN_ENUM = { + "0DB": es7210_mic_gain.ES7210_MIC_GAIN_0DB, + "3DB": es7210_mic_gain.ES7210_MIC_GAIN_3DB, + "6DB": es7210_mic_gain.ES7210_MIC_GAIN_6DB, + "9DB": es7210_mic_gain.ES7210_MIC_GAIN_9DB, + "12DB": es7210_mic_gain.ES7210_MIC_GAIN_12DB, + "15DB": es7210_mic_gain.ES7210_MIC_GAIN_15DB, + "18DB": es7210_mic_gain.ES7210_MIC_GAIN_18DB, + "21DB": es7210_mic_gain.ES7210_MIC_GAIN_21DB, + "24DB": es7210_mic_gain.ES7210_MIC_GAIN_24DB, + "27DB": es7210_mic_gain.ES7210_MIC_GAIN_27DB, + "30DB": es7210_mic_gain.ES7210_MIC_GAIN_30DB, + "33DB": es7210_mic_gain.ES7210_MIC_GAIN_33DB, + "34.5DB": es7210_mic_gain.ES7210_MIC_GAIN_34_5DB, + "36DB": es7210_mic_gain.ES7210_MIC_GAIN_36DB, + "37.5DB": es7210_mic_gain.ES7210_MIC_GAIN_37_5DB, +} + +_validate_bits = cv.float_with_unit("bits", "bit") + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ES7210), + cv.Optional(CONF_BITS_PER_SAMPLE, default="16bit"): cv.All( + _validate_bits, cv.enum(ES7210_BITS_PER_SAMPLE_ENUM) + ), + cv.Optional(CONF_MIC_GAIN, default="24DB"): cv.enum( + ES7210_MIC_GAIN_ENUM, upper=True + ), + cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x40)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) + cg.add(var.set_mic_gain(config[CONF_MIC_GAIN])) + cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) diff --git a/esphome/components/es7210/es7210.cpp b/esphome/components/es7210/es7210.cpp new file mode 100644 index 0000000000..d2f2c3c1ff --- /dev/null +++ b/esphome/components/es7210/es7210.cpp @@ -0,0 +1,201 @@ +#include "es7210.h" +#include "es7210_const.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace es7210 { + +static const char *const TAG = "es7210"; + +static const size_t MCLK_DIV_FRE = 256; + +// Mark the component as failed; use only in setup +#define ES7210_ERROR_FAILED(func) \ + if (!(func)) { \ + this->mark_failed(); \ + return; \ + } + +// Return false; use outside of setup +#define ES7210_ERROR_CHECK(func) \ + if (!(func)) { \ + return false; \ + } + +void ES7210::dump_config() { + ESP_LOGCONFIG(TAG, "ES7210 ADC:"); + ESP_LOGCONFIG(TAG, " Bits Per Sample: %" PRIu8, this->bits_per_sample_); + ESP_LOGCONFIG(TAG, " Sample Rate: %" PRIu32, this->sample_rate_); + + if (this->is_failed()) { + ESP_LOGCONFIG(TAG, " Failed to initialize!"); + return; + } +} + +void ES7210::setup() { + ESP_LOGCONFIG(TAG, "Setting up ES7210..."); + + // Software reset + ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0xff)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x32)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_CLOCK_OFF_REG01, 0x3f)); + + // Set initialization time when device powers up + ES7210_ERROR_FAILED(this->write_byte(ES7210_TIME_CONTROL0_REG09, 0x30)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_TIME_CONTROL1_REG0A, 0x30)); + + // Configure HFP for all ADC channels + ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC12_HPF2_REG23, 0x2a)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC12_HPF1_REG22, 0x0a)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC34_HPF2_REG20, 0x0a)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC34_HPF1_REG21, 0x2a)); + + // Secondary I2S mode settings + ES7210_ERROR_FAILED(this->es7210_update_reg_bit_(ES7210_MODE_CONFIG_REG08, 0x01, 0x00)); + + // Configure analog power + ES7210_ERROR_FAILED(this->write_byte(ES7210_ANALOG_REG40, 0xC3)); + + // Set mic bias + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC12_BIAS_REG41, 0x70)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC34_BIAS_REG42, 0x70)); + + // Configure I2S settings, sample rate, and microphone gains + ES7210_ERROR_FAILED(this->configure_i2s_format_()); + ES7210_ERROR_FAILED(this->configure_sample_rate_()); + ES7210_ERROR_FAILED(this->configure_mic_gain_()); + + // Power on mics 1 through 4 + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC1_POWER_REG47, 0x08)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC2_POWER_REG48, 0x08)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC3_POWER_REG49, 0x08)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC4_POWER_REG4A, 0x08)); + + // Power down DLL + ES7210_ERROR_FAILED(this->write_byte(ES7210_POWER_DOWN_REG06, 0x04)); + + // Power on MIC1-4 bias & ADC1-4 & PGA1-4 Power + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x0F)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x0F)); + + // Enable device + ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x71)); + ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x41)); +} + +bool ES7210::configure_sample_rate_() { + int mclk_fre = this->sample_rate_ * MCLK_DIV_FRE; + int coeff = -1; + + for (int i = 0; i < (sizeof(ES7210_COEFFICIENTS) / sizeof(ES7210_COEFFICIENTS[0])); ++i) { + if (ES7210_COEFFICIENTS[i].lrclk == this->sample_rate_ && ES7210_COEFFICIENTS[i].mclk == mclk_fre) + coeff = i; + } + + if (coeff >= 0) { + // Set adc_div & doubler & dll + uint8_t regv; + ES7210_ERROR_CHECK(this->read_byte(ES7210_MAINCLK_REG02, ®v)); + regv = regv & 0x00; + regv |= ES7210_COEFFICIENTS[coeff].adc_div; + regv |= ES7210_COEFFICIENTS[coeff].doubler << 6; + regv |= ES7210_COEFFICIENTS[coeff].dll << 7; + + ES7210_ERROR_CHECK(this->write_byte(ES7210_MAINCLK_REG02, regv)); + + // Set osr + regv = ES7210_COEFFICIENTS[coeff].osr; + ES7210_ERROR_CHECK(this->write_byte(ES7210_OSR_REG07, regv)); + // Set lrck + regv = ES7210_COEFFICIENTS[coeff].lrck_h; + ES7210_ERROR_CHECK(this->write_byte(ES7210_LRCK_DIVH_REG04, regv)); + regv = ES7210_COEFFICIENTS[coeff].lrck_l; + ES7210_ERROR_CHECK(this->write_byte(ES7210_LRCK_DIVL_REG05, regv)); + } else { + // Invalid sample frequency + ESP_LOGE(TAG, "Invalid sample rate"); + return false; + } + + return true; +} +bool ES7210::configure_mic_gain_() { + for (int i = 0; i < 4; ++i) { + this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43 + i, 0x10, 0x00); + } + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0xff)); + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0xff)); + + // Configure mic 1 + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x10, 0x10)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x0f, this->mic_gain_)); + + // Configure mic 2 + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x10, 0x10)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x0f, this->mic_gain_)); + + // Configure mic 3 + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x10, 0x10)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x0f, this->mic_gain_)); + + // Configure mic 4 + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); + ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x10, 0x10)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x0f, this->mic_gain_)); + + return true; +} + +bool ES7210::configure_i2s_format_() { + // Configure bits per sample + uint8_t reg_val = 0; + switch (this->bits_per_sample_) { + case ES7210_BITS_PER_SAMPLE_16: + reg_val = 0x60; + break; + case ES7210_BITS_PER_SAMPLE_18: + reg_val = 0x40; + break; + case ES7210_BITS_PER_SAMPLE_20: + reg_val = 0x20; + break; + case ES7210_BITS_PER_SAMPLE_24: + reg_val = 0x00; + break; + case ES7210_BITS_PER_SAMPLE_32: + reg_val = 0x80; + break; + default: + return false; + } + ES7210_ERROR_CHECK(this->write_byte(ES7210_SDP_INTERFACE1_REG11, reg_val)); + + if (this->enable_tdm_) { + ES7210_ERROR_CHECK(this->write_byte(ES7210_SDP_INTERFACE2_REG12, 0x02)); + } else { + // Microphones 1 and 2 output on SDOUT1, microphones 3 and 4 output on SDOUT2 + ES7210_ERROR_CHECK(this->write_byte(ES7210_SDP_INTERFACE2_REG12, 0x00)); + } + + return true; +} + +bool ES7210::es7210_update_reg_bit_(uint8_t reg_addr, uint8_t update_bits, uint8_t data) { + uint8_t regv; + ES7210_ERROR_CHECK(this->read_byte(reg_addr, ®v)); + regv = (regv & (~update_bits)) | (update_bits & data); + return this->write_byte(reg_addr, regv); +} + +} // namespace es7210 +} // namespace esphome diff --git a/esphome/components/es7210/es7210.h b/esphome/components/es7210/es7210.h new file mode 100644 index 0000000000..a40dde5aa5 --- /dev/null +++ b/esphome/components/es7210/es7210.h @@ -0,0 +1,69 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace es7210 { + +enum ES7210BitsPerSample : uint8_t { + ES7210_BITS_PER_SAMPLE_16 = 16, + ES7210_BITS_PER_SAMPLE_18 = 18, + ES7210_BITS_PER_SAMPLE_20 = 20, + ES7210_BITS_PER_SAMPLE_24 = 24, + ES7210_BITS_PER_SAMPLE_32 = 32, +}; + +enum ES7210MicGain : uint8_t { + ES7210_MIC_GAIN_0DB = 0, + ES7210_MIC_GAIN_3DB, + ES7210_MIC_GAIN_6DB, + ES7210_MIC_GAIN_9DB, + ES7210_MIC_GAIN_12DB, + ES7210_MIC_GAIN_15DB, + ES7210_MIC_GAIN_18DB, + ES7210_MIC_GAIN_21DB, + ES7210_MIC_GAIN_24DB, + ES7210_MIC_GAIN_27DB, + ES7210_MIC_GAIN_30DB, + ES7210_MIC_GAIN_33DB, + ES7210_MIC_GAIN_34_5DB, + ES7210_MIC_GAIN_36DB, + ES7210_MIC_GAIN_37_5DB, +}; + +class ES7210 : public Component, public i2c::I2CDevice { + /* Class for configuring an ES7210 ADC for microphone input. + * Based on code from: + * - https://github.com/espressif/esp-bsp/ (accessed 20241219) + * - https://github.com/espressif/esp-adf/ (accessed 20241219) + */ + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void dump_config() override; + + void set_bits_per_sample(ES7210BitsPerSample bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } + void set_mic_gain(ES7210MicGain mic_gain) { this->mic_gain_ = mic_gain; } + void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; } + + protected: + /// @brief Updates an I2C registry address by modifying the current state + /// @param reg_addr I2C register address + /// @param update_bits Mask of allowed bits to be modified + /// @param data Bit values to be written + /// @return True if successful, false otherwise + bool es7210_update_reg_bit_(uint8_t reg_addr, uint8_t update_bits, uint8_t data); + + bool configure_i2s_format_(); + bool configure_mic_gain_(); + bool configure_sample_rate_(); + + bool enable_tdm_{false}; // TDM is unsupported in ESPHome as of version 2024.12 + ES7210MicGain mic_gain_; + ES7210BitsPerSample bits_per_sample_; + uint32_t sample_rate_; +}; + +} // namespace es7210 +} // namespace esphome diff --git a/esphome/components/es7210/es7210_const.h b/esphome/components/es7210/es7210_const.h new file mode 100644 index 0000000000..87fd6d86d2 --- /dev/null +++ b/esphome/components/es7210/es7210_const.h @@ -0,0 +1,126 @@ +#pragma once + +#include "es7210.h" + +namespace esphome { +namespace es7210 { + +// ES7210 register addresses +static const uint8_t ES7210_RESET_REG00 = 0x00; /* Reset control */ +static const uint8_t ES7210_CLOCK_OFF_REG01 = 0x01; /* Used to turn off the ADC clock */ +static const uint8_t ES7210_MAINCLK_REG02 = 0x02; /* Set ADC clock frequency division */ + +static const uint8_t ES7210_MASTER_CLK_REG03 = 0x03; /* MCLK source $ SCLK division */ +static const uint8_t ES7210_LRCK_DIVH_REG04 = 0x04; /* lrck_divh */ +static const uint8_t ES7210_LRCK_DIVL_REG05 = 0x05; /* lrck_divl */ +static const uint8_t ES7210_POWER_DOWN_REG06 = 0x06; /* power down */ +static const uint8_t ES7210_OSR_REG07 = 0x07; +static const uint8_t ES7210_MODE_CONFIG_REG08 = 0x08; /* Set primary/secondary & channels */ +static const uint8_t ES7210_TIME_CONTROL0_REG09 = 0x09; /* Set Chip intial state period*/ +static const uint8_t ES7210_TIME_CONTROL1_REG0A = 0x0A; /* Set Power up state period */ +static const uint8_t ES7210_SDP_INTERFACE1_REG11 = 0x11; /* Set sample & fmt */ +static const uint8_t ES7210_SDP_INTERFACE2_REG12 = 0x12; /* Pins state */ +static const uint8_t ES7210_ADC_AUTOMUTE_REG13 = 0x13; /* Set mute */ +static const uint8_t ES7210_ADC34_MUTERANGE_REG14 = 0x14; /* Set mute range */ +static const uint8_t ES7210_ADC12_MUTERANGE_REG15 = 0x15; /* Set mute range */ +static const uint8_t ES7210_ADC34_HPF2_REG20 = 0x20; /* HPF */ +static const uint8_t ES7210_ADC34_HPF1_REG21 = 0x21; /* HPF */ +static const uint8_t ES7210_ADC12_HPF1_REG22 = 0x22; /* HPF */ +static const uint8_t ES7210_ADC12_HPF2_REG23 = 0x23; /* HPF */ +static const uint8_t ES7210_ANALOG_REG40 = 0x40; /* ANALOG Power */ +static const uint8_t ES7210_MIC12_BIAS_REG41 = 0x41; +static const uint8_t ES7210_MIC34_BIAS_REG42 = 0x42; +static const uint8_t ES7210_MIC1_GAIN_REG43 = 0x43; +static const uint8_t ES7210_MIC2_GAIN_REG44 = 0x44; +static const uint8_t ES7210_MIC3_GAIN_REG45 = 0x45; +static const uint8_t ES7210_MIC4_GAIN_REG46 = 0x46; +static const uint8_t ES7210_MIC1_POWER_REG47 = 0x47; +static const uint8_t ES7210_MIC2_POWER_REG48 = 0x48; +static const uint8_t ES7210_MIC3_POWER_REG49 = 0x49; +static const uint8_t ES7210_MIC4_POWER_REG4A = 0x4A; +static const uint8_t ES7210_MIC12_POWER_REG4B = 0x4B; /* MICBias & ADC & PGA Power */ +static const uint8_t ES7210_MIC34_POWER_REG4C = 0x4C; + +/* + * Clock coefficient structer + */ +struct ES7210Coefficient { + uint32_t mclk; // mclk frequency + uint32_t lrclk; + uint8_t ss_ds; + uint8_t adc_div; + uint8_t dll; // dll_bypass + uint8_t doubler; // doubler_enable + uint8_t osr; // adc osr + uint8_t mclk_src; // sselect mclk source + uint8_t lrck_h; // High 4 bits of lrck + uint8_t lrck_l; // Low 8 bits of lrck +}; + +/* Codec hifi mclk clock divider coefficients + * MEMBER REG + * mclk: 0x03 + * lrck: standard + * ss_ds: -- + * adc_div: 0x02 + * dll: 0x06 + * doubler: 0x02 + * osr: 0x07 + * mclk_src: 0x03 + * lrckh: 0x04 + * lrckl: 0x05 + */ +static const ES7210Coefficient ES7210_COEFFICIENTS[] = { + // mclk lrck ss_ds adc_div dll doubler osr mclk_src lrckh lrckl + /* 8k */ + {12288000, 8000, 0x00, 0x03, 0x01, 0x00, 0x20, 0x00, 0x06, 0x00}, + {16384000, 8000, 0x00, 0x04, 0x01, 0x00, 0x20, 0x00, 0x08, 0x00}, + {19200000, 8000, 0x00, 0x1e, 0x00, 0x01, 0x28, 0x00, 0x09, 0x60}, + {4096000, 8000, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00}, + + /* 11.025k */ + {11289600, 11025, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00, 0x01, 0x00}, + + /* 12k */ + {12288000, 12000, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00, 0x04, 0x00}, + {19200000, 12000, 0x00, 0x14, 0x00, 0x01, 0x28, 0x00, 0x06, 0x40}, + + /* 16k */ + {4096000, 16000, 0x00, 0x01, 0x01, 0x01, 0x20, 0x00, 0x01, 0x00}, + {19200000, 16000, 0x00, 0x0a, 0x00, 0x00, 0x1e, 0x00, 0x04, 0x80}, + {16384000, 16000, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00, 0x04, 0x00}, + {12288000, 16000, 0x00, 0x03, 0x01, 0x01, 0x20, 0x00, 0x03, 0x00}, + + /* 22.05k */ + {11289600, 22050, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00}, + + /* 24k */ + {12288000, 24000, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00}, + {19200000, 24000, 0x00, 0x0a, 0x00, 0x01, 0x28, 0x00, 0x03, 0x20}, + + /* 32k */ + {12288000, 32000, 0x00, 0x03, 0x00, 0x00, 0x20, 0x00, 0x01, 0x80}, + {16384000, 32000, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00}, + {19200000, 32000, 0x00, 0x05, 0x00, 0x00, 0x1e, 0x00, 0x02, 0x58}, + + /* 44.1k */ + {11289600, 44100, 0x00, 0x01, 0x01, 0x01, 0x20, 0x00, 0x01, 0x00}, + + /* 48k */ + {12288000, 48000, 0x00, 0x01, 0x01, 0x01, 0x20, 0x00, 0x01, 0x00}, + {19200000, 48000, 0x00, 0x05, 0x00, 0x01, 0x28, 0x00, 0x01, 0x90}, + + /* 64k */ + {16384000, 64000, 0x01, 0x01, 0x01, 0x00, 0x20, 0x00, 0x01, 0x00}, + {19200000, 64000, 0x00, 0x05, 0x00, 0x01, 0x1e, 0x00, 0x01, 0x2c}, + + /* 88.2k */ + {11289600, 88200, 0x01, 0x01, 0x01, 0x01, 0x20, 0x00, 0x00, 0x80}, + + /* 96k */ + {12288000, 96000, 0x01, 0x01, 0x01, 0x01, 0x20, 0x00, 0x00, 0x80}, + {19200000, 96000, 0x01, 0x05, 0x00, 0x01, 0x28, 0x00, 0x00, 0xc8}, +}; + +} // namespace es7210 +} // namespace esphome diff --git a/esphome/components/es8311/audio_dac.py b/esphome/components/es8311/audio_dac.py index 1b450c3c11..7d80cfd5fb 100644 --- a/esphome/components/es8311/audio_dac.py +++ b/esphome/components/es8311/audio_dac.py @@ -2,7 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c from esphome.components.audio_dac import AudioDac import esphome.config_validation as cv -from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_SAMPLE_RATE +from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_MIC_GAIN, CONF_SAMPLE_RATE CODEOWNERS = ["@kroimon", "@kahrendt"] DEPENDENCIES = ["i2c"] @@ -10,7 +10,6 @@ DEPENDENCIES = ["i2c"] es8311_ns = cg.esphome_ns.namespace("es8311") ES8311 = es8311_ns.class_("ES8311", AudioDac, cg.Component, i2c.I2CDevice) -CONF_MIC_GAIN = "mic_gain" CONF_USE_MCLK = "use_mclk" CONF_USE_MICROPHONE = "use_microphone" diff --git a/esphome/const.py b/esphome/const.py index 0f41dc1aec..4c0203c6a9 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -490,6 +490,7 @@ CONF_MEMORY_BLOCKS = "memory_blocks" CONF_MESSAGE = "message" CONF_METHANE = "methane" CONF_METHOD = "method" +CONF_MIC_GAIN = "mic_gain" CONF_MICROPHONE = "microphone" CONF_MIN_BRIGHTNESS = "min_brightness" CONF_MIN_COOLING_OFF_TIME = "min_cooling_off_time" diff --git a/tests/components/es7210/common.yaml b/tests/components/es7210/common.yaml new file mode 100644 index 0000000000..5c30f7e883 --- /dev/null +++ b/tests/components/es7210/common.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_aic3204 + scl: ${scl_pin} + sda: ${sda_pin} + +es7210: diff --git a/tests/components/es7210/test.esp32-ard.yaml b/tests/components/es7210/test.esp32-ard.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/es7210/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/es7210/test.esp32-c3-ard.yaml b/tests/components/es7210/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es7210/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/es7210/test.esp32-c3-idf.yaml b/tests/components/es7210/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es7210/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/es7210/test.esp32-idf.yaml b/tests/components/es7210/test.esp32-idf.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/es7210/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml From c458fd18df52923b6b3fb35e2ea6efd4d6603f72 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:49:58 +1300 Subject: [PATCH 10/36] Bump version to 2025.2.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 4c0203c6a9..284f8d5f78 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2025.1.0-dev" +__version__ = "2025.2.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From e779a8bcb2e73eddb7eb3474814dd6c64bb98486 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:54:45 +1300 Subject: [PATCH 11/36] [event] Store ``last_event_type`` in class (#8088) --- esphome/components/event/event.cpp | 6 ++++-- esphome/components/event/event.h | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index 061afcb026..d27b3b378e 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -8,11 +8,13 @@ namespace event { static const char *const TAG = "event"; void Event::trigger(const std::string &event_type) { - if (types_.find(event_type) == types_.end()) { + auto found = types_.find(event_type); + if (found == types_.end()) { ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str()); return; } - ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), event_type.c_str()); + last_event_type = &(*found); + ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str()); this->event_callback_.call(event_type); } diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h index 067a867360..03c3c8d95a 100644 --- a/esphome/components/event/event.h +++ b/esphome/components/event/event.h @@ -23,6 +23,8 @@ namespace event { class Event : public EntityBase, public EntityBase_DeviceClass { public: + const std::string *last_event_type; + void trigger(const std::string &event_type); void set_event_types(const std::set &event_types) { this->types_ = event_types; } std::set get_event_types() const { return this->types_; } From df26ace0f172d1bad7f019fc4d095f3a7be188f9 Mon Sep 17 00:00:00 2001 From: Jordan Zucker Date: Tue, 14 Jan 2025 19:56:22 -0800 Subject: [PATCH 12/36] [prometheus] Select, media_player, and number prometheus metrics (#7895) --- .../prometheus/prometheus_handler.cpp | 168 ++++++++++++++++++ .../prometheus/prometheus_handler.h | 24 +++ tests/components/prometheus/common.yaml | 20 +++ .../components/prometheus/test.esp32-ard.yaml | 33 ++++ 4 files changed, 245 insertions(+) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 5d1861202a..2d39d8ef3f 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -59,6 +59,24 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { this->text_sensor_row_(stream, obj, area, node, friendly_name); #endif +#ifdef USE_NUMBER + this->number_type_(stream); + for (auto *obj : App.get_numbers()) + this->number_row_(stream, obj, area, node, friendly_name); +#endif + +#ifdef USE_SELECT + this->select_type_(stream); + for (auto *obj : App.get_selects()) + this->select_row_(stream, obj, area, node, friendly_name); +#endif + +#ifdef USE_MEDIA_PLAYER + this->media_player_type_(stream); + for (auto *obj : App.get_media_players()) + this->media_player_row_(stream, obj, area, node, friendly_name); +#endif + req->send(stream); } @@ -511,6 +529,156 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso } #endif +// Type-specific implementation +#ifdef USE_NUMBER +void PrometheusHandler::number_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_number_value gauge\n")); + stream->print(F("#TYPE esphome_number_failed gauge\n")); +} +void PrometheusHandler::number_row_(AsyncResponseStream *stream, number::Number *obj, std::string &area, + std::string &node, std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + if (!std::isnan(obj->state)) { + // We have a valid value, output this value + stream->print(F("esphome_number_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_number_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} ")); + stream->print(obj->state); + stream->print(F("\n")); + } else { + // Invalid state + stream->print(F("esphome_number_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 1\n")); + } +} +#endif + +#ifdef USE_SELECT +void PrometheusHandler::select_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_select_value gauge\n")); + stream->print(F("#TYPE esphome_select_failed gauge\n")); +} +void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select *obj, std::string &area, + std::string &node, std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + if (obj->has_state()) { + // We have a valid value, output this value + stream->print(F("esphome_select_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_select_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\",value=\"")); + stream->print(obj->state.c_str()); + stream->print(F("\"} ")); + stream->print(F("1.0")); + stream->print(F("\n")); + } else { + // Invalid state + stream->print(F("esphome_select_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 1\n")); + } +} +#endif + +#ifdef USE_MEDIA_PLAYER +void PrometheusHandler::media_player_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_media_player_state_value gauge\n")); + stream->print(F("#TYPE esphome_media_player_volume gauge\n")); + stream->print(F("#TYPE esphome_media_player_is_muted gauge\n")); + stream->print(F("#TYPE esphome_media_player_failed gauge\n")); +} +void PrometheusHandler::media_player_row_(AsyncResponseStream *stream, media_player::MediaPlayer *obj, + std::string &area, std::string &node, std::string &friendly_name) { + if (obj->is_internal() && !this->include_internal_) + return; + stream->print(F("esphome_media_player_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_media_player_state_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\",value=\"")); + stream->print(media_player::media_player_state_to_string(obj->state)); + stream->print(F("\"} ")); + stream->print(F("1.0")); + stream->print(F("\n")); + stream->print(F("esphome_media_player_volume{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} ")); + stream->print(obj->volume); + stream->print(F("\n")); + stream->print(F("esphome_media_player_is_muted{id=\"")); + stream->print(relabel_id_(obj).c_str()); + add_area_label_(stream, area); + add_node_label_(stream, node); + add_friendly_name_label_(stream, friendly_name); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} ")); + if (obj->is_muted()) { + stream->print(F("1.0")); + } else { + stream->print(F("0.0")); + } + stream->print(F("\n")); +} +#endif + } // namespace prometheus } // namespace esphome #endif diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 5d08aca63a..41a06537ed 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -128,6 +128,30 @@ class PrometheusHandler : public AsyncWebHandler, public Component { std::string &friendly_name); #endif +#ifdef USE_NUMBER + /// Return the type for prometheus + void number_type_(AsyncResponseStream *stream); + /// Return the sensor state as prometheus data point + void number_row_(AsyncResponseStream *stream, number::Number *obj, std::string &area, std::string &node, + std::string &friendly_name); +#endif + +#ifdef USE_SELECT + /// Return the type for prometheus + void select_type_(AsyncResponseStream *stream); + /// Return the select state as prometheus data point + void select_row_(AsyncResponseStream *stream, select::Select *obj, std::string &area, std::string &node, + std::string &friendly_name); +#endif + +#ifdef USE_MEDIA_PLAYER + /// Return the type for prometheus + void media_player_type_(AsyncResponseStream *stream); + /// Return the select state as prometheus data point + void media_player_row_(AsyncResponseStream *stream, media_player::MediaPlayer *obj, std::string &area, + std::string &node, std::string &friendly_name); +#endif + web_server_base::WebServerBase *base_; bool include_internal_{false}; std::map relabel_map_id_; diff --git a/tests/components/prometheus/common.yaml b/tests/components/prometheus/common.yaml index 68ef2a2f58..1b87c1d6c1 100644 --- a/tests/components/prometheus/common.yaml +++ b/tests/components/prometheus/common.yaml @@ -78,6 +78,26 @@ lock: } optimistic: true +select: + - platform: template + id: template_select1 + name: "Template select" + optimistic: true + options: + - one + - two + - three + initial_option: two + +number: + - platform: template + id: template_number1 + name: "Template number" + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + prometheus: include_internal: true relabel: diff --git a/tests/components/prometheus/test.esp32-ard.yaml b/tests/components/prometheus/test.esp32-ard.yaml index dade44d145..3045a6db13 100644 --- a/tests/components/prometheus/test.esp32-ard.yaml +++ b/tests/components/prometheus/test.esp32-ard.yaml @@ -1 +1,34 @@ <<: !include common.yaml + +i2s_audio: + i2s_lrclk_pin: 1 + i2s_bclk_pin: 2 + i2s_mclk_pin: 3 + +media_player: + - platform: i2s_audio + name: "Media Player" + dac_type: external + i2s_dout_pin: 18 + mute_pin: 19 + on_state: + - media_player.play: + - media_player.play_media: http://localhost/media.mp3 + - media_player.play_media: !lambda 'return "http://localhost/media.mp3";' + on_idle: + - media_player.pause: + on_play: + - media_player.stop: + on_pause: + - media_player.toggle: + - wait_until: + media_player.is_idle: + - wait_until: + media_player.is_playing: + - wait_until: + media_player.is_announcing: + - wait_until: + media_player.is_paused: + - media_player.volume_up: + - media_player.volume_down: + - media_player.volume_set: 50% From b4a804cc779bcb8a3b1480a44741bf9f29caa33c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:53:23 +1300 Subject: [PATCH 13/36] Bump docker/build-push-action from 6.11.0 to 6.12.0 in /.github/actions/build-image (#8090) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/build-image/action.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/build-image/action.yaml b/.github/actions/build-image/action.yaml index e6b6da4177..b2a3394563 100644 --- a/.github/actions/build-image/action.yaml +++ b/.github/actions/build-image/action.yaml @@ -46,7 +46,7 @@ runs: - name: Build and push to ghcr by digest id: build-ghcr - uses: docker/build-push-action@v6.11.0 + uses: docker/build-push-action@v6.12.0 env: DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_RECORD_UPLOAD: false @@ -72,7 +72,7 @@ runs: - name: Build and push to dockerhub by digest id: build-dockerhub - uses: docker/build-push-action@v6.11.0 + uses: docker/build-push-action@v6.12.0 env: DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_RECORD_UPLOAD: false From 49c01c26f1847debfa53dc7e31b6434debcfd40e Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:12:30 +1100 Subject: [PATCH 14/36] Revert "Add resistance_sampler interface for config validation" (#8093) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 - esphome/components/ntc/sensor.py | 6 ++---- esphome/components/resistance/resistance_sensor.h | 5 ++--- esphome/components/resistance/sensor.py | 11 ++--------- esphome/components/resistance_sampler/__init__.py | 6 ------ .../resistance_sampler/resistance_sampler.h | 10 ---------- 6 files changed, 6 insertions(+), 33 deletions(-) delete mode 100644 esphome/components/resistance_sampler/__init__.py delete mode 100644 esphome/components/resistance_sampler/resistance_sampler.h diff --git a/CODEOWNERS b/CODEOWNERS index ba7106e6a3..f0075549fd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -339,7 +339,6 @@ esphome/components/radon_eye_rd200/* @jeffeb3 esphome/components/rc522/* @glmnet esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet -esphome/components/resistance_sampler/* @jesserockz esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py index 961511fe00..bd5f4a1841 100644 --- a/esphome/components/ntc/sensor.py +++ b/esphome/components/ntc/sensor.py @@ -2,7 +2,7 @@ from math import log import esphome.config_validation as cv import esphome.codegen as cg -from esphome.components import sensor, resistance_sampler +from esphome.components import sensor from esphome.const import ( CONF_CALIBRATION, CONF_REFERENCE_RESISTANCE, @@ -15,8 +15,6 @@ from esphome.const import ( UNIT_CELSIUS, ) -AUTO_LOAD = ["resistance_sampler"] - ntc_ns = cg.esphome_ns.namespace("ntc") NTC = ntc_ns.class_("NTC", cg.Component, sensor.Sensor) @@ -126,7 +124,7 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.Required(CONF_SENSOR): cv.use_id(resistance_sampler.ResistanceSampler), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_CALIBRATION): process_calibration, } ) diff --git a/esphome/components/resistance/resistance_sensor.h b/esphome/components/resistance/resistance_sensor.h index 8fa1f8b570..b57f90b59c 100644 --- a/esphome/components/resistance/resistance_sensor.h +++ b/esphome/components/resistance/resistance_sensor.h @@ -1,8 +1,7 @@ #pragma once -#include "esphome/components/resistance_sampler/resistance_sampler.h" -#include "esphome/components/sensor/sensor.h" #include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" namespace esphome { namespace resistance { @@ -12,7 +11,7 @@ enum ResistanceConfiguration { DOWNSTREAM, }; -class ResistanceSensor : public Component, public sensor::Sensor, resistance_sampler::ResistanceSampler { +class ResistanceSensor : public Component, public sensor::Sensor { public: void set_sensor(Sensor *sensor) { sensor_ = sensor; } void set_configuration(ResistanceConfiguration configuration) { configuration_ = configuration; } diff --git a/esphome/components/resistance/sensor.py b/esphome/components/resistance/sensor.py index ce4459fc6d..3622799a07 100644 --- a/esphome/components/resistance/sensor.py +++ b/esphome/components/resistance/sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import sensor, resistance_sampler +from esphome.components import sensor from esphome.const import ( CONF_REFERENCE_VOLTAGE, CONF_SENSOR, @@ -9,15 +9,8 @@ from esphome.const import ( ICON_FLASH, ) -AUTO_LOAD = ["resistance_sampler"] - resistance_ns = cg.esphome_ns.namespace("resistance") -ResistanceSensor = resistance_ns.class_( - "ResistanceSensor", - cg.Component, - sensor.Sensor, - resistance_sampler.ResistanceSampler, -) +ResistanceSensor = resistance_ns.class_("ResistanceSensor", cg.Component, sensor.Sensor) CONF_CONFIGURATION = "configuration" CONF_RESISTOR = "resistor" diff --git a/esphome/components/resistance_sampler/__init__.py b/esphome/components/resistance_sampler/__init__.py deleted file mode 100644 index d2032848aa..0000000000 --- a/esphome/components/resistance_sampler/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -import esphome.codegen as cg - -resistance_sampler_ns = cg.esphome_ns.namespace("resistance_sampler") -ResistanceSampler = resistance_sampler_ns.class_("ResistanceSampler") - -CODEOWNERS = ["@jesserockz"] diff --git a/esphome/components/resistance_sampler/resistance_sampler.h b/esphome/components/resistance_sampler/resistance_sampler.h deleted file mode 100644 index 9e300bebcc..0000000000 --- a/esphome/components/resistance_sampler/resistance_sampler.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -namespace esphome { -namespace resistance_sampler { - -/// Abstract interface to mark components that provide resistance values. -class ResistanceSampler {}; - -} // namespace resistance_sampler -} // namespace esphome From 16bf56b0f95b1c5a75cc2f4ae167e37485c8803b Mon Sep 17 00:00:00 2001 From: Katherine Whitlock Date: Thu, 16 Jan 2025 15:10:20 -0500 Subject: [PATCH 15/36] Fix running pre-commit on Windows (#8095) --- .pre-commit-config.yaml | 4 ++-- script/run-in-env | 53 +++++++++++++++++++++++++++++++++++++++++ script/run-in-env.sh | 13 ---------- 3 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 script/run-in-env delete mode 100755 script/run-in-env.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aeb434167a..5308a2c047 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,6 +53,6 @@ repos: hooks: - id: pylint name: pylint - entry: script/run-in-env.sh pylint - language: script + entry: python script/run-in-env pylint + language: system types: [python] diff --git a/script/run-in-env b/script/run-in-env new file mode 100644 index 0000000000..57bfddccf7 --- /dev/null +++ b/script/run-in-env @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import os +from pathlib import Path +import subprocess +import sys + + +def find_and_activate_virtualenv(): + try: + # Get the top-level directory of the git repository + my_path = subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], text=True + ).strip() + except subprocess.CalledProcessError: + print( + "Error: Not a git repository or unable to determine the top-level directory.", + file=sys.stderr, + ) + sys.exit(1) + + # Check for virtual environments + for venv in ["venv", ".venv", "."]: + activate_path = ( + Path(my_path) + / venv + / ("Scripts" if os.name == "nt" else "bin") + / "activate" + ) + if activate_path.exists(): + # Activate the virtual environment by updating PATH + env = os.environ.copy() + venv_bin_dir = activate_path.parent + env["PATH"] = f"{venv_bin_dir}{os.pathsep}{env['PATH']}" + env["VIRTUAL_ENV"] = str(venv_bin_dir.parent) + print(f"Activated virtual environment: {venv_bin_dir.parent}") + + # Execute the remaining arguments in the new environment + if len(sys.argv) > 1: + subprocess.run(sys.argv[1:], env=env, check=False) + else: + print( + "No command provided to run in the virtual environment.", + file=sys.stderr, + ) + return + + print("No virtual environment found.", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + find_and_activate_virtualenv() diff --git a/script/run-in-env.sh b/script/run-in-env.sh deleted file mode 100755 index 2e05fe1d17..0000000000 --- a/script/run-in-env.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env sh -set -eu - -my_path=$(git rev-parse --show-toplevel) - -for venv in venv .venv .; do - if [ -f "${my_path}/${venv}/bin/activate" ]; then - . "${my_path}/${venv}/bin/activate" - break - fi -done - -exec "$@" From 8c6c45e6c197eb45400f6a2dd17568afe3977749 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 16 Jan 2025 15:43:41 -0600 Subject: [PATCH 16/36] [http_request] Bugfix: run update function in a task (#8018) --- .../http_request/http_request_idf.cpp | 24 ++- .../http_request/http_request_idf.h | 3 + .../update/http_request_update.cpp | 147 ++++++++++-------- .../http_request/update/http_request_update.h | 9 ++ 4 files changed, 115 insertions(+), 68 deletions(-) diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index b449f046ee..66f064c2ce 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -12,6 +12,8 @@ #include "esp_crt_bundle.h" #endif +#include "esp_task_wdt.h" + namespace esphome { namespace http_request { @@ -117,11 +119,11 @@ std::shared_ptr HttpRequestIDF::start(std::string url, std::strin return nullptr; } - App.feed_wdt(); + container->feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); - App.feed_wdt(); + container->feed_wdt(); container->status_code = esp_http_client_get_status_code(client); - App.feed_wdt(); + container->feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; @@ -151,11 +153,11 @@ std::shared_ptr HttpRequestIDF::start(std::string url, std::strin return nullptr; } - App.feed_wdt(); + container->feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); - App.feed_wdt(); + container->feed_wdt(); container->status_code = esp_http_client_get_status_code(client); - App.feed_wdt(); + container->feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; @@ -185,8 +187,9 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { return 0; } - App.feed_wdt(); + this->feed_wdt(); int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize); + this->feed_wdt(); this->bytes_read_ += read_len; this->duration_ms += (millis() - start); @@ -201,6 +204,13 @@ void HttpContainerIDF::end() { esp_http_client_cleanup(this->client_); } +void HttpContainerIDF::feed_wdt() { + // Tests to see if the executing task has a watchdog timer attached + if (esp_task_wdt_status(nullptr) == ESP_OK) { + App.feed_wdt(); + } +} + } // namespace http_request } // namespace esphome diff --git a/esphome/components/http_request/http_request_idf.h b/esphome/components/http_request/http_request_idf.h index 431794924b..2ed50698b9 100644 --- a/esphome/components/http_request/http_request_idf.h +++ b/esphome/components/http_request/http_request_idf.h @@ -18,6 +18,9 @@ class HttpContainerIDF : public HttpContainer { int read(uint8_t *buf, size_t max_len) override; void end() override; + /// @brief Feeds the watchdog timer if the executing task has one attached + void feed_wdt(); + protected: esp_http_client_handle_t client_; }; diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 0e0966c22b..d683495ac6 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -9,6 +9,13 @@ namespace esphome { namespace http_request { +// The update function runs in a task only on ESP32s. +#ifdef USE_ESP32 +#define UPDATE_RETURN vTaskDelete(nullptr) // Delete the current update task +#else +#define UPDATE_RETURN return +#endif + static const char *const TAG = "http_request.update"; static const size_t MAX_READ_SIZE = 256; @@ -29,113 +36,131 @@ void HttpRequestUpdate::setup() { } void HttpRequestUpdate::update() { - auto container = this->request_parent_->get(this->source_url_); +#ifdef USE_ESP32 + xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_); +#else + this->update_task(this); +#endif +} + +void HttpRequestUpdate::update_task(void *params) { + HttpRequestUpdate *this_update = (HttpRequestUpdate *) params; + + auto container = this_update->request_parent_->get(this_update->source_url_); if (container == nullptr || container->status_code != HTTP_STATUS_OK) { - std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str()); - this->status_set_error(msg.c_str()); - return; + std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str()); + this_update->status_set_error(msg.c_str()); + UPDATE_RETURN; } ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); uint8_t *data = allocator.allocate(container->content_length); if (data == nullptr) { std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length); - this->status_set_error(msg.c_str()); + this_update->status_set_error(msg.c_str()); container->end(); - return; + UPDATE_RETURN; } size_t read_index = 0; while (container->get_bytes_read() < container->content_length) { int read_bytes = container->read(data + read_index, MAX_READ_SIZE); - App.feed_wdt(); yield(); read_index += read_bytes; } - std::string response((char *) data, read_index); - allocator.deallocate(data, container->content_length); + bool valid = false; + { // Ensures the response string falls out of scope and deallocates before the task ends + std::string response((char *) data, read_index); + allocator.deallocate(data, container->content_length); - container->end(); + container->end(); + container.reset(); // Release ownership of the container's shared_ptr - bool valid = json::parse_json(response, [this](JsonObject root) -> bool { - if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) { - ESP_LOGE(TAG, "Manifest does not contain required fields"); - return false; - } - this->update_info_.title = root["name"].as(); - this->update_info_.latest_version = root["version"].as(); - - for (auto build : root["builds"].as()) { - if (!build.containsKey("chipFamily")) { + valid = json::parse_json(response, [this_update](JsonObject root) -> bool { + if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - if (build["chipFamily"] == ESPHOME_VARIANT) { - if (!build.containsKey("ota")) { + this_update->update_info_.title = root["name"].as(); + this_update->update_info_.latest_version = root["version"].as(); + + for (auto build : root["builds"].as()) { + if (!build.containsKey("chipFamily")) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - auto ota = build["ota"]; - if (!ota.containsKey("path") || !ota.containsKey("md5")) { - ESP_LOGE(TAG, "Manifest does not contain required fields"); - return false; + if (build["chipFamily"] == ESPHOME_VARIANT) { + if (!build.containsKey("ota")) { + ESP_LOGE(TAG, "Manifest does not contain required fields"); + return false; + } + auto ota = build["ota"]; + if (!ota.containsKey("path") || !ota.containsKey("md5")) { + ESP_LOGE(TAG, "Manifest does not contain required fields"); + return false; + } + this_update->update_info_.firmware_url = ota["path"].as(); + this_update->update_info_.md5 = ota["md5"].as(); + + if (ota.containsKey("summary")) + this_update->update_info_.summary = ota["summary"].as(); + if (ota.containsKey("release_url")) + this_update->update_info_.release_url = ota["release_url"].as(); + + return true; } - this->update_info_.firmware_url = ota["path"].as(); - this->update_info_.md5 = ota["md5"].as(); - - if (ota.containsKey("summary")) - this->update_info_.summary = ota["summary"].as(); - if (ota.containsKey("release_url")) - this->update_info_.release_url = ota["release_url"].as(); - - return true; } - } - return false; - }); + return false; + }); + } if (!valid) { - std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str()); - this->status_set_error(msg.c_str()); - return; + std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str()); + this_update->status_set_error(msg.c_str()); + UPDATE_RETURN; } - // Merge source_url_ and this->update_info_.firmware_url - if (this->update_info_.firmware_url.find("http") == std::string::npos) { - std::string path = this->update_info_.firmware_url; + // Merge source_url_ and this_update->update_info_.firmware_url + if (this_update->update_info_.firmware_url.find("http") == std::string::npos) { + std::string path = this_update->update_info_.firmware_url; if (path[0] == '/') { - std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8)); - this->update_info_.firmware_url = domain + path; + std::string domain = this_update->source_url_.substr(0, this_update->source_url_.find('/', 8)); + this_update->update_info_.firmware_url = domain + path; } else { - std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1); - this->update_info_.firmware_url = domain + path; + std::string domain = this_update->source_url_.substr(0, this_update->source_url_.rfind('/') + 1); + this_update->update_info_.firmware_url = domain + path; } } - std::string current_version; + { // Ensures the current version string falls out of scope and deallocates before the task ends + std::string current_version; #ifdef ESPHOME_PROJECT_VERSION - current_version = ESPHOME_PROJECT_VERSION; + current_version = ESPHOME_PROJECT_VERSION; #else - current_version = ESPHOME_VERSION; + current_version = ESPHOME_VERSION; #endif - this->update_info_.current_version = current_version; - - if (this->update_info_.latest_version.empty() || this->update_info_.latest_version == update_info_.current_version) { - this->state_ = update::UPDATE_STATE_NO_UPDATE; - } else { - this->state_ = update::UPDATE_STATE_AVAILABLE; + this_update->update_info_.current_version = current_version; } - this->update_info_.has_progress = false; - this->update_info_.progress = 0.0f; + if (this_update->update_info_.latest_version.empty() || + this_update->update_info_.latest_version == this_update->update_info_.current_version) { + this_update->state_ = update::UPDATE_STATE_NO_UPDATE; + } else { + this_update->state_ = update::UPDATE_STATE_AVAILABLE; + } - this->status_clear_error(); - this->publish_state(); + this_update->update_info_.has_progress = false; + this_update->update_info_.progress = 0.0f; + + this_update->status_clear_error(); + this_update->publish_state(); + + UPDATE_RETURN; } void HttpRequestUpdate::perform(bool force) { diff --git a/esphome/components/http_request/update/http_request_update.h b/esphome/components/http_request/update/http_request_update.h index 45c7e6a447..e05fdb0cc2 100644 --- a/esphome/components/http_request/update/http_request_update.h +++ b/esphome/components/http_request/update/http_request_update.h @@ -7,6 +7,10 @@ #include "esphome/components/http_request/ota/ota_http_request.h" #include "esphome/components/update/update_entity.h" +#ifdef USE_ESP32 +#include +#endif + namespace esphome { namespace http_request { @@ -29,6 +33,11 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent { HttpRequestComponent *request_parent_; OtaHttpRequestComponent *ota_parent_; std::string source_url_; + + static void update_task(void *params); +#ifdef USE_ESP32 + TaskHandle_t update_task_handle_{nullptr}; +#endif }; } // namespace http_request From 820e3488d03f3644a5661156d78995c2d964a092 Mon Sep 17 00:00:00 2001 From: Katherine Whitlock Date: Thu, 16 Jan 2025 16:44:26 -0500 Subject: [PATCH 17/36] Remove black-formatter from pre-commit hooks (#8097) --- .pre-commit-config.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5308a2c047..adf0ac6fc2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,14 +11,6 @@ repos: args: [--fix] # Run the formatter. - id: ruff-format - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.4.2 - hooks: - - id: black - args: - - --safe - - --quiet - files: ^((esphome|script|tests)/.+)?[^/]+\.py$ - repo: https://github.com/PyCQA/flake8 rev: 6.1.0 hooks: From 07be7ad7e2f874eaedcfc20ea68a9f8c33b72494 Mon Sep 17 00:00:00 2001 From: j-sepul <117353037+j-sepul@users.noreply.github.com> Date: Thu, 16 Jan 2025 23:08:04 +0100 Subject: [PATCH 18/36] Increase Daly-BMS coltage cells from 16 to 18 cells (#8057) --- esphome/components/daly_bms/daly_bms.cpp | 6 ++++++ esphome/components/daly_bms/daly_bms.h | 2 ++ esphome/components/daly_bms/sensor.py | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/esphome/components/daly_bms/daly_bms.cpp b/esphome/components/daly_bms/daly_bms.cpp index 8f6fc0fb57..929f31e008 100644 --- a/esphome/components/daly_bms/daly_bms.cpp +++ b/esphome/components/daly_bms/daly_bms.cpp @@ -298,6 +298,12 @@ void DalyBmsComponent::decode_data_(std::vector data) { if (this->cell_16_voltage_sensor_) { this->cell_16_voltage_sensor_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); } + if (this->cell_17_voltage_sensor_) { + this->cell_17_voltage_sensor_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_18_voltage_sensor_) { + this->cell_18_voltage_sensor_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } break; } break; diff --git a/esphome/components/daly_bms/daly_bms.h b/esphome/components/daly_bms/daly_bms.h index 52ea30ecde..e6d476bcdd 100644 --- a/esphome/components/daly_bms/daly_bms.h +++ b/esphome/components/daly_bms/daly_bms.h @@ -54,6 +54,8 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice { SUB_SENSOR(cell_14_voltage) SUB_SENSOR(cell_15_voltage) SUB_SENSOR(cell_16_voltage) + SUB_SENSOR(cell_17_voltage) + SUB_SENSOR(cell_18_voltage) #endif #ifdef USE_TEXT_SENSOR diff --git a/esphome/components/daly_bms/sensor.py b/esphome/components/daly_bms/sensor.py index c447fbd8a2..6d78946a02 100644 --- a/esphome/components/daly_bms/sensor.py +++ b/esphome/components/daly_bms/sensor.py @@ -52,6 +52,8 @@ CONF_CELL_13_VOLTAGE = "cell_13_voltage" CONF_CELL_14_VOLTAGE = "cell_14_voltage" CONF_CELL_15_VOLTAGE = "cell_15_voltage" CONF_CELL_16_VOLTAGE = "cell_16_voltage" +CONF_CELL_17_VOLTAGE = "cell_17_voltage" +CONF_CELL_18_VOLTAGE = "cell_18_voltage" ICON_CURRENT_DC = "mdi:current-dc" ICON_BATTERY_OUTLINE = "mdi:battery-outline" ICON_THERMOMETER_CHEVRON_UP = "mdi:thermometer-chevron-up" @@ -92,6 +94,8 @@ TYPES = [ CONF_CELL_14_VOLTAGE, CONF_CELL_15_VOLTAGE, CONF_CELL_16_VOLTAGE, + CONF_CELL_17_VOLTAGE, + CONF_CELL_18_VOLTAGE, ] CELL_VOLTAGE_SCHEMA = sensor.sensor_schema( @@ -212,6 +216,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_CELL_14_VOLTAGE): CELL_VOLTAGE_SCHEMA, cv.Optional(CONF_CELL_15_VOLTAGE): CELL_VOLTAGE_SCHEMA, cv.Optional(CONF_CELL_16_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_17_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_18_VOLTAGE): CELL_VOLTAGE_SCHEMA, } ).extend(cv.COMPONENT_SCHEMA) ) From abdd6b232f922b640ef93c335f71e9fc93f4cc2b Mon Sep 17 00:00:00 2001 From: Piotr Szulc Date: Mon, 13 Jan 2025 05:31:01 +0100 Subject: [PATCH 19/36] Fixed libretiny preference wrongly detecting change in the data to store (#7990) --- esphome/components/libretiny/preferences.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index ceeb30baf5..a090f42aa7 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -147,7 +147,7 @@ class LibreTinyPreferences : public ESPPreferences { ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str()); return true; } - stored_data.data.reserve(kv.value_len); + stored_data.data.resize(kv.value_len); fdb_blob_make(&blob, stored_data.data.data(), kv.value_len); size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob); if (actual_len != kv.value_len) { From 03c36920ffce80b4cf2505d2196ac8dca4c441eb Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 16 Jan 2025 15:43:41 -0600 Subject: [PATCH 20/36] [http_request] Bugfix: run update function in a task (#8018) --- .../http_request/http_request_idf.cpp | 24 ++- .../http_request/http_request_idf.h | 3 + .../update/http_request_update.cpp | 147 ++++++++++-------- .../http_request/update/http_request_update.h | 9 ++ 4 files changed, 115 insertions(+), 68 deletions(-) diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index b449f046ee..66f064c2ce 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -12,6 +12,8 @@ #include "esp_crt_bundle.h" #endif +#include "esp_task_wdt.h" + namespace esphome { namespace http_request { @@ -117,11 +119,11 @@ std::shared_ptr HttpRequestIDF::start(std::string url, std::strin return nullptr; } - App.feed_wdt(); + container->feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); - App.feed_wdt(); + container->feed_wdt(); container->status_code = esp_http_client_get_status_code(client); - App.feed_wdt(); + container->feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; @@ -151,11 +153,11 @@ std::shared_ptr HttpRequestIDF::start(std::string url, std::strin return nullptr; } - App.feed_wdt(); + container->feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); - App.feed_wdt(); + container->feed_wdt(); container->status_code = esp_http_client_get_status_code(client); - App.feed_wdt(); + container->feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; @@ -185,8 +187,9 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { return 0; } - App.feed_wdt(); + this->feed_wdt(); int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize); + this->feed_wdt(); this->bytes_read_ += read_len; this->duration_ms += (millis() - start); @@ -201,6 +204,13 @@ void HttpContainerIDF::end() { esp_http_client_cleanup(this->client_); } +void HttpContainerIDF::feed_wdt() { + // Tests to see if the executing task has a watchdog timer attached + if (esp_task_wdt_status(nullptr) == ESP_OK) { + App.feed_wdt(); + } +} + } // namespace http_request } // namespace esphome diff --git a/esphome/components/http_request/http_request_idf.h b/esphome/components/http_request/http_request_idf.h index 431794924b..2ed50698b9 100644 --- a/esphome/components/http_request/http_request_idf.h +++ b/esphome/components/http_request/http_request_idf.h @@ -18,6 +18,9 @@ class HttpContainerIDF : public HttpContainer { int read(uint8_t *buf, size_t max_len) override; void end() override; + /// @brief Feeds the watchdog timer if the executing task has one attached + void feed_wdt(); + protected: esp_http_client_handle_t client_; }; diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 0e0966c22b..d683495ac6 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -9,6 +9,13 @@ namespace esphome { namespace http_request { +// The update function runs in a task only on ESP32s. +#ifdef USE_ESP32 +#define UPDATE_RETURN vTaskDelete(nullptr) // Delete the current update task +#else +#define UPDATE_RETURN return +#endif + static const char *const TAG = "http_request.update"; static const size_t MAX_READ_SIZE = 256; @@ -29,113 +36,131 @@ void HttpRequestUpdate::setup() { } void HttpRequestUpdate::update() { - auto container = this->request_parent_->get(this->source_url_); +#ifdef USE_ESP32 + xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_); +#else + this->update_task(this); +#endif +} + +void HttpRequestUpdate::update_task(void *params) { + HttpRequestUpdate *this_update = (HttpRequestUpdate *) params; + + auto container = this_update->request_parent_->get(this_update->source_url_); if (container == nullptr || container->status_code != HTTP_STATUS_OK) { - std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str()); - this->status_set_error(msg.c_str()); - return; + std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str()); + this_update->status_set_error(msg.c_str()); + UPDATE_RETURN; } ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); uint8_t *data = allocator.allocate(container->content_length); if (data == nullptr) { std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length); - this->status_set_error(msg.c_str()); + this_update->status_set_error(msg.c_str()); container->end(); - return; + UPDATE_RETURN; } size_t read_index = 0; while (container->get_bytes_read() < container->content_length) { int read_bytes = container->read(data + read_index, MAX_READ_SIZE); - App.feed_wdt(); yield(); read_index += read_bytes; } - std::string response((char *) data, read_index); - allocator.deallocate(data, container->content_length); + bool valid = false; + { // Ensures the response string falls out of scope and deallocates before the task ends + std::string response((char *) data, read_index); + allocator.deallocate(data, container->content_length); - container->end(); + container->end(); + container.reset(); // Release ownership of the container's shared_ptr - bool valid = json::parse_json(response, [this](JsonObject root) -> bool { - if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) { - ESP_LOGE(TAG, "Manifest does not contain required fields"); - return false; - } - this->update_info_.title = root["name"].as(); - this->update_info_.latest_version = root["version"].as(); - - for (auto build : root["builds"].as()) { - if (!build.containsKey("chipFamily")) { + valid = json::parse_json(response, [this_update](JsonObject root) -> bool { + if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - if (build["chipFamily"] == ESPHOME_VARIANT) { - if (!build.containsKey("ota")) { + this_update->update_info_.title = root["name"].as(); + this_update->update_info_.latest_version = root["version"].as(); + + for (auto build : root["builds"].as()) { + if (!build.containsKey("chipFamily")) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - auto ota = build["ota"]; - if (!ota.containsKey("path") || !ota.containsKey("md5")) { - ESP_LOGE(TAG, "Manifest does not contain required fields"); - return false; + if (build["chipFamily"] == ESPHOME_VARIANT) { + if (!build.containsKey("ota")) { + ESP_LOGE(TAG, "Manifest does not contain required fields"); + return false; + } + auto ota = build["ota"]; + if (!ota.containsKey("path") || !ota.containsKey("md5")) { + ESP_LOGE(TAG, "Manifest does not contain required fields"); + return false; + } + this_update->update_info_.firmware_url = ota["path"].as(); + this_update->update_info_.md5 = ota["md5"].as(); + + if (ota.containsKey("summary")) + this_update->update_info_.summary = ota["summary"].as(); + if (ota.containsKey("release_url")) + this_update->update_info_.release_url = ota["release_url"].as(); + + return true; } - this->update_info_.firmware_url = ota["path"].as(); - this->update_info_.md5 = ota["md5"].as(); - - if (ota.containsKey("summary")) - this->update_info_.summary = ota["summary"].as(); - if (ota.containsKey("release_url")) - this->update_info_.release_url = ota["release_url"].as(); - - return true; } - } - return false; - }); + return false; + }); + } if (!valid) { - std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str()); - this->status_set_error(msg.c_str()); - return; + std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str()); + this_update->status_set_error(msg.c_str()); + UPDATE_RETURN; } - // Merge source_url_ and this->update_info_.firmware_url - if (this->update_info_.firmware_url.find("http") == std::string::npos) { - std::string path = this->update_info_.firmware_url; + // Merge source_url_ and this_update->update_info_.firmware_url + if (this_update->update_info_.firmware_url.find("http") == std::string::npos) { + std::string path = this_update->update_info_.firmware_url; if (path[0] == '/') { - std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8)); - this->update_info_.firmware_url = domain + path; + std::string domain = this_update->source_url_.substr(0, this_update->source_url_.find('/', 8)); + this_update->update_info_.firmware_url = domain + path; } else { - std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1); - this->update_info_.firmware_url = domain + path; + std::string domain = this_update->source_url_.substr(0, this_update->source_url_.rfind('/') + 1); + this_update->update_info_.firmware_url = domain + path; } } - std::string current_version; + { // Ensures the current version string falls out of scope and deallocates before the task ends + std::string current_version; #ifdef ESPHOME_PROJECT_VERSION - current_version = ESPHOME_PROJECT_VERSION; + current_version = ESPHOME_PROJECT_VERSION; #else - current_version = ESPHOME_VERSION; + current_version = ESPHOME_VERSION; #endif - this->update_info_.current_version = current_version; - - if (this->update_info_.latest_version.empty() || this->update_info_.latest_version == update_info_.current_version) { - this->state_ = update::UPDATE_STATE_NO_UPDATE; - } else { - this->state_ = update::UPDATE_STATE_AVAILABLE; + this_update->update_info_.current_version = current_version; } - this->update_info_.has_progress = false; - this->update_info_.progress = 0.0f; + if (this_update->update_info_.latest_version.empty() || + this_update->update_info_.latest_version == this_update->update_info_.current_version) { + this_update->state_ = update::UPDATE_STATE_NO_UPDATE; + } else { + this_update->state_ = update::UPDATE_STATE_AVAILABLE; + } - this->status_clear_error(); - this->publish_state(); + this_update->update_info_.has_progress = false; + this_update->update_info_.progress = 0.0f; + + this_update->status_clear_error(); + this_update->publish_state(); + + UPDATE_RETURN; } void HttpRequestUpdate::perform(bool force) { diff --git a/esphome/components/http_request/update/http_request_update.h b/esphome/components/http_request/update/http_request_update.h index 45c7e6a447..e05fdb0cc2 100644 --- a/esphome/components/http_request/update/http_request_update.h +++ b/esphome/components/http_request/update/http_request_update.h @@ -7,6 +7,10 @@ #include "esphome/components/http_request/ota/ota_http_request.h" #include "esphome/components/update/update_entity.h" +#ifdef USE_ESP32 +#include +#endif + namespace esphome { namespace http_request { @@ -29,6 +33,11 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent { HttpRequestComponent *request_parent_; OtaHttpRequestComponent *ota_parent_; std::string source_url_; + + static void update_task(void *params); +#ifdef USE_ESP32 + TaskHandle_t update_task_handle_{nullptr}; +#endif }; } // namespace http_request From 7666581c547612984eaa7fb049ac0b5985c68066 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:24:22 +1300 Subject: [PATCH 21/36] Bump version to 2024.12.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 10ad9454fd..a86e20c2e5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.12.2" +__version__ = "2024.12.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From c2423b18cbd279d4545ca672bebb665944e4b378 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:32:10 +1300 Subject: [PATCH 22/36] Bump python3-setuptools to 66.1.1-1+deb12u1 (#8074) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 0bb558d35e..429f5c4a1f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -29,7 +29,7 @@ RUN \ # Use pinned versions so that we get updates with build caching && apt-get install -y --no-install-recommends \ python3-pip=23.0.1+dfsg-1 \ - python3-setuptools=66.1.1-1 \ + python3-setuptools=66.1.1-1+deb12u1 \ python3-venv=3.11.2-1+b1 \ python3-wheel=0.38.4-2 \ iputils-ping=3:20221126-1+deb12u1 \ From 5a01670803ff6e78a5e605613f4855c6c16e33e1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:40:12 +1300 Subject: [PATCH 23/36] Bump version to 2024.12.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a86e20c2e5..b3d64997c4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.12.3" +__version__ = "2024.12.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 47a0ec467abd812e85f6a65946f3922f31ac5981 Mon Sep 17 00:00:00 2001 From: guillempages Date: Sun, 19 Jan 2025 22:34:38 +0100 Subject: [PATCH 24/36] [image]Rename option "use_transparency" (#8113) --- esphome/components/image/__init__.py | 14 ++++++------- esphome/components/online_image/__init__.py | 4 ++-- tests/components/animation/common.yaml | 4 ++-- tests/components/image/common.yaml | 22 ++++++++++----------- tests/components/image/test.host.yaml | 14 ++++++------- tests/components/online_image/common.yaml | 6 +++--- 6 files changed, 31 insertions(+), 33 deletions(-) diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index a503e8f471..7cdce757be 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -282,7 +282,7 @@ IMAGE_TYPE = { TransparencyType = image_ns.enum("TransparencyType") -CONF_USE_TRANSPARENCY = "use_transparency" +CONF_TRANSPARENCY = "transparency" # If the MDI file cannot be downloaded within this time, abort. IMAGE_DOWNLOAD_TIMEOUT = 30 # seconds @@ -417,7 +417,7 @@ def validate_type(image_types): def validate_settings(value): type = value[CONF_TYPE] - transparency = value[CONF_USE_TRANSPARENCY].lower() + transparency = value[CONF_TRANSPARENCY].lower() allow_config = IMAGE_TYPE[type].allow_config if transparency not in allow_config: raise cv.Invalid( @@ -458,9 +458,7 @@ BASE_SCHEMA = cv.Schema( IMAGE_SCHEMA = BASE_SCHEMA.extend( { cv.Required(CONF_TYPE): validate_type(IMAGE_TYPE), - cv.Optional( - CONF_USE_TRANSPARENCY, default=CONF_OPAQUE - ): validate_transparency(), + cv.Optional(CONF_TRANSPARENCY, default=CONF_OPAQUE): validate_transparency(), } ) @@ -476,7 +474,7 @@ def typed_image_schema(image_type): BASE_SCHEMA.extend( { cv.Optional( - CONF_USE_TRANSPARENCY, default=t + CONF_TRANSPARENCY, default=t ): validate_transparency((t,)), cv.Optional(CONF_TYPE, default=image_type): validate_type( (image_type,) @@ -494,7 +492,7 @@ def typed_image_schema(image_type): BASE_SCHEMA.extend( { cv.Optional( - CONF_USE_TRANSPARENCY, default=CONF_OPAQUE + CONF_TRANSPARENCY, default=CONF_OPAQUE ): validate_transparency(), cv.Optional(CONF_TYPE, default=image_type): validate_type( (image_type,) @@ -556,7 +554,7 @@ async def write_image(config, all_frames=False): else Image.Dither.FLOYDSTEINBERG ) type = config[CONF_TYPE] - transparency = config[CONF_USE_TRANSPARENCY] + transparency = config[CONF_TRANSPARENCY] invert_alpha = config[CONF_INVERT_ALPHA] frame_count = 1 if all_frames: diff --git a/esphome/components/online_image/__init__.py b/esphome/components/online_image/__init__.py index d1915c7364..ca4eefea6f 100644 --- a/esphome/components/online_image/__init__.py +++ b/esphome/components/online_image/__init__.py @@ -5,7 +5,7 @@ import esphome.codegen as cg from esphome.components.http_request import CONF_HTTP_REQUEST_ID, HttpRequestComponent from esphome.components.image import ( CONF_INVERT_ALPHA, - CONF_USE_TRANSPARENCY, + CONF_TRANSPARENCY, IMAGE_SCHEMA, Image_, get_image_type_enum, @@ -168,7 +168,7 @@ async def to_code(config): url = config[CONF_URL] width, height = config.get(CONF_RESIZE, (0, 0)) - transparent = get_transparency_enum(config[CONF_USE_TRANSPARENCY]) + transparent = get_transparency_enum(config[CONF_TRANSPARENCY]) var = cg.new_Pvariable( config[CONF_ID], diff --git a/tests/components/animation/common.yaml b/tests/components/animation/common.yaml index c0f04fe768..8bb2a2f4d8 100644 --- a/tests/components/animation/common.yaml +++ b/tests/components/animation/common.yaml @@ -2,12 +2,12 @@ animation: - id: rgb565_animation file: $component_dir/anim.gif type: RGB565 - use_transparency: opaque + transparency: opaque resize: 50x50 - id: rgb_animation file: $component_dir/anim.apng type: RGB - use_transparency: chroma_key + transparency: chroma_key resize: 50x50 - id: grayscale_animation file: $component_dir/anim.apng diff --git a/tests/components/image/common.yaml b/tests/components/image/common.yaml index fdb0493d2a..4c9b9ed670 100644 --- a/tests/components/image/common.yaml +++ b/tests/components/image/common.yaml @@ -6,54 +6,54 @@ image: - id: transparent_transparent_image file: ../../pnglogo.png type: BINARY - use_transparency: chroma_key + transparency: chroma_key - id: rgba_image file: ../../pnglogo.png type: RGB - use_transparency: alpha_channel + transparency: alpha_channel resize: 50x50 - id: rgb24_image file: ../../pnglogo.png type: RGB - use_transparency: chroma_key + transparency: chroma_key - id: rgb_image file: ../../pnglogo.png type: RGB - use_transparency: opaque + transparency: opaque - id: rgb565_image file: ../../pnglogo.png type: RGB565 - use_transparency: opaque + transparency: opaque - id: rgb565_ck_image file: ../../pnglogo.png type: RGB565 - use_transparency: chroma_key + transparency: chroma_key - id: rgb565_alpha_image file: ../../pnglogo.png type: RGB565 - use_transparency: alpha_channel + transparency: alpha_channel - id: grayscale_alpha_image file: ../../pnglogo.png type: grayscale - use_transparency: alpha_channel + transparency: alpha_channel resize: 50x50 - id: grayscale_ck_image file: ../../pnglogo.png type: grayscale - use_transparency: chroma_key + transparency: chroma_key - id: grayscale_image file: ../../pnglogo.png type: grayscale - use_transparency: opaque + transparency: opaque - id: web_svg_image file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg resize: 256x48 type: BINARY - use_transparency: chroma_key + transparency: chroma_key - id: web_tiff_image file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff type: RGB diff --git a/tests/components/image/test.host.yaml b/tests/components/image/test.host.yaml index 61ecd5e374..0411195e2a 100644 --- a/tests/components/image/test.host.yaml +++ b/tests/components/image/test.host.yaml @@ -12,7 +12,7 @@ image: dither: FloydSteinberg - id: transparent_transparent_image file: ../../pnglogo.png - use_transparency: chroma_key + transparency: chroma_key rgb: alpha_channel: - id: rgba_image @@ -28,21 +28,21 @@ image: rgb565: - id: rgb565_image file: ../../pnglogo.png - use_transparency: opaque + transparency: opaque - id: rgb565_ck_image file: ../../pnglogo.png - use_transparency: chroma_key + transparency: chroma_key - id: rgb565_alpha_image file: ../../pnglogo.png - use_transparency: alpha_channel + transparency: alpha_channel grayscale: - id: grayscale_alpha_image file: ../../pnglogo.png - use_transparency: alpha_channel + transparency: alpha_channel resize: 50x50 - id: grayscale_ck_image file: ../../pnglogo.png - use_transparency: chroma_key + transparency: chroma_key - id: grayscale_image file: ../../pnglogo.png - use_transparency: opaque + transparency: opaque diff --git a/tests/components/online_image/common.yaml b/tests/components/online_image/common.yaml index 81f43e9fdc..6c161e4f20 100644 --- a/tests/components/online_image/common.yaml +++ b/tests/components/online_image/common.yaml @@ -14,18 +14,18 @@ online_image: - id: online_binary_transparent_image url: http://www.libpng.org/pub/png/img_png/pnglogo-blk-tiny.png type: BINARY - use_transparency: chroma_key + transparency: chroma_key format: png - id: online_rgba_image url: http://www.libpng.org/pub/png/img_png/pnglogo-blk-tiny.png format: PNG type: RGB - use_transparency: alpha_channel + transparency: alpha_channel - id: online_rgb24_image url: http://www.libpng.org/pub/png/img_png/pnglogo-blk-tiny.png format: PNG type: RGB - use_transparency: chroma_key + transparency: chroma_key # Check the set_url action esphome: From 75026be9519a17f3f502dcaea3f21ec5f0c2f917 Mon Sep 17 00:00:00 2001 From: guillempages Date: Sun, 19 Jan 2025 23:16:37 +0100 Subject: [PATCH 25/36] [online_image] Use RAMAllocator (#8114) --- esphome/components/online_image/image_decoder.h | 2 +- esphome/components/online_image/online_image.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/esphome/components/online_image/image_decoder.h b/esphome/components/online_image/image_decoder.h index cde7f572e3..7c5175d72d 100644 --- a/esphome/components/online_image/image_decoder.h +++ b/esphome/components/online_image/image_decoder.h @@ -100,7 +100,7 @@ class DownloadBuffer { void reset() { this->unread_ = 0; } protected: - ExternalRAMAllocator allocator_; + RAMAllocator allocator_{}; uint8_t *buffer_; size_t size_; /** Total number of downloaded bytes not yet read. */ diff --git a/esphome/components/online_image/online_image.h b/esphome/components/online_image/online_image.h index e044b4f390..bafd8ba67e 100644 --- a/esphome/components/online_image/online_image.h +++ b/esphome/components/online_image/online_image.h @@ -83,8 +83,7 @@ class OnlineImage : public PollingComponent, protected: bool validate_url_(const std::string &url); - using Allocator = ExternalRAMAllocator; - Allocator allocator_{Allocator::Flags::ALLOW_FAILURE}; + RAMAllocator allocator_{}; uint32_t get_buffer_size_() const { return get_buffer_size_(this->buffer_width_, this->buffer_height_); } int get_buffer_size_(int width, int height) const { return (this->get_bpp() * width + 7u) / 8u * height; } From 98b872abc71b555640a9465fd30e3ba184ab49be Mon Sep 17 00:00:00 2001 From: Mikkel Jeppesen <2756925+Duckle29@users.noreply.github.com> Date: Sun, 19 Jan 2025 23:36:07 +0100 Subject: [PATCH 26/36] Fixed incorrect display dimension (#8110) --- esphome/components/waveshare_epaper/waveshare_213v3.cpp | 6 ++++-- esphome/components/waveshare_epaper/waveshare_epaper.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/waveshare_epaper/waveshare_213v3.cpp b/esphome/components/waveshare_epaper/waveshare_213v3.cpp index 196aeed3f7..85d7033d4b 100644 --- a/esphome/components/waveshare_epaper/waveshare_213v3.cpp +++ b/esphome/components/waveshare_epaper/waveshare_213v3.cpp @@ -72,7 +72,8 @@ void WaveshareEPaper2P13InV3::write_buffer_(uint8_t cmd, int top, int bottom) { this->set_window_(top, bottom); this->command(cmd); this->start_data_(); - auto width_bytes = this->get_width_internal() / 8; + + auto width_bytes = this->get_width_controller() / 8; this->write_array(this->buffer_ + top * width_bytes, (bottom - top) * width_bytes); this->end_data_(); } @@ -162,7 +163,8 @@ void WaveshareEPaper2P13InV3::display() { } } -int WaveshareEPaper2P13InV3::get_width_internal() { return 128; } +int WaveshareEPaper2P13InV3::get_width_controller() { return 128; } +int WaveshareEPaper2P13InV3::get_width_internal() { return 122; } int WaveshareEPaper2P13InV3::get_height_internal() { return 250; } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 4544f7df59..0fc1051268 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -811,6 +811,7 @@ class WaveshareEPaper2P13InV3 : public WaveshareEPaper { void initialize() override; protected: + int get_width_controller() override; int get_width_internal() override; int get_height_internal() override; uint32_t idle_timeout_() override; From c3d00b45f7703666fc06289d2ff84b72ce800ea5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 21 Jan 2025 14:50:04 +1300 Subject: [PATCH 27/36] Update defines.h for esp-idf 5.1.5 (#8117) --- esphome/core/defines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index c38a26c6a8..96a05435ed 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -120,7 +120,7 @@ #endif #ifdef USE_ESP_IDF -#define USE_ESP_IDF_VERSION_CODE VERSION_CODE(4, 4, 2) +#define USE_ESP_IDF_VERSION_CODE VERSION_CODE(5, 1, 5) #endif #if defined(USE_ESP32_VARIANT_ESP32S2) From 576dbd6f0c33ccddb2be4dcffd79e39749857b21 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 20 Jan 2025 20:35:40 -0600 Subject: [PATCH 28/36] [audio_adc] Add new ``audio_adc`` component (#8094) --- CODEOWNERS | 1 + esphome/components/audio_adc/__init__.py | 41 ++++++++++++++ esphome/components/audio_adc/audio_adc.h | 17 ++++++ esphome/components/audio_adc/automation.h | 23 ++++++++ esphome/components/es7210/__init__.py | 67 ----------------------- esphome/components/es7210/audio_adc.py | 51 +++++++++++++++++ esphome/components/es7210/es7210.cpp | 43 ++++++++++++--- esphome/components/es7210/es7210.h | 39 ++++++------- esphome/components/es7210/es7210_const.h | 7 ++- tests/components/es7210/common.yaml | 12 +++- 10 files changed, 200 insertions(+), 101 deletions(-) create mode 100644 esphome/components/audio_adc/__init__.py create mode 100644 esphome/components/audio_adc/audio_adc.h create mode 100644 esphome/components/audio_adc/automation.h create mode 100644 esphome/components/es7210/audio_adc.py diff --git a/CODEOWNERS b/CODEOWNERS index f0075549fd..56e20133ef 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -49,6 +49,7 @@ esphome/components/atc_mithermometer/* @ahpohl esphome/components/atm90e26/* @danieltwagner esphome/components/atm90e32/* @circuitsetup @descipher esphome/components/audio/* @kahrendt +esphome/components/audio_adc/* @kbx81 esphome/components/audio_dac/* @kbx81 esphome/components/axs15231/* @clydebarrow esphome/components/b_parasite/* @rbaron diff --git a/esphome/components/audio_adc/__init__.py b/esphome/components/audio_adc/__init__.py new file mode 100644 index 0000000000..dd3c958821 --- /dev/null +++ b/esphome/components/audio_adc/__init__.py @@ -0,0 +1,41 @@ +from esphome import automation +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_MIC_GAIN +from esphome.core import coroutine_with_priority + +CODEOWNERS = ["@kbx81"] +IS_PLATFORM_COMPONENT = True + +audio_adc_ns = cg.esphome_ns.namespace("audio_adc") +AudioAdc = audio_adc_ns.class_("AudioAdc") + +SetMicGainAction = audio_adc_ns.class_("SetMicGainAction", automation.Action) + + +SET_MIC_GAIN_ACTION_SCHEMA = cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(AudioAdc), + cv.Required(CONF_MIC_GAIN): cv.templatable(cv.decibel), + }, + key=CONF_MIC_GAIN, +) + + +@automation.register_action( + "audio_adc.set_mic_gain", SetMicGainAction, SET_MIC_GAIN_ACTION_SCHEMA +) +async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + + template_ = await cg.templatable(config.get(CONF_MIC_GAIN), args, float) + cg.add(var.set_mic_gain(template_)) + + return var + + +@coroutine_with_priority(100.0) +async def to_code(config): + cg.add_define("USE_AUDIO_ADC") + cg.add_global(audio_adc_ns.using) diff --git a/esphome/components/audio_adc/audio_adc.h b/esphome/components/audio_adc/audio_adc.h new file mode 100644 index 0000000000..94bfb57db5 --- /dev/null +++ b/esphome/components/audio_adc/audio_adc.h @@ -0,0 +1,17 @@ +#pragma once + +#include "esphome/core/defines.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace audio_adc { + +class AudioAdc { + public: + virtual bool set_mic_gain(float mic_gain) = 0; + + virtual float mic_gain() = 0; +}; + +} // namespace audio_adc +} // namespace esphome diff --git a/esphome/components/audio_adc/automation.h b/esphome/components/audio_adc/automation.h new file mode 100644 index 0000000000..1b0bc2a6ad --- /dev/null +++ b/esphome/components/audio_adc/automation.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "audio_adc.h" + +namespace esphome { +namespace audio_adc { + +template class SetMicGainAction : public Action { + public: + explicit SetMicGainAction(AudioAdc *audio_adc) : audio_adc_(audio_adc) {} + + TEMPLATABLE_VALUE(float, mic_gain) + + void play(Ts... x) override { this->audio_adc_->set_mic_gain(this->mic_gain_.value(x...)); } + + protected: + AudioAdc *audio_adc_; +}; + +} // namespace audio_adc +} // namespace esphome diff --git a/esphome/components/es7210/__init__.py b/esphome/components/es7210/__init__.py index 8e63d7f04f..e69de29bb2 100644 --- a/esphome/components/es7210/__init__.py +++ b/esphome/components/es7210/__init__.py @@ -1,67 +0,0 @@ -import esphome.codegen as cg -from esphome.components import i2c -import esphome.config_validation as cv -from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_MIC_GAIN, CONF_SAMPLE_RATE - -CODEOWNERS = ["@kahrendt"] -DEPENDENCIES = ["i2c"] - -es7210_ns = cg.esphome_ns.namespace("es7210") -ES7210 = es7210_ns.class_("ES7210", cg.Component, i2c.I2CDevice) - - -es7210_bits_per_sample = es7210_ns.enum("ES7210BitsPerSample") -ES7210_BITS_PER_SAMPLE_ENUM = { - 16: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_16, - 24: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_24, - 32: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_32, -} - - -es7210_mic_gain = es7210_ns.enum("ES7210MicGain") -ES7210_MIC_GAIN_ENUM = { - "0DB": es7210_mic_gain.ES7210_MIC_GAIN_0DB, - "3DB": es7210_mic_gain.ES7210_MIC_GAIN_3DB, - "6DB": es7210_mic_gain.ES7210_MIC_GAIN_6DB, - "9DB": es7210_mic_gain.ES7210_MIC_GAIN_9DB, - "12DB": es7210_mic_gain.ES7210_MIC_GAIN_12DB, - "15DB": es7210_mic_gain.ES7210_MIC_GAIN_15DB, - "18DB": es7210_mic_gain.ES7210_MIC_GAIN_18DB, - "21DB": es7210_mic_gain.ES7210_MIC_GAIN_21DB, - "24DB": es7210_mic_gain.ES7210_MIC_GAIN_24DB, - "27DB": es7210_mic_gain.ES7210_MIC_GAIN_27DB, - "30DB": es7210_mic_gain.ES7210_MIC_GAIN_30DB, - "33DB": es7210_mic_gain.ES7210_MIC_GAIN_33DB, - "34.5DB": es7210_mic_gain.ES7210_MIC_GAIN_34_5DB, - "36DB": es7210_mic_gain.ES7210_MIC_GAIN_36DB, - "37.5DB": es7210_mic_gain.ES7210_MIC_GAIN_37_5DB, -} - -_validate_bits = cv.float_with_unit("bits", "bit") - -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(ES7210), - cv.Optional(CONF_BITS_PER_SAMPLE, default="16bit"): cv.All( - _validate_bits, cv.enum(ES7210_BITS_PER_SAMPLE_ENUM) - ), - cv.Optional(CONF_MIC_GAIN, default="24DB"): cv.enum( - ES7210_MIC_GAIN_ENUM, upper=True - ), - cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), - } - ) - .extend(cv.COMPONENT_SCHEMA) - .extend(i2c.i2c_device_schema(0x40)) -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await i2c.register_i2c_device(var, config) - - cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) - cg.add(var.set_mic_gain(config[CONF_MIC_GAIN])) - cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) diff --git a/esphome/components/es7210/audio_adc.py b/esphome/components/es7210/audio_adc.py new file mode 100644 index 0000000000..f0bd8bc25a --- /dev/null +++ b/esphome/components/es7210/audio_adc.py @@ -0,0 +1,51 @@ +import esphome.codegen as cg +from esphome.components import i2c +from esphome.components.audio_adc import AudioAdc +import esphome.config_validation as cv +from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_MIC_GAIN, CONF_SAMPLE_RATE + +CODEOWNERS = ["@kahrendt"] +DEPENDENCIES = ["i2c"] + +es7210_ns = cg.esphome_ns.namespace("es7210") +ES7210 = es7210_ns.class_("ES7210", AudioAdc, cg.Component, i2c.I2CDevice) + + +es7210_bits_per_sample = es7210_ns.enum("ES7210BitsPerSample") +ES7210_BITS_PER_SAMPLE_ENUM = { + 16: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_16, + 24: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_24, + 32: es7210_bits_per_sample.ES7210_BITS_PER_SAMPLE_32, +} + + +ES7210_MIC_GAINS = [0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 34.5, 36, 37.5] + +_validate_bits = cv.float_with_unit("bits", "bit") + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ES7210), + cv.Optional(CONF_BITS_PER_SAMPLE, default="16bit"): cv.All( + _validate_bits, cv.enum(ES7210_BITS_PER_SAMPLE_ENUM) + ), + cv.Optional(CONF_MIC_GAIN, default="24db"): cv.All( + cv.decibel, cv.one_of(*ES7210_MIC_GAINS) + ), + cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x40)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) + cg.add(var.set_mic_gain(config[CONF_MIC_GAIN])) + cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) diff --git a/esphome/components/es7210/es7210.cpp b/esphome/components/es7210/es7210.cpp index d2f2c3c1ff..9a99e60995 100644 --- a/esphome/components/es7210/es7210.cpp +++ b/esphome/components/es7210/es7210.cpp @@ -25,12 +25,12 @@ static const size_t MCLK_DIV_FRE = 256; } void ES7210::dump_config() { - ESP_LOGCONFIG(TAG, "ES7210 ADC:"); + ESP_LOGCONFIG(TAG, "ES7210 audio ADC:"); ESP_LOGCONFIG(TAG, " Bits Per Sample: %" PRIu8, this->bits_per_sample_); ESP_LOGCONFIG(TAG, " Sample Rate: %" PRIu32, this->sample_rate_); if (this->is_failed()) { - ESP_LOGCONFIG(TAG, " Failed to initialize!"); + ESP_LOGE(TAG, " Failed to initialize"); return; } } @@ -84,6 +84,16 @@ void ES7210::setup() { // Enable device ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x71)); ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x41)); + + this->setup_complete_ = true; +} + +bool ES7210::set_mic_gain(float mic_gain) { + this->mic_gain_ = clamp(mic_gain, ES7210_MIC_GAIN_MIN, ES7210_MIC_GAIN_MAX); + if (this->setup_complete_) { + return this->configure_mic_gain_(); + } + return true; } bool ES7210::configure_sample_rate_() { @@ -122,9 +132,11 @@ bool ES7210::configure_sample_rate_() { return true; } + bool ES7210::configure_mic_gain_() { - for (int i = 0; i < 4; ++i) { - this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43 + i, 0x10, 0x00); + auto regv = this->es7210_gain_reg_value_(this->mic_gain_); + for (uint8_t i = 0; i < 4; ++i) { + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43 + i, 0x10, 0x00)); } ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0xff)); ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0xff)); @@ -133,29 +145,44 @@ bool ES7210::configure_mic_gain_() { ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00)); ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x10, 0x10)); - ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x0f, this->mic_gain_)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x0f, regv)); // Configure mic 2 ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00)); ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x10, 0x10)); - ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x0f, this->mic_gain_)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x0f, regv)); // Configure mic 3 ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00)); ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x10, 0x10)); - ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x0f, this->mic_gain_)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x0f, regv)); // Configure mic 4 ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00)); ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00)); ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x10, 0x10)); - ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x0f, this->mic_gain_)); + ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x0f, regv)); return true; } +uint8_t ES7210::es7210_gain_reg_value_(float mic_gain) { + // reg: 12 - 34.5dB, 13 - 36dB, 14 - 37.5dB + mic_gain += 0.5; + if (mic_gain <= 33.0) { + return (uint8_t) mic_gain / 3; + } + if (mic_gain < 36.0) { + return 12; + } + if (mic_gain < 37.0) { + return 13; + } + return 14; +} + bool ES7210::configure_i2s_format_() { // Configure bits per sample uint8_t reg_val = 0; diff --git a/esphome/components/es7210/es7210.h b/esphome/components/es7210/es7210.h index a40dde5aa5..8f6d9d8136 100644 --- a/esphome/components/es7210/es7210.h +++ b/esphome/components/es7210/es7210.h @@ -1,8 +1,11 @@ #pragma once +#include "esphome/components/audio_adc/audio_adc.h" #include "esphome/components/i2c/i2c.h" #include "esphome/core/component.h" +#include "es7210_const.h" + namespace esphome { namespace es7210 { @@ -14,25 +17,7 @@ enum ES7210BitsPerSample : uint8_t { ES7210_BITS_PER_SAMPLE_32 = 32, }; -enum ES7210MicGain : uint8_t { - ES7210_MIC_GAIN_0DB = 0, - ES7210_MIC_GAIN_3DB, - ES7210_MIC_GAIN_6DB, - ES7210_MIC_GAIN_9DB, - ES7210_MIC_GAIN_12DB, - ES7210_MIC_GAIN_15DB, - ES7210_MIC_GAIN_18DB, - ES7210_MIC_GAIN_21DB, - ES7210_MIC_GAIN_24DB, - ES7210_MIC_GAIN_27DB, - ES7210_MIC_GAIN_30DB, - ES7210_MIC_GAIN_33DB, - ES7210_MIC_GAIN_34_5DB, - ES7210_MIC_GAIN_36DB, - ES7210_MIC_GAIN_37_5DB, -}; - -class ES7210 : public Component, public i2c::I2CDevice { +class ES7210 : public audio_adc::AudioAdc, public Component, public i2c::I2CDevice { /* Class for configuring an ES7210 ADC for microphone input. * Based on code from: * - https://github.com/espressif/esp-bsp/ (accessed 20241219) @@ -44,9 +29,11 @@ class ES7210 : public Component, public i2c::I2CDevice { void dump_config() override; void set_bits_per_sample(ES7210BitsPerSample bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } - void set_mic_gain(ES7210MicGain mic_gain) { this->mic_gain_ = mic_gain; } + bool set_mic_gain(float mic_gain) override; void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; } + float mic_gain() override { return this->mic_gain_; }; + protected: /// @brief Updates an I2C registry address by modifying the current state /// @param reg_addr I2C register address @@ -55,14 +42,20 @@ class ES7210 : public Component, public i2c::I2CDevice { /// @return True if successful, false otherwise bool es7210_update_reg_bit_(uint8_t reg_addr, uint8_t update_bits, uint8_t data); + /// @brief Convert floating point mic gain value to register value + /// @param mic_gain Gain value to convert + /// @return Corresponding register value for specified gain + uint8_t es7210_gain_reg_value_(float mic_gain); + bool configure_i2s_format_(); bool configure_mic_gain_(); bool configure_sample_rate_(); + bool setup_complete_{false}; bool enable_tdm_{false}; // TDM is unsupported in ESPHome as of version 2024.12 - ES7210MicGain mic_gain_; - ES7210BitsPerSample bits_per_sample_; - uint32_t sample_rate_; + float mic_gain_{0}; + ES7210BitsPerSample bits_per_sample_{ES7210_BITS_PER_SAMPLE_16}; + uint32_t sample_rate_{0}; }; } // namespace es7210 diff --git a/esphome/components/es7210/es7210_const.h b/esphome/components/es7210/es7210_const.h index 87fd6d86d2..e5ffea5743 100644 --- a/esphome/components/es7210/es7210_const.h +++ b/esphome/components/es7210/es7210_const.h @@ -1,6 +1,6 @@ #pragma once -#include "es7210.h" +#include namespace esphome { namespace es7210 { @@ -42,7 +42,7 @@ static const uint8_t ES7210_MIC12_POWER_REG4B = 0x4B; /* MICBias & ADC & PGA Pow static const uint8_t ES7210_MIC34_POWER_REG4C = 0x4C; /* - * Clock coefficient structer + * Clock coefficient structure */ struct ES7210Coefficient { uint32_t mclk; // mclk frequency @@ -122,5 +122,8 @@ static const ES7210Coefficient ES7210_COEFFICIENTS[] = { {19200000, 96000, 0x01, 0x05, 0x00, 0x01, 0x28, 0x00, 0x00, 0xc8}, }; +static const float ES7210_MIC_GAIN_MIN = 0.0; +static const float ES7210_MIC_GAIN_MAX = 37.5; + } // namespace es7210 } // namespace esphome diff --git a/tests/components/es7210/common.yaml b/tests/components/es7210/common.yaml index 5c30f7e883..3fab177cb3 100644 --- a/tests/components/es7210/common.yaml +++ b/tests/components/es7210/common.yaml @@ -1,6 +1,16 @@ +esphome: + on_boot: + then: + - audio_adc.set_mic_gain: 0db + - audio_adc.set_mic_gain: !lambda 'return 4;' + i2c: - id: i2c_aic3204 scl: ${scl_pin} sda: ${sda_pin} -es7210: +audio_adc: + - platform: es7210 + id: es7210_adc + bits_per_sample: 16bit + sample_rate: 16000 From 0f4e274e522bb403b2e73fb535e0743c4129e2c4 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:43:50 +1100 Subject: [PATCH 29/36] [uptime] Cosmetic improvements for uptime text_sensor (#8101) --- .../components/uptime/text_sensor/__init__.py | 2 +- .../uptime/text_sensor/uptime_text_sensor.cpp | 59 ++++++++++++------- .../uptime/text_sensor/uptime_text_sensor.h | 4 +- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/esphome/components/uptime/text_sensor/__init__.py b/esphome/components/uptime/text_sensor/__init__.py index 996d983e71..e4a7ac6517 100644 --- a/esphome/components/uptime/text_sensor/__init__.py +++ b/esphome/components/uptime/text_sensor/__init__.py @@ -11,7 +11,7 @@ CONFIG_SCHEMA = text_sensor.text_sensor_schema( UptimeTextSensor, icon=ICON_TIMER, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, -).extend(cv.polling_component_schema("60s")) +).extend(cv.polling_component_schema("30s")) async def to_code(config): diff --git a/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp b/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp index 0fa5e199f3..409af6e4ff 100644 --- a/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +++ b/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp @@ -9,34 +9,51 @@ namespace uptime { static const char *const TAG = "uptime.sensor"; -void UptimeTextSensor::setup() { this->last_ms_ = millis(); } +void UptimeTextSensor::setup() { + this->last_ms_ = millis(); + if (this->last_ms_ < 60 * 1000) + this->last_ms_ = 0; + this->update(); +} void UptimeTextSensor::update() { - const auto now = millis(); + auto now = millis(); // get whole seconds since last update. Note that even if the millis count has overflowed between updates, // the difference will still be correct due to the way twos-complement arithmetic works. - const uint32_t delta = (now - this->last_ms_) / 1000; - if (delta == 0) - return; - // set last_ms_ to the last second boundary - this->last_ms_ = now - (now % 1000); + uint32_t delta = now - this->last_ms_; + this->last_ms_ = now - delta % 1000; // save remainder for next update + delta /= 1000; this->uptime_ += delta; auto uptime = this->uptime_; - unsigned days = uptime / (24 * 3600); - unsigned seconds = uptime % (24 * 3600); - unsigned hours = seconds / 3600; - seconds %= 3600; - unsigned minutes = seconds / 60; - seconds %= 60; - if (days != 0) { - this->publish_state(str_sprintf("%dd%dh%dm%ds", days, hours, minutes, seconds)); - } else if (hours != 0) { - this->publish_state(str_sprintf("%dh%dm%ds", hours, minutes, seconds)); - } else if (minutes != 0) { - this->publish_state(str_sprintf("%dm%ds", minutes, seconds)); - } else { - this->publish_state(str_sprintf("%ds", seconds)); + unsigned interval = this->get_update_interval() / 1000; + std::string buffer{}; + // display from the largest unit that corresponds to the update interval, drop larger units that are zero. + while (true) { // enable use of break for early exit + unsigned remainder = uptime % 60; + uptime /= 60; + if (interval < 30) { + buffer.insert(0, str_sprintf("%us", remainder)); + if (uptime == 0) + break; + } + remainder = uptime % 60; + uptime /= 60; + if (interval < 1800) { + buffer.insert(0, str_sprintf("%um", remainder)); + if (uptime == 0) + break; + } + remainder = uptime % 24; + uptime /= 24; + if (interval < 12 * 3600) { + buffer.insert(0, str_sprintf("%uh", remainder)); + if (uptime == 0) + break; + } + buffer.insert(0, str_sprintf("%ud", (unsigned) uptime)); + break; } + this->publish_state(buffer); } float UptimeTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; } diff --git a/esphome/components/uptime/text_sensor/uptime_text_sensor.h b/esphome/components/uptime/text_sensor/uptime_text_sensor.h index 4baf1039b6..5719ef38a2 100644 --- a/esphome/components/uptime/text_sensor/uptime_text_sensor.h +++ b/esphome/components/uptime/text_sensor/uptime_text_sensor.h @@ -17,8 +17,8 @@ class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent float get_setup_priority() const override; protected: - uint64_t uptime_{0}; - uint64_t last_ms_{0}; + uint32_t uptime_{0}; // uptime in seconds, will overflow after 136 years + uint32_t last_ms_{0}; }; } // namespace uptime From 716a8b87e1b033b454e3a1f50e3e37bb37177403 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 20 Jan 2025 21:15:18 -0600 Subject: [PATCH 30/36] [es8156] Add support for ES8156 audio DAC (#8085) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/es8156/__init__.py | 0 esphome/components/es8156/audio_dac.py | 27 ++++++ esphome/components/es8156/es8156.cpp | 87 +++++++++++++++++++ esphome/components/es8156/es8156.h | 51 +++++++++++ esphome/components/es8156/es8156_const.h | 68 +++++++++++++++ tests/components/es8156/common.yaml | 15 ++++ tests/components/es8156/test.esp32-ard.yaml | 5 ++ .../components/es8156/test.esp32-c3-ard.yaml | 5 ++ .../components/es8156/test.esp32-c3-idf.yaml | 5 ++ tests/components/es8156/test.esp32-idf.yaml | 5 ++ tests/components/es8156/test.esp8266-ard.yaml | 5 ++ 12 files changed, 274 insertions(+) create mode 100644 esphome/components/es8156/__init__.py create mode 100644 esphome/components/es8156/audio_dac.py create mode 100644 esphome/components/es8156/es8156.cpp create mode 100644 esphome/components/es8156/es8156.h create mode 100644 esphome/components/es8156/es8156_const.h create mode 100644 tests/components/es8156/common.yaml create mode 100644 tests/components/es8156/test.esp32-ard.yaml create mode 100644 tests/components/es8156/test.esp32-c3-ard.yaml create mode 100644 tests/components/es8156/test.esp32-c3-idf.yaml create mode 100644 tests/components/es8156/test.esp32-idf.yaml create mode 100644 tests/components/es8156/test.esp8266-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 56e20133ef..e2c674cfd3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -133,6 +133,7 @@ esphome/components/ens160_i2c/* @latonita esphome/components/ens160_spi/* @latonita esphome/components/ens210/* @itn3rd77 esphome/components/es7210/* @kahrendt +esphome/components/es8156/* @kbx81 esphome/components/es8311/* @kahrendt @kroimon esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @Rapsssito @jesserockz diff --git a/esphome/components/es8156/__init__.py b/esphome/components/es8156/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/es8156/audio_dac.py b/esphome/components/es8156/audio_dac.py new file mode 100644 index 0000000000..b9d8eae6b0 --- /dev/null +++ b/esphome/components/es8156/audio_dac.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +from esphome.components import i2c +from esphome.components.audio_dac import AudioDac +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CODEOWNERS = ["@kbx81"] +DEPENDENCIES = ["i2c"] + +es8156_ns = cg.esphome_ns.namespace("es8156") +ES8156 = es8156_ns.class_("ES8156", AudioDac, cg.Component, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ES8156), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x08)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/es8156/es8156.cpp b/esphome/components/es8156/es8156.cpp new file mode 100644 index 0000000000..62d35aa2d2 --- /dev/null +++ b/esphome/components/es8156/es8156.cpp @@ -0,0 +1,87 @@ +#include "es8156.h" +#include "es8156_const.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace es8156 { + +static const char *const TAG = "es8156"; + +// Mark the component as failed; use only in setup +#define ES8156_ERROR_FAILED(func) \ + if (!(func)) { \ + this->mark_failed(); \ + return; \ + } + +void ES8156::setup() { + ESP_LOGCONFIG(TAG, "Setting up ES8156..."); + + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG02_SCLK_MODE, 0x04)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG20_ANALOG_SYS1, 0x2A)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG21_ANALOG_SYS2, 0x3C)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG22_ANALOG_SYS3, 0x00)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG24_ANALOG_LP, 0x07)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG23_ANALOG_SYS4, 0x00)); + + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0A_TIME_CONTROL1, 0x01)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0B_TIME_CONTROL2, 0x01)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG11_DAC_SDP, 0x00)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG19_EQ_CONTROL1, 0x20)); + + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0D_P2S_CONTROL, 0x14)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG09_MISC_CONTROL2, 0x00)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG18_MISC_CONTROL3, 0x00)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG08_CLOCK_ON_OFF, 0x3F)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG00_RESET, 0x02)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG00_RESET, 0x03)); + ES8156_ERROR_FAILED(this->write_byte(ES8156_REG25_ANALOG_SYS5, 0x20)); +} + +void ES8156::dump_config() { + ESP_LOGCONFIG(TAG, "ES8156 Audio Codec:"); + + if (this->is_failed()) { + ESP_LOGCONFIG(TAG, " Failed to initialize"); + return; + } +} + +bool ES8156::set_volume(float volume) { + volume = clamp(volume, 0.0f, 1.0f); + uint8_t reg = remap(volume, 0.0f, 1.0f, 0, 255); + ESP_LOGV(TAG, "Setting ES8156_REG14_VOLUME_CONTROL to %u (volume: %f)", reg, volume); + return this->write_byte(ES8156_REG14_VOLUME_CONTROL, reg); +} + +float ES8156::volume() { + uint8_t reg; + this->read_byte(ES8156_REG14_VOLUME_CONTROL, ®); + return remap(reg, 0, 255, 0.0f, 1.0f); +} + +bool ES8156::set_mute_state_(bool mute_state) { + uint8_t reg13; + + this->is_muted_ = mute_state; + + if (!this->read_byte(ES8156_REG13_DAC_MUTE, ®13)) { + return false; + } + + ESP_LOGV(TAG, "Read ES8156_REG13_DAC_MUTE: %u", reg13); + + if (mute_state) { + reg13 |= BIT(1) | BIT(2); + } else { + reg13 &= ~(BIT(1) | BIT(2)); + } + + ESP_LOGV(TAG, "Setting ES8156_REG13_DAC_MUTE to %u (muted: %s)", reg13, YESNO(mute_state)); + return this->write_byte(ES8156_REG13_DAC_MUTE, reg13); +} + +} // namespace es8156 +} // namespace esphome diff --git a/esphome/components/es8156/es8156.h b/esphome/components/es8156/es8156.h new file mode 100644 index 0000000000..e973599a7a --- /dev/null +++ b/esphome/components/es8156/es8156.h @@ -0,0 +1,51 @@ +#pragma once + +#include "esphome/components/audio_dac/audio_dac.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace es8156 { + +class ES8156 : public audio_dac::AudioDac, public Component, public i2c::I2CDevice { + public: + ///////////////////////// + // Component overrides // + ///////////////////////// + + void setup() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void dump_config() override; + + //////////////////////// + // AudioDac overrides // + //////////////////////// + + /// @brief Writes the volume out to the DAC + /// @param volume floating point between 0.0 and 1.0 + /// @return True if successful and false otherwise + bool set_volume(float volume) override; + + /// @brief Gets the current volume out from the DAC + /// @return floating point between 0.0 and 1.0 + float volume() override; + + /// @brief Disables mute for audio out + /// @return True if successful and false otherwise + bool set_mute_off() override { return this->set_mute_state_(false); } + + /// @brief Enables mute for audio out + /// @return True if successful and false otherwise + bool set_mute_on() override { return this->set_mute_state_(true); } + + bool is_muted() override { return this->is_muted_; } + + protected: + /// @brief Mutes or unmutes the DAC audio out + /// @param mute_state True to mute, false to unmute + /// @return True if successful and false otherwise + bool set_mute_state_(bool mute_state); +}; + +} // namespace es8156 +} // namespace esphome diff --git a/esphome/components/es8156/es8156_const.h b/esphome/components/es8156/es8156_const.h new file mode 100644 index 0000000000..0bc8f89dd4 --- /dev/null +++ b/esphome/components/es8156/es8156_const.h @@ -0,0 +1,68 @@ +#pragma once + +#include "es8156.h" + +namespace esphome { +namespace es8156 { + +/* ES8156 register addresses */ +/* + * RESET Control + */ +static const uint8_t ES8156_REG00_RESET = 0x00; +/* + * Clock Managerment + */ +static const uint8_t ES8156_REG01_MAINCLOCK_CTL = 0x01; +static const uint8_t ES8156_REG02_SCLK_MODE = 0x02; +static const uint8_t ES8156_REG03_LRCLK_DIV_H = 0x03; +static const uint8_t ES8156_REG04_LRCLK_DIV_L = 0x04; +static const uint8_t ES8156_REG05_SCLK_DIV = 0x05; +static const uint8_t ES8156_REG06_NFS_CONFIG = 0x06; +static const uint8_t ES8156_REG07_MISC_CONTROL1 = 0x07; +static const uint8_t ES8156_REG08_CLOCK_ON_OFF = 0x08; +static const uint8_t ES8156_REG09_MISC_CONTROL2 = 0x09; +static const uint8_t ES8156_REG0A_TIME_CONTROL1 = 0x0a; +static const uint8_t ES8156_REG0B_TIME_CONTROL2 = 0x0b; +/* + * System Control + */ +static const uint8_t ES8156_REG0C_CHIP_STATUS = 0x0c; +static const uint8_t ES8156_REG0D_P2S_CONTROL = 0x0d; +static const uint8_t ES8156_REG10_DAC_OSR_COUNTER = 0x10; +/* + * SDP Control + */ +static const uint8_t ES8156_REG11_DAC_SDP = 0x11; +static const uint8_t ES8156_REG12_AUTOMUTE_SET = 0x12; +static const uint8_t ES8156_REG13_DAC_MUTE = 0x13; +static const uint8_t ES8156_REG14_VOLUME_CONTROL = 0x14; + +/* + * ALC Control + */ +static const uint8_t ES8156_REG15_ALC_CONFIG1 = 0x15; +static const uint8_t ES8156_REG16_ALC_CONFIG2 = 0x16; +static const uint8_t ES8156_REG17_ALC_CONFIG3 = 0x17; +static const uint8_t ES8156_REG18_MISC_CONTROL3 = 0x18; +static const uint8_t ES8156_REG19_EQ_CONTROL1 = 0x19; +static const uint8_t ES8156_REG1A_EQ_CONTROL2 = 0x1a; +/* + * Analog System Control + */ +static const uint8_t ES8156_REG20_ANALOG_SYS1 = 0x20; +static const uint8_t ES8156_REG21_ANALOG_SYS2 = 0x21; +static const uint8_t ES8156_REG22_ANALOG_SYS3 = 0x22; +static const uint8_t ES8156_REG23_ANALOG_SYS4 = 0x23; +static const uint8_t ES8156_REG24_ANALOG_LP = 0x24; +static const uint8_t ES8156_REG25_ANALOG_SYS5 = 0x25; +/* + * Chip Information + */ +static const uint8_t ES8156_REGFC_I2C_PAGESEL = 0xFC; +static const uint8_t ES8156_REGFD_CHIPID1 = 0xFD; +static const uint8_t ES8156_REGFE_CHIPID0 = 0xFE; +static const uint8_t ES8156_REGFF_CHIP_VERSION = 0xFF; + +} // namespace es8156 +} // namespace esphome diff --git a/tests/components/es8156/common.yaml b/tests/components/es8156/common.yaml new file mode 100644 index 0000000000..addaa0b70a --- /dev/null +++ b/tests/components/es8156/common.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - audio_dac.mute_off: + - audio_dac.mute_on: + - audio_dac.set_volume: + volume: 50% + +i2c: + - id: i2c_es8156 + scl: ${scl_pin} + sda: ${sda_pin} + +audio_dac: + - platform: es8156 diff --git a/tests/components/es8156/test.esp32-ard.yaml b/tests/components/es8156/test.esp32-ard.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/es8156/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/es8156/test.esp32-c3-ard.yaml b/tests/components/es8156/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es8156/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/es8156/test.esp32-c3-idf.yaml b/tests/components/es8156/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es8156/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/es8156/test.esp32-idf.yaml b/tests/components/es8156/test.esp32-idf.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/es8156/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/es8156/test.esp8266-ard.yaml b/tests/components/es8156/test.esp8266-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es8156/test.esp8266-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml From db644542eda3482a50f0d30c4ab36ea3c635ff6c Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:17:32 -0500 Subject: [PATCH 31/36] [esp32_touch] Fix deprecated warning (#8092) Co-authored-by: Jonathan Swoboda --- esphome/components/esp32_touch/esp32_touch.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index e43c3b844c..0ae414fa12 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -52,7 +52,12 @@ void ESP32TouchComponent::setup() { } #endif +#if ESP_IDF_VERSION_MAJOR >= 5 + touch_pad_set_measurement_clock_cycles(this->meas_cycle_); + touch_pad_set_measurement_interval(this->sleep_cycle_); +#else touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_); +#endif touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); for (auto *child : this->children_) { From b454f63b3604d766abb038fe3c0f79dc20ab5cad Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:32:47 +1300 Subject: [PATCH 32/36] [core] Remove old style platform configuration (#8118) --- esphome/core/config.py | 75 ++----------------- .../binary_sensor/test_binary_sensor.yaml | 3 +- tests/component_tests/button/test_button.yaml | 3 +- .../deep_sleep/test_deep_sleep1.yaml | 3 +- .../deep_sleep/test_deep_sleep2.yaml | 3 +- tests/component_tests/sensor/test_sensor.yaml | 3 +- .../text_sensor/test_text_sensor.yaml | 3 +- .../yaml_util/broken_includetest.yaml | 6 +- .../fixtures/yaml_util/includetest.yaml | 6 +- tests/unit_tests/test_yaml_util.py | 2 +- 10 files changed, 27 insertions(+), 80 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index eee8b73934..c6a3b1b294 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -1,21 +1,16 @@ import logging import multiprocessing import os -import re from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( - CONF_ARDUINO_VERSION, CONF_AREA, - CONF_BOARD, - CONF_BOARD_FLASH_MODE, CONF_BUILD_PATH, CONF_COMMENT, CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, - CONF_FRAMEWORK, CONF_FRIENDLY_NAME, CONF_INCLUDES, CONF_LIBRARIES, @@ -30,12 +25,9 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, CONF_PRIORITY, CONF_PROJECT, - CONF_SOURCE, CONF_TRIGGER_ID, - CONF_TYPE, CONF_VERSION, KEY_CORE, - PLATFORM_ESP8266, TARGET_PLATFORMS, __version__ as ESPHOME_VERSION, ) @@ -44,7 +36,6 @@ from esphome.helpers import copy_file_if_changed, get_str_env, walk_files _LOGGER = logging.getLogger(__name__) -BUILD_FLASH_MODES = ["qio", "qout", "dio", "dout"] StartupTrigger = cg.esphome_ns.class_( "StartupTrigger", cg.Component, automation.Trigger.template() ) @@ -58,8 +49,6 @@ ProjectUpdateTrigger = cg.esphome_ns.class_( "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) ) -VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[ab]\d+)?$") - VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} @@ -111,7 +100,6 @@ else: _compile_process_limit_default = cv.UNDEFINED -CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -175,14 +163,9 @@ PRELOAD_CONFIG_SCHEMA = cv.Schema( { cv.Required(CONF_NAME): cv.valid_name, cv.Optional(CONF_BUILD_PATH): cv.string, - # Compat options, these were moved to target-platform specific sections - # but we'll keep these around for a long time because every config would - # be impacted - cv.Optional(CONF_PLATFORM): cv.one_of(*TARGET_PLATFORMS, lower=True), - cv.Optional(CONF_BOARD): cv.string_strict, - cv.Optional(CONF_ESP8266_RESTORE_FROM_FLASH): cv.valid, - cv.Optional(CONF_BOARD_FLASH_MODE): cv.valid, - cv.Optional(CONF_ARDUINO_VERSION): cv.valid, + cv.Optional(CONF_PLATFORM): cv.invalid( + "Please remove the `platform` key from the [esphome] block and use the correct platform component. This style of configuration has now been removed." + ), cv.Optional(CONF_MIN_VERSION, default=ESPHOME_VERSION): cv.All( cv.version_number, cv.validate_esphome_version ), @@ -204,62 +187,20 @@ def preload_core_config(config, result): conf[CONF_BUILD_PATH] = os.path.join(build_path, CORE.name) CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH]) - has_oldstyle = CONF_PLATFORM in conf - newstyle_found = [key for key in TARGET_PLATFORMS if key in config] - oldstyle_opts = [ - CONF_ESP8266_RESTORE_FROM_FLASH, - CONF_BOARD_FLASH_MODE, - CONF_ARDUINO_VERSION, - CONF_BOARD, - ] + target_platforms = [key for key in TARGET_PLATFORMS if key in config] - if not has_oldstyle and not newstyle_found: + if not target_platforms: raise cv.Invalid( "Platform missing. You must include one of the available platform keys: " + ", ".join(TARGET_PLATFORMS), [CONF_ESPHOME], ) - if has_oldstyle and newstyle_found: + if len(target_platforms) > 1: raise cv.Invalid( - f"Please remove the `platform` key from the [esphome] block. You're already using the new style with the [{conf[CONF_PLATFORM]}] block", - [CONF_ESPHOME, CONF_PLATFORM], + f"Found multiple target platform blocks: {', '.join(target_platforms)}. Only one is allowed.", + [target_platforms[0]], ) - if len(newstyle_found) > 1: - raise cv.Invalid( - f"Found multiple target platform blocks: {', '.join(newstyle_found)}. Only one is allowed.", - [newstyle_found[0]], - ) - if newstyle_found: - # Convert to newstyle - for key in oldstyle_opts: - if key in conf: - raise cv.Invalid( - f"Please move {key} to the [{newstyle_found[0]}] block.", - [CONF_ESPHOME, key], - ) - if has_oldstyle: - plat = conf.pop(CONF_PLATFORM) - plat_conf = {} - if CONF_ESP8266_RESTORE_FROM_FLASH in conf: - plat_conf["restore_from_flash"] = conf.pop(CONF_ESP8266_RESTORE_FROM_FLASH) - if CONF_BOARD_FLASH_MODE in conf: - plat_conf[CONF_BOARD_FLASH_MODE] = conf.pop(CONF_BOARD_FLASH_MODE) - if CONF_ARDUINO_VERSION in conf: - plat_conf[CONF_FRAMEWORK] = {} - if plat != PLATFORM_ESP8266: - plat_conf[CONF_FRAMEWORK][CONF_TYPE] = "arduino" - - try: - if conf[CONF_ARDUINO_VERSION] not in ("recommended", "latest", "dev"): - cv.Version.parse(conf[CONF_ARDUINO_VERSION]) - plat_conf[CONF_FRAMEWORK][CONF_VERSION] = conf.pop(CONF_ARDUINO_VERSION) - except ValueError: - plat_conf[CONF_FRAMEWORK][CONF_SOURCE] = conf.pop(CONF_ARDUINO_VERSION) - if CONF_BOARD in conf: - plat_conf[CONF_BOARD] = conf.pop(CONF_BOARD) - # Insert generated target platform config to main config - config[plat] = plat_conf config[CONF_ESPHOME] = conf diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.yaml b/tests/component_tests/binary_sensor/test_binary_sensor.yaml index f98ce693f7..8842dda837 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.yaml +++ b/tests/component_tests/binary_sensor/test_binary_sensor.yaml @@ -1,7 +1,8 @@ --- esphome: name: test - platform: ESP8266 + +esp8266: board: d1_mini_lite binary_sensor: diff --git a/tests/component_tests/button/test_button.yaml b/tests/component_tests/button/test_button.yaml index 48e13f0353..916c85cb8b 100644 --- a/tests/component_tests/button/test_button.yaml +++ b/tests/component_tests/button/test_button.yaml @@ -1,7 +1,8 @@ --- esphome: name: test - platform: ESP8266 + +esp8266: board: d1_mini_lite wifi: diff --git a/tests/component_tests/deep_sleep/test_deep_sleep1.yaml b/tests/component_tests/deep_sleep/test_deep_sleep1.yaml index 96514a677f..03145290a9 100644 --- a/tests/component_tests/deep_sleep/test_deep_sleep1.yaml +++ b/tests/component_tests/deep_sleep/test_deep_sleep1.yaml @@ -1,7 +1,8 @@ --- esphome: name: test - platform: ESP32 + +esp32: board: nodemcu-32s deep_sleep: diff --git a/tests/component_tests/deep_sleep/test_deep_sleep2.yaml b/tests/component_tests/deep_sleep/test_deep_sleep2.yaml index 0e8e598402..88ebb31728 100644 --- a/tests/component_tests/deep_sleep/test_deep_sleep2.yaml +++ b/tests/component_tests/deep_sleep/test_deep_sleep2.yaml @@ -1,7 +1,8 @@ --- esphome: name: test - platform: ESP32 + +esp32: board: nodemcu-32s deep_sleep: diff --git a/tests/component_tests/sensor/test_sensor.yaml b/tests/component_tests/sensor/test_sensor.yaml index 8c0fd85b17..612b8e5e56 100644 --- a/tests/component_tests/sensor/test_sensor.yaml +++ b/tests/component_tests/sensor/test_sensor.yaml @@ -1,7 +1,8 @@ --- esphome: name: test - platform: ESP8266 + +esp8266: board: d1_mini_lite sensor: diff --git a/tests/component_tests/text_sensor/test_text_sensor.yaml b/tests/component_tests/text_sensor/test_text_sensor.yaml index b426cb102c..9cc75082ac 100644 --- a/tests/component_tests/text_sensor/test_text_sensor.yaml +++ b/tests/component_tests/text_sensor/test_text_sensor.yaml @@ -1,7 +1,8 @@ --- esphome: name: test - platform: ESP8266 + +esp8266: board: d1_mini_lite text_sensor: diff --git a/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml b/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml index aaca55b807..a49ae706a4 100644 --- a/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml +++ b/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml @@ -12,7 +12,7 @@ esphome: # not overwritten by vars in the !include above name: ${name} name_add_mac_suffix: true - platform: esp8266 - board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}} - libraries: !include {file: includes/list.yaml, vars: {var1: Wire}} + +esp8266: + board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}} diff --git a/tests/unit_tests/fixtures/yaml_util/includetest.yaml b/tests/unit_tests/fixtures/yaml_util/includetest.yaml index af0a4e2030..7b01da6a17 100644 --- a/tests/unit_tests/fixtures/yaml_util/includetest.yaml +++ b/tests/unit_tests/fixtures/yaml_util/includetest.yaml @@ -12,7 +12,7 @@ esphome: # not overwritten by vars in the !include above name: ${name} name_add_mac_suffix: true - platform: esp8266 - board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}} - libraries: !include {file: includes/list.yaml, vars: {var1: Wire}} + +esp8266: + board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}} diff --git a/tests/unit_tests/test_yaml_util.py b/tests/unit_tests/test_yaml_util.py index 9178726247..828b2bf14b 100644 --- a/tests/unit_tests/test_yaml_util.py +++ b/tests/unit_tests/test_yaml_util.py @@ -10,7 +10,7 @@ def test_include_with_vars(fixture_path): substitutions.do_substitution_pass(actual, None) assert actual["esphome"]["name"] == "original" assert actual["esphome"]["libraries"][0] == "Wire" - assert actual["esphome"]["board"] == "nodemcu" + assert actual["esp8266"]["board"] == "nodemcu" assert actual["wifi"]["ssid"] == "my_custom_ssid" From 78ce8f014a8d8cf7b5f7e88101c6c9baa8a2da7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 08:15:56 +1300 Subject: [PATCH 33/36] Bump actions/stale from 9.0.0 to 9.1.0 (#8120) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 95f275e5a4..b79939fc8e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,7 +17,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9.0.0 + - uses: actions/stale@v9.1.0 with: days-before-pr-stale: 90 days-before-pr-close: 7 @@ -37,7 +37,7 @@ jobs: close-issues: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9.0.0 + - uses: actions/stale@v9.1.0 with: days-before-pr-stale: -1 days-before-pr-close: -1 From 4843bbd38a313911e7cb7ae3d87fc5d8a00079b3 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 21 Jan 2025 17:56:51 -0600 Subject: [PATCH 34/36] [custom] Remove platforms (#8119) --- esphome/components/custom/__init__.py | 3 - .../custom/binary_sensor/__init__.py | 30 +--------- .../binary_sensor/custom_binary_sensor.cpp | 16 ----- .../binary_sensor/custom_binary_sensor.h | 26 -------- esphome/components/custom/climate/__init__.py | 29 +-------- .../custom/climate/custom_climate.h | 22 ------- esphome/components/custom/cover/__init__.py | 29 +-------- .../components/custom/cover/custom_cover.h | 21 ------- esphome/components/custom/light/__init__.py | 29 +-------- .../custom/light/custom_light_output.h | 24 -------- esphome/components/custom/output/__init__.py | 60 +------------------ .../components/custom/output/custom_output.h | 37 ------------ esphome/components/custom/sensor/__init__.py | 26 +------- .../custom/sensor/custom_sensor.cpp | 16 ----- .../components/custom/sensor/custom_sensor.h | 24 -------- esphome/components/custom/switch/__init__.py | 26 +------- .../custom/switch/custom_switch.cpp | 16 ----- .../components/custom/switch/custom_switch.h | 24 -------- .../components/custom/text_sensor/__init__.py | 31 +--------- .../custom/text_sensor/custom_text_sensor.cpp | 16 ----- .../custom/text_sensor/custom_text_sensor.h | 26 -------- .../components/custom_component/__init__.py | 30 +--------- .../custom_component/custom_component.h | 28 --------- 23 files changed, 19 insertions(+), 570 deletions(-) delete mode 100644 esphome/components/custom/binary_sensor/custom_binary_sensor.cpp delete mode 100644 esphome/components/custom/binary_sensor/custom_binary_sensor.h delete mode 100644 esphome/components/custom/climate/custom_climate.h delete mode 100644 esphome/components/custom/cover/custom_cover.h delete mode 100644 esphome/components/custom/light/custom_light_output.h delete mode 100644 esphome/components/custom/output/custom_output.h delete mode 100644 esphome/components/custom/sensor/custom_sensor.cpp delete mode 100644 esphome/components/custom/sensor/custom_sensor.h delete mode 100644 esphome/components/custom/switch/custom_switch.cpp delete mode 100644 esphome/components/custom/switch/custom_switch.h delete mode 100644 esphome/components/custom/text_sensor/custom_text_sensor.cpp delete mode 100644 esphome/components/custom/text_sensor/custom_text_sensor.h delete mode 100644 esphome/components/custom_component/custom_component.h diff --git a/esphome/components/custom/__init__.py b/esphome/components/custom/__init__.py index 74450300f3..e69de29bb2 100644 --- a/esphome/components/custom/__init__.py +++ b/esphome/components/custom/__init__.py @@ -1,3 +0,0 @@ -import esphome.codegen as cg - -custom_ns = cg.esphome_ns.namespace("custom") diff --git a/esphome/components/custom/binary_sensor/__init__.py b/esphome/components/custom/binary_sensor/__init__.py index 8d6d621b3a..ca9747ea3e 100644 --- a/esphome/components/custom/binary_sensor/__init__.py +++ b/esphome/components/custom/binary_sensor/__init__.py @@ -1,31 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import binary_sensor -from esphome.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA -from .. import custom_ns -CustomBinarySensorConstructor = custom_ns.class_("CustomBinarySensorConstructor") - -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_BINARY_SENSORS): cv.ensure_list( - binary_sensor.binary_sensor_schema() - ), - } +CONFIG_SCHEMA = cv.invalid( + 'The "custom" component has been removed. Consider conversion to an external component.\nhttps://esphome.io/guides/contributing#a-note-about-custom-components' ) - - -async def to_code(config): - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [], - return_type=cg.std_vector.template(binary_sensor.BinarySensorPtr), - ) - - rhs = CustomBinarySensorConstructor(template_) - custom = cg.variable(config[CONF_ID], rhs) - for i, conf in enumerate(config[CONF_BINARY_SENSORS]): - rhs = custom.Pget_binary_sensor(i) - await binary_sensor.register_binary_sensor(rhs, conf) diff --git a/esphome/components/custom/binary_sensor/custom_binary_sensor.cpp b/esphome/components/custom/binary_sensor/custom_binary_sensor.cpp deleted file mode 100644 index ea83198568..0000000000 --- a/esphome/components/custom/binary_sensor/custom_binary_sensor.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "custom_binary_sensor.h" -#include "esphome/core/log.h" - -namespace esphome { -namespace custom { - -static const char *const TAG = "custom.binary_sensor"; - -void CustomBinarySensorConstructor::dump_config() { - for (auto *child : this->binary_sensors_) { - LOG_BINARY_SENSOR("", "Custom Binary Sensor", child); - } -} - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/binary_sensor/custom_binary_sensor.h b/esphome/components/custom/binary_sensor/custom_binary_sensor.h deleted file mode 100644 index b7d5458d9e..0000000000 --- a/esphome/components/custom/binary_sensor/custom_binary_sensor.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/binary_sensor/binary_sensor.h" - -#include - -namespace esphome { -namespace custom { - -class CustomBinarySensorConstructor : public Component { - public: - CustomBinarySensorConstructor(const std::function()> &init) { - this->binary_sensors_ = init(); - } - - binary_sensor::BinarySensor *get_binary_sensor(int i) { return this->binary_sensors_[i]; } - - void dump_config() override; - - protected: - std::vector binary_sensors_; -}; - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/climate/__init__.py b/esphome/components/custom/climate/__init__.py index a95456133a..ca9747ea3e 100644 --- a/esphome/components/custom/climate/__init__.py +++ b/esphome/components/custom/climate/__init__.py @@ -1,30 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import climate -from esphome.const import CONF_ID, CONF_LAMBDA -from .. import custom_ns -CustomClimateConstructor = custom_ns.class_("CustomClimateConstructor") -CONF_CLIMATES = "climates" - -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CustomClimateConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_CLIMATES): cv.ensure_list(climate.CLIMATE_SCHEMA), - } +CONFIG_SCHEMA = cv.invalid( + 'The "custom" component has been removed. Consider conversion to an external component.\nhttps://esphome.io/guides/contributing#a-note-about-custom-components' ) - - -async def to_code(config): - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [], - return_type=cg.std_vector.template(climate.Climate.operator("ptr")), - ) - - rhs = CustomClimateConstructor(template_) - custom = cg.variable(config[CONF_ID], rhs) - for i, conf in enumerate(config[CONF_CLIMATES]): - rhs = custom.Pget_climate(i) - await climate.register_climate(rhs, conf) diff --git a/esphome/components/custom/climate/custom_climate.h b/esphome/components/custom/climate/custom_climate.h deleted file mode 100644 index 37876f7115..0000000000 --- a/esphome/components/custom/climate/custom_climate.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/climate/climate.h" - -#include - -namespace esphome { -namespace custom { - -class CustomClimateConstructor { - public: - CustomClimateConstructor(const std::function()> &init) { this->climates_ = init(); } - - climate::Climate *get_climate(int i) { return this->climates_[i]; } - - protected: - std::vector climates_; -}; - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/cover/__init__.py b/esphome/components/custom/cover/__init__.py index 37fd4cdbbc..ca9747ea3e 100644 --- a/esphome/components/custom/cover/__init__.py +++ b/esphome/components/custom/cover/__init__.py @@ -1,30 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import cover -from esphome.const import CONF_ID, CONF_LAMBDA -from .. import custom_ns -CustomCoverConstructor = custom_ns.class_("CustomCoverConstructor") -CONF_COVERS = "covers" - -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CustomCoverConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_COVERS): cv.ensure_list(cover.COVER_SCHEMA), - } +CONFIG_SCHEMA = cv.invalid( + 'The "custom" component has been removed. Consider conversion to an external component.\nhttps://esphome.io/guides/contributing#a-note-about-custom-components' ) - - -async def to_code(config): - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [], - return_type=cg.std_vector.template(cover.Cover.operator("ptr")), - ) - - rhs = CustomCoverConstructor(template_) - custom = cg.variable(config[CONF_ID], rhs) - for i, conf in enumerate(config[CONF_COVERS]): - rhs = custom.Pget_cover(i) - await cover.register_cover(rhs, conf) diff --git a/esphome/components/custom/cover/custom_cover.h b/esphome/components/custom/cover/custom_cover.h deleted file mode 100644 index 58330b9d54..0000000000 --- a/esphome/components/custom/cover/custom_cover.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "esphome/components/cover/cover.h" - -#include - -namespace esphome { -namespace custom { - -class CustomCoverConstructor { - public: - CustomCoverConstructor(const std::function()> &init) { this->covers_ = init(); } - - cover::Cover *get_cover(int i) { return this->covers_[i]; } - - protected: - std::vector covers_; -}; - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/light/__init__.py b/esphome/components/custom/light/__init__.py index b6ebe13ab2..ca9747ea3e 100644 --- a/esphome/components/custom/light/__init__.py +++ b/esphome/components/custom/light/__init__.py @@ -1,30 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import light -from esphome.const import CONF_ID, CONF_LAMBDA -from .. import custom_ns -CustomLightOutputConstructor = custom_ns.class_("CustomLightOutputConstructor") -CONF_LIGHTS = "lights" - -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CustomLightOutputConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_LIGHTS): cv.ensure_list(light.ADDRESSABLE_LIGHT_SCHEMA), - } +CONFIG_SCHEMA = cv.invalid( + 'The "custom" component has been removed. Consider conversion to an external component.\nhttps://esphome.io/guides/contributing#a-note-about-custom-components' ) - - -async def to_code(config): - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [], - return_type=cg.std_vector.template(light.LightOutput.operator("ptr")), - ) - - rhs = CustomLightOutputConstructor(template_) - custom = cg.variable(config[CONF_ID], rhs) - for i, conf in enumerate(config[CONF_LIGHTS]): - rhs = custom.Pget_light(i) - await light.register_light(rhs, conf) diff --git a/esphome/components/custom/light/custom_light_output.h b/esphome/components/custom/light/custom_light_output.h deleted file mode 100644 index c2c83ebe97..0000000000 --- a/esphome/components/custom/light/custom_light_output.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/light/light_output.h" - -#include - -namespace esphome { -namespace custom { - -class CustomLightOutputConstructor { - public: - CustomLightOutputConstructor(const std::function()> &init) { - this->outputs_ = init(); - } - - light::LightOutput *get_light(int i) { return this->outputs_[i]; } - - protected: - std::vector outputs_; -}; - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/output/__init__.py b/esphome/components/custom/output/__init__.py index 97ef070fc3..ca9747ea3e 100644 --- a/esphome/components/custom/output/__init__.py +++ b/esphome/components/custom/output/__init__.py @@ -1,61 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import output -from esphome.const import CONF_ID, CONF_LAMBDA, CONF_OUTPUTS, CONF_TYPE, CONF_BINARY -from .. import custom_ns -CustomBinaryOutputConstructor = custom_ns.class_("CustomBinaryOutputConstructor") -CustomFloatOutputConstructor = custom_ns.class_("CustomFloatOutputConstructor") - -CONF_FLOAT = "float" - -CONFIG_SCHEMA = cv.typed_schema( - { - CONF_BINARY: cv.Schema( - { - cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_OUTPUTS): cv.ensure_list( - output.BINARY_OUTPUT_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(output.BinaryOutput), - } - ) - ), - } - ), - CONF_FLOAT: cv.Schema( - { - cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_OUTPUTS): cv.ensure_list( - output.FLOAT_OUTPUT_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(output.FloatOutput), - } - ) - ), - } - ), - }, - lower=True, +CONFIG_SCHEMA = cv.invalid( + 'The "custom" component has been removed. Consider conversion to an external component.\nhttps://esphome.io/guides/contributing#a-note-about-custom-components' ) - - -async def to_code(config): - type = config[CONF_TYPE] - if type == "binary": - ret_type = output.BinaryOutputPtr - klass = CustomBinaryOutputConstructor - else: - ret_type = output.FloatOutputPtr - klass = CustomFloatOutputConstructor - template_ = await cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.std_vector.template(ret_type) - ) - - rhs = klass(template_) - custom = cg.variable(config[CONF_ID], rhs) - for i, conf in enumerate(config[CONF_OUTPUTS]): - out = cg.Pvariable(conf[CONF_ID], custom.get_output(i)) - await output.register_output(out, conf) diff --git a/esphome/components/custom/output/custom_output.h b/esphome/components/custom/output/custom_output.h deleted file mode 100644 index 4624642420..0000000000 --- a/esphome/components/custom/output/custom_output.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/output/binary_output.h" -#include "esphome/components/output/float_output.h" - -#include - -namespace esphome { -namespace custom { - -class CustomBinaryOutputConstructor { - public: - CustomBinaryOutputConstructor(const std::function()> &init) { - this->outputs_ = init(); - } - - output::BinaryOutput *get_output(int i) { return this->outputs_[i]; } - - protected: - std::vector outputs_; -}; - -class CustomFloatOutputConstructor { - public: - CustomFloatOutputConstructor(const std::function()> &init) { - this->outputs_ = init(); - } - - output::FloatOutput *get_output(int i) { return this->outputs_[i]; } - - protected: - std::vector outputs_; -}; - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/sensor/__init__.py b/esphome/components/custom/sensor/__init__.py index be17d9a334..ca9747ea3e 100644 --- a/esphome/components/custom/sensor/__init__.py +++ b/esphome/components/custom/sensor/__init__.py @@ -1,27 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import sensor -from esphome.const import CONF_ID, CONF_LAMBDA, CONF_SENSORS -from .. import custom_ns -CustomSensorConstructor = custom_ns.class_("CustomSensorConstructor") - -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CustomSensorConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_SENSORS): cv.ensure_list(sensor.sensor_schema()), - } +CONFIG_SCHEMA = cv.invalid( + 'The "custom" component has been removed. Consider conversion to an external component.\nhttps://esphome.io/guides/contributing#a-note-about-custom-components' ) - - -async def to_code(config): - template_ = await cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.std_vector.template(sensor.SensorPtr) - ) - - rhs = CustomSensorConstructor(template_) - var = cg.variable(config[CONF_ID], rhs) - for i, conf in enumerate(config[CONF_SENSORS]): - sens = cg.Pvariable(conf[CONF_ID], var.get_sensor(i)) - await sensor.register_sensor(sens, conf) diff --git a/esphome/components/custom/sensor/custom_sensor.cpp b/esphome/components/custom/sensor/custom_sensor.cpp deleted file mode 100644 index e670f09530..0000000000 --- a/esphome/components/custom/sensor/custom_sensor.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "custom_sensor.h" -#include "esphome/core/log.h" - -namespace esphome { -namespace custom { - -static const char *const TAG = "custom.sensor"; - -void CustomSensorConstructor::dump_config() { - for (auto *child : this->sensors_) { - LOG_SENSOR("", "Custom Sensor", child); - } -} - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/sensor/custom_sensor.h b/esphome/components/custom/sensor/custom_sensor.h deleted file mode 100644 index d8f4fbc109..0000000000 --- a/esphome/components/custom/sensor/custom_sensor.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" - -#include - -namespace esphome { -namespace custom { - -class CustomSensorConstructor : public Component { - public: - CustomSensorConstructor(const std::function()> &init) { this->sensors_ = init(); } - - sensor::Sensor *get_sensor(int i) { return this->sensors_[i]; } - - void dump_config() override; - - protected: - std::vector sensors_; -}; - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/switch/__init__.py b/esphome/components/custom/switch/__init__.py index 5538ae6aa0..ca9747ea3e 100644 --- a/esphome/components/custom/switch/__init__.py +++ b/esphome/components/custom/switch/__init__.py @@ -1,27 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import switch -from esphome.const import CONF_ID, CONF_LAMBDA, CONF_SWITCHES -from .. import custom_ns -CustomSwitchConstructor = custom_ns.class_("CustomSwitchConstructor") - -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CustomSwitchConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_SWITCHES): cv.ensure_list(switch.switch_schema(switch.Switch)), - } +CONFIG_SCHEMA = cv.invalid( + 'The "custom" component has been removed. Consider conversion to an external component.\nhttps://esphome.io/guides/contributing#a-note-about-custom-components' ) - - -async def to_code(config): - template_ = await cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.std_vector.template(switch.SwitchPtr) - ) - - rhs = CustomSwitchConstructor(template_) - var = cg.variable(config[CONF_ID], rhs) - for i, conf in enumerate(config[CONF_SWITCHES]): - switch_ = cg.Pvariable(conf[CONF_ID], var.get_switch(i)) - await switch.register_switch(switch_, conf) diff --git a/esphome/components/custom/switch/custom_switch.cpp b/esphome/components/custom/switch/custom_switch.cpp deleted file mode 100644 index 6d0a8fa621..0000000000 --- a/esphome/components/custom/switch/custom_switch.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "custom_switch.h" -#include "esphome/core/log.h" - -namespace esphome { -namespace custom { - -static const char *const TAG = "custom.switch"; - -void CustomSwitchConstructor::dump_config() { - for (auto *child : this->switches_) { - LOG_SWITCH("", "Custom Switch", child); - } -} - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/switch/custom_switch.h b/esphome/components/custom/switch/custom_switch.h deleted file mode 100644 index 9657e4b44d..0000000000 --- a/esphome/components/custom/switch/custom_switch.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/switch/switch.h" - -#include - -namespace esphome { -namespace custom { - -class CustomSwitchConstructor : public Component { - public: - CustomSwitchConstructor(const std::function()> &init) { this->switches_ = init(); } - - switch_::Switch *get_switch(int i) { return this->switches_[i]; } - - void dump_config() override; - - protected: - std::vector switches_; -}; - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/text_sensor/__init__.py b/esphome/components/custom/text_sensor/__init__.py index 70728af604..ca9747ea3e 100644 --- a/esphome/components/custom/text_sensor/__init__.py +++ b/esphome/components/custom/text_sensor/__init__.py @@ -1,32 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import text_sensor -from esphome.const import CONF_ID, CONF_LAMBDA, CONF_TEXT_SENSORS -from .. import custom_ns -CustomTextSensorConstructor = custom_ns.class_("CustomTextSensorConstructor") - -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_TEXT_SENSORS): cv.ensure_list( - text_sensor.text_sensor_schema() - ), - } +CONFIG_SCHEMA = cv.invalid( + 'The "custom" component has been removed. Consider conversion to an external component.\nhttps://esphome.io/guides/contributing#a-note-about-custom-components' ) - - -async def to_code(config): - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [], - return_type=cg.std_vector.template(text_sensor.TextSensorPtr), - ) - - rhs = CustomTextSensorConstructor(template_) - var = cg.variable(config[CONF_ID], rhs) - - for i, conf in enumerate(config[CONF_TEXT_SENSORS]): - text = cg.Pvariable(conf[CONF_ID], var.get_text_sensor(i)) - await text_sensor.register_text_sensor(text, conf) diff --git a/esphome/components/custom/text_sensor/custom_text_sensor.cpp b/esphome/components/custom/text_sensor/custom_text_sensor.cpp deleted file mode 100644 index 618ba832a5..0000000000 --- a/esphome/components/custom/text_sensor/custom_text_sensor.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "custom_text_sensor.h" -#include "esphome/core/log.h" - -namespace esphome { -namespace custom { - -static const char *const TAG = "custom.text_sensor"; - -void CustomTextSensorConstructor::dump_config() { - for (auto *child : this->text_sensors_) { - LOG_TEXT_SENSOR("", "Custom Text Sensor", child); - } -} - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom/text_sensor/custom_text_sensor.h b/esphome/components/custom/text_sensor/custom_text_sensor.h deleted file mode 100644 index 13732c00b6..0000000000 --- a/esphome/components/custom/text_sensor/custom_text_sensor.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/text_sensor/text_sensor.h" - -#include - -namespace esphome { -namespace custom { - -class CustomTextSensorConstructor : public Component { - public: - CustomTextSensorConstructor(const std::function()> &init) { - this->text_sensors_ = init(); - } - - text_sensor::TextSensor *get_text_sensor(int i) { return this->text_sensors_[i]; } - - void dump_config() override; - - protected: - std::vector text_sensors_; -}; - -} // namespace custom -} // namespace esphome diff --git a/esphome/components/custom_component/__init__.py b/esphome/components/custom_component/__init__.py index d41dd7ea59..982153414d 100644 --- a/esphome/components/custom_component/__init__.py +++ b/esphome/components/custom_component/__init__.py @@ -1,31 +1,7 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_COMPONENTS, CONF_ID, CONF_LAMBDA - -custom_component_ns = cg.esphome_ns.namespace("custom_component") -CustomComponentConstructor = custom_component_ns.class_("CustomComponentConstructor") MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CustomComponentConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_COMPONENTS): cv.ensure_list( - cv.Schema({cv.GenerateID(): cv.declare_id(cg.Component)}).extend( - cv.COMPONENT_SCHEMA - ) - ), - } + +CONFIG_SCHEMA = cv.invalid( + 'The "custom" component has been removed. Consider conversion to an external component.\nhttps://esphome.io/guides/contributing#a-note-about-custom-components' ) - - -async def to_code(config): - template_ = await cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.std_vector.template(cg.ComponentPtr) - ) - - rhs = CustomComponentConstructor(template_) - var = cg.variable(config[CONF_ID], rhs) - for i, conf in enumerate(config.get(CONF_COMPONENTS, [])): - comp = cg.Pvariable(conf[CONF_ID], var.get_component(i)) - await cg.register_component(comp, conf) diff --git a/esphome/components/custom_component/custom_component.h b/esphome/components/custom_component/custom_component.h deleted file mode 100644 index 3b34019a7a..0000000000 --- a/esphome/components/custom_component/custom_component.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/core/application.h" - -#include - -namespace esphome { -namespace custom_component { - -class CustomComponentConstructor { - public: - CustomComponentConstructor(const std::function()> &init) { - this->components_ = init(); - - for (auto *comp : this->components_) { - App.register_component(comp); - } - } - - Component *get_component(int i) const { return this->components_[i]; } - - protected: - std::vector components_; -}; - -} // namespace custom_component -} // namespace esphome From c2e52f4b1177224a80e5585bbdc3151a61ec5a6d Mon Sep 17 00:00:00 2001 From: Citric Li <37475446+limengdu@users.noreply.github.com> Date: Wed, 22 Jan 2025 08:01:15 +0800 Subject: [PATCH 35/36] Add: Human Presence and Target Count to the Seeed Studio MR60BHA2 (#8010) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Spencer Yan --- .../seeed_mr60bha2/binary_sensor.py | 25 ++++++++++++++++ .../seeed_mr60bha2/seeed_mr60bha2.cpp | 28 ++++++++++++++++-- .../seeed_mr60bha2/seeed_mr60bha2.h | 29 ++++++------------- esphome/components/seeed_mr60bha2/sensor.py | 10 ++++++- tests/components/seeed_mr60bha2/common.yaml | 7 +++++ 5 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 esphome/components/seeed_mr60bha2/binary_sensor.py diff --git a/esphome/components/seeed_mr60bha2/binary_sensor.py b/esphome/components/seeed_mr60bha2/binary_sensor.py new file mode 100644 index 0000000000..ae9e1c23e6 --- /dev/null +++ b/esphome/components/seeed_mr60bha2/binary_sensor.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_OCCUPANCY, + CONF_HAS_TARGET, +) +from . import CONF_MR60BHA2_ID, MR60BHA2Component + +DEPENDENCIES = ["seeed_mr60bha2"] + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR60BHA2_ID): cv.use_id(MR60BHA2Component), + cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, icon="mdi:motion-sensor" + ), +} + + +async def to_code(config): + mr60bha2_component = await cg.get_variable(config[CONF_MR60BHA2_ID]) + + if has_target_config := config.get(CONF_HAS_TARGET): + sens = await binary_sensor.new_binary_sensor(has_target_config) + cg.add(mr60bha2_component.set_has_target_binary_sensor(sens)) diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp index 50d709c3b0..75f3f092a6 100644 --- a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp @@ -1,6 +1,7 @@ #include "seeed_mr60bha2.h" #include "esphome/core/log.h" +#include #include namespace esphome { @@ -12,10 +13,14 @@ static const char *const TAG = "seeed_mr60bha2"; // items in an easy-to-read format, including the configuration key-value pairs. void MR60BHA2Component::dump_config() { ESP_LOGCONFIG(TAG, "MR60BHA2:"); +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "People Exist Binary Sensor", this->has_target_binary_sensor_); +#endif #ifdef USE_SENSOR LOG_SENSOR(" ", "Breath Rate Sensor", this->breath_rate_sensor_); LOG_SENSOR(" ", "Heart Rate Sensor", this->heart_rate_sensor_); LOG_SENSOR(" ", "Distance Sensor", this->distance_sensor_); + LOG_SENSOR(" ", "Target Number Sensor", this->num_targets_sensor_); #endif } @@ -94,7 +99,8 @@ bool MR60BHA2Component::validate_message_() { uint16_t frame_type = encode_uint16(data[5], data[6]); if (frame_type != BREATH_RATE_TYPE_BUFFER && frame_type != HEART_RATE_TYPE_BUFFER && - frame_type != DISTANCE_TYPE_BUFFER) { + frame_type != DISTANCE_TYPE_BUFFER && frame_type != PEOPLE_EXIST_TYPE_BUFFER && + frame_type != PRINT_CLOUD_BUFFER) { return false; } @@ -144,6 +150,18 @@ void MR60BHA2Component::process_frame_(uint16_t frame_id, uint16_t frame_type, c } } break; + case PEOPLE_EXIST_TYPE_BUFFER: + if (this->has_target_binary_sensor_ != nullptr && length >= 2) { + uint16_t has_target_int = encode_uint16(data[1], data[0]); + this->has_target_binary_sensor_->publish_state(has_target_int); + if (has_target_int == 0) { + this->breath_rate_sensor_->publish_state(0.0); + this->heart_rate_sensor_->publish_state(0.0); + this->distance_sensor_->publish_state(0.0); + this->num_targets_sensor_->publish_state(0); + } + } + break; case HEART_RATE_TYPE_BUFFER: if (this->heart_rate_sensor_ != nullptr && length >= 4) { uint32_t current_heart_rate_int = encode_uint32(data[3], data[2], data[1], data[0]); @@ -155,7 +173,7 @@ void MR60BHA2Component::process_frame_(uint16_t frame_id, uint16_t frame_type, c } break; case DISTANCE_TYPE_BUFFER: - if (!data[0]) { + if (data[0] != 0) { if (this->distance_sensor_ != nullptr && length >= 8) { uint32_t current_distance_int = encode_uint32(data[7], data[6], data[5], data[4]); float distance_float; @@ -164,6 +182,12 @@ void MR60BHA2Component::process_frame_(uint16_t frame_id, uint16_t frame_type, c } } break; + case PRINT_CLOUD_BUFFER: + if (this->num_targets_sensor_ != nullptr && length >= 4) { + uint32_t current_num_targets_int = encode_uint32(data[3], data[2], data[1], data[0]); + this->num_targets_sensor_->publish_state(current_num_targets_int); + } + break; default: break; } diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.h b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.h index 0a4f21f1ad..d20c8e50cc 100644 --- a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.h +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.h @@ -1,6 +1,9 @@ #pragma once #include "esphome/core/component.h" #include "esphome/core/defines.h" +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif #ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" #endif @@ -12,37 +15,23 @@ namespace esphome { namespace seeed_mr60bha2 { - -static const uint8_t DATA_BUF_MAX_SIZE = 12; -static const uint8_t FRAME_BUF_MAX_SIZE = 21; -static const uint8_t LEN_TO_HEAD_CKSUM = 8; -static const uint8_t LEN_TO_DATA_FRAME = 9; - static const uint8_t FRAME_HEADER_BUFFER = 0x01; static const uint16_t BREATH_RATE_TYPE_BUFFER = 0x0A14; +static const uint16_t PEOPLE_EXIST_TYPE_BUFFER = 0x0F09; static const uint16_t HEART_RATE_TYPE_BUFFER = 0x0A15; static const uint16_t DISTANCE_TYPE_BUFFER = 0x0A16; - -enum FrameLocation { - LOCATE_FRAME_HEADER, - LOCATE_ID_FRAME1, - LOCATE_ID_FRAME2, - LOCATE_LENGTH_FRAME_H, - LOCATE_LENGTH_FRAME_L, - LOCATE_TYPE_FRAME1, - LOCATE_TYPE_FRAME2, - LOCATE_HEAD_CKSUM_FRAME, // Header checksum: [from the first byte to the previous byte of the HEAD_CKSUM bit] - LOCATE_DATA_FRAME, - LOCATE_DATA_CKSUM_FRAME, // Data checksum: [from the first to the previous byte of the DATA_CKSUM bit] - LOCATE_PROCESS_FRAME, -}; +static const uint16_t PRINT_CLOUD_BUFFER = 0x0A04; class MR60BHA2Component : public Component, public uart::UARTDevice { // The class name must be the name defined by text_sensor.py +#ifdef USE_BINARY_SENSOR + SUB_BINARY_SENSOR(has_target); +#endif #ifdef USE_SENSOR SUB_SENSOR(breath_rate); SUB_SENSOR(heart_rate); SUB_SENSOR(distance); + SUB_SENSOR(num_targets); #endif public: diff --git a/esphome/components/seeed_mr60bha2/sensor.py b/esphome/components/seeed_mr60bha2/sensor.py index 5f30b363bf..916d4b4ba2 100644 --- a/esphome/components/seeed_mr60bha2/sensor.py +++ b/esphome/components/seeed_mr60bha2/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( ICON_HEART_PULSE, ICON_PULSE, ICON_SIGNAL, + ICON_COUNTER, STATE_CLASS_MEASUREMENT, UNIT_BEATS_PER_MINUTE, UNIT_CENTIMETER, @@ -18,12 +19,13 @@ DEPENDENCIES = ["seeed_mr60bha2"] CONF_BREATH_RATE = "breath_rate" CONF_HEART_RATE = "heart_rate" +CONF_NUM_TARGETS = "num_targets" CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_MR60BHA2_ID): cv.use_id(MR60BHA2Component), cv.Optional(CONF_BREATH_RATE): sensor.sensor_schema( - accuracy_decimals=2, + accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, icon=ICON_PULSE, ), @@ -40,6 +42,9 @@ CONFIG_SCHEMA = cv.Schema( accuracy_decimals=2, icon=ICON_SIGNAL, ), + cv.Optional(CONF_NUM_TARGETS): sensor.sensor_schema( + icon=ICON_COUNTER, + ), } ) @@ -55,3 +60,6 @@ async def to_code(config): if distance_config := config.get(CONF_DISTANCE): sens = await sensor.new_sensor(distance_config) cg.add(mr60bha2_component.set_distance_sensor(sens)) + if num_targets_config := config.get(CONF_NUM_TARGETS): + sens = await sensor.new_sensor(num_targets_config) + cg.add(mr60bha2_component.set_num_targets_sensor(sens)) diff --git a/tests/components/seeed_mr60bha2/common.yaml b/tests/components/seeed_mr60bha2/common.yaml index e9d0c735af..9eb0c8d527 100644 --- a/tests/components/seeed_mr60bha2/common.yaml +++ b/tests/components/seeed_mr60bha2/common.yaml @@ -9,6 +9,11 @@ uart: seeed_mr60bha2: id: my_seeed_mr60bha2 +binary_sensor: + - platform: seeed_mr60bha2 + has_target: + name: "Person Information" + sensor: - platform: seeed_mr60bha2 breath_rate: @@ -17,3 +22,5 @@ sensor: name: "Real-time heart rate" distance: name: "Distance to detection object" + num_targets: + name: "Target Number" From f2170c633a3ffe54960f84803e838f31944428b7 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 22 Jan 2025 14:23:22 -0600 Subject: [PATCH 36/36] [es7243e] Add support for ES7243E audio ADC (#8098) --- CODEOWNERS | 1 + esphome/components/es7243e/__init__.py | 0 esphome/components/es7243e/audio_adc.py | 34 +++++ esphome/components/es7243e/es7243e.cpp | 125 ++++++++++++++++++ esphome/components/es7243e/es7243e.h | 37 ++++++ esphome/components/es7243e/es7243e_const.h | 54 ++++++++ tests/components/es7243e/common.yaml | 14 ++ tests/components/es7243e/test.esp32-ard.yaml | 5 + .../components/es7243e/test.esp32-c3-ard.yaml | 5 + .../components/es7243e/test.esp32-c3-idf.yaml | 5 + tests/components/es7243e/test.esp32-idf.yaml | 5 + 11 files changed, 285 insertions(+) create mode 100644 esphome/components/es7243e/__init__.py create mode 100644 esphome/components/es7243e/audio_adc.py create mode 100644 esphome/components/es7243e/es7243e.cpp create mode 100644 esphome/components/es7243e/es7243e.h create mode 100644 esphome/components/es7243e/es7243e_const.h create mode 100644 tests/components/es7243e/common.yaml create mode 100644 tests/components/es7243e/test.esp32-ard.yaml create mode 100644 tests/components/es7243e/test.esp32-c3-ard.yaml create mode 100644 tests/components/es7243e/test.esp32-c3-idf.yaml create mode 100644 tests/components/es7243e/test.esp32-idf.yaml diff --git a/CODEOWNERS b/CODEOWNERS index e2c674cfd3..aa24b6cb82 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -133,6 +133,7 @@ esphome/components/ens160_i2c/* @latonita esphome/components/ens160_spi/* @latonita esphome/components/ens210/* @itn3rd77 esphome/components/es7210/* @kahrendt +esphome/components/es7243e/* @kbx81 esphome/components/es8156/* @kbx81 esphome/components/es8311/* @kahrendt @kroimon esphome/components/esp32/* @esphome/core diff --git a/esphome/components/es7243e/__init__.py b/esphome/components/es7243e/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/es7243e/audio_adc.py b/esphome/components/es7243e/audio_adc.py new file mode 100644 index 0000000000..c305d60172 --- /dev/null +++ b/esphome/components/es7243e/audio_adc.py @@ -0,0 +1,34 @@ +import esphome.codegen as cg +from esphome.components import i2c +from esphome.components.audio_adc import AudioAdc +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_MIC_GAIN + +CODEOWNERS = ["@kbx81"] +DEPENDENCIES = ["i2c"] + +es7243e_ns = cg.esphome_ns.namespace("es7243e") +ES7243E = es7243e_ns.class_("ES7243E", AudioAdc, cg.Component, i2c.I2CDevice) + +ES7243E_MIC_GAINS = [0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 34.5, 36, 37.5] + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ES7243E), + cv.Optional(CONF_MIC_GAIN, default="24db"): cv.All( + cv.decibel, cv.one_of(*ES7243E_MIC_GAINS) + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x10)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_mic_gain(config[CONF_MIC_GAIN])) diff --git a/esphome/components/es7243e/es7243e.cpp b/esphome/components/es7243e/es7243e.cpp new file mode 100644 index 0000000000..ce65ce973e --- /dev/null +++ b/esphome/components/es7243e/es7243e.cpp @@ -0,0 +1,125 @@ +#include "es7243e.h" +#include "es7243e_const.h" + +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace es7243e { + +static const char *const TAG = "es7243e"; + +// Mark the component as failed; use only in setup +#define ES7243E_ERROR_FAILED(func) \ + if (!(func)) { \ + this->mark_failed(); \ + return; \ + } + +// Return false; use outside of setup +#define ES7243E_ERROR_CHECK(func) \ + if (!(func)) { \ + return false; \ + } + +void ES7243E::dump_config() { + ESP_LOGCONFIG(TAG, "ES7243E audio ADC:"); + + if (this->is_failed()) { + ESP_LOGE(TAG, " Failed to initialize"); + return; + } +} + +void ES7243E::setup() { + ESP_LOGCONFIG(TAG, "Setting up ES7243E..."); + + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG01, 0x3A)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_RESET_REG00, 0x80)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_TEST_MODE_REGF9, 0x00)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG04, 0x02)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG04, 0x01)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_TEST_MODE_REGF9, 0x01)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_RESET_REG00, 0x1E)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG01, 0x00)); + + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG02, 0x00)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG03, 0x20)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG04, 0x01)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ADC_CTRL_REG0D, 0x00)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG05, 0x00)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG06, 0x03)); // SCLK=MCLK/4 + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG07, 0x00)); // LRCK=MCLK/256 + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG08, 0xFF)); // LRCK=MCLK/256 + + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG09, 0xCA)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_SDP_REG0A, 0x85)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_SDP_REG0B, 0x00)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ADC_CTRL_REG0E, 0xBF)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ADC_CTRL_REG0F, 0x80)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ADC_CTRL_REG14, 0x0C)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ADC_CTRL_REG15, 0x0C)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG17, 0x02)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG18, 0x26)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG19, 0x77)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG1A, 0xF4)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG1B, 0x66)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG1C, 0x44)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG1E, 0x00)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG1F, 0x0C)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG20, 0x1A)); // PGA gain +30dB + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG21, 0x1A)); + + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_RESET_REG00, 0x80)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG01, 0x3A)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG16, 0x3F)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG16, 0x00)); + + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_TEST_MODE_REGF9, 0x00)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG04, 0x01)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG17, 0x01)); + ES7243E_ERROR_FAILED(this->configure_mic_gain_()); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_RESET_REG00, 0x80)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG01, 0x3A)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG16, 0x3F)); + ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG16, 0x00)); + + this->setup_complete_ = true; +} + +bool ES7243E::set_mic_gain(float mic_gain) { + this->mic_gain_ = clamp(mic_gain, 0, 37.5); + if (this->setup_complete_) { + return this->configure_mic_gain_(); + } + return true; +} + +bool ES7243E::configure_mic_gain_() { + auto regv = this->es7243e_gain_reg_value_(this->mic_gain_); + + ES7243E_ERROR_CHECK(this->write_byte(ES7243E_ANALOG_REG20, 0x10 | regv)); + ES7243E_ERROR_CHECK(this->write_byte(ES7243E_ANALOG_REG21, 0x10 | regv)); + + return true; +} + +uint8_t ES7243E::es7243e_gain_reg_value_(float mic_gain) { + // reg: 12 - 34.5dB, 13 - 36dB, 14 - 37.5dB + mic_gain += 0.5; + if (mic_gain <= 33.0) { + return (uint8_t) mic_gain / 3; + } + if (mic_gain < 36.0) { + return 12; + } + if (mic_gain < 37.0) { + return 13; + } + return 14; +} + +} // namespace es7243e +} // namespace esphome diff --git a/esphome/components/es7243e/es7243e.h b/esphome/components/es7243e/es7243e.h new file mode 100644 index 0000000000..41a8acac8d --- /dev/null +++ b/esphome/components/es7243e/es7243e.h @@ -0,0 +1,37 @@ +#pragma once + +#include "esphome/components/audio_adc/audio_adc.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace es7243e { + +class ES7243E : public audio_adc::AudioAdc, public Component, public i2c::I2CDevice { + /* Class for configuring an ES7243E ADC for microphone input. + * Based on code from: + * - https://github.com/espressif/esp-adf/ (accessed 20250116) + */ + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void dump_config() override; + + bool set_mic_gain(float mic_gain) override; + + float mic_gain() override { return this->mic_gain_; }; + + protected: + /// @brief Convert floating point mic gain value to register value + /// @param mic_gain Gain value to convert + /// @return Corresponding register value for specified gain + uint8_t es7243e_gain_reg_value_(float mic_gain); + + bool configure_mic_gain_(); + + bool setup_complete_{false}; + float mic_gain_{0}; +}; + +} // namespace es7243e +} // namespace esphome diff --git a/esphome/components/es7243e/es7243e_const.h b/esphome/components/es7243e/es7243e_const.h new file mode 100644 index 0000000000..daae53a108 --- /dev/null +++ b/esphome/components/es7243e/es7243e_const.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +namespace esphome { +namespace es7243e { + +// ES7243E register addresses +static const uint8_t ES7243E_RESET_REG00 = 0x00; // Reset control +static const uint8_t ES7243E_CLOCK_MGR_REG01 = 0x01; // MCLK/BCLK/ADCCLK/Analog clocks on/off +static const uint8_t ES7243E_CLOCK_MGR_REG02 = 0x02; // MCLK & BCLK configuration, source selection + +static const uint8_t ES7243E_CLOCK_MGR_REG03 = 0x03; // ADC Over-sample rate control +static const uint8_t ES7243E_CLOCK_MGR_REG04 = 0x04; // Pre-divide/Pre-multiplication +static const uint8_t ES7243E_CLOCK_MGR_REG05 = 0x05; // CF/DSP clock divider +static const uint8_t ES7243E_CLOCK_MGR_REG06 = 0x06; // BCLK divider at master mode +static const uint8_t ES7243E_CLOCK_MGR_REG07 = 0x07; // BCLK/LRCK/SDOUT tri-state control/LRCK divider bit 11->8 +static const uint8_t ES7243E_CLOCK_MGR_REG08 = 0x08; // Master LRCK divider bit 7 to bit 0 +static const uint8_t ES7243E_CLOCK_MGR_REG09 = 0x09; // SEL S1/Timer for S1 +static const uint8_t ES7243E_SDP_REG0A = 0x0A; // SEL S3/Timer for S3 +static const uint8_t ES7243E_SDP_REG0B = 0x0B; // SDP out mute control/I2S/left-justify case/word length/format +static const uint8_t ES7243E_SDP_REG0C = 0x0C; // NFS flag at slot0/LSB/TDM mode selection +static const uint8_t ES7243E_ADC_CTRL_REG0D = 0x0D; // data mux/pol. inv./ram clear on lrck/mclk active/gain scale up +static const uint8_t ES7243E_ADC_CTRL_REG0E = 0x0E; // volume control +static const uint8_t ES7243E_ADC_CTRL_REG0F = 0x0F; // offset freeze/auto level control/automute control/VC ramp rate +static const uint8_t ES7243E_ADC_CTRL_REG10 = 0x10; // automute noise gate/detection +static const uint8_t ES7243E_ADC_CTRL_REG11 = 0x11; // automute SDP control/out gain select +static const uint8_t ES7243E_ADC_CTRL_REG12 = 0x12; // controls for automute PDN_PGA/MOD/reset/digital circuit +static const uint8_t ES7243E_ADC_CTRL_REG13 = 0x13; // ALC rate selection/ALC target level +static const uint8_t ES7243E_ADC_CTRL_REG14 = 0x14; // ADCHPF stage1 coeff +static const uint8_t ES7243E_ADC_CTRL_REG15 = 0x15; // ADCHPF stage2 coeff +static const uint8_t ES7243E_ANALOG_REG16 = 0x16; // power-down/reset +static const uint8_t ES7243E_ANALOG_REG17 = 0x17; // VMIDSEL +static const uint8_t ES7243E_ANALOG_REG18 = 0x18; // ADC/ADCFL bias +static const uint8_t ES7243E_ANALOG_REG19 = 0x19; // PGA1/PGA2 bias +static const uint8_t ES7243E_ANALOG_REG1A = 0x1A; // ADCI1/ADCI23 bias +static const uint8_t ES7243E_ANALOG_REG1B = 0x1B; // ADCSM/ADCCM bias +static const uint8_t ES7243E_ANALOG_REG1C = 0x1C; // ADCVRP/ADCCPP bias +static const uint8_t ES7243E_ANALOG_REG1D = 0x1D; // low power bits +static const uint8_t ES7243E_ANALOG_REG1E = 0x1E; // low power bits +static const uint8_t ES7243E_ANALOG_REG1F = 0x1F; // ADC_DMIC_ON/REFSEL/VX2OFF/VX1SEL/VMIDLVL +static const uint8_t ES7243E_ANALOG_REG20 = 0x20; // select MIC1 as PGA1 input/PGA1 gain +static const uint8_t ES7243E_ANALOG_REG21 = 0x21; // select MIC2 as PGA1 input/PGA2 gain +static const uint8_t ES7243E_TEST_MODE_REGF7 = 0xF7; +static const uint8_t ES7243E_TEST_MODE_REGF8 = 0xF8; +static const uint8_t ES7243E_TEST_MODE_REGF9 = 0xF9; +static const uint8_t ES7243E_I2C_CONF_REGFA = 0xFA; // I2C signals retime/reset registers to default +static const uint8_t ES7243E_FLAG_REGFC = 0xFC; // CSM flag/ADC automute flag (RO) +static const uint8_t ES7243E_CHIP_ID1_REGFD = 0xFD; // chip ID 1, reads 0x7A (RO) +static const uint8_t ES7243E_CHIP_ID2_REGFE = 0xFE; // chip ID 2, reads 0x43 (RO) +static const uint8_t ES7243E_CHIP_VERSION_REGFF = 0xFF; // chip version, reads 0x00 (RO) + +} // namespace es7243e +} // namespace esphome diff --git a/tests/components/es7243e/common.yaml b/tests/components/es7243e/common.yaml new file mode 100644 index 0000000000..3de76909e9 --- /dev/null +++ b/tests/components/es7243e/common.yaml @@ -0,0 +1,14 @@ +esphome: + on_boot: + then: + - audio_adc.set_mic_gain: 0db + - audio_adc.set_mic_gain: !lambda 'return 4;' + +i2c: + - id: i2c_es7243e + scl: ${scl_pin} + sda: ${sda_pin} + +audio_adc: + - platform: es7243e + id: es7243e_adc diff --git a/tests/components/es7243e/test.esp32-ard.yaml b/tests/components/es7243e/test.esp32-ard.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/es7243e/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/es7243e/test.esp32-c3-ard.yaml b/tests/components/es7243e/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es7243e/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/es7243e/test.esp32-c3-idf.yaml b/tests/components/es7243e/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es7243e/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/es7243e/test.esp32-idf.yaml b/tests/components/es7243e/test.esp32-idf.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/es7243e/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml