From 43a020641b89b86b7082f956e3c335de38bd97b0 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sun, 20 Oct 2024 21:16:08 +0200 Subject: [PATCH 001/282] Fix broken ibeacon_uuid config in ble_rssi (#7640) --- esphome/components/ble_rssi/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index e3ba1abfd7..c4e767aa21 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -45,7 +45,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, - cv.Optional(CONF_IBEACON_UUID): cv.uuid, + cv.Optional(CONF_IBEACON_UUID): esp32_ble_tracker.bt_uuid, } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -79,7 +79,7 @@ async def to_code(config): cg.add(var.set_service_uuid128(uuid128)) if ibeacon_uuid := config.get(CONF_IBEACON_UUID): - ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid)) + ibeacon_uuid = esp32_ble_tracker.as_reversed_hex_array(ibeacon_uuid) cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None: From f7543a7b8dd1b5f8984c4dc84fc087df3f392784 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:28:52 +1300 Subject: [PATCH 002/282] Update Pull request template (#7620) --- .github/PULL_REQUEST_TEMPLATE.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3bf9c4e1f6..5703d39be1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,11 +7,16 @@ - [ ] Bugfix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Code quality improvements to existing code or addition of tests - [ ] Other -**Related issue or feature (if applicable):** fixes <link to issue> +**Related issue or feature (if applicable):** -**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here> +- fixes <link to issue> + +**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** + +- esphome/esphome-docs#<esphome-docs PR number goes here> ## Test Environment @@ -23,12 +28,6 @@ - [ ] RTL87xx ## Example entry for `config.yaml`: -<!-- - Supplying a configuration snippet, makes it easier for a maintainer to test - your PR. Furthermore, for new integrations, it gives an impression of how - the configuration would look like. - Note: Remove this section if this PR does not have an example entry. ---> ```yaml # Example config.yaml From 657527655d380554edfdd3274d7b1d5ac1b4a32a Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Sun, 20 Oct 2024 17:40:43 -0700 Subject: [PATCH 003/282] auto-load preferences (#7642) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- esphome/components/host/__init__.py | 2 +- esphome/components/libretiny/__init__.py | 2 +- esphome/components/rp2040/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index e83bf2dba8..eb8cfbd984 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -16,7 +16,7 @@ from .const import KEY_HOST from .gpio import host_pin_to_code # noqa CODEOWNERS = ["@esphome/core", "@clydebarrow"] -AUTO_LOAD = ["network"] +AUTO_LOAD = ["network", "preferences"] def set_core_data(config): diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index cc7fae7e70..b29d2e309c 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -46,7 +46,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@kuba2k2"] -AUTO_LOAD = [] +AUTO_LOAD = ["preferences"] def _detect_variant(value): diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index 925acb629d..f59962477f 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -26,7 +26,7 @@ from .gpio import rp2040_pin_to_code # noqa _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@jesserockz"] -AUTO_LOAD = [] +AUTO_LOAD = ["preferences"] def set_core_data(config): From 5e8794175dceb777f928a2966c4c5e9a977c2acc Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 21 Oct 2024 17:46:41 -0500 Subject: [PATCH 004/282] [wifi] Support custom MAC on Arduino, too (#7644) --- esphome/components/esp32/__init__.py | 12 ++++++++++-- .../components/wifi/wifi_component_esp32_arduino.cpp | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 8a73f2020d..61fbb53e3a 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -395,6 +395,13 @@ ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, + cv.Optional(CONF_ADVANCED, default={}): cv.Schema( + { + cv.Optional( + CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False + ): cv.boolean, + } + ), } ), _arduino_check_versions, @@ -494,6 +501,9 @@ async def to_code(config): conf = config[CONF_FRAMEWORK] cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) + if CONF_ADVANCED in conf and conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]: + cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC") + add_extra_script( "post", "post_build.py", @@ -540,8 +550,6 @@ async def to_code(config): for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) - if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]: - cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC") if conf[CONF_ADVANCED].get(CONF_IGNORE_EFUSE_MAC_CRC): add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True) if (framework_ver.major, framework_ver.minor) >= (4, 4): diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index b8724838c8..ef4308b28c 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -34,6 +34,11 @@ static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void WiFiComponent::wifi_pre_setup_() { + uint8_t mac[6]; + if (has_custom_mac_address()) { + get_mac_address_raw(mac); + set_mac_address(mac); + } auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2); WiFi.onEvent(f); WiFi.persistent(false); From c8d0cde329a83dd042412651c033dd133c9a3330 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:49:12 +1100 Subject: [PATCH 005/282] [config] Ensure user-supplied build flags don't get silently overwritten (#7622) --- esphome/core/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index f4253bee87..8c130eb6db 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -318,6 +318,8 @@ async def add_includes(includes): async def _add_platformio_options(pio_options): # Add includes at the very end, so that they override everything for key, val in pio_options.items(): + if key == "build_flags" and not isinstance(val, list): + val = [val] cg.add_platformio_option(key, val) From 612e2c164406fe0d4670b8af68478c3b3644c47e Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:50:16 +1100 Subject: [PATCH 006/282] [lvgl] Defer display rotation reset until setup(). (Bugfix) (#7627) --- esphome/components/lvgl/lvgl_esphome.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 413b039af0..c8f7848a4f 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -84,6 +84,7 @@ lv_event_code_t lv_api_event; // NOLINT lv_event_code_t lv_update_event; // NOLINT void LvglComponent::dump_config() { ESP_LOGCONFIG(TAG, "LVGL:"); + ESP_LOGCONFIG(TAG, " Display width/height: %d x %d", this->disp_drv_.hor_res, this->disp_drv_.ver_res); ESP_LOGCONFIG(TAG, " Rotation: %d", this->rotation); ESP_LOGCONFIG(TAG, " Draw rounding: %d", (int) this->draw_rounding); } @@ -426,19 +427,8 @@ LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buf this->disp_drv_.full_refresh = this->full_refresh_; this->disp_drv_.flush_cb = static_flush_cb; this->disp_drv_.rounder_cb = rounder_cb; - // reset the display rotation since we will handle all rotations - display->set_rotation(display::DISPLAY_ROTATION_0_DEGREES); - switch (this->rotation) { - default: - this->disp_drv_.hor_res = (lv_coord_t) display->get_width(); - this->disp_drv_.ver_res = (lv_coord_t) display->get_height(); - break; - case display::DISPLAY_ROTATION_90_DEGREES: - case display::DISPLAY_ROTATION_270_DEGREES: - this->disp_drv_.ver_res = (lv_coord_t) display->get_width(); - this->disp_drv_.hor_res = (lv_coord_t) display->get_height(); - break; - } + this->disp_drv_.hor_res = (lv_coord_t) display->get_width(); + this->disp_drv_.ver_res = (lv_coord_t) display->get_height(); this->disp_ = lv_disp_drv_register(&this->disp_drv_); } @@ -459,6 +449,9 @@ void LvglComponent::setup() { esp_log_printf_(LVGL_LOG_LEVEL, TAG, 0, "%.*s", (int) strlen(buf) - 1, buf); }); #endif + // Rotation will be handled by our drawing function, so reset the display rotation. + for (auto *display : this->displays_) + display->set_rotation(display::DISPLAY_ROTATION_0_DEGREES); this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0); lv_disp_trig_activity(this->disp_); ESP_LOGCONFIG(TAG, "LVGL Setup complete"); From 40ad6befa83f23674d09bf70198eb8761fa69c81 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:51:40 +1100 Subject: [PATCH 007/282] [lvgl] Remove states from style definitions (Bugfix) (#7645) --- esphome/components/lvgl/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index beaf279a9a..215fdecdb5 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -33,7 +33,7 @@ from .schemas import ( FLEX_OBJ_SCHEMA, GRID_CELL_SCHEMA, LAYOUT_SCHEMAS, - STATE_SCHEMA, + STYLE_SCHEMA, WIDGET_TYPES, any_widget_schema, container_schema, @@ -342,7 +342,7 @@ CONFIG_SCHEMA = ( ), cv.Optional(df.CONF_STYLE_DEFINITIONS): cv.ensure_list( cv.Schema({cv.Required(CONF_ID): cv.declare_id(lv_style_t)}) - .extend(STATE_SCHEMA) + .extend(STYLE_SCHEMA) .extend( { cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments, From dc42427c604ed14ab94270df8148fad22e101ee4 Mon Sep 17 00:00:00 2001 From: Michael Hansen <mike@rhasspy.org> Date: Mon, 21 Oct 2024 18:14:07 -0500 Subject: [PATCH 008/282] Move setting global voice assistant to constructor (#7630) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/voice_assistant/voice_assistant.cpp | 8 ++------ esphome/components/voice_assistant/voice_assistant.h | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index a2210f188d..0b53e74ba3 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -23,6 +23,8 @@ static const size_t SEND_BUFFER_SIZE = INPUT_BUFFER_SIZE * sizeof(int16_t); static const size_t RECEIVE_SIZE = 1024; static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE; +VoiceAssistant::VoiceAssistant() { global_voice_assistant = this; } + float VoiceAssistant::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } bool VoiceAssistant::start_udp_socket_() { @@ -68,12 +70,6 @@ bool VoiceAssistant::start_udp_socket_() { return true; } -void VoiceAssistant::setup() { - ESP_LOGCONFIG(TAG, "Setting up Voice Assistant..."); - - global_voice_assistant = this; -} - bool VoiceAssistant::allocate_buffers_() { if (this->send_buffer_ != nullptr) { return true; // Already allocated diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 56ada0e75a..870e2acdaa 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -91,7 +91,8 @@ struct Configuration { class VoiceAssistant : public Component { public: - void setup() override; + VoiceAssistant(); + void loop() override; float get_setup_priority() const override; void start_streaming(); From 7004053538c16544740e5e866395e9f94da1a63a Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:32:22 +1100 Subject: [PATCH 009/282] [config] Fix crash with empty substitutions block (#7612) --- esphome/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/config.py b/esphome/config.py index a2d0d15477..7d48569d2d 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -782,7 +782,7 @@ def validate_config( from esphome.components import substitutions result[CONF_SUBSTITUTIONS] = { - **config.get(CONF_SUBSTITUTIONS, {}), + **(config.get(CONF_SUBSTITUTIONS) or {}), **command_line_substitutions, } result.add_output_path([CONF_SUBSTITUTIONS], CONF_SUBSTITUTIONS) From 3dd34f66284ff8c5b0a69e70d57f6273d3395544 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sun, 20 Oct 2024 21:16:08 +0200 Subject: [PATCH 010/282] Fix broken ibeacon_uuid config in ble_rssi (#7640) --- esphome/components/ble_rssi/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index e3ba1abfd7..c4e767aa21 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -45,7 +45,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, - cv.Optional(CONF_IBEACON_UUID): cv.uuid, + cv.Optional(CONF_IBEACON_UUID): esp32_ble_tracker.bt_uuid, } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -79,7 +79,7 @@ async def to_code(config): cg.add(var.set_service_uuid128(uuid128)) if ibeacon_uuid := config.get(CONF_IBEACON_UUID): - ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid)) + ibeacon_uuid = esp32_ble_tracker.as_reversed_hex_array(ibeacon_uuid) cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None: From 10791db82e7784f2e8b22a69ca8ad01223f8f1d7 Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Sun, 20 Oct 2024 17:40:43 -0700 Subject: [PATCH 011/282] auto-load preferences (#7642) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- esphome/components/host/__init__.py | 2 +- esphome/components/libretiny/__init__.py | 2 +- esphome/components/rp2040/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index e83bf2dba8..eb8cfbd984 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -16,7 +16,7 @@ from .const import KEY_HOST from .gpio import host_pin_to_code # noqa CODEOWNERS = ["@esphome/core", "@clydebarrow"] -AUTO_LOAD = ["network"] +AUTO_LOAD = ["network", "preferences"] def set_core_data(config): diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index cc7fae7e70..b29d2e309c 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -46,7 +46,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@kuba2k2"] -AUTO_LOAD = [] +AUTO_LOAD = ["preferences"] def _detect_variant(value): diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index 925acb629d..f59962477f 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -26,7 +26,7 @@ from .gpio import rp2040_pin_to_code # noqa _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@jesserockz"] -AUTO_LOAD = [] +AUTO_LOAD = ["preferences"] def set_core_data(config): From 748256b3eef8bcce7472983bd5d6f48dd55362cb Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 21 Oct 2024 17:46:41 -0500 Subject: [PATCH 012/282] [wifi] Support custom MAC on Arduino, too (#7644) --- esphome/components/esp32/__init__.py | 12 ++++++++++-- .../components/wifi/wifi_component_esp32_arduino.cpp | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 8a73f2020d..61fbb53e3a 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -395,6 +395,13 @@ ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, + cv.Optional(CONF_ADVANCED, default={}): cv.Schema( + { + cv.Optional( + CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False + ): cv.boolean, + } + ), } ), _arduino_check_versions, @@ -494,6 +501,9 @@ async def to_code(config): conf = config[CONF_FRAMEWORK] cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) + if CONF_ADVANCED in conf and conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]: + cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC") + add_extra_script( "post", "post_build.py", @@ -540,8 +550,6 @@ async def to_code(config): for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) - if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]: - cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC") if conf[CONF_ADVANCED].get(CONF_IGNORE_EFUSE_MAC_CRC): add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True) if (framework_ver.major, framework_ver.minor) >= (4, 4): diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index b8724838c8..ef4308b28c 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -34,6 +34,11 @@ static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void WiFiComponent::wifi_pre_setup_() { + uint8_t mac[6]; + if (has_custom_mac_address()) { + get_mac_address_raw(mac); + set_mac_address(mac); + } auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2); WiFi.onEvent(f); WiFi.persistent(false); From c26c96b8f469a3071c14ef91b5e7aa073a3aa9ca Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:49:12 +1100 Subject: [PATCH 013/282] [config] Ensure user-supplied build flags don't get silently overwritten (#7622) --- esphome/core/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index f4253bee87..8c130eb6db 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -318,6 +318,8 @@ async def add_includes(includes): async def _add_platformio_options(pio_options): # Add includes at the very end, so that they override everything for key, val in pio_options.items(): + if key == "build_flags" and not isinstance(val, list): + val = [val] cg.add_platformio_option(key, val) From 3ebdd62c672dba8fa7d4c4ebd021750394c76596 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:51:40 +1100 Subject: [PATCH 014/282] [lvgl] Remove states from style definitions (Bugfix) (#7645) --- esphome/components/lvgl/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index ce3843567b..1a587edb57 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -33,7 +33,7 @@ from .schemas import ( FLEX_OBJ_SCHEMA, GRID_CELL_SCHEMA, LAYOUT_SCHEMAS, - STATE_SCHEMA, + STYLE_SCHEMA, WIDGET_TYPES, any_widget_schema, container_schema, @@ -323,7 +323,7 @@ CONFIG_SCHEMA = ( ), cv.Optional(df.CONF_STYLE_DEFINITIONS): cv.ensure_list( cv.Schema({cv.Required(CONF_ID): cv.declare_id(lv_style_t)}) - .extend(STATE_SCHEMA) + .extend(STYLE_SCHEMA) .extend( { cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments, From d95b370998527e279ac3a9c3376ede984929cd5d Mon Sep 17 00:00:00 2001 From: Michael Hansen <mike@rhasspy.org> Date: Mon, 21 Oct 2024 18:14:07 -0500 Subject: [PATCH 015/282] Move setting global voice assistant to constructor (#7630) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/voice_assistant/voice_assistant.cpp | 8 ++------ esphome/components/voice_assistant/voice_assistant.h | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index a2210f188d..0b53e74ba3 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -23,6 +23,8 @@ static const size_t SEND_BUFFER_SIZE = INPUT_BUFFER_SIZE * sizeof(int16_t); static const size_t RECEIVE_SIZE = 1024; static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE; +VoiceAssistant::VoiceAssistant() { global_voice_assistant = this; } + float VoiceAssistant::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } bool VoiceAssistant::start_udp_socket_() { @@ -68,12 +70,6 @@ bool VoiceAssistant::start_udp_socket_() { return true; } -void VoiceAssistant::setup() { - ESP_LOGCONFIG(TAG, "Setting up Voice Assistant..."); - - global_voice_assistant = this; -} - bool VoiceAssistant::allocate_buffers_() { if (this->send_buffer_ != nullptr) { return true; // Already allocated diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 56ada0e75a..870e2acdaa 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -91,7 +91,8 @@ struct Configuration { class VoiceAssistant : public Component { public: - void setup() override; + VoiceAssistant(); + void loop() override; float get_setup_priority() const override; void start_streaming(); From 735c04cd6995e1cd220440af0fb79100a0b638f2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 22 Oct 2024 12:57:17 +1300 Subject: [PATCH 016/282] Bump version to 2024.10.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5061b1a439..5fa457b25a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.10.0" +__version__ = "2024.10.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 8bb4316956a39a8c7141a9504f56ee0f5c32812a Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:03:32 +1100 Subject: [PATCH 017/282] [lvgl] light schema should require `widget:` not `led:` (Bugfix) (#7649) --- esphome/components/lvgl/light/__init__.py | 8 ++++---- tests/components/lvgl/common.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/lvgl/light/__init__.py b/esphome/components/lvgl/light/__init__.py index a0eeded349..8031ae8221 100644 --- a/esphome/components/lvgl/light/__init__.py +++ b/esphome/components/lvgl/light/__init__.py @@ -2,9 +2,9 @@ import esphome.codegen as cg from esphome.components import light from esphome.components.light import LightOutput import esphome.config_validation as cv -from esphome.const import CONF_GAMMA_CORRECT, CONF_LED, CONF_OUTPUT_ID +from esphome.const import CONF_GAMMA_CORRECT, CONF_OUTPUT_ID -from ..defines import CONF_LVGL_ID +from ..defines import CONF_LVGL_ID, CONF_WIDGET from ..lvcode import LvContext from ..schemas import LVGL_SCHEMA from ..types import LvType, lvgl_ns @@ -15,7 +15,7 @@ LVLight = lvgl_ns.class_("LVLight", LightOutput) CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( { cv.Optional(CONF_GAMMA_CORRECT, default=0.0): cv.positive_float, - cv.Required(CONF_LED): cv.use_id(lv_led_t), + cv.Required(CONF_WIDGET): cv.use_id(lv_led_t), cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(LVLight), } ).extend(LVGL_SCHEMA) @@ -26,7 +26,7 @@ async def to_code(config): await light.register_light(var, config) paren = await cg.get_variable(config[CONF_LVGL_ID]) - widget = await get_widgets(config, CONF_LED) + widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] await wait_for_widgets() async with LvContext(paren) as ctx: diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index 5dcf30e0c1..cebc3caaa7 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -93,7 +93,7 @@ light: - platform: lvgl name: LVGL LED id: lv_light - led: lv_led + widget: lv_led binary_sensor: - platform: lvgl From ff48f53989f7886c167364e04df72f79039a9bac Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:05:39 +1100 Subject: [PATCH 018/282] [image] Fix compile time problem with host image not using lvgl (#7654) --- esphome/components/image/image.h | 7 +------ esphome/components/lvgl/lvgl_proxy.h | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 esphome/components/lvgl/lvgl_proxy.h diff --git a/esphome/components/image/image.h b/esphome/components/image/image.h index ae5a7a814d..a8a8aab2c2 100644 --- a/esphome/components/image/image.h +++ b/esphome/components/image/image.h @@ -3,12 +3,7 @@ #include "esphome/components/display/display.h" #ifdef USE_LVGL -// required for clang-tidy -#ifndef LV_CONF_H -#define LV_CONF_SKIP 1 // NOLINT -#endif // LV_CONF_H - -#include <lvgl.h> +#include "esphome/components/lvgl/lvgl_proxy.h" #endif // USE_LVGL namespace esphome { diff --git a/esphome/components/lvgl/lvgl_proxy.h b/esphome/components/lvgl/lvgl_proxy.h new file mode 100644 index 0000000000..0ccd80e541 --- /dev/null +++ b/esphome/components/lvgl/lvgl_proxy.h @@ -0,0 +1,17 @@ +#pragma once +/** +* This header is for use in components that might or might not use LVGL. There is a platformio bug where +the mere mention of a header file, even if ifdefed, causes the build to fail. This is a workaround, since if this +file is included in the build, LVGL is always included. +*/ +#ifdef USE_LVGL +// required for clang-tidy +#ifndef LV_CONF_H +#define LV_CONF_SKIP 1 // NOLINT +#endif // LV_CONF_H + +#include <lvgl.h> +namespace esphome { +namespace lvgl {} // namespace lvgl +} // namespace esphome +#endif // USE_LVGL From 3ac730fb2f5b7231e77d5d7c3822e7cc59fb4e59 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:06:58 +1100 Subject: [PATCH 019/282] [lvgl] Fix rotation code for 90deg (Bugfix) (#7653) --- esphome/components/lvgl/lvgl_esphome.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index c8f7848a4f..70cfb859de 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -146,7 +146,7 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) { lv_color_t *dst = this->rotate_buf_; switch (this->rotation) { case display::DISPLAY_ROTATION_90_DEGREES: - for (lv_coord_t x = height - 1; x-- != 0;) { + for (lv_coord_t x = height; x-- != 0;) { for (lv_coord_t y = 0; y != width; y++) { dst[y * height + x] = *ptr++; } From 6330177d24b82060afec7628d6152fea6b54f963 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:10:09 +1100 Subject: [PATCH 020/282] [lvgl] Allow strings to be interpreted as integers (Bugfix) (#7652) --- esphome/components/lvgl/lv_validation.py | 6 ++---- tests/components/lvgl/lvgl-package.yaml | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index fd840cc417..9af25a4e90 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -274,10 +274,8 @@ def size_validator(value): return ["SIZE_CONTENT", "number of pixels", "percentage"] if isinstance(value, str) and value.lower().endswith("px"): value = cv.int_(value[:-2]) - if isinstance(value, str) and not value.endswith("%"): - if value.upper() == "SIZE_CONTENT": - return "LV_SIZE_CONTENT" - raise cv.Invalid("must be 'size_content', a percentage or an integer (pixels)") + if isinstance(value, str) and value.upper() == "SIZE_CONTENT": + return "LV_SIZE_CONTENT" return pixels_or_percent_validator(value) diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index cef76396c2..7fd824a87b 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -422,7 +422,7 @@ lvgl: id: lv_image src: cat_image align: top_left - y: 50 + y: "50" - tileview: id: tileview_id scrollbar_mode: active @@ -461,7 +461,7 @@ lvgl: bg_opa: transp knob: radius: 1 - width: 4 + width: "4" height: 10% bg_color: 0x000000 width: 100% From 2597975ae00dde096522b80824dbf9915a0d2fd7 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 22 Oct 2024 05:29:16 +0200 Subject: [PATCH 021/282] [rtttl] Add `get_gain()` (#7647) --- esphome/components/rtttl/rtttl.cpp | 5 ++++- esphome/components/rtttl/rtttl.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index 495b5c1c8a..db4cc731e4 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -26,7 +26,10 @@ inline double deg2rad(double degrees) { return degrees * PI_ON_180; } -void Rtttl::dump_config() { ESP_LOGCONFIG(TAG, "Rtttl"); } +void Rtttl::dump_config() { + ESP_LOGCONFIG(TAG, "Rtttl:"); + ESP_LOGCONFIG(TAG, " Gain: %f", gain_); +} void Rtttl::play(std::string rtttl) { if (this->state_ != State::STATE_STOPPED && this->state_ != State::STATE_STOPPING) { diff --git a/esphome/components/rtttl/rtttl.h b/esphome/components/rtttl/rtttl.h index 3cb6e3f5fb..10c290c5fb 100644 --- a/esphome/components/rtttl/rtttl.h +++ b/esphome/components/rtttl/rtttl.h @@ -39,6 +39,7 @@ class Rtttl : public Component { #ifdef USE_SPEAKER void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; } #endif + float get_gain() { return gain_; } void set_gain(float gain) { if (gain < 0.1f) gain = 0.1f; From a932ca2f64213e2af34c4d2e25f7f82766e98ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Mart=C3=ADn?= <contact@rodrigomartin.dev> Date: Tue, 22 Oct 2024 05:38:25 +0200 Subject: [PATCH 022/282] feat(MQTT): Add subscribe QoS to discovery (#7648) --- esphome/components/mqtt/__init__.py | 3 +++ esphome/components/mqtt/mqtt_component.cpp | 6 ++++++ esphome/components/mqtt/mqtt_component.h | 4 ++++ esphome/components/mqtt/mqtt_const.h | 2 ++ esphome/config_validation.py | 4 +++- esphome/const.py | 1 + tests/components/mqtt/common.yaml | 1 + 7 files changed, 20 insertions(+), 1 deletion(-) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 336d928f71..8851581ea0 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -41,6 +41,7 @@ from esphome.const import ( CONF_SHUTDOWN_MESSAGE, CONF_SSL_FINGERPRINTS, CONF_STATE_TOPIC, + CONF_SUBSCRIBE_QOS, CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_TRIGGER_ID, @@ -518,6 +519,8 @@ async def register_mqtt_component(var, config): cg.add(var.set_qos(config[CONF_QOS])) if CONF_RETAIN in config: cg.add(var.set_retain(config[CONF_RETAIN])) + if CONF_SUBSCRIBE_QOS in config: + cg.add(var.set_subscribe_qos(config[CONF_SUBSCRIBE_QOS])) if not config.get(CONF_DISCOVERY, True): cg.add(var.disable_discovery()) if CONF_STATE_TOPIC in config: diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 295fbba5e5..3b9d367a7b 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -16,6 +16,8 @@ static const char *const TAG = "mqtt.component"; void MQTTComponent::set_qos(uint8_t qos) { this->qos_ = qos; } +void MQTTComponent::set_subscribe_qos(uint8_t qos) { this->subscribe_qos_ = qos; } + void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; } std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const { @@ -76,6 +78,10 @@ bool MQTTComponent::send_discovery_() { config.command_topic = true; this->send_discovery(root, config); + // Set subscription QoS (default is 0) + if (this->subscribe_qos_ != 0) { + root[MQTT_QOS] = this->subscribe_qos_; + } // Fields from EntityBase if (this->get_entity()->has_own_name()) { diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 147840d11f..01ba98ad40 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -89,6 +89,9 @@ class MQTTComponent : public Component { void disable_discovery(); bool is_discovery_enabled() const; + /// Set the QOS for subscribe messages (used in discovery). + void set_subscribe_qos(uint8_t qos); + /// Override this method to return the component type (e.g. "light", "sensor", ...) virtual std::string component_type() const = 0; @@ -204,6 +207,7 @@ class MQTTComponent : public Component { bool command_retain_{false}; bool retain_{true}; uint8_t qos_{0}; + uint8_t subscribe_qos_{0}; bool discovery_enabled_{true}; bool resend_state_{false}; }; diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 71f169fbe8..c1c40c4b6d 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -180,6 +180,7 @@ constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t"; constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "pr_mode_stat_t"; constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "pr_mode_val_tpl"; constexpr const char *const MQTT_PRESET_MODES = "pr_modes"; +constexpr const char *const MQTT_QOS = "qos"; constexpr const char *const MQTT_RED_TEMPLATE = "r_tpl"; constexpr const char *const MQTT_RETAIN = "ret"; constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_cmd_tpl"; @@ -441,6 +442,7 @@ constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_comman constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"; constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"; constexpr const char *const MQTT_PRESET_MODES = "preset_modes"; +constexpr const char *const MQTT_QOS = "qos"; constexpr const char *const MQTT_RED_TEMPLATE = "red_template"; constexpr const char *const MQTT_RETAIN = "retain"; constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_command_template"; diff --git a/esphome/config_validation.py b/esphome/config_validation.py index a7525a62dd..98b81ec328 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -40,6 +40,7 @@ from esphome.const import ( CONF_SECOND, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, + CONF_SUBSCRIBE_QOS, CONF_TOPIC, CONF_TYPE, CONF_TYPE_ID, @@ -1893,9 +1894,10 @@ MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema( MQTT_COMPONENT_SCHEMA = Schema( { - Optional(CONF_QOS): All(requires_component("mqtt"), int_range(min=0, max=2)), + Optional(CONF_QOS): All(requires_component("mqtt"), mqtt_qos), Optional(CONF_RETAIN): All(requires_component("mqtt"), boolean), Optional(CONF_DISCOVERY): All(requires_component("mqtt"), boolean), + Optional(CONF_SUBSCRIBE_QOS): All(requires_component("mqtt"), mqtt_qos), Optional(CONF_STATE_TOPIC): All(requires_component("mqtt"), publish_topic), Optional(CONF_AVAILABILITY): All( requires_component("mqtt"), Any(None, MQTT_COMPONENT_AVAILABILITY_SCHEMA) diff --git a/esphome/const.py b/esphome/const.py index a3a4318d69..54ebb2815f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -819,6 +819,7 @@ CONF_STOP = "stop" CONF_STOP_ACTION = "stop_action" CONF_STORE_BASELINE = "store_baseline" CONF_SUBNET = "subnet" +CONF_SUBSCRIBE_QOS = "subscribe_qos" CONF_SUBSTITUTIONS = "substitutions" CONF_SUM = "sum" CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action" diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml index f7a727ab2f..5ed6335d65 100644 --- a/tests/components/mqtt/common.yaml +++ b/tests/components/mqtt/common.yaml @@ -227,6 +227,7 @@ datetime: type: date state_topic: some/topic/date qos: 2 + subscribe_qos: 2 set_action: - logger.log: "set_value" on_value: From 7c0543862ad593916e1230d1dc2f2dec8e61c507 Mon Sep 17 00:00:00 2001 From: Kyle Cascade <kyle@cascade.family> Date: Mon, 21 Oct 2024 21:11:23 -0700 Subject: [PATCH 023/282] Humanized the missing MQTT log topic error message (#7634) --- esphome/mqtt.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/mqtt.py b/esphome/mqtt.py index c1c45799cc..d55fb0202d 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -209,6 +209,12 @@ def show_logs(config, topic=None, username=None, password=None, client_id=None): elif CONF_MQTT in config: conf = config[CONF_MQTT] if CONF_LOG_TOPIC in conf: + if config[CONF_MQTT][CONF_LOG_TOPIC] is None: + _LOGGER.error("MQTT log topic set to null, can't start MQTT logs") + return 1 + if CONF_TOPIC not in config[CONF_MQTT][CONF_LOG_TOPIC]: + _LOGGER.error("MQTT log topic not available, can't start MQTT logs") + return 1 topic = config[CONF_MQTT][CONF_LOG_TOPIC][CONF_TOPIC] elif CONF_TOPIC_PREFIX in config[CONF_MQTT]: topic = f"{config[CONF_MQTT][CONF_TOPIC_PREFIX]}/debug" From 68844c48698c6983a3d22498dbe70ee20c9835bf Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:16:55 +1100 Subject: [PATCH 024/282] [lvgl] Some properties were not templatable (Bugfix) (#7655) --- esphome/components/lvgl/lv_validation.py | 4 ++++ esphome/components/lvgl/schemas.py | 14 +++++++------- tests/components/lvgl/lvgl-package.yaml | 7 +++++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 9af25a4e90..b91b0905df 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -267,6 +267,9 @@ def angle(value): return int(cv.float_range(0.0, 360.0)(cv.angle(value)) * 10) +lv_angle = LValidator(angle, uint32) + + @schema_extractor("one_of") def size_validator(value): """A size in one axis - one of "size_content", a number (pixels) or a percentage""" @@ -401,6 +404,7 @@ class TextValidator(LValidator): lv_text = TextValidator() lv_float = LValidator(cv.float_, cg.float_) lv_int = LValidator(cv.int_, cg.int_) +lv_positive_int = LValidator(cv.positive_int, cg.int_) lv_brightness = LValidator(cv.percentage, cg.float_, retmapper=lambda x: int(x * 255)) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 7599d64416..bb14c11ddd 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -91,7 +91,7 @@ STYLE_PROPS = { "arc_opa": lvalid.opacity, "arc_color": lvalid.lv_color, "arc_rounded": lvalid.lv_bool, - "arc_width": cv.positive_int, + "arc_width": lvalid.lv_positive_int, "anim_time": lvalid.lv_milliseconds, "bg_color": lvalid.lv_color, "bg_grad": lv_gradient, @@ -111,7 +111,7 @@ STYLE_PROPS = { "border_side": df.LvConstant( "LV_BORDER_SIDE_", "NONE", "TOP", "BOTTOM", "LEFT", "RIGHT", "INTERNAL" ).several_of, - "border_width": cv.positive_int, + "border_width": lvalid.lv_positive_int, "clip_corner": lvalid.lv_bool, "color_filter_opa": lvalid.opacity, "height": lvalid.size, @@ -134,11 +134,11 @@ STYLE_PROPS = { "pad_right": lvalid.pixels, "pad_top": lvalid.pixels, "shadow_color": lvalid.lv_color, - "shadow_ofs_x": cv.int_, - "shadow_ofs_y": cv.int_, + "shadow_ofs_x": lvalid.lv_int, + "shadow_ofs_y": lvalid.lv_int, "shadow_opa": lvalid.opacity, - "shadow_spread": cv.int_, - "shadow_width": cv.positive_int, + "shadow_spread": lvalid.lv_int, + "shadow_width": lvalid.lv_positive_int, "text_align": df.LvConstant( "LV_TEXT_ALIGN_", "LEFT", "CENTER", "RIGHT", "AUTO" ).one_of, @@ -150,7 +150,7 @@ STYLE_PROPS = { "text_letter_space": cv.positive_int, "text_line_space": cv.positive_int, "text_opa": lvalid.opacity, - "transform_angle": lvalid.angle, + "transform_angle": lvalid.lv_angle, "transform_height": lvalid.pixels_or_percent, "transform_pivot_x": lvalid.pixels_or_percent, "transform_pivot_y": lvalid.pixels_or_percent, diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 7fd824a87b..4962a71596 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -323,6 +323,13 @@ lvgl: id: button_button width: 20% height: 10% + transform_angle: !lambda return 180*100; + arc_width: !lambda return 4; + border_width: !lambda return 6; + shadow_ofs_x: !lambda return 6; + shadow_ofs_y: !lambda return 6; + shadow_spread: !lambda return 6; + shadow_width: !lambda return 6; pressed: bg_color: light_blue checkable: true From dd8d25e43f6d30f93391aeee207a912dd52907d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Poczkodi?= <gabest11@gmail.com> Date: Wed, 23 Oct 2024 05:23:10 +0200 Subject: [PATCH 025/282] i2c_device (#7641) --- CODEOWNERS | 1 + esphome/components/i2c_device/__init__.py | 26 +++++++++++++++++++ esphome/components/i2c_device/i2c_device.cpp | 17 ++++++++++++ esphome/components/i2c_device/i2c_device.h | 18 +++++++++++++ .../components/i2c_device/test.esp32-ard.yaml | 8 ++++++ .../i2c_device/test.esp32-c3-ard.yaml | 8 ++++++ .../i2c_device/test.esp32-c3-idf.yaml | 8 ++++++ .../components/i2c_device/test.esp32-idf.yaml | 8 ++++++ .../i2c_device/test.esp8266-ard.yaml | 8 ++++++ .../i2c_device/test.rp2040-ard.yaml | 8 ++++++ 10 files changed, 110 insertions(+) create mode 100644 esphome/components/i2c_device/__init__.py create mode 100644 esphome/components/i2c_device/i2c_device.cpp create mode 100644 esphome/components/i2c_device/i2c_device.h create mode 100644 tests/components/i2c_device/test.esp32-ard.yaml create mode 100644 tests/components/i2c_device/test.esp32-c3-ard.yaml create mode 100644 tests/components/i2c_device/test.esp32-c3-idf.yaml create mode 100644 tests/components/i2c_device/test.esp32-idf.yaml create mode 100644 tests/components/i2c_device/test.esp8266-ard.yaml create mode 100644 tests/components/i2c_device/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 53300d6740..616b18293d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -198,6 +198,7 @@ esphome/components/htu31d/* @betterengineering esphome/components/hydreon_rgxx/* @functionpointer esphome/components/hyt271/* @Philippe12 esphome/components/i2c/* @esphome/core +esphome/components/i2c_device/* @gabest11 esphome/components/i2s_audio/* @jesserockz esphome/components/i2s_audio/media_player/* @jesserockz esphome/components/i2s_audio/microphone/* @jesserockz diff --git a/esphome/components/i2c_device/__init__.py b/esphome/components/i2c_device/__init__.py new file mode 100644 index 0000000000..e145ba56f8 --- /dev/null +++ b/esphome/components/i2c_device/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@gabest11"] +MULTI_CONF = True + +i2c_device_ns = cg.esphome_ns.namespace("i2c_device") + +I2CDeviceComponent = i2c_device_ns.class_( + "I2CDeviceComponent", cg.Component, i2c.I2CDevice +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(I2CDeviceComponent), + } +).extend(i2c.i2c_device_schema(None)) + + +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/i2c_device/i2c_device.cpp b/esphome/components/i2c_device/i2c_device.cpp new file mode 100644 index 0000000000..455c68fbed --- /dev/null +++ b/esphome/components/i2c_device/i2c_device.cpp @@ -0,0 +1,17 @@ +#include "i2c_device.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include <cinttypes> + +namespace esphome { +namespace i2c_device { + +static const char *const TAG = "i2c_device"; + +void I2CDeviceComponent::dump_config() { + ESP_LOGCONFIG(TAG, "I2CDevice"); + LOG_I2C_DEVICE(this); +} + +} // namespace i2c_device +} // namespace esphome diff --git a/esphome/components/i2c_device/i2c_device.h b/esphome/components/i2c_device/i2c_device.h new file mode 100644 index 0000000000..ab118e3e89 --- /dev/null +++ b/esphome/components/i2c_device/i2c_device.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace i2c_device { + +class I2CDeviceComponent : public Component, public i2c::I2CDevice { + public: + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: +}; + +} // namespace i2c_device +} // namespace esphome diff --git a/tests/components/i2c_device/test.esp32-ard.yaml b/tests/components/i2c_device/test.esp32-ard.yaml new file mode 100644 index 0000000000..6169d113f8 --- /dev/null +++ b/tests/components/i2c_device/test.esp32-ard.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_i2c + scl: 16 + sda: 17 + +i2c_device: + id: i2cdev + address: 0x2C diff --git a/tests/components/i2c_device/test.esp32-c3-ard.yaml b/tests/components/i2c_device/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..5d53d12208 --- /dev/null +++ b/tests/components/i2c_device/test.esp32-c3-ard.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_i2c + scl: 5 + sda: 4 + +i2c_device: + id: i2cdev + address: 0x2C diff --git a/tests/components/i2c_device/test.esp32-c3-idf.yaml b/tests/components/i2c_device/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..5d53d12208 --- /dev/null +++ b/tests/components/i2c_device/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_i2c + scl: 5 + sda: 4 + +i2c_device: + id: i2cdev + address: 0x2C diff --git a/tests/components/i2c_device/test.esp32-idf.yaml b/tests/components/i2c_device/test.esp32-idf.yaml new file mode 100644 index 0000000000..6169d113f8 --- /dev/null +++ b/tests/components/i2c_device/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_i2c + scl: 16 + sda: 17 + +i2c_device: + id: i2cdev + address: 0x2C diff --git a/tests/components/i2c_device/test.esp8266-ard.yaml b/tests/components/i2c_device/test.esp8266-ard.yaml new file mode 100644 index 0000000000..5d53d12208 --- /dev/null +++ b/tests/components/i2c_device/test.esp8266-ard.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_i2c + scl: 5 + sda: 4 + +i2c_device: + id: i2cdev + address: 0x2C diff --git a/tests/components/i2c_device/test.rp2040-ard.yaml b/tests/components/i2c_device/test.rp2040-ard.yaml new file mode 100644 index 0000000000..5d53d12208 --- /dev/null +++ b/tests/components/i2c_device/test.rp2040-ard.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_i2c + scl: 5 + sda: 4 + +i2c_device: + id: i2cdev + address: 0x2C From fdebf041967549866f438431284c0690bec3d6da Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt <kevin.ahrendt@nabucasa.com> Date: Wed, 23 Oct 2024 13:25:31 -0400 Subject: [PATCH 026/282] [voice_assistant] Bugfix: Fix crash on start (#7662) --- .../voice_assistant/voice_assistant.cpp | 54 +++++++++++-------- .../voice_assistant/voice_assistant.h | 6 +-- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 0b53e74ba3..6f164f69d3 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -433,16 +433,18 @@ void VoiceAssistant::loop() { #ifdef USE_SPEAKER void VoiceAssistant::write_speaker_() { - if (this->speaker_buffer_size_ > 0) { - size_t write_chunk = std::min<size_t>(this->speaker_buffer_size_, 4 * 1024); - size_t written = this->speaker_->play(this->speaker_buffer_, write_chunk); - if (written > 0) { - memmove(this->speaker_buffer_, this->speaker_buffer_ + written, this->speaker_buffer_size_ - written); - this->speaker_buffer_size_ -= written; - this->speaker_buffer_index_ -= written; - this->set_timeout("speaker-timeout", 5000, [this]() { this->speaker_->stop(); }); - } else { - ESP_LOGV(TAG, "Speaker buffer full, trying again next loop"); + if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) { + if (this->speaker_buffer_size_ > 0) { + size_t write_chunk = std::min<size_t>(this->speaker_buffer_size_, 4 * 1024); + size_t written = this->speaker_->play(this->speaker_buffer_, write_chunk); + if (written > 0) { + memmove(this->speaker_buffer_, this->speaker_buffer_ + written, this->speaker_buffer_size_ - written); + this->speaker_buffer_size_ -= written; + this->speaker_buffer_index_ -= written; + this->set_timeout("speaker-timeout", 5000, [this]() { this->speaker_->stop(); }); + } else { + ESP_LOGV(TAG, "Speaker buffer full, trying again next loop"); + } } } } @@ -772,16 +774,20 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } case api::enums::VOICE_ASSISTANT_TTS_STREAM_START: { #ifdef USE_SPEAKER - this->wait_for_stream_end_ = true; - ESP_LOGD(TAG, "TTS stream start"); - this->defer([this] { this->tts_stream_start_trigger_->trigger(); }); + if (this->speaker_ != nullptr) { + this->wait_for_stream_end_ = true; + ESP_LOGD(TAG, "TTS stream start"); + this->defer([this] { this->tts_stream_start_trigger_->trigger(); }); + } #endif break; } case api::enums::VOICE_ASSISTANT_TTS_STREAM_END: { #ifdef USE_SPEAKER - this->stream_ended_ = true; - ESP_LOGD(TAG, "TTS stream end"); + if (this->speaker_ != nullptr) { + this->stream_ended_ = true; + ESP_LOGD(TAG, "TTS stream end"); + } #endif break; } @@ -802,14 +808,16 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) { #ifdef USE_SPEAKER // We should never get to this function if there is no speaker anyway - if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) { - memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length()); - this->speaker_buffer_index_ += msg.data.length(); - this->speaker_buffer_size_ += msg.data.length(); - this->speaker_bytes_received_ += msg.data.length(); - ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.length()); - } else { - ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); + if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) { + if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) { + memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length()); + this->speaker_buffer_index_ += msg.data.length(); + this->speaker_buffer_size_ += msg.data.length(); + this->speaker_bytes_received_ += msg.data.length(); + ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.length()); + } else { + ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); + } } #endif } diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 870e2acdaa..0016d3157c 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -250,7 +250,7 @@ class VoiceAssistant : public Component { #ifdef USE_SPEAKER void write_speaker_(); speaker::Speaker *speaker_{nullptr}; - uint8_t *speaker_buffer_; + uint8_t *speaker_buffer_{nullptr}; size_t speaker_buffer_index_{0}; size_t speaker_buffer_size_{0}; size_t speaker_bytes_received_{0}; @@ -282,8 +282,8 @@ class VoiceAssistant : public Component { float volume_multiplier_; uint32_t conversation_timeout_; - uint8_t *send_buffer_; - int16_t *input_buffer_; + uint8_t *send_buffer_{nullptr}; + int16_t *input_buffer_{nullptr}; bool continuous_{false}; bool silence_detection_; From 833565feb985e91c9512774a363ea2e5ca79d267 Mon Sep 17 00:00:00 2001 From: Kyle Cascade <kyle@cascade.family> Date: Mon, 21 Oct 2024 21:11:23 -0700 Subject: [PATCH 027/282] Humanized the missing MQTT log topic error message (#7634) --- esphome/mqtt.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/mqtt.py b/esphome/mqtt.py index c1c45799cc..d55fb0202d 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -209,6 +209,12 @@ def show_logs(config, topic=None, username=None, password=None, client_id=None): elif CONF_MQTT in config: conf = config[CONF_MQTT] if CONF_LOG_TOPIC in conf: + if config[CONF_MQTT][CONF_LOG_TOPIC] is None: + _LOGGER.error("MQTT log topic set to null, can't start MQTT logs") + return 1 + if CONF_TOPIC not in config[CONF_MQTT][CONF_LOG_TOPIC]: + _LOGGER.error("MQTT log topic not available, can't start MQTT logs") + return 1 topic = config[CONF_MQTT][CONF_LOG_TOPIC][CONF_TOPIC] elif CONF_TOPIC_PREFIX in config[CONF_MQTT]: topic = f"{config[CONF_MQTT][CONF_TOPIC_PREFIX]}/debug" From 8d90d256bf2518c46676a0a7dfe37ef3dd1868c8 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:16:55 +1100 Subject: [PATCH 028/282] [lvgl] Some properties were not templatable (Bugfix) (#7655) --- esphome/components/lvgl/lv_validation.py | 4 ++++ esphome/components/lvgl/schemas.py | 14 +++++++------- tests/components/lvgl/lvgl-package.yaml | 7 +++++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index fd840cc417..a58fbc33e0 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -267,6 +267,9 @@ def angle(value): return int(cv.float_range(0.0, 360.0)(cv.angle(value)) * 10) +lv_angle = LValidator(angle, uint32) + + @schema_extractor("one_of") def size_validator(value): """A size in one axis - one of "size_content", a number (pixels) or a percentage""" @@ -403,6 +406,7 @@ class TextValidator(LValidator): lv_text = TextValidator() lv_float = LValidator(cv.float_, cg.float_) lv_int = LValidator(cv.int_, cg.int_) +lv_positive_int = LValidator(cv.positive_int, cg.int_) lv_brightness = LValidator(cv.percentage, cg.float_, retmapper=lambda x: int(x * 255)) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 780057623a..0d5a609a2d 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -91,7 +91,7 @@ STYLE_PROPS = { "arc_opa": lvalid.opacity, "arc_color": lvalid.lv_color, "arc_rounded": lvalid.lv_bool, - "arc_width": cv.positive_int, + "arc_width": lvalid.lv_positive_int, "anim_time": lvalid.lv_milliseconds, "bg_color": lvalid.lv_color, "bg_grad": lv_gradient, @@ -111,7 +111,7 @@ STYLE_PROPS = { "border_side": df.LvConstant( "LV_BORDER_SIDE_", "NONE", "TOP", "BOTTOM", "LEFT", "RIGHT", "INTERNAL" ).several_of, - "border_width": cv.positive_int, + "border_width": lvalid.lv_positive_int, "clip_corner": lvalid.lv_bool, "color_filter_opa": lvalid.opacity, "height": lvalid.size, @@ -134,11 +134,11 @@ STYLE_PROPS = { "pad_right": lvalid.pixels, "pad_top": lvalid.pixels, "shadow_color": lvalid.lv_color, - "shadow_ofs_x": cv.int_, - "shadow_ofs_y": cv.int_, + "shadow_ofs_x": lvalid.lv_int, + "shadow_ofs_y": lvalid.lv_int, "shadow_opa": lvalid.opacity, - "shadow_spread": cv.int_, - "shadow_width": cv.positive_int, + "shadow_spread": lvalid.lv_int, + "shadow_width": lvalid.lv_positive_int, "text_align": df.LvConstant( "LV_TEXT_ALIGN_", "LEFT", "CENTER", "RIGHT", "AUTO" ).one_of, @@ -150,7 +150,7 @@ STYLE_PROPS = { "text_letter_space": cv.positive_int, "text_line_space": cv.positive_int, "text_opa": lvalid.opacity, - "transform_angle": lvalid.angle, + "transform_angle": lvalid.lv_angle, "transform_height": lvalid.pixels_or_percent, "transform_pivot_x": lvalid.pixels_or_percent, "transform_pivot_y": lvalid.pixels_or_percent, diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 1770c1bfbc..2e05cf10d3 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -299,6 +299,13 @@ lvgl: id: button_button width: 20% height: 10% + transform_angle: !lambda return 180*100; + arc_width: !lambda return 4; + border_width: !lambda return 6; + shadow_ofs_x: !lambda return 6; + shadow_ofs_y: !lambda return 6; + shadow_spread: !lambda return 6; + shadow_width: !lambda return 6; pressed: bg_color: light_blue checkable: true From 156ad773c91edca8b14a8518363287e027ef8147 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt <kevin.ahrendt@nabucasa.com> Date: Wed, 23 Oct 2024 13:25:31 -0400 Subject: [PATCH 029/282] [voice_assistant] Bugfix: Fix crash on start (#7662) --- .../voice_assistant/voice_assistant.cpp | 54 +++++++++++-------- .../voice_assistant/voice_assistant.h | 6 +-- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 0b53e74ba3..6f164f69d3 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -433,16 +433,18 @@ void VoiceAssistant::loop() { #ifdef USE_SPEAKER void VoiceAssistant::write_speaker_() { - if (this->speaker_buffer_size_ > 0) { - size_t write_chunk = std::min<size_t>(this->speaker_buffer_size_, 4 * 1024); - size_t written = this->speaker_->play(this->speaker_buffer_, write_chunk); - if (written > 0) { - memmove(this->speaker_buffer_, this->speaker_buffer_ + written, this->speaker_buffer_size_ - written); - this->speaker_buffer_size_ -= written; - this->speaker_buffer_index_ -= written; - this->set_timeout("speaker-timeout", 5000, [this]() { this->speaker_->stop(); }); - } else { - ESP_LOGV(TAG, "Speaker buffer full, trying again next loop"); + if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) { + if (this->speaker_buffer_size_ > 0) { + size_t write_chunk = std::min<size_t>(this->speaker_buffer_size_, 4 * 1024); + size_t written = this->speaker_->play(this->speaker_buffer_, write_chunk); + if (written > 0) { + memmove(this->speaker_buffer_, this->speaker_buffer_ + written, this->speaker_buffer_size_ - written); + this->speaker_buffer_size_ -= written; + this->speaker_buffer_index_ -= written; + this->set_timeout("speaker-timeout", 5000, [this]() { this->speaker_->stop(); }); + } else { + ESP_LOGV(TAG, "Speaker buffer full, trying again next loop"); + } } } } @@ -772,16 +774,20 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } case api::enums::VOICE_ASSISTANT_TTS_STREAM_START: { #ifdef USE_SPEAKER - this->wait_for_stream_end_ = true; - ESP_LOGD(TAG, "TTS stream start"); - this->defer([this] { this->tts_stream_start_trigger_->trigger(); }); + if (this->speaker_ != nullptr) { + this->wait_for_stream_end_ = true; + ESP_LOGD(TAG, "TTS stream start"); + this->defer([this] { this->tts_stream_start_trigger_->trigger(); }); + } #endif break; } case api::enums::VOICE_ASSISTANT_TTS_STREAM_END: { #ifdef USE_SPEAKER - this->stream_ended_ = true; - ESP_LOGD(TAG, "TTS stream end"); + if (this->speaker_ != nullptr) { + this->stream_ended_ = true; + ESP_LOGD(TAG, "TTS stream end"); + } #endif break; } @@ -802,14 +808,16 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) { #ifdef USE_SPEAKER // We should never get to this function if there is no speaker anyway - if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) { - memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length()); - this->speaker_buffer_index_ += msg.data.length(); - this->speaker_buffer_size_ += msg.data.length(); - this->speaker_bytes_received_ += msg.data.length(); - ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.length()); - } else { - ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); + if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) { + if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) { + memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length()); + this->speaker_buffer_index_ += msg.data.length(); + this->speaker_buffer_size_ += msg.data.length(); + this->speaker_bytes_received_ += msg.data.length(); + ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.length()); + } else { + ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); + } } #endif } diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 870e2acdaa..0016d3157c 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -250,7 +250,7 @@ class VoiceAssistant : public Component { #ifdef USE_SPEAKER void write_speaker_(); speaker::Speaker *speaker_{nullptr}; - uint8_t *speaker_buffer_; + uint8_t *speaker_buffer_{nullptr}; size_t speaker_buffer_index_{0}; size_t speaker_buffer_size_{0}; size_t speaker_bytes_received_{0}; @@ -282,8 +282,8 @@ class VoiceAssistant : public Component { float volume_multiplier_; uint32_t conversation_timeout_; - uint8_t *send_buffer_; - int16_t *input_buffer_; + uint8_t *send_buffer_{nullptr}; + int16_t *input_buffer_{nullptr}; bool continuous_{false}; bool silence_detection_; From 127acfde6482ebb7e44894af0c17660263166fce Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 24 Oct 2024 07:15:40 +1300 Subject: [PATCH 030/282] Bump version to 2024.10.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5fa457b25a..032a4c79a0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.10.1" +__version__ = "2024.10.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 4289e00ad0ee3f466a60d9044c97157a8070a047 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 08:06:45 +1300 Subject: [PATCH 031/282] Bump actions/cache from 4.1.1 to 4.1.2 in /.github/actions/restore-python (#7659) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/restore-python/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index 1f5812691e..c6978f68c5 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -22,7 +22,7 @@ runs: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.1.1 + uses: actions/cache/restore@v4.1.2 with: path: venv # yamllint disable-line rule:line-length From 2feffddc550c0241aede210bb273b8e0001084f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 08:06:53 +1300 Subject: [PATCH 032/282] Bump actions/cache from 4.1.1 to 4.1.2 (#7660) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 178b914a1c..0d2f1c877d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.1.1 + uses: actions/cache@v4.1.2 with: path: venv # yamllint disable-line rule:line-length @@ -302,14 +302,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@v4.1.1 + uses: actions/cache@v4.1.2 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@v4.1.1 + uses: actions/cache/restore@v4.1.2 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }} From bff0e81ed3863ee960d1612fd64bec78fe34c03b Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt <kevin.ahrendt@nabucasa.com> Date: Wed, 23 Oct 2024 16:37:38 -0400 Subject: [PATCH 033/282] [speaker, i2s_audio] Support audio_dac component, mute actions, and improved logging (#7664) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 4 +- .../components/i2s_audio/speaker/__init__.py | 2 +- .../i2s_audio/speaker/i2s_audio_speaker.cpp | 72 +++++++++++++++---- .../i2s_audio/speaker/i2s_audio_speaker.h | 14 ++-- esphome/components/speaker/__init__.py | 31 ++++++-- esphome/components/speaker/automation.h | 20 ++++++ esphome/components/speaker/speaker.h | 42 ++++++++++- tests/components/speaker/test.esp32-ard.yaml | 2 + .../components/speaker/test.esp32-c3-ard.yaml | 2 + .../components/speaker/test.esp32-c3-idf.yaml | 2 + tests/components/speaker/test.esp32-idf.yaml | 15 +++- 11 files changed, 177 insertions(+), 29 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 616b18293d..f96e43d5b7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -202,7 +202,7 @@ esphome/components/i2c_device/* @gabest11 esphome/components/i2s_audio/* @jesserockz esphome/components/i2s_audio/media_player/* @jesserockz esphome/components/i2s_audio/microphone/* @jesserockz -esphome/components/i2s_audio/speaker/* @jesserockz +esphome/components/i2s_audio/speaker/* @jesserockz @kahrendt esphome/components/iaqcore/* @yozik04 esphome/components/ili9xxx/* @clydebarrow @nielsnl68 esphome/components/improv_base/* @esphome/core @@ -377,7 +377,7 @@ esphome/components/smt100/* @piechade esphome/components/sn74hc165/* @jesserockz esphome/components/socket/* @esphome/core esphome/components/sonoff_d1/* @anatoly-savchenkov -esphome/components/speaker/* @jesserockz +esphome/components/speaker/* @jesserockz @kahrendt esphome/components/spi/* @clydebarrow @esphome/core esphome/components/spi_device/* @clydebarrow esphome/components/spi_led_strip/* @clydebarrow diff --git a/esphome/components/i2s_audio/speaker/__init__.py b/esphome/components/i2s_audio/speaker/__init__.py index 9fdaced64c..dd43d6cb39 100644 --- a/esphome/components/i2s_audio/speaker/__init__.py +++ b/esphome/components/i2s_audio/speaker/__init__.py @@ -17,7 +17,7 @@ from .. import ( ) AUTO_LOAD = ["audio"] -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@kahrendt"] DEPENDENCIES = ["i2s_audio"] I2SAudioSpeaker = i2s_audio_ns.class_( diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 4fc489d0a3..cf6c3bbbba 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -32,6 +32,7 @@ enum SpeakerEventGroupBits : uint32_t { STATE_RUNNING = (1 << 11), STATE_STOPPING = (1 << 12), STATE_STOPPED = (1 << 13), + ERR_INVALID_FORMAT = (1 << 14), ERR_TASK_FAILED_TO_START = (1 << 15), ERR_ESP_INVALID_STATE = (1 << 16), ERR_ESP_INVALID_ARG = (1 << 17), @@ -104,16 +105,6 @@ void I2SAudioSpeaker::setup() { void I2SAudioSpeaker::loop() { uint32_t event_group_bits = xEventGroupGetBits(this->event_group_); - if (event_group_bits & SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START) { - this->status_set_error("Failed to start speaker task"); - } - - if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { - uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; - ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); - this->status_set_warning(); - } - if (event_group_bits & SpeakerEventGroupBits::STATE_STARTING) { ESP_LOGD(TAG, "Starting Speaker"); this->state_ = speaker::STATE_STARTING; @@ -139,12 +130,64 @@ void I2SAudioSpeaker::loop() { this->speaker_task_handle_ = nullptr; } } + + if (event_group_bits & SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START) { + this->status_set_error("Failed to start speaker task"); + xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); + } + + if (event_group_bits & SpeakerEventGroupBits::ERR_INVALID_FORMAT) { + this->status_set_error("Failed to adjust I2S bus to match the incoming audio"); + ESP_LOGE(TAG, + "Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8, + this->audio_stream_info_.sample_rate, this->audio_stream_info_.channels, + this->audio_stream_info_.bits_per_sample); + } + + if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { + uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; + ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); + this->status_set_warning(); + } } void I2SAudioSpeaker::set_volume(float volume) { this->volume_ = volume; - ssize_t decibel_index = remap<ssize_t, float>(volume, 0.0f, 1.0f, 0, Q15_VOLUME_SCALING_FACTORS.size() - 1); - this->q15_volume_factor_ = Q15_VOLUME_SCALING_FACTORS[decibel_index]; +#ifdef USE_AUDIO_DAC + if (this->audio_dac_ != nullptr) { + if (volume > 0.0) { + this->audio_dac_->set_mute_off(); + } + this->audio_dac_->set_volume(volume); + } else +#endif + { + // Fallback to software volume control by using a Q15 fixed point scaling factor + ssize_t decibel_index = remap<ssize_t, float>(volume, 0.0f, 1.0f, 0, Q15_VOLUME_SCALING_FACTORS.size() - 1); + this->q15_volume_factor_ = Q15_VOLUME_SCALING_FACTORS[decibel_index]; + } +} + +void I2SAudioSpeaker::set_mute_state(bool mute_state) { + this->mute_state_ = mute_state; +#ifdef USE_AUDIO_DAC + if (this->audio_dac_) { + if (mute_state) { + this->audio_dac_->set_mute_on(); + } else { + this->audio_dac_->set_mute_off(); + } + } else +#endif + { + if (mute_state) { + // Fallback to software volume control and scale by 0 + this->q15_volume_factor_ = 0; + } else { + // Revert to previous volume when unmuting + this->set_volume(this->volume_); + } + } } size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) { @@ -275,6 +318,9 @@ void I2SAudioSpeaker::speaker_task(void *params) { i2s_zero_dma_buffer(this_speaker->parent_->get_port()); } } + } else { + // Couldn't configure the I2S port to be compatible with the incoming audio + xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_INVALID_FORMAT); } i2s_zero_dma_buffer(this_speaker->parent_->get_port()); @@ -288,7 +334,7 @@ void I2SAudioSpeaker::speaker_task(void *params) { } void I2SAudioSpeaker::start() { - if (this->is_failed()) + if (this->is_failed() || this->status_has_error()) return; if ((this->state_ == speaker::STATE_STARTING) || (this->state_ == speaker::STATE_RUNNING)) return; diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index 245f97d1e7..3c512d4d4d 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -49,11 +49,17 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp bool has_buffered_data() const override; - /// @brief Sets the volume of the speaker. It is implemented as a software volume control. - /// Overrides the default setter to convert the floating point volume to a Q15 fixed-point factor. - /// @param volume + /// @brief Sets the volume of the speaker. Uses the speaker's configured audio dac component. If unavailble, it is + /// implemented as a software volume control. Overrides the default setter to convert the floating point volume to a + /// Q15 fixed-point factor. + /// @param volume between 0.0 and 1.0 void set_volume(float volume) override; - float get_volume() override { return this->volume_; } + + /// @brief Mutes or unmute the speaker. Uses the speaker's configured audio dac component. If unavailble, it is + /// implemented as a software volume control. Overrides the default setter to convert the floating point volume to a + /// Q15 fixed-point factor. + /// @param mute_state true for muting, false for unmuting + void set_mute_state(bool mute_state) override; protected: /// @brief Function for the FreeRTOS task handling audio output. diff --git a/esphome/components/speaker/__init__.py b/esphome/components/speaker/__init__.py index 1bbc0b02ef..7a668dc2f3 100644 --- a/esphome/components/speaker/__init__.py +++ b/esphome/components/speaker/__init__.py @@ -1,15 +1,18 @@ from esphome import automation from esphome.automation import maybe_simple_id import esphome.codegen as cg +from esphome.components import audio_dac import esphome.config_validation as cv from esphome.const import CONF_DATA, CONF_ID, CONF_VOLUME from esphome.core import CORE from esphome.coroutine import coroutine_with_priority -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@kahrendt"] IS_PLATFORM_COMPONENT = True +CONF_AUDIO_DAC = "audio_dac" + speaker_ns = cg.esphome_ns.namespace("speaker") Speaker = speaker_ns.class_("Speaker") @@ -26,6 +29,12 @@ FinishAction = speaker_ns.class_( VolumeSetAction = speaker_ns.class_( "VolumeSetAction", automation.Action, cg.Parented.template(Speaker) ) +MuteOnAction = speaker_ns.class_( + "MuteOnAction", automation.Action, cg.Parented.template(Speaker) +) +MuteOffAction = speaker_ns.class_( + "MuteOffAction", automation.Action, cg.Parented.template(Speaker) +) IsPlayingCondition = speaker_ns.class_("IsPlayingCondition", automation.Condition) @@ -33,7 +42,9 @@ IsStoppedCondition = speaker_ns.class_("IsStoppedCondition", automation.Conditio async def setup_speaker_core_(var, config): - pass + if audio_dac_config := config.get(CONF_AUDIO_DAC): + aud_dac = await cg.get_variable(audio_dac_config) + cg.add(var.set_audio_dac(aud_dac)) async def register_speaker(var, config): @@ -42,8 +53,11 @@ async def register_speaker(var, config): await setup_speaker_core_(var, config) -SPEAKER_SCHEMA = cv.Schema({}) - +SPEAKER_SCHEMA = cv.Schema( + { + cv.Optional(CONF_AUDIO_DAC): cv.use_id(audio_dac.AudioDac), + } +) SPEAKER_AUTOMATION_SCHEMA = maybe_simple_id({cv.GenerateID(): cv.use_id(Speaker)}) @@ -113,6 +127,15 @@ async def speaker_volume_set_action(config, action_id, template_arg, args): return var +@automation.register_action( + "speaker.mute_off", MuteOffAction, SPEAKER_AUTOMATION_SCHEMA +) +@automation.register_action("speaker.mute_on", MuteOnAction, SPEAKER_AUTOMATION_SCHEMA) +async def speaker_mute_action_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + @coroutine_with_priority(100.0) async def to_code(config): cg.add_global(speaker_ns.using) diff --git a/esphome/components/speaker/automation.h b/esphome/components/speaker/automation.h index 9efda011f2..c083796eea 100644 --- a/esphome/components/speaker/automation.h +++ b/esphome/components/speaker/automation.h @@ -39,6 +39,26 @@ template<typename... Ts> class VolumeSetAction : public Action<Ts...>, public Pa void play(Ts... x) override { this->parent_->set_volume(this->volume_.value(x...)); } }; +template<typename... Ts> class MuteOnAction : public Action<Ts...> { + public: + explicit MuteOnAction(Speaker *speaker) : speaker_(speaker) {} + + void play(Ts... x) override { this->speaker_->set_mute_state(true); } + + protected: + Speaker *speaker_; +}; + +template<typename... Ts> class MuteOffAction : public Action<Ts...> { + public: + explicit MuteOffAction(Speaker *speaker) : speaker_(speaker) {} + + void play(Ts... x) override { this->speaker_->set_mute_state(false); } + + protected: + Speaker *speaker_; +}; + template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<Speaker> { public: void play(Ts... x) override { this->parent_->stop(); } diff --git a/esphome/components/speaker/speaker.h b/esphome/components/speaker/speaker.h index 9390e4edb7..96843e2d5a 100644 --- a/esphome/components/speaker/speaker.h +++ b/esphome/components/speaker/speaker.h @@ -8,7 +8,12 @@ #include <freertos/FreeRTOS.h> #endif +#include "esphome/core/defines.h" + #include "esphome/components/audio/audio.h" +#ifdef USE_AUDIO_DAC +#include "esphome/components/audio_dac/audio_dac.h" +#endif namespace esphome { namespace speaker { @@ -56,9 +61,35 @@ class Speaker { bool is_running() const { return this->state_ == STATE_RUNNING; } bool is_stopped() const { return this->state_ == STATE_STOPPED; } - // Volume control must be implemented by each speaker component, otherwise it will have no effect. - virtual void set_volume(float volume) { this->volume_ = volume; }; - virtual float get_volume() { return this->volume_; } + // Volume control is handled by a configured audio dac component. Individual speaker components can + // override and implement in software if an audio dac isn't available. + virtual void set_volume(float volume) { + this->volume_ = volume; +#ifdef USE_AUDIO_DAC + if (this->audio_dac_ != nullptr) { + this->audio_dac_->set_volume(volume); + } +#endif + }; + float get_volume() { return this->volume_; } + + virtual void set_mute_state(bool mute_state) { + this->mute_state_ = mute_state; +#ifdef USE_AUDIO_DAC + if (this->audio_dac_) { + if (mute_state) { + this->audio_dac_->set_mute_on(); + } else { + this->audio_dac_->set_mute_off(); + } + } +#endif + } + bool get_mute_state() { return this->mute_state_; } + +#ifdef USE_AUDIO_DAC + void set_audio_dac(audio_dac::AudioDac *audio_dac) { this->audio_dac_ = audio_dac; } +#endif void set_audio_stream_info(const audio::AudioStreamInfo &audio_stream_info) { this->audio_stream_info_ = audio_stream_info; @@ -68,6 +99,11 @@ class Speaker { State state_{STATE_STOPPED}; audio::AudioStreamInfo audio_stream_info_; float volume_{1.0f}; + bool mute_state_{false}; + +#ifdef USE_AUDIO_DAC + audio_dac::AudioDac *audio_dac_{nullptr}; +#endif }; } // namespace speaker diff --git a/tests/components/speaker/test.esp32-ard.yaml b/tests/components/speaker/test.esp32-ard.yaml index 9a24d00f68..396b4d95ea 100644 --- a/tests/components/speaker/test.esp32-ard.yaml +++ b/tests/components/speaker/test.esp32-ard.yaml @@ -1,6 +1,8 @@ esphome: on_boot: then: + - speaker.mute_on: + - speaker.mute_off: - if: condition: speaker.is_stopped then: diff --git a/tests/components/speaker/test.esp32-c3-ard.yaml b/tests/components/speaker/test.esp32-c3-ard.yaml index f28014337c..636aeba766 100644 --- a/tests/components/speaker/test.esp32-c3-ard.yaml +++ b/tests/components/speaker/test.esp32-c3-ard.yaml @@ -1,6 +1,8 @@ esphome: on_boot: then: + - speaker.mute_on: + - speaker.mute_off: - if: condition: speaker.is_stopped then: diff --git a/tests/components/speaker/test.esp32-c3-idf.yaml b/tests/components/speaker/test.esp32-c3-idf.yaml index f28014337c..636aeba766 100644 --- a/tests/components/speaker/test.esp32-c3-idf.yaml +++ b/tests/components/speaker/test.esp32-c3-idf.yaml @@ -1,6 +1,8 @@ esphome: on_boot: then: + - speaker.mute_on: + - speaker.mute_off: - if: condition: speaker.is_stopped then: diff --git a/tests/components/speaker/test.esp32-idf.yaml b/tests/components/speaker/test.esp32-idf.yaml index 9a24d00f68..b69440b133 100644 --- a/tests/components/speaker/test.esp32-idf.yaml +++ b/tests/components/speaker/test.esp32-idf.yaml @@ -1,6 +1,8 @@ esphome: on_boot: then: + - speaker.mute_on: + - speaker.mute_off: - if: condition: speaker.is_stopped then: @@ -17,8 +19,17 @@ i2s_audio: i2s_bclk_pin: 17 i2s_mclk_pin: 15 +i2c: + scl: 12 + sda: 10 + +audio_dac: + - platform: aic3204 + id: internal_dac + speaker: - platform: i2s_audio - id: speaker_id + id: speaker_with_audio_dac_id + audio_dac: internal_dac dac_type: external - i2s_dout_pin: 13 + i2s_dout_pin: 14 From 9acc21e81a393a409236f29aeaf195cedb1ea472 Mon Sep 17 00:00:00 2001 From: tomaszduda23 <tomaszduda23@gmail.com> Date: Wed, 23 Oct 2024 23:04:59 +0200 Subject: [PATCH 034/282] unified way how all platforms handle copy_files (#7614) Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com> --- esphome/components/rp2040/__init__.py | 9 ++++++--- esphome/writer.py | 25 +++++++------------------ 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index f59962477f..d612631a4c 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -17,7 +17,7 @@ from esphome.const import ( PLATFORM_RP2040, ) from esphome.core import CORE, EsphomeError, coroutine_with_priority -from esphome.helpers import copy_file_if_changed, mkdir_p, write_file +from esphome.helpers import copy_file_if_changed, mkdir_p, write_file, read_file from .const import KEY_BOARD, KEY_PIO_FILES, KEY_RP2040, rp2040_ns @@ -230,11 +230,14 @@ def generate_pio_files() -> bool: # Called by writer.py -def copy_files() -> bool: +def copy_files(): dir = os.path.dirname(__file__) post_build_file = os.path.join(dir, "post_build.py.script") copy_file_if_changed( post_build_file, CORE.relative_build_path("post_build.py"), ) - return generate_pio_files() + if generate_pio_files(): + path = CORE.relative_src_path("esphome.h") + content = read_file(path).rstrip("\n") + write_file(path, content + '\n#include "pio_includes.h"\n') diff --git a/esphome/writer.py b/esphome/writer.py index 79ee72996c..90446ae4b1 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -1,3 +1,4 @@ +import importlib import logging import os from pathlib import Path @@ -299,25 +300,13 @@ def copy_src_tree(): CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h() ) - if CORE.is_esp32: - from esphome.components.esp32 import copy_files - + platform = "esphome.components." + CORE.target_platform + try: + module = importlib.import_module(platform) + copy_files = getattr(module, "copy_files") copy_files() - - elif CORE.is_esp8266: - from esphome.components.esp8266 import copy_files - - copy_files() - - elif CORE.is_rp2040: - from esphome.components.rp2040 import copy_files - - (pio) = copy_files() - if pio: - write_file_if_changed( - CORE.relative_src_path("esphome.h"), - ESPHOME_H_FORMAT.format(include_s + '\n#include "pio_includes.h"'), - ) + except AttributeError: + pass def generate_defines_h(): From 5b5c2fe71b8307aa692d3def1268837a1b26de51 Mon Sep 17 00:00:00 2001 From: Aaron Solochek <aarons@gmail.com> Date: Wed, 23 Oct 2024 17:25:53 -0700 Subject: [PATCH 035/282] updating ESP32 board definitions (#7650) --- esphome/components/esp32/boards.py | 200 ++++++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index 60abcd447c..02744ecb6f 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -103,6 +103,173 @@ ESP32_BOARD_PINS = { "LED": 13, "LED_BUILTIN": 13, }, + "adafruit_feather_esp32s3": { + "BUTTON": 0, + "A0": 18, + "A1": 17, + "A2": 16, + "A3": 15, + "A4": 14, + "A5": 8, + "SCK": 36, + "MOSI": 35, + "MISO": 37, + "RX": 38, + "TX": 39, + "SCL": 4, + "SDA": 3, + "NEOPIXEL": 33, + "PIN_NEOPIXEL": 33, + "NEOPIXEL_POWER": 21, + "I2C_POWER": 7, + "LED": 13, + "LED_BUILTIN": 13, + }, + "adafruit_feather_esp32s3_nopsram": { + "BUTTON": 0, + "A0": 18, + "A1": 17, + "A2": 16, + "A3": 15, + "A4": 14, + "A5": 8, + "SCK": 36, + "MOSI": 35, + "MISO": 37, + "RX": 38, + "TX": 39, + "SCL": 4, + "SDA": 3, + "NEOPIXEL": 33, + "PIN_NEOPIXEL": 33, + "NEOPIXEL_POWER": 21, + "I2C_POWER": 7, + "LED": 13, + "LED_BUILTIN": 13, + }, + "adafruit_feather_esp32s3_tft": { + "BUTTON": 0, + "A0": 18, + "A1": 17, + "A2": 16, + "A3": 15, + "A4": 14, + "A5": 8, + "SCK": 36, + "MOSI": 35, + "MISO": 37, + "RX": 2, + "TX": 1, + "SCL": 41, + "SDA": 42, + "NEOPIXEL": 33, + "PIN_NEOPIXEL": 33, + "NEOPIXEL_POWER": 34, + "TFT_I2C_POWER": 21, + "TFT_CS": 7, + "TFT_DC": 39, + "TFT_RESET": 40, + "TFT_BACKLIGHT": 45, + "LED": 13, + "LED_BUILTIN": 13, + }, + "adafruit_funhouse_esp32s2": { + "BUTTON_UP": 5, + "BUTTON_DOWN": 3, + "BUTTON_SELECT": 4, + "DOTSTAR_DATA": 14, + "DOTSTAR_CLOCK": 15, + "PIR_SENSE": 16, + "A0": 17, + "A1": 2, + "A2": 1, + "CAP6": 6, + "CAP7": 7, + "CAP8": 8, + "CAP9": 9, + "CAP10": 10, + "CAP11": 11, + "CAP12": 12, + "CAP13": 13, + "SPEAKER": 42, + "LED": 37, + "LIGHT": 18, + "TFT_MOSI": 35, + "TFT_SCK": 36, + "TFT_CS": 40, + "TFT_DC": 39, + "TFT_RESET": 41, + "TFT_BACKLIGHT": 21, + "RED_LED": 31, + "BUTTON": 0, + }, + "adafruit_itsybitsy_esp32": { + "A0": 25, + "A1": 26, + "A2": 4, + "A3": 38, + "A4": 37, + "A5": 36, + "SCK": 19, + "MOSI": 21, + "MISO": 22, + "SCL": 27, + "SDA": 15, + "TX": 20, + "RX": 8, + "NEOPIXEL": 0, + "PIN_NEOPIXEL": 0, + "NEOPIXEL_POWER": 2, + "BUTTON": 35, + }, + "adafruit_magtag29_esp32s2": { + "A1": 18, + "BUTTON_A": 15, + "BUTTON_B": 14, + "BUTTON_C": 12, + "BUTTON_D": 11, + "SDA": 33, + "SCL": 34, + "SPEAKER": 17, + "SPEAKER_ENABLE": 16, + "VOLTAGE_MONITOR": 4, + "ACCELEROMETER_INT": 9, + "ACCELEROMETER_INTERRUPT": 9, + "LIGHT": 3, + "NEOPIXEL": 1, + "PIN_NEOPIXEL": 1, + "NEOPIXEL_POWER": 21, + "EPD_BUSY": 5, + "EPD_RESET": 6, + "EPD_DC": 7, + "EPD_CS": 8, + "EPD_MOSI": 35, + "EPD_SCK": 36, + "EPD_MISO": 37, + "BUTTON": 0, + "LED": 13, + "LED_BUILTIN": 13, + }, + "adafruit_metro_esp32s2": { + "A0": 17, + "A1": 18, + "A2": 1, + "A3": 2, + "A4": 3, + "A5": 4, + "RX": 38, + "TX": 37, + "SCL": 34, + "SDA": 33, + "MISO": 37, + "SCK": 36, + "MOSI": 35, + "NEOPIXEL": 45, + "PIN_NEOPIXEL": 45, + "LED": 42, + "LED_BUILTIN": 42, + "BUTTON": 0, + }, "adafruit_qtpy_esp32c3": { "A0": 4, "A1": 3, @@ -141,6 +308,26 @@ ESP32_BOARD_PINS = { "BUTTON": 0, "SWITCH": 0, }, + "adafruit_qtpy_esp32s3_nopsram": { + "A0": 18, + "A1": 17, + "A2": 9, + "A3": 8, + "SDA": 7, + "SCL": 6, + "MOSI": 35, + "MISO": 37, + "SCK": 36, + "RX": 16, + "TX": 5, + "SDA1": 41, + "SCL1": 40, + "NEOPIXEL": 39, + "PIN_NEOPIXEL": 39, + "NEOPIXEL_POWER": 38, + "BUTTON": 0, + "SWITCH": 0, + }, "adafruit_qtpy_esp32": { "A0": 26, "A1": 25, @@ -1068,7 +1255,18 @@ ESP32_BOARD_PINS = { "_VBAT": 35, }, "wemosbat": {"LED": 16}, - "wesp32": {"MISO": 32, "SCL": 4, "SDA": 15}, + "wesp32": { + "MISO": 32, + "MOSI": 23, + "SCK": 18, + "SCL": 4, + "SDA": 15, + "MISO1": 12, + "MOSI1": 13, + "SCK1": 14, + "SCL1": 5, + "SDA1": 33, + }, "widora-air": { "A1": 39, "A2": 35, From ca5c73d170c57a256688f51c09b38dac70b1fe6e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 25 Oct 2024 07:55:14 +1300 Subject: [PATCH 036/282] Support ignoring discovered devices from the dashboard (#7665) --- esphome/dashboard/core.py | 25 ++++++++++++++++++++ esphome/dashboard/web_server.py | 42 +++++++++++++++++++++++++++++++++ esphome/storage_json.py | 4 ++++ 3 files changed, 71 insertions(+) diff --git a/esphome/dashboard/core.py b/esphome/dashboard/core.py index eec0777da6..563ca1506d 100644 --- a/esphome/dashboard/core.py +++ b/esphome/dashboard/core.py @@ -5,10 +5,14 @@ from collections.abc import Coroutine import contextlib from dataclasses import dataclass from functools import partial +import json import logging +from pathlib import Path import threading from typing import TYPE_CHECKING, Any, Callable +from esphome.storage_json import ignored_devices_storage_path + from ..zeroconf import DiscoveredImport from .dns import DNSCache from .entries import DashboardEntries @@ -20,6 +24,8 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) +IGNORED_DEVICES_STORAGE_PATH = "ignored-devices.json" + @dataclass class Event: @@ -74,6 +80,7 @@ class ESPHomeDashboard: "settings", "dns_cache", "_background_tasks", + "ignored_devices", ) def __init__(self) -> None: @@ -89,12 +96,30 @@ class ESPHomeDashboard: self.settings = DashboardSettings() self.dns_cache = DNSCache() self._background_tasks: set[asyncio.Task] = set() + self.ignored_devices: set[str] = set() async def async_setup(self) -> None: """Setup the dashboard.""" self.loop = asyncio.get_running_loop() self.ping_request = asyncio.Event() self.entries = DashboardEntries(self) + self.load_ignored_devices() + + def load_ignored_devices(self) -> None: + storage_path = Path(ignored_devices_storage_path()) + try: + with storage_path.open("r", encoding="utf-8") as f_handle: + data = json.load(f_handle) + self.ignored_devices = set(data.get("ignored_devices", set())) + except FileNotFoundError: + pass + + def save_ignored_devices(self) -> None: + storage_path = Path(ignored_devices_storage_path()) + with storage_path.open("w", encoding="utf-8") as f_handle: + json.dump( + {"ignored_devices": sorted(self.ignored_devices)}, indent=2, fp=f_handle + ) async def async_run(self) -> None: """Run the dashboard.""" diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index e4b7b8d342..1a8b2ab83a 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -541,6 +541,46 @@ class ImportRequestHandler(BaseHandler): self.finish() +class IgnoreDeviceRequestHandler(BaseHandler): + @authenticated + def post(self) -> None: + dashboard = DASHBOARD + try: + args = json.loads(self.request.body.decode()) + device_name = args["name"] + ignore = args["ignore"] + except (json.JSONDecodeError, KeyError): + self.set_status(400) + self.set_header("content-type", "application/json") + self.write(json.dumps({"error": "Invalid payload"})) + return + + ignored_device = next( + ( + res + for res in dashboard.import_result.values() + if res.device_name == device_name + ), + None, + ) + + if ignored_device is None: + self.set_status(404) + self.set_header("content-type", "application/json") + self.write(json.dumps({"error": "Device not found"})) + return + + if ignore: + dashboard.ignored_devices.add(ignored_device.device_name) + else: + dashboard.ignored_devices.discard(ignored_device.device_name) + + dashboard.save_ignored_devices() + + self.set_status(204) + self.finish() + + class DownloadListRequestHandler(BaseHandler): @authenticated @bind_config @@ -688,6 +728,7 @@ class ListDevicesHandler(BaseHandler): "project_name": res.project_name, "project_version": res.project_version, "network": res.network, + "ignored": res.device_name in dashboard.ignored_devices, } for res in dashboard.import_result.values() if res.device_name not in configured @@ -1156,6 +1197,7 @@ def make_app(debug=get_bool_env(ENV_DEV)) -> tornado.web.Application: (f"{rel}prometheus-sd", PrometheusServiceDiscoveryHandler), (f"{rel}boards/([a-z0-9]+)", BoardsRequestHandler), (f"{rel}version", EsphomeVersionHandler), + (f"{rel}ignore-device", IgnoreDeviceRequestHandler), ], **app_settings, ) diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 2d12ee01a0..97cf9ceadd 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -28,6 +28,10 @@ def esphome_storage_path() -> str: return os.path.join(CORE.data_dir, "esphome.json") +def ignored_devices_storage_path() -> str: + return os.path.join(CORE.data_dir, "ignored-devices.json") + + def trash_storage_path() -> str: return CORE.relative_config_path("trash") From 4fa3c6915ca7aaba27d001415aee5b613db33565 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 25 Oct 2024 08:10:30 +1300 Subject: [PATCH 037/282] Bump esphome-dashboard to 20241025.0 (#7669) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c03a9f181a..8cc26e4da0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ pyserial==3.5 platformio==6.1.16 # When updating platformio, also update Dockerfile esptool==4.7.0 click==8.1.7 -esphome-dashboard==20240620.0 +esphome-dashboard==20241025.0 aioesphomeapi==24.6.2 zeroconf==0.132.2 puremagic==1.27 From c20e1975d1acd9c99fefd1a1da7e446a2e403bc7 Mon Sep 17 00:00:00 2001 From: tomaszduda23 <tomaszduda23@gmail.com> Date: Thu, 24 Oct 2024 23:25:19 +0200 Subject: [PATCH 038/282] unified way how all platforms handle get_download_types (#7617) Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com> --- esphome/dashboard/web_server.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 1a8b2ab83a..9aeece9aab 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -7,6 +7,7 @@ import datetime import functools import gzip import hashlib +import importlib import json import logging import os @@ -595,26 +596,18 @@ class DownloadListRequestHandler(BaseHandler): downloads = [] platform: str = storage_json.target_platform.lower() - if platform == const.PLATFORM_RP2040: - from esphome.components.rp2040 import get_download_types as rp2040_types - downloads = rp2040_types(storage_json) - elif platform == const.PLATFORM_ESP8266: - from esphome.components.esp8266 import get_download_types as esp8266_types - - downloads = esp8266_types(storage_json) - elif platform.upper() in ESP32_VARIANTS: - from esphome.components.esp32 import get_download_types as esp32_types - - downloads = esp32_types(storage_json) + if platform.upper() in ESP32_VARIANTS: + platform = "esp32" elif platform in (const.PLATFORM_RTL87XX, const.PLATFORM_BK72XX): - from esphome.components.libretiny import ( - get_download_types as libretiny_types, - ) + platform = "libretiny" - downloads = libretiny_types(storage_json) - else: - raise ValueError(f"Unknown platform {platform}") + try: + module = importlib.import_module(f"esphome.components.{platform}") + get_download_types = getattr(module, "get_download_types") + except AttributeError as exc: + raise ValueError(f"Unknown platform {platform}") from exc + downloads = get_download_types(storage_json) self.set_status(200) self.set_header("content-type", "application/json") From 4101d5dad15a5f3162ada989a536bf2cb6da0307 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt <kevin.ahrendt@nabucasa.com> Date: Thu, 24 Oct 2024 17:26:39 -0400 Subject: [PATCH 039/282] [media_player] Add new media player conditions (#7667) --- esphome/components/media_player/__init__.py | 11 +++++++++++ esphome/components/media_player/automation.h | 10 ++++++++++ esphome/components/media_player/media_player.cpp | 4 ++++ tests/components/media_player/common.yaml | 4 ++++ 4 files changed, 29 insertions(+) diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index 423cb065dc..a46b30db29 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -21,6 +21,7 @@ media_player_ns = cg.esphome_ns.namespace("media_player") MediaPlayer = media_player_ns.class_("MediaPlayer") + PlayAction = media_player_ns.class_( "PlayAction", automation.Action, cg.Parented.template(MediaPlayer) ) @@ -60,7 +61,11 @@ AnnoucementTrigger = media_player_ns.class_( "AnnouncementTrigger", automation.Trigger.template() ) IsIdleCondition = media_player_ns.class_("IsIdleCondition", automation.Condition) +IsPausedCondition = media_player_ns.class_("IsPausedCondition", automation.Condition) IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition) +IsAnnouncingCondition = media_player_ns.class_( + "IsAnnouncingCondition", automation.Condition +) async def setup_media_player_core_(var, config): @@ -159,9 +164,15 @@ async def media_player_play_media_action(config, action_id, template_arg, args): @automation.register_condition( "media_player.is_idle", IsIdleCondition, MEDIA_PLAYER_ACTION_SCHEMA ) +@automation.register_condition( + "media_player.is_paused", IsPausedCondition, MEDIA_PLAYER_ACTION_SCHEMA +) @automation.register_condition( "media_player.is_playing", IsPlayingCondition, MEDIA_PLAYER_ACTION_SCHEMA ) +@automation.register_condition( + "media_player.is_announcing", IsAnnouncingCondition, MEDIA_PLAYER_ACTION_SCHEMA +) async def media_player_action(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/media_player/automation.h b/esphome/components/media_player/automation.h index f0e0a5dd31..7b9220c4a5 100644 --- a/esphome/components/media_player/automation.h +++ b/esphome/components/media_player/automation.h @@ -68,5 +68,15 @@ template<typename... Ts> class IsPlayingCondition : public Condition<Ts...>, pub bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING; } }; +template<typename... Ts> class IsPausedCondition : public Condition<Ts...>, public Parented<MediaPlayer> { + public: + bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED; } +}; + +template<typename... Ts> class IsAnnouncingCondition : public Condition<Ts...>, public Parented<MediaPlayer> { + public: + bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING; } +}; + } // namespace media_player } // namespace esphome diff --git a/esphome/components/media_player/media_player.cpp b/esphome/components/media_player/media_player.cpp index 586345ac9f..b5190d8573 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -37,6 +37,10 @@ const char *media_player_command_to_string(MediaPlayerCommand command) { return "UNMUTE"; case MEDIA_PLAYER_COMMAND_TOGGLE: return "TOGGLE"; + case MEDIA_PLAYER_COMMAND_VOLUME_UP: + return "VOLUME_UP"; + case MEDIA_PLAYER_COMMAND_VOLUME_DOWN: + return "VOLUME_DOWN"; default: return "UNKNOWN"; } diff --git a/tests/components/media_player/common.yaml b/tests/components/media_player/common.yaml index 24b85cd474..af0d5c7765 100644 --- a/tests/components/media_player/common.yaml +++ b/tests/components/media_player/common.yaml @@ -27,6 +27,10 @@ media_player: 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 7dbda12008830ba576c2e6cddca87fab112999c6 Mon Sep 17 00:00:00 2001 From: tomaszduda23 <tomaszduda23@gmail.com> Date: Thu, 24 Oct 2024 23:55:58 +0200 Subject: [PATCH 040/282] [code-quality] weikai.h (#7601) --- esphome/components/weikai/weikai.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/weikai/weikai.h b/esphome/components/weikai/weikai.h index 042c729162..175a067b27 100644 --- a/esphome/components/weikai/weikai.h +++ b/esphome/components/weikai/weikai.h @@ -209,7 +209,7 @@ class WeikaiComponent : public Component { /// @brief store the name for the component /// @param name the name as defined by the python code generator - void set_name(std::string name) { this->name_ = std::move(name); } + void set_name(std::string &&name) { this->name_ = std::move(name); } /// @brief Get the name of the component /// @return the name @@ -308,7 +308,7 @@ class WeikaiChannel : public uart::UARTComponent { /// @brief The name as generated by the Python code generator /// @param name of the channel - void set_channel_name(std::string name) { this->name_ = std::move(name); } + void set_channel_name(std::string &&name) { this->name_ = std::move(name); } /// @brief Get the channel name /// @return the name From 34a8eaddb227738ed1d22d43025a9af56ad5d4a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:56:48 +1300 Subject: [PATCH 041/282] Bump actions/setup-python from 5.2.0 to 5.3.0 in /.github/actions/restore-python (#7671) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/restore-python/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index c6978f68c5..06c54578f5 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -17,7 +17,7 @@ runs: steps: - name: Set up Python ${{ inputs.python-version }} id: python - uses: actions/setup-python@v5.2.0 + uses: actions/setup-python@v5.3.0 with: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment From 09f9d915773c9ad2d15d60a60f6886f9da553da5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:57:09 +1300 Subject: [PATCH 042/282] Bump actions/setup-python from 5.2.0 to 5.3.0 (#7670) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .github/workflows/ci-api-proto.yml | 2 +- .github/workflows/ci-docker.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/sync-device-classes.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml index 8112c4e0ff..a6b2e2b2b3 100644 --- a/.github/workflows/ci-api-proto.yml +++ b/.github/workflows/ci-api-proto.yml @@ -23,7 +23,7 @@ jobs: - name: Checkout uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.2.0 + uses: actions/setup-python@v5.3.0 with: python-version: "3.11" diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index f003e5d24c..435a58498e 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -42,7 +42,7 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.2.0 + uses: actions/setup-python@v5.3.0 with: python-version: "3.9" - name: Set up Docker Buildx diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d2f1c877d..82a55d0e2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.2.0 + uses: actions/setup-python@v5.3.0 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 26a213f170..5072bec222 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.2.0 + uses: actions/setup-python@v5.3.0 with: python-version: "3.x" - name: Set up python environment @@ -85,7 +85,7 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.2.0 + uses: actions/setup-python@v5.3.0 with: python-version: "3.9" diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index c066ae9fb4..7a46d596a1 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -22,7 +22,7 @@ jobs: path: lib/home-assistant - name: Setup Python - uses: actions/setup-python@v5.2.0 + uses: actions/setup-python@v5.3.0 with: python-version: 3.12 From 33fdbbe30c4d9643300483bf2dfd85d992e2cf86 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:05:25 +1100 Subject: [PATCH 043/282] [image][online_image][animation] Fix transparency in RGB565 (#7631) --- esphome/components/animation/__init__.py | 13 +++--- esphome/components/animation/animation.cpp | 2 +- esphome/components/image/__init__.py | 13 +++--- esphome/components/image/image.cpp | 28 ++++++------- esphome/components/image/image.h | 37 ++++++++--------- .../components/online_image/online_image.cpp | 10 +---- .../components/online_image/online_image.h | 8 +--- tests/components/image/common.yaml | 38 ++++++++++++++++++ tests/components/image/test.esp32-ard.yaml | 40 +------------------ tests/components/image/test.esp32-c3-ard.yaml | 39 +----------------- tests/components/image/test.esp32-c3-idf.yaml | 39 +----------------- tests/components/image/test.esp32-idf.yaml | 39 +----------------- tests/components/image/test.esp8266-ard.yaml | 39 +----------------- tests/components/image/test.host.yaml | 8 ++++ tests/components/image/test.rp2040-ard.yaml | 39 +----------------- 15 files changed, 101 insertions(+), 291 deletions(-) create mode 100644 tests/components/image/common.yaml create mode 100644 tests/components/image/test.host.yaml diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index eb3d09ac96..5a308855de 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -271,7 +271,8 @@ async def to_code(config): pos += 1 elif config[CONF_TYPE] in ["RGB565", "TRANSPARENT_IMAGE"]: - data = [0 for _ in range(height * width * 2 * frames)] + bytes_per_pixel = 3 if transparent else 2 + data = [0 for _ in range(height * width * bytes_per_pixel * frames)] pos = 0 for frameIndex in range(frames): image.seek(frameIndex) @@ -288,17 +289,13 @@ async def to_code(config): G = g >> 2 B = b >> 3 rgb = (R << 11) | (G << 5) | B - - if transparent: - if rgb == 0x0020: - rgb = 0 - if a < 0x80: - rgb = 0x0020 - data[pos] = rgb >> 8 pos += 1 data[pos] = rgb & 0xFF pos += 1 + if transparent: + data[pos] = a + pos += 1 elif config[CONF_TYPE] in ["BINARY", "TRANSPARENT_BINARY"]: width8 = ((width + 7) // 8) * 8 diff --git a/esphome/components/animation/animation.cpp b/esphome/components/animation/animation.cpp index 7e0efa97e0..1375dfe07e 100644 --- a/esphome/components/animation/animation.cpp +++ b/esphome/components/animation/animation.cpp @@ -62,7 +62,7 @@ void Animation::set_frame(int frame) { } void Animation::update_data_start_() { - const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_; + const uint32_t image_size = this->get_width_stride() * this->height_; this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; } diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index c72417bcda..8742540067 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -361,24 +361,21 @@ async def to_code(config): elif config[CONF_TYPE] in ["RGB565"]: image = image.convert("RGBA") pixels = list(image.getdata()) - data = [0 for _ in range(height * width * 2)] + bytes_per_pixel = 3 if transparent else 2 + data = [0 for _ in range(height * width * bytes_per_pixel)] pos = 0 for r, g, b, a in pixels: R = r >> 3 G = g >> 2 B = b >> 3 rgb = (R << 11) | (G << 5) | B - - if transparent: - if rgb == 0x0020: - rgb = 0 - if a < 0x80: - rgb = 0x0020 - data[pos] = rgb >> 8 pos += 1 data[pos] = rgb & 0xFF pos += 1 + if transparent: + data[pos] = a + pos += 1 elif config[CONF_TYPE] in ["BINARY", "TRANSPARENT_BINARY"]: if transparent: diff --git a/esphome/components/image/image.cpp b/esphome/components/image/image.cpp index ded4c60d25..ca2f659fb0 100644 --- a/esphome/components/image/image.cpp +++ b/esphome/components/image/image.cpp @@ -88,7 +88,7 @@ lv_img_dsc_t *Image::get_lv_img_dsc() { this->dsc_.header.reserved = 0; this->dsc_.header.w = this->width_; this->dsc_.header.h = this->height_; - this->dsc_.data_size = image_type_to_width_stride(this->dsc_.header.w * this->dsc_.header.h, this->get_type()); + this->dsc_.data_size = this->get_width_stride() * this->get_height(); switch (this->get_type()) { case IMAGE_TYPE_BINARY: this->dsc_.header.cf = LV_IMG_CF_ALPHA_1BIT; @@ -104,17 +104,17 @@ lv_img_dsc_t *Image::get_lv_img_dsc() { case IMAGE_TYPE_RGB565: #if LV_COLOR_DEPTH == 16 - this->dsc_.header.cf = this->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR; + this->dsc_.header.cf = this->has_transparency() ? LV_IMG_CF_TRUE_COLOR_ALPHA : LV_IMG_CF_TRUE_COLOR; #else this->dsc_.header.cf = LV_IMG_CF_RGB565; #endif break; - case image::IMAGE_TYPE_RGBA: + case IMAGE_TYPE_RGBA: #if LV_COLOR_DEPTH == 32 this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR; #else - this->dsc_.header.cf = LV_IMG_CF_RGBA8888; + this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA; #endif break; } @@ -147,21 +147,21 @@ Color Image::get_rgb24_pixel_(int x, int y) const { return color; } Color Image::get_rgb565_pixel_(int x, int y) const { - const uint32_t pos = (x + y * this->width_) * 2; - uint16_t rgb565 = - progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1); + const uint8_t *pos = this->data_start_; + if (this->transparent_) { + pos += (x + y * this->width_) * 3; + } else { + pos += (x + y * this->width_) * 2; + } + uint16_t rgb565 = encode_uint16(progmem_read_byte(pos), progmem_read_byte(pos + 1)); auto r = (rgb565 & 0xF800) >> 11; auto g = (rgb565 & 0x07E0) >> 5; auto b = rgb565 & 0x001F; - Color color = Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2)); - if (rgb565 == 0x0020 && transparent_) { - // darkest green has been defined as transparent color for transparent RGB565 images. - color.w = 0; - } else { - color.w = 0xFF; - } + auto a = this->transparent_ ? progmem_read_byte(pos + 2) : 0xFF; + Color color = Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2), a); return color; } + 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); diff --git a/esphome/components/image/image.h b/esphome/components/image/image.h index a8a8aab2c2..40370d18da 100644 --- a/esphome/components/image/image.h +++ b/esphome/components/image/image.h @@ -17,24 +17,6 @@ enum ImageType { IMAGE_TYPE_RGBA = 4, }; -inline int image_type_to_bpp(ImageType type) { - switch (type) { - case IMAGE_TYPE_BINARY: - return 1; - case IMAGE_TYPE_GRAYSCALE: - return 8; - case IMAGE_TYPE_RGB565: - return 16; - case IMAGE_TYPE_RGB24: - return 24; - case IMAGE_TYPE_RGBA: - return 32; - } - return 0; -} - -inline int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; } - class Image : public display::BaseImage { public: Image(const uint8_t *data_start, int width, int height, ImageType type); @@ -44,6 +26,25 @@ class Image : public display::BaseImage { const uint8_t *get_data_start() const { return this->data_start_; } ImageType get_type() const; + int get_bpp() const { + switch (this->type_) { + case IMAGE_TYPE_BINARY: + return 1; + case IMAGE_TYPE_GRAYSCALE: + return 8; + case IMAGE_TYPE_RGB565: + return this->transparent_ ? 24 : 16; + case IMAGE_TYPE_RGB24: + return 24; + case IMAGE_TYPE_RGBA: + return 32; + } + return 0; + } + + /// Return the stride of the image in bytes, that is, the distance in bytes + /// between two consecutive rows of pixels. + uint32_t get_width_stride() const { return (this->width_ * this->get_bpp() + 7u) / 8u; } void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override; void set_transparency(bool transparent) { transparent_ = transparent; } diff --git a/esphome/components/online_image/online_image.cpp b/esphome/components/online_image/online_image.cpp index 480bad6aca..1786809dfa 100644 --- a/esphome/components/online_image/online_image.cpp +++ b/esphome/components/online_image/online_image.cpp @@ -215,16 +215,10 @@ void OnlineImage::draw_pixel_(int x, int y, Color color) { } case ImageType::IMAGE_TYPE_RGB565: { uint16_t col565 = display::ColorUtil::color_to_565(color); - if (this->has_transparency()) { - if (col565 == 0x0020) { - col565 = 0; - } - if (color.w < 0x80) { - col565 = 0x0020; - } - } this->buffer_[pos + 0] = static_cast<uint8_t>((col565 >> 8) & 0xFF); this->buffer_[pos + 1] = static_cast<uint8_t>(col565 & 0xFF); + if (this->has_transparency()) + this->buffer_[pos + 2] = color.w; break; } case ImageType::IMAGE_TYPE_RGBA: { diff --git a/esphome/components/online_image/online_image.h b/esphome/components/online_image/online_image.h index 51c11478cd..017402a088 100644 --- a/esphome/components/online_image/online_image.h +++ b/esphome/components/online_image/online_image.h @@ -86,13 +86,9 @@ class OnlineImage : public PollingComponent, Allocator allocator_{Allocator::Flags::ALLOW_FAILURE}; 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 std::ceil(image::image_type_to_bpp(this->type_) * width * height / 8.0); - } + int get_buffer_size_(int width, int height) const { return (this->get_bpp() * width + 7u) / 8u * height; } - int get_position_(int x, int y) const { - return ((x + y * this->buffer_width_) * image::image_type_to_bpp(this->type_)) / 8; - } + int get_position_(int x, int y) const { return (x + y * this->buffer_width_) * this->get_bpp() / 8; } ESPHOME_ALWAYS_INLINE bool auto_resize_() const { return this->fixed_width_ == 0 || this->fixed_height_ == 0; } diff --git a/tests/components/image/common.yaml b/tests/components/image/common.yaml new file mode 100644 index 0000000000..313da6bc0b --- /dev/null +++ b/tests/components/image/common.yaml @@ -0,0 +1,38 @@ +image: + - id: binary_image + file: ../../pnglogo.png + type: BINARY + dither: FloydSteinberg + - id: transparent_transparent_image + file: ../../pnglogo.png + type: TRANSPARENT_BINARY + - id: rgba_image + file: ../../pnglogo.png + type: RGBA + resize: 50x50 + - id: rgb24_image + file: ../../pnglogo.png + type: RGB24 + use_transparency: yes + - id: rgb565_image + file: ../../pnglogo.png + type: RGB565 + use_transparency: no + - id: web_svg_image + file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg + resize: 256x48 + type: TRANSPARENT_BINARY + - id: web_tiff_image + file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff + type: RGB24 + resize: 48x48 + - id: web_redirect_image + file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 + type: RGB24 + resize: 48x48 + - id: mdi_alert + file: mdi:alert-circle-outline + resize: 50x50 + - id: another_alert_icon + file: mdi:alert-outline + type: BINARY diff --git a/tests/components/image/test.esp32-ard.yaml b/tests/components/image/test.esp32-ard.yaml index 9dd44d177f..818e720221 100644 --- a/tests/components/image/test.esp32-ard.yaml +++ b/tests/components/image/test.esp32-ard.yaml @@ -13,41 +13,5 @@ display: reset_pin: 21 invert_colors: true -image: - - id: binary_image - file: ../../pnglogo.png - type: BINARY - dither: FloydSteinberg - - id: transparent_transparent_image - file: ../../pnglogo.png - type: TRANSPARENT_BINARY - - id: rgba_image - file: ../../pnglogo.png - type: RGBA - resize: 50x50 - - id: rgb24_image - file: ../../pnglogo.png - type: RGB24 - use_transparency: yes - - id: rgb565_image - file: ../../pnglogo.png - type: RGB565 - use_transparency: no - - id: web_svg_image - file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg - resize: 256x48 - type: TRANSPARENT_BINARY - - id: web_tiff_image - file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff - type: RGB24 - resize: 48x48 - - id: web_redirect_image - file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 - type: RGB24 - resize: 48x48 - - id: mdi_alert - file: mdi:alert-circle-outline - resize: 50x50 - - id: another_alert_icon - file: mdi:alert-outline - type: BINARY +<<: !include common.yaml + diff --git a/tests/components/image/test.esp32-c3-ard.yaml b/tests/components/image/test.esp32-c3-ard.yaml index c0b2779773..4dae9cd5ec 100644 --- a/tests/components/image/test.esp32-c3-ard.yaml +++ b/tests/components/image/test.esp32-c3-ard.yaml @@ -13,41 +13,4 @@ display: reset_pin: 10 invert_colors: true -image: - - id: binary_image - file: ../../pnglogo.png - type: BINARY - dither: FloydSteinberg - - id: transparent_transparent_image - file: ../../pnglogo.png - type: TRANSPARENT_BINARY - - id: rgba_image - file: ../../pnglogo.png - type: RGBA - resize: 50x50 - - id: rgb24_image - file: ../../pnglogo.png - type: RGB24 - use_transparency: yes - - id: rgb565_image - file: ../../pnglogo.png - type: RGB565 - use_transparency: no - - id: web_svg_image - file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg - resize: 256x48 - type: TRANSPARENT_BINARY - - id: web_tiff_image - file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff - type: RGB24 - resize: 48x48 - - id: web_redirect_image - file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 - type: RGB24 - resize: 48x48 - - id: mdi_alert - file: mdi:alert-circle-outline - resize: 50x50 - - id: another_alert_icon - file: mdi:alert-outline - type: BINARY +<<: !include common.yaml diff --git a/tests/components/image/test.esp32-c3-idf.yaml b/tests/components/image/test.esp32-c3-idf.yaml index c0b2779773..4dae9cd5ec 100644 --- a/tests/components/image/test.esp32-c3-idf.yaml +++ b/tests/components/image/test.esp32-c3-idf.yaml @@ -13,41 +13,4 @@ display: reset_pin: 10 invert_colors: true -image: - - id: binary_image - file: ../../pnglogo.png - type: BINARY - dither: FloydSteinberg - - id: transparent_transparent_image - file: ../../pnglogo.png - type: TRANSPARENT_BINARY - - id: rgba_image - file: ../../pnglogo.png - type: RGBA - resize: 50x50 - - id: rgb24_image - file: ../../pnglogo.png - type: RGB24 - use_transparency: yes - - id: rgb565_image - file: ../../pnglogo.png - type: RGB565 - use_transparency: no - - id: web_svg_image - file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg - resize: 256x48 - type: TRANSPARENT_BINARY - - id: web_tiff_image - file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff - type: RGB24 - resize: 48x48 - - id: web_redirect_image - file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 - type: RGB24 - resize: 48x48 - - id: mdi_alert - file: mdi:alert-circle-outline - resize: 50x50 - - id: another_alert_icon - file: mdi:alert-outline - type: BINARY +<<: !include common.yaml diff --git a/tests/components/image/test.esp32-idf.yaml b/tests/components/image/test.esp32-idf.yaml index e903afea1f..814f16c36c 100644 --- a/tests/components/image/test.esp32-idf.yaml +++ b/tests/components/image/test.esp32-idf.yaml @@ -13,41 +13,4 @@ display: reset_pin: 21 invert_colors: true -image: - - id: binary_image - file: ../../pnglogo.png - type: BINARY - dither: FloydSteinberg - - id: transparent_transparent_image - file: ../../pnglogo.png - type: TRANSPARENT_BINARY - - id: rgba_image - file: ../../pnglogo.png - type: RGBA - resize: 50x50 - - id: rgb24_image - file: ../../pnglogo.png - type: RGB24 - use_transparency: yes - - id: rgb565_image - file: ../../pnglogo.png - type: RGB565 - use_transparency: no - - id: web_svg_image - file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg - resize: 256x48 - type: TRANSPARENT_BINARY - - id: web_tiff_image - file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff - type: RGB24 - resize: 48x48 - - id: web_redirect_image - file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 - type: RGB24 - resize: 48x48 - - id: mdi_alert - file: mdi:alert-circle-outline - resize: 50x50 - - id: another_alert_icon - file: mdi:alert-outline - type: BINARY +<<: !include common.yaml diff --git a/tests/components/image/test.esp8266-ard.yaml b/tests/components/image/test.esp8266-ard.yaml index 5a96ed9497..f963022ff4 100644 --- a/tests/components/image/test.esp8266-ard.yaml +++ b/tests/components/image/test.esp8266-ard.yaml @@ -13,41 +13,4 @@ display: reset_pin: 16 invert_colors: true -image: - - id: binary_image - file: ../../pnglogo.png - type: BINARY - dither: FloydSteinberg - - id: transparent_transparent_image - file: ../../pnglogo.png - type: TRANSPARENT_BINARY - - id: rgba_image - file: ../../pnglogo.png - type: RGBA - resize: 50x50 - - id: rgb24_image - file: ../../pnglogo.png - type: RGB24 - use_transparency: yes - - id: rgb565_image - file: ../../pnglogo.png - type: RGB565 - use_transparency: no - - id: web_svg_image - file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg - resize: 256x48 - type: TRANSPARENT_BINARY - - id: web_tiff_image - file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff - type: RGB24 - resize: 48x48 - - id: web_redirect_image - file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 - type: RGB24 - resize: 48x48 - - id: mdi_alert - file: mdi:alert-circle-outline - resize: 50x50 - - id: another_alert_icon - file: mdi:alert-outline - type: BINARY +<<: !include common.yaml diff --git a/tests/components/image/test.host.yaml b/tests/components/image/test.host.yaml new file mode 100644 index 0000000000..29509db66c --- /dev/null +++ b/tests/components/image/test.host.yaml @@ -0,0 +1,8 @@ +display: + - platform: sdl + auto_clear_enabled: false + dimensions: + width: 480 + height: 480 + +<<: !include common.yaml diff --git a/tests/components/image/test.rp2040-ard.yaml b/tests/components/image/test.rp2040-ard.yaml index 4c40ca464f..5167c99a7d 100644 --- a/tests/components/image/test.rp2040-ard.yaml +++ b/tests/components/image/test.rp2040-ard.yaml @@ -13,41 +13,4 @@ display: reset_pin: 22 invert_colors: true -image: - - id: binary_image - file: ../../pnglogo.png - type: BINARY - dither: FloydSteinberg - - id: transparent_transparent_image - file: ../../pnglogo.png - type: TRANSPARENT_BINARY - - id: rgba_image - file: ../../pnglogo.png - type: RGBA - resize: 50x50 - - id: rgb24_image - file: ../../pnglogo.png - type: RGB24 - use_transparency: yes - - id: rgb565_image - file: ../../pnglogo.png - type: RGB565 - use_transparency: no - - id: web_svg_image - file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg - resize: 256x48 - type: TRANSPARENT_BINARY - - id: web_tiff_image - file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff - type: RGB24 - resize: 48x48 - - id: web_redirect_image - file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 - type: RGB24 - resize: 48x48 - - id: mdi_alert - file: mdi:alert-circle-outline - resize: 50x50 - - id: another_alert_icon - file: mdi:alert-outline - type: BINARY +<<: !include common.yaml From 21cb941bbe7afb7096ee342fae2c88bdaa27d28b Mon Sep 17 00:00:00 2001 From: Oleg Tarasov <me@olegtarasov.email> Date: Fri, 25 Oct 2024 05:00:28 +0300 Subject: [PATCH 044/282] Add OpenTherm component (part 2.1: sensor platform) (#7529) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/opentherm/__init__.py | 20 +- esphome/components/opentherm/const.py | 5 + esphome/components/opentherm/generate.py | 140 ++++++ esphome/components/opentherm/hub.cpp | 108 ++++- esphome/components/opentherm/hub.h | 19 +- esphome/components/opentherm/opentherm.cpp | 3 + esphome/components/opentherm/opentherm.h | 3 +- .../components/opentherm/opentherm_macros.h | 91 ++++ esphome/components/opentherm/schema.py | 438 ++++++++++++++++++ .../components/opentherm/sensor/__init__.py | 35 ++ esphome/components/opentherm/validate.py | 31 ++ tests/components/opentherm/common.yaml | 77 ++- 12 files changed, 933 insertions(+), 37 deletions(-) create mode 100644 esphome/components/opentherm/const.py create mode 100644 esphome/components/opentherm/generate.py create mode 100644 esphome/components/opentherm/opentherm_macros.h create mode 100644 esphome/components/opentherm/schema.py create mode 100644 esphome/components/opentherm/sensor/__init__.py create mode 100644 esphome/components/opentherm/validate.py diff --git a/esphome/components/opentherm/__init__.py b/esphome/components/opentherm/__init__.py index 23443a4028..ee19818a29 100644 --- a/esphome/components/opentherm/__init__.py +++ b/esphome/components/opentherm/__init__.py @@ -1,9 +1,10 @@ from typing import Any -from esphome import pins import esphome.codegen as cg import esphome.config_validation as cv +from esphome import pins from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266 +from . import generate CODEOWNERS = ["@olegtarasov"] MULTI_CONF = True @@ -15,15 +16,14 @@ CONF_DHW_ENABLE = "dhw_enable" CONF_COOLING_ENABLE = "cooling_enable" CONF_OTC_ACTIVE = "otc_active" CONF_CH2_ACTIVE = "ch2_active" +CONF_SUMMER_MODE_ACTIVE = "summer_mode_active" +CONF_DHW_BLOCK = "dhw_block" CONF_SYNC_MODE = "sync_mode" -opentherm_ns = cg.esphome_ns.namespace("opentherm") -OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) - CONFIG_SCHEMA = cv.All( cv.Schema( { - cv.GenerateID(): cv.declare_id(OpenthermHub), + cv.GenerateID(): cv.declare_id(generate.OpenthermHub), cv.Required(CONF_IN_PIN): pins.internal_gpio_input_pin_schema, cv.Required(CONF_OUT_PIN): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_CH_ENABLE, True): cv.boolean, @@ -31,6 +31,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_COOLING_ENABLE, False): cv.boolean, cv.Optional(CONF_OTC_ACTIVE, False): cv.boolean, cv.Optional(CONF_CH2_ACTIVE, False): cv.boolean, + cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean, + cv.Optional(CONF_DHW_BLOCK, False): cv.boolean, cv.Optional(CONF_SYNC_MODE, False): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA), @@ -39,8 +41,6 @@ CONFIG_SCHEMA = cv.All( async def to_code(config: dict[str, Any]) -> None: - # Create the hub, passing the two callbacks defined below - # Since the hub is used in the callbacks, we need to define it first var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -53,5 +53,7 @@ async def to_code(config: dict[str, Any]) -> None: non_sensors = {CONF_ID, CONF_IN_PIN, CONF_OUT_PIN} for key, value in config.items(): - if key not in non_sensors: - cg.add(getattr(var, f"set_{key}")(value)) + if key in non_sensors: + continue + + cg.add(getattr(var, f"set_{key}")(value)) diff --git a/esphome/components/opentherm/const.py b/esphome/components/opentherm/const.py new file mode 100644 index 0000000000..1f997c5d9c --- /dev/null +++ b/esphome/components/opentherm/const.py @@ -0,0 +1,5 @@ +OPENTHERM = "opentherm" + +CONF_OPENTHERM_ID = "opentherm_id" + +SENSOR = "sensor" diff --git a/esphome/components/opentherm/generate.py b/esphome/components/opentherm/generate.py new file mode 100644 index 0000000000..6a97835a57 --- /dev/null +++ b/esphome/components/opentherm/generate.py @@ -0,0 +1,140 @@ +from collections.abc import Awaitable +from typing import Any, Callable + +import esphome.codegen as cg +from esphome.const import CONF_ID +from . import const +from .schema import TSchema + +opentherm_ns = cg.esphome_ns.namespace("opentherm") +OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) + + +def define_has_component(component_type: str, keys: list[str]) -> None: + cg.add_define( + f"OPENTHERM_{component_type.upper()}_LIST(F, sep)", + cg.RawExpression( + " sep ".join(map(lambda key: f"F({key}_{component_type.lower()})", keys)) + ), + ) + for key in keys: + cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}") + + +def define_message_handler( + component_type: str, keys: list[str], schemas: dict[str, TSchema] +) -> None: + # The macros defined here should be able to generate things like this: + # // Parsing a message and publishing to sensors + # case MessageId::Message: + # // Can have multiple sensors here, for example for a Status message with multiple flags + # this->thing_binary_sensor->publish_state(parse_flag8_lb_0(response)); + # this->other_binary_sensor->publish_state(parse_flag8_lb_1(response)); + # break; + # // Building a message for a write request + # case MessageId::Message: { + # unsigned int data = 0; + # data = write_flag8_lb_0(some_input_switch->state, data); // Where input_sensor can also be a number/output/switch + # data = write_u8_hb(some_number->state, data); + # return opentherm_->build_request_(MessageType::WriteData, MessageId::Message, data); + # } + + messages: dict[str, list[tuple[str, str]]] = {} + for key in keys: + msg = schemas[key].message + if msg not in messages: + messages[msg] = [] + messages[msg].append((key, schemas[key].message_data)) + + cg.add_define( + f"OPENTHERM_{component_type.upper()}_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)", + cg.RawExpression( + " msg_sep ".join( + [ + f"MESSAGE({msg}) " + + " entity_sep ".join( + [ + f"ENTITY({key}_{component_type.lower()}, {msg_data})" + for key, msg_data in keys + ] + ) + + " postscript" + for msg, keys in messages.items() + ] + ) + ), + ) + + +def define_readers(component_type: str, keys: list[str]) -> None: + for key in keys: + cg.add_define( + f"OPENTHERM_READ_{key}", + cg.RawExpression(f"this->{key}_{component_type.lower()}->state"), + ) + + +def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]): + messages: set[tuple[str, bool]] = set() + for key in keys: + messages.add((schemas[key].message, schemas[key].keep_updated)) + for msg, keep_updated in messages: + msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}") + if keep_updated: + cg.add(hub.add_repeating_message(msg_expr)) + else: + cg.add(hub.add_initial_message(msg_expr)) + + +def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None: + if config_key in config: + cg.add(getattr(var, f"set_{config_key}")(config[config_key])) + + +Create = Callable[[dict[str, Any], str, cg.MockObj], Awaitable[cg.Pvariable]] + + +def create_only_conf( + create: Callable[[dict[str, Any]], Awaitable[cg.Pvariable]] +) -> Create: + return lambda conf, _key, _hub: create(conf) + + +async def component_to_code( + component_type: str, + schemas: dict[str, TSchema], + type: cg.MockObjClass, + create: Create, + config: dict[str, Any], +) -> list[str]: + """Generate the code for each configured component in the schema of a component type. + + Parameters: + - component_type: The type of component, e.g. "sensor" or "binary_sensor" + - schema_: The schema for that component type, a list of available components + - type: The type of the component, e.g. sensor.Sensor or OpenthermOutput + - create: A constructor function for the component, which receives the config, + the key and the hub and should asynchronously return the new component + - config: The configuration for this component type + + Returns: The list of keys for the created components + """ + cg.add_define(f"OPENTHERM_USE_{component_type.upper()}") + + hub = await cg.get_variable(config[const.CONF_OPENTHERM_ID]) + + keys: list[str] = [] + for key, conf in config.items(): + if not isinstance(conf, dict): + continue + id = conf[CONF_ID] + if id and id.type == type: + entity = await create(conf, key, hub) + cg.add(getattr(hub, f"set_{key}_{component_type.lower()}")(entity)) + keys.append(key) + + define_has_component(component_type, keys) + define_message_handler(component_type, keys, schemas) + add_messages(hub, keys, schemas) + + return keys diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index c26fbced32..770bbd82b7 100644 --- a/esphome/components/opentherm/hub.cpp +++ b/esphome/components/opentherm/hub.cpp @@ -7,50 +7,114 @@ namespace esphome { namespace opentherm { static const char *const TAG = "opentherm"; +namespace message_data { +bool parse_flag8_lb_0(OpenthermData &data) { return read_bit(data.valueLB, 0); } +bool parse_flag8_lb_1(OpenthermData &data) { return read_bit(data.valueLB, 1); } +bool parse_flag8_lb_2(OpenthermData &data) { return read_bit(data.valueLB, 2); } +bool parse_flag8_lb_3(OpenthermData &data) { return read_bit(data.valueLB, 3); } +bool parse_flag8_lb_4(OpenthermData &data) { return read_bit(data.valueLB, 4); } +bool parse_flag8_lb_5(OpenthermData &data) { return read_bit(data.valueLB, 5); } +bool parse_flag8_lb_6(OpenthermData &data) { return read_bit(data.valueLB, 6); } +bool parse_flag8_lb_7(OpenthermData &data) { return read_bit(data.valueLB, 7); } +bool parse_flag8_hb_0(OpenthermData &data) { return read_bit(data.valueHB, 0); } +bool parse_flag8_hb_1(OpenthermData &data) { return read_bit(data.valueHB, 1); } +bool parse_flag8_hb_2(OpenthermData &data) { return read_bit(data.valueHB, 2); } +bool parse_flag8_hb_3(OpenthermData &data) { return read_bit(data.valueHB, 3); } +bool parse_flag8_hb_4(OpenthermData &data) { return read_bit(data.valueHB, 4); } +bool parse_flag8_hb_5(OpenthermData &data) { return read_bit(data.valueHB, 5); } +bool parse_flag8_hb_6(OpenthermData &data) { return read_bit(data.valueHB, 6); } +bool parse_flag8_hb_7(OpenthermData &data) { return read_bit(data.valueHB, 7); } +uint8_t parse_u8_lb(OpenthermData &data) { return data.valueLB; } +uint8_t parse_u8_hb(OpenthermData &data) { return data.valueHB; } +int8_t parse_s8_lb(OpenthermData &data) { return (int8_t) data.valueLB; } +int8_t parse_s8_hb(OpenthermData &data) { return (int8_t) data.valueHB; } +uint16_t parse_u16(OpenthermData &data) { return data.u16(); } +int16_t parse_s16(OpenthermData &data) { return data.s16(); } +float parse_f88(OpenthermData &data) { return data.f88(); } -OpenthermData OpenthermHub::build_request_(MessageId request_id) { +void write_flag8_lb_0(const bool value, OpenthermData &data) { data.valueLB = write_bit(data.valueLB, 0, value); } +void write_flag8_lb_1(const bool value, OpenthermData &data) { data.valueLB = write_bit(data.valueLB, 1, value); } +void write_flag8_lb_2(const bool value, OpenthermData &data) { data.valueLB = write_bit(data.valueLB, 2, value); } +void write_flag8_lb_3(const bool value, OpenthermData &data) { data.valueLB = write_bit(data.valueLB, 3, value); } +void write_flag8_lb_4(const bool value, OpenthermData &data) { data.valueLB = write_bit(data.valueLB, 4, value); } +void write_flag8_lb_5(const bool value, OpenthermData &data) { data.valueLB = write_bit(data.valueLB, 5, value); } +void write_flag8_lb_6(const bool value, OpenthermData &data) { data.valueLB = write_bit(data.valueLB, 6, value); } +void write_flag8_lb_7(const bool value, OpenthermData &data) { data.valueLB = write_bit(data.valueLB, 7, value); } +void write_flag8_hb_0(const bool value, OpenthermData &data) { data.valueHB = write_bit(data.valueHB, 0, value); } +void write_flag8_hb_1(const bool value, OpenthermData &data) { data.valueHB = write_bit(data.valueHB, 1, value); } +void write_flag8_hb_2(const bool value, OpenthermData &data) { data.valueHB = write_bit(data.valueHB, 2, value); } +void write_flag8_hb_3(const bool value, OpenthermData &data) { data.valueHB = write_bit(data.valueHB, 3, value); } +void write_flag8_hb_4(const bool value, OpenthermData &data) { data.valueHB = write_bit(data.valueHB, 4, value); } +void write_flag8_hb_5(const bool value, OpenthermData &data) { data.valueHB = write_bit(data.valueHB, 5, value); } +void write_flag8_hb_6(const bool value, OpenthermData &data) { data.valueHB = write_bit(data.valueHB, 6, value); } +void write_flag8_hb_7(const bool value, OpenthermData &data) { data.valueHB = write_bit(data.valueHB, 7, value); } +void write_u8_lb(const uint8_t value, OpenthermData &data) { data.valueLB = value; } +void write_u8_hb(const uint8_t value, OpenthermData &data) { data.valueHB = value; } +void write_s8_lb(const int8_t value, OpenthermData &data) { data.valueLB = (uint8_t) value; } +void write_s8_hb(const int8_t value, OpenthermData &data) { data.valueHB = (uint8_t) value; } +void write_u16(const uint16_t value, OpenthermData &data) { data.u16(value); } +void write_s16(const int16_t value, OpenthermData &data) { data.s16(value); } +void write_f88(const float value, OpenthermData &data) { data.f88(value); } + +} // namespace message_data + +OpenthermData OpenthermHub::build_request_(MessageId request_id) const { OpenthermData data; data.type = 0; data.id = 0; data.valueHB = 0; data.valueLB = 0; - // First, handle the status request. This requires special logic, because we - // wouldn't want to inadvertently disable domestic hot water, for example. - // It is also included in the macro-generated code below, but that will - // never be executed, because we short-circuit it here. + // We need this special logic for STATUS message because we have two options for specifying boiler modes: + // with static config values in the hub, or with separate switches. if (request_id == MessageId::STATUS) { - bool const ch_enabled = this->ch_enable; - bool dhw_enabled = this->dhw_enable; - bool cooling_enabled = this->cooling_enable; - bool otc_enabled = this->otc_active; - bool ch2_enabled = this->ch2_active; + // NOLINTBEGIN + bool const ch_enabled = this->ch_enable && OPENTHERM_READ_ch_enable && OPENTHERM_READ_t_set > 0.0; + bool const dhw_enabled = this->dhw_enable && OPENTHERM_READ_dhw_enable; + bool const cooling_enabled = + this->cooling_enable && OPENTHERM_READ_cooling_enable && OPENTHERM_READ_cooling_control > 0.0; + bool const otc_enabled = this->otc_active && OPENTHERM_READ_otc_active; + bool const ch2_enabled = this->ch2_active && OPENTHERM_READ_ch2_active && OPENTHERM_READ_t_set_ch2 > 0.0; + bool const summer_mode_is_active = this->summer_mode_active && OPENTHERM_READ_summer_mode_active; + bool const dhw_blocked = this->dhw_block && OPENTHERM_READ_dhw_block; + // NOLINTEND data.type = MessageType::READ_DATA; data.id = MessageId::STATUS; - data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4); + data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) | + (summer_mode_is_active << 5) | (dhw_blocked << 6); + + return data; + } // Disable incomplete switch statement warnings, because the cases in each // switch are generated based on the configured sensors and inputs. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch" - // TODO: This is a placeholder for an auto-generated switch statement which builds request structure based on - // which sensors are enabled in config. + switch (request_id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) } #pragma GCC diagnostic pop - return data; - } - return OpenthermData(); + // And if we get here, a message was requested which somehow wasn't handled. + // This shouldn't happen due to the way the defines are configured, so we + // log an error and just return a 0 message. + ESP_LOGE(TAG, "Tried to create a request with unknown id %d. This should never happen, so please open an issue.", + request_id); + return {}; } -OpenthermHub::OpenthermHub() : Component() {} +OpenthermHub::OpenthermHub() : Component(), in_pin_{}, out_pin_{} {} void OpenthermHub::process_response(OpenthermData &data) { ESP_LOGD(TAG, "Received OpenTherm response with id %d (%s)", data.id, this->opentherm_->message_id_to_str((MessageId) data.id)); ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(data).c_str()); + + switch (data.id) { + OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_RESPONSE_MESSAGE, OPENTHERM_MESSAGE_RESPONSE_ENTITY, , + OPENTHERM_MESSAGE_RESPONSE_POSTSCRIPT, ) + } } void OpenthermHub::setup() { @@ -254,15 +318,17 @@ void OpenthermHub::handle_timeout_error_() { this->stop_opentherm_(); } -#define ID(x) x -#define SHOW2(x) #x -#define SHOW(x) SHOW2(x) - void OpenthermHub::dump_config() { ESP_LOGCONFIG(TAG, "OpenTherm:"); LOG_PIN(" In: ", this->in_pin_); LOG_PIN(" Out: ", this->out_pin_); ESP_LOGCONFIG(TAG, " Sync mode: %d", this->sync_mode_); + ESP_LOGCONFIG(TAG, " Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, ))); + ESP_LOGCONFIG(TAG, " Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, ))); + ESP_LOGCONFIG(TAG, " Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, ))); + ESP_LOGCONFIG(TAG, " Input sensors: %s", SHOW(OPENTHERM_INPUT_SENSOR_LIST(ID, ))); + ESP_LOGCONFIG(TAG, " Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, ))); + ESP_LOGCONFIG(TAG, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Initial requests:"); for (auto type : this->initial_messages_) { ESP_LOGCONFIG(TAG, " - %d", type); diff --git a/esphome/components/opentherm/hub.h b/esphome/components/opentherm/hub.h index ce9f09fe33..3b90cdf427 100644 --- a/esphome/components/opentherm/hub.h +++ b/esphome/components/opentherm/hub.h @@ -7,11 +7,17 @@ #include "opentherm.h" +#ifdef OPENTHERM_USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif + #include <memory> #include <unordered_map> #include <unordered_set> #include <functional> +#include "opentherm_macros.h" + namespace esphome { namespace opentherm { @@ -23,6 +29,8 @@ class OpenthermHub : public Component { // The OpenTherm interface std::unique_ptr<OpenTherm> opentherm_; + OPENTHERM_SENSOR_LIST(OPENTHERM_DECLARE_SENSOR, ) + // The set of initial messages to send on starting communication with the boiler std::unordered_set<MessageId> initial_messages_; // and the repeating messages which are sent repeatedly to update various sensors @@ -44,7 +52,7 @@ class OpenthermHub : public Component { bool sync_mode_ = false; // Create OpenTherm messages based on the message id - OpenthermData build_request_(MessageId request_id); + OpenthermData build_request_(MessageId request_id) const; void handle_protocol_write_error_(); void handle_protocol_read_error_(); void handle_timeout_error_(); @@ -78,6 +86,8 @@ class OpenthermHub : public Component { void set_in_pin(InternalGPIOPin *in_pin) { this->in_pin_ = in_pin; } void set_out_pin(InternalGPIOPin *out_pin) { this->out_pin_ = out_pin; } + OPENTHERM_SENSOR_LIST(OPENTHERM_SET_SENSOR, ) + // Add a request to the set of initial requests void add_initial_message(MessageId message_id) { this->initial_messages_.insert(message_id); } // Add a request to the set of repeating requests. Note that a large number of repeating @@ -86,9 +96,10 @@ class OpenthermHub : public Component { // will be processed. void add_repeating_message(MessageId message_id) { this->repeating_messages_.insert(message_id); } - // There are five status variables, which can either be set as a simple variable, + // There are seven status variables, which can either be set as a simple variable, // or using a switch. ch_enable and dhw_enable default to true, the others to false. - bool ch_enable = true, dhw_enable = true, cooling_enable = false, otc_active = false, ch2_active = false; + bool ch_enable = true, dhw_enable = true, cooling_enable = false, otc_active = false, ch2_active = false, + summer_mode_active = false, dhw_block = false; // Setters for the status variables void set_ch_enable(bool value) { this->ch_enable = value; } @@ -96,6 +107,8 @@ class OpenthermHub : public Component { void set_cooling_enable(bool value) { this->cooling_enable = value; } void set_otc_active(bool value) { this->otc_active = value; } void set_ch2_active(bool value) { this->ch2_active = value; } + void set_summer_mode_active(bool value) { this->summer_mode_active = value; } + void set_dhw_block(bool value) { this->dhw_block = value; } void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; } float get_setup_priority() const override { return setup_priority::HARDWARE; } diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index b830cc01d3..4a23bb94cf 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -283,6 +283,9 @@ bool OpenTherm::init_esp32_timer_() { .clk_src = TIMER_SRC_CLK_DEFAULT, #endif .divider = 80, +#if defined(SOC_TIMER_GROUP_SUPPORT_XTAL) && ESP_IDF_VERSION_MAJOR < 5 + .clk_src = TIMER_SRC_CLK_APB +#endif }; esp_err_t result; diff --git a/esphome/components/opentherm/opentherm.h b/esphome/components/opentherm/opentherm.h index 609cfb6243..23f4b39a1a 100644 --- a/esphome/components/opentherm/opentherm.h +++ b/esphome/components/opentherm/opentherm.h @@ -20,7 +20,6 @@ namespace esphome { namespace opentherm { -// TODO: Account for immutable semantics change in hub.cpp when doing later installments of OpenTherm PR template<class T> constexpr T read_bit(T value, uint8_t bit) { return (value >> bit) & 0x01; } template<class T> constexpr T set_bit(T value, uint8_t bit) { return value |= (1UL << bit); } @@ -28,7 +27,7 @@ template<class T> constexpr T set_bit(T value, uint8_t bit) { return value |= (1 template<class T> constexpr T clear_bit(T value, uint8_t bit) { return value &= ~(1UL << bit); } template<class T> constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value) { - return bit_value ? setBit(value, bit) : clearBit(value, bit); + return bit_value ? set_bit(value, bit) : clear_bit(value, bit); } enum OperationMode { diff --git a/esphome/components/opentherm/opentherm_macros.h b/esphome/components/opentherm/opentherm_macros.h new file mode 100644 index 0000000000..0389e975ff --- /dev/null +++ b/esphome/components/opentherm/opentherm_macros.h @@ -0,0 +1,91 @@ +#pragma once +namespace esphome { +namespace opentherm { + +// ===== hub.h macros ===== + +// *_LIST macros will be generated in defines.h if at least one sensor from each platform is used. +// These lists will look like this: +// #define OPENTHERM_BINARY_SENSOR_LIST(F, sep) F(sensor_1) sep F(sensor_2) +// These lists will be used in hub.h to define sensor fields (passing macros like OPENTHERM_DECLARE_SENSOR as F) +// and setters (passing macros like OPENTHERM_SET_SENSOR as F) (see below) +// In order for things not to break, we define empty lists here in case some platforms are not used in config. +#ifndef OPENTHERM_SENSOR_LIST +#define OPENTHERM_SENSOR_LIST(F, sep) +#endif + +// Use macros to create fields for every entity specified in the ESPHome configuration +#define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity; + +// Setter macros +#define OPENTHERM_SET_SENSOR(entity) \ + void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; } + +// ===== hub.cpp macros ===== + +// *_MESSAGE_HANDLERS are generated in defines.h and look like this: +// OPENTHERM_NUMBER_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) MESSAGE(COOLING_CONTROL) +// ENTITY(cooling_control_number, f88) postscript msg_sep They contain placeholders for message part and entities parts, +// since one message can contain multiple entities. MESSAGE part is substituted with OPENTHERM_MESSAGE_WRITE_MESSAGE, +// OPENTHERM_MESSAGE_READ_MESSAGE or OPENTHERM_MESSAGE_RESPONSE_MESSAGE. ENTITY part is substituted with +// OPENTHERM_MESSAGE_WRITE_ENTITY or OPENTHERM_MESSAGE_RESPONSE_ENTITY. OPENTHERM_IGNORE is used for sensor read +// requests since no data needs to be sent or processed, just the data id. + +// In order for things not to break, we define empty lists here in case some platforms are not used in config. +#ifndef OPENTHERM_SENSOR_MESSAGE_HANDLERS +#define OPENTHERM_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) +#endif + +// Read data request builder +#define OPENTHERM_MESSAGE_READ_MESSAGE(msg) \ + case MessageId::msg: \ + data.type = MessageType::READ_DATA; \ + data.id = request_id; \ + return data; + +// Data processing builders +#define OPENTHERM_MESSAGE_RESPONSE_MESSAGE(msg) case MessageId::msg: +#define OPENTHERM_MESSAGE_RESPONSE_ENTITY(key, msg_data) this->key->publish_state(message_data::parse_##msg_data(data)); +#define OPENTHERM_MESSAGE_RESPONSE_POSTSCRIPT break; + +#define OPENTHERM_IGNORE(x, y) + +// Default macros for STATUS entities +#ifndef OPENTHERM_READ_ch_enable +#define OPENTHERM_READ_ch_enable true +#endif +#ifndef OPENTHERM_READ_dhw_enable +#define OPENTHERM_READ_dhw_enable true +#endif +#ifndef OPENTHERM_READ_t_set +#define OPENTHERM_READ_t_set 0.0 +#endif +#ifndef OPENTHERM_READ_cooling_enable +#define OPENTHERM_READ_cooling_enable false +#endif +#ifndef OPENTHERM_READ_cooling_control +#define OPENTHERM_READ_cooling_control 0.0 +#endif +#ifndef OPENTHERM_READ_otc_active +#define OPENTHERM_READ_otc_active false +#endif +#ifndef OPENTHERM_READ_ch2_active +#define OPENTHERM_READ_ch2_active false +#endif +#ifndef OPENTHERM_READ_t_set_ch2 +#define OPENTHERM_READ_t_set_ch2 0.0 +#endif +#ifndef OPENTHERM_READ_summer_mode_active +#define OPENTHERM_READ_summer_mode_active false +#endif +#ifndef OPENTHERM_READ_dhw_block +#define OPENTHERM_READ_dhw_block false +#endif + +// These macros utilize the structure of *_LIST macros in order +#define ID(x) x +#define SHOW_INNER(x) #x +#define SHOW(x) SHOW_INNER(x) + +} // namespace opentherm +} // namespace esphome diff --git a/esphome/components/opentherm/schema.py b/esphome/components/opentherm/schema.py new file mode 100644 index 0000000000..6ed0029437 --- /dev/null +++ b/esphome/components/opentherm/schema.py @@ -0,0 +1,438 @@ +# This file contains a schema for all supported sensors, binary sensors and +# inputs of the OpenTherm component. + +from dataclasses import dataclass +from typing import Optional, TypeVar + +from esphome.const import ( + UNIT_CELSIUS, + UNIT_EMPTY, + UNIT_KILOWATT, + UNIT_MICROAMP, + UNIT_PERCENT, + UNIT_REVOLUTIONS_PER_MINUTE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_NONE, + STATE_CLASS_TOTAL_INCREASING, +) + + +@dataclass +class EntitySchema: + description: str + """Description of the item, based on the OpenTherm spec""" + + message: str + """OpenTherm message id used to read or write the value""" + + keep_updated: bool + """Whether the value should be read or write repeatedly (True) or only during + the initialization phase (False) + """ + + message_data: str + """Instructions on how to interpret the data in the message + - flag8_[hb|lb]_[0-7]: data is a byte of single bit flags, + this flag is set in the high (hb) or low byte (lb), + at position 0 to 7 + - u8_[hb|lb]: data is an unsigned 8-bit integer, + in the high (hb) or low byte (lb) + - s8_[hb|lb]: data is an signed 8-bit integer, + in the high (hb) or low byte (lb) + - f88: data is a signed fixed point value with + 1 sign bit, 7 integer bits, 8 fractional bits + - u16: data is an unsigned 16-bit integer + - s16: data is a signed 16-bit integer + """ + + +TSchema = TypeVar("TSchema", bound=EntitySchema) + + +@dataclass +class SensorSchema(EntitySchema): + accuracy_decimals: int + state_class: str + unit_of_measurement: Optional[str] = None + icon: Optional[str] = None + device_class: Optional[str] = None + disabled_by_default: bool = False + + +SENSORS: dict[str, SensorSchema] = { + "rel_mod_level": SensorSchema( + description="Relative modulation level", + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=2, + icon="mdi:percent", + state_class=STATE_CLASS_MEASUREMENT, + message="MODULATION_LEVEL", + keep_updated=True, + message_data="f88", + ), + "ch_pressure": SensorSchema( + description="Water pressure in CH circuit", + unit_of_measurement="bar", + accuracy_decimals=2, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + message="CH_WATER_PRESSURE", + keep_updated=True, + message_data="f88", + ), + "dhw_flow_rate": SensorSchema( + description="Water flow rate in DHW circuit", + unit_of_measurement="l/min", + accuracy_decimals=2, + icon="mdi:waves-arrow-right", + state_class=STATE_CLASS_MEASUREMENT, + message="DHW_FLOW_RATE", + keep_updated=True, + message_data="f88", + ), + "t_boiler": SensorSchema( + description="Boiler water temperature", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="FEED_TEMP", + keep_updated=True, + message_data="f88", + ), + "t_dhw": SensorSchema( + description="DHW temperature", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="DHW_TEMP", + keep_updated=True, + message_data="f88", + ), + "t_outside": SensorSchema( + description="Outside temperature", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="OUTSIDE_TEMP", + keep_updated=True, + message_data="f88", + ), + "t_ret": SensorSchema( + description="Return water temperature", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="RETURN_WATER_TEMP", + keep_updated=True, + message_data="f88", + ), + "t_storage": SensorSchema( + description="Solar storage temperature", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="SOLAR_STORE_TEMP", + keep_updated=True, + message_data="f88", + ), + "t_collector": SensorSchema( + description="Solar collector temperature", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="SOLAR_COLLECT_TEMP", + keep_updated=True, + message_data="s16", + ), + "t_flow_ch2": SensorSchema( + description="Flow water temperature CH2 circuit", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="FEED_TEMP_CH2", + keep_updated=True, + message_data="f88", + ), + "t_dhw2": SensorSchema( + description="Domestic hot water temperature 2", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="DHW2_TEMP", + keep_updated=True, + message_data="f88", + ), + "t_exhaust": SensorSchema( + description="Boiler exhaust temperature", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="EXHAUST_TEMP", + keep_updated=True, + message_data="s16", + ), + "fan_speed": SensorSchema( + description="Boiler fan speed", + unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + message="FAN_SPEED", + keep_updated=True, + message_data="u16", + ), + "flame_current": SensorSchema( + description="Boiler flame current", + unit_of_measurement=UNIT_MICROAMP, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + message="FLAME_CURRENT", + keep_updated=True, + message_data="f88", + ), + "burner_starts": SensorSchema( + description="Number of starts burner", + accuracy_decimals=0, + icon="mdi:gas-burner", + state_class=STATE_CLASS_TOTAL_INCREASING, + message="BURNER_STARTS", + keep_updated=True, + message_data="u16", + ), + "ch_pump_starts": SensorSchema( + description="Number of starts CH pump", + accuracy_decimals=0, + icon="mdi:pump", + state_class=STATE_CLASS_TOTAL_INCREASING, + message="CH_PUMP_STARTS", + keep_updated=True, + message_data="u16", + ), + "dhw_pump_valve_starts": SensorSchema( + description="Number of starts DHW pump/valve", + accuracy_decimals=0, + icon="mdi:water-pump", + state_class=STATE_CLASS_TOTAL_INCREASING, + message="DHW_PUMP_STARTS", + keep_updated=True, + message_data="u16", + ), + "dhw_burner_starts": SensorSchema( + description="Number of starts burner during DHW mode", + accuracy_decimals=0, + icon="mdi:gas-burner", + state_class=STATE_CLASS_TOTAL_INCREASING, + message="DHW_BURNER_STARTS", + keep_updated=True, + message_data="u16", + ), + "burner_operation_hours": SensorSchema( + description="Number of hours that burner is in operation", + accuracy_decimals=0, + icon="mdi:clock-outline", + state_class=STATE_CLASS_TOTAL_INCREASING, + message="BURNER_HOURS", + keep_updated=True, + message_data="u16", + ), + "ch_pump_operation_hours": SensorSchema( + description="Number of hours that CH pump has been running", + accuracy_decimals=0, + icon="mdi:clock-outline", + state_class=STATE_CLASS_TOTAL_INCREASING, + message="CH_PUMP_HOURS", + keep_updated=True, + message_data="u16", + ), + "dhw_pump_valve_operation_hours": SensorSchema( + description="Number of hours that DHW pump has been running or DHW valve has been opened", + accuracy_decimals=0, + icon="mdi:clock-outline", + state_class=STATE_CLASS_TOTAL_INCREASING, + message="DHW_PUMP_HOURS", + keep_updated=True, + message_data="u16", + ), + "dhw_burner_operation_hours": SensorSchema( + description="Number of hours that burner is in operation during DHW mode", + accuracy_decimals=0, + icon="mdi:clock-outline", + state_class=STATE_CLASS_TOTAL_INCREASING, + message="DHW_BURNER_HOURS", + keep_updated=True, + message_data="u16", + ), + "t_dhw_set_ub": SensorSchema( + description="Upper bound for adjustment of DHW setpoint", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="DHW_BOUNDS", + keep_updated=False, + message_data="s8_hb", + ), + "t_dhw_set_lb": SensorSchema( + description="Lower bound for adjustment of DHW setpoint", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="DHW_BOUNDS", + keep_updated=False, + message_data="s8_lb", + ), + "max_t_set_ub": SensorSchema( + description="Upper bound for adjustment of max CH setpoint", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="CH_BOUNDS", + keep_updated=False, + message_data="s8_hb", + ), + "max_t_set_lb": SensorSchema( + description="Lower bound for adjustment of max CH setpoint", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="CH_BOUNDS", + keep_updated=False, + message_data="s8_lb", + ), + "t_dhw_set": SensorSchema( + description="Domestic hot water temperature setpoint", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="DHW_SETPOINT", + keep_updated=True, + message_data="f88", + ), + "max_t_set": SensorSchema( + description="Maximum allowable CH water setpoint", + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + message="MAX_CH_SETPOINT", + keep_updated=True, + message_data="f88", + ), + "oem_fault_code": SensorSchema( + description="OEM fault code", + unit_of_measurement=UNIT_EMPTY, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, + message="FAULT_FLAGS", + keep_updated=True, + message_data="u8_lb", + ), + "oem_diagnostic_code": SensorSchema( + description="OEM diagnostic code", + unit_of_measurement=UNIT_EMPTY, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, + message="OEM_DIAGNOSTIC", + keep_updated=True, + message_data="u16", + ), + "max_capacity": SensorSchema( + description="Maximum boiler capacity (KW)", + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + disabled_by_default=True, + message="MAX_BOILER_CAPACITY", + keep_updated=False, + message_data="u8_hb", + ), + "min_mod_level": SensorSchema( + description="Minimum modulation level", + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + icon="mdi:percent", + disabled_by_default=True, + state_class=STATE_CLASS_MEASUREMENT, + message="MAX_BOILER_CAPACITY", + keep_updated=False, + message_data="u8_lb", + ), + "opentherm_version_device": SensorSchema( + description="Version of OpenTherm implemented by device", + unit_of_measurement=UNIT_EMPTY, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, + disabled_by_default=True, + message="OT_VERSION_DEVICE", + keep_updated=False, + message_data="f88", + ), + "device_type": SensorSchema( + description="Device product type", + unit_of_measurement=UNIT_EMPTY, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, + disabled_by_default=True, + message="VERSION_DEVICE", + keep_updated=False, + message_data="u8_hb", + ), + "device_version": SensorSchema( + description="Device product version", + unit_of_measurement=UNIT_EMPTY, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, + disabled_by_default=True, + message="VERSION_DEVICE", + keep_updated=False, + message_data="u8_lb", + ), + "device_id": SensorSchema( + description="Device ID code", + unit_of_measurement=UNIT_EMPTY, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, + disabled_by_default=True, + message="DEVICE_CONFIG", + keep_updated=False, + message_data="u8_lb", + ), + "otc_hc_ratio_ub": SensorSchema( + description="OTC heat curve ratio upper bound", + unit_of_measurement=UNIT_EMPTY, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, + disabled_by_default=True, + message="OTC_CURVE_BOUNDS", + keep_updated=False, + message_data="u8_hb", + ), + "otc_hc_ratio_lb": SensorSchema( + description="OTC heat curve ratio lower bound", + unit_of_measurement=UNIT_EMPTY, + accuracy_decimals=0, + state_class=STATE_CLASS_NONE, + disabled_by_default=True, + message="OTC_CURVE_BOUNDS", + keep_updated=False, + message_data="u8_lb", + ), +} diff --git a/esphome/components/opentherm/sensor/__init__.py b/esphome/components/opentherm/sensor/__init__.py new file mode 100644 index 0000000000..20224e0eda --- /dev/null +++ b/esphome/components/opentherm/sensor/__init__.py @@ -0,0 +1,35 @@ +from typing import Any + +import esphome.config_validation as cv +from esphome.components import sensor +from .. import const, schema, validate, generate + +DEPENDENCIES = [const.OPENTHERM] +COMPONENT_TYPE = const.SENSOR + + +def get_entity_validation_schema(entity: schema.SensorSchema) -> cv.Schema: + return sensor.sensor_schema( + unit_of_measurement=entity.unit_of_measurement + or sensor._UNDEF, # pylint: disable=protected-access + accuracy_decimals=entity.accuracy_decimals, + device_class=entity.device_class + or sensor._UNDEF, # pylint: disable=protected-access + icon=entity.icon or sensor._UNDEF, # pylint: disable=protected-access + state_class=entity.state_class, + ) + + +CONFIG_SCHEMA = validate.create_component_schema( + schema.SENSORS, get_entity_validation_schema +) + + +async def to_code(config: dict[str, Any]) -> None: + await generate.component_to_code( + COMPONENT_TYPE, + schema.SENSORS, + sensor.Sensor, + generate.create_only_conf(sensor.new_sensor), + config, + ) diff --git a/esphome/components/opentherm/validate.py b/esphome/components/opentherm/validate.py new file mode 100644 index 0000000000..d4507672a5 --- /dev/null +++ b/esphome/components/opentherm/validate.py @@ -0,0 +1,31 @@ +from typing import Callable + +from voluptuous import Schema + +import esphome.config_validation as cv + +from . import const, schema, generate +from .schema import TSchema + + +def create_entities_schema( + entities: dict[str, schema.EntitySchema], + get_entity_validation_schema: Callable[[TSchema], cv.Schema], +) -> Schema: + entity_schema = {} + for key, entity in entities.items(): + entity_schema[cv.Optional(key)] = get_entity_validation_schema(entity) + return cv.Schema(entity_schema) + + +def create_component_schema( + entities: dict[str, schema.EntitySchema], + get_entity_validation_schema: Callable[[TSchema], cv.Schema], +) -> Schema: + return ( + cv.Schema( + {cv.GenerateID(const.CONF_OPENTHERM_ID): cv.use_id(generate.OpenthermHub)} + ) + .extend(create_entities_schema(entities, get_entity_validation_schema)) + .extend(cv.COMPONENT_SCHEMA) + ) diff --git a/tests/components/opentherm/common.yaml b/tests/components/opentherm/common.yaml index 4148b280d0..27cbae280a 100644 --- a/tests/components/opentherm/common.yaml +++ b/tests/components/opentherm/common.yaml @@ -1,3 +1,76 @@ +api: +wifi: + ap: + ssid: "Thermostat" + password: "MySecretThemostat" + opentherm: - in_pin: 1 - out_pin: 2 + in_pin: 4 + out_pin: 5 + ch_enable: true + dhw_enable: false + cooling_enable: false + otc_active: false + ch2_active: true + summer_mode_active: true + dhw_block: true + sync_mode: true + +sensor: + - platform: opentherm + rel_mod_level: + name: "Boiler Relative modulation level" + ch_pressure: + name: "Boiler Water pressure in CH circuit" + dhw_flow_rate: + name: "Boiler Water flow rate in DHW circuit" + t_boiler: + name: "Boiler water temperature" + t_dhw: + name: "Boiler DHW temperature" + t_outside: + name: "Boiler Outside temperature" + t_ret: + name: "Boiler Return water temperature" + t_storage: + name: "Boiler Solar storage temperature" + t_collector: + name: "Boiler Solar collector temperature" + t_flow_ch2: + name: "Boiler Flow water temperature CH2 circuit" + t_dhw2: + name: "Boiler Domestic hot water temperature 2" + t_exhaust: + name: "Boiler Exhaust temperature" + burner_starts: + name: "Boiler Number of starts burner" + ch_pump_starts: + name: "Boiler Number of starts CH pump" + dhw_pump_valve_starts: + name: "Boiler Number of starts DHW pump/valve" + dhw_burner_starts: + name: "Boiler Number of starts burner during DHW mode" + burner_operation_hours: + name: "Boiler Number of hours that burner is in operation (i.e. flame on)" + ch_pump_operation_hours: + name: "Boiler Number of hours that CH pump has been running" + dhw_pump_valve_operation_hours: + name: "Boiler Number of hours that DHW pump has been running or DHW valve has been opened" + dhw_burner_operation_hours: + name: "Boiler Number of hours that burner is in operation during DHW mode" + t_dhw_set_ub: + name: "Boiler Upper bound for adjustement of DHW setpoint" + t_dhw_set_lb: + name: "Boiler Lower bound for adjustement of DHW setpoint" + max_t_set_ub: + name: "Boiler Upper bound for adjustement of max CH setpoint" + max_t_set_lb: + name: "Boiler Lower bound for adjustement of max CH setpoint" + t_dhw_set: + name: "Boiler Domestic hot water temperature setpoint" + max_t_set: + name: "Boiler Maximum allowable CH water setpoint" + otc_hc_ratio_ub: + name: "OTC heat curve ratio upper bound" + otc_hc_ratio_lb: + name: "OTC heat curve ratio lower bound" From 34de2bbe992b842ad8017daa94ba6500ffb26d1c Mon Sep 17 00:00:00 2001 From: SeByDocKy <sebydocky@hotmail.com> Date: Sat, 26 Oct 2024 23:54:57 +0200 Subject: [PATCH 045/282] gp8403 : Add the possibility to use substitution for channel selection (#7681) --- esphome/components/gp8403/output/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/gp8403/output/__init__.py b/esphome/components/gp8403/output/__init__.py index 1cf95ac6e5..7f17faa1b1 100644 --- a/esphome/components/gp8403/output/__init__.py +++ b/esphome/components/gp8403/output/__init__.py @@ -16,7 +16,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(GP8403Output), cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403), - cv.Required(CONF_CHANNEL): cv.one_of(0, 1), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=1), } ).extend(cv.COMPONENT_SCHEMA) From 1e2497748d3a18847df868ac8f4b893800426652 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sun, 27 Oct 2024 13:17:09 +1100 Subject: [PATCH 046/282] [rpi_dpi_rgb] Fix get_width and height (Bugfix) (#7675) Co-authored-by: clydeps <U5yx99dok9> --- .../components/rpi_dpi_rgb/rpi_dpi_rgb.cpp | 20 +++++++++++++++++++ esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h | 5 +++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp index 655b469b91..ba09171649 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -84,6 +84,26 @@ void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uin ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); } +int RpiDpiRgb::get_width() { + switch (this->rotation_) { + case display::DISPLAY_ROTATION_90_DEGREES: + case display::DISPLAY_ROTATION_270_DEGREES: + return this->get_height_internal(); + default: + return this->get_width_internal(); + } +} + +int RpiDpiRgb::get_height() { + switch (this->rotation_) { + case display::DISPLAY_ROTATION_90_DEGREES: + case display::DISPLAY_ROTATION_270_DEGREES: + return this->get_width_internal(); + default: + return this->get_height_internal(); + } +} + void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) { if (!this->get_clipping().inside(x, y)) return; // NOLINT diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h index 10f77a2624..7525040cd1 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h @@ -24,6 +24,7 @@ class RpiDpiRgb : public display::Display { void update() override { this->do_update_(); } void setup() override; void loop() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; void draw_pixel_at(int x, int y, Color color) override; @@ -44,8 +45,8 @@ class RpiDpiRgb : public display::Display { this->width_ = width; this->height_ = height; } - int get_width() override { return this->width_; } - int get_height() override { return this->height_; } + int get_width() override; + int get_height() override; void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; } void set_hsync_front_porch(uint16_t hsync_front_porch) { this->hsync_front_porch_ = hsync_front_porch; } void set_hsync_pulse_width(uint16_t hsync_pulse_width) { this->hsync_pulse_width_ = hsync_pulse_width; } From 22f30d42a668e89b64318b1d8bf6c5cde38e2b21 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:05:51 +1100 Subject: [PATCH 047/282] [lvgl] Implement qrcode (#7623) --- esphome/components/lvgl/__init__.py | 2 + esphome/components/lvgl/widgets/qrcode.py | 54 +++++++++++++++++++++++ tests/components/lvgl/lvgl-package.yaml | 12 +++++ 3 files changed, 68 insertions(+) create mode 100644 esphome/components/lvgl/widgets/qrcode.py diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 215fdecdb5..4a1a26cc0b 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -71,6 +71,7 @@ from .widgets.meter import meter_spec from .widgets.msgbox import MSGBOX_SCHEMA, msgboxes_to_code from .widgets.obj import obj_spec from .widgets.page import add_pages, generate_page_triggers, page_spec +from .widgets.qrcode import qr_code_spec from .widgets.roller import roller_spec from .widgets.slider import slider_spec from .widgets.spinbox import spinbox_spec @@ -109,6 +110,7 @@ for w_type in ( spinbox_spec, keyboard_spec, tileview_spec, + qr_code_spec, ): WIDGET_TYPES[w_type.name] = w_type diff --git a/esphome/components/lvgl/widgets/qrcode.py b/esphome/components/lvgl/widgets/qrcode.py new file mode 100644 index 0000000000..742b538938 --- /dev/null +++ b/esphome/components/lvgl/widgets/qrcode.py @@ -0,0 +1,54 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_SIZE, CONF_TEXT +from esphome.cpp_generator import MockObjClass + +from ..defines import CONF_MAIN, literal +from ..lv_validation import color, color_retmapper, lv_text +from ..lvcode import LocalVariable, lv, lv_expr +from ..schemas import TEXT_SCHEMA +from ..types import WidgetType, lv_obj_t +from . import Widget + +CONF_QRCODE = "qrcode" +CONF_DARK_COLOR = "dark_color" +CONF_LIGHT_COLOR = "light_color" + +QRCODE_SCHEMA = TEXT_SCHEMA.extend( + { + cv.Optional(CONF_DARK_COLOR, default="black"): color, + cv.Optional(CONF_LIGHT_COLOR, default="white"): color, + cv.Required(CONF_SIZE): cv.int_, + } +) + + +class QrCodeType(WidgetType): + def __init__(self): + super().__init__( + CONF_QRCODE, + lv_obj_t, + (CONF_MAIN,), + QRCODE_SCHEMA, + modify_schema=TEXT_SCHEMA, + ) + + def get_uses(self): + return ("canvas", "img") + + def obj_creator(self, parent: MockObjClass, config: dict): + dark_color = color_retmapper(config[CONF_DARK_COLOR]) + light_color = color_retmapper(config[CONF_LIGHT_COLOR]) + size = config[CONF_SIZE] + return lv_expr.call("qrcode_create", parent, size, dark_color, light_color) + + async def to_code(self, w: Widget, config): + if (value := config.get(CONF_TEXT)) is not None: + value = await lv_text.process(value) + with LocalVariable( + "qr_text", cg.const_char_ptr, value, modifier="" + ) as str_obj: + lv.qrcode_update(w.obj, str_obj, literal(f"strlen({str_obj})")) + + +qr_code_spec = QrCodeType() diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 4962a71596..9bfbb5fc95 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -458,6 +458,18 @@ lvgl: - id: page2 widgets: + - qrcode: + id: lv_qr + align: left_mid + size: 100 + light_color: whitesmoke + dark_color: steelblue + text: esphome.io + on_click: + lvgl.qrcode.update: + id: lv_qr + text: homeassistant.io + - slider: min_value: 0 max_value: 255 From 858d97ccefff68f92af1c8f722738424ff2db791 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:08:29 +1100 Subject: [PATCH 048/282] [bytebuffer] Rework ByteBuffer using templates (#7638) --- CODEOWNERS | 1 + esphome/components/bytebuffer/__init__.py | 5 + esphome/components/bytebuffer/bytebuffer.h | 421 ++++++++++++++++++ esphome/core/bytebuffer.cpp | 167 ------- esphome/core/bytebuffer.h | 144 ------ tests/components/bytebuffer/common.yaml | 161 +++++++ .../components/bytebuffer/test.esp32-ard.yaml | 1 + .../bytebuffer/test.esp32-c3-ard.yaml | 1 + .../bytebuffer/test.esp32-c3-idf.yaml | 1 + .../components/bytebuffer/test.esp32-idf.yaml | 1 + .../bytebuffer/test.esp8266-ard.yaml | 1 + tests/components/bytebuffer/test.host.yaml | 1 + .../bytebuffer/test.rp2040-ard.yaml | 1 + 13 files changed, 595 insertions(+), 311 deletions(-) create mode 100644 esphome/components/bytebuffer/__init__.py create mode 100644 esphome/components/bytebuffer/bytebuffer.h delete mode 100644 esphome/core/bytebuffer.cpp delete mode 100644 esphome/core/bytebuffer.h create mode 100644 tests/components/bytebuffer/common.yaml create mode 100644 tests/components/bytebuffer/test.esp32-ard.yaml create mode 100644 tests/components/bytebuffer/test.esp32-c3-ard.yaml create mode 100644 tests/components/bytebuffer/test.esp32-c3-idf.yaml create mode 100644 tests/components/bytebuffer/test.esp32-idf.yaml create mode 100644 tests/components/bytebuffer/test.esp8266-ard.yaml create mode 100644 tests/components/bytebuffer/test.host.yaml create mode 100644 tests/components/bytebuffer/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index f96e43d5b7..5eb1f863f2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -85,6 +85,7 @@ esphome/components/bmp581/* @kahrendt esphome/components/bp1658cj/* @Cossid esphome/components/bp5758d/* @Cossid esphome/components/button/* @esphome/core +esphome/components/bytebuffer/* @clydebarrow esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @mreditor97 esphome/components/captive_portal/* @OttoWinter diff --git a/esphome/components/bytebuffer/__init__.py b/esphome/components/bytebuffer/__init__.py new file mode 100644 index 0000000000..3c7c695118 --- /dev/null +++ b/esphome/components/bytebuffer/__init__.py @@ -0,0 +1,5 @@ +CODEOWNERS = ["@clydebarrow"] + +# Allows bytebuffer to be configured in yaml, to allow use of the C++ api. + +CONFIG_SCHEMA = {} diff --git a/esphome/components/bytebuffer/bytebuffer.h b/esphome/components/bytebuffer/bytebuffer.h new file mode 100644 index 0000000000..030484ce32 --- /dev/null +++ b/esphome/components/bytebuffer/bytebuffer.h @@ -0,0 +1,421 @@ +#pragma once + +#include <utility> +#include <vector> +#include <cinttypes> +#include <cstddef> +#include "esphome/core/helpers.h" + +namespace esphome { +namespace bytebuffer { + +enum Endian { LITTLE, BIG }; + +/** + * A class modelled on the Java ByteBuffer class. It wraps a vector of bytes and permits putting and getting + * items of various sizes, with an automatically incremented position. + * + * There are three variables maintained pointing into the buffer: + * + * capacity: the maximum amount of data that can be stored - set on construction and cannot be changed + * limit: the limit of the data currently available to get or put + * position: the current insert or extract position + * + * 0 <= position <= limit <= capacity + * + * In addition a mark can be set to the current position with mark(). A subsequent call to reset() will restore + * the position to the mark. + * + * The buffer can be marked to be little-endian (default) or big-endian. All subsequent operations will use that order. + * + * The flip() operation will reset the position to 0 and limit to the current position. This is useful for reading + * data from a buffer after it has been written. + * + * The code is defined here in the header file rather than in a .cpp file, so that it does not get compiled if not used. + * The templated functions ensure that only those typed functions actually used are compiled. The functions + * are implicitly inline-able which will aid performance. + */ +class ByteBuffer { + public: + // Default constructor (compatibility with TEMPLATABLE_VALUE) + // Creates a zero-length ByteBuffer which is little use to anybody. + ByteBuffer() : ByteBuffer(std::vector<uint8_t>()) {} + + /** + * Create a new Bytebuffer with the given capacity + */ + ByteBuffer(size_t capacity, Endian endianness = LITTLE) + : data_(std::vector<uint8_t>(capacity)), endianness_(endianness), limit_(capacity){}; + + // templated functions to implement putting and getting data of various types. There are two flavours of all + // functions - one that uses the position as the offset, and updates the position accordingly, and one that + // takes an explicit offset and does not update the position. + // Separate temnplates are provided for types that fit into 32 bits and those that are bigger. These delegate + // the actual put/get to common code based around those sizes. + // This reduces the code size and execution time for smaller types. A similar structure for e.g. 16 bits is unlikely + // to provide any further benefit given that all target platforms are native 32 bit. + + template<typename T> + T get(typename std::enable_if<std::is_integral<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { + // integral types that fit into 32 bit + return static_cast<T>(this->get_uint32_(sizeof(T))); + } + + template<typename T> + T get(size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { + return static_cast<T>(this->get_uint32_(offset, sizeof(T))); + } + + template<typename T> + void put(const T &value, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { + this->put_uint32_(static_cast<uint32_t>(value), sizeof(T)); + } + + template<typename T> + void put(const T &value, size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { + this->put_uint32_(static_cast<uint32_t>(value), offset, sizeof(T)); + } + + // integral types that do not fit into 32 bit (basically only 64 bit types) + template<typename T> + T get(typename std::enable_if<std::is_integral<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { + return static_cast<T>(this->get_uint64_(sizeof(T))); + } + + template<typename T> + T get(size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { + return static_cast<T>(this->get_uint64_(offset, sizeof(T))); + } + + template<typename T> + void put(const T &value, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { + this->put_uint64_(value, sizeof(T)); + } + + template<typename T> + void put(const T &value, size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { + this->put_uint64_(static_cast<uint64_t>(value), offset, sizeof(T)); + } + + // floating point types. Caters for 32 and 64 bit floating point. + template<typename T> + T get(typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) == sizeof(uint32_t)), T>::type * = 0) { + return bit_cast<T>(this->get_uint32_(sizeof(T))); + } + + template<typename T> + T get(typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { + return bit_cast<T>(this->get_uint64_(sizeof(T))); + } + + template<typename T> + T get(size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) == sizeof(uint32_t)), T>::type * = 0) { + return bit_cast<T>(this->get_uint32_(offset, sizeof(T))); + } + + template<typename T> + T get(size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { + return bit_cast<T>(this->get_uint64_(offset, sizeof(T))); + } + template<typename T> + void put(const T &value, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { + this->put_uint32_(bit_cast<uint32_t>(value), sizeof(T)); + } + + template<typename T> + void put(const T &value, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { + this->put_uint64_(bit_cast<uint64_t>(value), sizeof(T)); + } + + template<typename T> + void put(const T &value, size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { + this->put_uint32_(bit_cast<uint32_t>(value), offset, sizeof(T)); + } + + template<typename T> + void put(const T &value, size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0, + typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { + this->put_uint64_(bit_cast<uint64_t>(value), offset, sizeof(T)); + } + + template<typename T> static ByteBuffer wrap(T value, Endian endianness = LITTLE) { + ByteBuffer buffer = ByteBuffer(sizeof(T), endianness); + buffer.put(value); + buffer.flip(); + return buffer; + } + + static ByteBuffer wrap(std::vector<uint8_t> const &data, Endian endianness = LITTLE) { + ByteBuffer buffer = {data}; + buffer.endianness_ = endianness; + return buffer; + } + + static ByteBuffer wrap(const uint8_t *ptr, size_t len, Endian endianness = LITTLE) { + return wrap(std::vector<uint8_t>(ptr, ptr + len), endianness); + } + + // convenience functions with explicit types named.. + void put_float(float value) { this->put(value); } + void put_double(double value) { this->put(value); } + + uint8_t get_uint8() { return this->data_[this->position_++]; } + // Get a 16 bit unsigned value, increment by 2 + uint16_t get_uint16() { return this->get<uint16_t>(); } + // Get a 24 bit unsigned value, increment by 3 + uint32_t get_uint24() { return this->get_uint32_(3); }; + // Get a 32 bit unsigned value, increment by 4 + uint32_t get_uint32() { return this->get<uint32_t>(); }; + // Get a 64 bit unsigned value, increment by 8 + uint64_t get_uint64() { return this->get<uint64_t>(); }; + // Signed versions of the get functions + uint8_t get_int8() { return static_cast<int8_t>(this->get_uint8()); }; + int16_t get_int16() { return this->get<uint16_t>(); } + int32_t get_int32() { return this->get<int32_t>(); } + int64_t get_int64() { return this->get<int64_t>(); } + // Get a float value, increment by 4 + float get_float() { return this->get<float>(); } + // Get a double value, increment by 8 + double get_double() { return this->get<double>(); } + + // Get a bool value, increment by 1 + bool get_bool() { return static_cast<bool>(this->get_uint8()); } + + uint32_t get_int24(size_t offset) { + auto value = this->get_uint24(offset); + uint32_t mask = (~static_cast<uint32_t>(0)) << 23; + if ((value & mask) != 0) + value |= mask; + return value; + } + + uint32_t get_int24() { + auto value = this->get_uint24(); + uint32_t mask = (~static_cast<uint32_t>(0)) << 23; + if ((value & mask) != 0) + value |= mask; + return value; + } + std::vector<uint8_t> get_vector(size_t length, size_t offset) { + auto start = this->data_.begin() + offset; + return {start, start + length}; + } + + std::vector<uint8_t> get_vector(size_t length) { + auto result = this->get_vector(length, this->position_); + this->position_ += length; + return result; + } + + // Convenience named functions + void put_uint8(uint8_t value) { this->data_[this->position_++] = value; } + void put_uint16(uint16_t value) { this->put(value); } + void put_uint24(uint32_t value) { this->put_uint32_(value, 3); } + void put_uint32(uint32_t value) { this->put(value); } + void put_uint64(uint64_t value) { this->put(value); } + // Signed versions of the put functions + void put_int8(int8_t value) { this->put_uint8(static_cast<uint8_t>(value)); } + void put_int16(int16_t value) { this->put(value); } + void put_int24(int32_t value) { this->put_uint32_(value, 3); } + void put_int32(int32_t value) { this->put(value); } + void put_int64(int64_t value) { this->put(value); } + // Extra put functions + void put_bool(bool value) { this->put_uint8(value); } + + // versions of the above with an offset, these do not update the position + + uint64_t get_uint64(size_t offset) { return this->get<uint64_t>(offset); } + uint32_t get_uint24(size_t offset) { return this->get_uint32_(offset, 3); }; + double get_double(size_t offset) { return get<double>(offset); } + + // Get one byte from the buffer, increment position by 1 + uint8_t get_uint8(size_t offset) { return this->data_[offset]; } + // Get a 16 bit unsigned value, increment by 2 + uint16_t get_uint16(size_t offset) { return get<uint16_t>(offset); } + // Get a 24 bit unsigned value, increment by 3 + uint32_t get_uint32(size_t offset) { return this->get<uint32_t>(offset); }; + // Get a 64 bit unsigned value, increment by 8 + uint8_t get_int8(size_t offset) { return get<int8_t>(offset); } + int16_t get_int16(size_t offset) { return get<int16_t>(offset); } + int32_t get_int32(size_t offset) { return get<int32_t>(offset); } + int64_t get_int64(size_t offset) { return get<int64_t>(offset); } + // Get a float value, increment by 4 + float get_float(size_t offset) { return get<float>(offset); } + // Get a double value, increment by 8 + + // Get a bool value, increment by 1 + bool get_bool(size_t offset) { return this->get_uint8(offset); } + + void put_uint8(uint8_t value, size_t offset) { this->data_[offset] = value; } + void put_uint16(uint16_t value, size_t offset) { this->put(value, offset); } + void put_uint24(uint32_t value, size_t offset) { this->put(value, offset); } + void put_uint32(uint32_t value, size_t offset) { this->put(value, offset); } + void put_uint64(uint64_t value, size_t offset) { this->put(value, offset); } + // Signed versions of the put functions + void put_int8(int8_t value, size_t offset) { this->put_uint8(static_cast<uint8_t>(value), offset); } + void put_int16(int16_t value, size_t offset) { this->put(value, offset); } + void put_int24(int32_t value, size_t offset) { this->put_uint32_(value, offset, 3); } + void put_int32(int32_t value, size_t offset) { this->put(value, offset); } + void put_int64(int64_t value, size_t offset) { this->put(value, offset); } + // Extra put functions + void put_float(float value, size_t offset) { this->put(value, offset); } + void put_double(double value, size_t offset) { this->put(value, offset); } + void put_bool(bool value, size_t offset) { this->put_uint8(value, offset); } + void put(const std::vector<uint8_t> &value, size_t offset) { + std::copy(value.begin(), value.end(), this->data_.begin() + offset); + } + void put_vector(const std::vector<uint8_t> &value, size_t offset) { this->put(value, offset); } + void put(const std::vector<uint8_t> &value) { + this->put_vector(value, this->position_); + this->position_ += value.size(); + } + void put_vector(const std::vector<uint8_t> &value) { this->put(value); } + + // Getters + + inline size_t get_capacity() const { return this->data_.size(); } + inline size_t get_position() const { return this->position_; } + inline size_t get_limit() const { return this->limit_; } + inline size_t get_remaining() const { return this->get_limit() - this->get_position(); } + inline Endian get_endianness() const { return this->endianness_; } + inline void mark() { this->mark_ = this->position_; } + inline void big_endian() { this->endianness_ = BIG; } + inline void little_endian() { this->endianness_ = LITTLE; } + // retrieve a pointer to the underlying data. + std::vector<uint8_t> get_data() { return this->data_; }; + + void get_bytes(void *dest, size_t length) { + std::copy(this->data_.begin() + this->position_, this->data_.begin() + this->position_ + length, (uint8_t *) dest); + this->position_ += length; + } + + void get_bytes(void *dest, size_t length, size_t offset) { + std::copy(this->data_.begin() + offset, this->data_.begin() + offset + length, (uint8_t *) dest); + } + + void rewind() { this->position_ = 0; } + void reset() { this->position_ = this->mark_; } + + void set_limit(size_t limit) { this->limit_ = limit; } + void set_position(size_t position) { this->position_ = position; } + void clear() { + this->limit_ = this->get_capacity(); + this->position_ = 0; + } + void flip() { + this->limit_ = this->position_; + this->position_ = 0; + } + + protected: + uint64_t get_uint64_(size_t offset, size_t length) const { + uint64_t value = 0; + if (this->endianness_ == LITTLE) { + offset += length; + while (length-- != 0) { + value <<= 8; + value |= this->data_[--offset]; + } + } else { + while (length-- != 0) { + value <<= 8; + value |= this->data_[offset++]; + } + } + return value; + } + + uint64_t get_uint64_(size_t length) { + auto result = this->get_uint64_(this->position_, length); + this->position_ += length; + return result; + } + uint32_t get_uint32_(size_t offset, size_t length) const { + uint32_t value = 0; + if (this->endianness_ == LITTLE) { + offset += length; + while (length-- != 0) { + value <<= 8; + value |= this->data_[--offset]; + } + } else { + while (length-- != 0) { + value <<= 8; + value |= this->data_[offset++]; + } + } + return value; + } + + uint32_t get_uint32_(size_t length) { + auto result = this->get_uint32_(this->position_, length); + this->position_ += length; + return result; + } + + /// Putters + + void put_uint64_(uint64_t value, size_t length) { + this->put_uint64_(value, this->position_, length); + this->position_ += length; + } + void put_uint32_(uint32_t value, size_t length) { + this->put_uint32_(value, this->position_, length); + this->position_ += length; + } + + void put_uint64_(uint64_t value, size_t offset, size_t length) { + if (this->endianness_ == LITTLE) { + while (length-- != 0) { + this->data_[offset++] = static_cast<uint8_t>(value); + value >>= 8; + } + } else { + offset += length; + while (length-- != 0) { + this->data_[--offset] = static_cast<uint8_t>(value); + value >>= 8; + } + } + } + + void put_uint32_(uint32_t value, size_t offset, size_t length) { + if (this->endianness_ == LITTLE) { + while (length-- != 0) { + this->data_[offset++] = static_cast<uint8_t>(value); + value >>= 8; + } + } else { + offset += length; + while (length-- != 0) { + this->data_[--offset] = static_cast<uint8_t>(value); + value >>= 8; + } + } + } + ByteBuffer(std::vector<uint8_t> const &data) : data_(data), limit_(data.size()) {} + + std::vector<uint8_t> data_; + Endian endianness_{LITTLE}; + size_t position_{0}; + size_t mark_{0}; + size_t limit_{0}; +}; + +} // namespace bytebuffer +} // namespace esphome diff --git a/esphome/core/bytebuffer.cpp b/esphome/core/bytebuffer.cpp deleted file mode 100644 index 9dd32bf87a..0000000000 --- a/esphome/core/bytebuffer.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "bytebuffer.h" -#include <cassert> -#include "esphome/core/helpers.h" - -#include <list> -#include <vector> - -namespace esphome { - -ByteBuffer ByteBuffer::wrap(const uint8_t *ptr, size_t len, Endian endianness) { - // there is a double copy happening here, could be optimized but at cost of clarity. - std::vector<uint8_t> data(ptr, ptr + len); - ByteBuffer buffer = {data}; - buffer.endianness_ = endianness; - return buffer; -} - -ByteBuffer ByteBuffer::wrap(std::vector<uint8_t> const &data, Endian endianness) { - ByteBuffer buffer = {data}; - buffer.endianness_ = endianness; - return buffer; -} - -ByteBuffer ByteBuffer::wrap(uint8_t value) { - ByteBuffer buffer = ByteBuffer(1); - buffer.put_uint8(value); - buffer.flip(); - return buffer; -} - -ByteBuffer ByteBuffer::wrap(uint16_t value, Endian endianness) { - ByteBuffer buffer = ByteBuffer(2, endianness); - buffer.put_uint16(value); - buffer.flip(); - return buffer; -} - -ByteBuffer ByteBuffer::wrap(uint32_t value, Endian endianness) { - ByteBuffer buffer = ByteBuffer(4, endianness); - buffer.put_uint32(value); - buffer.flip(); - return buffer; -} - -ByteBuffer ByteBuffer::wrap(uint64_t value, Endian endianness) { - ByteBuffer buffer = ByteBuffer(8, endianness); - buffer.put_uint64(value); - buffer.flip(); - return buffer; -} - -ByteBuffer ByteBuffer::wrap(float value, Endian endianness) { - ByteBuffer buffer = ByteBuffer(sizeof(float), endianness); - buffer.put_float(value); - buffer.flip(); - return buffer; -} - -ByteBuffer ByteBuffer::wrap(double value, Endian endianness) { - ByteBuffer buffer = ByteBuffer(sizeof(double), endianness); - buffer.put_double(value); - buffer.flip(); - return buffer; -} - -void ByteBuffer::set_limit(size_t limit) { - assert(limit <= this->get_capacity()); - this->limit_ = limit; -} -void ByteBuffer::set_position(size_t position) { - assert(position <= this->get_limit()); - this->position_ = position; -} -void ByteBuffer::clear() { - this->limit_ = this->get_capacity(); - this->position_ = 0; -} -void ByteBuffer::flip() { - this->limit_ = this->position_; - this->position_ = 0; -} - -/// Getters -uint8_t ByteBuffer::get_uint8() { - assert(this->get_remaining() >= 1); - return this->data_[this->position_++]; -} -uint64_t ByteBuffer::get_uint(size_t length) { - assert(this->get_remaining() >= length); - uint64_t value = 0; - if (this->endianness_ == LITTLE) { - this->position_ += length; - auto index = this->position_; - while (length-- != 0) { - value <<= 8; - value |= this->data_[--index]; - } - } else { - while (length-- != 0) { - value <<= 8; - value |= this->data_[this->position_++]; - } - } - return value; -} - -uint32_t ByteBuffer::get_int24() { - auto value = this->get_uint24(); - uint32_t mask = (~static_cast<uint32_t>(0)) << 23; - if ((value & mask) != 0) - value |= mask; - return value; -} -float ByteBuffer::get_float() { - assert(this->get_remaining() >= sizeof(float)); - return bit_cast<float>(this->get_uint32()); -} -double ByteBuffer::get_double() { - assert(this->get_remaining() >= sizeof(double)); - return bit_cast<double>(this->get_uint64()); -} - -std::vector<uint8_t> ByteBuffer::get_vector(size_t length) { - assert(this->get_remaining() >= length); - auto start = this->data_.begin() + this->position_; - this->position_ += length; - return {start, start + length}; -} - -/// Putters -void ByteBuffer::put_uint8(uint8_t value) { - assert(this->get_remaining() >= 1); - this->data_[this->position_++] = value; -} - -void ByteBuffer::put_uint(uint64_t value, size_t length) { - assert(this->get_remaining() >= length); - if (this->endianness_ == LITTLE) { - while (length-- != 0) { - this->data_[this->position_++] = static_cast<uint8_t>(value); - value >>= 8; - } - } else { - this->position_ += length; - auto index = this->position_; - while (length-- != 0) { - this->data_[--index] = static_cast<uint8_t>(value); - value >>= 8; - } - } -} -void ByteBuffer::put_float(float value) { - static_assert(sizeof(float) == sizeof(uint32_t), "Float sizes other than 32 bit not supported"); - assert(this->get_remaining() >= sizeof(float)); - this->put_uint32(bit_cast<uint32_t>(value)); -} -void ByteBuffer::put_double(double value) { - static_assert(sizeof(double) == sizeof(uint64_t), "Double sizes other than 64 bit not supported"); - assert(this->get_remaining() >= sizeof(double)); - this->put_uint64(bit_cast<uint64_t>(value)); -} -void ByteBuffer::put_vector(const std::vector<uint8_t> &value) { - assert(this->get_remaining() >= value.size()); - std::copy(value.begin(), value.end(), this->data_.begin() + this->position_); - this->position_ += value.size(); -} -} // namespace esphome diff --git a/esphome/core/bytebuffer.h b/esphome/core/bytebuffer.h deleted file mode 100644 index d44d01f275..0000000000 --- a/esphome/core/bytebuffer.h +++ /dev/null @@ -1,144 +0,0 @@ -#pragma once - -#include <utility> -#include <vector> -#include <cinttypes> -#include <cstddef> - -namespace esphome { - -enum Endian { LITTLE, BIG }; - -/** - * A class modelled on the Java ByteBuffer class. It wraps a vector of bytes and permits putting and getting - * items of various sizes, with an automatically incremented position. - * - * There are three variables maintained pointing into the buffer: - * - * capacity: the maximum amount of data that can be stored - set on construction and cannot be changed - * limit: the limit of the data currently available to get or put - * position: the current insert or extract position - * - * 0 <= position <= limit <= capacity - * - * In addition a mark can be set to the current position with mark(). A subsequent call to reset() will restore - * the position to the mark. - * - * The buffer can be marked to be little-endian (default) or big-endian. All subsequent operations will use that order. - * - * The flip() operation will reset the position to 0 and limit to the current position. This is useful for reading - * data from a buffer after it has been written. - * - */ -class ByteBuffer { - public: - // Default constructor (compatibility with TEMPLATABLE_VALUE) - ByteBuffer() : ByteBuffer(std::vector<uint8_t>()) {} - /** - * Create a new Bytebuffer with the given capacity - */ - ByteBuffer(size_t capacity, Endian endianness = LITTLE) - : data_(std::vector<uint8_t>(capacity)), endianness_(endianness), limit_(capacity){}; - /** - * Wrap an existing vector in a ByteBufffer - */ - static ByteBuffer wrap(std::vector<uint8_t> const &data, Endian endianness = LITTLE); - /** - * Wrap an existing array in a ByteBuffer. Note that this will create a copy of the data. - */ - static ByteBuffer wrap(const uint8_t *ptr, size_t len, Endian endianness = LITTLE); - // Convenience functions to create a ByteBuffer from a value - static ByteBuffer wrap(uint8_t value); - static ByteBuffer wrap(uint16_t value, Endian endianness = LITTLE); - static ByteBuffer wrap(uint32_t value, Endian endianness = LITTLE); - static ByteBuffer wrap(uint64_t value, Endian endianness = LITTLE); - static ByteBuffer wrap(int8_t value) { return wrap(static_cast<uint8_t>(value)); } - static ByteBuffer wrap(int16_t value, Endian endianness = LITTLE) { - return wrap(static_cast<uint16_t>(value), endianness); - } - static ByteBuffer wrap(int32_t value, Endian endianness = LITTLE) { - return wrap(static_cast<uint32_t>(value), endianness); - } - static ByteBuffer wrap(int64_t value, Endian endianness = LITTLE) { - return wrap(static_cast<uint64_t>(value), endianness); - } - static ByteBuffer wrap(float value, Endian endianness = LITTLE); - static ByteBuffer wrap(double value, Endian endianness = LITTLE); - static ByteBuffer wrap(bool value) { return wrap(static_cast<uint8_t>(value)); } - - // Get an integral value from the buffer, increment position by length - uint64_t get_uint(size_t length); - // Get one byte from the buffer, increment position by 1 - uint8_t get_uint8(); - // Get a 16 bit unsigned value, increment by 2 - uint16_t get_uint16() { return static_cast<uint16_t>(this->get_uint(sizeof(uint16_t))); }; - // Get a 24 bit unsigned value, increment by 3 - uint32_t get_uint24() { return static_cast<uint32_t>(this->get_uint(3)); }; - // Get a 32 bit unsigned value, increment by 4 - uint32_t get_uint32() { return static_cast<uint32_t>(this->get_uint(sizeof(uint32_t))); }; - // Get a 64 bit unsigned value, increment by 8 - uint64_t get_uint64() { return this->get_uint(sizeof(uint64_t)); }; - // Signed versions of the get functions - uint8_t get_int8() { return static_cast<int8_t>(this->get_uint8()); }; - int16_t get_int16() { return static_cast<int16_t>(this->get_uint(sizeof(int16_t))); } - uint32_t get_int24(); - int32_t get_int32() { return static_cast<int32_t>(this->get_uint(sizeof(int32_t))); } - int64_t get_int64() { return static_cast<int64_t>(this->get_uint(sizeof(int64_t))); } - // Get a float value, increment by 4 - float get_float(); - // Get a double value, increment by 8 - double get_double(); - // Get a bool value, increment by 1 - bool get_bool() { return this->get_uint8(); } - // Get vector of bytes, increment by length - std::vector<uint8_t> get_vector(size_t length); - - // Put values into the buffer, increment the position accordingly - // put any integral value, length represents the number of bytes - void put_uint(uint64_t value, size_t length); - void put_uint8(uint8_t value); - void put_uint16(uint16_t value) { this->put_uint(value, sizeof(uint16_t)); } - void put_uint24(uint32_t value) { this->put_uint(value, 3); } - void put_uint32(uint32_t value) { this->put_uint(value, sizeof(uint32_t)); } - void put_uint64(uint64_t value) { this->put_uint(value, sizeof(uint64_t)); } - // Signed versions of the put functions - void put_int8(int8_t value) { this->put_uint8(static_cast<uint8_t>(value)); } - void put_int16(int32_t value) { this->put_uint(static_cast<uint16_t>(value), sizeof(uint16_t)); } - void put_int24(int32_t value) { this->put_uint(static_cast<uint32_t>(value), 3); } - void put_int32(int32_t value) { this->put_uint(static_cast<uint32_t>(value), sizeof(uint32_t)); } - void put_int64(int64_t value) { this->put_uint(static_cast<uint64_t>(value), sizeof(uint64_t)); } - // Extra put functions - void put_float(float value); - void put_double(double value); - void put_bool(bool value) { this->put_uint8(value); } - void put_vector(const std::vector<uint8_t> &value); - - inline size_t get_capacity() const { return this->data_.size(); } - inline size_t get_position() const { return this->position_; } - inline size_t get_limit() const { return this->limit_; } - inline size_t get_remaining() const { return this->get_limit() - this->get_position(); } - inline Endian get_endianness() const { return this->endianness_; } - inline void mark() { this->mark_ = this->position_; } - inline void big_endian() { this->endianness_ = BIG; } - inline void little_endian() { this->endianness_ = LITTLE; } - void set_limit(size_t limit); - void set_position(size_t position); - // set position to 0, limit to capacity. - void clear(); - // set limit to current position, postition to zero. Used when swapping from write to read operations. - void flip(); - // retrieve a pointer to the underlying data. - std::vector<uint8_t> get_data() { return this->data_; }; - void rewind() { this->position_ = 0; } - void reset() { this->position_ = this->mark_; } - - protected: - ByteBuffer(std::vector<uint8_t> const &data) : data_(data), limit_(data.size()) {} - std::vector<uint8_t> data_; - Endian endianness_{LITTLE}; - size_t position_{0}; - size_t mark_{0}; - size_t limit_{0}; -}; - -} // namespace esphome diff --git a/tests/components/bytebuffer/common.yaml b/tests/components/bytebuffer/common.yaml new file mode 100644 index 0000000000..177f487e1e --- /dev/null +++ b/tests/components/bytebuffer/common.yaml @@ -0,0 +1,161 @@ +bytebuffer: + +esphome: + on_boot: + - lambda: |- + using namespace bytebuffer; + auto buf = ByteBuffer(16); + assert(buf.get_endianness() == LITTLE); + assert(buf.get_remaining() == 16); + buf.set_limit(10); + assert(buf.get_capacity() == 16); + buf.put_uint8(1); + assert(buf.get_remaining() == 9); + buf.put_uint16(0xABCD); + auto da = buf.get_data(); + assert(buf.get_uint8(0) == 1); + auto x = buf.get_uint16(1); + assert(buf.get_uint16(1) == 0xABCD); + assert(buf.get_remaining() == 7); + buf.put_uint32(0x12345678UL); + assert(buf.get_uint32(3) == 0x12345678UL); + assert(buf.get_remaining() == 3); + assert(buf.get_data()[1] == 0xCD); + assert(buf.get_data()[2] == 0xAB); + assert(buf.get_data()[3] == 0x78); + assert(buf.get_data()[4] == 0x56); + assert(buf.get_data()[5] == 0x34); + assert(buf.get_data()[6] == 0x12); + buf.flip(); + assert(buf.get_capacity() == 16); + assert(buf.get_uint32(3) == 0x12345678UL); + assert(buf.get_uint8(0) == 1); + assert(buf.get_uint16(1) == 0xABCD); + buf.put_uint16(0x1234, 1); + assert(buf.get_uint16(1) == 0x1234); + assert(buf.get_remaining() == 7); + assert(buf.get_uint8() == 1); + assert(buf.get_uint16() == 0x1234); + assert(buf.get_uint32() == 0x12345678ul); + assert(buf.get_remaining() == 0); + assert(buf.get_remaining() == 0); + buf.rewind(); + buf.big_endian(); + assert(buf.get_remaining() == 7); + assert(buf.get_uint8() == 1); + assert(buf.get_uint16() == 0x3412); + buf.mark(); + assert(buf.get_uint32() == 0x78563412ul); + assert(buf.get_remaining() == 0); + buf.reset(); + assert(buf.get_remaining() == 4); + assert(buf.get_uint32() == 0x78563412ul); + auto buf1 = ByteBuffer::wrap(buf.get_data().data(), buf.get_limit()); + buf.clear(); + assert(buf.get_position() == 0); + assert(buf.get_capacity() == 16); + assert(buf.get_limit() == 16); + assert(buf1.get_remaining() == 7); + assert(buf1.get_capacity() == 7); + buf1.set_position(3); + assert(buf1.get_uint32() == 0x12345678ul); + buf1.clear(); + assert(buf1.get_limit() == 7); + assert(buf1.get_capacity() == 7); + assert(buf1.get_position() == 0); + float f = 1.2345; + buf1.put_float(f); + buf1.flip(); + assert(buf1.get_remaining() == 4); + assert(buf1.get_float() == f); + buf1.clear(); + buf1.put_uint16(-32760); + buf1.put_uint24(-302760); + buf1.flip(); + assert(buf1.get_int16() == -32760); + assert(buf1.get_int24() == -302760); + uint8_t arr[4] = {0x10, 0x20, 0x30, 0x40}; + buf1 = ByteBuffer::wrap(arr, 4); + assert(buf1.get_capacity() == 4); + assert(buf1.get_limit() == 4); + assert(buf1.get_position() == 0); + assert(buf1.get_uint32() == 0x40302010UL); + assert(buf1.get_position() == 4); + assert(buf1.get_remaining() == 0); + std::vector<uint8_t> vec{}; + vec.push_back(0x10); + vec.push_back(0x20); + vec.push_back(0x30); + vec.push_back(0x40); + buf1 = ByteBuffer::wrap(vec); + assert(buf1.get_capacity() == 4); + assert(buf1.get_limit() == 4); + assert(buf1.get_position() == 0); + buf1.mark(); + buf1.reset(); + assert(buf1.get_uint32() == 0x40302010UL); + buf = ByteBuffer::wrap(true); + assert(buf.get_bool() == true); + buf = ByteBuffer::wrap((uint8_t)0xFE); + assert(buf.get_uint8() == 0xFE); + buf = ByteBuffer::wrap((uint16_t)0xA5A6, BIG); + assert(buf.get_remaining() == 2); + assert(buf.get_position() == 0); + assert(buf.get_capacity() == 2); + assert(buf.get_endianness() == BIG); + assert(buf.get_data()[0] == 0xA5); + assert(buf.get_uint16() == 0xA5A6); + buf.flip(); + buf.little_endian(); + assert(buf.get_uint16() == 0xA6A5); + buf = ByteBuffer::wrap(f, BIG); + assert(buf.get_float() == f); + double d = 1.2345678E7; + buf = ByteBuffer::wrap(d, BIG); + assert(buf.get_double() == d); + buf = ByteBuffer::wrap({1, 2, 3, 4}, BIG); + assert(buf.get_endianness() == BIG); + assert(buf.get_remaining() == 4); + assert(buf.get_data()[2] == 3); + buf.little_endian(); + assert(buf.get_data()[2] == 3); + assert(buf.get_uint16() == 0x0201); + buf.big_endian(); + assert(buf.get_uint16() == 0x0304); + buf.rewind(); + vec = buf.get_vector(3); + assert(buf.get_remaining() == 1); + assert(vec[0] == 1); + assert(vec.size() == 3); + buf = ByteBuffer(10); + buf.put_vector(vec); + assert(buf.get_remaining() == 7); + buf.flip(); + assert(buf.get_remaining() == 3); + assert(buf.get_uint24() == 0x030201); + buf = ByteBuffer(64); + buf.put_uint8(1, 1); + buf.put_uint16(16, 2); + buf.put_uint32(1232, 4); + buf.put_uint64(123432ul, 8); + buf.put_float(1.2f, 16); + buf.put_int24(0x678, 20); + + assert(buf.get_uint8(1) == 1); + assert(buf.get<uint8_t>(1) == 1); + assert(buf.get_uint16(2) == 16); + assert(buf.get<uint16_t>(2) == 16); + assert(buf.get_uint32(4) == 1232); + assert(buf.get<uint32_t>(4) == 1232); + assert(buf.get_uint64(8) == 123432ul); + assert(buf.get<uint64_t>(8) == 123432ul); + assert(buf.get_float(16) == 1.2f); + assert(buf.get<float>(16) == 1.2f); + assert(buf.get_int24(20) == 0x678); + buf.clear(); + buf.put(1.234, 10); + double dx = buf.get<double>(10); + assert(dx == 1.234); + buf.put((uint16_t)1, 10); + assert(buf.get_uint16(10) == 1); + ESP_LOGD("bytebuffer", "******************** All tests succeeded"); diff --git a/tests/components/bytebuffer/test.esp32-ard.yaml b/tests/components/bytebuffer/test.esp32-ard.yaml new file mode 100644 index 0000000000..380ca87628 --- /dev/null +++ b/tests/components/bytebuffer/test.esp32-ard.yaml @@ -0,0 +1 @@ +!include common.yaml diff --git a/tests/components/bytebuffer/test.esp32-c3-ard.yaml b/tests/components/bytebuffer/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..380ca87628 --- /dev/null +++ b/tests/components/bytebuffer/test.esp32-c3-ard.yaml @@ -0,0 +1 @@ +!include common.yaml diff --git a/tests/components/bytebuffer/test.esp32-c3-idf.yaml b/tests/components/bytebuffer/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..380ca87628 --- /dev/null +++ b/tests/components/bytebuffer/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +!include common.yaml diff --git a/tests/components/bytebuffer/test.esp32-idf.yaml b/tests/components/bytebuffer/test.esp32-idf.yaml new file mode 100644 index 0000000000..380ca87628 --- /dev/null +++ b/tests/components/bytebuffer/test.esp32-idf.yaml @@ -0,0 +1 @@ +!include common.yaml diff --git a/tests/components/bytebuffer/test.esp8266-ard.yaml b/tests/components/bytebuffer/test.esp8266-ard.yaml new file mode 100644 index 0000000000..380ca87628 --- /dev/null +++ b/tests/components/bytebuffer/test.esp8266-ard.yaml @@ -0,0 +1 @@ +!include common.yaml diff --git a/tests/components/bytebuffer/test.host.yaml b/tests/components/bytebuffer/test.host.yaml new file mode 100644 index 0000000000..380ca87628 --- /dev/null +++ b/tests/components/bytebuffer/test.host.yaml @@ -0,0 +1 @@ +!include common.yaml diff --git a/tests/components/bytebuffer/test.rp2040-ard.yaml b/tests/components/bytebuffer/test.rp2040-ard.yaml new file mode 100644 index 0000000000..380ca87628 --- /dev/null +++ b/tests/components/bytebuffer/test.rp2040-ard.yaml @@ -0,0 +1 @@ +!include common.yaml From 88627095fbc049ca342325be22d72e66e9b9c28a Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:12:32 +1100 Subject: [PATCH 049/282] [http_request] Always return defined server response status (#7689) --- esphome/components/http_request/http_request_arduino.cpp | 3 +-- esphome/components/http_request/http_request_idf.cpp | 3 +-- esphome/components/http_request/ota/ota_http_request.cpp | 2 +- esphome/components/http_request/update/http_request_update.cpp | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp index 2148d92ad2..f000082034 100644 --- a/esphome/components/http_request/http_request_arduino.cpp +++ b/esphome/components/http_request/http_request_arduino.cpp @@ -116,8 +116,7 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s if (container->status_code < 200 || container->status_code >= 300) { ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code); this->status_momentary_error("failed", 1000); - container->end(); - return nullptr; + // Still return the container, so it can be used to get the status code and error message } int content_length = container->client_.getSize(); diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index 3819f5544e..e47e1d488e 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -172,8 +172,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code); this->status_momentary_error("failed", 1000); - esp_http_client_cleanup(client); - return nullptr; + return container; } int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 1553de0bc1..f7c941da18 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -106,7 +106,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() { auto container = this->parent_->get(url_with_auth); - if (container == nullptr) { + if (container == nullptr || container->status_code != 200) { return OTA_CONNECTION_ERROR; } diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 059148e7e5..4d913f2158 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -31,7 +31,7 @@ void HttpRequestUpdate::setup() { void HttpRequestUpdate::update() { auto container = this->request_parent_->get(this->source_url_); - if (container == nullptr) { + if (container == nullptr || container->status_code != 200) { std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str()); this->status_set_error(msg.c_str()); return; From 63e4d4b493ec6c704ac6d41273b4190614726578 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:56:32 +1100 Subject: [PATCH 050/282] [font] Fix failure with bitmap fonts (#7691) --- esphome/components/font/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index dacd0779b1..a3f11df50e 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -344,7 +344,7 @@ class TrueTypeFontWrapper: return offset_x, offset_y def getmask(self, glyph, **kwargs): - return self.font.getmask(glyph, **kwargs) + return self.font.getmask(str(glyph), **kwargs) def getmetrics(self, glyphs): return self.font.getmetrics() @@ -359,7 +359,7 @@ class BitmapFontWrapper: return 0, 0 def getmask(self, glyph, **kwargs): - return self.font.getmask(glyph, **kwargs) + return self.font.getmask(str(glyph), **kwargs) def getmetrics(self, glyphs): max_height = 0 From df750d0d11ce31e793de06d0e00663c8e23a3680 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:05:58 +1100 Subject: [PATCH 051/282] [http_request] Add enum for status codes (#7690) --- .../components/http_request/http_request.h | 57 +++++++++++++++++++ .../http_request/http_request_arduino.cpp | 2 +- .../http_request/http_request_idf.cpp | 17 ++---- .../http_request/ota/ota_http_request.cpp | 2 +- .../update/http_request_update.cpp | 2 +- 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index c01baf8644..d87d9b8a45 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -22,6 +22,63 @@ struct Header { const char *value; }; +// Some common HTTP status codes +enum HttpStatus { + HTTP_STATUS_OK = 200, + HTTP_STATUS_NO_CONTENT = 204, + HTTP_STATUS_PARTIAL_CONTENT = 206, + + /* 3xx - Redirection */ + HTTP_STATUS_MULTIPLE_CHOICES = 300, + HTTP_STATUS_MOVED_PERMANENTLY = 301, + HTTP_STATUS_FOUND = 302, + HTTP_STATUS_SEE_OTHER = 303, + HTTP_STATUS_NOT_MODIFIED = 304, + HTTP_STATUS_TEMPORARY_REDIRECT = 307, + HTTP_STATUS_PERMANENT_REDIRECT = 308, + + /* 4XX - CLIENT ERROR */ + HTTP_STATUS_BAD_REQUEST = 400, + HTTP_STATUS_UNAUTHORIZED = 401, + HTTP_STATUS_FORBIDDEN = 403, + HTTP_STATUS_NOT_FOUND = 404, + HTTP_STATUS_METHOD_NOT_ALLOWED = 405, + HTTP_STATUS_NOT_ACCEPTABLE = 406, + HTTP_STATUS_LENGTH_REQUIRED = 411, + + /* 5xx - Server Error */ + HTTP_STATUS_INTERNAL_ERROR = 500 +}; + +/** + * @brief Returns true if the HTTP status code is a redirect. + * + * @param status the HTTP status code to check + * @return true if the status code is a redirect, false otherwise + */ +inline bool is_redirect(int const status) { + switch (status) { + case HTTP_STATUS_MOVED_PERMANENTLY: + case HTTP_STATUS_FOUND: + case HTTP_STATUS_SEE_OTHER: + case HTTP_STATUS_TEMPORARY_REDIRECT: + case HTTP_STATUS_PERMANENT_REDIRECT: + return true; + default: + return false; + } +} + +/** + * @brief Checks if the given HTTP status code indicates a successful request. + * + * A successful request is one where the status code is in the range 200-299 + * + * @param status the HTTP status code to check + * @return true if the status code indicates a successful request, false otherwise + */ +inline bool is_success(int const status) { return status >= HTTP_STATUS_OK && status < HTTP_STATUS_MULTIPLE_CHOICES; } + class HttpRequestComponent; class HttpContainer : public Parented<HttpRequestComponent> { diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp index f000082034..af1eb6f459 100644 --- a/esphome/components/http_request/http_request_arduino.cpp +++ b/esphome/components/http_request/http_request_arduino.cpp @@ -113,7 +113,7 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s return nullptr; } - if (container->status_code < 200 || container->status_code >= 300) { + if (!is_success(container->status_code)) { ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code); this->status_momentary_error("failed", 1000); // Still return the container, so it can be used to get the status code and error message diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index e47e1d488e..c6c567b620 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -6,7 +6,6 @@ #include "esphome/components/watchdog/watchdog.h" #include "esphome/core/application.h" -#include "esphome/core/defines.h" #include "esphome/core/log.h" #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE @@ -118,20 +117,14 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin return nullptr; } - auto is_ok = [](int code) { return code >= HttpStatus_Ok && code < HttpStatus_MultipleChoices; }; - container->content_length = esp_http_client_fetch_headers(client); container->status_code = esp_http_client_get_status_code(client); - if (is_ok(container->status_code)) { + if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; } if (this->follow_redirects_) { - auto is_redirect = [](int code) { - return code == HttpStatus_MovedPermanently || code == HttpStatus_Found || code == HttpStatus_SeeOther || - code == HttpStatus_TemporaryRedirect || code == HttpStatus_PermanentRedirect; - }; auto num_redirects = this->redirect_limit_; while (is_redirect(container->status_code) && num_redirects > 0) { err = esp_http_client_set_redirection(client); @@ -142,9 +135,9 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin return nullptr; } #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - char url[256]{}; - if (esp_http_client_get_url(client, url, sizeof(url) - 1) == ESP_OK) { - ESP_LOGV(TAG, "redirecting to url: %s", url); + char redirect_url[256]{}; + if (esp_http_client_get_url(client, redirect_url, sizeof(redirect_url) - 1) == ESP_OK) { + ESP_LOGV(TAG, "redirecting to url: %s", redirect_url); } #endif err = esp_http_client_open(client, 0); @@ -157,7 +150,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin container->content_length = esp_http_client_fetch_headers(client); container->status_code = esp_http_client_get_status_code(client); - if (is_ok(container->status_code)) { + if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; } diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index f7c941da18..cec30d72ec 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -106,7 +106,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() { auto container = this->parent_->get(url_with_auth); - if (container == nullptr || container->status_code != 200) { + if (container == nullptr || container->status_code != HTTP_STATUS_OK) { return OTA_CONNECTION_ERROR; } diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 4d913f2158..0e0966c22b 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -31,7 +31,7 @@ void HttpRequestUpdate::setup() { void HttpRequestUpdate::update() { auto container = this->request_parent_->get(this->source_url_); - if (container == nullptr || container->status_code != 200) { + 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; From 302ba2874e12960c76a624fa6066bf5df1ffac2e Mon Sep 17 00:00:00 2001 From: Satoshi YAMADA <slakichi@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:08:08 +0900 Subject: [PATCH 052/282] Support W5500 SPI-Ethernet polling mode if framework is supported (#7503) --- esphome/components/ethernet/__init__.py | 56 ++++++++++++++++++- .../ethernet/ethernet_component.cpp | 15 ++++- .../components/ethernet/ethernet_component.h | 8 ++- esphome/const.py | 1 + 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 475d60df53..dca37b8dc2 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -1,3 +1,4 @@ +import logging from esphome import pins import esphome.codegen as cg from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant @@ -23,6 +24,7 @@ from esphome.const import ( CONF_MISO_PIN, CONF_MOSI_PIN, CONF_PAGE_ID, + CONF_POLLING_INTERVAL, CONF_RESET_PIN, CONF_SPI, CONF_STATIC_IP, @@ -30,13 +32,16 @@ from esphome.const import ( CONF_TYPE, CONF_USE_ADDRESS, CONF_VALUE, + KEY_CORE, + KEY_FRAMEWORK_VERSION, ) -from esphome.core import CORE, coroutine_with_priority +from esphome.core import CORE, TimePeriodMilliseconds, coroutine_with_priority import esphome.final_validate as fv CONFLICTS_WITH = ["wifi"] DEPENDENCIES = ["esp32"] AUTO_LOAD = ["network"] +LOGGER = logging.getLogger(__name__) ethernet_ns = cg.esphome_ns.namespace("ethernet") PHYRegister = ethernet_ns.struct("PHYRegister") @@ -63,6 +68,7 @@ ETHERNET_TYPES = { } SPI_ETHERNET_TYPES = ["W5500"] +SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10) emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t") @@ -100,6 +106,24 @@ EthernetComponent = ethernet_ns.class_("EthernetComponent", cg.Component) ManualIP = ethernet_ns.struct("ManualIP") +def _is_framework_spi_polling_mode_supported(): + # SPI Ethernet without IRQ feature is added in + # esp-idf >= (5.3+ ,5.2.1+, 5.1.4) and arduino-esp32 >= 3.0.0 + framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + if CORE.using_esp_idf: + if framework_version >= cv.Version(5, 3, 0): + return True + if cv.Version(5, 3, 0) > framework_version >= cv.Version(5, 2, 1): + return True + if cv.Version(5, 2, 0) > framework_version >= cv.Version(5, 1, 4): + return True + return False + if CORE.using_arduino: + return framework_version >= cv.Version(3, 0, 0) + # fail safe: Unknown framework + return False + + def _validate(config): if CONF_USE_ADDRESS not in config: if CONF_MANUAL_IP in config: @@ -107,6 +131,27 @@ def _validate(config): else: use_address = CORE.name + config[CONF_DOMAIN] config[CONF_USE_ADDRESS] = use_address + if config[CONF_TYPE] in SPI_ETHERNET_TYPES: + if _is_framework_spi_polling_mode_supported(): + if CONF_POLLING_INTERVAL in config and CONF_INTERRUPT_PIN in config: + raise cv.Invalid( + f"Cannot specify more than one of {CONF_INTERRUPT_PIN}, {CONF_POLLING_INTERVAL}" + ) + if CONF_POLLING_INTERVAL not in config and CONF_INTERRUPT_PIN not in config: + config[CONF_POLLING_INTERVAL] = SPI_ETHERNET_DEFAULT_POLLING_INTERVAL + else: + if CONF_POLLING_INTERVAL in config: + raise cv.Invalid( + "In this version of the framework " + f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), " + f"'{CONF_POLLING_INTERVAL}' is not supported." + ) + if CONF_INTERRUPT_PIN not in config: + raise cv.Invalid( + "In this version of the framework " + f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), " + f"'{CONF_INTERRUPT_PIN}' is a required option for [ethernet]." + ) return config @@ -157,6 +202,11 @@ SPI_SCHEMA = BASE_SCHEMA.extend( cv.Optional(CONF_CLOCK_SPEED, default="26.67MHz"): cv.All( cv.frequency, cv.int_range(int(8e6), int(80e6)) ), + # Set default value (SPI_ETHERNET_DEFAULT_POLLING_INTERVAL) at _validate() + cv.Optional(CONF_POLLING_INTERVAL): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(min=TimePeriodMilliseconds(milliseconds=1)), + ), } ), ) @@ -234,6 +284,10 @@ async def to_code(config): cg.add(var.set_cs_pin(config[CONF_CS_PIN])) if CONF_INTERRUPT_PIN in config: cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN])) + else: + cg.add(var.set_polling_interval(config[CONF_POLLING_INTERVAL])) + if _is_framework_spi_polling_mode_supported(): + cg.add_define("USE_ETHERNET_SPI_POLLING_SUPPORT") if CONF_RESET_PIN in config: cg.add(var.set_reset_pin(config[CONF_RESET_PIN])) cg.add(var.set_clock_speed(config[CONF_CLOCK_SPEED])) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 00c7ae4ab8..08f5fa6642 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -116,6 +116,9 @@ void EthernetComponent::setup() { eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); #endif w5500_config.int_gpio_num = this->interrupt_pin_; +#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT + w5500_config.poll_period_ms = this->polling_interval_; +#endif phy_config.phy_addr = this->phy_addr_spi_; phy_config.reset_gpio_num = this->reset_pin_; @@ -327,7 +330,14 @@ void EthernetComponent::dump_config() { ESP_LOGCONFIG(TAG, " MISO Pin: %u", this->miso_pin_); ESP_LOGCONFIG(TAG, " MOSI Pin: %u", this->mosi_pin_); ESP_LOGCONFIG(TAG, " CS Pin: %u", this->cs_pin_); - ESP_LOGCONFIG(TAG, " IRQ Pin: %u", this->interrupt_pin_); +#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT + if (this->polling_interval_ != 0) { + ESP_LOGCONFIG(TAG, " Polling Interval: %lu ms", this->polling_interval_); + } else +#endif + { + ESP_LOGCONFIG(TAG, " IRQ Pin: %d", this->interrupt_pin_); + } ESP_LOGCONFIG(TAG, " Reset Pin: %d", this->reset_pin_); ESP_LOGCONFIG(TAG, " Clock Speed: %d MHz", this->clock_speed_ / 1000000); #else @@ -536,6 +546,9 @@ void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; } void EthernetComponent::set_interrupt_pin(uint8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; } void EthernetComponent::set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; } void EthernetComponent::set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; } +#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT +void EthernetComponent::set_polling_interval(uint32_t polling_interval) { this->polling_interval_ = polling_interval; } +#endif #else void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; } diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 5ee430c046..fb178431d5 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -67,6 +67,9 @@ class EthernetComponent : public Component { void set_interrupt_pin(uint8_t interrupt_pin); void set_reset_pin(uint8_t reset_pin); void set_clock_speed(int clock_speed); +#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT + void set_polling_interval(uint32_t polling_interval); +#endif #else void set_phy_addr(uint8_t phy_addr); void set_power_pin(int power_pin); @@ -108,10 +111,13 @@ class EthernetComponent : public Component { uint8_t miso_pin_; uint8_t mosi_pin_; uint8_t cs_pin_; - uint8_t interrupt_pin_; + int interrupt_pin_{-1}; int reset_pin_{-1}; int phy_addr_spi_{-1}; int clock_speed_; +#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT + uint32_t polling_interval_{0}; +#endif #else uint8_t phy_addr_{0}; int power_pin_{-1}; diff --git a/esphome/const.py b/esphome/const.py index 54ebb2815f..c39061631b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -666,6 +666,7 @@ CONF_PMC_1_0 = "pmc_1_0" CONF_PMC_10_0 = "pmc_10_0" CONF_PMC_2_5 = "pmc_2_5" CONF_PMC_4_0 = "pmc_4_0" +CONF_POLLING_INTERVAL = "polling_interval" CONF_PORT = "port" CONF_POSITION = "position" CONF_POSITION_ACTION = "position_action" From 444c0fc67f4284e42e00ad61466febe999122690 Mon Sep 17 00:00:00 2001 From: Jordan Zucker <jordan.zucker@gmail.com> Date: Mon, 28 Oct 2024 20:09:22 -0700 Subject: [PATCH 053/282] Add asdf to gitignore (and dockerignore) (#7686) --- .dockerignore | 3 +++ .gitignore | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.dockerignore b/.dockerignore index 9f14b98059..7998ff877f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -75,6 +75,9 @@ target/ # pyenv .python-version +# asdf +.tool-versions + # celery beat schedule file celerybeat-schedule diff --git a/.gitignore b/.gitignore index 79820249ac..ad38e26fdd 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,9 @@ cov.xml # pyenv .python-version +# asdf +.tool-versions + # Environments .env .venv From 90b076eccd67253fb9ce612251130a76039e549d Mon Sep 17 00:00:00 2001 From: Jordan Zucker <jordan.zucker@gmail.com> Date: Mon, 28 Oct 2024 20:43:02 -0700 Subject: [PATCH 054/282] Add more prometheus metrics (#7683) --- .../prometheus/prometheus_handler.cpp | 43 +++++++++++++++++++ .../prometheus/prometheus_handler.h | 7 +++ tests/components/prometheus/common.yaml | 14 ++++++ 3 files changed, 64 insertions(+) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 3e9cf81e6e..63b3fdf53f 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -50,6 +50,12 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { this->lock_row_(stream, obj); #endif +#ifdef USE_TEXT_SENSOR + this->text_sensor_type_(stream); + for (auto *obj : App.get_text_sensors()) + this->text_sensor_row_(stream, obj); +#endif + req->send(stream); } @@ -349,6 +355,43 @@ void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) } #endif +// Type-specific implementation +#ifdef USE_TEXT_SENSOR +void PrometheusHandler::text_sensor_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_text_sensor_value gauge\n")); + stream->print(F("#TYPE esphome_text_sensor_failed gauge\n")); +} +void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_sensor::TextSensor *obj) { + if (obj->is_internal() && !this->include_internal_) + return; + if (obj->has_state()) { + // We have a valid value, output this value + stream->print(F("esphome_text_sensor_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_text_sensor_value{id=\"")); + stream->print(relabel_id_(obj).c_str()); + 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_text_sensor_failed{id=\"")); + stream->print(relabel_id_(obj).c_str()); + stream->print(F("\",name=\"")); + stream->print(relabel_name_(obj).c_str()); + stream->print(F("\"} 1\n")); + } +} +#endif + } // namespace prometheus } // namespace esphome #endif diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index f5e49a1419..512e1bee4f 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -110,6 +110,13 @@ class PrometheusHandler : public AsyncWebHandler, public Component { void lock_row_(AsyncResponseStream *stream, lock::Lock *obj); #endif +#ifdef USE_TEXT_SENSOR + /// Return the type for prometheus + void text_sensor_type_(AsyncResponseStream *stream); + /// Return the lock Values state as prometheus data point + void text_sensor_row_(AsyncResponseStream *stream, text_sensor::TextSensor *obj); +#endif + web_server_base::WebServerBase *base_; bool include_internal_{false}; std::map<EntityBase *, std::string> relabel_map_id_; diff --git a/tests/components/prometheus/common.yaml b/tests/components/prometheus/common.yaml index c8ce17da88..7aa509f8b2 100644 --- a/tests/components/prometheus/common.yaml +++ b/tests/components/prometheus/common.yaml @@ -13,9 +13,23 @@ sensor: } update_interval: 60s +text_sensor: + - platform: template + id: template_text_sensor1 + lambda: |- + if (millis() > 10000) { + return {"Hello World"}; + } else { + return {"Goodbye (cruel) World"}; + } + update_interval: 60s + prometheus: include_internal: true relabel: template_sensor1: id: hellow_world name: Hello World + template_text_sensor1: + id: hello_text + name: Text Substitution From 0dab280440de853d57d750463b3be4e065fd480d Mon Sep 17 00:00:00 2001 From: Sean Brogan <spbrogan@live.com> Date: Mon, 28 Oct 2024 20:49:06 -0700 Subject: [PATCH 055/282] Mopeka Pro Check improvement to allow user to configure the sensor reporting for lower quality readings (#7475) --- .../mopeka_pro_check/mopeka_pro_check.cpp | 63 ++++++++++++------- .../mopeka_pro_check/mopeka_pro_check.h | 11 +++- esphome/components/mopeka_pro_check/sensor.py | 41 ++++++++++++ tests/components/mopeka_pro_check/common.yaml | 17 +++++ 4 files changed, 108 insertions(+), 24 deletions(-) diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp index f79e40bb4e..9527f09f59 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -17,6 +17,8 @@ void MopekaProCheck::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); LOG_SENSOR(" ", "Reading Distance", this->distance_); + LOG_SENSOR(" ", "Read Quality", this->read_quality_); + LOG_SENSOR(" ", "Ignored Reads", this->ignored_reads_); } /** @@ -66,34 +68,49 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) this->battery_level_->publish_state(level); } + // Get the quality value + SensorReadQuality quality_value = this->parse_read_quality_(manu_data.data); + if (this->read_quality_ != nullptr) { + this->read_quality_->publish_state(static_cast<int>(quality_value)); + } + + // Determine if we have a good enough quality of read to report level and distance + // sensors. This sensor is reported regardless of distance or level sensors being enabled + if (quality_value < this->min_signal_quality_) { + ESP_LOGW(TAG, "Read Quality too low to report distance or level"); + this->ignored_read_count_++; + } else { + // reset to zero since read quality was sufficient + this->ignored_read_count_ = 0; + } + // Report number of contiguous ignored reads if sensor defined + if (this->ignored_reads_ != nullptr) { + this->ignored_reads_->publish_state(this->ignored_read_count_); + } + // Get distance and level if either are sensors if ((this->distance_ != nullptr) || (this->level_ != nullptr)) { uint32_t distance_value = this->parse_distance_(manu_data.data); - SensorReadQuality quality_value = this->parse_read_quality_(manu_data.data); ESP_LOGD(TAG, "Distance Sensor: Quality (0x%X) Distance (%" PRId32 "mm)", quality_value, distance_value); - if (quality_value < QUALITY_HIGH) { - ESP_LOGW(TAG, "Poor read quality."); - } - if (quality_value < QUALITY_MED) { - // if really bad reading set to 0 - ESP_LOGW(TAG, "Setting distance to 0"); - distance_value = 0; - } - // update distance sensor - if (this->distance_ != nullptr) { - this->distance_->publish_state(distance_value); - } - - // update level sensor - if (this->level_ != nullptr) { - uint8_t tank_level = 0; - if (distance_value >= this->full_mm_) { - tank_level = 100; // cap at 100% - } else if (distance_value > this->empty_mm_) { - tank_level = ((100.0f / (this->full_mm_ - this->empty_mm_)) * (distance_value - this->empty_mm_)); + // only update distance and level sensors if read quality was sufficient. This can be determined by + // if the ignored_read_count is zero. + if (this->ignored_read_count_ == 0) { + // update distance sensor + if (this->distance_ != nullptr) { + this->distance_->publish_state(distance_value); + } + + // update level sensor + if (this->level_ != nullptr) { + uint8_t tank_level = 0; + if (distance_value >= this->full_mm_) { + tank_level = 100; // cap at 100% + } else if (distance_value > this->empty_mm_) { + tank_level = ((100.0f / (this->full_mm_ - this->empty_mm_)) * (distance_value - this->empty_mm_)); + } + this->level_->publish_state(tank_level); } - this->level_->publish_state(tank_level); } } @@ -131,6 +148,8 @@ uint32_t MopekaProCheck::parse_distance_(const std::vector<uint8_t> &message) { uint8_t MopekaProCheck::parse_temperature_(const std::vector<uint8_t> &message) { return (message[2] & 0x7F) - 40; } SensorReadQuality MopekaProCheck::parse_read_quality_(const std::vector<uint8_t> &message) { + // Since a 8 bit value is being shifted and truncated to 2 bits all possible values are defined as enumeration + // value and the static cast is safe. return static_cast<SensorReadQuality>(message[4] >> 6); } diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.h b/esphome/components/mopeka_pro_check/mopeka_pro_check.h index 8b4d47e4c6..c58406ac18 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.h +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.h @@ -24,9 +24,9 @@ enum SensorType { }; // Sensor read quality. If sensor is poorly placed or tank level -// gets too low the read quality will show and the distanace +// gets too low the read quality will show and the distance // measurement may be inaccurate. -enum SensorReadQuality { QUALITY_HIGH = 0x3, QUALITY_MED = 0x2, QUALITY_LOW = 0x1, QUALITY_NONE = 0x0 }; +enum SensorReadQuality { QUALITY_HIGH = 0x3, QUALITY_MED = 0x2, QUALITY_LOW = 0x1, QUALITY_ZERO = 0x0 }; class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: @@ -35,11 +35,14 @@ class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceLi bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } + void set_min_signal_quality(SensorReadQuality min) { this->min_signal_quality_ = min; }; void set_level(sensor::Sensor *level) { level_ = level; }; void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }; void set_battery_level(sensor::Sensor *bat) { battery_level_ = bat; }; void set_distance(sensor::Sensor *distance) { distance_ = distance; }; + void set_signal_quality(sensor::Sensor *rq) { read_quality_ = rq; }; + void set_ignored_reads(sensor::Sensor *ir) { ignored_reads_ = ir; }; void set_tank_full(float full) { full_mm_ = full; }; void set_tank_empty(float empty) { empty_mm_ = empty; }; @@ -49,9 +52,13 @@ class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceLi sensor::Sensor *temperature_{nullptr}; sensor::Sensor *distance_{nullptr}; sensor::Sensor *battery_level_{nullptr}; + sensor::Sensor *read_quality_{nullptr}; + sensor::Sensor *ignored_reads_{nullptr}; uint32_t full_mm_; uint32_t empty_mm_; + uint32_t ignored_read_count_ = 0; + SensorReadQuality min_signal_quality_ = QUALITY_MED; uint8_t parse_battery_level_(const std::vector<uint8_t> &message); uint32_t parse_distance_(const std::vector<uint8_t> &message); diff --git a/esphome/components/mopeka_pro_check/sensor.py b/esphome/components/mopeka_pro_check/sensor.py index 0ba33e94de..95ade53013 100644 --- a/esphome/components/mopeka_pro_check/sensor.py +++ b/esphome/components/mopeka_pro_check/sensor.py @@ -5,9 +5,12 @@ from esphome.const import ( CONF_DISTANCE, CONF_MAC_ADDRESS, CONF_ID, + ICON_COUNTER, ICON_THERMOMETER, ICON_RULER, + ICON_SIGNAL, UNIT_PERCENT, + UNIT_EMPTY, CONF_LEVEL, CONF_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, @@ -16,11 +19,15 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, CONF_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, + ENTITY_CATEGORY_DIAGNOSTIC, ) CONF_TANK_TYPE = "tank_type" CONF_CUSTOM_DISTANCE_FULL = "custom_distance_full" CONF_CUSTOM_DISTANCE_EMPTY = "custom_distance_empty" +CONF_SIGNAL_QUALITY = "signal_quality" +CONF_MINIMUM_SIGNAL_QUALITY = "minimum_signal_quality" +CONF_IGNORED_READS = "ignored_reads" ICON_PROPANE_TANK = "mdi:propane-tank" @@ -56,6 +63,14 @@ MopekaProCheck = mopeka_pro_check_ns.class_( "MopekaProCheck", esp32_ble_tracker.ESPBTDeviceListener, cg.Component ) +SensorReadQuality = mopeka_pro_check_ns.enum("SensorReadQuality") +SIGNAL_QUALITIES = { + "ZERO": SensorReadQuality.QUALITY_ZERO, + "LOW": SensorReadQuality.QUALITY_LOW, + "MEDIUM": SensorReadQuality.QUALITY_MED, + "HIGH": SensorReadQuality.QUALITY_HIGH, +} + CONFIG_SCHEMA = ( cv.Schema( { @@ -89,6 +104,21 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_SIGNAL_QUALITY): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_SIGNAL, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_IGNORED_READS): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_COUNTER, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_MINIMUM_SIGNAL_QUALITY, default="MEDIUM"): cv.enum( + SIGNAL_QUALITIES, upper=True + ), } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -119,6 +149,11 @@ async def to_code(config): cg.add(var.set_tank_empty(CONF_SUPPORTED_TANKS_MAP[t][0])) cg.add(var.set_tank_full(CONF_SUPPORTED_TANKS_MAP[t][1])) + if ( + minimum_signal_quality := config.get(CONF_MINIMUM_SIGNAL_QUALITY, None) + ) is not None: + cg.add(var.set_min_signal_quality(minimum_signal_quality)) + if CONF_TEMPERATURE in config: sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) cg.add(var.set_temperature(sens)) @@ -131,3 +166,9 @@ async def to_code(config): if CONF_BATTERY_LEVEL in config: sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) cg.add(var.set_battery_level(sens)) + if CONF_SIGNAL_QUALITY in config: + sens = await sensor.new_sensor(config[CONF_SIGNAL_QUALITY]) + cg.add(var.set_signal_quality(sens)) + if CONF_IGNORED_READS in config: + sens = await sensor.new_sensor(config[CONF_IGNORED_READS]) + cg.add(var.set_ignored_reads(sens)) diff --git a/tests/components/mopeka_pro_check/common.yaml b/tests/components/mopeka_pro_check/common.yaml index 147cbcb9de..3533ecf631 100644 --- a/tests/components/mopeka_pro_check/common.yaml +++ b/tests/components/mopeka_pro_check/common.yaml @@ -14,3 +14,20 @@ sensor: name: Propane test distance battery_level: name: Propane test battery level + + - platform: mopeka_pro_check + mac_address: AA:BB:CC:DD:EE:FF + tank_type: 20LB_V + temperature: + name: "Propane test2 temp" + level: + name: "Propane test2 level" + distance: + name: "Propane test2 distance" + battery_level: + name: "Propane test2 battery level" + signal_quality: + name: "propane test2 read quality" + ignored_reads: + name: "propane test2 ignored reads" + minimum_signal_quality: "LOW" From aa0e155e22c422c789feeb0b1be9495f703fc8fa Mon Sep 17 00:00:00 2001 From: Bonne Eggleston <bonne@exciton.com.au> Date: Mon, 28 Oct 2024 20:52:39 -0700 Subject: [PATCH 056/282] Fixes modbus timing error (#7674) --- esphome/components/modbus/modbus.cpp | 36 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index f8dd4c18b9..8544b50261 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -15,23 +15,33 @@ void Modbus::setup() { void Modbus::loop() { const uint32_t now = millis(); - if (now - this->last_modbus_byte_ > 50) { - this->rx_buffer_.clear(); - this->last_modbus_byte_ = now; - } - // stop blocking new send commands after send_wait_time_ ms regardless if a response has been received since then - if (now - this->last_send_ > send_wait_time_) { - waiting_for_response = 0; - } - while (this->available()) { uint8_t byte; this->read_byte(&byte); if (this->parse_modbus_byte_(byte)) { this->last_modbus_byte_ = now; } else { + size_t at = this->rx_buffer_.size(); + if (at > 0) { + ESP_LOGV(TAG, "Clearing buffer of %d bytes - parse failed", at); + this->rx_buffer_.clear(); + } + } + } + + if (now - this->last_modbus_byte_ > 50) { + size_t at = this->rx_buffer_.size(); + if (at > 0) { + ESP_LOGV(TAG, "Clearing buffer of %d bytes - timeout", at); this->rx_buffer_.clear(); } + + // stop blocking new send commands after sent_wait_time_ ms after response received + if (now - this->last_send_ > send_wait_time_) { + if (waiting_for_response > 0) + ESP_LOGV(TAG, "Stop waiting for response from %d", waiting_for_response); + waiting_for_response = 0; + } } } @@ -39,7 +49,7 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { size_t at = this->rx_buffer_.size(); this->rx_buffer_.push_back(byte); const uint8_t *raw = &this->rx_buffer_[0]; - ESP_LOGV(TAG, "Modbus received Byte %d (0X%x)", byte, byte); + ESP_LOGVV(TAG, "Modbus received Byte %d (0X%x)", byte, byte); // Byte 0: modbus address (match all) if (at == 0) return true; @@ -144,8 +154,10 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X! ", address); } - // return false to reset buffer - return false; + // reset buffer + ESP_LOGV(TAG, "Clearing buffer of %d bytes - parse succeeded", at); + this->rx_buffer_.clear(); + return true; } void Modbus::dump_config() { From abbd7faa641220fc3cbc2e77f76f3bfcf41546ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Mart=C3=ADn?= <contact@rodrigomartin.dev> Date: Tue, 29 Oct 2024 04:56:50 +0100 Subject: [PATCH 057/282] fix(WiFi): Fix strncpy missing NULL terminator [-Werror=stringop-truncation] (#7668) --- esphome/components/wifi/wifi_component.cpp | 4 ++-- esphome/components/wifi/wifi_component_esp32_arduino.cpp | 8 ++++---- esphome/components/wifi/wifi_component_esp8266.cpp | 8 ++++---- esphome/components/wifi/wifi_component_esp_idf.cpp | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 583a27466a..8788711d5a 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -297,8 +297,8 @@ void WiFiComponent::set_sta(const WiFiAP &ap) { void WiFiComponent::clear_sta() { this->sta_.clear(); } void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &password) { SavedWifiSettings save{}; - strncpy(save.ssid, ssid.c_str(), sizeof(save.ssid)); - strncpy(save.password, password.c_str(), sizeof(save.password)); + snprintf(save.ssid, sizeof(save.ssid), "%s", ssid.c_str()); + snprintf(save.password, sizeof(save.password), "%s", password.c_str()); this->pref_.save(&save); // ensure it's written immediately global_preferences->sync(); diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index ef4308b28c..88648093c6 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -137,8 +137,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast<char *>(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); - strncpy(reinterpret_cast<char *>(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); + snprintf(reinterpret_cast<char *>(conf.sta.ssid), sizeof(conf.sta.ssid), "%s", ap.get_ssid().c_str()); + snprintf(reinterpret_cast<char *>(conf.sta.password), sizeof(conf.sta.password), "%s", ap.get_password().c_str()); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -746,7 +746,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast<char *>(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); + snprintf(reinterpret_cast<char *>(conf.ap.ssid), sizeof(conf.ap.ssid), "%s", ap.get_ssid().c_str()); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -757,7 +757,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strncpy(reinterpret_cast<char *>(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password)); + snprintf(reinterpret_cast<char *>(conf.ap.password), sizeof(conf.ap.password), "%s", ap.get_password().c_str()); } // pairwise cipher of SoftAP, group cipher will be derived using this. diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 92f80c1e52..4568895950 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -236,8 +236,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { struct station_config conf {}; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), sizeof(conf.ssid)); - strncpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), sizeof(conf.password)); + snprintf(reinterpret_cast<char *>(conf.ssid), sizeof(conf.ssid), "%s", ap.get_ssid().c_str()); + snprintf(reinterpret_cast<char *>(conf.password), sizeof(conf.password), "%s", ap.get_password().c_str()); if (ap.get_bssid().has_value()) { conf.bssid_set = 1; @@ -775,7 +775,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return false; struct softap_config conf {}; - strncpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), sizeof(conf.ssid)); + snprintf(reinterpret_cast<char *>(conf.ssid), sizeof(conf.ssid), "%s", ap.get_ssid().c_str()); conf.ssid_len = static_cast<uint8>(ap.get_ssid().size()); conf.channel = ap.get_channel().value_or(1); conf.ssid_hidden = ap.get_hidden(); @@ -787,7 +787,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.password = 0; } else { conf.authmode = AUTH_WPA2_PSK; - strncpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), sizeof(conf.password)); + snprintf(reinterpret_cast<char *>(conf.password), sizeof(conf.password), "%s", ap.get_password().c_str()); } ETS_UART_INTR_DISABLE(); diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 0f2e181e31..13870136d4 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -289,8 +289,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast<char *>(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); - strncpy(reinterpret_cast<char *>(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); + snprintf(reinterpret_cast<char *>(conf.sta.ssid), sizeof(conf.sta.ssid), "%s", ap.get_ssid().c_str()); + snprintf(reinterpret_cast<char *>(conf.sta.password), sizeof(conf.sta.password), "%s", ap.get_password().c_str()); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { From 71e1e3b5f8575a3c075b90dbf573c95927a9bb0a Mon Sep 17 00:00:00 2001 From: tomaszduda23 <tomaszduda23@gmail.com> Date: Tue, 29 Oct 2024 04:58:36 +0100 Subject: [PATCH 058/282] let make new platform implementation in external components (#7615) Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com> --- esphome/core/helpers.cpp | 57 ++++++++++++++++++++++++---------------- esphome/core/helpers.h | 4 +++ 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index dca35819ff..8f94f624f1 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -10,6 +10,7 @@ #include <cstdarg> #include <cstdio> #include <cstring> +#include <strings.h> #ifdef USE_HOST #ifndef _WIN32 @@ -188,37 +189,39 @@ uint32_t fnv1_hash(const std::string &str) { return hash; } -uint32_t random_uint32() { #ifdef USE_ESP32 - return esp_random(); +uint32_t random_uint32() { return esp_random(); } #elif defined(USE_ESP8266) - return os_random(); +uint32_t random_uint32() { return os_random(); } #elif defined(USE_RP2040) +uint32_t random_uint32() { uint32_t result = 0; for (uint8_t i = 0; i < 32; i++) { result <<= 1; result |= rosc_hw->randombit; } return result; +} #elif defined(USE_LIBRETINY) - return rand(); +uint32_t random_uint32() { return rand(); } #elif defined(USE_HOST) +uint32_t random_uint32() { std::random_device dev; std::mt19937 rng(dev()); std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max()); return dist(rng); -#else -#error "No random source available for this configuration." -#endif } +#endif float random_float() { return static_cast<float>(random_uint32()) / static_cast<float>(UINT32_MAX); } -bool random_bytes(uint8_t *data, size_t len) { #ifdef USE_ESP32 +bool random_bytes(uint8_t *data, size_t len) { esp_fill_random(data, len); return true; +} #elif defined(USE_ESP8266) - return os_get_random(data, len) == 0; +bool random_bytes(uint8_t *data, size_t len) { return os_get_random(data, len) == 0; } #elif defined(USE_RP2040) +bool random_bytes(uint8_t *data, size_t len) { while (len-- != 0) { uint8_t result = 0; for (uint8_t i = 0; i < 8; i++) { @@ -228,10 +231,14 @@ bool random_bytes(uint8_t *data, size_t len) { *data++ = result; } return true; +} #elif defined(USE_LIBRETINY) +bool random_bytes(uint8_t *data, size_t len) { lt_rand_bytes(data, len); return true; +} #elif defined(USE_HOST) +bool random_bytes(uint8_t *data, size_t len) { FILE *fp = fopen("/dev/urandom", "r"); if (fp == nullptr) { ESP_LOGW(TAG, "Could not open /dev/urandom, errno=%d", errno); @@ -244,10 +251,8 @@ bool random_bytes(uint8_t *data, size_t len) { } fclose(fp); return true; -#else -#error "No random source available for this configuration." -#endif } +#endif // Strings @@ -619,11 +624,13 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green #if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_HOST) // ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS. Mutex::Mutex() {} +Mutex::~Mutex() {} void Mutex::lock() {} bool Mutex::try_lock() { return true; } void Mutex::unlock() {} #elif defined(USE_ESP32) || defined(USE_LIBRETINY) Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); } +Mutex::~Mutex() {} void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); } bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; } void Mutex::unlock() { xSemaphoreGive(this->handle_); } @@ -657,11 +664,13 @@ void HighFrequencyLoopRequester::stop() { } bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0; } -void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) #if defined(USE_HOST) +void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) static const uint8_t esphome_host_mac_address[6] = USE_ESPHOME_HOST_MAC_ADDRESS; memcpy(mac, esphome_host_mac_address, sizeof(esphome_host_mac_address)); +} #elif defined(USE_ESP32) +void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) #if defined(CONFIG_SOC_IEEE802154_SUPPORTED) // When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default // returns the 802.15.4 EUI-64 address, so we read directly from eFuse instead. @@ -677,16 +686,20 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame esp_efuse_mac_get_default(mac); } #endif -#elif defined(USE_ESP8266) - wifi_get_macaddr(STATION_IF, mac); -#elif defined(USE_RP2040) && defined(USE_WIFI) - WiFi.macAddress(mac); -#elif defined(USE_LIBRETINY) - WiFi.macAddress(mac); -#else -// this should be an error, but that messes with CI checks. #error No mac address method defined -#endif } +#elif defined(USE_ESP8266) +void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) + wifi_get_macaddr(STATION_IF, mac); +} +#elif defined(USE_RP2040) && defined(USE_WIFI) +void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) + WiFi.macAddress(mac); +} +#elif defined(USE_LIBRETINY) +void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) + WiFi.macAddress(mac); +} +#endif std::string get_mac_address() { uint8_t mac[6]; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 7f6fe9bfdc..43001bafdd 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -7,6 +7,7 @@ #include <string> #include <type_traits> #include <vector> +#include <limits> #include "esphome/core/optional.h" @@ -545,6 +546,7 @@ class Mutex { public: Mutex(); Mutex(const Mutex &) = delete; + ~Mutex(); void lock(); bool try_lock(); void unlock(); @@ -554,6 +556,8 @@ class Mutex { private: #if defined(USE_ESP32) || defined(USE_LIBRETINY) SemaphoreHandle_t handle_; +#else + void *handle_; // d-pointer to store private data on new platforms #endif }; From 38dd566e0cbb51b4eb6f14a8fcb03b36b7b962dc Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Mon, 28 Oct 2024 21:12:54 -0700 Subject: [PATCH 059/282] remove use of delay (#7680) Co-authored-by: Samuel Sieb <samuel@sieb.net> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/sgp4x/sgp4x.cpp | 114 ++++++++++------------------- esphome/components/sgp4x/sgp4x.h | 8 +- 2 files changed, 45 insertions(+), 77 deletions(-) diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index 7e474b9371..bf91c90832 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -111,7 +111,7 @@ void SGP4xComponent::setup() { number of records reported from being overwhelming. */ ESP_LOGD(TAG, "Component requires sampling of 1Hz, setting up background sampler"); - this->set_interval(1000, [this]() { this->update_gas_indices(); }); + this->set_interval(1000, [this]() { this->take_sample(); }); } void SGP4xComponent::self_test_() { @@ -146,31 +146,15 @@ void SGP4xComponent::self_test_() { }); } -/** - * @brief Combined the measured gasses, temperature, and humidity - * to calculate the VOC Index - * - * @param temperature The measured temperature in degrees C - * @param humidity The measured relative humidity in % rH - * @return int32_t The VOC Index - */ -bool SGP4xComponent::measure_gas_indices_(int32_t &voc, int32_t &nox) { - uint16_t voc_sraw; - uint16_t nox_sraw; - if (!measure_raw_(voc_sraw, nox_sraw)) - return false; - - this->status_clear_warning(); - - voc = voc_algorithm_.process(voc_sraw); - if (nox_sensor_) { - nox = nox_algorithm_.process(nox_sraw); - } - ESP_LOGV(TAG, "VOC = %" PRId32 ", NOx = %" PRId32, voc, nox); +void SGP4xComponent::update_gas_indices_() { + this->voc_index_ = this->voc_algorithm_.process(this->voc_sraw_); + if (this->nox_sensor_ != nullptr) + this->nox_index_ = this->nox_algorithm_.process(this->nox_sraw_); + ESP_LOGV(TAG, "VOC = %" PRId32 ", NOx = %" PRId32, this->voc_index_, this->nox_index_); // Store baselines after defined interval or if the difference between current and stored baseline becomes too // much if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { - voc_algorithm_.get_states(this->voc_state0_, this->voc_state1_); + this->voc_algorithm_.get_states(this->voc_state0_, this->voc_state1_); if (std::abs(this->voc_baselines_storage_.state0 - this->voc_state0_) > MAXIMUM_STORAGE_DIFF || std::abs(this->voc_baselines_storage_.state1 - this->voc_state1_) > MAXIMUM_STORAGE_DIFF) { this->seconds_since_last_store_ = 0; @@ -179,29 +163,27 @@ bool SGP4xComponent::measure_gas_indices_(int32_t &voc, int32_t &nox) { if (this->pref_.save(&this->voc_baselines_storage_)) { ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04" PRIX32 " ,state1: 0x%04" PRIX32, - this->voc_baselines_storage_.state0, voc_baselines_storage_.state1); + this->voc_baselines_storage_.state0, this->voc_baselines_storage_.state1); } else { ESP_LOGW(TAG, "Could not store VOC baselines"); } } } - return true; + if (this->samples_read_ < this->samples_to_stabilize_) { + this->samples_read_++; + ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %" PRIu32, this->samples_read_, + this->samples_to_stabilize_, this->voc_index_); + } } -/** - * @brief Return the raw gas measurement - * - * @param temperature The measured temperature in degrees C - * @param humidity The measured relative humidity in % rH - * @return uint16_t The current raw gas measurement - */ -bool SGP4xComponent::measure_raw_(uint16_t &voc_raw, uint16_t &nox_raw) { + +void SGP4xComponent::measure_raw_() { float humidity = NAN; static uint32_t nox_conditioning_start = millis(); if (!this->self_test_complete_) { ESP_LOGD(TAG, "Self-test not yet complete"); - return false; + return; } if (this->humidity_sensor_ != nullptr) { humidity = this->humidity_sensor_->state; @@ -243,61 +225,45 @@ bool SGP4xComponent::measure_raw_(uint16_t &voc_raw, uint16_t &nox_raw) { data[1] = tempticks; if (!this->write_command(command, data, 2)) { - this->status_set_warning(); ESP_LOGD(TAG, "write error (%d)", this->last_error_); - return false; + this->status_set_warning("measurement request failed"); + return; } - delay(measure_time_); - uint16_t raw_data[2]; - raw_data[1] = 0; - if (!this->read_data(raw_data, response_words)) { - this->status_set_warning(); - ESP_LOGD(TAG, "read error (%d)", this->last_error_); - return false; - } - voc_raw = raw_data[0]; - nox_raw = raw_data[1]; // either 0 or the measured NOx ticks - return true; + + this->set_timeout(this->measure_time_, [this, response_words]() { + uint16_t raw_data[2]; + raw_data[1] = 0; + if (!this->read_data(raw_data, response_words)) { + ESP_LOGD(TAG, "read error (%d)", this->last_error_); + this->status_set_warning("measurement read failed"); + this->voc_index_ = this->nox_index_ = UINT16_MAX; + return; + } + this->voc_sraw_ = raw_data[0]; + this->nox_sraw_ = raw_data[1]; // either 0 or the measured NOx ticks + this->status_clear_warning(); + this->update_gas_indices_(); + }); } -void SGP4xComponent::update_gas_indices() { +void SGP4xComponent::take_sample() { if (!this->self_test_complete_) return; - this->seconds_since_last_store_ += 1; - if (!this->measure_gas_indices_(this->voc_index_, this->nox_index_)) { - // Set values to UINT16_MAX to indicate failure - this->voc_index_ = this->nox_index_ = UINT16_MAX; - ESP_LOGE(TAG, "measure gas indices failed"); - return; - } - if (this->samples_read_ < this->samples_to_stabilize_) { - this->samples_read_++; - ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %" PRIu32, this->samples_read_, - this->samples_to_stabilize_, this->voc_index_); - return; - } + this->measure_raw_(); } void SGP4xComponent::update() { if (this->samples_read_ < this->samples_to_stabilize_) { return; } - if (this->voc_sensor_) { - if (this->voc_index_ != UINT16_MAX) { - this->status_clear_warning(); + if (this->voc_sensor_ != nullptr) { + if (this->voc_index_ != UINT16_MAX) this->voc_sensor_->publish_state(this->voc_index_); - } else { - this->status_set_warning(); - } } - if (this->nox_sensor_) { - if (this->nox_index_ != UINT16_MAX) { - this->status_clear_warning(); + if (this->nox_sensor_ != nullptr) { + if (this->nox_index_ != UINT16_MAX) this->nox_sensor_->publish_state(this->nox_index_); - } else { - this->status_set_warning(); - } } } @@ -329,7 +295,7 @@ void SGP4xComponent::dump_config() { } LOG_UPDATE_INTERVAL(this); - if (this->humidity_sensor_ != nullptr && this->temperature_sensor_ != nullptr) { + if (this->humidity_sensor_ != nullptr || this->temperature_sensor_ != nullptr) { ESP_LOGCONFIG(TAG, " Compensation:"); LOG_SENSOR(" ", "Temperature Source:", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity Source:", this->humidity_sensor_); diff --git a/esphome/components/sgp4x/sgp4x.h b/esphome/components/sgp4x/sgp4x.h index aa5ae4b9d2..959ff12c27 100644 --- a/esphome/components/sgp4x/sgp4x.h +++ b/esphome/components/sgp4x/sgp4x.h @@ -73,7 +73,7 @@ class SGP4xComponent : public PollingComponent, public sensor::Sensor, public se void setup() override; void update() override; - void update_gas_indices(); + void take_sample(); void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } void set_store_baseline(bool store_baseline) { store_baseline_ = store_baseline; } @@ -108,8 +108,10 @@ class SGP4xComponent : public PollingComponent, public sensor::Sensor, public se sensor::Sensor *temperature_sensor_{nullptr}; int16_t sensirion_init_sensors_(); - bool measure_gas_indices_(int32_t &voc, int32_t &nox); - bool measure_raw_(uint16_t &voc_raw, uint16_t &nox_raw); + void update_gas_indices_(); + void measure_raw_(); + uint16_t voc_sraw_; + uint16_t nox_sraw_; SgpType sgp_type_{SGP40}; uint64_t serial_number_; From 0982ab58ac1ff8c027bae542908e204b5d053728 Mon Sep 17 00:00:00 2001 From: tomaszduda23 <tomaszduda23@gmail.com> Date: Tue, 29 Oct 2024 19:53:36 +0100 Subject: [PATCH 060/282] fix build error (#7694) Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com> --- esphome/core/helpers.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 8f94f624f1..dae60a4e1d 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -691,9 +691,11 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) wifi_get_macaddr(STATION_IF, mac); } -#elif defined(USE_RP2040) && defined(USE_WIFI) +#elif defined(USE_RP2040) void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) +#ifdef USE_WIFI WiFi.macAddress(mac); +#endif } #elif defined(USE_LIBRETINY) void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) From bac6880a1e9df93a38cb6a7ba467c28a78209c2f Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov <hostcc@users.noreply.github.com> Date: Wed, 30 Oct 2024 02:32:55 +0300 Subject: [PATCH 061/282] fix: [climate] Allow substitutions in `visual.temperature_step.{target_temperature,current_temperature}` (#7679) --- esphome/components/climate/__init__.py | 29 +++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index b302e2ab4e..ec68940726 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -119,10 +119,21 @@ visual_temperature = cv.float_with_unit( ) -def single_visual_temperature(value): - if isinstance(value, dict): - return value +VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema( + { + cv.Required(CONF_TARGET_TEMPERATURE): visual_temperature, + cv.Required(CONF_CURRENT_TEMPERATURE): visual_temperature, + } +) + +def visual_temperature_step(value): + + # Allow defining target/current temperature steps separately + if isinstance(value, dict): + return VISUAL_TEMPERATURE_STEP_SCHEMA(value) + + # Otherwise, use the single value for both properties value = visual_temperature(value) return VISUAL_TEMPERATURE_STEP_SCHEMA( { @@ -141,16 +152,6 @@ ControlTrigger = climate_ns.class_( "ControlTrigger", automation.Trigger.template(ClimateCall.operator("ref")) ) -VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any( - single_visual_temperature, - cv.Schema( - { - cv.Required(CONF_TARGET_TEMPERATURE): visual_temperature, - cv.Required(CONF_CURRENT_TEMPERATURE): visual_temperature, - } - ), -) - CLIMATE_SCHEMA = ( cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) @@ -162,7 +163,7 @@ CLIMATE_SCHEMA = ( { cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, - cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA, + cv.Optional(CONF_TEMPERATURE_STEP): visual_temperature_step, cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int, cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int, } From aae2ee2ecb34222ad61b52064f10006e86fc0fa3 Mon Sep 17 00:00:00 2001 From: Jordan Zucker <jordan.zucker@gmail.com> Date: Tue, 29 Oct 2024 18:03:10 -0700 Subject: [PATCH 062/282] Add in area and device to the prometheus labels (#7692) --- .../prometheus/prometheus_handler.cpp | 151 ++++++++++++++++-- .../prometheus/prometheus_handler.h | 27 +++- tests/components/prometheus/common.yaml | 54 +++++++ 3 files changed, 208 insertions(+), 24 deletions(-) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 63b3fdf53f..5d1861202a 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -7,53 +7,56 @@ namespace prometheus { void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { AsyncResponseStream *stream = req->beginResponseStream("text/plain; version=0.0.4; charset=utf-8"); + std::string area = App.get_area(); + std::string node = App.get_name(); + std::string friendly_name = App.get_friendly_name(); #ifdef USE_SENSOR this->sensor_type_(stream); for (auto *obj : App.get_sensors()) - this->sensor_row_(stream, obj); + this->sensor_row_(stream, obj, area, node, friendly_name); #endif #ifdef USE_BINARY_SENSOR this->binary_sensor_type_(stream); for (auto *obj : App.get_binary_sensors()) - this->binary_sensor_row_(stream, obj); + this->binary_sensor_row_(stream, obj, area, node, friendly_name); #endif #ifdef USE_FAN this->fan_type_(stream); for (auto *obj : App.get_fans()) - this->fan_row_(stream, obj); + this->fan_row_(stream, obj, area, node, friendly_name); #endif #ifdef USE_LIGHT this->light_type_(stream); for (auto *obj : App.get_lights()) - this->light_row_(stream, obj); + this->light_row_(stream, obj, area, node, friendly_name); #endif #ifdef USE_COVER this->cover_type_(stream); for (auto *obj : App.get_covers()) - this->cover_row_(stream, obj); + this->cover_row_(stream, obj, area, node, friendly_name); #endif #ifdef USE_SWITCH this->switch_type_(stream); for (auto *obj : App.get_switches()) - this->switch_row_(stream, obj); + this->switch_row_(stream, obj, area, node, friendly_name); #endif #ifdef USE_LOCK this->lock_type_(stream); for (auto *obj : App.get_locks()) - this->lock_row_(stream, obj); + this->lock_row_(stream, obj, area, node, friendly_name); #endif #ifdef USE_TEXT_SENSOR this->text_sensor_type_(stream); for (auto *obj : App.get_text_sensors()) - this->text_sensor_row_(stream, obj); + this->text_sensor_row_(stream, obj, area, node, friendly_name); #endif req->send(stream); @@ -69,25 +72,53 @@ std::string PrometheusHandler::relabel_name_(EntityBase *obj) { return item == relabel_map_name_.end() ? obj->get_name() : item->second; } +void PrometheusHandler::add_area_label_(AsyncResponseStream *stream, std::string &area) { + if (!area.empty()) { + stream->print(F("\",area=\"")); + stream->print(area.c_str()); + } +} + +void PrometheusHandler::add_node_label_(AsyncResponseStream *stream, std::string &node) { + if (!node.empty()) { + stream->print(F("\",node=\"")); + stream->print(node.c_str()); + } +} + +void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, std::string &friendly_name) { + if (!friendly_name.empty()) { + stream->print(F("\",friendly_name=\"")); + stream->print(friendly_name.c_str()); + } +} + // Type-specific implementation #ifdef USE_SENSOR void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_sensor_value gauge\n")); stream->print(F("#TYPE esphome_sensor_failed gauge\n")); } -void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj) { +void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor *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_sensor_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_sensor_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("\",unit=\"")); @@ -99,6 +130,9 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor // Invalid state stream->print(F("esphome_sensor_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")); @@ -112,19 +146,26 @@ void PrometheusHandler::binary_sensor_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_binary_sensor_value gauge\n")); stream->print(F("#TYPE esphome_binary_sensor_failed gauge\n")); } -void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_sensor::BinarySensor *obj) { +void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_sensor::BinarySensor *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_binary_sensor_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_binary_sensor_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("\"} ")); @@ -134,6 +175,9 @@ void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_s // Invalid state stream->print(F("esphome_binary_sensor_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")); @@ -148,17 +192,24 @@ void PrometheusHandler::fan_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_fan_speed gauge\n")); stream->print(F("#TYPE esphome_fan_oscillation gauge\n")); } -void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { +void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj, std::string &area, std::string &node, + std::string &friendly_name) { if (obj->is_internal() && !this->include_internal_) return; stream->print(F("esphome_fan_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_fan_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("\"} ")); @@ -168,6 +219,9 @@ void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { if (obj->get_traits().supports_speed()) { stream->print(F("esphome_fan_speed{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("\"} ")); @@ -178,6 +232,9 @@ void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { if (obj->get_traits().supports_oscillation()) { stream->print(F("esphome_fan_oscillation{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("\"} ")); @@ -193,12 +250,16 @@ void PrometheusHandler::light_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_light_color gauge\n")); stream->print(F("#TYPE esphome_light_effect_active gauge\n")); } -void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightState *obj) { +void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightState *obj, std::string &area, + std::string &node, std::string &friendly_name) { if (obj->is_internal() && !this->include_internal_) return; // State stream->print(F("esphome_light_state{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("\"} ")); @@ -211,6 +272,9 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat color.as_rgbw(&r, &g, &b, &w); stream->print(F("esphome_light_color{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("\",channel=\"brightness\"} ")); @@ -218,6 +282,9 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat stream->print(F("\n")); stream->print(F("esphome_light_color{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("\",channel=\"r\"} ")); @@ -225,6 +292,9 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat stream->print(F("\n")); stream->print(F("esphome_light_color{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("\",channel=\"g\"} ")); @@ -232,6 +302,9 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat stream->print(F("\n")); stream->print(F("esphome_light_color{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("\",channel=\"b\"} ")); @@ -239,6 +312,9 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat stream->print(F("\n")); stream->print(F("esphome_light_color{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("\",channel=\"w\"} ")); @@ -249,12 +325,18 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat if (effect == "None") { stream->print(F("esphome_light_effect_active{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("\",effect=\"None\"} 0\n")); } else { stream->print(F("esphome_light_effect_active{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("\",effect=\"")); @@ -269,19 +351,26 @@ void PrometheusHandler::cover_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_cover_value gauge\n")); stream->print(F("#TYPE esphome_cover_failed gauge\n")); } -void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *obj) { +void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *obj, std::string &area, std::string &node, + std::string &friendly_name) { if (obj->is_internal() && !this->include_internal_) return; if (!std::isnan(obj->position)) { // We have a valid value, output this value stream->print(F("esphome_cover_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_cover_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("\"} ")); @@ -290,6 +379,9 @@ void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *ob if (obj->get_traits().get_supports_tilt()) { stream->print(F("esphome_cover_tilt{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("\"} ")); @@ -300,6 +392,9 @@ void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *ob // Invalid state stream->print(F("esphome_cover_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")); @@ -312,17 +407,24 @@ void PrometheusHandler::switch_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_switch_value gauge\n")); stream->print(F("#TYPE esphome_switch_failed gauge\n")); } -void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch *obj) { +void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch *obj, std::string &area, + std::string &node, std::string &friendly_name) { if (obj->is_internal() && !this->include_internal_) return; stream->print(F("esphome_switch_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_switch_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("\"} ")); @@ -336,17 +438,24 @@ void PrometheusHandler::lock_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_lock_value gauge\n")); stream->print(F("#TYPE esphome_lock_failed gauge\n")); } -void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) { +void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj, std::string &area, std::string &node, + std::string &friendly_name) { if (obj->is_internal() && !this->include_internal_) return; stream->print(F("esphome_lock_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_lock_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("\"} ")); @@ -361,19 +470,26 @@ void PrometheusHandler::text_sensor_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_text_sensor_value gauge\n")); stream->print(F("#TYPE esphome_text_sensor_failed gauge\n")); } -void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_sensor::TextSensor *obj) { +void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_sensor::TextSensor *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_text_sensor_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_text_sensor_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=\"")); @@ -385,6 +501,9 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso // Invalid state stream->print(F("esphome_text_sensor_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")); diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 512e1bee4f..5d08aca63a 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -60,61 +60,72 @@ class PrometheusHandler : public AsyncWebHandler, public Component { protected: std::string relabel_id_(EntityBase *obj); std::string relabel_name_(EntityBase *obj); + void add_area_label_(AsyncResponseStream *stream, std::string &area); + void add_node_label_(AsyncResponseStream *stream, std::string &node); + void add_friendly_name_label_(AsyncResponseStream *stream, std::string &friendly_name); #ifdef USE_SENSOR /// Return the type for prometheus void sensor_type_(AsyncResponseStream *stream); /// Return the sensor state as prometheus data point - void sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj); + void sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj, std::string &area, std::string &node, + std::string &friendly_name); #endif #ifdef USE_BINARY_SENSOR /// Return the type for prometheus void binary_sensor_type_(AsyncResponseStream *stream); /// Return the sensor state as prometheus data point - void binary_sensor_row_(AsyncResponseStream *stream, binary_sensor::BinarySensor *obj); + void binary_sensor_row_(AsyncResponseStream *stream, binary_sensor::BinarySensor *obj, std::string &area, + std::string &node, std::string &friendly_name); #endif #ifdef USE_FAN /// Return the type for prometheus void fan_type_(AsyncResponseStream *stream); /// Return the sensor state as prometheus data point - void fan_row_(AsyncResponseStream *stream, fan::Fan *obj); + void fan_row_(AsyncResponseStream *stream, fan::Fan *obj, std::string &area, std::string &node, + std::string &friendly_name); #endif #ifdef USE_LIGHT /// Return the type for prometheus void light_type_(AsyncResponseStream *stream); /// Return the Light Values state as prometheus data point - void light_row_(AsyncResponseStream *stream, light::LightState *obj); + void light_row_(AsyncResponseStream *stream, light::LightState *obj, std::string &area, std::string &node, + std::string &friendly_name); #endif #ifdef USE_COVER /// Return the type for prometheus void cover_type_(AsyncResponseStream *stream); /// Return the switch Values state as prometheus data point - void cover_row_(AsyncResponseStream *stream, cover::Cover *obj); + void cover_row_(AsyncResponseStream *stream, cover::Cover *obj, std::string &area, std::string &node, + std::string &friendly_name); #endif #ifdef USE_SWITCH /// Return the type for prometheus void switch_type_(AsyncResponseStream *stream); /// Return the switch Values state as prometheus data point - void switch_row_(AsyncResponseStream *stream, switch_::Switch *obj); + void switch_row_(AsyncResponseStream *stream, switch_::Switch *obj, std::string &area, std::string &node, + std::string &friendly_name); #endif #ifdef USE_LOCK /// Return the type for prometheus void lock_type_(AsyncResponseStream *stream); /// Return the lock Values state as prometheus data point - void lock_row_(AsyncResponseStream *stream, lock::Lock *obj); + void lock_row_(AsyncResponseStream *stream, lock::Lock *obj, std::string &area, std::string &node, + std::string &friendly_name); #endif #ifdef USE_TEXT_SENSOR /// Return the type for prometheus void text_sensor_type_(AsyncResponseStream *stream); /// Return the lock Values state as prometheus data point - void text_sensor_row_(AsyncResponseStream *stream, text_sensor::TextSensor *obj); + void text_sensor_row_(AsyncResponseStream *stream, text_sensor::TextSensor *obj, std::string &area, std::string &node, + std::string &friendly_name); #endif web_server_base::WebServerBase *base_; diff --git a/tests/components/prometheus/common.yaml b/tests/components/prometheus/common.yaml index 7aa509f8b2..68ef2a2f58 100644 --- a/tests/components/prometheus/common.yaml +++ b/tests/components/prometheus/common.yaml @@ -1,3 +1,8 @@ +esphome: + name: livingroomdevice + friendly_name: Living Room Device + area: Living Room + wifi: ssid: MySSID password: password1 @@ -14,6 +19,9 @@ sensor: update_interval: 60s text_sensor: + - platform: version + name: "ESPHome Version" + hide_timestamp: true - platform: template id: template_text_sensor1 lambda: |- @@ -24,6 +32,52 @@ text_sensor: } update_interval: 60s +binary_sensor: + - platform: template + id: template_binary_sensor1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +switch: + - platform: template + id: template_switch1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + optimistic: true + +fan: + - platform: template + id: template_fan1 + +cover: + - platform: template + id: template_cover1 + lambda: |- + if (millis() > 10000) { + return COVER_OPEN; + } else { + return COVER_CLOSED; + } + +lock: + - platform: template + id: template_lock1 + lambda: |- + if (millis() > 10000) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + optimistic: true + prometheus: include_internal: true relabel: From ee3ee3a63b784767ea907f2f52935db34f0e1267 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:10:58 +1100 Subject: [PATCH 063/282] [http_request] Implement `on_error` trigger for requests (#7696) --- esphome/components/http_request/__init__.py | 12 ++++++++++++ esphome/components/http_request/http_request.h | 11 ++++++++--- tests/components/http_request/common.yaml | 2 ++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 0407bbd326..78064fb4b4 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_ESP8266_DISABLE_SSL_SUPPORT, CONF_ID, CONF_METHOD, + CONF_ON_ERROR, CONF_TIMEOUT, CONF_TRIGGER_ID, CONF_URL, @@ -185,6 +186,13 @@ HTTP_REQUEST_ACTION_SCHEMA = cv.Schema( cv.Optional(CONF_ON_RESPONSE): automation.validate_automation( {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)} ), + cv.Optional(CONF_ON_ERROR): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + automation.Trigger.template() + ) + } + ), cv.Optional(CONF_MAX_RESPONSE_BUFFER_SIZE, default="1kB"): cv.validate_bytes, } ) @@ -272,5 +280,9 @@ async def http_request_action_to_code(config, action_id, template_arg, args): ], conf, ) + for conf in config.get(CONF_ON_ERROR, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_error_trigger(trigger)) + await automation.build_automation(trigger, [], conf) return var diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index d87d9b8a45..4ed2c834f8 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -135,8 +135,8 @@ class HttpRequestComponent : public Component { protected: const char *useragent_{nullptr}; - bool follow_redirects_; - uint16_t redirect_limit_; + bool follow_redirects_{}; + uint16_t redirect_limit_{}; uint16_t timeout_{4500}; uint32_t watchdog_timeout_{0}; }; @@ -157,6 +157,8 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> { void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); } + void register_error_trigger(Trigger<> *trigger) { this->error_triggers_.push_back(trigger); } + void set_max_response_buffer_size(size_t max_response_buffer_size) { this->max_response_buffer_size_ = max_response_buffer_size; } @@ -186,6 +188,8 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> { auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, headers); if (container == nullptr) { + for (auto *trigger : this->error_triggers_) + trigger->trigger(x...); return; } @@ -237,7 +241,8 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> { std::map<const char *, TemplatableValue<const char *, Ts...>> headers_{}; std::map<const char *, TemplatableValue<std::string, Ts...>> json_{}; std::function<void(Ts..., JsonObject)> json_func_{nullptr}; - std::vector<HttpRequestResponseTrigger *> response_triggers_; + std::vector<HttpRequestResponseTrigger *> response_triggers_{}; + std::vector<Trigger<> *> error_triggers_{}; size_t max_response_buffer_size_{SIZE_MAX}; }; diff --git a/tests/components/http_request/common.yaml b/tests/components/http_request/common.yaml index 589b7fb4b4..593b85e435 100644 --- a/tests/components/http_request/common.yaml +++ b/tests/components/http_request/common.yaml @@ -12,6 +12,8 @@ esphome: url: https://esphome.io headers: Content-Type: application/json + on_error: + logger.log: "Request failed" on_response: then: - logger.log: From 6afd004ec54670ed27cd29202a7c290d258a8b64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:25:36 +1300 Subject: [PATCH 064/282] Bump pypa/gh-action-pypi-publish from 1.10.3 to 1.11.0 (#7700) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5072bec222..82d7ae5ee8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,7 @@ jobs: pip3 install build python3 -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.10.3 + uses: pypa/gh-action-pypi-publish@v1.11.0 deploy-docker: name: Build ESPHome ${{ matrix.platform }} From 765579dabbd607975901b34b350c4252f80ca3ab Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt <kevin.ahrendt@nabucasa.com> Date: Wed, 30 Oct 2024 15:29:24 -0400 Subject: [PATCH 065/282] [es8311] Add es8311 dac component (#7693) --- CODEOWNERS | 1 + esphome/components/es8311/__init__.py | 0 esphome/components/es8311/audio_dac.py | 70 ++++++ esphome/components/es8311/es8311.cpp | 227 ++++++++++++++++++ esphome/components/es8311/es8311.h | 135 +++++++++++ esphome/components/es8311/es8311_const.h | 195 +++++++++++++++ esphome/components/i2s_audio/__init__.py | 4 +- esphome/const.py | 1 + tests/components/es8311/common.yaml | 15 ++ tests/components/es8311/test.esp32-ard.yaml | 5 + .../components/es8311/test.esp32-c3-ard.yaml | 5 + .../components/es8311/test.esp32-c3-idf.yaml | 5 + tests/components/es8311/test.esp32-idf.yaml | 5 + tests/components/es8311/test.esp8266-ard.yaml | 5 + 14 files changed, 670 insertions(+), 3 deletions(-) create mode 100644 esphome/components/es8311/__init__.py create mode 100644 esphome/components/es8311/audio_dac.py create mode 100644 esphome/components/es8311/es8311.cpp create mode 100644 esphome/components/es8311/es8311.h create mode 100644 esphome/components/es8311/es8311_const.h create mode 100644 tests/components/es8311/common.yaml create mode 100644 tests/components/es8311/test.esp32-ard.yaml create mode 100644 tests/components/es8311/test.esp32-c3-ard.yaml create mode 100644 tests/components/es8311/test.esp32-c3-idf.yaml create mode 100644 tests/components/es8311/test.esp32-idf.yaml create mode 100644 tests/components/es8311/test.esp8266-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 5eb1f863f2..8fbbacef59 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/es8311/* @kahrendt @kroimon esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @Rapsssito @jesserockz esphome/components/esp32_ble_client/* @jesserockz diff --git a/esphome/components/es8311/__init__.py b/esphome/components/es8311/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/es8311/audio_dac.py b/esphome/components/es8311/audio_dac.py new file mode 100644 index 0000000000..1b450c3c11 --- /dev/null +++ b/esphome/components/es8311/audio_dac.py @@ -0,0 +1,70 @@ +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 + +CODEOWNERS = ["@kroimon", "@kahrendt"] +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" + +es8311_resolution = es8311_ns.enum("ES8311Resolution") +ES8311_BITS_PER_SAMPLE_ENUM = { + 16: es8311_resolution.ES8311_RESOLUTION_16, + 24: es8311_resolution.ES8311_RESOLUTION_24, + 32: es8311_resolution.ES8311_RESOLUTION_32, +} + +es8311_mic_gain = es8311_ns.enum("ES8311MicGain") +ES8311_MIC_GAIN_ENUM = { + "MIN": es8311_mic_gain.ES8311_MIC_GAIN_MIN, + "0DB": es8311_mic_gain.ES8311_MIC_GAIN_0DB, + "6DB": es8311_mic_gain.ES8311_MIC_GAIN_6DB, + "12DB": es8311_mic_gain.ES8311_MIC_GAIN_12DB, + "18DB": es8311_mic_gain.ES8311_MIC_GAIN_18DB, + "24DB": es8311_mic_gain.ES8311_MIC_GAIN_24DB, + "30DB": es8311_mic_gain.ES8311_MIC_GAIN_30DB, + "36DB": es8311_mic_gain.ES8311_MIC_GAIN_36DB, + "42DB": es8311_mic_gain.ES8311_MIC_GAIN_42DB, + "MAX": es8311_mic_gain.ES8311_MIC_GAIN_MAX, +} + + +_validate_bits = cv.float_with_unit("bits", "bit") + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ES8311), + cv.Optional(CONF_BITS_PER_SAMPLE, default="16bit"): cv.All( + _validate_bits, cv.enum(ES8311_BITS_PER_SAMPLE_ENUM) + ), + cv.Optional(CONF_MIC_GAIN, default="42DB"): cv.enum( + ES8311_MIC_GAIN_ENUM, upper=True + ), + cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), + cv.Optional(CONF_USE_MCLK, default=True): cv.boolean, + cv.Optional(CONF_USE_MICROPHONE, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x18)) +) + + +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_frequency(config[CONF_SAMPLE_RATE])) + cg.add(var.set_use_mclk(config[CONF_USE_MCLK])) + cg.add(var.set_use_mic(config[CONF_USE_MICROPHONE])) diff --git a/esphome/components/es8311/es8311.cpp b/esphome/components/es8311/es8311.cpp new file mode 100644 index 0000000000..1cb1fbbe08 --- /dev/null +++ b/esphome/components/es8311/es8311.cpp @@ -0,0 +1,227 @@ +#include "es8311.h" +#include "es8311_const.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include <cinttypes> + +namespace esphome { +namespace es8311 { + +static const char *const TAG = "es8311"; + +// Mark the component as failed; use only in setup +#define ES8311_ERROR_FAILED(func) \ + if (!(func)) { \ + this->mark_failed(); \ + return; \ + } +// Return false; use outside of setup +#define ES8311_ERROR_CHECK(func) \ + if (!(func)) { \ + return false; \ + } + +void ES8311::setup() { + ESP_LOGCONFIG(TAG, "Setting up ES8311..."); + + // Reset + ES8311_ERROR_FAILED(this->write_byte(ES8311_REG00_RESET, 0x1F)); + ES8311_ERROR_FAILED(this->write_byte(ES8311_REG00_RESET, 0x00)); + + ES8311_ERROR_FAILED(this->configure_clock_()); + ES8311_ERROR_FAILED(this->configure_format_()); + ES8311_ERROR_FAILED(this->configure_mic_()); + + // Set initial volume + this->set_volume(0.75); // 0.75 = 0xBF = 0dB + + // Power up analog circuitry + ES8311_ERROR_FAILED(this->write_byte(ES8311_REG0D_SYSTEM, 0x01)); + // Enable analog PGA, enable ADC modulator + ES8311_ERROR_FAILED(this->write_byte(ES8311_REG0E_SYSTEM, 0x02)); + // Power up DAC + ES8311_ERROR_FAILED(this->write_byte(ES8311_REG12_SYSTEM, 0x00)); + // Enable output to HP drive + ES8311_ERROR_FAILED(this->write_byte(ES8311_REG13_SYSTEM, 0x10)); + // ADC Equalizer bypass, cancel DC offset in digital domain + ES8311_ERROR_FAILED(this->write_byte(ES8311_REG1C_ADC, 0x6A)); + // Bypass DAC equalizer + ES8311_ERROR_FAILED(this->write_byte(ES8311_REG37_DAC, 0x08)); + // Power On + ES8311_ERROR_FAILED(this->write_byte(ES8311_REG00_RESET, 0x80)); +} + +void ES8311::dump_config() { + ESP_LOGCONFIG(TAG, "ES8311 Audio Codec:"); + ESP_LOGCONFIG(TAG, " Use MCLK: %s", YESNO(this->use_mclk_)); + ESP_LOGCONFIG(TAG, " Use Microphone: %s", YESNO(this->use_mic_)); + ESP_LOGCONFIG(TAG, " DAC Bits per Sample: %" PRIu8, this->resolution_out_); + ESP_LOGCONFIG(TAG, " Sample Rate: %" PRIu32, this->sample_frequency_); + + if (this->is_failed()) { + ESP_LOGCONFIG(TAG, " Failed to initialize!"); + return; + } +} + +bool ES8311::set_volume(float volume) { + volume = clamp(volume, 0.0f, 1.0f); + uint8_t reg32 = remap<uint8_t, float>(volume, 0.0f, 1.0f, 0, 255); + return this->write_byte(ES8311_REG32_DAC, reg32); +} + +float ES8311::volume() { + uint8_t reg32; + this->read_byte(ES8311_REG32_DAC, ®32); + return remap<float, uint8_t>(reg32, 0, 255, 0.0f, 1.0f); +} + +uint8_t ES8311::calculate_resolution_value(ES8311Resolution resolution) { + switch (resolution) { + case ES8311_RESOLUTION_16: + return (3 << 2); + case ES8311_RESOLUTION_18: + return (2 << 2); + case ES8311_RESOLUTION_20: + return (1 << 2); + case ES8311_RESOLUTION_24: + return (0 << 2); + case ES8311_RESOLUTION_32: + return (4 << 2); + default: + return 0; + } +} + +const ES8311Coefficient *ES8311::get_coefficient(uint32_t mclk, uint32_t rate) { + for (const auto &coefficient : ES8311_COEFFICIENTS) { + if (coefficient.mclk == mclk && coefficient.rate == rate) + return &coefficient; + } + return nullptr; +} + +bool ES8311::configure_clock_() { + // Register 0x01: select clock source for internal MCLK and determine its frequency + uint8_t reg01 = 0x3F; // Enable all clocks + + uint32_t mclk_frequency = this->sample_frequency_ * this->mclk_multiple_; + if (!this->use_mclk_) { + reg01 |= BIT(7); // Use SCLK + mclk_frequency = this->sample_frequency_ * (int) this->resolution_out_ * 2; + } + if (this->mclk_inverted_) { + reg01 |= BIT(6); // Invert MCLK pin + } + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG01_CLK_MANAGER, reg01)); + + // Get clock coefficients from coefficient table + auto *coefficient = get_coefficient(mclk_frequency, this->sample_frequency_); + if (coefficient == nullptr) { + ESP_LOGE(TAG, "Unable to configure sample rate %" PRIu32 "Hz with %" PRIu32 "Hz MCLK", this->sample_frequency_, + mclk_frequency); + return false; + } + + // Register 0x02 + uint8_t reg02; + ES8311_ERROR_CHECK(this->read_byte(ES8311_REG02_CLK_MANAGER, ®02)); + reg02 &= 0x07; + reg02 |= (coefficient->pre_div - 1) << 5; + reg02 |= coefficient->pre_mult << 3; + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG02_CLK_MANAGER, reg02)); + + // Register 0x03 + const uint8_t reg03 = (coefficient->fs_mode << 6) | coefficient->adc_osr; + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG03_CLK_MANAGER, reg03)); + + // Register 0x04 + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG04_CLK_MANAGER, coefficient->dac_osr)); + + // Register 0x05 + const uint8_t reg05 = ((coefficient->adc_div - 1) << 4) | (coefficient->dac_div - 1); + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG05_CLK_MANAGER, reg05)); + + // Register 0x06 + uint8_t reg06; + ES8311_ERROR_CHECK(this->read_byte(ES8311_REG06_CLK_MANAGER, ®06)); + if (this->sclk_inverted_) { + reg06 |= BIT(5); + } else { + reg06 &= ~BIT(5); + } + reg06 &= 0xE0; + if (coefficient->bclk_div < 19) { + reg06 |= (coefficient->bclk_div - 1) << 0; + } else { + reg06 |= (coefficient->bclk_div) << 0; + } + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG06_CLK_MANAGER, reg06)); + + // Register 0x07 + uint8_t reg07; + ES8311_ERROR_CHECK(this->read_byte(ES8311_REG07_CLK_MANAGER, ®07)); + reg07 &= 0xC0; + reg07 |= coefficient->lrck_h << 0; + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG07_CLK_MANAGER, reg07)); + + // Register 0x08 + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG08_CLK_MANAGER, coefficient->lrck_l)); + + // Successfully configured the clock + return true; +} + +bool ES8311::configure_format_() { + // Configure I2S mode and format + uint8_t reg00; + ES8311_ERROR_CHECK(this->read_byte(ES8311_REG00_RESET, ®00)); + reg00 &= 0xBF; + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG00_RESET, reg00)); + + // Configure SDP in resolution + uint8_t reg09 = calculate_resolution_value(this->resolution_in_); + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG09_SDPIN, reg09)); + + // Configure SDP out resolution + uint8_t reg0a = calculate_resolution_value(this->resolution_out_); + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG0A_SDPOUT, reg0a)); + + // Successfully configured the format + return true; +} + +bool ES8311::configure_mic_() { + uint8_t reg14 = 0x1A; // Enable analog MIC and max PGA gain + if (this->use_mic_) { + reg14 |= BIT(6); // Enable PDM digital microphone + } + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG14_SYSTEM, reg14)); + + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG16_ADC, this->mic_gain_)); // ADC gain scale up + ES8311_ERROR_CHECK(this->write_byte(ES8311_REG17_ADC, 0xC8)); // Set ADC gain + + // Successfully configured the microphones + return true; +} + +bool ES8311::set_mute_state_(bool mute_state) { + uint8_t reg31; + + this->is_muted_ = mute_state; + + if (!this->read_byte(ES8311_REG31_DAC, ®31)) { + return false; + } + + if (mute_state) { + reg31 |= BIT(6) | BIT(5); + } else { + reg31 &= ~(BIT(6) | BIT(5)); + } + + return this->write_byte(ES8311_REG31_DAC, reg31); +} + +} // namespace es8311 +} // namespace esphome diff --git a/esphome/components/es8311/es8311.h b/esphome/components/es8311/es8311.h new file mode 100644 index 0000000000..840a07204c --- /dev/null +++ b/esphome/components/es8311/es8311.h @@ -0,0 +1,135 @@ +#pragma once + +#include "esphome/components/audio_dac/audio_dac.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace es8311 { + +enum ES8311MicGain { + ES8311_MIC_GAIN_MIN = -1, + ES8311_MIC_GAIN_0DB, + ES8311_MIC_GAIN_6DB, + ES8311_MIC_GAIN_12DB, + ES8311_MIC_GAIN_18DB, + ES8311_MIC_GAIN_24DB, + ES8311_MIC_GAIN_30DB, + ES8311_MIC_GAIN_36DB, + ES8311_MIC_GAIN_42DB, + ES8311_MIC_GAIN_MAX +}; + +enum ES8311Resolution : uint8_t { + ES8311_RESOLUTION_16 = 16, + ES8311_RESOLUTION_18 = 18, + ES8311_RESOLUTION_20 = 20, + ES8311_RESOLUTION_24 = 24, + ES8311_RESOLUTION_32 = 32 +}; + +struct ES8311Coefficient { + uint32_t mclk; // mclk frequency + uint32_t rate; // sample rate + uint8_t pre_div; // the pre divider with range from 1 to 8 + uint8_t pre_mult; // the pre multiplier with x1, x2, x4 and x8 selection + uint8_t adc_div; // adcclk divider + uint8_t dac_div; // dacclk divider + uint8_t fs_mode; // single speed (0) or double speed (1) + uint8_t lrck_h; // adc lrck divider and dac lrck divider + uint8_t lrck_l; // + uint8_t bclk_div; // sclk divider + uint8_t adc_osr; // adc osr + uint8_t dac_osr; // dac osr +}; + +class ES8311 : 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_; } + + ////////////////////////////////// + // ES8311 configuration setters // + ////////////////////////////////// + + void set_use_mclk(bool use_mclk) { this->use_mclk_ = use_mclk; } + void set_bits_per_sample(ES8311Resolution resolution) { + this->resolution_in_ = resolution; + this->resolution_out_ = resolution; + } + void set_sample_frequency(uint32_t sample_frequency) { this->sample_frequency_ = sample_frequency; } + void set_use_mic(bool use_mic) { this->use_mic_ = use_mic; } + void set_mic_gain(ES8311MicGain mic_gain) { this->mic_gain_ = mic_gain; } + + protected: + /// @brief Computes the register value for the configured resolution (bits per sample) + /// @param resolution bits per sample enum for both audio in and audio out + /// @return register value + static uint8_t calculate_resolution_value(ES8311Resolution resolution); + + /// @brief Retrieves the appropriate registers values for the configured mclk and rate + /// @param mclk mlck frequency in Hz + /// @param rate sample rate frequency in Hz + /// @return ES8311Coeffecient containing appropriate register values to configure the ES8311 or nullptr if impossible + static const ES8311Coefficient *get_coefficient(uint32_t mclk, uint32_t rate); + + /// @brief Configures the ES8311 registers for the chosen sample rate + /// @return True if successful and false otherwise + bool configure_clock_(); + + /// @brief Configures the ES8311 registers for the chosen bits per sample + /// @return True if successful and false otherwise + bool configure_format_(); + + /// @brief Configures the ES8311 microphone registers + /// @return True if successful and false otherwise + bool configure_mic_(); + + /// @brief Mutes or unmute the DAC audio out + /// @param mute_state True to mute, false to unmute + /// @return + bool set_mute_state_(bool mute_state); + + bool use_mic_; + ES8311MicGain mic_gain_; + + bool use_mclk_; // true = use dedicated MCLK pin, false = use SCLK + bool sclk_inverted_{false}; // SCLK is inverted + bool mclk_inverted_{false}; // MCLK is inverted (ignored if use_mclk_ == false) + uint32_t mclk_multiple_{256}; // MCLK frequency is sample rate * mclk_multiple_ (ignored if use_mclk_ == false) + + uint32_t sample_frequency_; // in Hz + ES8311Resolution resolution_in_; + ES8311Resolution resolution_out_; +}; + +} // namespace es8311 +} // namespace esphome diff --git a/esphome/components/es8311/es8311_const.h b/esphome/components/es8311/es8311_const.h new file mode 100644 index 0000000000..7463a92ef1 --- /dev/null +++ b/esphome/components/es8311/es8311_const.h @@ -0,0 +1,195 @@ +#pragma once + +#include "es8311.h" + +namespace esphome { +namespace es8311 { + +// ES8311 register addresses +static const uint8_t ES8311_REG00_RESET = 0x00; // Reset +static const uint8_t ES8311_REG01_CLK_MANAGER = 0x01; // Clock Manager: select clk src for mclk, enable clock for codec +static const uint8_t ES8311_REG02_CLK_MANAGER = 0x02; // Clock Manager: clk divider and clk multiplier +static const uint8_t ES8311_REG03_CLK_MANAGER = 0x03; // Clock Manager: adc fsmode and osr +static const uint8_t ES8311_REG04_CLK_MANAGER = 0x04; // Clock Manager: dac osr +static const uint8_t ES8311_REG05_CLK_MANAGER = 0x05; // Clock Manager: clk divider for adc and dac +static const uint8_t ES8311_REG06_CLK_MANAGER = 0x06; // Clock Manager: bclk inverter BIT(5) and divider +static const uint8_t ES8311_REG07_CLK_MANAGER = 0x07; // Clock Manager: tri-state, lrck divider +static const uint8_t ES8311_REG08_CLK_MANAGER = 0x08; // Clock Manager: lrck divider +static const uint8_t ES8311_REG09_SDPIN = 0x09; // Serial Digital Port: DAC +static const uint8_t ES8311_REG0A_SDPOUT = 0x0A; // Serial Digital Port: ADC +static const uint8_t ES8311_REG0B_SYSTEM = 0x0B; // System +static const uint8_t ES8311_REG0C_SYSTEM = 0x0C; // System +static const uint8_t ES8311_REG0D_SYSTEM = 0x0D; // System: power up/down +static const uint8_t ES8311_REG0E_SYSTEM = 0x0E; // System: power up/down +static const uint8_t ES8311_REG0F_SYSTEM = 0x0F; // System: low power +static const uint8_t ES8311_REG10_SYSTEM = 0x10; // System +static const uint8_t ES8311_REG11_SYSTEM = 0x11; // System +static const uint8_t ES8311_REG12_SYSTEM = 0x12; // System: Enable DAC +static const uint8_t ES8311_REG13_SYSTEM = 0x13; // System +static const uint8_t ES8311_REG14_SYSTEM = 0x14; // System: select DMIC, select analog pga gain +static const uint8_t ES8311_REG15_ADC = 0x15; // ADC: adc ramp rate, dmic sense +static const uint8_t ES8311_REG16_ADC = 0x16; // ADC +static const uint8_t ES8311_REG17_ADC = 0x17; // ADC: volume +static const uint8_t ES8311_REG18_ADC = 0x18; // ADC: alc enable and winsize +static const uint8_t ES8311_REG19_ADC = 0x19; // ADC: alc maxlevel +static const uint8_t ES8311_REG1A_ADC = 0x1A; // ADC: alc automute +static const uint8_t ES8311_REG1B_ADC = 0x1B; // ADC: alc automute, adc hpf s1 +static const uint8_t ES8311_REG1C_ADC = 0x1C; // ADC: equalizer, hpf s2 +static const uint8_t ES8311_REG1D_ADCEQ = 0x1D; // ADCEQ: equalizer B0 +static const uint8_t ES8311_REG1E_ADCEQ = 0x1E; // ADCEQ: equalizer B0 +static const uint8_t ES8311_REG1F_ADCEQ = 0x1F; // ADCEQ: equalizer B0 +static const uint8_t ES8311_REG20_ADCEQ = 0x20; // ADCEQ: equalizer B0 +static const uint8_t ES8311_REG21_ADCEQ = 0x21; // ADCEQ: equalizer A1 +static const uint8_t ES8311_REG22_ADCEQ = 0x22; // ADCEQ: equalizer A1 +static const uint8_t ES8311_REG23_ADCEQ = 0x23; // ADCEQ: equalizer A1 +static const uint8_t ES8311_REG24_ADCEQ = 0x24; // ADCEQ: equalizer A1 +static const uint8_t ES8311_REG25_ADCEQ = 0x25; // ADCEQ: equalizer A2 +static const uint8_t ES8311_REG26_ADCEQ = 0x26; // ADCEQ: equalizer A2 +static const uint8_t ES8311_REG27_ADCEQ = 0x27; // ADCEQ: equalizer A2 +static const uint8_t ES8311_REG28_ADCEQ = 0x28; // ADCEQ: equalizer A2 +static const uint8_t ES8311_REG29_ADCEQ = 0x29; // ADCEQ: equalizer B1 +static const uint8_t ES8311_REG2A_ADCEQ = 0x2A; // ADCEQ: equalizer B1 +static const uint8_t ES8311_REG2B_ADCEQ = 0x2B; // ADCEQ: equalizer B1 +static const uint8_t ES8311_REG2C_ADCEQ = 0x2C; // ADCEQ: equalizer B1 +static const uint8_t ES8311_REG2D_ADCEQ = 0x2D; // ADCEQ: equalizer B2 +static const uint8_t ES8311_REG2E_ADCEQ = 0x2E; // ADCEQ: equalizer B2 +static const uint8_t ES8311_REG2F_ADCEQ = 0x2F; // ADCEQ: equalizer B2 +static const uint8_t ES8311_REG30_ADCEQ = 0x30; // ADCEQ: equalizer B2 +static const uint8_t ES8311_REG31_DAC = 0x31; // DAC: mute +static const uint8_t ES8311_REG32_DAC = 0x32; // DAC: volume +static const uint8_t ES8311_REG33_DAC = 0x33; // DAC: offset +static const uint8_t ES8311_REG34_DAC = 0x34; // DAC: drc enable, drc winsize +static const uint8_t ES8311_REG35_DAC = 0x35; // DAC: drc maxlevel, minilevel +static const uint8_t ES8311_REG36_DAC = 0x36; // DAC +static const uint8_t ES8311_REG37_DAC = 0x37; // DAC: ramprate +static const uint8_t ES8311_REG38_DACEQ = 0x38; // DACEQ: equalizer B0 +static const uint8_t ES8311_REG39_DACEQ = 0x39; // DACEQ: equalizer B0 +static const uint8_t ES8311_REG3A_DACEQ = 0x3A; // DACEQ: equalizer B0 +static const uint8_t ES8311_REG3B_DACEQ = 0x3B; // DACEQ: equalizer B0 +static const uint8_t ES8311_REG3C_DACEQ = 0x3C; // DACEQ: equalizer B1 +static const uint8_t ES8311_REG3D_DACEQ = 0x3D; // DACEQ: equalizer B1 +static const uint8_t ES8311_REG3E_DACEQ = 0x3E; // DACEQ: equalizer B1 +static const uint8_t ES8311_REG3F_DACEQ = 0x3F; // DACEQ: equalizer B1 +static const uint8_t ES8311_REG40_DACEQ = 0x40; // DACEQ: equalizer A1 +static const uint8_t ES8311_REG41_DACEQ = 0x41; // DACEQ: equalizer A1 +static const uint8_t ES8311_REG42_DACEQ = 0x42; // DACEQ: equalizer A1 +static const uint8_t ES8311_REG43_DACEQ = 0x43; // DACEQ: equalizer A1 +static const uint8_t ES8311_REG44_GPIO = 0x44; // GPIO: dac2adc for test +static const uint8_t ES8311_REG45_GP = 0x45; // GPIO: GP control +static const uint8_t ES8311_REGFA_I2C = 0xFA; // I2C: reset registers +static const uint8_t ES8311_REGFC_FLAG = 0xFC; // Flag +static const uint8_t ES8311_REGFD_CHD1 = 0xFD; // Chip: ID1 +static const uint8_t ES8311_REGFE_CHD2 = 0xFE; // Chip: ID2 +static const uint8_t ES8311_REGFF_CHVER = 0xFF; // Chip: Version + +// ES8311 clock divider coefficients +static const ES8311Coefficient ES8311_COEFFICIENTS[] = { + // clang-format off + + // mclk, rate, pre_ pre_ adc_ dac_ fs_ lrck lrck bclk_ adc_ dac_ + // div, mult, div, div, mode, _h, _l, div, osr, osr + + // 8k + {12288000, 8000, 0x06, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + {18432000, 8000, 0x03, 0x02, 0x03, 0x03, 0x00, 0x05, 0xff, 0x18, 0x10, 0x20}, + {16384000, 8000, 0x08, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 8192000, 8000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 6144000, 8000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 4096000, 8000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 3072000, 8000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 2048000, 8000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 1536000, 8000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 1024000, 8000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + + // 11.025k + {11289600, 11025, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 5644800, 11025, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 2822400, 11025, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 1411200, 11025, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + + // 12k + {12288000, 12000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 6144000, 12000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 3072000, 12000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 1536000, 12000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + + // 16k + {12288000, 16000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + {18432000, 16000, 0x03, 0x02, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, 0x20}, + {16384000, 16000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 8192000, 16000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 6144000, 16000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 4096000, 16000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 3072000, 16000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 2048000, 16000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 1536000, 16000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + { 1024000, 16000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x20}, + + // 22.05k + {11289600, 22050, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 5644800, 22050, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 2822400, 22050, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 1411200, 22050, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + + // 24k + {12288000, 24000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {18432000, 24000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 6144000, 24000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 3072000, 24000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 1536000, 24000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + + // 32k + {12288000, 32000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {18432000, 32000, 0x03, 0x04, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, 0x10}, + {16384000, 32000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 8192000, 32000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 6144000, 32000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 4096000, 32000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 3072000, 32000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 2048000, 32000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 1536000, 32000, 0x03, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, + { 1024000, 32000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + + // 44.1k + {11289600, 44100, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 5644800, 44100, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 2822400, 44100, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 1411200, 44100, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + + // 48k + {12288000, 48000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {18432000, 48000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 6144000, 48000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 3072000, 48000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 1536000, 48000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + + // 64k + {12288000, 64000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {18432000, 64000, 0x03, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10}, + {16384000, 64000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 8192000, 64000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 6144000, 64000, 0x01, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10}, + { 4096000, 64000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 3072000, 64000, 0x01, 0x08, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10}, + { 2048000, 64000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 1536000, 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0xbf, 0x03, 0x18, 0x18}, + { 1024000, 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, + + // 88.2k + {11289600, 88200, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 5644800, 88200, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 2822400, 88200, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 1411200, 88200, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, + + // 96k + {12288000, 96000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {18432000, 96000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 6144000, 96000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 3072000, 96000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + { 1536000, 96000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, + + // clang-format on +}; + +} // namespace es8311 +} // namespace esphome diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index d376907925..fa515a585f 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -8,7 +8,7 @@ from esphome.components.esp32.const import ( VARIANT_ESP32S3, ) import esphome.config_validation as cv -from esphome.const import CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE +from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE from esphome.cpp_generator import MockObjClass import esphome.final_validate as fv @@ -25,13 +25,11 @@ CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin" CONF_I2S_AUDIO = "i2s_audio" CONF_I2S_AUDIO_ID = "i2s_audio_id" -CONF_BITS_PER_SAMPLE = "bits_per_sample" CONF_I2S_MODE = "i2s_mode" CONF_PRIMARY = "primary" CONF_SECONDARY = "secondary" CONF_USE_APLL = "use_apll" -CONF_BITS_PER_SAMPLE = "bits_per_sample" CONF_BITS_PER_CHANNEL = "bits_per_channel" CONF_MONO = "mono" CONF_LEFT = "left" diff --git a/esphome/const.py b/esphome/const.py index c39061631b..5645c9eaab 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -92,6 +92,7 @@ CONF_BINARY_SENSORS = "binary_sensors" CONF_BINDKEY = "bindkey" CONF_BIRTH_MESSAGE = "birth_message" CONF_BIT_DEPTH = "bit_depth" +CONF_BITS_PER_SAMPLE = "bits_per_sample" CONF_BLOCK = "block" CONF_BLUE = "blue" CONF_BOARD = "board" diff --git a/tests/components/es8311/common.yaml b/tests/components/es8311/common.yaml new file mode 100644 index 0000000000..d833d1c043 --- /dev/null +++ b/tests/components/es8311/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_aic3204 + scl: ${scl_pin} + sda: ${sda_pin} + +audio_dac: + - platform: es8311 diff --git a/tests/components/es8311/test.esp32-ard.yaml b/tests/components/es8311/test.esp32-ard.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/es8311/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/es8311/test.esp32-c3-ard.yaml b/tests/components/es8311/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es8311/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/es8311/test.esp32-c3-idf.yaml b/tests/components/es8311/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es8311/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/es8311/test.esp32-idf.yaml b/tests/components/es8311/test.esp32-idf.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/es8311/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/es8311/test.esp8266-ard.yaml b/tests/components/es8311/test.esp8266-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/es8311/test.esp8266-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml From d3563e4e9782299c7820ad4783a1813a361a9575 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 31 Oct 2024 06:30:46 +1100 Subject: [PATCH 066/282] [sdl] Allow window to be resized. (#7698) --- esphome/components/sdl/sdl_esphome.cpp | 27 +++++++++++++++++++++----- esphome/components/sdl/sdl_esphome.h | 1 + 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/sdl/sdl_esphome.cpp b/esphome/components/sdl/sdl_esphome.cpp index 5e17ca5650..8f0821a2fa 100644 --- a/esphome/components/sdl/sdl_esphome.cpp +++ b/esphome/components/sdl/sdl_esphome.cpp @@ -9,8 +9,9 @@ void Sdl::setup() { ESP_LOGD(TAG, "Starting setup"); SDL_Init(SDL_INIT_VIDEO); this->window_ = SDL_CreateWindow(App.get_name().c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - this->width_, this->height_, 0); + this->width_, this->height_, SDL_WINDOW_RESIZABLE); this->renderer_ = SDL_CreateRenderer(this->window_, -1, SDL_RENDERER_SOFTWARE); + SDL_RenderSetLogicalSize(this->renderer_, this->width_, this->height_); this->texture_ = SDL_CreateTexture(this->renderer_, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STATIC, this->width_, this->height_); SDL_SetTextureBlendMode(this->texture_, SDL_BLENDMODE_BLEND); @@ -25,6 +26,10 @@ void Sdl::update() { this->y_low_ = this->height_; this->x_high_ = 0; this->y_high_ = 0; + this->redraw_(rect); +} + +void Sdl::redraw_(SDL_Rect &rect) { SDL_RenderCopy(this->renderer_, this->texture_, &rect, &rect); SDL_RenderPresent(this->renderer_); } @@ -33,15 +38,13 @@ void Sdl::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t * display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { SDL_Rect rect{x_start, y_start, w, h}; if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || big_endian) { - display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, - x_pad); + Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); } else { auto stride = x_offset + w + x_pad; auto data = ptr + (stride * y_offset + x_offset) * 2; SDL_UpdateTexture(this->texture_, &rect, data, stride * 2); } - SDL_RenderCopy(this->renderer_, this->texture_, &rect, &rect); - SDL_RenderPresent(this->renderer_); + this->redraw_(rect); } void Sdl::draw_pixel_at(int x, int y, Color color) { @@ -84,6 +87,20 @@ void Sdl::loop() { } break; + case SDL_WINDOWEVENT: + switch (e.window.event) { + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_EXPOSED: + case SDL_WINDOWEVENT_RESIZED: { + SDL_Rect rect{0, 0, this->width_, this->height_}; + this->redraw_(rect); + break; + } + default: + break; + } + break; + default: ESP_LOGV(TAG, "Event %d", e.type); break; diff --git a/esphome/components/sdl/sdl_esphome.h b/esphome/components/sdl/sdl_esphome.h index e4b2d9dd9f..4b0e59c9fe 100644 --- a/esphome/components/sdl/sdl_esphome.h +++ b/esphome/components/sdl/sdl_esphome.h @@ -38,6 +38,7 @@ class Sdl : public display::Display { protected: int get_width_internal() override { return this->width_; } int get_height_internal() override { return this->height_; } + void redraw_(SDL_Rect &rect); int width_{}; int height_{}; SDL_Renderer *renderer_{}; From e85157db4b246fd3c701c7b4195d4f52b735c554 Mon Sep 17 00:00:00 2001 From: Jason Nagin <33561705+JasonN3@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:34:33 -0400 Subject: [PATCH 067/282] Add config for current temperature precision (#7699) --- esphome/components/mqtt/mqtt_climate.cpp | 6 ++++-- esphome/components/mqtt/mqtt_const.h | 4 ++++ tests/components/mqtt/common.yaml | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 49a8f06734..773d863835 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -71,8 +71,10 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo root[MQTT_MIN_TEMP] = traits.get_visual_min_temperature(); // max_temp root[MQTT_MAX_TEMP] = traits.get_visual_max_temperature(); - // temp_step - root["temp_step"] = traits.get_visual_target_temperature_step(); + // target_temp_step + root[MQTT_TARGET_TEMPERATURE_STEP] = traits.get_visual_target_temperature_step(); + // current_temp_step + root[MQTT_CURRENT_TEMPERATURE_STEP] = traits.get_visual_current_temperature_step(); // temperature units are always coerced to Celsius internally root[MQTT_TEMPERATURE_UNIT] = "C"; diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index c1c40c4b6d..445457a27f 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -51,6 +51,7 @@ constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t"; constexpr const char *const MQTT_CONFIGURATION_URL = "cu"; constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "curr_hum_tpl"; constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "curr_hum_t"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_STEP = "precision"; constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl"; constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t"; constexpr const char *const MQTT_DEVICE = "dev"; @@ -232,6 +233,7 @@ constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t"; constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl"; constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t"; +constexpr const char *const MQTT_TARGET_TEMPERATURE_STEP = "temp_step"; constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl"; constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t"; constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temp_hi_cmd_tpl"; @@ -313,6 +315,7 @@ constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic"; constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url"; constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "current_humidity_template"; constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "current_humidity_topic"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_STEP = "precision"; constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template"; constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic"; constexpr const char *const MQTT_DEVICE = "device"; @@ -494,6 +497,7 @@ constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humi constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic"; constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template"; constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic"; +constexpr const char *const MQTT_TARGET_TEMPERATURE_STEP = "temp_step"; constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temperature_command_template"; constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic"; constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temperature_high_command_template"; diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml index 5ed6335d65..e154be8b5c 100644 --- a/tests/components/mqtt/common.yaml +++ b/tests/components/mqtt/common.yaml @@ -200,6 +200,10 @@ climate: fan_only_cooling: true fan_with_cooling: true fan_with_heating: true + visual: + temperature_step: + target_temperature: 0.1 + current_temperature: 0.1 cover: - platform: template From 5a2fed35693f7191f81fe31236e69c2132c23d99 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:28:18 +1100 Subject: [PATCH 068/282] [spi] Add mosi pin checks for displays (#7702) --- esphome/components/ili9xxx/display.py | 4 ++++ esphome/components/pcd8544/display.py | 12 +++++++---- esphome/components/ssd1306_spi/display.py | 8 +++++-- esphome/components/ssd1322_spi/display.py | 8 +++++-- esphome/components/ssd1325_spi/display.py | 8 +++++-- esphome/components/ssd1327_spi/display.py | 8 +++++-- esphome/components/ssd1331_spi/display.py | 8 +++++-- esphome/components/ssd1351_spi/display.py | 8 +++++-- esphome/components/st7567_spi/display.py | 8 +++++-- esphome/components/st7701s/display.py | 4 ++++ esphome/components/st7735/display.py | 17 +++++++++------ esphome/components/st7789v/display.py | 21 ++++++++++++------- .../components/waveshare_epaper/display.py | 8 +++++-- 13 files changed, 88 insertions(+), 34 deletions(-) diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 68e3aa953d..739ad07843 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -196,6 +196,10 @@ CONFIG_SCHEMA = cv.All( _validate, ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "ili9xxx", require_miso=False, require_mosi=True +) + async def to_code(config): rhs = MODELS[config[CONF_MODEL]].new() diff --git a/esphome/components/pcd8544/display.py b/esphome/components/pcd8544/display.py index d7e72d1c81..2c24b133da 100644 --- a/esphome/components/pcd8544/display.py +++ b/esphome/components/pcd8544/display.py @@ -1,15 +1,15 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import display, spi +import esphome.config_validation as cv from esphome.const import ( + CONF_CONTRAST, + CONF_CS_PIN, CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES, CONF_RESET_PIN, - CONF_CS_PIN, - CONF_CONTRAST, ) DEPENDENCIES = ["spi"] @@ -35,6 +35,10 @@ CONFIG_SCHEMA = cv.All( cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "pcd8544", require_miso=False, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/ssd1306_spi/display.py b/esphome/components/ssd1306_spi/display.py index 0af1168bde..4af41073d4 100644 --- a/esphome/components/ssd1306_spi/display.py +++ b/esphome/components/ssd1306_spi/display.py @@ -1,8 +1,8 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import spi, ssd1306_base from esphome.components.ssd1306_base import _validate +import esphome.config_validation as cv from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES AUTO_LOAD = ["ssd1306_base"] @@ -24,6 +24,10 @@ CONFIG_SCHEMA = cv.All( _validate, ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "ssd1306_spi", require_miso=False, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/ssd1322_spi/display.py b/esphome/components/ssd1322_spi/display.py index 88b3a53355..849e71abee 100644 --- a/esphome/components/ssd1322_spi/display.py +++ b/esphome/components/ssd1322_spi/display.py @@ -1,7 +1,7 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import spi, ssd1322_base +import esphome.config_validation as cv from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES CODEOWNERS = ["@kbx81"] @@ -24,6 +24,10 @@ CONFIG_SCHEMA = cv.All( cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "ssd1322_spi", require_miso=False, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/ssd1325_spi/display.py b/esphome/components/ssd1325_spi/display.py index a86dc751d5..e18db33c68 100644 --- a/esphome/components/ssd1325_spi/display.py +++ b/esphome/components/ssd1325_spi/display.py @@ -1,7 +1,7 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import spi, ssd1325_base +import esphome.config_validation as cv from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES CODEOWNERS = ["@kbx81"] @@ -24,6 +24,10 @@ CONFIG_SCHEMA = cv.All( cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "ssd1325_spi", require_miso=False, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/ssd1327_spi/display.py b/esphome/components/ssd1327_spi/display.py index 138e85eecd..b622c098ec 100644 --- a/esphome/components/ssd1327_spi/display.py +++ b/esphome/components/ssd1327_spi/display.py @@ -1,7 +1,7 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import spi, ssd1327_base +import esphome.config_validation as cv from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES CODEOWNERS = ["@kbx81"] @@ -24,6 +24,10 @@ CONFIG_SCHEMA = cv.All( cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "ssd1327_spi", require_miso=False, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/ssd1331_spi/display.py b/esphome/components/ssd1331_spi/display.py index c32ac60578..50895b3175 100644 --- a/esphome/components/ssd1331_spi/display.py +++ b/esphome/components/ssd1331_spi/display.py @@ -1,7 +1,7 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import spi, ssd1331_base +import esphome.config_validation as cv from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES CODEOWNERS = ["@kbx81"] @@ -24,6 +24,10 @@ CONFIG_SCHEMA = cv.All( cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "ssd1331_spi", require_miso=False, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/ssd1351_spi/display.py b/esphome/components/ssd1351_spi/display.py index 3f3409226c..bd7033c3d4 100644 --- a/esphome/components/ssd1351_spi/display.py +++ b/esphome/components/ssd1351_spi/display.py @@ -1,7 +1,7 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import spi, ssd1351_base +import esphome.config_validation as cv from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES CODEOWNERS = ["@kbx81"] @@ -24,6 +24,10 @@ CONFIG_SCHEMA = cv.All( cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "ssd1351_spi", require_miso=False, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/st7567_spi/display.py b/esphome/components/st7567_spi/display.py index aabe02a2d8..305aa35024 100644 --- a/esphome/components/st7567_spi/display.py +++ b/esphome/components/st7567_spi/display.py @@ -1,7 +1,7 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import spi, st7567_base +import esphome.config_validation as cv from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES CODEOWNERS = ["@latonita"] @@ -24,6 +24,10 @@ CONFIG_SCHEMA = cv.All( cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "st7567_spi", require_miso=False, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/st7701s/display.py b/esphome/components/st7701s/display.py index e73c2467da..c6ad43c14c 100644 --- a/esphome/components/st7701s/display.py +++ b/esphome/components/st7701s/display.py @@ -167,6 +167,10 @@ CONFIG_SCHEMA = cv.All( cv.only_with_esp_idf, ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "st7701s", require_miso=False, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/st7735/display.py b/esphome/components/st7735/display.py index d5bb2fa3d6..2761214315 100644 --- a/esphome/components/st7735/display.py +++ b/esphome/components/st7735/display.py @@ -1,17 +1,17 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins -from esphome.components import spi -from esphome.components import display +import esphome.codegen as cg +from esphome.components import display, spi +import esphome.config_validation as cv from esphome.const import ( CONF_DC_PIN, CONF_ID, + CONF_INVERT_COLORS, CONF_LAMBDA, CONF_MODEL, - CONF_RESET_PIN, CONF_PAGES, - CONF_INVERT_COLORS, + CONF_RESET_PIN, ) + from . import st7735_ns CODEOWNERS = ["@SenexCrenshaw"] @@ -68,6 +68,11 @@ CONFIG_SCHEMA = cv.All( ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "st7735", require_miso=False, require_mosi=True +) + + async def setup_st7735(var, config): await display.register_display(var, config) diff --git a/esphome/components/st7789v/display.py b/esphome/components/st7789v/display.py index 04dce2cf6c..8259eacf2d 100644 --- a/esphome/components/st7789v/display.py +++ b/esphome/components/st7789v/display.py @@ -1,22 +1,23 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins -from esphome.components import display, spi, power_supply +import esphome.codegen as cg +from esphome.components import display, power_supply, spi +import esphome.config_validation as cv from esphome.const import ( CONF_BACKLIGHT_PIN, + CONF_CS_PIN, CONF_DC_PIN, CONF_HEIGHT, CONF_ID, CONF_LAMBDA, CONF_MODEL, - CONF_RESET_PIN, - CONF_WIDTH, - CONF_POWER_SUPPLY, - CONF_ROTATION, - CONF_CS_PIN, CONF_OFFSET_HEIGHT, CONF_OFFSET_WIDTH, + CONF_POWER_SUPPLY, + CONF_RESET_PIN, + CONF_ROTATION, + CONF_WIDTH, ) + from . import st7789v_ns CONF_EIGHTBITCOLOR = "eightbitcolor" @@ -168,6 +169,10 @@ CONFIG_SCHEMA = cv.All( validate_st7789v, ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "st7789v", require_miso=False, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 4d3965449f..8287788de5 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -1,7 +1,7 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import core, pins +import esphome.codegen as cg from esphome.components import display, spi +import esphome.config_validation as cv from esphome.const import ( CONF_BUSY_PIN, CONF_DC_PIN, @@ -187,6 +187,10 @@ CONFIG_SCHEMA = cv.All( cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "waveshare_epaper", require_miso=False, require_mosi=True +) + async def to_code(config): model_type, model = MODELS[config[CONF_MODEL]] From 74ea1b60e35fa351b5a5fc380dfdc1879d22f043 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:37:54 +1300 Subject: [PATCH 069/282] [CI] Fix webserver defines to be present based on platform, not just framework (#7703) --- esphome/core/defines.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index b5511b57eb..3798ddba6a 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -86,8 +86,6 @@ #ifdef USE_ARDUINO #define USE_CAPTIVE_PORTAL #define USE_PROMETHEUS -#define USE_WEBSERVER -#define USE_WEBSERVER_PORT 80 // NOLINT #define USE_WIFI_WPA2_EAP #endif @@ -111,6 +109,8 @@ #define USE_SPEAKER #define USE_SPI #define USE_VOICE_ASSISTANT +#define USE_WEBSERVER +#define USE_WEBSERVER_PORT 80 // NOLINT #define USE_WIFI_11KV_SUPPORT #ifdef USE_ARDUINO @@ -147,6 +147,8 @@ #define USE_SHD_FIRMWARE_DATA \ {} +#define USE_WEBSERVER +#define USE_WEBSERVER_PORT 80 // NOLINT #endif #ifdef USE_RP2040 @@ -158,6 +160,8 @@ #ifdef USE_LIBRETINY #define USE_SOCKET_IMPL_LWIP_SOCKETS +#define USE_WEBSERVER +#define USE_WEBSERVER_PORT 80 // NOLINT #endif #ifdef USE_HOST From 8b7e061f3ac5427dcc931feffdd0251100945a57 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:15:39 +1100 Subject: [PATCH 070/282] [touchscreen] Calibration fixes (#7704) --- esphome/components/touchscreen/__init__.py | 97 +++++++++---------- esphome/components/touchscreen/touchscreen.h | 12 +-- .../xpt2046/touchscreen/__init__.py | 39 ++------ tests/components/xpt2046/test.esp32-ard.yaml | 4 +- .../components/xpt2046/test.esp32-c3-ard.yaml | 2 +- .../components/xpt2046/test.esp32-c3-idf.yaml | 2 +- tests/components/xpt2046/test.esp32-idf.yaml | 2 +- .../components/xpt2046/test.esp8266-ard.yaml | 2 +- tests/components/xpt2046/test.rp2040-ard.yaml | 4 +- 9 files changed, 63 insertions(+), 101 deletions(-) diff --git a/esphome/components/touchscreen/__init__.py b/esphome/components/touchscreen/__init__.py index b2d3f60d2b..01a271a34e 100644 --- a/esphome/components/touchscreen/__init__.py +++ b/esphome/components/touchscreen/__init__.py @@ -1,21 +1,18 @@ -import esphome.config_validation as cv -import esphome.codegen as cg - -from esphome.components import display from esphome import automation - +import esphome.codegen as cg +from esphome.components import display +import esphome.config_validation as cv from esphome.const import ( + CONF_CALIBRATION, CONF_DISPLAY, - CONF_ON_TOUCH, - CONF_ON_RELEASE, - CONF_ON_UPDATE, - CONF_SWAP_XY, CONF_MIRROR_X, CONF_MIRROR_Y, + CONF_ON_RELEASE, + CONF_ON_TOUCH, + CONF_ON_UPDATE, + CONF_SWAP_XY, CONF_TRANSFORM, - CONF_CALIBRATION, ) - from esphome.core import coroutine_with_priority CODEOWNERS = ["@jesserockz", "@nielsnl68"] @@ -43,51 +40,45 @@ CONF_Y_MIN = "y_min" CONF_Y_MAX = "y_max" -def validate_calibration(config): - if CONF_CALIBRATION in config: - calibration_config = config[CONF_CALIBRATION] - if ( - cv.int_([CONF_X_MIN]) != 0 - and cv.int_(calibration_config[CONF_X_MAX]) != 0 - and abs( - cv.int_(calibration_config[CONF_X_MIN]) - - cv.int_(calibration_config[CONF_X_MAX]) - ) - < 10 - ): - raise cv.Invalid("Calibration X values difference must be more than 10") - - if ( - cv.int_(calibration_config[CONF_Y_MIN]) != 0 - and cv.int_(calibration_config[CONF_Y_MAX]) != 0 - and abs( - cv.int_(calibration_config[CONF_Y_MIN]) - - cv.int_(calibration_config[CONF_Y_MAX]) - ) - < 10 - ): - raise cv.Invalid("Calibration Y values difference must be more than 10") - - return config +def validate_calibration(calibration_config): + x_min = calibration_config[CONF_X_MIN] + x_max = calibration_config[CONF_X_MAX] + y_min = calibration_config[CONF_Y_MIN] + y_max = calibration_config[CONF_Y_MAX] + if x_max < x_min: + raise cv.Invalid( + "x_min must be smaller than x_max. To mirror the direction use the 'transform' options" + ) + if y_max < y_min: + raise cv.Invalid( + "y_min must be smaller than y_max. To mirror the direction use the 'transform' options" + ) + x_delta = x_max - x_min + y_delta = y_max - y_min + if x_delta < 10 or y_delta < 10: + raise cv.Invalid("Calibration value range must be greater than 10") + return calibration_config -def calibration_schema(default_max_values): - return cv.Schema( +CALIBRATION_SCHEMA = cv.All( + cv.Schema( { - cv.Optional(CONF_X_MIN, default=0): cv.int_range(min=0, max=4095), - cv.Optional(CONF_X_MAX, default=default_max_values): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_Y_MIN, default=0): cv.int_range(min=0, max=4095), - cv.Optional(CONF_Y_MAX, default=default_max_values): cv.int_range( - min=0, max=4095 - ), - }, - validate_calibration, + cv.Required(CONF_X_MIN): cv.int_range(min=0, max=4095), + cv.Required(CONF_X_MAX): cv.int_range(min=0, max=4095), + cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=4095), + cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=4095), + } + ), + validate_calibration, +) + + +def touchscreen_schema(default_touch_timeout=cv.UNDEFINED, calibration_required=False): + calibration = ( + cv.Required(CONF_CALIBRATION) + if calibration_required + else cv.Optional(CONF_CALIBRATION) ) - - -def touchscreen_schema(default_touch_timeout): return cv.Schema( { cv.GenerateID(CONF_DISPLAY): cv.use_id(display.Display), @@ -102,7 +93,7 @@ def touchscreen_schema(default_touch_timeout): cv.positive_time_period_milliseconds, cv.Range(max=cv.TimePeriod(milliseconds=65535)), ), - cv.Optional(CONF_CALIBRATION): calibration_schema(0), + calibration: CALIBRATION_SCHEMA, cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), cv.Optional(CONF_ON_UPDATE): automation.validate_automation(single=True), cv.Optional(CONF_ON_RELEASE): automation.validate_automation(single=True), diff --git a/esphome/components/touchscreen/touchscreen.h b/esphome/components/touchscreen/touchscreen.h index 21111f87b3..8016323d49 100644 --- a/esphome/components/touchscreen/touchscreen.h +++ b/esphome/components/touchscreen/touchscreen.h @@ -53,14 +53,10 @@ class Touchscreen : public PollingComponent { void set_swap_xy(bool swap) { this->swap_x_y_ = swap; } void set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { - this->x_raw_min_ = std::min(x_min, x_max); - this->x_raw_max_ = std::max(x_min, x_max); - this->y_raw_min_ = std::min(y_min, y_max); - this->y_raw_max_ = std::max(y_min, y_max); - if (x_min > x_max) - this->invert_x_ = true; - if (y_min > y_max) - this->invert_y_ = true; + this->x_raw_min_ = x_min; + this->x_raw_max_ = x_max; + this->y_raw_min_ = y_min; + this->y_raw_max_ = y_max; } Trigger<TouchPoint, const TouchPoints_t &> *get_touch_trigger() { return &this->touch_trigger_; } diff --git a/esphome/components/xpt2046/touchscreen/__init__.py b/esphome/components/xpt2046/touchscreen/__init__.py index d45f309a3b..d91ae44789 100644 --- a/esphome/components/xpt2046/touchscreen/__init__.py +++ b/esphome/components/xpt2046/touchscreen/__init__.py @@ -1,9 +1,8 @@ -import esphome.codegen as cg -import esphome.config_validation as cv - from esphome import pins +import esphome.codegen as cg from esphome.components import spi, touchscreen -from esphome.const import CONF_ID, CONF_THRESHOLD, CONF_INTERRUPT_PIN +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_THRESHOLD CODEOWNERS = ["@numo68", "@nielsnl68"] DEPENDENCIES = ["spi"] @@ -15,13 +14,9 @@ XPT2046Component = XPT2046_ns.class_( spi.SPIDevice, ) -CONF_CALIBRATION_X_MIN = "calibration_x_min" -CONF_CALIBRATION_X_MAX = "calibration_x_max" -CONF_CALIBRATION_Y_MIN = "calibration_y_min" -CONF_CALIBRATION_Y_MAX = "calibration_y_max" - CONFIG_SCHEMA = cv.All( - touchscreen.TOUCHSCREEN_SCHEMA.extend( + touchscreen.touchscreen_schema(calibration_required=True) + .extend( cv.Schema( { cv.GenerateID(): cv.declare_id(XPT2046Component), @@ -29,30 +24,10 @@ CONFIG_SCHEMA = cv.All( pins.internal_gpio_input_pin_schema ), cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095), - cv.Optional( - touchscreen.CONF_CALIBRATION - ): touchscreen.calibration_schema(4095), - cv.Optional(CONF_CALIBRATION_X_MIN): cv.invalid( - "Deprecated: use the new 'calibration' configuration variable" - ), - cv.Optional(CONF_CALIBRATION_X_MAX): cv.invalid( - "Deprecated: use the new 'calibration' configuration variable" - ), - cv.Optional(CONF_CALIBRATION_Y_MIN): cv.invalid( - "Deprecated: use the new 'calibration' configuration variable" - ), - cv.Optional(CONF_CALIBRATION_Y_MAX): cv.invalid( - "Deprecated: use the new 'calibration' configuration variable" - ), - cv.Optional(CONF_CALIBRATION_Y_MAX): cv.invalid( - "Deprecated: use the new 'calibration' configuration variable" - ), - cv.Optional("report_interval"): cv.invalid( - "Deprecated: use the 'update_interval' configuration variable" - ), }, ) - ).extend(spi.spi_device_schema()), + ) + .extend(spi.spi_device_schema()), ) diff --git a/tests/components/xpt2046/test.esp32-ard.yaml b/tests/components/xpt2046/test.esp32-ard.yaml index f15d1f9b41..9e305791e0 100644 --- a/tests/components/xpt2046/test.esp32-ard.yaml +++ b/tests/components/xpt2046/test.esp32-ard.yaml @@ -25,8 +25,8 @@ touchscreen: update_interval: 50ms threshold: 400 calibration: - x_min: 3860 - x_max: 280 + x_min: 280 + x_max: 3860 y_min: 340 y_max: 3860 on_touch: diff --git a/tests/components/xpt2046/test.esp32-c3-ard.yaml b/tests/components/xpt2046/test.esp32-c3-ard.yaml index ef4daa800d..c03fd6b345 100644 --- a/tests/components/xpt2046/test.esp32-c3-ard.yaml +++ b/tests/components/xpt2046/test.esp32-c3-ard.yaml @@ -25,7 +25,7 @@ touchscreen: update_interval: 50ms threshold: 400 calibration: - x_min: 3860 + x_min: 28 x_max: 280 y_min: 340 y_max: 3860 diff --git a/tests/components/xpt2046/test.esp32-c3-idf.yaml b/tests/components/xpt2046/test.esp32-c3-idf.yaml index ef4daa800d..787ca9b1ed 100644 --- a/tests/components/xpt2046/test.esp32-c3-idf.yaml +++ b/tests/components/xpt2046/test.esp32-c3-idf.yaml @@ -25,7 +25,7 @@ touchscreen: update_interval: 50ms threshold: 400 calibration: - x_min: 3860 + x_min: 50 x_max: 280 y_min: 340 y_max: 3860 diff --git a/tests/components/xpt2046/test.esp32-idf.yaml b/tests/components/xpt2046/test.esp32-idf.yaml index f15d1f9b41..e79997146b 100644 --- a/tests/components/xpt2046/test.esp32-idf.yaml +++ b/tests/components/xpt2046/test.esp32-idf.yaml @@ -25,7 +25,7 @@ touchscreen: update_interval: 50ms threshold: 400 calibration: - x_min: 3860 + x_min: 50 x_max: 280 y_min: 340 y_max: 3860 diff --git a/tests/components/xpt2046/test.esp8266-ard.yaml b/tests/components/xpt2046/test.esp8266-ard.yaml index 0daa25ad60..ab71f7b8bc 100644 --- a/tests/components/xpt2046/test.esp8266-ard.yaml +++ b/tests/components/xpt2046/test.esp8266-ard.yaml @@ -25,7 +25,7 @@ touchscreen: update_interval: 50ms threshold: 400 calibration: - x_min: 3860 + x_min: 50 x_max: 280 y_min: 340 y_max: 3860 diff --git a/tests/components/xpt2046/test.rp2040-ard.yaml b/tests/components/xpt2046/test.rp2040-ard.yaml index 8afc45d04d..622e69ac98 100644 --- a/tests/components/xpt2046/test.rp2040-ard.yaml +++ b/tests/components/xpt2046/test.rp2040-ard.yaml @@ -25,8 +25,8 @@ touchscreen: update_interval: 50ms threshold: 400 calibration: - x_min: 3860 - x_max: 280 + x_min: 280 + x_max: 3860 y_min: 340 y_max: 3860 on_touch: From a043022444609d11b2a8b040ae9515e54ce940f2 Mon Sep 17 00:00:00 2001 From: Faidon Liambotis <paravoid@debian.org> Date: Thu, 31 Oct 2024 05:36:23 +0200 Subject: [PATCH 071/282] [font] Add support for "glyphsets" (#7429) Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- docker/Dockerfile | 70 +- esphome/components/font/__init__.py | 303 +- requirements.txt | 3 + requirements_optional.txt | 1 - script/ci-custom.py | 2 +- tests/components/font/.gitattributes | 2 + tests/components/font/MatrixChunky8X.bdf | 7461 ++++++++++++++++++++++ tests/components/font/common.yaml | 24 + tests/components/font/test.host.yaml | 34 + tests/components/font/x11.pcf | Bin 0 -> 13368 bytes 10 files changed, 7771 insertions(+), 129 deletions(-) create mode 100644 tests/components/font/.gitattributes create mode 100644 tests/components/font/MatrixChunky8X.bdf create mode 100644 tests/components/font/x11.pcf diff --git a/docker/Dockerfile b/docker/Dockerfile index 52a4794f24..44ee879a12 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -40,25 +40,6 @@ RUN \ libcairo2=1.16.0-7 \ libmagic1=1:5.44-3 \ patch=2.7.6-7 \ - && ( \ - ( \ - [ "$TARGETARCH$TARGETVARIANT" = "armv7" ] && \ - apt-get install -y --no-install-recommends \ - build-essential=12.9 \ - python3-dev=3.11.2-1+b1 \ - zlib1g-dev=1:1.2.13.dfsg-1 \ - libjpeg-dev=1:2.1.5-2 \ - libfreetype-dev=2.12.1+dfsg-5+deb12u3 \ - libssl-dev=3.0.14-1~deb12u2 \ - libffi-dev=3.4.4-1 \ - libopenjp2-7=2.5.0-2 \ - libtiff6=4.5.0-6+deb12u1 \ - cargo=0.66.0+ds1-1 \ - pkg-config=1.8.1-1 \ - gcc-arm-linux-gnueabihf=4:12.2.0-3 \ - ) \ - || [ "$TARGETARCH$TARGETVARIANT" != "armv7" ] \ - ) \ && rm -rf \ /tmp/* \ /var/{cache,log}/* \ @@ -97,15 +78,48 @@ RUN \ # tmpfs is for https://github.com/rust-lang/cargo/issues/8719 COPY requirements.txt requirements_optional.txt / -RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ - curl -L https://www.piwheels.org/cp311/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl -o /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ - && pip3 install --break-system-packages --no-cache-dir /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ - && rm /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ - && export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ - fi; \ - CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \ - pip3 install \ - --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt +RUN --mount=type=tmpfs,target=/root/.cargo <<END-OF-RUN +# Fail on any non-zero status +set -e + +if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ] +then + curl -L https://www.piwheels.org/cp311/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl -o /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl + pip3 install --break-system-packages --no-cache-dir /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl + rm /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl + export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; +fi + +# install build tools in case wheels are not available +BUILD_DEPS=" + build-essential=12.9 + python3-dev=3.11.2-1+b1 + zlib1g-dev=1:1.2.13.dfsg-1 + libjpeg-dev=1:2.1.5-2 + libfreetype-dev=2.12.1+dfsg-5+deb12u3 + libssl-dev=3.0.14-1~deb12u2 + libffi-dev=3.4.4-1 + libopenjp2-7=2.5.0-2 + libtiff6=4.5.0-6+deb12u1 + cargo=0.66.0+ds1-1 + pkg-config=1.8.1-1 +" +if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ] || [ "$TARGETARCH$TARGETVARIANT" = "armv7" ] +then + apt-get update + apt-get install -y --no-install-recommends $BUILD_DEPS +fi + +CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo +pip3 install --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt + +if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ] || [ "$TARGETARCH$TARGETVARIANT" = "armv7" ] +then + apt-get remove -y --purge --auto-remove $BUILD_DEPS + rm -rf /tmp/* /var/{cache,log}/* /var/lib/apt/lists/* +fi +END-OF-RUN + COPY script/platformio_install_deps.py platformio.ini / RUN /platformio_install_deps.py /platformio.ini --libraries diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index a3f11df50e..6fd2d7c310 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -1,3 +1,4 @@ +from collections.abc import Iterable import functools import hashlib import logging @@ -5,6 +6,8 @@ import os from pathlib import Path import re +import freetype +import glyphsets from packaging import version import requests @@ -43,6 +46,18 @@ GlyphData = font_ns.struct("GlyphData") CONF_BPP = "bpp" CONF_EXTRAS = "extras" CONF_FONTS = "fonts" +CONF_GLYPHSETS = "glyphsets" +CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs" + + +# Cache loaded freetype fonts +class FontCache(dict): + def __missing__(self, key): + res = self[key] = freetype.Face(key) + return res + + +FONT_CACHE = FontCache() def glyph_comparator(x, y): @@ -59,36 +74,106 @@ def glyph_comparator(x, y): return -1 if len(x_) > len(y_): return 1 - raise cv.Invalid(f"Found duplicate glyph {x}") + return 0 -def validate_glyphs(value): - if isinstance(value, list): - value = cv.Schema([cv.string])(value) - value = cv.Schema([cv.string])(list(value)) +def flatten(lists) -> list: + """ + Given a list of lists, flatten it to a single list of all elements of all lists. + This wraps itertools.chain.from_iterable to make it more readable, and return a list + rather than a single use iterable. + """ + from itertools import chain - value.sort(key=functools.cmp_to_key(glyph_comparator)) - return value + return list(chain.from_iterable(lists)) -font_map = {} +def check_missing_glyphs(file, codepoints: Iterable, warning: bool = False): + """ + Check that the given font file actually contains the requested glyphs + :param file: A Truetype font file + :param codepoints: A list of codepoints to check + :param warning: If true, log a warning instead of raising an exception + """ - -def merge_glyphs(config): - glyphs = [] - glyphs.extend(config[CONF_GLYPHS]) - font_list = [(EFont(config[CONF_FILE], config[CONF_SIZE], config[CONF_GLYPHS]))] - if extras := config.get(CONF_EXTRAS): - extra_fonts = list( - map( - lambda x: EFont(x[CONF_FILE], config[CONF_SIZE], x[CONF_GLYPHS]), extras - ) + font = FONT_CACHE[file] + missing = [chr(x) for x in codepoints if font.get_char_index(x) == 0] + if missing: + # Only list up to 10 missing glyphs + missing.sort(key=functools.cmp_to_key(glyph_comparator)) + count = len(missing) + missing = missing[:10] + missing_str = "\n ".join( + f"{x} ({x.encode('unicode_escape')})" for x in missing ) - font_list.extend(extra_fonts) - for extra in extras: - glyphs.extend(extra[CONF_GLYPHS]) - validate_glyphs(glyphs) - font_map[config[CONF_ID]] = font_list + if count > 10: + missing_str += f"\n and {count - 10} more." + message = f"Font {Path(file).name} is missing {count} glyph{'s' if count != 1 else ''}:\n {missing_str}" + if warning: + _LOGGER.warning(message) + else: + raise cv.Invalid(message) + + +def validate_glyphs(config): + """ + Check for duplicate codepoints, then check that all requested codepoints actually + have glyphs defined in the appropriate font file. + """ + + # Collect all glyph codepoints and flatten to a list of chars + glyphspoints = flatten( + [x[CONF_GLYPHS] for x in config[CONF_EXTRAS]] + config[CONF_GLYPHS] + ) + # Convert a list of strings to a list of chars (one char strings) + glyphspoints = flatten([list(x) for x in glyphspoints]) + if len(set(glyphspoints)) != len(glyphspoints): + duplicates = {x for x in glyphspoints if glyphspoints.count(x) > 1} + dup_str = ", ".join(f"{x} ({x.encode('unicode_escape')})" for x in duplicates) + raise cv.Invalid( + f"Found duplicate glyph{'s' if len(duplicates) != 1 else ''}: {dup_str}" + ) + # convert to codepoints + glyphspoints = {ord(x) for x in glyphspoints} + fileconf = config[CONF_FILE] + setpoints = set( + flatten([glyphsets.unicodes_per_glyphset(x) for x in config[CONF_GLYPHSETS]]) + ) + # Make setpoints and glyphspoints disjoint + setpoints.difference_update(glyphspoints) + if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP: + # Pillow only allows 256 glyphs per bitmap font. Not sure if that is a Pillow limitation + # or a file format limitation + if any(x >= 256 for x in setpoints.copy().union(glyphspoints)): + raise cv.Invalid("Codepoints in bitmap fonts must be in the range 0-255") + else: + # for TT fonts, check that glyphs are actually present + # Check extras against their own font, exclude from parent font codepoints + for extra in config[CONF_EXTRAS]: + points = {ord(x) for x in flatten(extra[CONF_GLYPHS])} + glyphspoints.difference_update(points) + setpoints.difference_update(points) + check_missing_glyphs(extra[CONF_FILE][CONF_PATH], points) + + # A named glyph that can't be provided is an error + check_missing_glyphs(fileconf[CONF_PATH], glyphspoints) + # A missing glyph from a set is a warning. + if not config[CONF_IGNORE_MISSING_GLYPHS]: + check_missing_glyphs(fileconf[CONF_PATH], setpoints, warning=True) + + # Populate the default after the above checks so that use of the default doesn't trigger errors + if not config[CONF_GLYPHS] and not config[CONF_GLYPHSETS]: + if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP: + config[CONF_GLYPHS] = [DEFAULT_GLYPHS] + else: + # set a default glyphset, intersected with what the font actually offers + font = FONT_CACHE[fileconf[CONF_PATH]] + config[CONF_GLYPHS] = [ + chr(x) + for x in glyphsets.unicodes_per_glyphset(DEFAULT_GLYPHSET) + if font.get_char_index(x) != 0 + ] + return config @@ -120,7 +205,7 @@ def validate_truetype_file(value): ) if not any(map(value.lower().endswith, FONT_EXTENSIONS)): raise cv.Invalid(f"Only {FONT_EXTENSIONS} files are supported.") - return cv.file_(value) + return CORE.relative_config_path(cv.file_(value)) TYPE_LOCAL = "local" @@ -139,6 +224,10 @@ LOCAL_BITMAP_SCHEMA = cv.Schema( } ) +FULLPATH_SCHEMA = cv.maybe_simple_value( + {cv.Required(CONF_PATH): cv.string}, key=CONF_PATH +) + CONF_ITALIC = "italic" FONT_WEIGHTS = { "thin": 100, @@ -167,13 +256,13 @@ def _compute_local_font_path(value: dict) -> Path: return base_dir / key -def get_font_path(value, type) -> Path: - if type == TYPE_GFONTS: +def get_font_path(value, font_type) -> Path: + if font_type == TYPE_GFONTS: name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" return external_files.compute_local_file_dir(DOMAIN) / f"{name}.ttf" - if type == TYPE_WEB: + if font_type == TYPE_WEB: return _compute_local_font_path(value) / "font.ttf" - return None + assert False def download_gfont(value): @@ -203,7 +292,7 @@ def download_gfont(value): _LOGGER.debug("download_gfont: ttf_url=%s", ttf_url) external_files.download_content(ttf_url, path) - return value + return FULLPATH_SCHEMA(path) def download_web_font(value): @@ -212,7 +301,7 @@ def download_web_font(value): external_files.download_content(url, path) _LOGGER.debug("download_web_font: path=%s", path) - return value + return FULLPATH_SCHEMA(path) EXTERNAL_FONT_SCHEMA = cv.Schema( @@ -225,7 +314,6 @@ EXTERNAL_FONT_SCHEMA = cv.Schema( } ) - GFONTS_SCHEMA = cv.All( EXTERNAL_FONT_SCHEMA.extend( { @@ -259,10 +347,10 @@ def validate_file_shorthand(value): } if weight is not None: data[CONF_WEIGHT] = weight[1:] - return FILE_SCHEMA(data) + return font_file_schema(data) if value.startswith("http://") or value.startswith("https://"): - return FILE_SCHEMA( + return font_file_schema( { CONF_TYPE: TYPE_WEB, CONF_URL: value, @@ -270,14 +358,15 @@ def validate_file_shorthand(value): ) if value.endswith(".pcf") or value.endswith(".bdf"): - return FILE_SCHEMA( - { - CONF_TYPE: TYPE_LOCAL_BITMAP, - CONF_PATH: value, - } + value = convert_bitmap_to_pillow_font( + CORE.relative_config_path(cv.file_(value)) ) + return { + CONF_TYPE: TYPE_LOCAL_BITMAP, + CONF_PATH: value, + } - return FILE_SCHEMA( + return font_file_schema( { CONF_TYPE: TYPE_LOCAL, CONF_PATH: value, @@ -295,31 +384,35 @@ TYPED_FILE_SCHEMA = cv.typed_schema( ) -def _file_schema(value): +def font_file_schema(value): if isinstance(value, str): return validate_file_shorthand(value) return TYPED_FILE_SCHEMA(value) -FILE_SCHEMA = cv.All(_file_schema) +# Default if no glyphs or glyphsets are provided +DEFAULT_GLYPHSET = "GF_Latin_Kernel" +# default for bitmap fonts +DEFAULT_GLYPHS = ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz<C2><B0>' -DEFAULT_GLYPHS = ( - ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' -) CONF_RAW_GLYPH_ID = "raw_glyph_id" FONT_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.declare_id(Font), - cv.Required(CONF_FILE): FILE_SCHEMA, - cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs, + cv.Required(CONF_FILE): font_file_schema, + cv.Optional(CONF_GLYPHS, default=[]): cv.ensure_list(cv.string_strict), + cv.Optional(CONF_GLYPHSETS, default=[]): cv.ensure_list( + cv.one_of(*glyphsets.defined_glyphsets()) + ), + cv.Optional(CONF_IGNORE_MISSING_GLYPHS, default=False): cv.boolean, cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1), cv.Optional(CONF_BPP, default=1): cv.one_of(1, 2, 4, 8), - cv.Optional(CONF_EXTRAS): cv.ensure_list( + cv.Optional(CONF_EXTRAS, default=[]): cv.ensure_list( cv.Schema( { - cv.Required(CONF_FILE): FILE_SCHEMA, - cv.Required(CONF_GLYPHS): validate_glyphs, + cv.Required(CONF_FILE): font_file_schema, + cv.Required(CONF_GLYPHS): cv.ensure_list(cv.string_strict), } ) ), @@ -328,7 +421,7 @@ FONT_SCHEMA = cv.Schema( }, ) -CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, merge_glyphs) +CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, validate_glyphs) # PIL doesn't provide a consistent interface for both TrueType and bitmap @@ -367,28 +460,20 @@ class BitmapFontWrapper: mask = self.getmask(glyph, mode="1") _, height = mask.size max_height = max(max_height, height) - return (max_height, 0) + return max_height, 0 class EFont: - def __init__(self, file, size, glyphs): - self.glyphs = glyphs + def __init__(self, file, size, codepoints): + self.codepoints = codepoints + path = file[CONF_PATH] + self.name = Path(path).name ftype = file[CONF_TYPE] if ftype == TYPE_LOCAL_BITMAP: - font = load_bitmap_font(CORE.relative_config_path(file[CONF_PATH])) - elif ftype == TYPE_LOCAL: - path = CORE.relative_config_path(file[CONF_PATH]) - font = load_ttf_font(path, size) - elif ftype in (TYPE_GFONTS, TYPE_WEB): - path = get_font_path(file, ftype) - font = load_ttf_font(path, size) + self.font = load_bitmap_font(path) else: - raise cv.Invalid(f"Could not load font: unknown type: {ftype}") - self.font = font - self.ascent, self.descent = font.getmetrics(glyphs) - - def has_glyph(self, glyph): - return glyph in self.glyphs + self.font = load_ttf_font(path, size) + self.ascent, self.descent = self.font.getmetrics(codepoints) def convert_bitmap_to_pillow_font(filepath): @@ -400,6 +485,7 @@ def convert_bitmap_to_pillow_font(filepath): copy_file_if_changed(filepath, local_bitmap_font_file) + local_pil_font_file = local_bitmap_font_file.with_suffix(".pil") with open(local_bitmap_font_file, "rb") as fp: try: try: @@ -409,28 +495,22 @@ def convert_bitmap_to_pillow_font(filepath): p = BdfFontFile.BdfFontFile(fp) # Convert to pillow-formatted fonts, which have a .pil and .pbm extension. - p.save(local_bitmap_font_file) + p.save(local_pil_font_file) except (SyntaxError, OSError) as err: raise core.EsphomeError( f"Failed to parse as bitmap font: '{filepath}': {err}" ) - local_pil_font_file = os.path.splitext(local_bitmap_font_file)[0] + ".pil" - return cv.file_(local_pil_font_file) + return str(local_pil_font_file) def load_bitmap_font(filepath): from PIL import ImageFont - # Convert bpf and pcf files to pillow fonts, first. - pil_font_path = convert_bitmap_to_pillow_font(filepath) - try: - font = ImageFont.load(str(pil_font_path)) + font = ImageFont.load(str(filepath)) except Exception as e: - raise core.EsphomeError( - f"Failed to load bitmap font file: {pil_font_path} : {e}" - ) + raise core.EsphomeError(f"Failed to load bitmap font file: {filepath}: {e}") return BitmapFontWrapper(font) @@ -441,7 +521,7 @@ def load_ttf_font(path, size): try: font = ImageFont.truetype(str(path), size) except Exception as e: - raise core.EsphomeError(f"Could not load truetype file {path}: {e}") + raise core.EsphomeError(f"Could not load TrueType file {path}: {e}") return TrueTypeFontWrapper(font) @@ -456,14 +536,35 @@ class GlyphInfo: async def to_code(config): - glyph_to_font_map = {} - font_list = font_map[config[CONF_ID]] - glyphs = [] - for font in font_list: - glyphs.extend(font.glyphs) - for glyph in font.glyphs: - glyph_to_font_map[glyph] = font - glyphs.sort(key=functools.cmp_to_key(glyph_comparator)) + """ + Collect all glyph codepoints, construct a map from a codepoint to a font file. + Codepoints are either explicit (glyphs key in top level or extras) or part of a glyphset. + Codepoints listed in extras use the extra font and override codepoints from glyphsets. + Achieve this by processing the base codepoints first, then the extras + """ + + # get the codepoints from glyphsets and flatten to a set of chrs. + point_set: set[str] = { + chr(x) + for x in flatten( + [glyphsets.unicodes_per_glyphset(x) for x in config[CONF_GLYPHSETS]] + ) + } + # get the codepoints from the glyphs key, flatten to a list of chrs and combine with the points from glyphsets + point_set.update(flatten(config[CONF_GLYPHS])) + size = config[CONF_SIZE] + # Create the codepoint to font file map + base_font = EFont(config[CONF_FILE], size, point_set) + point_font_map: dict[str, EFont] = {c: base_font for c in point_set} + # process extras, updating the map and extending the codepoint list + for extra in config[CONF_EXTRAS]: + extra_points = flatten(extra[CONF_GLYPHS]) + point_set.update(extra_points) + extra_font = EFont(extra[CONF_FILE], size, extra_points) + point_font_map.update({c: extra_font for c in extra_points}) + + codepoints = list(point_set) + codepoints.sort(key=functools.cmp_to_key(glyph_comparator)) glyph_args = {} data = [] bpp = config[CONF_BPP] @@ -473,10 +574,11 @@ async def to_code(config): else: mode = "L" scale = 256 // (1 << bpp) - for glyph in glyphs: - font = glyph_to_font_map[glyph].font - mask = font.getmask(glyph, mode=mode) - offset_x, offset_y = font.getoffset(glyph) + # create the data array for all glyphs + for codepoint in codepoints: + font = point_font_map[codepoint] + mask = font.font.getmask(codepoint, mode=mode) + offset_x, offset_y = font.font.getoffset(codepoint) width, height = mask.size glyph_data = [0] * ((height * width * bpp + 7) // 8) pos = 0 @@ -487,31 +589,34 @@ async def to_code(config): if pixel & (1 << (bpp - bit_num - 1)): glyph_data[pos // 8] |= 0x80 >> (pos % 8) pos += 1 - glyph_args[glyph] = GlyphInfo(len(data), offset_x, offset_y, width, height) + glyph_args[codepoint] = GlyphInfo(len(data), offset_x, offset_y, width, height) data += glyph_data rhs = [HexInt(x) for x in data] prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) + # Create the glyph table that points to data in the above array. glyph_initializer = [] - for glyph in glyphs: + for codepoint in codepoints: glyph_initializer.append( cg.StructInitializer( GlyphData, ( "a_char", - cg.RawExpression(f"(const uint8_t *){cpp_string_escape(glyph)}"), + cg.RawExpression( + f"(const uint8_t *){cpp_string_escape(codepoint)}" + ), ), ( "data", cg.RawExpression( - f"{str(prog_arr)} + {str(glyph_args[glyph].data_len)}" + f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}" ), ), - ("offset_x", glyph_args[glyph].offset_x), - ("offset_y", glyph_args[glyph].offset_y), - ("width", glyph_args[glyph].width), - ("height", glyph_args[glyph].height), + ("offset_x", glyph_args[codepoint].offset_x), + ("offset_y", glyph_args[codepoint].offset_y), + ("width", glyph_args[codepoint].width), + ("height", glyph_args[codepoint].height), ) ) @@ -521,7 +626,7 @@ async def to_code(config): config[CONF_ID], glyphs, len(glyph_initializer), - font_list[0].ascent, - font_list[0].ascent + font_list[0].descent, + base_font.ascent, + base_font.ascent + base_font.descent, bpp, ) diff --git a/requirements.txt b/requirements.txt index 8cc26e4da0..e11e629743 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,9 @@ aioesphomeapi==24.6.2 zeroconf==0.132.2 puremagic==1.27 ruamel.yaml==0.18.6 # dashboard_import +glyphsets==1.0.0 +pillow==10.4.0 +freetype-py==2.5.1 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 diff --git a/requirements_optional.txt b/requirements_optional.txt index 2d57c5fd96..7416753d55 100644 --- a/requirements_optional.txt +++ b/requirements_optional.txt @@ -1,2 +1 @@ -pillow==10.4.0 cairosvg==2.7.1 diff --git a/script/ci-custom.py b/script/ci-custom.py index 9a97d3e4a8..81e3da311a 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -58,7 +58,7 @@ file_types = ( ) cpp_include = ("*.h", "*.c", "*.cpp", "*.tcc") py_include = ("*.py",) -ignore_types = (".ico", ".png", ".woff", ".woff2", "", ".ttf", ".otf") +ignore_types = (".ico", ".png", ".woff", ".woff2", "", ".ttf", ".otf", ".pcf") LINT_FILE_CHECKS = [] LINT_CONTENT_CHECKS = [] diff --git a/tests/components/font/.gitattributes b/tests/components/font/.gitattributes new file mode 100644 index 0000000000..18d9a389e8 --- /dev/null +++ b/tests/components/font/.gitattributes @@ -0,0 +1,2 @@ +*.pcf -text + diff --git a/tests/components/font/MatrixChunky8X.bdf b/tests/components/font/MatrixChunky8X.bdf new file mode 100644 index 0000000000..89b3683180 --- /dev/null +++ b/tests/components/font/MatrixChunky8X.bdf @@ -0,0 +1,7461 @@ +STARTFONT 2.1 +FONT -Trip5-MatrixChunky8X-Medium-R-Normal--8-80-75-75-P-40-ISO10646-1 +SIZE 8 75 75 +FONTBOUNDINGBOX 8 8 -1 0 +COMMENT "Generated by fontforge, http://fontforge.sourceforge.net" +COMMENT "Trip5" +COMMENT "Conventional Chaos" +COMMENT "CC-BY" +STARTPROPERTIES 25 +FOUNDRY "Conventional Chaos" +FAMILY_NAME "MatrixChunky8X" +FONT_NAME "MatrixChunky8X" +FACE_NAME "MatrixChunky8X" +COPYRIGHT "https://github.com/trip5/Matrix-Fonts" +FONT_VERSION "001.000" +WEIGHT_NAME "Medium" +SLANT "R" +SETWIDTH_NAME "Normal" +ADD_STYLE_NAME "" +PIXEL_SIZE 8 +POINT_SIZE 80 +RESOLUTION_X 75 +RESOLUTION_Y 75 +SPACING "P" +AVERAGE_WIDTH 40 +CHARSET_REGISTRY "ISO10646" +CHARSET_ENCODING "1" +CHARSET_COLLECTIONS "ISO8859-2 ISO8859-9 ISO8859-4 ISO10646-1" +FONT_ASCENT 8 +FONT_DESCENT 0 +UNDERLINE_POSITION 0 +UNDERLINE_THICKNESS 1 +X_HEIGHT 6 +CAP_HEIGHT 8 +ENDPROPERTIES +CHARS 535 +STARTCHAR uni0000 +ENCODING 0 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 2 +BITMAP +A0 +00 +40 +00 +A0 +ENDCHAR +STARTCHAR space +ENCODING 32 +SWIDTH 250 0 +DWIDTH 2 0 +BBX 1 1 0 0 +BITMAP +00 +ENDCHAR +STARTCHAR exclam +ENCODING 33 +SWIDTH 250 0 +DWIDTH 2 0 +BBX 1 8 0 0 +BITMAP +80 +80 +80 +80 +80 +80 +00 +80 +ENDCHAR +STARTCHAR quotedbl +ENCODING 34 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 2 0 6 +BITMAP +A0 +A0 +ENDCHAR +STARTCHAR numbersign +ENCODING 35 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +50 +50 +F8 +50 +50 +F8 +50 +50 +ENDCHAR +STARTCHAR dollar +ENCODING 36 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +E0 +80 +E0 +20 +20 +E0 +40 +ENDCHAR +STARTCHAR percent +ENCODING 37 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +20 +40 +40 +80 +A0 +A0 +ENDCHAR +STARTCHAR ampersand +ENCODING 38 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +E0 +A0 +A0 +40 +B0 +A0 +B0 +D0 +ENDCHAR +STARTCHAR quotesingle +ENCODING 39 +SWIDTH 250 0 +DWIDTH 2 0 +BBX 1 3 0 5 +BITMAP +80 +80 +80 +ENDCHAR +STARTCHAR parenleft +ENCODING 40 +SWIDTH 1000 0 +DWIDTH 3 0 +BBX 2 8 0 0 +BITMAP +40 +40 +80 +80 +80 +80 +40 +40 +ENDCHAR +STARTCHAR parenright +ENCODING 41 +SWIDTH 1000 0 +DWIDTH 3 0 +BBX 2 8 0 0 +BITMAP +80 +80 +40 +40 +40 +40 +80 +80 +ENDCHAR +STARTCHAR asterisk +ENCODING 42 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 4 0 4 +BITMAP +90 +60 +60 +90 +ENDCHAR +STARTCHAR plus +ENCODING 43 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 3 0 3 +BITMAP +40 +E0 +40 +ENDCHAR +STARTCHAR comma +ENCODING 44 +SWIDTH 1000 0 +DWIDTH 2 0 +BBX 1 2 0 0 +BITMAP +80 +80 +ENDCHAR +STARTCHAR hyphen +ENCODING 45 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 1 0 4 +BITMAP +E0 +ENDCHAR +STARTCHAR period +ENCODING 46 +SWIDTH 1000 0 +DWIDTH 2 0 +BBX 1 1 0 0 +BITMAP +80 +ENDCHAR +STARTCHAR slash +ENCODING 47 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +20 +40 +40 +40 +80 +80 +80 +ENDCHAR +STARTCHAR zero +ENCODING 48 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR one +ENCODING 49 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +C0 +40 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR two +ENCODING 50 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +20 +20 +E0 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR three +ENCODING 51 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +20 +20 +E0 +20 +20 +20 +E0 +ENDCHAR +STARTCHAR four +ENCODING 52 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +E0 +20 +20 +20 +20 +ENDCHAR +STARTCHAR five +ENCODING 53 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +E0 +20 +20 +20 +E0 +ENDCHAR +STARTCHAR six +ENCODING 54 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR seven +ENCODING 55 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +20 +20 +20 +20 +20 +20 +20 +ENDCHAR +STARTCHAR eight +ENCODING 56 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR nine +ENCODING 57 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +E0 +20 +20 +20 +E0 +ENDCHAR +STARTCHAR colon +ENCODING 58 +SWIDTH 1000 0 +DWIDTH 2 0 +BBX 1 3 0 3 +BITMAP +80 +00 +80 +ENDCHAR +STARTCHAR semicolon +ENCODING 59 +SWIDTH 1000 0 +DWIDTH 2 0 +BBX 1 4 0 2 +BITMAP +80 +00 +80 +80 +ENDCHAR +STARTCHAR less +ENCODING 60 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 2 +BITMAP +20 +40 +80 +40 +20 +ENDCHAR +STARTCHAR equal +ENCODING 61 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 3 0 3 +BITMAP +E0 +00 +E0 +ENDCHAR +STARTCHAR greater +ENCODING 62 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 2 +BITMAP +80 +40 +20 +40 +80 +ENDCHAR +STARTCHAR question +ENCODING 63 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +20 +60 +40 +40 +00 +40 +ENDCHAR +STARTCHAR at +ENCODING 64 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +90 +90 +B0 +B0 +80 +80 +F0 +ENDCHAR +STARTCHAR A +ENCODING 65 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +E0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR B +ENCODING 66 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +C0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR C +ENCODING 67 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +80 +80 +80 +80 +A0 +E0 +ENDCHAR +STARTCHAR D +ENCODING 68 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +C0 +A0 +A0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR E +ENCODING 69 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +E0 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR F +ENCODING 70 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +E0 +80 +80 +80 +80 +ENDCHAR +STARTCHAR G +ENCODING 71 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +E0 +80 +80 +B0 +90 +90 +90 +F0 +ENDCHAR +STARTCHAR H +ENCODING 72 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +90 +90 +90 +F0 +90 +90 +90 +90 +ENDCHAR +STARTCHAR I +ENCODING 73 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +40 +40 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR J +ENCODING 74 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +20 +20 +20 +20 +A0 +A0 +E0 +ENDCHAR +STARTCHAR K +ENCODING 75 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +C0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR L +ENCODING 76 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +80 +80 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR M +ENCODING 77 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +88 +D8 +A8 +A8 +88 +88 +88 +88 +ENDCHAR +STARTCHAR N +ENCODING 78 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +90 +90 +D0 +B0 +90 +90 +90 +90 +ENDCHAR +STARTCHAR O +ENCODING 79 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +90 +90 +90 +90 +90 +90 +F0 +ENDCHAR +STARTCHAR P +ENCODING 80 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +E0 +80 +80 +80 +80 +ENDCHAR +STARTCHAR Q +ENCODING 81 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +90 +90 +90 +B0 +B0 +A0 +D0 +ENDCHAR +STARTCHAR R +ENCODING 82 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +C0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR S +ENCODING 83 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +80 +E0 +20 +20 +A0 +E0 +ENDCHAR +STARTCHAR T +ENCODING 84 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +40 +40 +40 +40 +40 +40 +40 +ENDCHAR +STARTCHAR U +ENCODING 85 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR V +ENCODING 86 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +A0 +A0 +A0 +E0 +40 +ENDCHAR +STARTCHAR W +ENCODING 87 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +A8 +A8 +A8 +A8 +A8 +A8 +F8 +50 +ENDCHAR +STARTCHAR X +ENCODING 88 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +40 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR Y +ENCODING 89 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +E0 +40 +40 +40 +40 +ENDCHAR +STARTCHAR Z +ENCODING 90 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +20 +20 +40 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR bracketleft +ENCODING 91 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +80 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR backslash +ENCODING 92 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +40 +40 +40 +20 +20 +20 +ENDCHAR +STARTCHAR bracketright +ENCODING 93 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +20 +20 +20 +20 +20 +20 +E0 +ENDCHAR +STARTCHAR asciicircum +ENCODING 94 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 3 0 5 +BITMAP +40 +E0 +A0 +ENDCHAR +STARTCHAR underscore +ENCODING 95 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 1 0 0 +BITMAP +E0 +ENDCHAR +STARTCHAR grave +ENCODING 96 +SWIDTH 1000 0 +DWIDTH 3 0 +BBX 2 2 0 6 +BITMAP +80 +40 +ENDCHAR +STARTCHAR a +ENCODING 97 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +20 +E0 +A0 +E0 +ENDCHAR +STARTCHAR b +ENCODING 98 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +80 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR c +ENCODING 99 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR d +ENCODING 100 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +20 +20 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR e +ENCODING 101 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR f +ENCODING 102 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +60 +40 +40 +E0 +40 +40 +40 +ENDCHAR +STARTCHAR g +ENCODING 103 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR h +ENCODING 104 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +80 +E0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR i +ENCODING 105 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR j +ENCODING 106 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +20 +00 +20 +20 +20 +A0 +E0 +ENDCHAR +STARTCHAR k +ENCODING 107 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +A0 +A0 +C0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR l +ENCODING 108 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +C0 +40 +40 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR m +ENCODING 109 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +F8 +A8 +A8 +A8 +A8 +ENDCHAR +STARTCHAR n +ENCODING 110 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR o +ENCODING 111 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR p +ENCODING 112 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +E0 +80 +80 +ENDCHAR +STARTCHAR q +ENCODING 113 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +E0 +20 +20 +ENDCHAR +STARTCHAR r +ENCODING 114 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +80 +80 +80 +80 +ENDCHAR +STARTCHAR s +ENCODING 115 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +80 +E0 +20 +E0 +ENDCHAR +STARTCHAR t +ENCODING 116 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +40 +40 +E0 +40 +40 +40 +60 +ENDCHAR +STARTCHAR u +ENCODING 117 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR v +ENCODING 118 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +A0 +E0 +40 +ENDCHAR +STARTCHAR w +ENCODING 119 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +A8 +A8 +A8 +F8 +50 +ENDCHAR +STARTCHAR x +ENCODING 120 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +40 +A0 +A0 +ENDCHAR +STARTCHAR y +ENCODING 121 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR z +ENCODING 122 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +20 +40 +80 +E0 +ENDCHAR +STARTCHAR braceleft +ENCODING 123 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +60 +40 +40 +C0 +40 +40 +40 +60 +ENDCHAR +STARTCHAR bar +ENCODING 124 +SWIDTH 1000 0 +DWIDTH 2 0 +BBX 1 8 0 0 +BITMAP +80 +80 +80 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR braceright +ENCODING 125 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +C0 +40 +40 +60 +40 +40 +40 +C0 +ENDCHAR +STARTCHAR asciitilde +ENCODING 126 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 2 0 3 +BITMAP +60 +C0 +ENDCHAR +STARTCHAR exclamdown +ENCODING 161 +SWIDTH 250 0 +DWIDTH 2 0 +BBX 1 8 0 0 +BITMAP +80 +80 +00 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR cent +ENCODING 162 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +E0 +80 +80 +80 +E0 +40 +ENDCHAR +STARTCHAR sterling +ENCODING 163 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +70 +50 +40 +E0 +40 +40 +40 +F0 +ENDCHAR +STARTCHAR currency +ENCODING 164 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 6 0 1 +BITMAP +F0 +60 +90 +90 +60 +F0 +ENDCHAR +STARTCHAR yen +ENCODING 165 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +E0 +40 +E0 +40 +40 +40 +ENDCHAR +STARTCHAR brokenbar +ENCODING 166 +SWIDTH 1000 0 +DWIDTH 2 0 +BBX 1 8 0 0 +BITMAP +80 +80 +80 +00 +80 +80 +80 +80 +ENDCHAR +STARTCHAR section +ENCODING 167 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +60 +80 +E0 +A0 +A0 +E0 +20 +C0 +ENDCHAR +STARTCHAR dieresis +ENCODING 168 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +E0 +A0 +C0 +A0 +A0 +E0 +40 +ENDCHAR +STARTCHAR copyright +ENCODING 169 +SWIDTH 1000 0 +DWIDTH 7 0 +BBX 6 7 0 1 +BITMAP +78 +CC +B4 +A4 +B4 +CC +78 +ENDCHAR +STARTCHAR ordfeminine +ENCODING 170 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 3 +BITMAP +60 +A0 +E0 +00 +E0 +ENDCHAR +STARTCHAR guillemotleft +ENCODING 171 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 3 0 3 +BITMAP +50 +F0 +50 +ENDCHAR +STARTCHAR logicalnot +ENCODING 172 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +B8 +A8 +20 +20 +20 +28 +28 +38 +ENDCHAR +STARTCHAR uni00AD +ENCODING 173 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 1 0 4 +BITMAP +E0 +ENDCHAR +STARTCHAR registered +ENCODING 174 +SWIDTH 1000 0 +DWIDTH 7 0 +BBX 6 7 0 1 +BITMAP +78 +CC +B4 +A4 +A4 +CC +78 +ENDCHAR +STARTCHAR macron +ENCODING 175 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +B8 +A0 +20 +38 +20 +20 +20 +20 +ENDCHAR +STARTCHAR degree +ENCODING 176 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 3 0 5 +BITMAP +E0 +A0 +E0 +ENDCHAR +STARTCHAR plusminus +ENCODING 177 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 2 +BITMAP +40 +E0 +40 +00 +E0 +ENDCHAR +STARTCHAR uni00B2 +ENCODING 178 +SWIDTH 1000 0 +DWIDTH 3 0 +BBX 2 5 0 3 +BITMAP +C0 +40 +C0 +80 +C0 +ENDCHAR +STARTCHAR uni00B3 +ENCODING 179 +SWIDTH 1000 0 +DWIDTH 3 0 +BBX 2 5 0 3 +BITMAP +C0 +40 +C0 +40 +C0 +ENDCHAR +STARTCHAR acute +ENCODING 180 +SWIDTH 1000 0 +DWIDTH 3 0 +BBX 2 2 0 6 +BITMAP +40 +C0 +ENDCHAR +STARTCHAR mu +ENCODING 181 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +A0 +A0 +A0 +E0 +80 +ENDCHAR +STARTCHAR paragraph +ENCODING 182 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +D0 +F0 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR periodcentered +ENCODING 183 +SWIDTH 1000 0 +DWIDTH 2 0 +BBX 1 1 0 4 +BITMAP +80 +ENDCHAR +STARTCHAR cedilla +ENCODING 184 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +00 +E0 +00 +00 +00 +E0 +ENDCHAR +STARTCHAR uni00B9 +ENCODING 185 +SWIDTH 1000 0 +DWIDTH 2 0 +BBX 1 5 0 3 +BITMAP +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR ordmasculine +ENCODING 186 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 5 0 3 +BITMAP +E0 +A0 +E0 +00 +E0 +ENDCHAR +STARTCHAR guillemotright +ENCODING 187 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 3 0 3 +BITMAP +A0 +F0 +A0 +ENDCHAR +STARTCHAR onequarter +ENCODING 188 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +30 +60 +40 +F0 +40 +40 +60 +30 +ENDCHAR +STARTCHAR onehalf +ENCODING 189 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +54 +54 +FE +54 +54 +54 +7C +28 +ENDCHAR +STARTCHAR threequarters +ENCODING 190 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +70 +50 +10 +F8 +40 +40 +50 +70 +ENDCHAR +STARTCHAR questiondown +ENCODING 191 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +00 +40 +40 +C0 +80 +A0 +E0 +ENDCHAR +STARTCHAR Agrave +ENCODING 192 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +40 +00 +E0 +A0 +E0 +A0 +A0 +ENDCHAR +STARTCHAR Aacute +ENCODING 193 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +A0 +E0 +A0 +A0 +ENDCHAR +STARTCHAR Acircumflex +ENCODING 194 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +E0 +A0 +E0 +A0 +A0 +ENDCHAR +STARTCHAR Atilde +ENCODING 195 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +E0 +A0 +E0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR Adieresis +ENCODING 196 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +E0 +A0 +E0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR Aring +ENCODING 197 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +00 +E0 +A0 +E0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR AE +ENCODING 198 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +F8 +A0 +A0 +F8 +A0 +A0 +A0 +B8 +ENDCHAR +STARTCHAR Ccedilla +ENCODING 199 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +80 +80 +80 +E0 +40 +ENDCHAR +STARTCHAR Egrave +ENCODING 200 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +40 +00 +E0 +80 +E0 +80 +E0 +ENDCHAR +STARTCHAR Eacute +ENCODING 201 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +80 +E0 +80 +E0 +ENDCHAR +STARTCHAR Ecircumflex +ENCODING 202 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +E0 +80 +E0 +80 +E0 +ENDCHAR +STARTCHAR Edieresis +ENCODING 203 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +E0 +80 +E0 +80 +80 +E0 +ENDCHAR +STARTCHAR Igrave +ENCODING 204 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +40 +00 +E0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Iacute +ENCODING 205 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Icircumflex +ENCODING 206 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +E0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Idieresis +ENCODING 207 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +E0 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Eth +ENCODING 208 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +60 +50 +50 +F0 +50 +50 +50 +60 +ENDCHAR +STARTCHAR Ntilde +ENCODING 209 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +60 +00 +90 +D0 +B0 +90 +90 +90 +ENDCHAR +STARTCHAR Ograve +ENCODING 210 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +20 +00 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Oacute +ENCODING 211 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +80 +00 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Ocircumflex +ENCODING 212 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Otilde +ENCODING 213 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +E0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Odieresis +ENCODING 214 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +E0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR multiply +ENCODING 215 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 3 0 3 +BITMAP +A0 +40 +A0 +ENDCHAR +STARTCHAR Oslash +ENCODING 216 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +90 +90 +B0 +D0 +90 +90 +F0 +ENDCHAR +STARTCHAR Ugrave +ENCODING 217 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +40 +00 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Uacute +ENCODING 218 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Ucircumflex +ENCODING 219 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Udieresis +ENCODING 220 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +A0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Yacute +ENCODING 221 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +A0 +A0 +E0 +40 +40 +ENDCHAR +STARTCHAR Thorn +ENCODING 222 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +E0 +A0 +A0 +E0 +80 +80 +ENDCHAR +STARTCHAR germandbls +ENCODING 223 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +C0 +A0 +A0 +E0 +80 +80 +ENDCHAR +STARTCHAR agrave +ENCODING 224 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +20 +00 +E0 +20 +E0 +A0 +E0 +ENDCHAR +STARTCHAR aacute +ENCODING 225 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +80 +00 +E0 +20 +E0 +A0 +E0 +ENDCHAR +STARTCHAR acircumflex +ENCODING 226 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +E0 +20 +E0 +A0 +E0 +ENDCHAR +STARTCHAR atilde +ENCODING 227 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +E0 +00 +E0 +20 +E0 +A0 +E0 +ENDCHAR +STARTCHAR adieresis +ENCODING 228 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +00 +E0 +20 +E0 +A0 +E0 +ENDCHAR +STARTCHAR aring +ENCODING 229 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +00 +E0 +20 +E0 +A0 +E0 +ENDCHAR +STARTCHAR ae +ENCODING 230 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +F8 +28 +F8 +A0 +F8 +ENDCHAR +STARTCHAR ccedilla +ENCODING 231 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +80 +80 +E0 +40 +ENDCHAR +STARTCHAR egrave +ENCODING 232 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +20 +00 +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR eacute +ENCODING 233 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +80 +00 +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR ecircumflex +ENCODING 234 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR edieresis +ENCODING 235 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +00 +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR igrave +ENCODING 236 +SWIDTH 375 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +40 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR iacute +ENCODING 237 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR icircumflex +ENCODING 238 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR idieresis +ENCODING 239 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR eth +ENCODING 240 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +A0 +20 +E0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR ntilde +ENCODING 241 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +E0 +00 +E0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR ograve +ENCODING 242 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +80 +40 +00 +E0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR oacute +ENCODING 243 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +20 +40 +00 +E0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR ocircumflex +ENCODING 244 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +A0 +00 +E0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR otilde +ENCODING 245 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +E0 +00 +E0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR odieresis +ENCODING 246 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +00 +E0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR divide +ENCODING 247 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 2 +BITMAP +40 +00 +E0 +00 +40 +ENDCHAR +STARTCHAR oslash +ENCODING 248 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 5 0 0 +BITMAP +F0 +B0 +D0 +90 +F0 +ENDCHAR +STARTCHAR ugrave +ENCODING 249 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +80 +40 +00 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR uacute +ENCODING 250 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +20 +40 +00 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR ucircumflex +ENCODING 251 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +A0 +00 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR udieresis +ENCODING 252 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +00 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR yacute +ENCODING 253 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +A0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR thorn +ENCODING 254 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +80 +E0 +A0 +A0 +E0 +80 +ENDCHAR +STARTCHAR ydieresis +ENCODING 255 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +A0 +A0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR Amacron +ENCODING 256 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +E0 +A0 +E0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR amacron +ENCODING 257 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +E0 +00 +E0 +20 +E0 +A0 +E0 +ENDCHAR +STARTCHAR Abreve +ENCODING 258 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +A0 +E0 +A0 +A0 +ENDCHAR +STARTCHAR abreve +ENCODING 259 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +20 +E0 +A0 +E0 +ENDCHAR +STARTCHAR Aogonek +ENCODING 260 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 4 8 0 0 +BITMAP +E0 +A0 +A0 +E0 +A0 +A0 +A0 +30 +ENDCHAR +STARTCHAR aogonek +ENCODING 261 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 4 6 0 0 +BITMAP +E0 +20 +E0 +A0 +E0 +30 +ENDCHAR +STARTCHAR Cacute +ENCODING 262 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +80 +00 +E0 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR cacute +ENCODING 263 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +80 +00 +E0 +80 +80 +E0 +ENDCHAR +STARTCHAR Ccircumflex +ENCODING 264 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +E0 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR ccircumflex +ENCODING 265 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +A0 +00 +E0 +80 +80 +E0 +ENDCHAR +STARTCHAR Cdotaccent +ENCODING 266 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +00 +E0 +80 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR cdotaccent +ENCODING 267 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +00 +E0 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR Ccaron +ENCODING 268 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR ccaron +ENCODING 269 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +40 +00 +E0 +80 +80 +E0 +ENDCHAR +STARTCHAR Dcaron +ENCODING 270 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +C0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR dcaron +ENCODING 271 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +30 +30 +20 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Dcroat +ENCODING 272 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +60 +50 +50 +F0 +50 +50 +50 +70 +ENDCHAR +STARTCHAR dcroat +ENCODING 273 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +20 +70 +20 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Emacron +ENCODING 274 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +E0 +80 +E0 +80 +80 +E0 +ENDCHAR +STARTCHAR emacron +ENCODING 275 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +E0 +00 +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR Ebreve +ENCODING 276 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +80 +E0 +80 +E0 +ENDCHAR +STARTCHAR ebreve +ENCODING 277 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR Edotaccent +ENCODING 278 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +00 +E0 +80 +E0 +80 +80 +E0 +ENDCHAR +STARTCHAR edotaccent +ENCODING 279 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +00 +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR Eogonek +ENCODING 280 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 4 8 0 0 +BITMAP +E0 +80 +80 +E0 +80 +80 +E0 +30 +ENDCHAR +STARTCHAR eogonek +ENCODING 281 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +E0 +A0 +E0 +80 +E0 +40 +ENDCHAR +STARTCHAR Ecaron +ENCODING 282 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +80 +E0 +80 +E0 +ENDCHAR +STARTCHAR ecaron +ENCODING 283 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR Gcircumflex +ENCODING 284 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +40 +A0 +00 +E0 +80 +B0 +90 +F0 +ENDCHAR +STARTCHAR gcircumflex +ENCODING 285 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +E0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR Gbreve +ENCODING 286 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +A0 +40 +00 +E0 +80 +B0 +90 +F0 +ENDCHAR +STARTCHAR gbreve +ENCODING 287 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR Gdotaccent +ENCODING 288 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +40 +00 +E0 +80 +80 +B0 +90 +F0 +ENDCHAR +STARTCHAR gdotaccent +ENCODING 289 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +00 +E0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR uni0122 +ENCODING 290 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +E0 +80 +80 +B0 +90 +90 +F0 +40 +ENDCHAR +STARTCHAR uni0123 +ENCODING 291 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +40 +00 +E0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR Hcircumflex +ENCODING 292 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +A0 +A0 +E0 +A0 +A0 +ENDCHAR +STARTCHAR hcircumflex +ENCODING 293 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +80 +80 +E0 +A0 +A0 +ENDCHAR +STARTCHAR Hbar +ENCODING 294 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +50 +F8 +50 +70 +50 +50 +50 +50 +ENDCHAR +STARTCHAR hbar +ENCODING 295 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +40 +E0 +40 +40 +70 +50 +50 +50 +ENDCHAR +STARTCHAR Itilde +ENCODING 296 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +E0 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR itilde +ENCODING 297 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +E0 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Imacron +ENCODING 298 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +E0 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR imacron +ENCODING 299 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +E0 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Ibreve +ENCODING 300 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR ibreve +ENCODING 301 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Iogonek +ENCODING 302 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +40 +40 +40 +40 +40 +E0 +40 +ENDCHAR +STARTCHAR iogonek +ENCODING 303 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +00 +C0 +40 +40 +E0 +20 +ENDCHAR +STARTCHAR Idotaccent +ENCODING 304 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +00 +E0 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR dotlessi +ENCODING 305 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR IJ +ENCODING 306 +SWIDTH 875 0 +DWIDTH 7 0 +BBX 6 8 0 0 +BITMAP +E4 +44 +44 +44 +44 +54 +54 +FC +ENDCHAR +STARTCHAR ij +ENCODING 307 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 5 7 -1 0 +BITMAP +48 +00 +C8 +48 +48 +68 +F8 +ENDCHAR +STARTCHAR Jcircumflex +ENCODING 308 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +20 +50 +00 +20 +20 +A0 +A0 +E0 +ENDCHAR +STARTCHAR jcircumflex +ENCODING 309 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 7 0 0 +BITMAP +20 +50 +00 +20 +20 +A0 +E0 +ENDCHAR +STARTCHAR uni0136 +ENCODING 310 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +C0 +A0 +A0 +A0 +40 +ENDCHAR +STARTCHAR uni0137 +ENCODING 311 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +A0 +A0 +C0 +A0 +A0 +40 +ENDCHAR +STARTCHAR kgreenlandic +ENCODING 312 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +A0 +A0 +C0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR Lacute +ENCODING 313 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +80 +00 +80 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR lacute +ENCODING 314 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +80 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR uni013B +ENCODING 315 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +80 +80 +80 +80 +E0 +40 +ENDCHAR +STARTCHAR uni013C +ENCODING 316 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +C0 +40 +40 +40 +40 +40 +E0 +40 +ENDCHAR +STARTCHAR Lcaron +ENCODING 317 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +80 +80 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR lcaron +ENCODING 318 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +D0 +50 +40 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Ldot +ENCODING 319 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +A0 +80 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR ldot +ENCODING 320 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +C0 +40 +60 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Lslash +ENCODING 321 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +40 +40 +60 +C0 +40 +40 +40 +70 +ENDCHAR +STARTCHAR lslash +ENCODING 322 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +C0 +40 +60 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Nacute +ENCODING 323 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +20 +40 +00 +90 +D0 +B0 +90 +90 +ENDCHAR +STARTCHAR nacute +ENCODING 324 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR uni0145 +ENCODING 325 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +90 +90 +D0 +B0 +90 +90 +90 +40 +ENDCHAR +STARTCHAR uni0146 +ENCODING 326 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +A0 +A0 +40 +ENDCHAR +STARTCHAR Ncaron +ENCODING 327 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +A0 +40 +00 +90 +D0 +B0 +90 +90 +ENDCHAR +STARTCHAR ncaron +ENCODING 328 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR napostrophe +ENCODING 329 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +80 +80 +00 +70 +50 +50 +50 +50 +ENDCHAR +STARTCHAR Eng +ENCODING 330 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +90 +90 +90 +90 +90 +90 +20 +ENDCHAR +STARTCHAR eng +ENCODING 331 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +E0 +A0 +A0 +A0 +A0 +20 +ENDCHAR +STARTCHAR Omacron +ENCODING 332 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +E0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR omacron +ENCODING 333 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +E0 +00 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Obreve +ENCODING 334 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR obreve +ENCODING 335 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +40 +00 +E0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Ohungarumlaut +ENCODING 336 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +E0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR ohungarumlaut +ENCODING 337 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +00 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR OE +ENCODING 338 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +F8 +A0 +A0 +B8 +A0 +A0 +A0 +F8 +ENDCHAR +STARTCHAR oe +ENCODING 339 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +F8 +A8 +B8 +A0 +F8 +ENDCHAR +STARTCHAR Racute +ENCODING 340 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +A0 +C0 +A0 +A0 +ENDCHAR +STARTCHAR racute +ENCODING 341 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +80 +80 +80 +80 +ENDCHAR +STARTCHAR uni0156 +ENCODING 342 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +C0 +A0 +A0 +A0 +40 +ENDCHAR +STARTCHAR uni0157 +ENCODING 343 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +E0 +80 +80 +80 +80 +40 +ENDCHAR +STARTCHAR Rcaron +ENCODING 344 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +A0 +C0 +A0 +A0 +ENDCHAR +STARTCHAR rcaron +ENCODING 345 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +80 +80 +80 +80 +ENDCHAR +STARTCHAR Sacute +ENCODING 346 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +80 +00 +E0 +80 +E0 +20 +E0 +ENDCHAR +STARTCHAR sacute +ENCODING 347 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +80 +E0 +20 +E0 +ENDCHAR +STARTCHAR Scircumflex +ENCODING 348 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +E0 +80 +E0 +20 +E0 +ENDCHAR +STARTCHAR scircumflex +ENCODING 349 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +E0 +80 +E0 +20 +E0 +ENDCHAR +STARTCHAR Scedilla +ENCODING 350 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +E0 +20 +20 +E0 +40 +ENDCHAR +STARTCHAR scedilla +ENCODING 351 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +E0 +80 +E0 +20 +E0 +40 +ENDCHAR +STARTCHAR Scaron +ENCODING 352 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +80 +E0 +20 +E0 +ENDCHAR +STARTCHAR scaron +ENCODING 353 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +80 +E0 +20 +E0 +ENDCHAR +STARTCHAR uni0162 +ENCODING 354 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +40 +40 +40 +40 +40 +60 +20 +ENDCHAR +STARTCHAR uni0163 +ENCODING 355 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +40 +E0 +40 +40 +40 +60 +20 +ENDCHAR +STARTCHAR Tcaron +ENCODING 356 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +E0 +40 +40 +40 +40 +40 +ENDCHAR +STARTCHAR tcaron +ENCODING 357 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +50 +50 +40 +E0 +40 +40 +40 +60 +ENDCHAR +STARTCHAR Tbar +ENCODING 358 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +40 +40 +E0 +40 +40 +40 +40 +ENDCHAR +STARTCHAR tbar +ENCODING 359 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +40 +E0 +40 +E0 +40 +40 +60 +ENDCHAR +STARTCHAR Utilde +ENCODING 360 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +A0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR utilde +ENCODING 361 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +E0 +00 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Umacron +ENCODING 362 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +A0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR umacron +ENCODING 363 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +E0 +00 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Ubreve +ENCODING 364 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR ubreve +ENCODING 365 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +40 +00 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Uring +ENCODING 366 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +00 +A0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR uring +ENCODING 367 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +00 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Uhungarumlaut +ENCODING 368 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +A0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR uhungarumlaut +ENCODING 369 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +00 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Uogonek +ENCODING 370 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +A0 +A0 +A0 +E0 +20 +ENDCHAR +STARTCHAR uogonek +ENCODING 371 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +A0 +E0 +20 +ENDCHAR +STARTCHAR Wcircumflex +ENCODING 372 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +20 +50 +00 +A8 +A8 +A8 +F8 +50 +ENDCHAR +STARTCHAR wcircumflex +ENCODING 373 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 7 0 0 +BITMAP +20 +50 +00 +A8 +A8 +F8 +50 +ENDCHAR +STARTCHAR Ycircumflex +ENCODING 374 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +A0 +A0 +E0 +40 +40 +ENDCHAR +STARTCHAR ycircumflex +ENCODING 375 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +A0 +00 +A0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR Ydieresis +ENCODING 376 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +A0 +A0 +E0 +40 +40 +40 +ENDCHAR +STARTCHAR Zacute +ENCODING 377 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +20 +40 +80 +E0 +ENDCHAR +STARTCHAR zacute +ENCODING 378 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +20 +40 +80 +E0 +ENDCHAR +STARTCHAR Zdotaccent +ENCODING 379 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +00 +E0 +20 +40 +80 +80 +E0 +ENDCHAR +STARTCHAR zdotaccent +ENCODING 380 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +00 +E0 +20 +40 +80 +E0 +ENDCHAR +STARTCHAR Zcaron +ENCODING 381 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +20 +40 +80 +E0 +ENDCHAR +STARTCHAR zcaron +ENCODING 382 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +40 +00 +E0 +20 +40 +80 +E0 +ENDCHAR +STARTCHAR longs +ENCODING 383 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR Alphatonos +ENCODING 902 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +B8 +A8 +28 +38 +28 +28 +28 +28 +ENDCHAR +STARTCHAR Epsilontonos +ENCODING 904 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +B8 +A0 +20 +38 +20 +20 +20 +38 +ENDCHAR +STARTCHAR Etatonos +ENCODING 905 +SWIDTH 875 0 +DWIDTH 7 0 +BBX 6 8 0 0 +BITMAP +A4 +A4 +24 +3C +24 +24 +24 +24 +ENDCHAR +STARTCHAR Iotatonos +ENCODING 906 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +B8 +90 +10 +10 +10 +10 +10 +38 +ENDCHAR +STARTCHAR Omicrontonos +ENCODING 908 +SWIDTH 875 0 +DWIDTH 7 0 +BBX 6 8 0 0 +BITMAP +BC +A4 +24 +24 +24 +24 +24 +3C +ENDCHAR +STARTCHAR Upsilontonos +ENCODING 910 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +A8 +A8 +28 +38 +10 +10 +10 +10 +ENDCHAR +STARTCHAR Omegatonos +ENCODING 911 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +BE +A2 +22 +22 +22 +36 +14 +36 +ENDCHAR +STARTCHAR Alpha +ENCODING 913 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +E0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR Beta +ENCODING 914 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +C0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR Gamma +ENCODING 915 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR uni0394 +ENCODING 916 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +20 +20 +50 +50 +88 +88 +88 +F8 +ENDCHAR +STARTCHAR Epsilon +ENCODING 917 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +E0 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR Zeta +ENCODING 918 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +20 +20 +40 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR Eta +ENCODING 919 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +90 +90 +90 +F0 +90 +90 +90 +90 +ENDCHAR +STARTCHAR Theta +ENCODING 920 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +90 +90 +F0 +90 +90 +90 +F0 +ENDCHAR +STARTCHAR Iota +ENCODING 921 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +40 +40 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Kappa +ENCODING 922 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +C0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR Lambda +ENCODING 923 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +20 +20 +50 +50 +50 +88 +88 +88 +ENDCHAR +STARTCHAR Mu +ENCODING 924 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +88 +D8 +A8 +A8 +88 +88 +88 +88 +ENDCHAR +STARTCHAR Nu +ENCODING 925 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +90 +90 +D0 +B0 +90 +90 +90 +90 +ENDCHAR +STARTCHAR Xi +ENCODING 926 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +00 +E0 +00 +00 +00 +E0 +ENDCHAR +STARTCHAR Omicron +ENCODING 927 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +90 +90 +90 +90 +90 +90 +F0 +ENDCHAR +STARTCHAR Pi +ENCODING 928 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +90 +90 +90 +90 +90 +90 +90 +ENDCHAR +STARTCHAR Rho +ENCODING 929 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +E0 +80 +80 +80 +80 +ENDCHAR +STARTCHAR Sigma +ENCODING 931 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +40 +20 +40 +80 +80 +E0 +ENDCHAR +STARTCHAR Tau +ENCODING 932 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +40 +40 +40 +40 +40 +40 +40 +ENDCHAR +STARTCHAR Upsilon +ENCODING 933 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +E0 +40 +40 +40 +40 +ENDCHAR +STARTCHAR Phi +ENCODING 934 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +20 +F8 +A8 +A8 +A8 +A8 +F8 +20 +ENDCHAR +STARTCHAR Chi +ENCODING 935 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +40 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR Psi +ENCODING 936 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +A8 +A8 +A8 +F8 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni03A9 +ENCODING 937 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +F8 +88 +88 +88 +88 +D8 +50 +D8 +ENDCHAR +STARTCHAR Iotadieresis +ENCODING 938 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +E0 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR Upsilondieresis +ENCODING 939 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +A0 +A0 +E0 +40 +40 +40 +ENDCHAR +STARTCHAR alphatonos +ENCODING 940 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +20 +40 +00 +D0 +A0 +A0 +A0 +D0 +ENDCHAR +STARTCHAR epsilontonos +ENCODING 941 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +80 +40 +80 +E0 +ENDCHAR +STARTCHAR etatonos +ENCODING 942 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +A0 +A0 +A0 +20 +ENDCHAR +STARTCHAR iotatonos +ENCODING 943 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +C0 +40 +40 +40 +60 +ENDCHAR +STARTCHAR alpha +ENCODING 945 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 5 0 0 +BITMAP +D0 +A0 +A0 +A0 +D0 +ENDCHAR +STARTCHAR beta +ENCODING 946 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +60 +A0 +A0 +C0 +A0 +A0 +E0 +80 +ENDCHAR +STARTCHAR gamma +ENCODING 947 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 5 0 0 +BITMAP +D0 +50 +70 +20 +20 +ENDCHAR +STARTCHAR delta +ENCODING 948 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +40 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR epsilon +ENCODING 949 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +80 +40 +80 +E0 +ENDCHAR +STARTCHAR zeta +ENCODING 950 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +20 +C0 +80 +80 +80 +E0 +20 +ENDCHAR +STARTCHAR eta +ENCODING 951 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +A0 +A0 +20 +ENDCHAR +STARTCHAR theta +ENCODING 952 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR iota +ENCODING 953 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +C0 +40 +40 +40 +60 +ENDCHAR +STARTCHAR kappa +ENCODING 954 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +C0 +A0 +A0 +ENDCHAR +STARTCHAR lambda +ENCODING 955 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +60 +20 +20 +50 +50 +88 +88 +88 +ENDCHAR +STARTCHAR uni03BC +ENCODING 956 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +A0 +E0 +80 +ENDCHAR +STARTCHAR nu +ENCODING 957 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +A0 +E0 +40 +ENDCHAR +STARTCHAR xi +ENCODING 958 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +E0 +80 +60 +80 +80 +E0 +20 +ENDCHAR +STARTCHAR omicron +ENCODING 959 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR pi +ENCODING 960 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +F8 +50 +50 +50 +58 +ENDCHAR +STARTCHAR rho +ENCODING 961 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +A0 +E0 +80 +ENDCHAR +STARTCHAR sigma1 +ENCODING 962 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +80 +80 +E0 +20 +ENDCHAR +STARTCHAR sigma +ENCODING 963 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 5 0 0 +BITMAP +F0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR tau +ENCODING 964 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +40 +40 +40 +60 +ENDCHAR +STARTCHAR upsilon +ENCODING 965 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR phi +ENCODING 966 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +B8 +A8 +A8 +F8 +20 +ENDCHAR +STARTCHAR chi +ENCODING 967 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +40 +A0 +A0 +ENDCHAR +STARTCHAR psi +ENCODING 968 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +A8 +A8 +A8 +F8 +20 +ENDCHAR +STARTCHAR omega +ENCODING 969 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +88 +A8 +A8 +F8 +50 +ENDCHAR +STARTCHAR iotadieresis +ENCODING 970 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +00 +C0 +40 +40 +40 +60 +ENDCHAR +STARTCHAR upsilondieresis +ENCODING 971 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +00 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR omicrontonos +ENCODING 972 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR upsilontonos +ENCODING 973 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +20 +40 +00 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR omegatonos +ENCODING 974 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +10 +20 +00 +88 +A8 +A8 +F8 +50 +ENDCHAR +STARTCHAR uni0401 +ENCODING 1025 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +E0 +80 +E0 +80 +80 +E0 +ENDCHAR +STARTCHAR uni0404 +ENCODING 1028 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +70 +80 +80 +E0 +80 +80 +80 +70 +ENDCHAR +STARTCHAR uni0406 +ENCODING 1030 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +40 +40 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR uni0407 +ENCODING 1031 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +00 +E0 +40 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR uni040E +ENCODING 1038 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +00 +A0 +A0 +E0 +20 +20 +60 +ENDCHAR +STARTCHAR uni0410 +ENCODING 1040 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +E0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR uni0411 +ENCODING 1041 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR uni0412 +ENCODING 1042 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +C0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR uni0413 +ENCODING 1043 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR uni0414 +ENCODING 1044 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +30 +50 +50 +50 +50 +50 +F8 +88 +ENDCHAR +STARTCHAR uni0415 +ENCODING 1045 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +80 +80 +E0 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR uni0416 +ENCODING 1046 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +A8 +A8 +A8 +F8 +A8 +A8 +A8 +A8 +ENDCHAR +STARTCHAR uni0417 +ENCODING 1047 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +20 +40 +20 +20 +A0 +E0 +ENDCHAR +STARTCHAR uni0418 +ENCODING 1048 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +88 +88 +98 +A8 +C8 +88 +88 +88 +ENDCHAR +STARTCHAR uni0419 +ENCODING 1049 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +A8 +88 +98 +A8 +C8 +88 +88 +88 +ENDCHAR +STARTCHAR uni041A +ENCODING 1050 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +C0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR uni041B +ENCODING 1051 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +30 +50 +50 +50 +50 +50 +50 +D0 +ENDCHAR +STARTCHAR uni041C +ENCODING 1052 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +88 +D8 +A8 +A8 +88 +88 +88 +88 +ENDCHAR +STARTCHAR uni041D +ENCODING 1053 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +90 +90 +90 +F0 +90 +90 +90 +90 +ENDCHAR +STARTCHAR uni041E +ENCODING 1054 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +90 +90 +90 +90 +90 +90 +F0 +ENDCHAR +STARTCHAR uni041F +ENCODING 1055 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +90 +90 +90 +90 +90 +90 +90 +ENDCHAR +STARTCHAR uni0420 +ENCODING 1056 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +E0 +80 +80 +80 +80 +ENDCHAR +STARTCHAR uni0421 +ENCODING 1057 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +80 +80 +80 +80 +A0 +E0 +ENDCHAR +STARTCHAR uni0422 +ENCODING 1058 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +40 +40 +40 +40 +40 +40 +40 +ENDCHAR +STARTCHAR uni0423 +ENCODING 1059 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +90 +90 +90 +F0 +10 +10 +10 +70 +ENDCHAR +STARTCHAR uni0424 +ENCODING 1060 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +20 +F8 +A8 +A8 +A8 +A8 +F8 +20 +ENDCHAR +STARTCHAR uni0425 +ENCODING 1061 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +40 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR uni0426 +ENCODING 1062 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +A0 +A0 +A0 +A0 +A0 +A0 +A0 +F0 +ENDCHAR +STARTCHAR uni0427 +ENCODING 1063 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +A0 +E0 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni0428 +ENCODING 1064 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +A8 +A8 +A8 +A8 +A8 +A8 +A8 +F8 +ENDCHAR +STARTCHAR uni0429 +ENCODING 1065 +SWIDTH 1000 0 +DWIDTH 7 0 +BBX 6 8 0 0 +BITMAP +A8 +A8 +A8 +A8 +A8 +A8 +A8 +FC +ENDCHAR +STARTCHAR uni042A +ENCODING 1066 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +C0 +40 +40 +70 +50 +50 +50 +70 +ENDCHAR +STARTCHAR uni042B +ENCODING 1067 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +88 +88 +88 +E8 +A8 +A8 +A8 +E8 +ENDCHAR +STARTCHAR uni042C +ENCODING 1068 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +80 +80 +80 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR uni042D +ENCODING 1069 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +E0 +10 +10 +70 +10 +10 +10 +E0 +ENDCHAR +STARTCHAR uni042E +ENCODING 1070 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +B8 +A8 +A8 +E8 +A8 +A8 +A8 +B8 +ENDCHAR +STARTCHAR uni042F +ENCODING 1071 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +A0 +A0 +60 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR uni0430 +ENCODING 1072 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +20 +E0 +A0 +E0 +ENDCHAR +STARTCHAR uni0431 +ENCODING 1073 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +60 +80 +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR uni0432 +ENCODING 1074 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +C0 +A0 +E0 +ENDCHAR +STARTCHAR uni0433 +ENCODING 1075 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +80 +80 +80 +80 +ENDCHAR +STARTCHAR uni0434 +ENCODING 1076 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +30 +50 +50 +F8 +88 +ENDCHAR +STARTCHAR uni0435 +ENCODING 1077 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR uni0436 +ENCODING 1078 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +A8 +A8 +F8 +A8 +A8 +ENDCHAR +STARTCHAR uni0437 +ENCODING 1079 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +20 +40 +20 +E0 +ENDCHAR +STARTCHAR uni0438 +ENCODING 1080 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 5 0 0 +BITMAP +90 +B0 +D0 +90 +90 +ENDCHAR +STARTCHAR uni0439 +ENCODING 1081 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 7 0 0 +BITMAP +60 +00 +90 +B0 +D0 +90 +90 +ENDCHAR +STARTCHAR uni043A +ENCODING 1082 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +C0 +A0 +A0 +ENDCHAR +STARTCHAR uni043B +ENCODING 1083 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 5 0 0 +BITMAP +30 +50 +50 +50 +D0 +ENDCHAR +STARTCHAR uni043C +ENCODING 1084 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +88 +D8 +A8 +88 +88 +ENDCHAR +STARTCHAR uni043D +ENCODING 1085 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +E0 +A0 +A0 +ENDCHAR +STARTCHAR uni043E +ENCODING 1086 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR uni043F +ENCODING 1087 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +A0 +A0 +A0 +ENDCHAR +STARTCHAR uni0440 +ENCODING 1088 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +E0 +80 +80 +ENDCHAR +STARTCHAR uni0441 +ENCODING 1089 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +80 +80 +80 +E0 +ENDCHAR +STARTCHAR uni0442 +ENCODING 1090 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +40 +40 +40 +40 +ENDCHAR +STARTCHAR uni0443 +ENCODING 1091 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR uni0444 +ENCODING 1092 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +20 +20 +20 +F8 +A8 +A8 +F8 +20 +ENDCHAR +STARTCHAR uni0445 +ENCODING 1093 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +40 +A0 +A0 +ENDCHAR +STARTCHAR uni0446 +ENCODING 1094 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 5 0 0 +BITMAP +A0 +A0 +A0 +A0 +F0 +ENDCHAR +STARTCHAR uni0447 +ENCODING 1095 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +A0 +A0 +E0 +20 +20 +ENDCHAR +STARTCHAR uni0448 +ENCODING 1096 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +A8 +A8 +A8 +A8 +F8 +ENDCHAR +STARTCHAR uni0449 +ENCODING 1097 +SWIDTH 1000 0 +DWIDTH 7 0 +BBX 6 5 0 0 +BITMAP +A8 +A8 +A8 +A8 +FC +ENDCHAR +STARTCHAR uni044A +ENCODING 1098 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 5 0 0 +BITMAP +C0 +40 +70 +50 +70 +ENDCHAR +STARTCHAR uni044B +ENCODING 1099 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +88 +88 +E8 +A8 +E8 +ENDCHAR +STARTCHAR uni044C +ENCODING 1100 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +80 +80 +E0 +A0 +E0 +ENDCHAR +STARTCHAR uni044D +ENCODING 1101 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +C0 +20 +E0 +20 +C0 +ENDCHAR +STARTCHAR uni044E +ENCODING 1102 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 5 0 0 +BITMAP +B8 +A8 +E8 +A8 +B8 +ENDCHAR +STARTCHAR uni044F +ENCODING 1103 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +E0 +A0 +60 +A0 +A0 +ENDCHAR +STARTCHAR uni0451 +ENCODING 1105 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +00 +E0 +A0 +E0 +80 +E0 +ENDCHAR +STARTCHAR uni0454 +ENCODING 1108 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 5 0 0 +BITMAP +60 +80 +E0 +80 +60 +ENDCHAR +STARTCHAR uni0456 +ENCODING 1110 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR uni0457 +ENCODING 1111 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +A0 +00 +C0 +40 +40 +40 +E0 +ENDCHAR +STARTCHAR uni045E +ENCODING 1118 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +00 +A0 +A0 +E0 +20 +60 +ENDCHAR +STARTCHAR uni0490 +ENCODING 1168 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +10 +E0 +80 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR uni0491 +ENCODING 1169 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +20 +C0 +80 +80 +80 +80 +ENDCHAR +STARTCHAR uni2002 +ENCODING 8194 +SWIDTH 375 0 +DWIDTH 3 0 +BBX 1 1 0 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2003 +ENCODING 8195 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 1 1 0 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2009 +ENCODING 8201 +SWIDTH 1000 0 +DWIDTH 1 0 +BBX 1 1 6 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2010 +ENCODING 8208 +SWIDTH 1000 0 +DWIDTH 1 0 +BBX 1 1 0 4 +BITMAP +80 +ENDCHAR +STARTCHAR endash +ENCODING 8211 +SWIDTH 1000 0 +DWIDTH 3 0 +BBX 2 1 0 4 +BITMAP +C0 +ENDCHAR +STARTCHAR emdash +ENCODING 8212 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 1 0 4 +BITMAP +F0 +ENDCHAR +STARTCHAR uni2015 +ENCODING 8213 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 4 1 0 4 +BITMAP +F0 +ENDCHAR +STARTCHAR bullet +ENCODING 8226 +SWIDTH 1000 0 +DWIDTH 2 0 +BBX 1 1 0 4 +BITMAP +80 +ENDCHAR +STARTCHAR colonmonetary +ENCODING 8353 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +10 +F0 +A0 +A0 +A0 +A0 +F0 +40 +ENDCHAR +STARTCHAR uni20A2 +ENCODING 8354 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +80 +80 +B0 +A0 +A0 +A0 +F0 +ENDCHAR +STARTCHAR uni20A6 +ENCODING 8358 +SWIDTH 875 0 +DWIDTH 7 0 +BBX 6 8 0 0 +BITMAP +48 +48 +EC +58 +CC +48 +48 +48 +ENDCHAR +STARTCHAR uni20A9 +ENCODING 8361 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +54 +54 +FE +54 +54 +54 +7C +28 +ENDCHAR +STARTCHAR uni20AA +ENCODING 8362 +SWIDTH 875 0 +DWIDTH 7 0 +BBX 6 6 0 0 +BITMAP +F4 +94 +B4 +B4 +A4 +BC +ENDCHAR +STARTCHAR dong +ENCODING 8363 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +20 +70 +20 +E0 +A0 +E0 +00 +E0 +ENDCHAR +STARTCHAR Euro +ENCODING 8364 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +30 +60 +40 +F0 +40 +40 +60 +30 +ENDCHAR +STARTCHAR uni20AD +ENCODING 8365 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +50 +50 +60 +F0 +60 +50 +50 +50 +ENDCHAR +STARTCHAR uni20AE +ENCODING 8366 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +40 +60 +40 +C0 +40 +40 +40 +ENDCHAR +STARTCHAR uni20B1 +ENCODING 8369 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +70 +D8 +50 +70 +40 +40 +40 +40 +ENDCHAR +STARTCHAR uni20B2 +ENCODING 8370 +SWIDTH 1000 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +40 +E0 +80 +B0 +90 +90 +F0 +40 +ENDCHAR +STARTCHAR uni20B4 +ENCODING 8372 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +70 +50 +10 +F8 +40 +50 +50 +70 +ENDCHAR +STARTCHAR uni20B5 +ENCODING 8373 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 7 0 0 +BITMAP +40 +E0 +80 +80 +80 +E0 +40 +ENDCHAR +STARTCHAR uni20B8 +ENCODING 8376 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +E0 +00 +E0 +40 +40 +40 +40 +40 +ENDCHAR +STARTCHAR uni20B9 +ENCODING 8377 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +20 +F0 +80 +40 +20 +10 +10 +ENDCHAR +STARTCHAR uni20BA +ENCODING 8378 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +E0 +40 +E0 +40 +40 +50 +50 +70 +ENDCHAR +STARTCHAR uni20BC +ENCODING 8380 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 7 0 0 +BITMAP +20 +20 +F8 +A8 +A8 +A8 +A8 +ENDCHAR +STARTCHAR uni20BD +ENCODING 8381 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +70 +50 +70 +40 +E0 +40 +40 +40 +ENDCHAR +STARTCHAR uni20BE +ENCODING 8382 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +20 +F8 +A8 +A8 +A0 +80 +40 +F8 +ENDCHAR +STARTCHAR uni20BF +ENCODING 8383 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +40 +E0 +A0 +C0 +A0 +A0 +E0 +40 +ENDCHAR +STARTCHAR uni20C0 +ENCODING 8384 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 3 6 0 0 +BITMAP +E0 +80 +80 +E0 +00 +E0 +ENDCHAR +STARTCHAR uni2103 +ENCODING 8451 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +B8 +A8 +20 +20 +20 +20 +28 +38 +ENDCHAR +STARTCHAR uni2109 +ENCODING 8457 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +B8 +A0 +20 +38 +20 +20 +20 +20 +ENDCHAR +STARTCHAR uni2460 +ENCODING 9312 +SWIDTH 125 0 +DWIDTH 1 0 +BBX 1 1 4 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2461 +ENCODING 9313 +SWIDTH 250 0 +DWIDTH 2 0 +BBX 1 1 1 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2462 +ENCODING 9314 +SWIDTH 375 0 +DWIDTH 3 0 +BBX 1 1 3 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2463 +ENCODING 9315 +SWIDTH 500 0 +DWIDTH 4 0 +BBX 1 1 4 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2464 +ENCODING 9316 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 1 1 4 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2465 +ENCODING 9317 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 1 1 1 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2466 +ENCODING 9318 +SWIDTH 875 0 +DWIDTH 7 0 +BBX 1 1 3 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2467 +ENCODING 9319 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 1 1 4 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2468 +ENCODING 9320 +SWIDTH 1125 0 +DWIDTH 9 0 +BBX 1 1 4 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni2469 +ENCODING 9321 +SWIDTH 1250 0 +DWIDTH 10 0 +BBX 1 1 4 0 +BITMAP +00 +ENDCHAR +STARTCHAR uni24EA +ENCODING 9450 +SWIDTH 0 0 +DWIDTH 0 0 +BBX 1 1 3 0 +BITMAP +00 +ENDCHAR +STARTCHAR H22073 +ENCODING 9633 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 1 +BITMAP +E0 +A0 +A0 +A0 +A0 +E0 +ENDCHAR +STARTCHAR uni4E00 +ENCODING 19968 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 2 0 3 +BITMAP +04 +FE +ENDCHAR +STARTCHAR uni4E03 +ENCODING 19971 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +20 +20 +2E +F0 +20 +20 +22 +1E +ENDCHAR +STARTCHAR uni4E09 +ENCODING 19977 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +7C +00 +00 +38 +00 +00 +04 +FE +ENDCHAR +STARTCHAR uni4E0A +ENCODING 19978 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +40 +40 +70 +40 +40 +40 +40 +F0 +ENDCHAR +STARTCHAR uni4E0B +ENCODING 19979 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 4 8 0 0 +BITMAP +F0 +40 +60 +50 +40 +40 +40 +40 +ENDCHAR +STARTCHAR uni4E5D +ENCODING 20061 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +20 +F8 +28 +28 +48 +48 +4A +8E +ENDCHAR +STARTCHAR uni4E8C +ENCODING 20108 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 6 0 1 +BITMAP +7C +00 +00 +00 +04 +FE +ENDCHAR +STARTCHAR uni4E94 +ENCODING 20116 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +7C +10 +10 +7C +24 +24 +24 +FE +ENDCHAR +STARTCHAR uni516B +ENCODING 20843 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +28 +28 +28 +28 +44 +44 +44 +82 +ENDCHAR +STARTCHAR uni516D +ENCODING 20845 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +10 +FE +00 +28 +28 +44 +44 +82 +ENDCHAR +STARTCHAR uni5341 +ENCODING 21313 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +10 +10 +FE +10 +10 +10 +10 +10 +ENDCHAR +STARTCHAR uni5348 +ENCODING 21320 +SWIDTH 625 0 +DWIDTH 5 0 +BBX 5 8 0 0 +BITMAP +40 +70 +A0 +20 +F8 +20 +20 +20 +ENDCHAR +STARTCHAR uni56DB +ENCODING 22235 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +FE +AA +AA +AE +C2 +82 +FE +82 +ENDCHAR +STARTCHAR uni5929 +ENCODING 22825 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +7C +10 +10 +FE +10 +10 +28 +C6 +ENDCHAR +STARTCHAR uni661F +ENCODING 26143 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +FC +84 +FC +5E +90 +7C +10 +FE +ENDCHAR +STARTCHAR uni6708 +ENCODING 26376 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +3E +22 +3E +22 +3E +22 +42 +86 +ENDCHAR +STARTCHAR uni671F +ENCODING 26399 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +5E +FA +5E +7A +5E +EA +12 +A6 +ENDCHAR +STARTCHAR uniAE08 +ENCODING 44552 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +F8 +08 +00 +F8 +00 +F8 +88 +F8 +ENDCHAR +STARTCHAR uniBAA9 +ENCODING 47785 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +F8 +88 +F8 +20 +F8 +00 +F8 +08 +ENDCHAR +STARTCHAR uniC218 +ENCODING 49688 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +20 +20 +50 +88 +00 +F8 +20 +20 +ENDCHAR +STARTCHAR uniC624 +ENCODING 50724 +SWIDTH 750 0 +DWIDTH 6 0 +BBX 5 7 0 1 +BITMAP +70 +88 +88 +70 +20 +20 +F8 +ENDCHAR +STARTCHAR uniC694 +ENCODING 50836 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 7 0 1 +BITMAP +70 +88 +88 +70 +50 +50 +F8 +ENDCHAR +STARTCHAR uniC6D4 +ENCODING 50900 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +48 +A8 +48 +E8 +58 +38 +C0 +F8 +ENDCHAR +STARTCHAR uniC77C +ENCODING 51068 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +48 +A8 +48 +00 +F8 +38 +C0 +F8 +ENDCHAR +STARTCHAR uniC804 +ENCODING 51204 +SWIDTH 875 0 +DWIDTH 7 0 +BBX 6 8 0 0 +BITMAP +F4 +24 +4C +A4 +94 +44 +40 +7C +ENDCHAR +STARTCHAR uniD1A0 +ENCODING 53664 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 7 0 1 +BITMAP +F8 +80 +F0 +80 +F8 +20 +F8 +ENDCHAR +STARTCHAR uniD654 +ENCODING 54868 +SWIDTH 1000 0 +DWIDTH 8 0 +BBX 7 8 0 0 +BITMAP +24 +FC +54 +56 +24 +24 +FC +04 +ENDCHAR +STARTCHAR uniD6C4 +ENCODING 54980 +SWIDTH 1000 0 +DWIDTH 6 0 +BBX 5 8 0 0 +BITMAP +20 +F8 +50 +20 +00 +F8 +20 +20 +ENDCHAR +STARTCHAR uniFFE5 +ENCODING 65509 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 8 0 0 +BITMAP +A0 +A0 +E0 +40 +E0 +40 +40 +40 +ENDCHAR +STARTCHAR uniFFFD +ENCODING 65533 +SWIDTH 1000 0 +DWIDTH 4 0 +BBX 3 6 0 1 +BITMAP +A0 +A0 +40 +A0 +A0 +A0 +ENDCHAR +ENDFONT diff --git a/tests/components/font/common.yaml b/tests/components/font/common.yaml index a81457a05d..5be9faf5be 100644 --- a/tests/components/font/common.yaml +++ b/tests/components/font/common.yaml @@ -1,4 +1,12 @@ font: + - file: + type: gfonts + family: "Roboto" + weight: bold + italic: true + size: 32 + id: roboto32 + - file: "gfonts://Roboto" id: roboto size: 20 @@ -9,6 +17,10 @@ font: - file: "gfonts://Roboto" id: roboto_web size: 20 + - file: "gfonts://Roboto" + id: roboto_greek + size: 20 + glyphs: ["\u0300", "\u00C5", "\U000000C7"] - file: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" id: monocraft size: 20 @@ -20,6 +32,17 @@ font: - file: $component_dir/Monocraft.ttf id: monocraft3 size: 28 + - file: $component_dir/MatrixChunky8X.bdf + id: special_font + glyphs: + - '"' + - "'" + - '#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz°' + + - file: $component_dir/MatrixChunky8X.bdf + id: default_font + - file: $component_dir/x11.pcf + id: pcf_font i2c: scl: ${i2c_scl} @@ -36,3 +59,4 @@ display: it.print(0, 40, id(monocraft), "Hello, World!"); it.print(0, 60, id(monocraft2), "Hello, World!"); it.print(0, 80, id(monocraft3), "Hello, World!"); + it.print(0, 100, id(roboto_greek), "Hello κόσμε!"); diff --git a/tests/components/font/test.host.yaml b/tests/components/font/test.host.yaml index 017328ec83..c5399f2826 100644 --- a/tests/components/font/test.host.yaml +++ b/tests/components/font/test.host.yaml @@ -1,4 +1,12 @@ font: + - file: + type: gfonts + family: "Roboto" + weight: bold + italic: true + size: 32 + id: roboto32 + - file: "gfonts://Roboto" id: roboto size: 20 @@ -9,6 +17,10 @@ font: - file: "gfonts://Roboto" id: roboto_web size: 20 + - file: "gfonts://Roboto" + id: roboto_greek + size: 20 + glyphs: ["\u0300", "\u00C5", "\U000000C7"] - file: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" id: monocraft size: 20 @@ -20,4 +32,26 @@ font: - file: $component_dir/Monocraft.ttf id: monocraft3 size: 28 + - file: $component_dir/MatrixChunky8X.bdf + id: special_font + glyphs: + - '"' + - "'" + - '#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz°' + - file: $component_dir/MatrixChunky8X.bdf + id: default_font + +display: + - platform: sdl + id: sdl_display + dimensions: + width: 800 + height: 600 + lambda: |- + it.print(0, 0, id(roboto), "Hello, World!"); + it.print(0, 20, id(roboto_web), "Hello, World!"); + it.print(0, 40, id(roboto_greek), "Hello κόσμε!"); + it.print(0, 60, id(monocraft), "Hello, World!"); + it.print(0, 80, id(monocraft2), "Hello, World!"); + it.print(0, 100, id(monocraft3), "Hello, World!"); diff --git a/tests/components/font/x11.pcf b/tests/components/font/x11.pcf new file mode 100644 index 0000000000000000000000000000000000000000..19a38d4e3918b6bb4796fb23d34a24e2a8fa9656 GIT binary patch literal 13368 zcmbuG4Rl;bb;oD58-K)(tvF6BJBq!DgJZx-<zR@Mny|u>ZGj>gOHOPE=&g5G(&CkN zvnx4r;@D4XSznfeo2E1=X=^AYfu_{7lm?o%56E#$j|m3S9JfG^&PgbtC*=r|5>e8) zzyJGKFD8*)cqhL%cV_O~J9F>Md%N-`)S1Z5F~)>o0oY+<Mky=&dXjQZ#2EfF3s5>q ze};<2NdNKW#;l_f_3M=koLps0%&*Tue`+;$gMM_U-%bTv8lQwY>pU5;8r-?K_BE+x zaIJ(IxJg1C+#+EMv`Y90bbxE|qY`AcKM;z6@G&X>Av~;>7I;)GPk<xg+wfg5m%_hG zm<>OYa1;DO!Y1Z7SHfw8N=Aj4VW>vJ%}|su2H%hlSLk2V@;v-ELd86YN_Y!cQW!_r zErBIh?Ag@1y`^E>PE()E?n;?W^|v)O@9b!)zpc@1@5|=WadSsw)8;MhUekNCnWSlJ zu5W2KO{T4}{f?%F_AOplOE%vV&zSm#hK{!Ooz0D2tJ&JLqp`W8t?7e}W@~FxOS>m- zYiw(6-rnBS+S0MZlvQ?`wypIWn_4!Tt)~9=#%=YR8#`PdX5*InZ8+@M*0{N;t)2Nc zwYApOy>or3qp@XUYlB9vH5==<c5HEHZFU60w#MfA_NLn#JAzr`)^G6^=&zFqZ1m^u ztv{+YN1OWMnRLQzY~8wZo5Eo>W^;S<>8|d+=<38<qifgKtzW}^{o1G-$7tK$LSL$< z5N+yBWb?UfKHir~-WZM6XEM=k8dZpHOBGW2J+zvZ*7l~2LAayoLNp%Dr@GP@$fuIg zzI;5H>WSxfN3)$X88;1$ZS74hEV8x5bksMrzPGVsYulz)QsphAMho%R(b3Sj5pKtC zhg6jBHnX+u-M_BtTBvbXRa4em(^TVSyryRDx|+4MwI#OJ++0`VXQyUuS$53_w%0fK z=`5W;?L%Fc0jJ!A&%W;7&z_xDX%^CRncGA4|9k2AVVuE<!(+B>AGGbkLqp?sab$S> zp24xvaa*^+;*p8*!NJ4hV|H=q;P}18;X~ua!9(1Nqa)+@4vvnFFm&jk+J^2MJUCRe zZCPRLm_0bc%^sC>?&fyrRxC+g@6ZsYxD{Qa+zuUZrQ+x?_9hOG+xGaFe=ABFJWSi5 zD~&VV;^A?sN7OcQ2t97a!3ni#G4}mOxT#t!4U=@xGcf8Jz-Up9iW8DPrV)oF>E=2g z)bfkNOvqLYH1YlR$jAY=Z7+^SJa2=e!+|t1GGZSXJv4sM9vc}sSRBKY;%6Vg)IoP@ zZnb#i2qVVFxGAPGrCx3$GUeWe4-rYJ5PNPDhY6S@-45DvS=6G72T4FMzeD@o)GDwz zS3ca>kB*KWD2^)&QpTkcIy^q$E+22B`--EZ!#GkFlv*z?Bg10@Y=N<RF@@jZ;jw*# zx*cTG>?K0@AQi>p(9qES;?VHm5V1l<_Sg*An;@nv)xTkYiI0yRQFPtggd}cb!R^5P z6DX79Ay$4w%Xe=RL<ki!%`n{tM-MSfH+Nf*=?P`}Na^NQgPXfH?E{vDihrYb;;>Xa zALI6Z{Q5ca55!?VzvrsTg3>)SI5MKG=iSibe0Z7Grm?xvrj4m!``gx){9CzY!tU;a z(LvT5D36b!qAKb3W<}1CO3w<%KmXTLie|VIhTu4SpLZTbXfZTEAB@7Mfmd$mNq7og z;=NZ1d<v-80sG(>&{y#y51si?3+<4D2LbzG2k;Szz#5=G!q^D*Bd6dMV=k$N2FL*8 zF2Uv{FBvmyF>HV&Ou!cbU$dFV>^iW3xz5JV>@(h{ojI#1w}K6if!gsgXWE##5s1PT zz~)?R&P8V~^ShM3OV>a<i~=?<W$dMlork}9^v$Dh9&?}f5PT8npGW_E`sdR>AG`B& zfZh2|!V5g27Xv<B=)WujN8xce4a{r7YN&-B&<)Ie0XhpF1newu0Nn*I86NF`&4t)p zh`ogafc`>s7d{400J;mYTZv93I+Z#2G@LQ!@;Oik-S7}NfbOC-U;%y?(YELnW3E^Y zJ75%!!=y2b7sD19fXCnrOc`@!6*Pkl=w6BLRp?%|0dnvVVD~E4u!Qy{v@hv~AwYi# zb}(V8m`7DR>;vXpbsDCPSsDfWF2&Z;hu|r|)-r4@s|Cg^W6UzfEMv?v#$1i9t1a^T zRpR{5$PXHG^XktX!wr`mb&Fps)sI0JsR#>VpeqKB&lyT_)JLC47x^5ZyxvEzXD08$ z<lI+tHKjgc69mJ=&8=`1JO|H09Yn#{#=Hl1B7O(dmv-2B7QSFr%$SX~H?R4|^S{n9 z=AE?NNBsxz4Cn*$Ui;kK5oOw?l2U87Am-yx+HXLNwy2NBolnqb(H8?7q;n_8$BPh! z@^Ok!dEKHO5JR?M3LKv~l!1ONa@5D7jQQA<%i$f6@Ohf@gwH9;0Z_~~Kvfxvz3hr< z2<*t#Y|wWi>0b-`LGzc5PM8H6uTq=<`3l-Jwi>R7HP8$iFMG1_9=H{vp!y~#w=+O} zIRxuc{Xal{gzU-JevsV=1o4U@TQCVRkevc3KAjM(L3T7}+16ZFf%?U*Ae-`Y5)>oL zCq}6&298e<N2=~+aSw<g+fV_Di{?G)BY75R4Z$VZhDlJr#suRWWX)Ieo&fb*;J_5< zngj>*#H@?4?x-<6$Bg+V<-fxBV#a*f_F}(+GHg6%Q;K<T1*q>@pO+o)d{$A4rLe*n zvmVyk9?F&GzFch(hZs0zd=0r1f<3U*_Uy@KtxsT2Ig`BuHtT>|xQY6$pwa^QNPzV8 z{HO|`t~ms{LH?#`)4b)w0tZ<<Z5#7-l@QEt0|BkFy!q8R*ayvVt+4S8FC%L$RX)K! z2uh1~fhjjG^C<(}@-om@X@LXxO%h+*I|J*e%a7ROlc4;m{j2`#XbWuRkne&ZjvK1k z=O~E9J9rKsWzQcs<^~3cRhBWYOkoeIqYyLZMpy+mz)IVro>G378!yI+gZQeC;wUzO z=Bpei&svM(rhTHe99TZMyr8`xFD%1758A7Gj>vXEU|Xdqfaa<(*TF83-+iF@5Boet zIRIyE?=LbBBUb^TD#f!FS!1W+myWk)ty_5zTC+IeBYQ79z8<oC3*}Oj+n%D5sPyQH z8nau(JPJ1J0Qq?LB=bVnm*U{L5R_NYwh~@-@N0Ye^4ACQRqiiX#|p>$bfnxWp0X9} z(OTMczRp50Hi{fzA;-?cp*{&VI1no%iX5O=vRv10l`pFvp#EqX7V;znV^$+)putBm ze;pjKKzcgo?}ARBQ<Oh|pMZSIhqxJj7qnj#FX<=Y3DCY#-2w-+Ph=+tAyCX_f%eiW zkgodA_m!co8K%Jjy;Ec(442qm%r)Qnpt+S(NnP_i4wG<=?afv5723-d=uDmP8KBgd zDX^dyZU@cpC6NDrgYSd%gsvz|g5~2-o`y-7<wNm?^VFOJTgQ<np%QYS_8TA#-DP|W z`4p7vS=0k!$PS!_Nw|^QtClf$$5;!TvU#r^1INqbZsWxyPpP$b+TJsLLr|wY0rDvn zC(TRa3XlMeQ~q~=<6~1^1Hpchjc4FlP(0`RXdP?dHL!eQl)`~J$bbckiDGsJa-gvm zO#7we7&vemCPDAC=OF=IpgBBed!Kt{_a$hCEpQToajP6YKS2_jZSoGmS-97DXGL=n zOX2fy3iSTa9$QvMHFBUE*w=Ho1=LpyyFf9R0O>}3EJ|TRtPItK<Cl^zg#1*3?5h-W z{8D|<G92XVU>}Tv`U4$ASJ$G9`8bq9_QVFzd-t@D@;(4jm<>zqb1~65)SNDd{qQ;{ z7K-Ok8LF%OZcx0|K^<t0nw$D8m;(DesJfTUy_^Nq7eOUxe$B86WM7||s$mP9gdAuN zLUXQ%J8fegi1DoPk*qfLNoNCegT_q5^?qIUPJ?U<%{@}a6!Q5#Wcv|N`9Yi5gZ7c^ zT5!}ya;4AfWip~0+d9fMFdu3`zH?CJ*U$KM^;uv;6}P?MejcIJ9@jJFL&y}ReYga< z&*v4&7vM!$4Rb&~gytuoj!%rzK2KJ^<`4x7rl1a*A(*3LC$yf7FCV3pu^e0rE2%1< zFF1VbhebAjS)g1=S<*wi4w8;BKeU)DRNAaJ%Ja@K?jm!PRiGGrkEXmu&L*)L<F5#m zmpfietBrSUpiqpp-W)syr(gggFx%$F7534&5cA*)SYiL__X!0nOq;HY9pn0lUA+D* zoAp2iKFa4)niK6S>DTADA31!kq7=Gj`RMN*fxh<VO4tZLvb}jkC<EInrTeD(Y+(JR zvBDu{CmA<QZbpeUHr5UiALLhQ(>Y!Xaj?KC<7>#BpuALq@-YW$eOY659kAJ7PzyTy zw}MIwWH$jiPa#+cx-NE%)+xUitA91RLh;l4MRP5mlj^&CG^Z$Nzv)sbUqX82Sk%Q7 zC|}D#SNWJX8ka+78Wa;<7t*)Tkq<p5lo#n&`vh~Vqh3BY+1GljY;edG?1Ng+nG*%$ z<zQhH!ND$0s*$T>Gk^E0L|t>xr8T>I#rF2`n|%)}&-2BO3G#fg`pcPv{9LTQ_JsDs z#p-iJ+~;Eu=PlILCclb7f-(i)@cAxn&%-8o4Kyx@+b<n2UKjcf7qU60pzC7Ctnkz? zT0b}+vVWI-?tJQue%<E;rO-Yr&*jUucb0X2`uzU#y_lm-d+}DN0?loy{p;Vy6&vY= zCRs0)S+E>=zU@C3C0mS*f#caaZM^fRvDKhU_TL4aK6*#|0Db~t&^tueOnMRerKe|v zu9<Wd3$0DpOnN%ILf1@s^1H}Kvi9ejt!IV9_YRA-!ZWtF&SzuZyFu|#3@mWqq)(1A z1{Opi=9kKuF6Af#&7gcZps^aKxTs&(OnP(t{_81Y7tqz(gsz!vI`nG}x}q=%mXAYu z8YV$DE=Div>m8*W1=T0P@^L5wo2QV4u1c5!%V&~O@ljhXTm?GgBG`i`kOLb7$mM(H zX6m~^dm|6-7~A{aD0{)ap2^;<i_}wIWG@N2X0lm6$C>oX_w`JAT8q&2npc$GSK3z^ zt9iA+bMOKvW{Ot~a!?1-)wA|_NI(}n1JBvs_fS1A!_+UNr~U;Hobj1#2G2*-XY?(< zvZ?DXP;4&r-8I;+ip>r1b|`?>roE7aGvN5xl;ttMSU>)}&NAG+&i+;xTeyNSN6aN= z79T(7n7RB^Y#tBI%Lu?i4$0*_#I7)l&6OO8B^=771mbEo-!;5!S8&*_<qN_}zQDbO zKV03w7n--4x3Tm!{J8B#^E+&ewIt>pd?|e=4~Lsc{CaZ>e<s^t-p#}IJ#2`2^Ikp% zH<(6VuA6x&H<|aD_w&%YjSbYwue3g3wwX4=Z;bfE(;fUVWvBU|`4Im=_F;Y_*YR5| z-5qQ!KE&=KNlB9;O<jB~O`DH$kTa%-KX+x>u6J>q3%nBh`RaZ*+jy_J$K1=;>-+dx ze!$pf&=k!<K0X}c&%h%*g2wm(++qG?am3tjKE~J2-!l)G-!~7MKQJHXr+N?ZQ@uYj zpD>S@KQ@12{?r^ZpERE`e`Y>y{@i@VJZc^@pEaK|e_<Xsf61Ru{)#`>eZl;-`5W^^ z^S9<p=I{7(;+M@=%-@?Q%|DobH2+lErw*qj%7*w3Po-<%X}%q>6-C&loTBgNw133s z720+nvu)kqFsS4x1!FE$y5krl8v&sc_~Q6i+yjrnG3q?hDte&_cpPYsp&|@XKMwDs z?P1Cv!Y|-Kzx*sE$Ec#4ejaxfdnh0BO4r^;v7xo_Xi@2|g*AnaBDVu}LnkTYl;4Lt zkuQaQ<Tkhs`4-B}*nI%`Uf>Z@AzNE$e*!)UUDUN6))D6MQ9+DCKL*7aKcTr^DgQz2 z&ySsSD(cW<Zef*AQm+NZDE4j}eWAPkHs%u&T8n%)AXj;HxBU^?7$1K89E!cGEB*5b zt4=Q5xG5JtZXT2ipYVM8f;=b}<uF&(m5Xv*Tg0@Y4;1$|Abcf0$c38+<sv|ND8r3^ zkO$?W9QjxMytOG8ZXAE?KNlVHpo_J*AHSATUI#U>4mQ9h*amk3`87FU4V-GjSvH(t z!^t(AS;Kkd&r9$rcoc}aISyZfC*iB`G<+M_-|nBnU!eRin1Y|dEAVrehW~-r;C0qc z5vl;~DfU;0*oN4nA?6-pIiX5e1dHJ+sDfos4a*@4tSxjs)WR*W85C3YaVQC$&<#2- z86fwe9OR)7_P_uT*U&!L4>k}7ozc)RjKVlf!2NI(9suGK`Zy4y5b+6p2EG8qCG<7; zCVU6J2QR>j@G~F=6})0AE(7x8p7Wb1*FyumAGX1rz}$8AE0|lw0U$RO_^UVu<f7us za1y=?_^Hr-_&NM9`!oW?FuVk=g&W{)@D3nW;d;0Y?f~KyP64msF#8~kuP|#59|6`G z#$WhRV141Q0C^ANH~cNYZ<uovehFA#7~k%1xcKu;1m6*I6<G~8!dk$01mBS@&<^;H z;5$NoBIF`M>?8P&;5&lv$R_~bk<Y^uz*9eR0=@~)!X*4B{1jg0UxGr(RA;<D)0gOu z^XkgQ6DgA#NMzzY=C1xMKNQ}TF}?jgyHfc=x~tbDvzZKfxl}%p>g_Y}9;8CNH|Z%C z(!E`ol*z^OsoqSgv(J;<UnZNlenOls>^8Yff59ZOJw0*Ly*JmL>Xo&0Hfai(c%j?e zlgejJwl`(^?#`ON?tF?@ZMHveI@5bnrjQ;mh14F#r1ZmO)0;*yk<Da#Y3@mTQYKX> znABbU@r>!pr}(jRp4p`eeQC7feZSF%h+X-3Vt1;~UDGZ^Z>oN4u)ah(ok-^s{XM3? zH%VCdv2}_W#F_VR^07hIQ@+Q%acgCs`qG&s@hHnzGJAKgakHXGXGn!f^ykS$Vz1dt z#CGMgyHmZpl;u>y4MsA}kGczKWyAmdxXI>|odnX2Ygd0dlS%bty=7;zUFk$T)0^!J z(su55;ie~^$P?XUihz@UGK>w<Ob!eEZhG@xE=#p|qQ5U?diu3R;shzz?Qa9EK)*F- zDM|dtwHHdGm|jo3kmzT1gf^I;7Z&u$d0y#8?2czTJ(qrCp|lCyK-GJRsrTkl?<KA? zlNvDfUTEt}(W%cXmi3KhV=$#gZ*ZeGxUp<-V`)^Ar_<!=G?nQzm2?{Wx=oAcuGLd- z_0(I-)LVUp(x@IzYdV+NYg)Z+xZNAN-5a{SZ0PpVn4Ml{dv`Y9Yr0bW-x0l>?1E9~ zwLiV?c-h#v=Qdt45qHB7=ez}BAln+8@&>2M29r8}Yo$G%w5Q`A5I4^0l1_>+^m^{H zo_f|(&z70Y`U<5{$@HEyM<DA3q2C+W?+xrP8`xhOvDfSDbAzzA)ND$hi;>*xGVZ(& J-u(Cf{|En5p|}75 literal 0 HcmV?d00001 From 749b9421329f0ca8453e7df0df0702a3d29e8276 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:37:32 +1100 Subject: [PATCH 072/282] [lvlg] fix tests (#7708) --- tests/components/lvgl/common.yaml | 40 +------------------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index cebc3caaa7..c7d635db1c 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -4,56 +4,18 @@ touchscreen: display: tft_display update_interval: 50ms threshold: 1 - calibration: - x_max: 240 - y_max: 320 font: - file: "$component_dir/roboto.ttf" id: roboto20 size: 20 - extras: - - file: '$component_dir/materialdesignicons-webfont.ttf' - glyphs: [ - "\U000F004B", - "\U0000f0ed", - "\U000F006E", - "\U000F012C", - "\U000F179B", - "\U000F0748", - "\U000F1A1B", - "\U000F02DC", - "\U000F0A02", - "\U000F035F", - "\U000F0156", - "\U000F0C5F", - "\U000f0084", - "\U000f0091", - ] + - file: "$component_dir/helvetica.ttf" id: helvetica20 - file: "$component_dir/roboto.ttf" id: roboto10 size: 10 bpp: 4 - extras: - - file: '$component_dir/materialdesignicons-webfont.ttf' - glyphs: [ - "\U000F004B", - "\U0000f0ed", - "\U000F006E", - "\U000F012C", - "\U000F179B", - "\U000F0748", - "\U000F1A1B", - "\U000F02DC", - "\U000F0A02", - "\U000F035F", - "\U000F0156", - "\U000F0C5F", - "\U000f0084", - "\U000f0091", - ] sensor: - platform: lvgl From cefbfb75bd4b5f8baaf5599c1c4221aab1919426 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 31 Oct 2024 23:46:35 +1300 Subject: [PATCH 073/282] [esp32_ble] Add disconnect as a virtual function to ``ESPBTClient`` (#7705) --- esphome/components/esp32_ble_client/ble_client_base.h | 2 +- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index fd586e59d6..fca66c0b3c 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -35,7 +35,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; void connect() override; esp_err_t pair(); - void disconnect(); + void disconnect() override; void release_services(); bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index d2bb6a6e6d..2fc5da829d 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -11,9 +11,9 @@ #ifdef USE_ESP32 +#include <esp_bt_defs.h> #include <esp_gap_ble_api.h> #include <esp_gattc_api.h> -#include <esp_bt_defs.h> #include <freertos/FreeRTOS.h> #include <freertos/semphr.h> @@ -172,6 +172,7 @@ class ESPBTClient : public ESPBTDeviceListener { esp_ble_gattc_cb_param_t *param) = 0; virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; virtual void connect() = 0; + virtual void disconnect() = 0; virtual void set_state(ClientState st) { this->state_ = st; } ClientState state() const { return state_; } int app_id; From 77bb46ff3bf17afb7bfd3fe963a86af014a17d4b Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Fri, 1 Nov 2024 02:54:34 -0700 Subject: [PATCH 074/282] handle bad pin schemas (#7711) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- esphome/components/esp32/gpio.py | 4 +++- esphome/components/esp8266/gpio.py | 12 +++++++----- esphome/components/host/gpio.py | 10 ++++++---- esphome/components/libretiny/gpio.py | 6 ++++-- esphome/components/rp2040/gpio.py | 8 +++++--- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 558ff51af8..df01769a66 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -67,8 +67,10 @@ def _translate_pin(value): "This variable only supports pin numbers, not full pin schemas " "(with inverted and mode)." ) - if isinstance(value, int): + if isinstance(value, int) and not isinstance(value, bool): return value + if not isinstance(value, str): + raise cv.Invalid(f"Invalid pin number: {value}") try: return int(value) except ValueError: diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py index c42bc9204f..53016d2130 100644 --- a/esphome/components/esp8266/gpio.py +++ b/esphome/components/esp8266/gpio.py @@ -1,6 +1,9 @@ -import logging from dataclasses import dataclass +import logging +from esphome import pins +import esphome.codegen as cg +import esphome.config_validation as cv from esphome.const import ( CONF_ANALOG, CONF_ID, @@ -14,10 +17,7 @@ from esphome.const import ( CONF_PULLUP, PLATFORM_ESP8266, ) -from esphome import pins from esphome.core import CORE, coroutine_with_priority -import esphome.config_validation as cv -import esphome.codegen as cg from . import boards from .const import KEY_BOARD, KEY_ESP8266, KEY_PIN_INITIAL_STATES, esp8266_ns @@ -48,8 +48,10 @@ def _translate_pin(value): "This variable only supports pin numbers, not full pin schemas " "(with inverted and mode)." ) - if isinstance(value, int): + if isinstance(value, int) and not isinstance(value, bool): return value + if not isinstance(value, str): + raise cv.Invalid(f"Invalid pin number: {value}") try: return int(value) except ValueError: diff --git a/esphome/components/host/gpio.py b/esphome/components/host/gpio.py index 180919de4f..0f22a790bd 100644 --- a/esphome/components/host/gpio.py +++ b/esphome/components/host/gpio.py @@ -1,5 +1,8 @@ import logging +from esphome import pins +import esphome.codegen as cg +import esphome.config_validation as cv from esphome.const import ( CONF_ID, CONF_INPUT, @@ -11,9 +14,6 @@ from esphome.const import ( CONF_PULLDOWN, CONF_PULLUP, ) -from esphome import pins -import esphome.config_validation as cv -import esphome.codegen as cg from .const import host_ns @@ -28,8 +28,10 @@ def _translate_pin(value): "This variable only supports pin numbers, not full pin schemas " "(with inverted and mode)." ) - if isinstance(value, int): + if isinstance(value, int) and not isinstance(value, bool): return value + if not isinstance(value, str): + raise cv.Invalid(f"Invalid pin number: {value}") try: return int(value) except ValueError: diff --git a/esphome/components/libretiny/gpio.py b/esphome/components/libretiny/gpio.py index 1d7b37cc9b..07eb0ce133 100644 --- a/esphome/components/libretiny/gpio.py +++ b/esphome/components/libretiny/gpio.py @@ -1,8 +1,8 @@ import logging +from esphome import pins import esphome.codegen as cg import esphome.config_validation as cv -from esphome import pins from esphome.const import ( CONF_ANALOG, CONF_ID, @@ -103,8 +103,10 @@ def _translate_pin(value): "This variable only supports pin numbers, not full pin schemas " "(with inverted and mode)." ) - if isinstance(value, int): + if isinstance(value, int) and not isinstance(value, bool): return value + if not isinstance(value, str): + raise cv.Invalid(f"Invalid pin number: {value}") try: return int(value) except ValueError: diff --git a/esphome/components/rp2040/gpio.py b/esphome/components/rp2040/gpio.py index 6ba0975a2c..58514f7db5 100644 --- a/esphome/components/rp2040/gpio.py +++ b/esphome/components/rp2040/gpio.py @@ -1,6 +1,8 @@ +from esphome import pins import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( + CONF_ANALOG, CONF_ID, CONF_INPUT, CONF_INVERTED, @@ -10,10 +12,8 @@ from esphome.const import ( CONF_OUTPUT, CONF_PULLDOWN, CONF_PULLUP, - CONF_ANALOG, ) from esphome.core import CORE -from esphome import pins from . import boards from .const import KEY_BOARD, KEY_RP2040, rp2040_ns @@ -41,8 +41,10 @@ def _translate_pin(value): "This variable only supports pin numbers, not full pin schemas " "(with inverted and mode)." ) - if isinstance(value, int): + if isinstance(value, int) and not isinstance(value, bool): return value + if not isinstance(value, str): + raise cv.Invalid(f"Invalid pin number: {value}") try: return int(value) except ValueError: From 01497c891d08c4a9a245eaadb982e302d43f0b58 Mon Sep 17 00:00:00 2001 From: tomaszduda23 <tomaszduda23@gmail.com> Date: Sun, 3 Nov 2024 22:22:16 +0100 Subject: [PATCH 075/282] datetime fix build_language_schema (#7710) Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com> --- esphome/components/datetime/__init__.py | 6 +++--- tests/components/mqtt/common.yaml | 1 + tests/components/web_server/common_v3.yaml | 8 ++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/esphome/components/datetime/__init__.py b/esphome/components/datetime/__init__.py index 7edf527e01..630bf6962c 100644 --- a/esphome/components/datetime/__init__.py +++ b/esphome/components/datetime/__init__.py @@ -70,8 +70,6 @@ def _validate_time_present(config): _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( - web_server.WEBSERVER_SORTING_SCHEMA, - cv.MQTT_COMMAND_COMPONENT_SCHEMA, cv.Schema( { cv.Optional(CONF_ON_VALUE): automation.validate_automation( @@ -81,7 +79,9 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( ), cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), } - ), + ) + .extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) ).add_extra(_validate_time_present) diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml index e154be8b5c..75c34bec56 100644 --- a/tests/components/mqtt/common.yaml +++ b/tests/components/mqtt/common.yaml @@ -230,6 +230,7 @@ datetime: id: test_date type: date state_topic: some/topic/date + command_topic: test_date/custom_command_topic qos: 2 subscribe_qos: 2 set_action: diff --git a/tests/components/web_server/common_v3.yaml b/tests/components/web_server/common_v3.yaml index 69f4b67f15..bdacaaddbe 100644 --- a/tests/components/web_server/common_v3.yaml +++ b/tests/components/web_server/common_v3.yaml @@ -35,3 +35,11 @@ switch: web_server: sorting_group_id: sorting_group_2 sorting_weight: -10 +datetime: + - platform: template + name: Pick a Date + type: datetime + optimistic: yes + web_server: + sorting_group_id: sorting_group_3 + sorting_weight: -5 From 2dca3d79e490abcf2a03e962cb7c88dc3bbfd83b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:32:18 +1100 Subject: [PATCH 076/282] [lvgl] Ensure images are configured before using them. (Bugfix) (#7721) --- esphome/components/lvgl/widgets/animimg.py | 7 ++++--- esphome/components/lvgl/widgets/img.py | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/lvgl/widgets/animimg.py b/esphome/components/lvgl/widgets/animimg.py index 3b20008c3d..8adea72ad3 100644 --- a/esphome/components/lvgl/widgets/animimg.py +++ b/esphome/components/lvgl/widgets/animimg.py @@ -60,9 +60,10 @@ class AnimimgType(WidgetType): lvgl_components_required.add(CONF_IMAGE) lvgl_components_required.add(CONF_ANIMIMG) if CONF_SRC in config: - for x in config[CONF_SRC]: - await cg.get_variable(x) - srcs = [await lv_image.process(x) for x in config[CONF_SRC]] + srcs = [ + await lv_image.process(await cg.get_variable(x)) + for x in config[CONF_SRC] + ] src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs) count = len(config[CONF_SRC]) lv.animimg_set_src(w.obj, src_id, count) diff --git a/esphome/components/lvgl/widgets/img.py b/esphome/components/lvgl/widgets/img.py index 59b2c97c63..931d0c0b5b 100644 --- a/esphome/components/lvgl/widgets/img.py +++ b/esphome/components/lvgl/widgets/img.py @@ -1,3 +1,4 @@ +import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ANGLE, CONF_MODE @@ -64,6 +65,7 @@ class ImgType(WidgetType): async def to_code(self, w: Widget, config): if src := config.get(CONF_SRC): + src = await cg.get_variable(src) lv.img_set_src(w.obj, await lv_image.process(src)) if (cf_angle := config.get(CONF_ANGLE)) is not None: pivot_x = config[CONF_PIVOT_X] From dcc537d0d43c2b8fdff7ecac86c8154c7ed78172 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:45:40 +1300 Subject: [PATCH 077/282] [lvgl] Don't just throw key error if someone types a bad layout type (#7722) Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/lvgl/schemas.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index bb14c11ddd..516627708e 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -391,7 +391,9 @@ def container_validator(schema, widget_type: WidgetType): add_lv_use(ltype) if value == SCHEMA_EXTRACT: return result - result = result.extend(LAYOUT_SCHEMAS[ltype.lower()]) + result = result.extend( + LAYOUT_SCHEMAS.get(ltype.lower(), LAYOUT_SCHEMAS[df.TYPE_NONE]) + ) return result(value) return validator From 5bb4d042e48873812684fcad189c233c64698629 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:54:47 +1100 Subject: [PATCH 078/282] [spi_device] rename mode to spi_mode (#7724) --- esphome/components/spi/__init__.py | 29 ++++++++++--------- esphome/components/spi_device/__init__.py | 19 +++--------- .../components/spi_device/test.esp32-ard.yaml | 2 +- .../spi_device/test.esp32-c3-ard.yaml | 2 +- .../spi_device/test.esp32-c3-idf.yaml | 2 +- .../components/spi_device/test.esp32-idf.yaml | 2 +- .../spi_device/test.esp8266-ard.yaml | 2 +- .../spi_device/test.rp2040-ard.yaml | 2 +- 8 files changed, 25 insertions(+), 35 deletions(-) diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index fdf19bb56e..52afbf365e 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -1,40 +1,37 @@ import re +from esphome import pins import esphome.codegen as cg -import esphome.config_validation as cv -import esphome.final_validate as fv from esphome.components.esp32.const import ( KEY_ESP32, - VARIANT_ESP32S2, - VARIANT_ESP32S3, VARIANT_ESP32C2, VARIANT_ESP32C3, VARIANT_ESP32C6, VARIANT_ESP32H2, + VARIANT_ESP32S2, + VARIANT_ESP32S3, ) -from esphome import pins +import esphome.config_validation as cv from esphome.const import ( CONF_CLK_PIN, + CONF_CS_PIN, + CONF_DATA_PINS, + CONF_DATA_RATE, CONF_ID, + CONF_INVERTED, CONF_MISO_PIN, CONF_MOSI_PIN, - CONF_SPI_ID, - CONF_CS_PIN, CONF_NUMBER, - CONF_INVERTED, + CONF_SPI_ID, KEY_CORE, KEY_TARGET_PLATFORM, KEY_VARIANT, - CONF_DATA_RATE, PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, - CONF_DATA_PINS, -) -from esphome.core import ( - coroutine_with_priority, - CORE, ) +from esphome.core import CORE, coroutine_with_priority +import esphome.final_validate as fv CODEOWNERS = ["@esphome/core", "@clydebarrow"] spi_ns = cg.esphome_ns.namespace("spi") @@ -69,6 +66,10 @@ SPI_MODE_OPTIONS = { 1: SPIMode.MODE1, 2: SPIMode.MODE2, 3: SPIMode.MODE3, + "0": SPIMode.MODE0, + "1": SPIMode.MODE1, + "2": SPIMode.MODE2, + "3": SPIMode.MODE3, } CONF_SPI_MODE = "spi_mode" diff --git a/esphome/components/spi_device/__init__.py b/esphome/components/spi_device/__init__.py index 65e7ee6fc6..2f23d8a011 100644 --- a/esphome/components/spi_device/__init__.py +++ b/esphome/components/spi_device/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import spi +import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_MODE DEPENDENCIES = ["spi"] @@ -11,18 +11,6 @@ spi_device_ns = cg.esphome_ns.namespace("spi_device") spi_device = spi_device_ns.class_("SPIDeviceComponent", cg.Component, spi.SPIDevice) -Mode = spi.spi_ns.enum("SPIMode") -MODES = { - "0": Mode.MODE0, - "1": Mode.MODE1, - "2": Mode.MODE2, - "3": Mode.MODE3, - "MODE0": Mode.MODE0, - "MODE1": Mode.MODE1, - "MODE2": Mode.MODE2, - "MODE3": Mode.MODE3, -} - BitOrder = spi.spi_ns.enum("SPIBitOrder") ORDERS = { "msb_first": BitOrder.BIT_ORDER_MSB_FIRST, @@ -34,7 +22,9 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_ID): cv.declare_id(spi_device), cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True), - cv.Optional(CONF_MODE, default="0"): cv.enum(MODES, upper=True), + cv.Optional(CONF_MODE): cv.invalid( + "The 'mode' option has been renamed to 'spi_mode'." + ), } ).extend(spi.spi_device_schema(False, "1MHz")) @@ -42,6 +32,5 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - cg.add(var.set_mode(config[CONF_MODE])) cg.add(var.set_bit_order(config[CONF_BIT_ORDER])) await spi.register_spi_device(var, config) diff --git a/tests/components/spi_device/test.esp32-ard.yaml b/tests/components/spi_device/test.esp32-ard.yaml index cad8ca49f8..b539cb3ec4 100644 --- a/tests/components/spi_device/test.esp32-ard.yaml +++ b/tests/components/spi_device/test.esp32-ard.yaml @@ -7,5 +7,5 @@ spi: spi_device: id: spi_device_test data_rate: 2MHz - mode: 3 + spi_mode: 3 bit_order: lsb_first diff --git a/tests/components/spi_device/test.esp32-c3-ard.yaml b/tests/components/spi_device/test.esp32-c3-ard.yaml index 49e2733676..99c0ac1ebb 100644 --- a/tests/components/spi_device/test.esp32-c3-ard.yaml +++ b/tests/components/spi_device/test.esp32-c3-ard.yaml @@ -7,5 +7,5 @@ spi: spi_device: id: spi_device_test data_rate: 2MHz - mode: 3 + spi_mode: 3 bit_order: lsb_first diff --git a/tests/components/spi_device/test.esp32-c3-idf.yaml b/tests/components/spi_device/test.esp32-c3-idf.yaml index 49e2733676..99c0ac1ebb 100644 --- a/tests/components/spi_device/test.esp32-c3-idf.yaml +++ b/tests/components/spi_device/test.esp32-c3-idf.yaml @@ -7,5 +7,5 @@ spi: spi_device: id: spi_device_test data_rate: 2MHz - mode: 3 + spi_mode: 3 bit_order: lsb_first diff --git a/tests/components/spi_device/test.esp32-idf.yaml b/tests/components/spi_device/test.esp32-idf.yaml index cad8ca49f8..b539cb3ec4 100644 --- a/tests/components/spi_device/test.esp32-idf.yaml +++ b/tests/components/spi_device/test.esp32-idf.yaml @@ -7,5 +7,5 @@ spi: spi_device: id: spi_device_test data_rate: 2MHz - mode: 3 + spi_mode: 3 bit_order: lsb_first diff --git a/tests/components/spi_device/test.esp8266-ard.yaml b/tests/components/spi_device/test.esp8266-ard.yaml index 1b191bdb6a..988825ce2d 100644 --- a/tests/components/spi_device/test.esp8266-ard.yaml +++ b/tests/components/spi_device/test.esp8266-ard.yaml @@ -7,5 +7,5 @@ spi: spi_device: id: spi_device_test data_rate: 2MHz - mode: 3 + spi_mode: 3 bit_order: lsb_first diff --git a/tests/components/spi_device/test.rp2040-ard.yaml b/tests/components/spi_device/test.rp2040-ard.yaml index c70493c70d..6020643f21 100644 --- a/tests/components/spi_device/test.rp2040-ard.yaml +++ b/tests/components/spi_device/test.rp2040-ard.yaml @@ -7,5 +7,5 @@ spi: spi_device: id: spi_device_test data_rate: 2MHz - mode: 3 + spi_mode: 3 bit_order: lsb_first From 80b4c264818df8ce79813d3f572e87b9c7d24bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Mart=C3=ADn?= <contact@rodrigomartin.dev> Date: Wed, 6 Nov 2024 01:56:48 +0100 Subject: [PATCH 079/282] feat(MQTT): Add `enable`, `disable` and `enable_on_boot` (#7716) --- esphome/components/mqtt/__init__.py | 33 +++++++++++++++++++++++++ esphome/components/mqtt/mqtt_client.cpp | 30 +++++++++++++++++++--- esphome/components/mqtt/mqtt_client.h | 29 ++++++++++++++++++++-- tests/components/mqtt/common.yaml | 3 +++ 4 files changed, 90 insertions(+), 5 deletions(-) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 8851581ea0..86d163e61d 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -22,6 +22,7 @@ from esphome.const import ( CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_RETAIN, CONF_DISCOVERY_UNIQUE_ID_GENERATOR, + CONF_ENABLE_ON_BOOT, CONF_ID, CONF_KEEPALIVE, CONF_LEVEL, @@ -99,6 +100,8 @@ MQTTMessage = mqtt_ns.struct("MQTTMessage") MQTTClientComponent = mqtt_ns.class_("MQTTClientComponent", cg.Component) MQTTPublishAction = mqtt_ns.class_("MQTTPublishAction", automation.Action) MQTTPublishJsonAction = mqtt_ns.class_("MQTTPublishJsonAction", automation.Action) +MQTTEnableAction = mqtt_ns.class_("MQTTEnableAction", automation.Action) +MQTTDisableAction = mqtt_ns.class_("MQTTDisableAction", automation.Action) MQTTMessageTrigger = mqtt_ns.class_( "MQTTMessageTrigger", automation.Trigger.template(cg.std_string), cg.Component ) @@ -208,6 +211,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(MQTTClientComponent), cv.Required(CONF_BROKER): cv.string_strict, + cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, cv.Optional(CONF_PORT, default=1883): cv.port, cv.Optional(CONF_USERNAME, default=""): cv.string, cv.Optional(CONF_PASSWORD, default=""): cv.string, @@ -325,6 +329,7 @@ async def to_code(config): cg.add_global(mqtt_ns.using) cg.add(var.set_broker_address(config[CONF_BROKER])) + cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_broker_port(config[CONF_PORT])) cg.add(var.set_username(config[CONF_USERNAME])) cg.add(var.set_password(config[CONF_PASSWORD])) @@ -555,3 +560,31 @@ async def register_mqtt_component(var, config): async def mqtt_connected_to_code(config, condition_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) return cg.new_Pvariable(condition_id, template_arg, paren) + + +@automation.register_action( + "mqtt.enable", + MQTTEnableAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(MQTTClientComponent), + } + ), +) +async def mqtt_enable_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + +@automation.register_action( + "mqtt.disable", + MQTTDisableAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(MQTTClientComponent), + } + ), +) +async def mqtt_disable_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index b5ac285026..106192c0e3 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -50,6 +50,8 @@ void MQTTClientComponent::setup() { } }); this->mqtt_backend_.set_on_disconnect([this](MQTTClientDisconnectReason reason) { + if (this->state_ == MQTT_CLIENT_DISABLED) + return; this->state_ = MQTT_CLIENT_DISCONNECTED; this->disconnect_reason_ = reason; }); @@ -77,8 +79,9 @@ void MQTTClientComponent::setup() { topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2); } - this->last_connected_ = millis(); - this->start_dnslookup_(); + if (this->enable_on_boot_) { + this->enable(); + } } void MQTTClientComponent::send_device_info_() { @@ -163,7 +166,9 @@ void MQTTClientComponent::dump_config() { ESP_LOGCONFIG(TAG, " Availability: '%s'", this->availability_.topic.c_str()); } } -bool MQTTClientComponent::can_proceed() { return network::is_disabled() || this->is_connected(); } +bool MQTTClientComponent::can_proceed() { + return network::is_disabled() || this->state_ == MQTT_CLIENT_DISABLED || this->is_connected(); +} void MQTTClientComponent::start_dnslookup_() { for (auto &subscription : this->subscriptions_) { @@ -339,6 +344,8 @@ void MQTTClientComponent::loop() { const uint32_t now = millis(); switch (this->state_) { + case MQTT_CLIENT_DISABLED: + return; // Return to avoid a reboot when disabled case MQTT_CLIENT_DISCONNECTED: if (now - this->connect_begin_ > 5000) { this->start_dnslookup_(); @@ -501,6 +508,23 @@ bool MQTTClientComponent::publish_json(const std::string &topic, const json::jso return this->publish(topic, message, qos, retain); } +void MQTTClientComponent::enable() { + if (this->state_ != MQTT_CLIENT_DISABLED) + return; + ESP_LOGD(TAG, "Enabling MQTT..."); + this->state_ = MQTT_CLIENT_DISCONNECTED; + this->last_connected_ = millis(); + this->start_dnslookup_(); +} + +void MQTTClientComponent::disable() { + if (this->state_ == MQTT_CLIENT_DISABLED) + return; + ESP_LOGD(TAG, "Disabling MQTT..."); + this->state_ = MQTT_CLIENT_DISABLED; + this->on_shutdown(); +} + /** Check if the message topic matches the given subscription topic * * INFO: MQTT spec mandates that topics must not be empty and must be valid NULL-terminated UTF-8 strings. diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 887800f201..7ae3a6c5e8 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -87,7 +87,8 @@ struct MQTTDiscoveryInfo { }; enum MQTTClientState { - MQTT_CLIENT_DISCONNECTED = 0, + MQTT_CLIENT_DISABLED = 0, + MQTT_CLIENT_DISCONNECTED, MQTT_CLIENT_RESOLVING_ADDRESS, MQTT_CLIENT_CONNECTING, MQTT_CLIENT_CONNECTED, @@ -247,6 +248,9 @@ class MQTTClientComponent : public Component { void register_mqtt_component(MQTTComponent *component); bool is_connected(); + void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; } + void enable(); + void disable(); void on_shutdown() override; @@ -314,10 +318,11 @@ class MQTTClientComponent : public Component { MQTTBackendLibreTiny mqtt_backend_; #endif - MQTTClientState state_{MQTT_CLIENT_DISCONNECTED}; + MQTTClientState state_{MQTT_CLIENT_DISABLED}; network::IPAddress ip_; bool dns_resolved_{false}; bool dns_resolve_error_{false}; + bool enable_on_boot_{true}; std::vector<MQTTComponent *> children_; uint32_t reboot_timeout_{300000}; uint32_t connect_begin_; @@ -414,6 +419,26 @@ template<typename... Ts> class MQTTConnectedCondition : public Condition<Ts...> MQTTClientComponent *parent_; }; +template<typename... Ts> class MQTTEnableAction : public Action<Ts...> { + public: + MQTTEnableAction(MQTTClientComponent *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->enable(); } + + protected: + MQTTClientComponent *parent_; +}; + +template<typename... Ts> class MQTTDisableAction : public Action<Ts...> { + public: + MQTTDisableAction(MQTTClientComponent *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->disable(); } + + protected: + MQTTClientComponent *parent_; +}; + } // namespace mqtt } // namespace esphome diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml index 75c34bec56..d22fe9579f 100644 --- a/tests/components/mqtt/common.yaml +++ b/tests/components/mqtt/common.yaml @@ -10,6 +10,7 @@ mqtt: port: 1883 username: debug password: debug + enable_on_boot: false clean_session: True client_id: someclient use_abbreviations: false @@ -87,6 +88,8 @@ button: state_topic: some/topic/button qos: 2 on_press: + - mqtt.disable + - mqtt.enable - mqtt.publish: topic: some/topic/button payload: Hello From 248b0bc378b93b8be87899a65f47b4d7a95b866d Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 8 Nov 2024 07:05:23 +1100 Subject: [PATCH 080/282] [lvgl] Allow multiple LVGL instances (#7712) Co-authored-by: clydeps <U5yx99dok9> --- esphome/components/lvgl/__init__.py | 241 +++++++++++------- esphome/components/lvgl/automation.py | 14 +- .../components/lvgl/binary_sensor/__init__.py | 23 +- esphome/components/lvgl/light/__init__.py | 8 +- esphome/components/lvgl/lvcode.py | 4 +- esphome/components/lvgl/lvgl_esphome.cpp | 25 +- esphome/components/lvgl/lvgl_esphome.h | 14 +- esphome/components/lvgl/number/__init__.py | 25 +- esphome/components/lvgl/select/__init__.py | 21 +- esphome/components/lvgl/sensor/__init__.py | 22 +- esphome/components/lvgl/switch/__init__.py | 21 +- esphome/components/lvgl/text/__init__.py | 11 +- .../components/lvgl/text_sensor/__init__.py | 30 +-- esphome/components/lvgl/touchscreens.py | 5 +- esphome/components/lvgl/trigger.py | 12 +- esphome/components/lvgl/widgets/page.py | 5 +- tests/components/lvgl/test.host.yaml | 32 +++ 17 files changed, 287 insertions(+), 226 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 4a1a26cc0b..7476c0a09c 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -27,7 +27,7 @@ from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code from .gradient import GRADIENT_SCHEMA, gradients_to_code from .hello_world import get_hello_world from .lv_validation import lv_bool, lv_images_used -from .lvcode import LvContext, LvglComponent +from .lvcode import LvContext, LvglComponent, lvgl_static from .schemas import ( DISP_BG_SCHEMA, FLEX_OBJ_SCHEMA, @@ -152,41 +152,70 @@ def generate_lv_conf_h(): return LV_CONF_H_FORMAT.format("\n".join(definitions)) -def final_validation(config): - if pages := config.get(CONF_PAGES): - if all(p[df.CONF_SKIP] for p in pages): - raise cv.Invalid("At least one page must not be skipped") +def multi_conf_validate(configs: list[dict]): + displays = [config[df.CONF_DISPLAYS] for config in configs] + # flatten the display list + display_list = [disp for disps in displays for disp in disps] + if len(display_list) != len(set(display_list)): + raise cv.Invalid("A display ID may be used in only one LVGL instance") + base_config = configs[0] + for config in configs[1:]: + for item in ( + df.CONF_LOG_LEVEL, + df.CONF_COLOR_DEPTH, + df.CONF_BYTE_ORDER, + df.CONF_TRANSPARENCY_KEY, + ): + if base_config[item] != config[item]: + raise cv.Invalid( + f"Config item '{item}' must be the same for all LVGL instances" + ) + + +def final_validation(configs): + multi_conf_validate(configs) global_config = full_config.get() - for display_id in config[df.CONF_DISPLAYS]: - path = global_config.get_path_for_id(display_id)[:-1] - display = global_config.get_config_for_path(path) - if CONF_LAMBDA in display: - raise cv.Invalid("Using lambda: in display config not compatible with LVGL") - if display[CONF_AUTO_CLEAR_ENABLED]: - raise cv.Invalid( - "Using auto_clear_enabled: true in display config not compatible with LVGL" - ) - buffer_frac = config[CONF_BUFFER_SIZE] - if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config: - LOGGER.warning("buffer_size: may need to be reduced without PSRAM") - for image_id in lv_images_used: - path = global_config.get_path_for_id(image_id)[:-1] - image_conf = global_config.get_config_for_path(path) - if image_conf[CONF_TYPE] in ("RGBA", "RGB24"): - raise cv.Invalid( - "Using RGBA or RGB24 in image config not compatible with LVGL", path - ) - for w in focused_widgets: - path = global_config.get_path_for_id(w) - widget_conf = global_config.get_config_for_path(path[:-1]) - if df.CONF_ADJUSTABLE in widget_conf and not widget_conf[df.CONF_ADJUSTABLE]: - raise cv.Invalid( - "A non adjustable arc may not be focused", - path, - ) + for config in configs: + if pages := config.get(CONF_PAGES): + if all(p[df.CONF_SKIP] for p in pages): + raise cv.Invalid("At least one page must not be skipped") + for display_id in config[df.CONF_DISPLAYS]: + path = global_config.get_path_for_id(display_id)[:-1] + display = global_config.get_config_for_path(path) + if CONF_LAMBDA in display: + raise cv.Invalid( + "Using lambda: in display config not compatible with LVGL" + ) + if display[CONF_AUTO_CLEAR_ENABLED]: + raise cv.Invalid( + "Using auto_clear_enabled: true in display config not compatible with LVGL" + ) + buffer_frac = config[CONF_BUFFER_SIZE] + if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config: + LOGGER.warning("buffer_size: may need to be reduced without PSRAM") + for image_id in lv_images_used: + path = global_config.get_path_for_id(image_id)[:-1] + image_conf = global_config.get_config_for_path(path) + if image_conf[CONF_TYPE] in ("RGBA", "RGB24"): + raise cv.Invalid( + "Using RGBA or RGB24 in image config not compatible with LVGL", path + ) + for w in focused_widgets: + path = global_config.get_path_for_id(w) + widget_conf = global_config.get_config_for_path(path[:-1]) + if ( + df.CONF_ADJUSTABLE in widget_conf + and not widget_conf[df.CONF_ADJUSTABLE] + ): + raise cv.Invalid( + "A non adjustable arc may not be focused", + path, + ) -async def to_code(config): +async def to_code(configs): + config_0 = configs[0] + # Global configuration cg.add_library("lvgl/lvgl", "8.4.0") cg.add_define("USE_LVGL") # suppress default enabling of extra widgets @@ -203,53 +232,33 @@ async def to_code(config): add_define("LV_MEM_CUSTOM_INCLUDE", '"esphome/components/lvgl/lvgl_hal.h"') add_define( - "LV_LOG_LEVEL", f"LV_LOG_LEVEL_{df.LV_LOG_LEVELS[config[df.CONF_LOG_LEVEL]]}" + "LV_LOG_LEVEL", + f"LV_LOG_LEVEL_{df.LV_LOG_LEVELS[config_0[df.CONF_LOG_LEVEL]]}", ) cg.add_define( "LVGL_LOG_LEVEL", - cg.RawExpression(f"ESPHOME_LOG_LEVEL_{config[df.CONF_LOG_LEVEL]}"), + cg.RawExpression(f"ESPHOME_LOG_LEVEL_{config_0[df.CONF_LOG_LEVEL]}"), ) - add_define("LV_COLOR_DEPTH", config[df.CONF_COLOR_DEPTH]) + add_define("LV_COLOR_DEPTH", config_0[df.CONF_COLOR_DEPTH]) for font in helpers.lv_fonts_used: add_define(f"LV_FONT_{font.upper()}") - if config[df.CONF_COLOR_DEPTH] == 16: + if config_0[df.CONF_COLOR_DEPTH] == 16: add_define( "LV_COLOR_16_SWAP", - "1" if config[df.CONF_BYTE_ORDER] == "big_endian" else "0", + "1" if config_0[df.CONF_BYTE_ORDER] == "big_endian" else "0", ) add_define( "LV_COLOR_CHROMA_KEY", - await lvalid.lv_color.process(config[df.CONF_TRANSPARENCY_KEY]), + await lvalid.lv_color.process(config_0[df.CONF_TRANSPARENCY_KEY]), ) cg.add_build_flag("-Isrc") cg.add_global(lvgl_ns.using) - frac = config[CONF_BUFFER_SIZE] - if frac >= 0.75: - frac = 1 - elif frac >= 0.375: - frac = 2 - elif frac > 0.19: - frac = 4 - else: - frac = 8 - displays = [await cg.get_variable(display) for display in config[df.CONF_DISPLAYS]] - lv_component = cg.new_Pvariable( - config[CONF_ID], - displays, - frac, - config[df.CONF_FULL_REFRESH], - config[df.CONF_DRAW_ROUNDING], - config[df.CONF_RESUME_ON_INPUT], - ) - await cg.register_component(lv_component, config) - Widget.create(config[CONF_ID], lv_component, obj_spec, config) - for font in helpers.esphome_fonts_used: await cg.get_variable(font) cg.new_Pvariable(ID(f"{font}_engine", True, type=FontEngine), MockObj(font)) - default_font = config[df.CONF_DEFAULT_FONT] + default_font = config_0[df.CONF_DEFAULT_FONT] if not lvalid.is_lv_font(default_font): add_define( "LV_FONT_CUSTOM_DECLARE", f"LV_FONT_DECLARE(*{df.DEFAULT_ESPHOME_FONT})" @@ -265,39 +274,71 @@ async def to_code(config): add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT) else: add_define("LV_FONT_DEFAULT", await lvalid.lv_font.process(default_font)) + cg.add(lvgl_static.esphome_lvgl_init()) - lv_scr_act = get_scr_act(lv_component) - async with LvContext(lv_component): - await touchscreens_to_code(lv_component, config) - await encoders_to_code(lv_component, config) - await theme_to_code(config) - await styles_to_code(config) - await gradients_to_code(config) - await set_obj_properties(lv_scr_act, config) - await add_widgets(lv_scr_act, config) - await add_pages(lv_component, config) - await add_top_layer(lv_component, config) - await msgboxes_to_code(lv_component, config) - await disp_update(lv_component.get_disp(), config) + for config in configs: + frac = config[CONF_BUFFER_SIZE] + if frac >= 0.75: + frac = 1 + elif frac >= 0.375: + frac = 2 + elif frac > 0.19: + frac = 4 + else: + frac = 8 + displays = [ + await cg.get_variable(display) for display in config[df.CONF_DISPLAYS] + ] + lv_component = cg.new_Pvariable( + config[CONF_ID], + displays, + frac, + config[df.CONF_FULL_REFRESH], + config[df.CONF_DRAW_ROUNDING], + config[df.CONF_RESUME_ON_INPUT], + ) + await cg.register_component(lv_component, config) + Widget.create(config[CONF_ID], lv_component, obj_spec, config) + + lv_scr_act = get_scr_act(lv_component) + async with LvContext(): + await touchscreens_to_code(lv_component, config) + await encoders_to_code(lv_component, config) + await theme_to_code(config) + await styles_to_code(config) + await gradients_to_code(config) + await set_obj_properties(lv_scr_act, config) + await add_widgets(lv_scr_act, config) + await add_pages(lv_component, config) + await add_top_layer(lv_component, config) + await msgboxes_to_code(lv_component, config) + await disp_update(lv_component.get_disp(), config) # Set this directly since we are limited in how many methods can be added to the Widget class. Widget.widgets_completed = True - async with LvContext(lv_component): - await generate_triggers(lv_component) - await generate_page_triggers(lv_component, config) - await initial_focus_to_code(config) - for conf in config.get(CONF_ON_IDLE, ()): - templ = await cg.templatable(conf[CONF_TIMEOUT], [], cg.uint32) - idle_trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], lv_component, templ) - await build_automation(idle_trigger, [], conf) - for conf in config.get(df.CONF_ON_PAUSE, ()): - pause_trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], lv_component, True) - await build_automation(pause_trigger, [], conf) - for conf in config.get(df.CONF_ON_RESUME, ()): - resume_trigger = cg.new_Pvariable( - conf[CONF_TRIGGER_ID], lv_component, False - ) - await build_automation(resume_trigger, [], conf) + async with LvContext(): + await generate_triggers() + for config in configs: + lv_component = await cg.get_variable(config[CONF_ID]) + await generate_page_triggers(config) + await initial_focus_to_code(config) + for conf in config.get(CONF_ON_IDLE, ()): + templ = await cg.templatable(conf[CONF_TIMEOUT], [], cg.uint32) + idle_trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], lv_component, templ + ) + await build_automation(idle_trigger, [], conf) + for conf in config.get(df.CONF_ON_PAUSE, ()): + pause_trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], lv_component, True + ) + await build_automation(pause_trigger, [], conf) + for conf in config.get(df.CONF_ON_RESUME, ()): + resume_trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], lv_component, False + ) + await build_automation(resume_trigger, [], conf) + # This must be done after all widgets are created for comp in helpers.lvgl_components_required: cg.add_define(f"USE_LVGL_{comp.upper()}") if "transform_angle" in styles_used: @@ -312,7 +353,10 @@ async def to_code(config): def display_schema(config): value = cv.ensure_list(cv.use_id(Display))(config) - return value or [cv.use_id(Display)(config)] + value = value or [cv.use_id(Display)(config)] + if len(set(value)) != len(value): + raise cv.Invalid("Display IDs must be unique") + return value def add_hello_world(config): @@ -324,7 +368,7 @@ def add_hello_world(config): FINAL_VALIDATE_SCHEMA = final_validation -CONFIG_SCHEMA = ( +LVGL_SCHEMA = ( cv.polling_component_schema("1s") .extend(obj_schema(obj_spec)) .extend( @@ -393,3 +437,16 @@ CONFIG_SCHEMA = ( .extend(DISP_BG_SCHEMA) .add_extra(add_hello_world) ) + + +def lvgl_config_schema(config): + """ + Can't use cv.ensure_list here because it converts an empty config to an empty list, + rather than a default config. + """ + if not config or isinstance(config, dict): + return [LVGL_SCHEMA(config)] + return cv.Schema([LVGL_SCHEMA])(config) + + +CONFIG_SCHEMA = lvgl_config_schema diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index 48472354f8..58e3dd808b 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -137,20 +137,18 @@ async def disp_update(disp, config: dict): cv.maybe_simple_value( { cv.Required(CONF_ID): cv.use_id(lv_obj_t), - cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent), }, key=CONF_ID, ), - cv.Schema( - { - cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent), - } - ), + LVGL_SCHEMA, ), ) async def obj_invalidate_to_code(config, action_id, template_arg, args): - lv_comp = await cg.get_variable(config[CONF_LVGL_ID]) - widgets = await get_widgets(config) or [get_scr_act(lv_comp)] + if CONF_LVGL_ID in config: + lv_comp = await cg.get_variable(config[CONF_LVGL_ID]) + widgets = [get_scr_act(lv_comp)] + else: + widgets = await get_widgets(config) async def do_invalidate(widget: Widget): lv_obj.invalidate(widget.obj) diff --git a/esphome/components/lvgl/binary_sensor/__init__.py b/esphome/components/lvgl/binary_sensor/__init__.py index 56984405aa..ffbdc977b2 100644 --- a/esphome/components/lvgl/binary_sensor/__init__.py +++ b/esphome/components/lvgl/binary_sensor/__init__.py @@ -1,4 +1,3 @@ -import esphome.codegen as cg from esphome.components.binary_sensor import ( BinarySensor, binary_sensor_schema, @@ -6,36 +5,30 @@ from esphome.components.binary_sensor import ( ) import esphome.config_validation as cv -from ..defines import CONF_LVGL_ID, CONF_WIDGET -from ..lvcode import EVENT_ARG, LambdaContext, LvContext -from ..schemas import LVGL_SCHEMA +from ..defines import CONF_WIDGET +from ..lvcode import EVENT_ARG, LambdaContext, LvContext, lvgl_static from ..types import LV_EVENT, lv_pseudo_button_t from ..widgets import Widget, get_widgets, wait_for_widgets -CONFIG_SCHEMA = ( - binary_sensor_schema(BinarySensor) - .extend(LVGL_SCHEMA) - .extend( - { - cv.Required(CONF_WIDGET): cv.use_id(lv_pseudo_button_t), - } - ) +CONFIG_SCHEMA = binary_sensor_schema(BinarySensor).extend( + { + cv.Required(CONF_WIDGET): cv.use_id(lv_pseudo_button_t), + } ) async def to_code(config): sensor = await new_binary_sensor(config) - paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] assert isinstance(widget, Widget) await wait_for_widgets() async with LambdaContext(EVENT_ARG) as pressed_ctx: pressed_ctx.add(sensor.publish_state(widget.is_pressed())) - async with LvContext(paren) as ctx: + async with LvContext() as ctx: ctx.add(sensor.publish_initial_state(widget.is_pressed())) ctx.add( - paren.add_event_cb( + lvgl_static.add_event_cb( widget.obj, await pressed_ctx.get_lambda(), LV_EVENT.PRESSING, diff --git a/esphome/components/lvgl/light/__init__.py b/esphome/components/lvgl/light/__init__.py index 8031ae8221..dcdf67a520 100644 --- a/esphome/components/lvgl/light/__init__.py +++ b/esphome/components/lvgl/light/__init__.py @@ -4,9 +4,8 @@ from esphome.components.light import LightOutput import esphome.config_validation as cv from esphome.const import CONF_GAMMA_CORRECT, CONF_OUTPUT_ID -from ..defines import CONF_LVGL_ID, CONF_WIDGET +from ..defines import CONF_WIDGET from ..lvcode import LvContext -from ..schemas import LVGL_SCHEMA from ..types import LvType, lvgl_ns from ..widgets import get_widgets, wait_for_widgets @@ -18,16 +17,15 @@ CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( cv.Required(CONF_WIDGET): cv.use_id(lv_led_t), cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(LVLight), } -).extend(LVGL_SCHEMA) +) async def to_code(config): var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) await light.register_light(var, config) - paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] await wait_for_widgets() - async with LvContext(paren) as ctx: + async with LvContext() as ctx: ctx.add(var.set_obj(widget.obj)) diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index 37d6670b84..6b98cc4251 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -178,10 +178,9 @@ class LvContext(LambdaContext): added_lambda_count = 0 - def __init__(self, lv_component, args=None): + def __init__(self, args=None): self.args = args or LVGL_COMP_ARG super().__init__(parameters=self.args) - self.lv_component = lv_component async def __aexit__(self, exc_type, exc_val, exc_tb): await super().__aexit__(exc_type, exc_val, exc_tb) @@ -298,6 +297,7 @@ lv_expr = LvExpr("lv_") lv_obj = MockLv("lv_obj_") # Operations on the LVGL component lvgl_comp = MockObj(LVGL_COMP, "->") +lvgl_static = MockObj("LvglComponent", "::") # equivalent to cg.add() for the current code context diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 70cfb859de..41346bc732 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -98,19 +98,24 @@ void LvglComponent::set_paused(bool paused, bool show_snow) { this->pause_callbacks_.call(paused); } +void LvglComponent::esphome_lvgl_init() { + lv_init(); + lv_update_event = static_cast<lv_event_code_t>(lv_event_register_id()); + lv_api_event = static_cast<lv_event_code_t>(lv_event_register_id()); +} void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event) { - lv_obj_add_event_cb(obj, callback, event, this); + lv_obj_add_event_cb(obj, callback, event, nullptr); } void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2) { - this->add_event_cb(obj, callback, event1); - this->add_event_cb(obj, callback, event2); + add_event_cb(obj, callback, event1); + add_event_cb(obj, callback, event2); } void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2, lv_event_code_t event3) { - this->add_event_cb(obj, callback, event1); - this->add_event_cb(obj, callback, event2); - this->add_event_cb(obj, callback, event3); + add_event_cb(obj, callback, event1); + add_event_cb(obj, callback, event2); + add_event_cb(obj, callback, event3); } void LvglComponent::add_page(LvPageType *page) { this->pages_.push_back(page); @@ -218,8 +223,10 @@ PauseTrigger::PauseTrigger(LvglComponent *parent, TemplatableValue<bool> paused) } #ifdef USE_LVGL_TOUCHSCREEN -LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time) { +LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time, LvglComponent *parent) { + this->set_parent(parent); lv_indev_drv_init(&this->drv_); + this->drv_.disp = parent->get_disp(); this->drv_.long_press_repeat_time = long_press_repeat_time; this->drv_.long_press_time = long_press_time; this->drv_.type = LV_INDEV_TYPE_POINTER; @@ -235,6 +242,7 @@ LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_r } }; } + void LVTouchListener::update(const touchscreen::TouchPoints_t &tpoints) { this->touch_pressed_ = !this->parent_->is_paused() && !tpoints.empty(); if (this->touch_pressed_) @@ -405,9 +413,6 @@ LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buf buffer_frac_(buffer_frac), full_refresh_(full_refresh), resume_on_input_(resume_on_input) { - lv_init(); - lv_update_event = static_cast<lv_event_code_t>(lv_event_register_id()); - lv_api_event = static_cast<lv_event_code_t>(lv_event_register_id()); auto *display = this->displays_[0]; size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_; auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8; diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index f357c4950c..dae07d5153 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -146,10 +146,14 @@ class LvglComponent : public PollingComponent { } } - void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event); - void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2); - void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2, - lv_event_code_t event3); + /** + * Initialize the LVGL library and register custom events. + */ + static void esphome_lvgl_init(); + static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event); + static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2); + static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2, + lv_event_code_t event3); void add_page(LvPageType *page); void show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time); void show_next_page(lv_scr_load_anim_t anim, uint32_t time); @@ -231,7 +235,7 @@ template<typename... Ts> class LvglCondition : public Condition<Ts...>, public P #ifdef USE_LVGL_TOUCHSCREEN class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglComponent> { public: - LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time); + LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time, LvglComponent *parent); void update(const touchscreen::TouchPoints_t &tpoints) override; void release() override { touch_pressed_ = false; diff --git a/esphome/components/lvgl/number/__init__.py b/esphome/components/lvgl/number/__init__.py index 07f92635b5..b41a36bc0f 100644 --- a/esphome/components/lvgl/number/__init__.py +++ b/esphome/components/lvgl/number/__init__.py @@ -3,7 +3,7 @@ from esphome.components import number import esphome.config_validation as cv from esphome.cpp_generator import MockObj -from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_UPDATE_ON_RELEASE, CONF_WIDGET +from ..defines import CONF_ANIMATED, CONF_UPDATE_ON_RELEASE, CONF_WIDGET from ..lv_validation import animated from ..lvcode import ( API_EVENT, @@ -13,28 +13,23 @@ from ..lvcode import ( LvContext, lv, lv_add, + lvgl_static, ) -from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvNumber, lvgl_ns from ..widgets import get_widgets, wait_for_widgets LVGLNumber = lvgl_ns.class_("LVGLNumber", number.Number) -CONFIG_SCHEMA = ( - number.number_schema(LVGLNumber) - .extend(LVGL_SCHEMA) - .extend( - { - cv.Required(CONF_WIDGET): cv.use_id(LvNumber), - cv.Optional(CONF_ANIMATED, default=True): animated, - cv.Optional(CONF_UPDATE_ON_RELEASE, default=False): cv.boolean, - } - ) +CONFIG_SCHEMA = number.number_schema(LVGLNumber).extend( + { + cv.Required(CONF_WIDGET): cv.use_id(LvNumber), + cv.Optional(CONF_ANIMATED, default=True): animated, + cv.Optional(CONF_UPDATE_ON_RELEASE, default=False): cv.boolean, + } ) async def to_code(config): - paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] var = await number.new_number( @@ -58,10 +53,10 @@ async def to_code(config): if not config[CONF_UPDATE_ON_RELEASE] else LV_EVENT.RELEASED ) - async with LvContext(paren): + async with LvContext(): lv_add(var.set_control_lambda(await control.get_lambda())) lv_add( - paren.add_event_cb( + lvgl_static.add_event_cb( widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code ) ) diff --git a/esphome/components/lvgl/select/__init__.py b/esphome/components/lvgl/select/__init__.py index 5e50b6b385..bd5ef8f237 100644 --- a/esphome/components/lvgl/select/__init__.py +++ b/esphome/components/lvgl/select/__init__.py @@ -1,25 +1,19 @@ -import esphome.codegen as cg from esphome.components import select import esphome.config_validation as cv from esphome.const import CONF_OPTIONS -from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_WIDGET, literal +from ..defines import CONF_ANIMATED, CONF_WIDGET, literal from ..lvcode import LvContext -from ..schemas import LVGL_SCHEMA from ..types import LvSelect, lvgl_ns from ..widgets import get_widgets, wait_for_widgets LVGLSelect = lvgl_ns.class_("LVGLSelect", select.Select) -CONFIG_SCHEMA = ( - select.select_schema(LVGLSelect) - .extend(LVGL_SCHEMA) - .extend( - { - cv.Required(CONF_WIDGET): cv.use_id(LvSelect), - cv.Optional(CONF_ANIMATED, default=False): cv.boolean, - } - ) +CONFIG_SCHEMA = select.select_schema(LVGLSelect).extend( + { + cv.Required(CONF_WIDGET): cv.use_id(LvSelect), + cv.Optional(CONF_ANIMATED, default=False): cv.boolean, + } ) @@ -28,9 +22,8 @@ async def to_code(config): widget = widget[0] options = widget.config.get(CONF_OPTIONS, []) selector = await select.new_select(config, options=options) - paren = await cg.get_variable(config[CONF_LVGL_ID]) await wait_for_widgets() - async with LvContext(paren) as ctx: + async with LvContext() as ctx: ctx.add( selector.set_widget( widget.var, diff --git a/esphome/components/lvgl/sensor/__init__.py b/esphome/components/lvgl/sensor/__init__.py index a2a2298c27..03b2638ed0 100644 --- a/esphome/components/lvgl/sensor/__init__.py +++ b/esphome/components/lvgl/sensor/__init__.py @@ -1,8 +1,7 @@ -import esphome.codegen as cg from esphome.components.sensor import Sensor, new_sensor, sensor_schema import esphome.config_validation as cv -from ..defines import CONF_LVGL_ID, CONF_WIDGET +from ..defines import CONF_WIDGET from ..lvcode import ( API_EVENT, EVENT_ARG, @@ -11,34 +10,29 @@ from ..lvcode import ( LambdaContext, LvContext, lv_add, + lvgl_static, ) -from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvNumber from ..widgets import Widget, get_widgets, wait_for_widgets -CONFIG_SCHEMA = ( - sensor_schema(Sensor) - .extend(LVGL_SCHEMA) - .extend( - { - cv.Required(CONF_WIDGET): cv.use_id(LvNumber), - } - ) +CONFIG_SCHEMA = sensor_schema(Sensor).extend( + { + cv.Required(CONF_WIDGET): cv.use_id(LvNumber), + } ) async def to_code(config): sensor = await new_sensor(config) - paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] assert isinstance(widget, Widget) await wait_for_widgets() async with LambdaContext(EVENT_ARG) as lamb: lv_add(sensor.publish_state(widget.get_value())) - async with LvContext(paren, LVGL_COMP_ARG): + async with LvContext(LVGL_COMP_ARG): lv_add( - paren.add_event_cb( + lvgl_static.add_event_cb( widget.obj, await lamb.get_lambda(), LV_EVENT.VALUE_CHANGED, diff --git a/esphome/components/lvgl/switch/__init__.py b/esphome/components/lvgl/switch/__init__.py index 8c090543f9..4e1e7f72e0 100644 --- a/esphome/components/lvgl/switch/__init__.py +++ b/esphome/components/lvgl/switch/__init__.py @@ -3,7 +3,7 @@ from esphome.components.switch import Switch, new_switch, switch_schema import esphome.config_validation as cv from esphome.cpp_generator import MockObj -from ..defines import CONF_LVGL_ID, CONF_WIDGET, literal +from ..defines import CONF_WIDGET, literal from ..lvcode import ( API_EVENT, EVENT_ARG, @@ -13,26 +13,21 @@ from ..lvcode import ( LvContext, lv, lv_add, + lvgl_static, ) -from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LV_STATE, lv_pseudo_button_t, lvgl_ns from ..widgets import get_widgets, wait_for_widgets LVGLSwitch = lvgl_ns.class_("LVGLSwitch", Switch) -CONFIG_SCHEMA = ( - switch_schema(LVGLSwitch) - .extend(LVGL_SCHEMA) - .extend( - { - cv.Required(CONF_WIDGET): cv.use_id(lv_pseudo_button_t), - } - ) +CONFIG_SCHEMA = switch_schema(LVGLSwitch).extend( + { + cv.Required(CONF_WIDGET): cv.use_id(lv_pseudo_button_t), + } ) async def to_code(config): switch = await new_switch(config) - paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] await wait_for_widgets() @@ -45,10 +40,10 @@ async def to_code(config): widget.clear_state(LV_STATE.CHECKED) lv.event_send(widget.obj, API_EVENT, cg.nullptr) control.add(switch.publish_state(literal("v"))) - async with LvContext(paren) as ctx: + async with LvContext() as ctx: lv_add(switch.set_control_lambda(await control.get_lambda())) ctx.add( - paren.add_event_cb( + lvgl_static.add_event_cb( widget.obj, await checked_ctx.get_lambda(), LV_EVENT.VALUE_CHANGED, diff --git a/esphome/components/lvgl/text/__init__.py b/esphome/components/lvgl/text/__init__.py index a59e703591..89db139a6a 100644 --- a/esphome/components/lvgl/text/__init__.py +++ b/esphome/components/lvgl/text/__init__.py @@ -3,7 +3,7 @@ from esphome.components import text from esphome.components.text import new_text import esphome.config_validation as cv -from ..defines import CONF_LVGL_ID, CONF_WIDGET +from ..defines import CONF_WIDGET from ..lvcode import ( API_EVENT, EVENT_ARG, @@ -12,14 +12,14 @@ from ..lvcode import ( LvContext, lv, lv_add, + lvgl_static, ) -from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvText, lvgl_ns from ..widgets import get_widgets, wait_for_widgets LVGLText = lvgl_ns.class_("LVGLText", text.Text) -CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(LVGL_SCHEMA).extend( +CONFIG_SCHEMA = text.TEXT_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(LVGLText), cv.Required(CONF_WIDGET): cv.use_id(LvText), @@ -29,7 +29,6 @@ CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(LVGL_SCHEMA).extend( async def to_code(config): textvar = await new_text(config) - paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] await wait_for_widgets() @@ -39,10 +38,10 @@ async def to_code(config): control.add(textvar.publish_state(widget.get_value())) async with LambdaContext(EVENT_ARG) as lamb: lv_add(textvar.publish_state(widget.get_value())) - async with LvContext(paren): + async with LvContext(): lv_add(textvar.set_control_lambda(await control.get_lambda())) lv_add( - paren.add_event_cb( + lvgl_static.add_event_cb( widget.obj, await lamb.get_lambda(), LV_EVENT.VALUE_CHANGED, diff --git a/esphome/components/lvgl/text_sensor/__init__.py b/esphome/components/lvgl/text_sensor/__init__.py index ae39eec291..4728fd137a 100644 --- a/esphome/components/lvgl/text_sensor/__init__.py +++ b/esphome/components/lvgl/text_sensor/__init__.py @@ -1,4 +1,3 @@ -import esphome.codegen as cg from esphome.components.text_sensor import ( TextSensor, new_text_sensor, @@ -6,34 +5,35 @@ from esphome.components.text_sensor import ( ) import esphome.config_validation as cv -from ..defines import CONF_LVGL_ID, CONF_WIDGET -from ..lvcode import API_EVENT, EVENT_ARG, UPDATE_EVENT, LambdaContext, LvContext -from ..schemas import LVGL_SCHEMA +from ..defines import CONF_WIDGET +from ..lvcode import ( + API_EVENT, + EVENT_ARG, + UPDATE_EVENT, + LambdaContext, + LvContext, + lvgl_static, +) from ..types import LV_EVENT, LvText from ..widgets import get_widgets, wait_for_widgets -CONFIG_SCHEMA = ( - text_sensor_schema(TextSensor) - .extend(LVGL_SCHEMA) - .extend( - { - cv.Required(CONF_WIDGET): cv.use_id(LvText), - } - ) +CONFIG_SCHEMA = text_sensor_schema(TextSensor).extend( + { + cv.Required(CONF_WIDGET): cv.use_id(LvText), + } ) async def to_code(config): sensor = await new_text_sensor(config) - paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] await wait_for_widgets() async with LambdaContext(EVENT_ARG) as pressed_ctx: pressed_ctx.add(sensor.publish_state(widget.get_value())) - async with LvContext(paren) as ctx: + async with LvContext() as ctx: ctx.add( - paren.add_event_cb( + lvgl_static.add_event_cb( widget.obj, await pressed_ctx.get_lambda(), LV_EVENT.VALUE_CHANGED, diff --git a/esphome/components/lvgl/touchscreens.py b/esphome/components/lvgl/touchscreens.py index 4d430a428e..f2dd013f6d 100644 --- a/esphome/components/lvgl/touchscreens.py +++ b/esphome/components/lvgl/touchscreens.py @@ -33,13 +33,12 @@ def touchscreen_schema(config): return [TOUCHSCREENS_CONFIG(config)] -async def touchscreens_to_code(var, config): +async def touchscreens_to_code(lv_component, config): for tconf in config[CONF_TOUCHSCREENS]: lvgl_components_required.add(CONF_TOUCHSCREEN) touchscreen = await cg.get_variable(tconf[CONF_TOUCHSCREEN_ID]) lpt = tconf[CONF_LONG_PRESS_TIME].total_milliseconds lprt = tconf[CONF_LONG_PRESS_REPEAT_TIME].total_milliseconds - listener = cg.new_Pvariable(tconf[CONF_ID], lpt, lprt) - await cg.register_parented(listener, var) + listener = cg.new_Pvariable(tconf[CONF_ID], lpt, lprt, lv_component) lv.indev_drv_register(listener.get_drv()) cg.add(touchscreen.register_listener(listener)) diff --git a/esphome/components/lvgl/trigger.py b/esphome/components/lvgl/trigger.py index eb6e483203..fb856df04e 100644 --- a/esphome/components/lvgl/trigger.py +++ b/esphome/components/lvgl/trigger.py @@ -20,17 +20,16 @@ from .lvcode import ( lv, lv_add, lv_event_t_ptr, + lvgl_static, ) from .types import LV_EVENT from .widgets import widget_map -async def generate_triggers(lv_component): +async def generate_triggers(): """ Generate LVGL triggers for all defined widgets Must be done after all widgets completed - :param lv_component: The parent component - :return: """ for w in widget_map.values(): @@ -43,11 +42,10 @@ async def generate_triggers(lv_component): conf = conf[0] w.add_flag("LV_OBJ_FLAG_CLICKABLE") event = literal("LV_EVENT_" + LV_EVENT_MAP[event[3:].upper()]) - await add_trigger(conf, lv_component, w, event) + await add_trigger(conf, w, event) for conf in w.config.get(CONF_ON_VALUE, ()): await add_trigger( conf, - lv_component, w, LV_EVENT.VALUE_CHANGED, API_EVENT, @@ -63,7 +61,7 @@ async def generate_triggers(lv_component): lv.obj_align_to(w.obj, target, align, x, y) -async def add_trigger(conf, lv_component, w, *events): +async def add_trigger(conf, w, *events): tid = conf[CONF_TRIGGER_ID] trigger = cg.new_Pvariable(tid) args = w.get_args() + [(lv_event_t_ptr, "event")] @@ -72,4 +70,4 @@ async def add_trigger(conf, lv_component, w, *events): async with LambdaContext(EVENT_ARG, where=tid) as context: with LvConditional(w.is_selected()): lv_add(trigger.trigger(*value, literal("event"))) - lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), *events)) + lv_add(lvgl_static.add_event_cb(w.obj, await context.get_lambda(), *events)) diff --git a/esphome/components/lvgl/widgets/page.py b/esphome/components/lvgl/widgets/page.py index 0e84ab6791..a754a9cb9a 100644 --- a/esphome/components/lvgl/widgets/page.py +++ b/esphome/components/lvgl/widgets/page.py @@ -20,6 +20,7 @@ from ..lvcode import ( add_line_marks, lv_add, lvgl_comp, + lvgl_static, ) from ..schemas import LVGL_SCHEMA from ..types import LvglAction, lv_page_t @@ -139,7 +140,7 @@ async def add_pages(lv_component, config): await add_widgets(page, pconf) -async def generate_page_triggers(lv_component, config): +async def generate_page_triggers(config): for pconf in config.get(CONF_PAGES, ()): page = (await get_widgets(pconf))[0] for ev in (CONF_ON_LOAD, CONF_ON_UNLOAD): @@ -149,7 +150,7 @@ async def generate_page_triggers(lv_component, config): async with LambdaContext(EVENT_ARG, where=id) as context: lv_add(trigger.trigger()) lv_add( - lv_component.add_event_cb( + lvgl_static.add_event_cb( page.obj, await context.get_lambda(), literal(f"LV_EVENT_SCREEN_{ev[3:].upper()}_START"), diff --git a/tests/components/lvgl/test.host.yaml b/tests/components/lvgl/test.host.yaml index 3a490bbe15..34918cb113 100644 --- a/tests/components/lvgl/test.host.yaml +++ b/tests/components/lvgl/test.host.yaml @@ -1,5 +1,12 @@ display: - platform: sdl + id: sdl0 + auto_clear_enabled: false + dimensions: + width: 480 + height: 320 + - platform: sdl + id: sdl1 auto_clear_enabled: false dimensions: width: 480 @@ -7,5 +14,30 @@ display: touchscreen: - platform: sdl + display: sdl0 + sdl_id: sdl0 lvgl: + - id: lvgl_0 + displays: sdl0 + - id: lvgl_1 + displays: sdl1 + on_idle: + timeout: 8s + then: + if: + condition: + lvgl.is_idle: + lvgl_id: lvgl_1 + timeout: 5s + then: + logger.log: Lvgl2 is idle + widgets: + - button: + align: center + widgets: + - label: + text: Click ME + on_click: + logger.log: Clicked + From c0658ffe2c31edcba1091dd05c922c36b3292c1c Mon Sep 17 00:00:00 2001 From: Ramil Valitov <ramilvalitov@gmail.com> Date: Fri, 8 Nov 2024 01:10:58 +0300 Subject: [PATCH 081/282] [fix] deprecated legacy driver tsens (#7658) Co-authored-by: luar123 <49960470+luar123@users.noreply.github.com> --- .../internal_temperature.cpp | 51 ++++++++++++++++++- .../internal_temperature.h | 1 + 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/esphome/components/internal_temperature/internal_temperature.cpp b/esphome/components/internal_temperature/internal_temperature.cpp index 9ef5cbecd5..afa5583e59 100644 --- a/esphome/components/internal_temperature/internal_temperature.cpp +++ b/esphome/components/internal_temperature/internal_temperature.cpp @@ -8,8 +8,13 @@ extern "C" { uint8_t temprature_sens_read(); } #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32C2) +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) #include "driver/temp_sensor.h" +#else +#include "driver/temperature_sensor.h" +#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) #endif // USE_ESP32_VARIANT #endif // USE_ESP32 #ifdef USE_RP2040 @@ -25,6 +30,13 @@ namespace esphome { namespace internal_temperature { static const char *const TAG = "internal_temperature"; +#ifdef USE_ESP32 +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \ + (defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2)) +static temperature_sensor_handle_t tsensNew = NULL; +#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT +#endif // USE_ESP32 void InternalTemperatureSensor::update() { float temperature = NAN; @@ -36,7 +48,9 @@ void InternalTemperatureSensor::update() { temperature = (raw - 32) / 1.8f; success = (raw != 128); #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32C2) +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT(); temp_sensor_set_config(tsens); temp_sensor_start(); @@ -47,6 +61,13 @@ void InternalTemperatureSensor::update() { esp_err_t result = temp_sensor_read_celsius(&temperature); temp_sensor_stop(); success = (result == ESP_OK); +#else + esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature); + success = (result == ESP_OK); + if (!success) { + ESP_LOGE(TAG, "Failed to get temperature: %d", result); + } +#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) #endif // USE_ESP32_VARIANT #endif // USE_ESP32 #ifdef USE_RP2040 @@ -75,6 +96,32 @@ void InternalTemperatureSensor::update() { } } +void InternalTemperatureSensor::setup() { +#ifdef USE_ESP32 +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \ + (defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2)) + ESP_LOGCONFIG(TAG, "Setting up temperature sensor..."); + + temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80); + + esp_err_t result = temperature_sensor_install(&tsens_config, &tsensNew); + if (result != ESP_OK) { + ESP_LOGE(TAG, "Failed to install temperature sensor: %d", result); + this->mark_failed(); + return; + } + + result = temperature_sensor_enable(tsensNew); + if (result != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable temperature sensor: %d", result); + this->mark_failed(); + return; + } +#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT +#endif // USE_ESP32 +} + void InternalTemperatureSensor::dump_config() { LOG_SENSOR("", "Internal Temperature Sensor", this); } } // namespace internal_temperature diff --git a/esphome/components/internal_temperature/internal_temperature.h b/esphome/components/internal_temperature/internal_temperature.h index 0e46a69769..78e3bcef7d 100644 --- a/esphome/components/internal_temperature/internal_temperature.h +++ b/esphome/components/internal_temperature/internal_temperature.h @@ -8,6 +8,7 @@ namespace internal_temperature { class InternalTemperatureSensor : public sensor::Sensor, public PollingComponent { public: + void setup() override; void dump_config() override; void update() override; From d189cc1fbe406dc971bfcbb33e302bedd806cc05 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:39:01 +1100 Subject: [PATCH 082/282] [lvgl] Fix id config for the lvgl component (Bugfix) (#7731) Co-authored-by: clydeps <U5yx99dok9> --- esphome/components/lvgl/automation.py | 52 +++++++++++++-------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index 58e3dd808b..c26ae54892 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -1,5 +1,4 @@ -from collections.abc import Awaitable -from typing import Callable +from typing import Any, Callable from esphome import automation import esphome.codegen as cg @@ -23,7 +22,6 @@ from .lvcode import ( UPDATE_EVENT, LambdaContext, LocalVariable, - LvglComponent, ReturnStatement, add_line_marks, lv, @@ -58,7 +56,7 @@ focused_widgets = set() async def action_to_code( widgets: list[Widget], - action: Callable[[Widget], Awaitable[None]], + action: Callable[[Widget], Any], action_id, template_arg, args, @@ -159,14 +157,12 @@ async def obj_invalidate_to_code(config, action_id, template_arg, args): @automation.register_action( "lvgl.update", LvglAction, - DISP_BG_SCHEMA.extend( - { - cv.GenerateID(): cv.use_id(LvglComponent), - } - ).add_extra(cv.has_at_least_one_key(CONF_DISP_BG_COLOR, CONF_DISP_BG_IMAGE)), + DISP_BG_SCHEMA.extend(LVGL_SCHEMA).add_extra( + cv.has_at_least_one_key(CONF_DISP_BG_COLOR, CONF_DISP_BG_IMAGE) + ), ) async def lvgl_update_to_code(config, action_id, template_arg, args): - widgets = await get_widgets(config) + widgets = await get_widgets(config, CONF_LVGL_ID) w = widgets[0] disp = literal(f"{w.obj}->get_disp()") async with LambdaContext(LVGL_COMP_ARG, where=action_id) as context: @@ -179,32 +175,33 @@ async def lvgl_update_to_code(config, action_id, template_arg, args): @automation.register_action( "lvgl.pause", LvglAction, - { - cv.GenerateID(): cv.use_id(LvglComponent), - cv.Optional(CONF_SHOW_SNOW, default=False): lv_bool, - }, + LVGL_SCHEMA.extend( + { + cv.Optional(CONF_SHOW_SNOW, default=False): lv_bool, + } + ), ) async def pause_action_to_code(config, action_id, template_arg, args): + lv_comp = await cg.get_variable(config[CONF_LVGL_ID]) async with LambdaContext(LVGL_COMP_ARG) as context: add_line_marks(where=action_id) lv_add(lvgl_comp.set_paused(True, config[CONF_SHOW_SNOW])) var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) - await cg.register_parented(var, config[CONF_ID]) + await cg.register_parented(var, lv_comp) return var @automation.register_action( "lvgl.resume", LvglAction, - { - cv.GenerateID(): cv.use_id(LvglComponent), - }, + LVGL_SCHEMA, ) async def resume_action_to_code(config, action_id, template_arg, args): + lv_comp = await cg.get_variable(config[CONF_LVGL_ID]) async with LambdaContext(LVGL_COMP_ARG, where=action_id) as context: lv_add(lvgl_comp.set_paused(False, False)) var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) - await cg.register_parented(var, config[CONF_ID]) + await cg.register_parented(var, lv_comp) return var @@ -263,14 +260,15 @@ def focused_id(value): ObjUpdateAction, cv.Any( cv.maybe_simple_value( - { - cv.Optional(CONF_GROUP): cv.use_id(lv_group_t), - cv.Required(CONF_ACTION): cv.one_of( - "MARK", "RESTORE", "NEXT", "PREVIOUS", upper=True - ), - cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent), - cv.Optional(CONF_FREEZE, default=False): cv.boolean, - }, + LVGL_SCHEMA.extend( + { + cv.Optional(CONF_GROUP): cv.use_id(lv_group_t), + cv.Required(CONF_ACTION): cv.one_of( + "MARK", "RESTORE", "NEXT", "PREVIOUS", upper=True + ), + cv.Optional(CONF_FREEZE, default=False): cv.boolean, + } + ), key=CONF_ACTION, ), cv.maybe_simple_value( From 3f123d7542f850e7abe30d6196ec99356a9e343c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:42:36 +1300 Subject: [PATCH 083/282] Bump pypa/gh-action-pypi-publish from 1.11.0 to 1.12.2 (#7730) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82d7ae5ee8..096b00f0f1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,7 @@ jobs: pip3 install build python3 -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.11.0 + uses: pypa/gh-action-pypi-publish@v1.12.2 deploy-docker: name: Build ESPHome ${{ matrix.platform }} From 2f77d316905f688fef7d899dfedc004ba6a73a49 Mon Sep 17 00:00:00 2001 From: David Woodhouse <dwmw2@infradead.org> Date: Fri, 8 Nov 2024 03:38:13 +0000 Subject: [PATCH 084/282] OTA: Fix IPv6 and multiple address support (#7414) --- esphome/__main__.py | 4 +-- esphome/espota2.py | 69 +++++++++++++++++++------------------- esphome/helpers.py | 82 ++++++++++++++++++++++++++++++++++++--------- esphome/mqtt.py | 11 ++++-- esphome/zeroconf.py | 8 ++--- 5 files changed, 117 insertions(+), 57 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index cf2741dbdb..85ab3cc00c 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -38,7 +38,7 @@ from esphome.const import ( SECRETS_FILES, ) from esphome.core import CORE, EsphomeError, coroutine -from esphome.helpers import indent, is_ip_address, get_bool_env +from esphome.helpers import get_bool_env, indent, is_ip_address from esphome.log import Fore, color, setup_log from esphome.util import ( get_serial_ports, @@ -378,7 +378,7 @@ def show_logs(config, args, port): port = mqtt.get_esphome_device_ip( config, args.username, args.password, args.client_id - ) + )[0] from esphome.components.api.client import run_logs diff --git a/esphome/espota2.py b/esphome/espota2.py index 580536153a..94b845b246 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -10,7 +10,7 @@ import sys import time from esphome.core import EsphomeError -from esphome.helpers import is_ip_address, resolve_ip_address +from esphome.helpers import resolve_ip_address RESPONSE_OK = 0x00 RESPONSE_REQUEST_AUTH = 0x01 @@ -311,44 +311,45 @@ def perform_ota( def run_ota_impl_(remote_host, remote_port, password, filename): - if is_ip_address(remote_host): - _LOGGER.info("Connecting to %s", remote_host) - ip = remote_host - else: - _LOGGER.info("Resolving IP address of %s", remote_host) - try: - ip = resolve_ip_address(remote_host) - except EsphomeError as err: - _LOGGER.error( - "Error resolving IP address of %s. Is it connected to WiFi?", - remote_host, - ) - _LOGGER.error( - "(If this error persists, please set a static IP address: " - "https://esphome.io/components/wifi.html#manual-ips)" - ) - raise OTAError(err) from err - _LOGGER.info(" -> %s", ip) - - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(10.0) try: - sock.connect((ip, remote_port)) - except OSError as err: - sock.close() - _LOGGER.error("Connecting to %s:%s failed: %s", remote_host, remote_port, err) - return 1 + res = resolve_ip_address(remote_host, remote_port) + except EsphomeError as err: + _LOGGER.error( + "Error resolving IP address of %s. Is it connected to WiFi?", + remote_host, + ) + _LOGGER.error( + "(If this error persists, please set a static IP address: " + "https://esphome.io/components/wifi.html#manual-ips)" + ) + raise OTAError(err) from err - with open(filename, "rb") as file_handle: + for r in res: + af, socktype, _, _, sa = r + _LOGGER.info("Connecting to %s port %s...", sa[0], sa[1]) + sock = socket.socket(af, socktype) + sock.settimeout(10.0) try: - perform_ota(sock, password, file_handle, filename) - except OTAError as err: - _LOGGER.error(str(err)) - return 1 - finally: + sock.connect(sa) + except OSError as err: sock.close() + _LOGGER.error("Connecting to %s port %s failed: %s", sa[0], sa[1], err) + continue - return 0 + _LOGGER.info("Connected to %s", sa[0]) + with open(filename, "rb") as file_handle: + try: + perform_ota(sock, password, file_handle, filename) + except OTAError as err: + _LOGGER.error(str(err)) + return 1 + finally: + sock.close() + + return 0 + + _LOGGER.error("Connection failed.") + return 1 def run_ota(remote_host, remote_port, password, filename): diff --git a/esphome/helpers.py b/esphome/helpers.py index 2a7e5cd9b6..8aae43c2bb 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -1,5 +1,6 @@ import codecs from contextlib import suppress +import ipaddress import logging import os from pathlib import Path @@ -91,12 +92,8 @@ def mkdir_p(path): def is_ip_address(host): - parts = host.split(".") - if len(parts) != 4: - return False try: - for p in parts: - int(p) + ipaddress.ip_address(host) return True except ValueError: return False @@ -127,25 +124,80 @@ def _resolve_with_zeroconf(host): return info -def resolve_ip_address(host): +def addr_preference_(res): + # Trivial alternative to RFC6724 sorting. Put sane IPv6 first, then + # Legacy IP, then IPv6 link-local addresses without an actual link. + sa = res[4] + ip = ipaddress.ip_address(sa[0]) + if ip.version == 4: + return 2 + if ip.is_link_local and sa[3] == 0: + return 3 + return 1 + + +def resolve_ip_address(host, port): import socket from esphome.core import EsphomeError + # There are five cases here. The host argument could be one of: + # • a *list* of IP addresses discovered by MQTT, + # • a single IP address specified by the user, + # • a .local hostname to be resolved by mDNS, + # • a normal hostname to be resolved in DNS, or + # • A URL from which we should extract the hostname. + # + # In each of the first three cases, we end up with IP addresses in + # string form which need to be converted to a 5-tuple to be used + # for the socket connection attempt. The easiest way to construct + # those is to pass the IP address string to getaddrinfo(). Which, + # coincidentally, is how we do hostname lookups in the other cases + # too. So first build a list which contains either IP addresses or + # a single hostname, then call getaddrinfo() on each element of + # that list. + errs = [] + if isinstance(host, list): + addr_list = host + elif is_ip_address(host): + addr_list = [host] + else: + url = urlparse(host) + if url.scheme != "": + host = url.hostname - if host.endswith(".local"): + addr_list = [] + if host.endswith(".local"): + try: + _LOGGER.info("Resolving IP address of %s in mDNS", host) + addr_list = _resolve_with_zeroconf(host) + except EsphomeError as err: + errs.append(str(err)) + + # If not mDNS, or if mDNS failed, use normal DNS + if not addr_list: + addr_list = [host] + + # Now we have a list containing either IP addresses or a hostname + res = [] + for addr in addr_list: + if not is_ip_address(addr): + _LOGGER.info("Resolving IP address of %s", host) try: - return _resolve_with_zeroconf(host) - except EsphomeError as err: + r = socket.getaddrinfo(addr, port, proto=socket.IPPROTO_TCP) + except OSError as err: errs.append(str(err)) + raise EsphomeError( + f"Error resolving IP address: {', '.join(errs)}" + ) from err - try: - host_url = host if (urlparse(host).scheme != "") else "http://" + host - return socket.gethostbyname(urlparse(host_url).hostname) - except OSError as err: - errs.append(str(err)) - raise EsphomeError(f"Error resolving IP address: {', '.join(errs)}") from err + res = res + r + + # Zeroconf tends to give us link-local IPv6 addresses without specifying + # the link. Put those last in the list to be attempted. + res.sort(key=addr_preference_) + return res def get_bool_env(var, default=False): diff --git a/esphome/mqtt.py b/esphome/mqtt.py index d55fb0202d..2f90c49025 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -175,8 +175,15 @@ def get_esphome_device_ip( _LOGGER.Warn("Wrong device answer") return - if "ip" in data: - dev_ip = data["ip"] + dev_ip = [] + key = "ip" + n = 0 + while key in data: + dev_ip.append(data[key]) + n = n + 1 + key = "ip" + str(n) + + if dev_ip: client.disconnect() def on_connect(client, userdata, flags, return_code): diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index b3ee64e259..76049fa776 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -182,8 +182,8 @@ class EsphomeZeroconf(Zeroconf): if ( info.load_from_cache(self) or (timeout and info.request(self, timeout * 1000)) - ) and (addresses := info.ip_addresses_by_version(IPVersion.V4Only)): - return str(addresses[0]) + ) and (addresses := info.parsed_scoped_addresses(IPVersion.All)): + return addresses return None @@ -194,6 +194,6 @@ class AsyncEsphomeZeroconf(AsyncZeroconf): if ( info.load_from_cache(self.zeroconf) or (timeout and await info.async_request(self.zeroconf, timeout * 1000)) - ) and (addresses := info.ip_addresses_by_version(IPVersion.V4Only)): - return str(addresses[0]) + ) and (addresses := info.parsed_scoped_addresses(IPVersion.All)): + return addresses return None From 2ec17eed588f2e0ca2392337034981bdb83fcdcf Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sun, 27 Oct 2024 13:17:09 +1100 Subject: [PATCH 085/282] [rpi_dpi_rgb] Fix get_width and height (Bugfix) (#7675) Co-authored-by: clydeps <U5yx99dok9> --- .../components/rpi_dpi_rgb/rpi_dpi_rgb.cpp | 20 +++++++++++++++++++ esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h | 5 +++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp index 655b469b91..ba09171649 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -84,6 +84,26 @@ void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uin ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); } +int RpiDpiRgb::get_width() { + switch (this->rotation_) { + case display::DISPLAY_ROTATION_90_DEGREES: + case display::DISPLAY_ROTATION_270_DEGREES: + return this->get_height_internal(); + default: + return this->get_width_internal(); + } +} + +int RpiDpiRgb::get_height() { + switch (this->rotation_) { + case display::DISPLAY_ROTATION_90_DEGREES: + case display::DISPLAY_ROTATION_270_DEGREES: + return this->get_width_internal(); + default: + return this->get_height_internal(); + } +} + void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) { if (!this->get_clipping().inside(x, y)) return; // NOLINT diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h index 10f77a2624..7525040cd1 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h @@ -24,6 +24,7 @@ class RpiDpiRgb : public display::Display { void update() override { this->do_update_(); } void setup() override; void loop() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; void draw_pixel_at(int x, int y, Color color) override; @@ -44,8 +45,8 @@ class RpiDpiRgb : public display::Display { this->width_ = width; this->height_ = height; } - int get_width() override { return this->width_; } - int get_height() override { return this->height_; } + int get_width() override; + int get_height() override; void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; } void set_hsync_front_porch(uint16_t hsync_front_porch) { this->hsync_front_porch_ = hsync_front_porch; } void set_hsync_pulse_width(uint16_t hsync_pulse_width) { this->hsync_pulse_width_ = hsync_pulse_width; } From e85cbf26f881e91c4f02f4612d1b92d556ff56af Mon Sep 17 00:00:00 2001 From: Bonne Eggleston <bonne@exciton.com.au> Date: Mon, 28 Oct 2024 20:52:39 -0700 Subject: [PATCH 086/282] Fixes modbus timing error (#7674) --- esphome/components/modbus/modbus.cpp | 36 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index f8dd4c18b9..8544b50261 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -15,23 +15,33 @@ void Modbus::setup() { void Modbus::loop() { const uint32_t now = millis(); - if (now - this->last_modbus_byte_ > 50) { - this->rx_buffer_.clear(); - this->last_modbus_byte_ = now; - } - // stop blocking new send commands after send_wait_time_ ms regardless if a response has been received since then - if (now - this->last_send_ > send_wait_time_) { - waiting_for_response = 0; - } - while (this->available()) { uint8_t byte; this->read_byte(&byte); if (this->parse_modbus_byte_(byte)) { this->last_modbus_byte_ = now; } else { + size_t at = this->rx_buffer_.size(); + if (at > 0) { + ESP_LOGV(TAG, "Clearing buffer of %d bytes - parse failed", at); + this->rx_buffer_.clear(); + } + } + } + + if (now - this->last_modbus_byte_ > 50) { + size_t at = this->rx_buffer_.size(); + if (at > 0) { + ESP_LOGV(TAG, "Clearing buffer of %d bytes - timeout", at); this->rx_buffer_.clear(); } + + // stop blocking new send commands after sent_wait_time_ ms after response received + if (now - this->last_send_ > send_wait_time_) { + if (waiting_for_response > 0) + ESP_LOGV(TAG, "Stop waiting for response from %d", waiting_for_response); + waiting_for_response = 0; + } } } @@ -39,7 +49,7 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { size_t at = this->rx_buffer_.size(); this->rx_buffer_.push_back(byte); const uint8_t *raw = &this->rx_buffer_[0]; - ESP_LOGV(TAG, "Modbus received Byte %d (0X%x)", byte, byte); + ESP_LOGVV(TAG, "Modbus received Byte %d (0X%x)", byte, byte); // Byte 0: modbus address (match all) if (at == 0) return true; @@ -144,8 +154,10 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X! ", address); } - // return false to reset buffer - return false; + // reset buffer + ESP_LOGV(TAG, "Clearing buffer of %d bytes - parse succeeded", at); + this->rx_buffer_.clear(); + return true; } void Modbus::dump_config() { From 3a25eaca3f90345dd45ffc7e03734f8a6c0c3c45 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:32:18 +1100 Subject: [PATCH 087/282] [lvgl] Ensure images are configured before using them. (Bugfix) (#7721) --- esphome/components/lvgl/widgets/animimg.py | 7 ++++--- esphome/components/lvgl/widgets/img.py | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/lvgl/widgets/animimg.py b/esphome/components/lvgl/widgets/animimg.py index 3b20008c3d..8adea72ad3 100644 --- a/esphome/components/lvgl/widgets/animimg.py +++ b/esphome/components/lvgl/widgets/animimg.py @@ -60,9 +60,10 @@ class AnimimgType(WidgetType): lvgl_components_required.add(CONF_IMAGE) lvgl_components_required.add(CONF_ANIMIMG) if CONF_SRC in config: - for x in config[CONF_SRC]: - await cg.get_variable(x) - srcs = [await lv_image.process(x) for x in config[CONF_SRC]] + srcs = [ + await lv_image.process(await cg.get_variable(x)) + for x in config[CONF_SRC] + ] src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs) count = len(config[CONF_SRC]) lv.animimg_set_src(w.obj, src_id, count) diff --git a/esphome/components/lvgl/widgets/img.py b/esphome/components/lvgl/widgets/img.py index 59b2c97c63..931d0c0b5b 100644 --- a/esphome/components/lvgl/widgets/img.py +++ b/esphome/components/lvgl/widgets/img.py @@ -1,3 +1,4 @@ +import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ANGLE, CONF_MODE @@ -64,6 +65,7 @@ class ImgType(WidgetType): async def to_code(self, w: Widget, config): if src := config.get(CONF_SRC): + src = await cg.get_variable(src) lv.img_set_src(w.obj, await lv_image.process(src)) if (cf_angle := config.get(CONF_ANGLE)) is not None: pivot_x = config[CONF_PIVOT_X] From 551ea378824bc18df6523fa8ce810833f31b205e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:02:31 +1300 Subject: [PATCH 088/282] Bump version to 2024.10.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 032a4c79a0..c9decd4fd2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.10.2" +__version__ = "2024.10.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 335faf858baabee77a8104379e5fcba8da5ac58b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 11 Nov 2024 08:55:19 +1300 Subject: [PATCH 089/282] Fix dashboard ip resolving (#7747) --- esphome/dashboard/status/mdns.py | 7 +++---- esphome/dashboard/web_server.py | 4 ++-- esphome/zeroconf.py | 6 ++++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/esphome/dashboard/status/mdns.py b/esphome/dashboard/status/mdns.py index bd212bc563..9f6399ca8b 100644 --- a/esphome/dashboard/status/mdns.py +++ b/esphome/dashboard/status/mdns.py @@ -26,7 +26,7 @@ class MDNSStatus: self.host_mdns_state: dict[str, bool | None] = {} self._loop = asyncio.get_running_loop() - async def async_resolve_host(self, host_name: str) -> str | None: + async def async_resolve_host(self, host_name: str) -> list[str] | None: """Resolve a host name to an address in a thread-safe manner.""" if aiozc := self.aiozc: return await aiozc.async_resolve_host(host_name) @@ -50,13 +50,12 @@ class MDNSStatus: poll_names.setdefault(entry.name, set()).add(entry) elif (online := host_mdns_state.get(entry.name, SENTINEL)) != SENTINEL: entries.async_set_state(entry, bool_to_entry_state(online)) - if poll_names and self.aiozc: results = await asyncio.gather( *(self.aiozc.async_resolve_host(name) for name in poll_names) ) - for name, address in zip(poll_names, results): - result = bool(address) + for name, address_list in zip(poll_names, results): + result = bool(address_list) host_mdns_state[name] = result for entry in poll_names[name]: entries.async_set_state(entry, bool_to_entry_state(result)) diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 9aeece9aab..07f7f019f8 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -320,12 +320,12 @@ class EsphomePortCommandWebSocket(EsphomeCommandWebSocket): and "api" in entry.loaded_integrations ): if (mdns := dashboard.mdns_status) and ( - address := await mdns.async_resolve_host(entry.name) + address_list := await mdns.async_resolve_host(entry.name) ): # Use the IP address if available but only # if the API is loaded and the device is online # since MQTT logging will not work otherwise - port = address + port = address_list[0] elif ( entry.address and ( diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index 76049fa776..5a92a4ed7c 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -176,7 +176,7 @@ def _make_host_resolver(host: str) -> HostResolver: class EsphomeZeroconf(Zeroconf): - def resolve_host(self, host: str, timeout: float = 3.0) -> str | None: + def resolve_host(self, host: str, timeout: float = 3.0) -> list[str] | None: """Resolve a host name to an IP address.""" info = _make_host_resolver(host) if ( @@ -188,7 +188,9 @@ class EsphomeZeroconf(Zeroconf): class AsyncEsphomeZeroconf(AsyncZeroconf): - async def async_resolve_host(self, host: str, timeout: float = 3.0) -> str | None: + async def async_resolve_host( + self, host: str, timeout: float = 3.0 + ) -> list[str] | None: """Resolve a host name to an IP address.""" info = _make_host_resolver(host) if ( From 7c00c5db7020f09a6e8b3ffc3cb637fa2aa2fdc6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 11 Nov 2024 09:44:02 +1300 Subject: [PATCH 090/282] [docker] Bump curl, iputils-ping and libssl-dev (#7748) --- docker/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 44ee879a12..ed6ce083a8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -32,9 +32,9 @@ RUN \ python3-setuptools=66.1.1-1 \ python3-venv=3.11.2-1+b1 \ python3-wheel=0.38.4-2 \ - iputils-ping=3:20221126-1 \ + iputils-ping=3:20221126-1+deb12u1 \ git=1:2.39.5-0+deb12u1 \ - curl=7.88.1-10+deb12u7 \ + curl=7.88.1-10+deb12u8 \ openssh-client=1:9.2p1-2+deb12u3 \ python3-cffi=1.15.1-5 \ libcairo2=1.16.0-7 \ @@ -97,7 +97,7 @@ BUILD_DEPS=" zlib1g-dev=1:1.2.13.dfsg-1 libjpeg-dev=1:2.1.5-2 libfreetype-dev=2.12.1+dfsg-5+deb12u3 - libssl-dev=3.0.14-1~deb12u2 + libssl-dev=3.0.15-1~deb12u1 libffi-dev=3.4.4-1 libopenjp2-7=2.5.0-2 libtiff6=4.5.0-6+deb12u1 From c35240ca3207f7efef3cb0dcd146c32c5e33c6c7 Mon Sep 17 00:00:00 2001 From: Kyle Cascade <kyle@xkyle.com> Date: Sun, 10 Nov 2024 17:13:43 -0800 Subject: [PATCH 091/282] Remove the choice for MQTT logging if it is disabled (#7723) --- esphome/__main__.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 85ab3cc00c..86d529e1bf 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -20,6 +20,8 @@ from esphome.const import ( CONF_DEASSERT_RTS_DTR, CONF_DISABLED, CONF_ESPHOME, + CONF_LEVEL, + CONF_LOG_TOPIC, CONF_LOGGER, CONF_MDNS, CONF_MQTT, @@ -30,6 +32,7 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, CONF_PORT, CONF_SUBSTITUTIONS, + CONF_TOPIC, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -95,8 +98,12 @@ def choose_upload_log_host( options.append((f"Over The Air ({CORE.address})", CORE.address)) if default == "OTA": return CORE.address - if show_mqtt and CONF_MQTT in CORE.config: - options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT")) + if ( + show_mqtt + and (mqtt_config := CORE.config.get(CONF_MQTT)) + and mqtt_logging_enabled(mqtt_config) + ): + options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT")) if default == "OTA": return "MQTT" if default is not None: @@ -106,6 +113,17 @@ def choose_upload_log_host( return choose_prompt(options, purpose=purpose) +def mqtt_logging_enabled(mqtt_config): + log_topic = mqtt_config[CONF_LOG_TOPIC] + if log_topic is None: + return False + if CONF_TOPIC not in log_topic: + return False + if log_topic.get(CONF_LEVEL, None) == "NONE": + return False + return True + + def get_port_type(port): if port.startswith("/") or port.startswith("COM"): return "SERIAL" From d885d65c9bc7667c07afc1066f4bbc00efe09aff Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:18:05 +1100 Subject: [PATCH 092/282] [sensor] Make some values templatable (#7735) --- esphome/components/sensor/__init__.py | 26 +++++++++++++------ esphome/components/sensor/filter.cpp | 37 ++++++++++++++------------- esphome/components/sensor/filter.h | 19 +++++++------- tests/components/template/common.yaml | 19 ++++++++++++++ 4 files changed, 65 insertions(+), 36 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 27338b8608..9dbad27102 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -335,19 +335,28 @@ def sensor_schema( return SENSOR_SCHEMA.extend(schema) -@FILTER_REGISTRY.register("offset", OffsetFilter, cv.float_) +@FILTER_REGISTRY.register("offset", OffsetFilter, cv.templatable(cv.float_)) async def offset_filter_to_code(config, filter_id): - return cg.new_Pvariable(filter_id, config) + template_ = await cg.templatable(config, [], float) + return cg.new_Pvariable(filter_id, template_) -@FILTER_REGISTRY.register("multiply", MultiplyFilter, cv.float_) +@FILTER_REGISTRY.register("multiply", MultiplyFilter, cv.templatable(cv.float_)) async def multiply_filter_to_code(config, filter_id): - return cg.new_Pvariable(filter_id, config) + template_ = await cg.templatable(config, [], float) + return cg.new_Pvariable(filter_id, template_) -@FILTER_REGISTRY.register("filter_out", FilterOutValueFilter, cv.float_) +@FILTER_REGISTRY.register( + "filter_out", + FilterOutValueFilter, + cv.Any(cv.templatable(cv.float_), [cv.templatable(cv.float_)]), +) async def filter_out_filter_to_code(config, filter_id): - return cg.new_Pvariable(filter_id, config) + if not isinstance(config, list): + config = [config] + template_ = [await cg.templatable(x, [], float) for x in config] + return cg.new_Pvariable(filter_id, template_) QUANTILE_SCHEMA = cv.All( @@ -573,7 +582,7 @@ async def heartbeat_filter_to_code(config, filter_id): TIMEOUT_SCHEMA = cv.maybe_simple_value( { cv.Required(CONF_TIMEOUT): cv.positive_time_period_milliseconds, - cv.Optional(CONF_VALUE, default="nan"): cv.float_, + cv.Optional(CONF_VALUE, default="nan"): cv.templatable(cv.float_), }, key=CONF_TIMEOUT, ) @@ -581,7 +590,8 @@ TIMEOUT_SCHEMA = cv.maybe_simple_value( @FILTER_REGISTRY.register("timeout", TimeoutFilter, TIMEOUT_SCHEMA) async def timeout_filter_to_code(config, filter_id): - var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], config[CONF_VALUE]) + template_ = await cg.templatable(config[CONF_VALUE], [], float) + var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_) await cg.register_component(var, {}) return var diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index bcf1fc8269..0a8740dd5b 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -288,36 +288,36 @@ optional<float> LambdaFilter::new_value(float value) { } // OffsetFilter -OffsetFilter::OffsetFilter(float offset) : offset_(offset) {} +OffsetFilter::OffsetFilter(TemplatableValue<float> offset) : offset_(std::move(offset)) {} -optional<float> OffsetFilter::new_value(float value) { return value + this->offset_; } +optional<float> OffsetFilter::new_value(float value) { return value + this->offset_.value(); } // MultiplyFilter -MultiplyFilter::MultiplyFilter(float multiplier) : multiplier_(multiplier) {} +MultiplyFilter::MultiplyFilter(TemplatableValue<float> multiplier) : multiplier_(std::move(multiplier)) {} -optional<float> MultiplyFilter::new_value(float value) { return value * this->multiplier_; } +optional<float> MultiplyFilter::new_value(float value) { return value * this->multiplier_.value(); } // FilterOutValueFilter -FilterOutValueFilter::FilterOutValueFilter(float value_to_filter_out) : value_to_filter_out_(value_to_filter_out) {} +FilterOutValueFilter::FilterOutValueFilter(std::vector<TemplatableValue<float>> values_to_filter_out) + : values_to_filter_out_(std::move(values_to_filter_out)) {} optional<float> FilterOutValueFilter::new_value(float value) { - if (std::isnan(this->value_to_filter_out_)) { - if (std::isnan(value)) { - return {}; - } else { - return value; + int8_t accuracy = this->parent_->get_accuracy_decimals(); + float accuracy_mult = powf(10.0f, accuracy); + for (auto filter_value : this->values_to_filter_out_) { + if (std::isnan(filter_value.value())) { + if (std::isnan(value)) { + return {}; + } + continue; } - } else { - int8_t accuracy = this->parent_->get_accuracy_decimals(); - float accuracy_mult = powf(10.0f, accuracy); - float rounded_filter_out = roundf(accuracy_mult * this->value_to_filter_out_); + float rounded_filter_out = roundf(accuracy_mult * filter_value.value()); float rounded_value = roundf(accuracy_mult * value); if (rounded_filter_out == rounded_value) { return {}; - } else { - return value; } } + return value; } // ThrottleFilter @@ -383,11 +383,12 @@ void OrFilter::initialize(Sensor *parent, Filter *next) { // TimeoutFilter optional<float> TimeoutFilter::new_value(float value) { - this->set_timeout("timeout", this->time_period_, [this]() { this->output(this->value_); }); + this->set_timeout("timeout", this->time_period_, [this]() { this->output(this->value_.value()); }); return value; } -TimeoutFilter::TimeoutFilter(uint32_t time_period, float new_value) : time_period_(time_period), value_(new_value) {} +TimeoutFilter::TimeoutFilter(uint32_t time_period, TemplatableValue<float> new_value) + : time_period_(time_period), value_(std::move(new_value)) {} float TimeoutFilter::get_setup_priority() const { return setup_priority::HARDWARE; } // DebounceFilter diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 92b1d8d240..86586b458d 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -5,6 +5,7 @@ #include <vector> #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/automation.h" namespace esphome { namespace sensor { @@ -273,34 +274,33 @@ class LambdaFilter : public Filter { /// A simple filter that adds `offset` to each value it receives. class OffsetFilter : public Filter { public: - explicit OffsetFilter(float offset); + explicit OffsetFilter(TemplatableValue<float> offset); optional<float> new_value(float value) override; protected: - float offset_; + TemplatableValue<float> offset_; }; /// A simple filter that multiplies to each value it receives by `multiplier`. class MultiplyFilter : public Filter { public: - explicit MultiplyFilter(float multiplier); - + explicit MultiplyFilter(TemplatableValue<float> multiplier); optional<float> new_value(float value) override; protected: - float multiplier_; + TemplatableValue<float> multiplier_; }; /// A simple filter that only forwards the filter chain if it doesn't receive `value_to_filter_out`. class FilterOutValueFilter : public Filter { public: - explicit FilterOutValueFilter(float value_to_filter_out); + explicit FilterOutValueFilter(std::vector<TemplatableValue<float>> values_to_filter_out); optional<float> new_value(float value) override; protected: - float value_to_filter_out_; + std::vector<TemplatableValue<float>> values_to_filter_out_; }; class ThrottleFilter : public Filter { @@ -316,8 +316,7 @@ class ThrottleFilter : public Filter { class TimeoutFilter : public Filter, public Component { public: - explicit TimeoutFilter(uint32_t time_period, float new_value); - void set_value(float new_value) { this->value_ = new_value; } + explicit TimeoutFilter(uint32_t time_period, TemplatableValue<float> new_value); optional<float> new_value(float value) override; @@ -325,7 +324,7 @@ class TimeoutFilter : public Filter, public Component { protected: uint32_t time_period_; - float value_; + TemplatableValue<float> value_; }; class DebounceFilter : public Filter, public Component { diff --git a/tests/components/template/common.yaml b/tests/components/template/common.yaml index 3565926933..79201fbe07 100644 --- a/tests/components/template/common.yaml +++ b/tests/components/template/common.yaml @@ -9,6 +9,25 @@ sensor: return 0.0; } update_interval: 60s + filters: + - offset: 10 + - multiply: 1 + - offset: !lambda return 10; + - multiply: !lambda return 2; + - filter_out: + - 10 + - 20 + - !lambda return 10; + - filter_out: 10 + - filter_out: !lambda return NAN; + - timeout: + timeout: 10s + value: !lambda return 10; + - timeout: + timeout: 1h + value: 20.0 + - timeout: + timeout: 1d esphome: on_boot: From ffee2f0e8878edf6c5feafbd643a7ef6bcc3fc7a Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:07:48 +1100 Subject: [PATCH 093/282] [lvgl] Implement keypads (#7719) --- esphome/components/lvgl/__init__.py | 24 +++++++- esphome/components/lvgl/defines.py | 1 + esphome/components/lvgl/encoders.py | 19 +++--- esphome/components/lvgl/keypads.py | 77 +++++++++++++++++++++++++ esphome/components/lvgl/lvgl_esphome.h | 11 +--- esphome/components/lvgl/types.py | 1 + tests/components/lvgl/lvgl-package.yaml | 10 ++++ 7 files changed, 123 insertions(+), 20 deletions(-) create mode 100644 esphome/components/lvgl/keypads.py diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 7476c0a09c..d03adc9624 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -7,6 +7,7 @@ import esphome.config_validation as cv from esphome.const import ( CONF_AUTO_CLEAR_ENABLED, CONF_BUFFER_SIZE, + CONF_GROUP, CONF_ID, CONF_LAMBDA, CONF_ON_IDLE, @@ -23,9 +24,15 @@ from esphome.helpers import write_file_if_changed from . import defines as df, helpers, lv_validation as lvalid from .automation import disp_update, focused_widgets, update_to_code from .defines import add_define -from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code +from .encoders import ( + ENCODERS_CONFIG, + encoders_to_code, + get_default_group, + initial_focus_to_code, +) from .gradient import GRADIENT_SCHEMA, gradients_to_code from .hello_world import get_hello_world +from .keypads import KEYPADS_CONFIG, keypads_to_code from .lv_validation import lv_bool, lv_images_used from .lvcode import LvContext, LvglComponent, lvgl_static from .schemas import ( @@ -158,6 +165,13 @@ def multi_conf_validate(configs: list[dict]): display_list = [disp for disps in displays for disp in disps] if len(display_list) != len(set(display_list)): raise cv.Invalid("A display ID may be used in only one LVGL instance") + for config in configs: + for item in (df.CONF_ENCODERS, df.CONF_KEYPADS): + for enc in config.get(item, ()): + if CONF_GROUP not in enc: + raise cv.Invalid( + f"'{item}' must have an explicit group set when using multiple LVGL instances" + ) base_config = configs[0] for config in configs[1:]: for item in ( @@ -173,7 +187,8 @@ def multi_conf_validate(configs: list[dict]): def final_validation(configs): - multi_conf_validate(configs) + if len(configs) != 1: + multi_conf_validate(configs) global_config = full_config.get() for config in configs: if pages := config.get(CONF_PAGES): @@ -275,6 +290,7 @@ async def to_code(configs): else: add_define("LV_FONT_DEFAULT", await lvalid.lv_font.process(default_font)) cg.add(lvgl_static.esphome_lvgl_init()) + default_group = get_default_group(config_0) for config in configs: frac = config[CONF_BUFFER_SIZE] @@ -303,7 +319,8 @@ async def to_code(configs): lv_scr_act = get_scr_act(lv_component) async with LvContext(): await touchscreens_to_code(lv_component, config) - await encoders_to_code(lv_component, config) + await encoders_to_code(lv_component, config, default_group) + await keypads_to_code(lv_component, config, default_group) await theme_to_code(config) await styles_to_code(config) await gradients_to_code(config) @@ -430,6 +447,7 @@ LVGL_SCHEMA = ( cv.Optional(df.CONF_GRADIENTS): GRADIENT_SCHEMA, cv.Optional(df.CONF_TOUCHSCREENS, default=None): touchscreen_schema, cv.Optional(df.CONF_ENCODERS, default=None): ENCODERS_CONFIG, + cv.Optional(df.CONF_KEYPADS, default=None): KEYPADS_CONFIG, cv.GenerateID(df.CONF_DEFAULT_GROUP): cv.declare_id(lv_group_t), cv.Optional(df.CONF_RESUME_ON_INPUT, default=True): cv.boolean, } diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 4d48028611..ea345fa55c 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -438,6 +438,7 @@ CONF_HEADER_MODE = "header_mode" CONF_HOME = "home" CONF_INITIAL_FOCUS = "initial_focus" CONF_KEY_CODE = "key_code" +CONF_KEYPADS = "keypads" CONF_LAYOUT = "layout" CONF_LEFT_BUTTON = "left_button" CONF_LINE_WIDTH = "line_width" diff --git a/esphome/components/lvgl/encoders.py b/esphome/components/lvgl/encoders.py index 81bcda95b4..952572df43 100644 --- a/esphome/components/lvgl/encoders.py +++ b/esphome/components/lvgl/encoders.py @@ -17,7 +17,7 @@ from .defines import ( from .helpers import lvgl_components_required, requires_component from .lvcode import lv, lv_add, lv_assign, lv_expr, lv_Pvariable from .schemas import ENCODER_SCHEMA -from .types import lv_group_t, lv_indev_type_t +from .types import lv_group_t, lv_indev_type_t, lv_key_t ENCODERS_CONFIG = cv.ensure_list( ENCODER_SCHEMA.extend( @@ -39,10 +39,13 @@ ENCODERS_CONFIG = cv.ensure_list( ) -async def encoders_to_code(var, config): - default_group = lv_Pvariable(lv_group_t, config[CONF_DEFAULT_GROUP]) - lv_assign(default_group, lv_expr.group_create()) - lv.group_set_default(default_group) +def get_default_group(config): + default_group = cg.Pvariable(config[CONF_DEFAULT_GROUP], lv_expr.group_create()) + cg.add(lv.group_set_default(default_group)) + return default_group + + +async def encoders_to_code(var, config, default_group): for enc_conf in config[CONF_ENCODERS]: lvgl_components_required.add("KEY_LISTENER") lpt = enc_conf[CONF_LONG_PRESS_TIME].total_milliseconds @@ -54,14 +57,14 @@ async def encoders_to_code(var, config): if sensor_config := enc_conf.get(CONF_SENSOR): if isinstance(sensor_config, dict): b_sensor = await cg.get_variable(sensor_config[CONF_LEFT_BUTTON]) - cg.add(listener.set_left_button(b_sensor)) + cg.add(listener.add_button(b_sensor, lv_key_t.LV_KEY_LEFT)) b_sensor = await cg.get_variable(sensor_config[CONF_RIGHT_BUTTON]) - cg.add(listener.set_right_button(b_sensor)) + cg.add(listener.add_button(b_sensor, lv_key_t.LV_KEY_RIGHT)) else: sensor_config = await cg.get_variable(sensor_config) lv_add(listener.set_sensor(sensor_config)) b_sensor = await cg.get_variable(enc_conf[CONF_ENTER_BUTTON]) - cg.add(listener.set_enter_button(b_sensor)) + cg.add(listener.add_button(b_sensor, lv_key_t.LV_KEY_ENTER)) if group := enc_conf.get(CONF_GROUP): group = lv_Pvariable(lv_group_t, group) lv_assign(group, lv_expr.group_create()) diff --git a/esphome/components/lvgl/keypads.py b/esphome/components/lvgl/keypads.py new file mode 100644 index 0000000000..5e2953d57f --- /dev/null +++ b/esphome/components/lvgl/keypads.py @@ -0,0 +1,77 @@ +import esphome.codegen as cg +from esphome.components.binary_sensor import BinarySensor +import esphome.config_validation as cv +from esphome.const import CONF_GROUP, CONF_ID + +from .defines import ( + CONF_ENCODERS, + CONF_INITIAL_FOCUS, + CONF_KEYPADS, + CONF_LONG_PRESS_REPEAT_TIME, + CONF_LONG_PRESS_TIME, + literal, +) +from .helpers import lvgl_components_required +from .lvcode import lv, lv_assign, lv_expr, lv_Pvariable +from .schemas import ENCODER_SCHEMA +from .types import lv_group_t, lv_indev_type_t + +KEYPAD_KEYS = ( + "up", + "down", + "right", + "left", + "esc", + "del", + "backspace", + "enter", + "next", + "prev", + "home", + "end", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "#", + "*", +) + +KEYPADS_CONFIG = cv.ensure_list( + ENCODER_SCHEMA.extend( + {cv.Optional(key): cv.use_id(BinarySensor) for key in KEYPAD_KEYS} + ) +) + + +async def keypads_to_code(var, config, default_group): + for enc_conf in config[CONF_KEYPADS]: + lvgl_components_required.add("KEY_LISTENER") + lpt = enc_conf[CONF_LONG_PRESS_TIME].total_milliseconds + lprt = enc_conf[CONF_LONG_PRESS_REPEAT_TIME].total_milliseconds + listener = cg.new_Pvariable( + enc_conf[CONF_ID], lv_indev_type_t.LV_INDEV_TYPE_KEYPAD, lpt, lprt + ) + await cg.register_parented(listener, var) + for key in [x for x in enc_conf if x in KEYPAD_KEYS]: + b_sensor = await cg.get_variable(enc_conf[key]) + cg.add(listener.add_button(b_sensor, literal(f"LV_KEY_{key.upper()}"))) + if group := enc_conf.get(CONF_GROUP): + group = lv_Pvariable(lv_group_t, group) + lv_assign(group, lv_expr.group_create()) + else: + group = default_group + lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group) + + +async def initial_focus_to_code(config): + for enc_conf in config[CONF_ENCODERS]: + if default_focus := enc_conf.get(CONF_INITIAL_FOCUS): + obj = await cg.get_variable(default_focus) + lv.group_focus_obj(obj) diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index dae07d5153..208cb1cbd5 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -256,15 +256,8 @@ class LVEncoderListener : public Parented<LvglComponent> { LVEncoderListener(lv_indev_type_t type, uint16_t lpt, uint16_t lprt); #ifdef USE_BINARY_SENSOR - void set_left_button(binary_sensor::BinarySensor *left_button) { - left_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_LEFT, state); }); - } - void set_right_button(binary_sensor::BinarySensor *right_button) { - right_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_RIGHT, state); }); - } - - void set_enter_button(binary_sensor::BinarySensor *enter_button) { - enter_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_ENTER, state); }); + void add_button(binary_sensor::BinarySensor *button, lv_key_t key) { + button->add_on_state_callback([this, key](bool state) { this->event(key, state); }); } #endif diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index b504f24674..40e69119f0 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -40,6 +40,7 @@ void_ptr = cg.void.operator("ptr") lv_coord_t = cg.global_ns.namespace("lv_coord_t") lv_event_code_t = cg.global_ns.enum("lv_event_code_t") lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t") +lv_key_t = cg.global_ns.enum("lv_key_t") FontEngine = lvgl_ns.class_("FontEngine") IdleTrigger = lvgl_ns.class_("IdleTrigger", automation.Trigger.template()) PauseTrigger = lvgl_ns.class_("PauseTrigger", automation.Trigger.template()) diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 9bfbb5fc95..db0443b3bb 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -11,6 +11,12 @@ substitutions: check: "\U000F012C" arrow_down: "\U000F004B" +binary_sensor: + - id: enter_sensor + platform: template + - id: left_sensor + platform: template + lvgl: log_level: debug resume_on_input: true @@ -93,6 +99,10 @@ lvgl: - touchscreen_id: tft_touch long_press_repeat_time: 200ms long_press_time: 500ms + keypads: + - initial_focus: button_button + enter: enter_sensor + next: left_sensor msgboxes: - id: message_box From a2dccc4730566536b700985310016fe1299ae371 Mon Sep 17 00:00:00 2001 From: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com> Date: Mon, 11 Nov 2024 05:14:01 +0100 Subject: [PATCH 094/282] [midea] Add temperature validation in do_follow_me method (bugfix) (#7736) --- esphome/components/midea/air_conditioner.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp index b5bf43b64f..a823680d03 100644 --- a/esphome/components/midea/air_conditioner.cpp +++ b/esphome/components/midea/air_conditioner.cpp @@ -3,6 +3,8 @@ #include "esphome/core/log.h" #include "air_conditioner.h" #include "ac_adapter.h" +#include <cmath> +#include <cstdint> namespace esphome { namespace midea { @@ -121,7 +123,21 @@ void AirConditioner::dump_config() { void AirConditioner::do_follow_me(float temperature, bool beeper) { #ifdef USE_REMOTE_TRANSMITTER - IrFollowMeData data(static_cast<uint8_t>(lroundf(temperature)), beeper); + // Check if temperature is finite (not NaN or infinite) + if (!std::isfinite(temperature)) { + ESP_LOGW(Constants::TAG, "Follow me action requires a finite temperature, got: %f", temperature); + return; + } + + // Round and convert temperature to long, then clamp and convert it to uint8_t + uint8_t temp_uint8 = + static_cast<uint8_t>(std::max(0L, std::min(static_cast<long>(UINT8_MAX), std::lroundf(temperature)))); + + ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %f °C, rounded to: %u °C", temperature, + temp_uint8); + + // Create and transmit the data + IrFollowMeData data(temp_uint8, beeper); this->transmitter_.transmit(data); #else ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); From 58d028ac1375511362fac3bf524b86567ef584b0 Mon Sep 17 00:00:00 2001 From: Oleg Tarasov <me@olegtarasov.email> Date: Tue, 12 Nov 2024 06:19:42 +0300 Subject: [PATCH 095/282] Add OpenTherm component (part 3: rest of the sensors) (#7676) Co-authored-by: FreeBear <freebear@tuxcnc.org> Co-authored-by: FreeBear-nc <67865163+FreeBear-nc@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/opentherm/__init__.py | 30 +- .../opentherm/binary_sensor/__init__.py | 33 ++ esphome/components/opentherm/const.py | 6 + esphome/components/opentherm/generate.py | 2 + esphome/components/opentherm/hub.cpp | 42 +- esphome/components/opentherm/hub.h | 52 ++- esphome/components/opentherm/input.h | 18 + esphome/components/opentherm/input.py | 51 +++ .../components/opentherm/number/__init__.py | 74 ++++ .../components/opentherm/number/number.cpp | 40 ++ esphome/components/opentherm/number/number.h | 31 ++ esphome/components/opentherm/opentherm.h | 33 ++ .../components/opentherm/opentherm_macros.h | 60 +++ .../components/opentherm/output/__init__.py | 47 +++ .../components/opentherm/output/output.cpp | 18 + esphome/components/opentherm/output/output.h | 33 ++ esphome/components/opentherm/schema.py | 378 +++++++++++++++++- .../components/opentherm/sensor/__init__.py | 16 + .../components/opentherm/switch/__init__.py | 43 ++ .../components/opentherm/switch/switch.cpp | 28 ++ esphome/components/opentherm/switch/switch.h | 20 + tests/components/opentherm/common.yaml | 84 ++++ 22 files changed, 1128 insertions(+), 11 deletions(-) create mode 100644 esphome/components/opentherm/binary_sensor/__init__.py create mode 100644 esphome/components/opentherm/input.h create mode 100644 esphome/components/opentherm/input.py create mode 100644 esphome/components/opentherm/number/__init__.py create mode 100644 esphome/components/opentherm/number/number.cpp create mode 100644 esphome/components/opentherm/number/number.h create mode 100644 esphome/components/opentherm/output/__init__.py create mode 100644 esphome/components/opentherm/output/output.cpp create mode 100644 esphome/components/opentherm/output/output.h create mode 100644 esphome/components/opentherm/switch/__init__.py create mode 100644 esphome/components/opentherm/switch/switch.cpp create mode 100644 esphome/components/opentherm/switch/switch.h diff --git a/esphome/components/opentherm/__init__.py b/esphome/components/opentherm/__init__.py index ee19818a29..81cd78af08 100644 --- a/esphome/components/opentherm/__init__.py +++ b/esphome/components/opentherm/__init__.py @@ -3,8 +3,9 @@ from typing import Any import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins +from esphome.components import sensor from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266 -from . import generate +from . import const, schema, validate, generate CODEOWNERS = ["@olegtarasov"] MULTI_CONF = True @@ -19,6 +20,7 @@ CONF_CH2_ACTIVE = "ch2_active" CONF_SUMMER_MODE_ACTIVE = "summer_mode_active" CONF_DHW_BLOCK = "dhw_block" CONF_SYNC_MODE = "sync_mode" +CONF_OPENTHERM_VERSION = "opentherm_version" CONFIG_SCHEMA = cv.All( cv.Schema( @@ -34,8 +36,15 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean, cv.Optional(CONF_DHW_BLOCK, False): cv.boolean, cv.Optional(CONF_SYNC_MODE, False): cv.boolean, + cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float, } - ).extend(cv.COMPONENT_SCHEMA), + ) + .extend( + validate.create_entities_schema( + schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor)) + ) + ) + .extend(cv.COMPONENT_SCHEMA), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), ) @@ -52,8 +61,23 @@ async def to_code(config: dict[str, Any]) -> None: cg.add(var.set_out_pin(out_pin)) non_sensors = {CONF_ID, CONF_IN_PIN, CONF_OUT_PIN} + input_sensors = [] for key, value in config.items(): if key in non_sensors: continue + if key in schema.INPUTS: + input_sensor = await cg.get_variable(value) + cg.add( + getattr(var, f"set_{key}_{const.INPUT_SENSOR.lower()}")(input_sensor) + ) + input_sensors.append(key) + else: + cg.add(getattr(var, f"set_{key}")(value)) - cg.add(getattr(var, f"set_{key}")(value)) + if len(input_sensors) > 0: + generate.define_has_component(const.INPUT_SENSOR, input_sensors) + generate.define_message_handler( + const.INPUT_SENSOR, input_sensors, schema.INPUTS + ) + generate.define_readers(const.INPUT_SENSOR, input_sensors) + generate.add_messages(var, input_sensors, schema.INPUTS) diff --git a/esphome/components/opentherm/binary_sensor/__init__.py b/esphome/components/opentherm/binary_sensor/__init__.py new file mode 100644 index 0000000000..643734f90c --- /dev/null +++ b/esphome/components/opentherm/binary_sensor/__init__.py @@ -0,0 +1,33 @@ +from typing import Any + +import esphome.config_validation as cv +from esphome.components import binary_sensor +from .. import const, schema, validate, generate + +DEPENDENCIES = [const.OPENTHERM] +COMPONENT_TYPE = const.BINARY_SENSOR + + +def get_entity_validation_schema(entity: schema.BinarySensorSchema) -> cv.Schema: + return binary_sensor.binary_sensor_schema( + device_class=( + entity.device_class + or binary_sensor._UNDEF # pylint: disable=protected-access + ), + icon=(entity.icon or binary_sensor._UNDEF), # pylint: disable=protected-access + ) + + +CONFIG_SCHEMA = validate.create_component_schema( + schema.BINARY_SENSORS, get_entity_validation_schema +) + + +async def to_code(config: dict[str, Any]) -> None: + await generate.component_to_code( + COMPONENT_TYPE, + schema.BINARY_SENSORS, + binary_sensor.BinarySensor, + generate.create_only_conf(binary_sensor.new_binary_sensor), + config, + ) diff --git a/esphome/components/opentherm/const.py b/esphome/components/opentherm/const.py index 1f997c5d9c..a113331585 100644 --- a/esphome/components/opentherm/const.py +++ b/esphome/components/opentherm/const.py @@ -1,5 +1,11 @@ OPENTHERM = "opentherm" CONF_OPENTHERM_ID = "opentherm_id" +CONF_DATA_TYPE = "data_type" SENSOR = "sensor" +BINARY_SENSOR = "binary_sensor" +SWITCH = "switch" +NUMBER = "number" +OUTPUT = "output" +INPUT_SENSOR = "input_sensor" diff --git a/esphome/components/opentherm/generate.py b/esphome/components/opentherm/generate.py index 6a97835a57..9716cab093 100644 --- a/esphome/components/opentherm/generate.py +++ b/esphome/components/opentherm/generate.py @@ -130,6 +130,8 @@ async def component_to_code( id = conf[CONF_ID] if id and id.type == type: entity = await create(conf, key, hub) + if const.CONF_DATA_TYPE in conf: + schemas[key].message_data = conf[const.CONF_DATA_TYPE] cg.add(getattr(hub, f"set_{key}_{component_type.lower()}")(entity)) keys.append(key) diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index 770bbd82b7..432036d58d 100644 --- a/esphome/components/opentherm/hub.cpp +++ b/esphome/components/opentherm/hub.cpp @@ -29,6 +29,8 @@ uint8_t parse_u8_hb(OpenthermData &data) { return data.valueHB; } int8_t parse_s8_lb(OpenthermData &data) { return (int8_t) data.valueLB; } int8_t parse_s8_hb(OpenthermData &data) { return (int8_t) data.valueHB; } uint16_t parse_u16(OpenthermData &data) { return data.u16(); } +uint16_t parse_u8_lb_60(OpenthermData &data) { return data.valueLB * 60; } +uint16_t parse_u8_hb_60(OpenthermData &data) { return data.valueHB * 60; } int16_t parse_s16(OpenthermData &data) { return data.s16(); } float parse_f88(OpenthermData &data) { return data.f88(); } @@ -87,13 +89,40 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const { return data; } + // Another special case is OpenTherm version number which is configured at hub level as a constant + if (request_id == MessageId::OT_VERSION_CONTROLLER) { + data.type = MessageType::WRITE_DATA; + data.id = MessageId::OT_VERSION_CONTROLLER; + data.f88(this->opentherm_version_); + + return data; + } + // Disable incomplete switch statement warnings, because the cases in each // switch are generated based on the configured sensors and inputs. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch" - switch (request_id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) } + // Next, we start with the write requests from switches and other inputs, + // because we would want to write that data if it is available, rather than + // request a read for that type (in the case that both read and write are + // supported). + switch (request_id) { + OPENTHERM_SWITCH_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, , + OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) + OPENTHERM_NUMBER_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, , + OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) + OPENTHERM_OUTPUT_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, , + OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) + OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, , + OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) + } + // Finally, handle the simple read requests, which only change with the message id. + switch (request_id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) } + switch (request_id) { + OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) + } #pragma GCC diagnostic pop // And if we get here, a message was requested which somehow wasn't handled. @@ -115,6 +144,10 @@ void OpenthermHub::process_response(OpenthermData &data) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_RESPONSE_MESSAGE, OPENTHERM_MESSAGE_RESPONSE_ENTITY, , OPENTHERM_MESSAGE_RESPONSE_POSTSCRIPT, ) } + switch (data.id) { + OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_RESPONSE_MESSAGE, OPENTHERM_MESSAGE_RESPONSE_ENTITY, , + OPENTHERM_MESSAGE_RESPONSE_POSTSCRIPT, ) + } } void OpenthermHub::setup() { @@ -131,6 +164,13 @@ void OpenthermHub::setup() { // good practice anyway. this->add_repeating_message(MessageId::STATUS); + // Also ensure that we start communication with the STATUS message + this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::STATUS); + + if (this->opentherm_version_ > 0.0f) { + this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::OT_VERSION_CONTROLLER); + } + this->current_message_iterator_ = this->initial_messages_.begin(); } diff --git a/esphome/components/opentherm/hub.h b/esphome/components/opentherm/hub.h index 3b90cdf427..1f536653e8 100644 --- a/esphome/components/opentherm/hub.h +++ b/esphome/components/opentherm/hub.h @@ -4,6 +4,7 @@ #include "esphome/core/hal.h" #include "esphome/core/component.h" #include "esphome/core/log.h" +#include <vector> #include "opentherm.h" @@ -11,6 +12,22 @@ #include "esphome/components/sensor/sensor.h" #endif +#ifdef OPENTHERM_USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif + +#ifdef OPENTHERM_USE_SWITCH +#include "esphome/components/opentherm/switch/switch.h" +#endif + +#ifdef OPENTHERM_USE_OUTPUT +#include "esphome/components/opentherm/output/output.h" +#endif + +#ifdef OPENTHERM_USE_NUMBER +#include "esphome/components/opentherm/number/number.h" +#endif + #include <memory> #include <unordered_map> #include <unordered_set> @@ -31,15 +48,25 @@ class OpenthermHub : public Component { OPENTHERM_SENSOR_LIST(OPENTHERM_DECLARE_SENSOR, ) + OPENTHERM_BINARY_SENSOR_LIST(OPENTHERM_DECLARE_BINARY_SENSOR, ) + + OPENTHERM_SWITCH_LIST(OPENTHERM_DECLARE_SWITCH, ) + + OPENTHERM_NUMBER_LIST(OPENTHERM_DECLARE_NUMBER, ) + + OPENTHERM_OUTPUT_LIST(OPENTHERM_DECLARE_OUTPUT, ) + + OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, ) + // The set of initial messages to send on starting communication with the boiler - std::unordered_set<MessageId> initial_messages_; + std::vector<MessageId> initial_messages_; // and the repeating messages which are sent repeatedly to update various sensors // and boiler parameters (like the setpoint). - std::unordered_set<MessageId> repeating_messages_; + std::vector<MessageId> repeating_messages_; // Indicates if we are still working on the initial requests or not bool sending_initial_ = true; // Index for the current request in one of the _requests sets. - std::unordered_set<MessageId>::const_iterator current_message_iterator_; + std::vector<MessageId>::const_iterator current_message_iterator_; uint32_t last_conversation_start_ = 0; uint32_t last_conversation_end_ = 0; @@ -51,6 +78,8 @@ class OpenthermHub : public Component { // Very likely to happen while using Dallas temperature sensors. bool sync_mode_ = false; + float opentherm_version_ = 0.0f; + // Create OpenTherm messages based on the message id OpenthermData build_request_(MessageId request_id) const; void handle_protocol_write_error_(); @@ -88,13 +117,23 @@ class OpenthermHub : public Component { OPENTHERM_SENSOR_LIST(OPENTHERM_SET_SENSOR, ) - // Add a request to the set of initial requests - void add_initial_message(MessageId message_id) { this->initial_messages_.insert(message_id); } + OPENTHERM_BINARY_SENSOR_LIST(OPENTHERM_SET_BINARY_SENSOR, ) + + OPENTHERM_SWITCH_LIST(OPENTHERM_SET_SWITCH, ) + + OPENTHERM_NUMBER_LIST(OPENTHERM_SET_NUMBER, ) + + OPENTHERM_OUTPUT_LIST(OPENTHERM_SET_OUTPUT, ) + + OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, ) + + // Add a request to the vector of initial requests + void add_initial_message(MessageId message_id) { this->initial_messages_.push_back(message_id); } // Add a request to the set of repeating requests. Note that a large number of repeating // requests will slow down communication with the boiler. Each request may take up to 1 second, // so with all sensors enabled, it may take about half a minute before a change in setpoint // will be processed. - void add_repeating_message(MessageId message_id) { this->repeating_messages_.insert(message_id); } + void add_repeating_message(MessageId message_id) { this->repeating_messages_.push_back(message_id); } // There are seven status variables, which can either be set as a simple variable, // or using a switch. ch_enable and dhw_enable default to true, the others to false. @@ -110,6 +149,7 @@ class OpenthermHub : public Component { void set_summer_mode_active(bool value) { this->summer_mode_active = value; } void set_dhw_block(bool value) { this->dhw_block = value; } void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; } + void set_opentherm_version(float value) { this->opentherm_version_ = value; } float get_setup_priority() const override { return setup_priority::HARDWARE; } diff --git a/esphome/components/opentherm/input.h b/esphome/components/opentherm/input.h new file mode 100644 index 0000000000..3567138792 --- /dev/null +++ b/esphome/components/opentherm/input.h @@ -0,0 +1,18 @@ +#pragma once + +namespace esphome { +namespace opentherm { + +class OpenthermInput { + public: + bool auto_min_value, auto_max_value; + + virtual void set_min_value(float min_value) = 0; + virtual void set_max_value(float max_value) = 0; + + virtual void set_auto_min_value(bool auto_min_value) { this->auto_min_value = auto_min_value; } + virtual void set_auto_max_value(bool auto_max_value) { this->auto_max_value = auto_max_value; } +}; + +} // namespace opentherm +} // namespace esphome diff --git a/esphome/components/opentherm/input.py b/esphome/components/opentherm/input.py new file mode 100644 index 0000000000..7897747be1 --- /dev/null +++ b/esphome/components/opentherm/input.py @@ -0,0 +1,51 @@ +from typing import Any + +import esphome.codegen as cg +import esphome.config_validation as cv +from . import schema, generate + +CONF_min_value = "min_value" +CONF_max_value = "max_value" +CONF_auto_min_value = "auto_min_value" +CONF_auto_max_value = "auto_max_value" +CONF_step = "step" + +OpenthermInput = generate.opentherm_ns.class_("OpenthermInput") + + +def validate_min_value_less_than_max_value(conf): + if ( + CONF_min_value in conf + and CONF_max_value in conf + and conf[CONF_min_value] > conf[CONF_max_value] + ): + raise cv.Invalid(f"{CONF_min_value} must be less than {CONF_max_value}") + return conf + + +def input_schema(entity: schema.InputSchema) -> cv.Schema: + result = cv.Schema( + { + cv.Optional(CONF_min_value, entity.range[0]): cv.float_range( + entity.range[0], entity.range[1] + ), + cv.Optional(CONF_max_value, entity.range[1]): cv.float_range( + entity.range[0], entity.range[1] + ), + } + ) + result = result.add_extra(validate_min_value_less_than_max_value) + result = result.extend({cv.Optional(CONF_step, False): cv.float_}) + if entity.auto_min_value is not None: + result = result.extend({cv.Optional(CONF_auto_min_value, False): cv.boolean}) + if entity.auto_max_value is not None: + result = result.extend({cv.Optional(CONF_auto_max_value, False): cv.boolean}) + + return result + + +def generate_setters(entity: cg.MockObj, conf: dict[str, Any]) -> None: + generate.add_property_set(entity, CONF_min_value, conf) + generate.add_property_set(entity, CONF_max_value, conf) + generate.add_property_set(entity, CONF_auto_min_value, conf) + generate.add_property_set(entity, CONF_auto_max_value, conf) diff --git a/esphome/components/opentherm/number/__init__.py b/esphome/components/opentherm/number/__init__.py new file mode 100644 index 0000000000..bbf3e87586 --- /dev/null +++ b/esphome/components/opentherm/number/__init__.py @@ -0,0 +1,74 @@ +from typing import Any + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import number +from esphome.const import ( + CONF_ID, + CONF_UNIT_OF_MEASUREMENT, + CONF_STEP, + CONF_INITIAL_VALUE, + CONF_RESTORE_VALUE, +) +from .. import const, schema, validate, input, generate + +DEPENDENCIES = [const.OPENTHERM] +COMPONENT_TYPE = const.NUMBER + +OpenthermNumber = generate.opentherm_ns.class_( + "OpenthermNumber", number.Number, cg.Component, input.OpenthermInput +) + + +async def new_openthermnumber(config: dict[str, Any]) -> cg.Pvariable: + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await number.register_number( + var, + config, + min_value=config[input.CONF_min_value], + max_value=config[input.CONF_max_value], + step=config[input.CONF_step], + ) + input.generate_setters(var, config) + + if CONF_INITIAL_VALUE in config: + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) + if CONF_RESTORE_VALUE in config: + cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) + + return var + + +def get_entity_validation_schema(entity: schema.InputSchema) -> cv.Schema: + return ( + number.NUMBER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(OpenthermNumber), + cv.Optional( + CONF_UNIT_OF_MEASUREMENT, entity.unit_of_measurement + ): cv.string_strict, + cv.Optional(CONF_STEP, entity.step): cv.float_, + cv.Optional(CONF_INITIAL_VALUE): cv.float_, + cv.Optional(CONF_RESTORE_VALUE): cv.boolean, + } + ) + .extend(input.input_schema(entity)) + .extend(cv.COMPONENT_SCHEMA) + ) + + +CONFIG_SCHEMA = validate.create_component_schema( + schema.INPUTS, get_entity_validation_schema +) + + +async def to_code(config: dict[str, Any]) -> None: + keys = await generate.component_to_code( + COMPONENT_TYPE, + schema.INPUTS, + OpenthermNumber, + generate.create_only_conf(new_openthermnumber), + config, + ) + generate.define_readers(COMPONENT_TYPE, keys) diff --git a/esphome/components/opentherm/number/number.cpp b/esphome/components/opentherm/number/number.cpp new file mode 100644 index 0000000000..d02b99ee9c --- /dev/null +++ b/esphome/components/opentherm/number/number.cpp @@ -0,0 +1,40 @@ +#include "number.h" + +namespace esphome { +namespace opentherm { + +static const char *const TAG = "opentherm.number"; + +void OpenthermNumber::control(float value) { + this->publish_state(value); + + if (this->restore_value_) + this->pref_.save(&value); +} + +void OpenthermNumber::setup() { + float value; + if (!this->restore_value_) { + value = this->initial_value_; + } else { + this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash()); + if (!this->pref_.load(&value)) { + if (!std::isnan(this->initial_value_)) { + value = this->initial_value_; + } else { + value = this->traits.get_min_value(); + } + } + } + this->publish_state(value); +} + +void OpenthermNumber::dump_config() { + LOG_NUMBER("", "OpenTherm Number", this); + ESP_LOGCONFIG(TAG, " Restore value: %d", this->restore_value_); + ESP_LOGCONFIG(TAG, " Initial value: %.2f", this->initial_value_); + ESP_LOGCONFIG(TAG, " Current value: %.2f", this->state); +} + +} // namespace opentherm +} // namespace esphome diff --git a/esphome/components/opentherm/number/number.h b/esphome/components/opentherm/number/number.h new file mode 100644 index 0000000000..6f86072754 --- /dev/null +++ b/esphome/components/opentherm/number/number.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "esphome/core/preferences.h" +#include "esphome/core/log.h" +#include "esphome/components/opentherm/input.h" + +namespace esphome { +namespace opentherm { + +// Just a simple number, which stores the number +class OpenthermNumber : public number::Number, public Component, public OpenthermInput { + protected: + void control(float value) override; + void setup() override; + void dump_config() override; + + float initial_value_{NAN}; + bool restore_value_{false}; + + ESPPreferenceObject pref_; + + public: + void set_min_value(float min_value) override { this->traits.set_min_value(min_value); } + void set_max_value(float max_value) override { this->traits.set_max_value(max_value); } + void set_initial_value(float initial_value) { initial_value_ = initial_value; } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } +}; + +} // namespace opentherm +} // namespace esphome diff --git a/esphome/components/opentherm/opentherm.h b/esphome/components/opentherm/opentherm.h index 23f4b39a1a..85f4611125 100644 --- a/esphome/components/opentherm/opentherm.h +++ b/esphome/components/opentherm/opentherm.h @@ -99,6 +99,8 @@ enum MessageId { EXHAUST_TEMP = 33, FAN_SPEED = 35, FLAME_CURRENT = 36, + ROOM_TEMP_CH2 = 37, + REL_HUMIDITY = 38, DHW_BOUNDS = 48, CH_BOUNDS = 49, OTC_CURVE_BOUNDS = 50, @@ -110,15 +112,46 @@ enum MessageId { HVAC_STATUS = 70, REL_VENT_SETPOINT = 71, DEVICE_VENT = 74, + HVAC_VER_ID = 75, REL_VENTILATION = 77, REL_HUMID_EXHAUST = 78, + EXHAUST_CO2 = 79, SUPPLY_INLET_TEMP = 80, SUPPLY_OUTLET_TEMP = 81, EXHAUST_INLET_TEMP = 82, EXHAUST_OUTLET_TEMP = 83, + EXHAUST_FAN_SPEED = 84, + SUPPLY_FAN_SPEED = 85, + REMOTE_VENTILATION_PARAM = 86, NOM_REL_VENTILATION = 87, + HVAC_NUM_TSP = 88, + HVAC_IDX_TSP = 89, + HVAC_FHB_SIZE = 90, + HVAC_FHB_IDX = 91, + RF_SIGNAL = 98, + DHW_MODE = 99, OVERRIDE_FUNC = 100, + + // Solar Specific Message IDs + SOLAR_MODE_FLAGS = 101, // hb0-2 Controller storage mode + // lb0 Device fault + // lb1-3 Device mode status + // lb4-5 Device status + SOLAR_ASF = 102, + SOLAR_VERSION_ID = 103, + SOLAR_PRODUCT_ID = 104, + SOLAR_NUM_TSP = 105, + SOLAR_IDX_TSP = 106, + SOLAR_FHB_SIZE = 107, + SOLAR_FHB_IDX = 108, + SOLAR_STARTS = 109, + SOLAR_HOURS = 110, + SOLAR_ENERGY = 111, + SOLAR_TOTAL_ENERGY = 112, + + FAILED_BURNER_STARTS = 113, + BURNER_FLAME_LOW = 114, OEM_DIAGNOSTIC = 115, BURNER_STARTS = 116, CH_PUMP_STARTS = 117, diff --git a/esphome/components/opentherm/opentherm_macros.h b/esphome/components/opentherm/opentherm_macros.h index 0389e975ff..8aaec0b48a 100644 --- a/esphome/components/opentherm/opentherm_macros.h +++ b/esphome/components/opentherm/opentherm_macros.h @@ -13,14 +13,49 @@ namespace opentherm { #ifndef OPENTHERM_SENSOR_LIST #define OPENTHERM_SENSOR_LIST(F, sep) #endif +#ifndef OPENTHERM_BINARY_SENSOR_LIST +#define OPENTHERM_BINARY_SENSOR_LIST(F, sep) +#endif +#ifndef OPENTHERM_SWITCH_LIST +#define OPENTHERM_SWITCH_LIST(F, sep) +#endif +#ifndef OPENTHERM_NUMBER_LIST +#define OPENTHERM_NUMBER_LIST(F, sep) +#endif +#ifndef OPENTHERM_OUTPUT_LIST +#define OPENTHERM_OUTPUT_LIST(F, sep) +#endif +#ifndef OPENTHERM_INPUT_SENSOR_LIST +#define OPENTHERM_INPUT_SENSOR_LIST(F, sep) +#endif // Use macros to create fields for every entity specified in the ESPHome configuration #define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity; +#define OPENTHERM_DECLARE_BINARY_SENSOR(entity) binary_sensor::BinarySensor *entity; +#define OPENTHERM_DECLARE_SWITCH(entity) OpenthermSwitch *entity; +#define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity; +#define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity; +#define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity; // Setter macros #define OPENTHERM_SET_SENSOR(entity) \ void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; } +#define OPENTHERM_SET_BINARY_SENSOR(entity) \ + void set_##entity(binary_sensor::BinarySensor *binary_sensor) { this->entity = binary_sensor; } + +#define OPENTHERM_SET_SWITCH(entity) \ + void set_##entity(OpenthermSwitch *sw) { this->entity = sw; } + +#define OPENTHERM_SET_NUMBER(entity) \ + void set_##entity(OpenthermNumber *number) { this->entity = number; } + +#define OPENTHERM_SET_OUTPUT(entity) \ + void set_##entity(OpenthermOutput *output) { this->entity = output; } + +#define OPENTHERM_SET_INPUT_SENSOR(entity) \ + void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; } + // ===== hub.cpp macros ===== // *_MESSAGE_HANDLERS are generated in defines.h and look like this: @@ -35,6 +70,31 @@ namespace opentherm { #ifndef OPENTHERM_SENSOR_MESSAGE_HANDLERS #define OPENTHERM_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) #endif +#ifndef OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS +#define OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) +#endif +#ifndef OPENTHERM_SWITCH_MESSAGE_HANDLERS +#define OPENTHERM_SWITCH_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) +#endif +#ifndef OPENTHERM_NUMBER_MESSAGE_HANDLERS +#define OPENTHERM_NUMBER_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) +#endif +#ifndef OPENTHERM_OUTPUT_MESSAGE_HANDLERS +#define OPENTHERM_OUTPUT_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) +#endif +#ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS +#define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) +#endif + +// Write data request builders +#define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \ + case MessageId::msg: { \ + data.type = MessageType::WRITE_DATA; \ + data.id = request_id; +#define OPENTHERM_MESSAGE_WRITE_ENTITY(key, msg_data) message_data::write_##msg_data(this->key->state, data); +#define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \ + return data; \ + } // Read data request builder #define OPENTHERM_MESSAGE_READ_MESSAGE(msg) \ diff --git a/esphome/components/opentherm/output/__init__.py b/esphome/components/opentherm/output/__init__.py new file mode 100644 index 0000000000..3a53c9d4f4 --- /dev/null +++ b/esphome/components/opentherm/output/__init__.py @@ -0,0 +1,47 @@ +from typing import Any + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_ID +from .. import const, schema, validate, input, generate + +DEPENDENCIES = [const.OPENTHERM] +COMPONENT_TYPE = const.OUTPUT + +OpenthermOutput = generate.opentherm_ns.class_( + "OpenthermOutput", output.FloatOutput, cg.Component, input.OpenthermInput +) + + +async def new_openthermoutput( + config: dict[str, Any], key: str, _hub: cg.MockObj +) -> cg.Pvariable: + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await output.register_output(var, config) + cg.add(getattr(var, "set_id")(cg.RawExpression(f'"{key}_{config[CONF_ID]}"'))) + input.generate_setters(var, config) + return var + + +def get_entity_validation_schema(entity: schema.InputSchema) -> cv.Schema: + return ( + output.FLOAT_OUTPUT_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(OpenthermOutput)} + ) + .extend(input.input_schema(entity)) + .extend(cv.COMPONENT_SCHEMA) + ) + + +CONFIG_SCHEMA = validate.create_component_schema( + schema.INPUTS, get_entity_validation_schema +) + + +async def to_code(config: dict[str, Any]) -> None: + keys = await generate.component_to_code( + COMPONENT_TYPE, schema.INPUTS, OpenthermOutput, new_openthermoutput, config + ) + generate.define_readers(COMPONENT_TYPE, keys) diff --git a/esphome/components/opentherm/output/output.cpp b/esphome/components/opentherm/output/output.cpp new file mode 100644 index 0000000000..f820dc76f1 --- /dev/null +++ b/esphome/components/opentherm/output/output.cpp @@ -0,0 +1,18 @@ +#include "esphome/core/helpers.h" // for clamp() and lerp() +#include "output.h" + +namespace esphome { +namespace opentherm { + +static const char *const TAG = "opentherm.output"; + +void opentherm::OpenthermOutput::write_state(float state) { + ESP_LOGD(TAG, "Received state: %.2f. Min value: %.2f, max value: %.2f", state, min_value_, max_value_); + this->state = state < 0.003 && this->zero_means_zero_ + ? 0.0 + : clamp(lerp(state, min_value_, max_value_), min_value_, max_value_); + this->has_state_ = true; + ESP_LOGD(TAG, "Output %s set to %.2f", this->id_, this->state); +} +} // namespace opentherm +} // namespace esphome diff --git a/esphome/components/opentherm/output/output.h b/esphome/components/opentherm/output/output.h new file mode 100644 index 0000000000..8d6a0ee4ba --- /dev/null +++ b/esphome/components/opentherm/output/output.h @@ -0,0 +1,33 @@ +#pragma once + +#include "esphome/components/output/float_output.h" +#include "esphome/components/opentherm/input.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace opentherm { + +class OpenthermOutput : public output::FloatOutput, public Component, public OpenthermInput { + protected: + bool has_state_ = false; + const char *id_ = nullptr; + + float min_value_, max_value_; + + public: + float state; + + void set_id(const char *id) { this->id_ = id; } + + void write_state(float state) override; + + bool has_state() { return this->has_state_; }; + + void set_min_value(float min_value) override { this->min_value_ = min_value; } + void set_max_value(float max_value) override { this->max_value_ = max_value; } + float get_min_value() { return this->min_value_; } + float get_max_value() { return this->max_value_; } +}; + +} // namespace opentherm +} // namespace esphome diff --git a/esphome/components/opentherm/schema.py b/esphome/components/opentherm/schema.py index 6ed0029437..fe0f2a77a3 100644 --- a/esphome/components/opentherm/schema.py +++ b/esphome/components/opentherm/schema.py @@ -11,9 +11,12 @@ from esphome.const import ( UNIT_MICROAMP, UNIT_PERCENT, UNIT_REVOLUTIONS_PER_MINUTE, + DEVICE_CLASS_COLD, DEVICE_CLASS_CURRENT, DEVICE_CLASS_EMPTY, + DEVICE_CLASS_HEAT, DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_PROBLEM, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, @@ -188,11 +191,23 @@ SENSORS: dict[str, SensorSchema] = { description="Boiler fan speed", unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE, accuracy_decimals=0, + icon="mdi:fan", device_class=DEVICE_CLASS_EMPTY, state_class=STATE_CLASS_MEASUREMENT, message="FAN_SPEED", keep_updated=True, - message_data="u16", + message_data="u8_lb_60", + ), + "fan_speed_setpoint": SensorSchema( + description="Boiler fan speed setpoint", + unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE, + accuracy_decimals=0, + icon="mdi:fan", + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + message="FAN_SPEED", + keep_updated=True, + message_data="u8_hb_60", ), "flame_current": SensorSchema( description="Boiler flame current", @@ -436,3 +451,364 @@ SENSORS: dict[str, SensorSchema] = { message_data="u8_lb", ), } + + +@dataclass +class BinarySensorSchema(EntitySchema): + icon: Optional[str] = None + device_class: Optional[str] = None + + +BINARY_SENSORS: dict[str, BinarySensorSchema] = { + "fault_indication": BinarySensorSchema( + description="Status: Fault indication", + device_class=DEVICE_CLASS_PROBLEM, + message="STATUS", + keep_updated=True, + message_data="flag8_lb_0", + ), + "ch_active": BinarySensorSchema( + description="Status: Central Heating active", + device_class=DEVICE_CLASS_HEAT, + icon="mdi:radiator", + message="STATUS", + keep_updated=True, + message_data="flag8_lb_1", + ), + "dhw_active": BinarySensorSchema( + description="Status: Domestic Hot Water active", + device_class=DEVICE_CLASS_HEAT, + icon="mdi:faucet", + message="STATUS", + keep_updated=True, + message_data="flag8_lb_2", + ), + "flame_on": BinarySensorSchema( + description="Status: Flame on", + device_class=DEVICE_CLASS_HEAT, + icon="mdi:fire", + message="STATUS", + keep_updated=True, + message_data="flag8_lb_3", + ), + "cooling_active": BinarySensorSchema( + description="Status: Cooling active", + device_class=DEVICE_CLASS_COLD, + message="STATUS", + keep_updated=True, + message_data="flag8_lb_4", + ), + "ch2_active": BinarySensorSchema( + description="Status: Central Heating 2 active", + device_class=DEVICE_CLASS_HEAT, + icon="mdi:radiator", + message="STATUS", + keep_updated=True, + message_data="flag8_lb_5", + ), + "diagnostic_indication": BinarySensorSchema( + description="Status: Diagnostic event", + device_class=DEVICE_CLASS_PROBLEM, + message="STATUS", + keep_updated=True, + message_data="flag8_lb_6", + ), + "electricity_production": BinarySensorSchema( + description="Status: Electricity production", + device_class=DEVICE_CLASS_PROBLEM, + message="STATUS", + keep_updated=True, + message_data="flag8_lb_7", + ), + "dhw_present": BinarySensorSchema( + description="Configuration: DHW present", + message="DEVICE_CONFIG", + keep_updated=False, + message_data="flag8_hb_0", + ), + "control_type_on_off": BinarySensorSchema( + description="Configuration: Control type is on/off", + message="DEVICE_CONFIG", + keep_updated=False, + message_data="flag8_hb_1", + ), + "cooling_supported": BinarySensorSchema( + description="Configuration: Cooling supported", + message="DEVICE_CONFIG", + keep_updated=False, + message_data="flag8_hb_2", + ), + "dhw_storage_tank": BinarySensorSchema( + description="Configuration: DHW storage tank", + message="DEVICE_CONFIG", + keep_updated=False, + message_data="flag8_hb_3", + ), + "controller_pump_control_allowed": BinarySensorSchema( + description="Configuration: Controller pump control allowed", + message="DEVICE_CONFIG", + keep_updated=False, + message_data="flag8_hb_4", + ), + "ch2_present": BinarySensorSchema( + description="Configuration: CH2 present", + message="DEVICE_CONFIG", + keep_updated=False, + message_data="flag8_hb_5", + ), + "water_filling": BinarySensorSchema( + description="Configuration: Remote water filling", + message="DEVICE_CONFIG", + keep_updated=False, + message_data="flag8_hb_6", + ), + "heat_mode": BinarySensorSchema( + description="Configuration: Heating or cooling", + message="DEVICE_CONFIG", + keep_updated=False, + message_data="flag8_hb_7", + ), + "dhw_setpoint_transfer_enabled": BinarySensorSchema( + description="Remote boiler parameters: DHW setpoint transfer enabled", + message="REMOTE", + keep_updated=False, + message_data="flag8_hb_0", + ), + "max_ch_setpoint_transfer_enabled": BinarySensorSchema( + description="Remote boiler parameters: CH maximum setpoint transfer enabled", + message="REMOTE", + keep_updated=False, + message_data="flag8_hb_1", + ), + "dhw_setpoint_rw": BinarySensorSchema( + description="Remote boiler parameters: DHW setpoint read/write", + message="REMOTE", + keep_updated=False, + message_data="flag8_lb_0", + ), + "max_ch_setpoint_rw": BinarySensorSchema( + description="Remote boiler parameters: CH maximum setpoint read/write", + message="REMOTE", + keep_updated=False, + message_data="flag8_lb_1", + ), + "service_request": BinarySensorSchema( + description="Service required", + device_class=DEVICE_CLASS_PROBLEM, + message="FAULT_FLAGS", + keep_updated=True, + message_data="flag8_hb_0", + ), + "lockout_reset": BinarySensorSchema( + description="Lockout Reset", + device_class=DEVICE_CLASS_PROBLEM, + message="FAULT_FLAGS", + keep_updated=True, + message_data="flag8_hb_1", + ), + "low_water_pressure": BinarySensorSchema( + description="Low water pressure fault", + device_class=DEVICE_CLASS_PROBLEM, + message="FAULT_FLAGS", + keep_updated=True, + message_data="flag8_hb_2", + ), + "flame_fault": BinarySensorSchema( + description="Flame fault", + device_class=DEVICE_CLASS_PROBLEM, + message="FAULT_FLAGS", + keep_updated=True, + message_data="flag8_hb_3", + ), + "air_pressure_fault": BinarySensorSchema( + description="Air pressure fault", + device_class=DEVICE_CLASS_PROBLEM, + message="FAULT_FLAGS", + keep_updated=True, + message_data="flag8_hb_4", + ), + "water_over_temp": BinarySensorSchema( + description="Water overtemperature", + device_class=DEVICE_CLASS_PROBLEM, + message="FAULT_FLAGS", + keep_updated=True, + message_data="flag8_hb_5", + ), +} + + +@dataclass +class SwitchSchema(EntitySchema): + default_mode: Optional[str] = None + + +SWITCHES: dict[str, SwitchSchema] = { + "ch_enable": SwitchSchema( + description="Central Heating enabled", + message="STATUS", + keep_updated=True, + message_data="flag8_hb_0", + default_mode="restore_default_off", + ), + "dhw_enable": SwitchSchema( + description="Domestic Hot Water enabled", + message="STATUS", + keep_updated=True, + message_data="flag8_hb_1", + default_mode="restore_default_off", + ), + "cooling_enable": SwitchSchema( + description="Cooling enabled", + message="STATUS", + keep_updated=True, + message_data="flag8_hb_2", + default_mode="restore_default_off", + ), + "otc_active": SwitchSchema( + description="Outside temperature compensation active", + message="STATUS", + keep_updated=True, + message_data="flag8_hb_3", + default_mode="restore_default_off", + ), + "ch2_active": SwitchSchema( + description="Central Heating 2 active", + message="STATUS", + keep_updated=True, + message_data="flag8_hb_4", + default_mode="restore_default_off", + ), + "summer_mode_active": SwitchSchema( + description="Summer mode active", + message="STATUS", + keep_updated=True, + message_data="flag8_hb_5", + default_mode="restore_default_off", + ), + "dhw_block": SwitchSchema( + description="DHW blocked", + message="STATUS", + keep_updated=True, + message_data="flag8_hb_6", + default_mode="restore_default_off", + ), +} + + +@dataclass +class AutoConfigure: + message: str + message_data: str + + +@dataclass +class InputSchema(EntitySchema): + unit_of_measurement: str + step: float + range: tuple[int, int] + icon: Optional[str] = None + auto_max_value: Optional[AutoConfigure] = None + auto_min_value: Optional[AutoConfigure] = None + + +INPUTS: dict[str, InputSchema] = { + "t_set": InputSchema( + description="Control setpoint: temperature setpoint for the boiler's supply water", + unit_of_measurement=UNIT_CELSIUS, + step=0.1, + message="CH_SETPOINT", + keep_updated=True, + message_data="f88", + range=(0, 100), + auto_max_value=AutoConfigure(message="MAX_CH_SETPOINT", message_data="f88"), + ), + "t_set_ch2": InputSchema( + description="Control setpoint 2: temperature setpoint for the boiler's supply water on the second heating circuit", + unit_of_measurement=UNIT_CELSIUS, + step=0.1, + message="CH2_SETPOINT", + keep_updated=True, + message_data="f88", + range=(0, 100), + auto_max_value=AutoConfigure(message="MAX_CH_SETPOINT", message_data="f88"), + ), + "cooling_control": InputSchema( + description="Cooling control signal", + unit_of_measurement=UNIT_PERCENT, + step=1.0, + message="COOLING_CONTROL", + keep_updated=True, + message_data="f88", + range=(0, 100), + ), + "t_dhw_set": InputSchema( + description="Domestic hot water temperature setpoint", + unit_of_measurement=UNIT_CELSIUS, + step=0.1, + message="DHW_SETPOINT", + keep_updated=True, + message_data="f88", + range=(0, 127), + auto_min_value=AutoConfigure(message="DHW_BOUNDS", message_data="s8_lb"), + auto_max_value=AutoConfigure(message="DHW_BOUNDS", message_data="s8_hb"), + ), + "max_t_set": InputSchema( + description="Maximum allowable CH water setpoint", + unit_of_measurement=UNIT_CELSIUS, + step=0.1, + message="MAX_CH_SETPOINT", + keep_updated=True, + message_data="f88", + range=(0, 127), + auto_min_value=AutoConfigure(message="CH_BOUNDS", message_data="s8_lb"), + auto_max_value=AutoConfigure(message="CH_BOUNDS", message_data="s8_hb"), + ), + "t_room_set": InputSchema( + description="Current room temperature setpoint (informational)", + unit_of_measurement=UNIT_CELSIUS, + step=0.1, + message="ROOM_SETPOINT", + keep_updated=True, + message_data="f88", + range=(-40, 127), + ), + "t_room_set_ch2": InputSchema( + description="Current room temperature setpoint on CH2 (informational)", + unit_of_measurement=UNIT_CELSIUS, + step=0.1, + message="ROOM_SETPOINT_CH2", + keep_updated=True, + message_data="f88", + range=(-40, 127), + ), + "t_room": InputSchema( + description="Current sensed room temperature (informational)", + unit_of_measurement=UNIT_CELSIUS, + step=0.1, + message="ROOM_TEMP", + keep_updated=True, + message_data="f88", + range=(-40, 127), + ), + "max_rel_mod_level": InputSchema( + description="Maximum relative modulation level", + unit_of_measurement=UNIT_PERCENT, + step=1, + icon="mdi:percent", + message="MAX_MODULATION_LEVEL", + keep_updated=True, + message_data="f88", + range=(0, 100), + ), + "otc_hc_ratio": InputSchema( + description="OTC heat curve ratio", + unit_of_measurement=UNIT_CELSIUS, + step=0.1, + message="OTC_CURVE_RATIO", + keep_updated=True, + message_data="f88", + range=(0, 127), + auto_min_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_lb"), + auto_max_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_hb"), + ), +} diff --git a/esphome/components/opentherm/sensor/__init__.py b/esphome/components/opentherm/sensor/__init__.py index 20224e0eda..546a79054b 100644 --- a/esphome/components/opentherm/sensor/__init__.py +++ b/esphome/components/opentherm/sensor/__init__.py @@ -7,6 +7,18 @@ from .. import const, schema, validate, generate DEPENDENCIES = [const.OPENTHERM] COMPONENT_TYPE = const.SENSOR +MSG_DATA_TYPES = { + "u8_lb", + "u8_hb", + "s8_lb", + "s8_hb", + "u8_lb_60", + "u8_hb_60", + "u16", + "s16", + "f88", +} + def get_entity_validation_schema(entity: schema.SensorSchema) -> cv.Schema: return sensor.sensor_schema( @@ -17,6 +29,10 @@ def get_entity_validation_schema(entity: schema.SensorSchema) -> cv.Schema: or sensor._UNDEF, # pylint: disable=protected-access icon=entity.icon or sensor._UNDEF, # pylint: disable=protected-access state_class=entity.state_class, + ).extend( + { + cv.Optional(const.CONF_DATA_TYPE): cv.one_of(*MSG_DATA_TYPES), + } ) diff --git a/esphome/components/opentherm/switch/__init__.py b/esphome/components/opentherm/switch/__init__.py new file mode 100644 index 0000000000..94ec25e36c --- /dev/null +++ b/esphome/components/opentherm/switch/__init__.py @@ -0,0 +1,43 @@ +from typing import Any + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import CONF_ID +from .. import const, schema, validate, generate + +DEPENDENCIES = [const.OPENTHERM] +COMPONENT_TYPE = const.SWITCH + +OpenthermSwitch = generate.opentherm_ns.class_( + "OpenthermSwitch", switch.Switch, cg.Component +) + + +async def new_openthermswitch(config: dict[str, Any]) -> cg.Pvariable: + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await switch.register_switch(var, config) + return var + + +def get_entity_validation_schema(entity: schema.SwitchSchema) -> cv.Schema: + return switch.SWITCH_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(OpenthermSwitch)} + ).extend(cv.COMPONENT_SCHEMA) + + +CONFIG_SCHEMA = validate.create_component_schema( + schema.SWITCHES, get_entity_validation_schema +) + + +async def to_code(config: dict[str, Any]) -> None: + keys = await generate.component_to_code( + COMPONENT_TYPE, + schema.SWITCHES, + OpenthermSwitch, + generate.create_only_conf(new_openthermswitch), + config, + ) + generate.define_readers(COMPONENT_TYPE, keys) diff --git a/esphome/components/opentherm/switch/switch.cpp b/esphome/components/opentherm/switch/switch.cpp new file mode 100644 index 0000000000..228d9ac8f3 --- /dev/null +++ b/esphome/components/opentherm/switch/switch.cpp @@ -0,0 +1,28 @@ +#include "switch.h" + +namespace esphome { +namespace opentherm { + +static const char *const TAG = "opentherm.switch"; + +void OpenthermSwitch::write_state(bool state) { this->publish_state(state); } + +void OpenthermSwitch::setup() { + auto restored = this->get_initial_state_with_restore_mode(); + bool state = false; + if (!restored.has_value()) { + ESP_LOGD(TAG, "Couldn't restore state for OpenTherm switch '%s'", this->get_name().c_str()); + } else { + ESP_LOGD(TAG, "Restored state for OpenTherm switch '%s': %d", this->get_name().c_str(), restored.value()); + state = restored.value(); + } + this->write_state(state); +} + +void OpenthermSwitch::dump_config() { + LOG_SWITCH("", "OpenTherm Switch", this); + ESP_LOGCONFIG(TAG, " Current state: %d", this->state); +} + +} // namespace opentherm +} // namespace esphome diff --git a/esphome/components/opentherm/switch/switch.h b/esphome/components/opentherm/switch/switch.h new file mode 100644 index 0000000000..0c20a0d9ed --- /dev/null +++ b/esphome/components/opentherm/switch/switch.h @@ -0,0 +1,20 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace opentherm { + +class OpenthermSwitch : public switch_::Switch, public Component { + protected: + void write_state(bool state) override; + + public: + void setup() override; + void dump_config() override; +}; + +} // namespace opentherm +} // namespace esphome diff --git a/tests/components/opentherm/common.yaml b/tests/components/opentherm/common.yaml index 27cbae280a..744580f18b 100644 --- a/tests/components/opentherm/common.yaml +++ b/tests/components/opentherm/common.yaml @@ -12,10 +12,41 @@ opentherm: cooling_enable: false otc_active: false ch2_active: true + t_room: boiler_sensor summer_mode_active: true dhw_block: true sync_mode: true +output: + - platform: opentherm + t_set: + id: t_set + min_value: 20 + auto_max_value: true + zero_means_zero: true + t_set_ch2: + id: t_set_ch2 + min_value: 20 + max_value: 40 + zero_means_zero: true + +number: + - platform: opentherm + cooling_control: + name: "Boiler Cooling control signal" + t_dhw_set: + name: "Boiler DHW Setpoint" + max_t_set: + name: "Boiler Max Setpoint" + t_room_set: + name: "Boiler Room Setpoint" + t_room_set_ch2: + name: "Boiler Room Setpoint CH2" + max_rel_mod_level: + name: "Maximum relative modulation level" + otc_hc_ratio: + name: "OTC heat curve ratio" + sensor: - platform: opentherm rel_mod_level: @@ -25,6 +56,7 @@ sensor: dhw_flow_rate: name: "Boiler Water flow rate in DHW circuit" t_boiler: + id: "boiler_sensor" name: "Boiler water temperature" t_dhw: name: "Boiler DHW temperature" @@ -74,3 +106,55 @@ sensor: name: "OTC heat curve ratio upper bound" otc_hc_ratio_lb: name: "OTC heat curve ratio lower bound" + +binary_sensor: + - platform: opentherm + fault_indication: + name: "Boiler Fault indication" + ch_active: + name: "Boiler Central Heating active" + dhw_active: + name: "Boiler Domestic Hot Water active" + flame_on: + name: "Boiler Flame on" + cooling_active: + name: "Boiler Cooling active" + ch2_active: + name: "Boiler Central Heating 2 active" + diagnostic_indication: + name: "Boiler Diagnostic event" + dhw_present: + name: "Boiler DHW present" + control_type_on_off: + name: "Boiler Control type is on/off" + cooling_supported: + name: "Boiler Cooling supported" + dhw_storage_tank: + name: "Boiler DHW storage tank" + controller_pump_control_allowed: + name: "Boiler Controller pump control allowed" + ch2_present: + name: "Boiler CH2 present" + dhw_setpoint_transfer_enabled: + name: "Boiler DHW setpoint transfer enabled" + max_ch_setpoint_transfer_enabled: + name: "Boiler CH maximum setpoint transfer enabled" + dhw_setpoint_rw: + name: "Boiler DHW setpoint read/write" + max_ch_setpoint_rw: + name: "Boiler CH maximum setpoint read/write" + +switch: + - platform: opentherm + ch_enable: + name: "Boiler Central Heating enabled" + restore_mode: RESTORE_DEFAULT_ON + dhw_enable: + name: "Boiler Domestic Hot Water enabled" + cooling_enable: + name: "Boiler Cooling enabled" + restore_mode: ALWAYS_OFF + otc_active: + name: "Boiler Outside temperature compensation active" + ch2_active: + name: "Boiler Central Heating 2 active" From 928b39f4950536ff2d6501da8c35c139ea650b8b Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt <kevin.ahrendt@nabucasa.com> Date: Tue, 12 Nov 2024 13:20:12 -0500 Subject: [PATCH 096/282] [i2s_audio] I2S speaker improvements (#7749) --- .../components/i2s_audio/speaker/__init__.py | 13 ++- .../i2s_audio/speaker/i2s_audio_speaker.cpp | 106 +++++++++--------- .../i2s_audio/speaker/i2s_audio_speaker.h | 12 +- 3 files changed, 72 insertions(+), 59 deletions(-) diff --git a/esphome/components/i2s_audio/speaker/__init__.py b/esphome/components/i2s_audio/speaker/__init__.py index dd43d6cb39..0355c16321 100644 --- a/esphome/components/i2s_audio/speaker/__init__.py +++ b/esphome/components/i2s_audio/speaker/__init__.py @@ -24,9 +24,10 @@ I2SAudioSpeaker = i2s_audio_ns.class_( "I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut ) - +CONF_BUFFER_DURATION = "buffer_duration" CONF_DAC_TYPE = "dac_type" CONF_I2S_COMM_FMT = "i2s_comm_fmt" +CONF_NEVER = "never" i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t") INTERNAL_DAC_OPTIONS = { @@ -73,8 +74,12 @@ BASE_SCHEMA = ( .extend( { cv.Optional( - CONF_TIMEOUT, default="500ms" + CONF_BUFFER_DURATION, default="500ms" ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_TIMEOUT, default="500ms"): cv.Any( + cv.positive_time_period_milliseconds, + cv.one_of(CONF_NEVER, lower=True), + ), } ) .extend(cv.COMPONENT_SCHEMA) @@ -116,4 +121,6 @@ async def to_code(config): else: cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) cg.add(var.set_i2s_comm_fmt(config[CONF_I2S_COMM_FMT])) - cg.add(var.set_timeout(config[CONF_TIMEOUT])) + if config[CONF_TIMEOUT] != CONF_NEVER: + cg.add(var.set_timeout(config[CONF_TIMEOUT])) + cg.add(var.set_buffer_duration(config[CONF_BUFFER_DURATION])) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index cf6c3bbbba..c3f4566411 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -13,21 +13,22 @@ namespace esphome { namespace i2s_audio { -static const size_t DMA_BUFFER_SIZE = 512; +static const uint8_t DMA_BUFFER_DURATION_MS = 15; static const size_t DMA_BUFFERS_COUNT = 4; -static const size_t FRAMES_IN_ALL_DMA_BUFFERS = DMA_BUFFER_SIZE * DMA_BUFFERS_COUNT; -static const size_t RING_BUFFER_SAMPLES = 8192; -static const size_t TASK_DELAY_MS = 10; + +static const size_t TASK_DELAY_MS = DMA_BUFFER_DURATION_MS * DMA_BUFFERS_COUNT / 2; + static const size_t TASK_STACK_SIZE = 4096; static const ssize_t TASK_PRIORITY = 23; +static const size_t I2S_EVENT_QUEUE_COUNT = DMA_BUFFERS_COUNT + 1; + static const char *const TAG = "i2s_audio.speaker"; enum SpeakerEventGroupBits : uint32_t { - COMMAND_START = (1 << 0), // Starts the main task purpose - COMMAND_STOP = (1 << 1), // stops the main task - COMMAND_STOP_GRACEFULLY = (1 << 2), // Stops the task once all data has been written - MESSAGE_RING_BUFFER_AVAILABLE_TO_WRITE = (1 << 5), // Locks the ring buffer when not set + COMMAND_START = (1 << 0), // starts the speaker task + COMMAND_STOP = (1 << 1), // stops the speaker task + COMMAND_STOP_GRACEFULLY = (1 << 2), // Stops the speaker task once all data has been written STATE_STARTING = (1 << 10), STATE_RUNNING = (1 << 11), STATE_STOPPING = (1 << 12), @@ -91,15 +92,21 @@ static const std::vector<int16_t> Q15_VOLUME_SCALING_FACTORS = { void I2SAudioSpeaker::setup() { ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker..."); - if (this->event_group_ == nullptr) { - this->event_group_ = xEventGroupCreate(); - } + this->event_group_ = xEventGroupCreate(); if (this->event_group_ == nullptr) { ESP_LOGE(TAG, "Failed to create event group"); this->mark_failed(); return; } + + this->i2s_event_queue_ = xQueueCreate(I2S_EVENT_QUEUE_COUNT, sizeof(i2s_event_t)); + + if (this->i2s_event_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create I2S event queue"); + this->mark_failed(); + return; + } } void I2SAudioSpeaker::loop() { @@ -199,23 +206,17 @@ size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t tick this->start(); } - // Wait for the ring buffer to be available - uint32_t event_bits = - xEventGroupWaitBits(this->event_group_, SpeakerEventGroupBits::MESSAGE_RING_BUFFER_AVAILABLE_TO_WRITE, pdFALSE, - pdFALSE, pdMS_TO_TICKS(TASK_DELAY_MS)); + size_t bytes_written = 0; + if ((this->state_ == speaker::STATE_RUNNING) && (this->audio_ring_buffer_.use_count() == 1)) { + // Only one owner of the ring buffer (the speaker task), so the ring buffer is allocated and no other components are + // attempting to write to it. - if (event_bits & SpeakerEventGroupBits::MESSAGE_RING_BUFFER_AVAILABLE_TO_WRITE) { - // Ring buffer is available to write - - // Lock the ring buffer, write to it, then unlock it - xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::MESSAGE_RING_BUFFER_AVAILABLE_TO_WRITE); - size_t bytes_written = this->audio_ring_buffer_->write_without_replacement((void *) data, length, ticks_to_wait); - xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::MESSAGE_RING_BUFFER_AVAILABLE_TO_WRITE); - - return bytes_written; + // Temporarily share ownership of the ring buffer so it won't be deallocated while writing + std::shared_ptr<RingBuffer> temp_ring_buffer = this->audio_ring_buffer_; + bytes_written = temp_ring_buffer->write_without_replacement((void *) data, length, ticks_to_wait); } - return 0; + return bytes_written; } bool I2SAudioSpeaker::has_buffered_data() const { @@ -246,10 +247,12 @@ void I2SAudioSpeaker::speaker_task(void *params) { const ssize_t bytes_per_sample = audio_stream_info.get_bytes_per_sample(); const uint8_t number_of_channels = audio_stream_info.channels; - const size_t dma_buffers_size = FRAMES_IN_ALL_DMA_BUFFERS * bytes_per_sample * number_of_channels; + const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * this_speaker->sample_rate_ / 1000 * + bytes_per_sample * number_of_channels; + const size_t ring_buffer_size = + this_speaker->buffer_duration_ms_ * this_speaker->sample_rate_ / 1000 * bytes_per_sample * number_of_channels; - if (this_speaker->send_esp_err_to_event_group_( - this_speaker->allocate_buffers_(dma_buffers_size, RING_BUFFER_SAMPLES * bytes_per_sample))) { + if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { // Failed to allocate buffers xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM); this_speaker->delete_task_(dma_buffers_size); @@ -258,9 +261,6 @@ void I2SAudioSpeaker::speaker_task(void *params) { if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) { // Failed to start I2S driver this_speaker->delete_task_(dma_buffers_size); - } else { - // Ring buffer is allocated, so indicate its can be written to - xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::MESSAGE_RING_BUFFER_AVAILABLE_TO_WRITE); } if (!this_speaker->send_esp_err_to_event_group_(this_speaker->reconfigure_i2s_stream_info_(audio_stream_info))) { @@ -270,8 +270,10 @@ void I2SAudioSpeaker::speaker_task(void *params) { bool stop_gracefully = false; uint32_t last_data_received_time = millis(); + bool tx_dma_underflow = false; - while ((millis() - last_data_received_time) <= this_speaker->timeout_) { + while (!this_speaker->timeout_.has_value() || + (millis() - last_data_received_time) <= this_speaker->timeout_.value()) { event_group_bits = xEventGroupGetBits(this_speaker->event_group_); if (event_group_bits & SpeakerEventGroupBits::COMMAND_STOP) { @@ -281,12 +283,18 @@ void I2SAudioSpeaker::speaker_task(void *params) { stop_gracefully = true; } + i2s_event_t i2s_event; + while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) { + if (i2s_event.type == I2S_EVENT_TX_Q_OVF) { + tx_dma_underflow = true; + } + } + size_t bytes_to_read = dma_buffers_size; size_t bytes_read = this_speaker->audio_ring_buffer_->read((void *) this_speaker->data_buffer_, bytes_to_read, pdMS_TO_TICKS(TASK_DELAY_MS)); if (bytes_read > 0) { - last_data_received_time = millis(); size_t bytes_written = 0; if ((audio_stream_info.bits_per_sample == 16) && (this_speaker->q15_volume_factor_ < INT16_MAX)) { @@ -307,15 +315,13 @@ void I2SAudioSpeaker::speaker_task(void *params) { if (bytes_written != bytes_read) { xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_ESP_INVALID_SIZE); } - + tx_dma_underflow = false; + last_data_received_time = millis(); } else { // No data received - - if (stop_gracefully) { + if (stop_gracefully && tx_dma_underflow) { break; } - - i2s_zero_dma_buffer(this_speaker->parent_->get_port()); } } } else { @@ -326,7 +332,6 @@ void I2SAudioSpeaker::speaker_task(void *params) { xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); - i2s_stop(this_speaker->parent_->get_port()); i2s_driver_uninstall(this_speaker->parent_->get_port()); this_speaker->parent_->unlock(); @@ -402,8 +407,8 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin return ESP_ERR_NO_MEM; } - if (this->audio_ring_buffer_ == nullptr) { - // Allocate ring buffer + if (this->audio_ring_buffer_.use_count() == 0) { + // Allocate ring buffer. Uses a shared_ptr to ensure it isn't improperly deallocated. this->audio_ring_buffer_ = RingBuffer::create(ring_buffer_size); } @@ -419,6 +424,8 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() { return ESP_ERR_INVALID_STATE; } + int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000; + i2s_driver_config_t config = { .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX), .sample_rate = this->sample_rate_, @@ -427,7 +434,7 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() { .communication_format = this->i2s_comm_fmt_, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = DMA_BUFFERS_COUNT, - .dma_buf_len = DMA_BUFFER_SIZE, + .dma_buf_len = dma_buffer_length, .use_apll = this->use_apll_, .tx_desc_auto_clear = true, .fixed_mclk = I2S_PIN_NO_CHANGE, @@ -448,7 +455,8 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() { } #endif - esp_err_t err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); + esp_err_t err = + i2s_driver_install(this->parent_->get_port(), &config, I2S_EVENT_QUEUE_COUNT, &this->i2s_event_queue_); if (err != ESP_OK) { // Failed to install the driver, so unlock the I2S port this->parent_->unlock(); @@ -502,16 +510,7 @@ esp_err_t I2SAudioSpeaker::reconfigure_i2s_stream_info_(audio::AudioStreamInfo & } void I2SAudioSpeaker::delete_task_(size_t buffer_size) { - if (this->audio_ring_buffer_ != nullptr) { - xEventGroupWaitBits(this->event_group_, - MESSAGE_RING_BUFFER_AVAILABLE_TO_WRITE, // Bit message to read - pdFALSE, // Don't clear the bits on exit - pdTRUE, // Don't wait for all the bits, - portMAX_DELAY); // Block indefinitely until a command bit is set - - this->audio_ring_buffer_.reset(); // Deallocates the ring buffer stored in the unique_ptr - this->audio_ring_buffer_ = nullptr; - } + this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr if (this->data_buffer_ != nullptr) { ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); @@ -520,6 +519,7 @@ void I2SAudioSpeaker::delete_task_(size_t buffer_size) { } xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::STATE_STOPPED); + xQueueReset(this->i2s_event_queue_); this->task_created_ = false; vTaskDelete(nullptr); diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index 3c512d4d4d..8b7386ba58 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -7,6 +7,7 @@ #include <driver/i2s.h> #include <freertos/event_groups.h> +#include <freertos/queue.h> #include <freertos/FreeRTOS.h> #include "esphome/components/audio/audio.h" @@ -27,6 +28,7 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp void setup() override; void loop() override; + void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; } void set_timeout(uint32_t ms) { this->timeout_ = ms; } void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; } #if SOC_I2S_SUPPORTS_DAC @@ -117,10 +119,14 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp TaskHandle_t speaker_task_handle_{nullptr}; EventGroupHandle_t event_group_{nullptr}; - uint8_t *data_buffer_; - std::unique_ptr<RingBuffer> audio_ring_buffer_; + QueueHandle_t i2s_event_queue_; - uint32_t timeout_; + uint8_t *data_buffer_; + std::shared_ptr<RingBuffer> audio_ring_buffer_; + + uint32_t buffer_duration_ms_; + + optional<uint32_t> timeout_; uint8_t dout_pin_; bool task_created_{false}; From 1e80c4807eaab36d92c732a303664d29576d9e06 Mon Sep 17 00:00:00 2001 From: FreeBear-nc <67865163+FreeBear-nc@users.noreply.github.com> Date: Tue, 12 Nov 2024 18:20:48 +0000 Subject: [PATCH 097/282] Message to string extend (#7755) --- esphome/components/opentherm/hub.cpp | 4 ++-- esphome/components/opentherm/opentherm.cpp | 27 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index 432036d58d..dfa8ea95c5 100644 --- a/esphome/components/opentherm/hub.cpp +++ b/esphome/components/opentherm/hub.cpp @@ -371,11 +371,11 @@ void OpenthermHub::dump_config() { ESP_LOGCONFIG(TAG, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Initial requests:"); for (auto type : this->initial_messages_) { - ESP_LOGCONFIG(TAG, " - %d", type); + ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type))); } ESP_LOGCONFIG(TAG, " Repeating requests:"); for (auto type : this->repeating_messages_) { - ESP_LOGCONFIG(TAG, " - %d", type); + ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type))); } } diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 4a23bb94cf..26c707f9a0 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -483,6 +483,8 @@ const char *OpenTherm::message_id_to_str(MessageId id) { TO_STRING_MEMBER(EXHAUST_TEMP) TO_STRING_MEMBER(FAN_SPEED) TO_STRING_MEMBER(FLAME_CURRENT) + TO_STRING_MEMBER(ROOM_TEMP_CH2) + TO_STRING_MEMBER(REL_HUMIDITY) TO_STRING_MEMBER(DHW_BOUNDS) TO_STRING_MEMBER(CH_BOUNDS) TO_STRING_MEMBER(OTC_CURVE_BOUNDS) @@ -492,14 +494,39 @@ const char *OpenTherm::message_id_to_str(MessageId id) { TO_STRING_MEMBER(HVAC_STATUS) TO_STRING_MEMBER(REL_VENT_SETPOINT) TO_STRING_MEMBER(DEVICE_VENT) + TO_STRING_MEMBER(HVAC_VER_ID) TO_STRING_MEMBER(REL_VENTILATION) TO_STRING_MEMBER(REL_HUMID_EXHAUST) + TO_STRING_MEMBER(EXHAUST_CO2) TO_STRING_MEMBER(SUPPLY_INLET_TEMP) TO_STRING_MEMBER(SUPPLY_OUTLET_TEMP) TO_STRING_MEMBER(EXHAUST_INLET_TEMP) TO_STRING_MEMBER(EXHAUST_OUTLET_TEMP) + TO_STRING_MEMBER(EXHAUST_FAN_SPEED) + TO_STRING_MEMBER(SUPPLY_FAN_SPEED) + TO_STRING_MEMBER(REMOTE_VENTILATION_PARAM) TO_STRING_MEMBER(NOM_REL_VENTILATION) + TO_STRING_MEMBER(HVAC_NUM_TSP) + TO_STRING_MEMBER(HVAC_IDX_TSP) + TO_STRING_MEMBER(HVAC_FHB_SIZE) + TO_STRING_MEMBER(HVAC_FHB_IDX) + TO_STRING_MEMBER(RF_SIGNAL) + TO_STRING_MEMBER(DHW_MODE) TO_STRING_MEMBER(OVERRIDE_FUNC) + TO_STRING_MEMBER(SOLAR_MODE_FLAGS) + TO_STRING_MEMBER(SOLAR_ASF) + TO_STRING_MEMBER(SOLAR_VERSION_ID) + TO_STRING_MEMBER(SOLAR_PRODUCT_ID) + TO_STRING_MEMBER(SOLAR_NUM_TSP) + TO_STRING_MEMBER(SOLAR_IDX_TSP) + TO_STRING_MEMBER(SOLAR_FHB_SIZE) + TO_STRING_MEMBER(SOLAR_FHB_IDX) + TO_STRING_MEMBER(SOLAR_STARTS) + TO_STRING_MEMBER(SOLAR_HOURS) + TO_STRING_MEMBER(SOLAR_ENERGY) + TO_STRING_MEMBER(SOLAR_TOTAL_ENERGY) + TO_STRING_MEMBER(FAILED_BURNER_STARTS) + TO_STRING_MEMBER(BURNER_FLAME_LOW) TO_STRING_MEMBER(OEM_DIAGNOSTIC) TO_STRING_MEMBER(BURNER_STARTS) TO_STRING_MEMBER(CH_PUMP_STARTS) From e6a1254e65d69ae0f362891409e7085768b6a479 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 12 Nov 2024 19:23:00 +0100 Subject: [PATCH 098/282] [sun] Implements `is_above_horizon()` (#7754) --- esphome/components/sun/sun.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/sun/sun.h b/esphome/components/sun/sun.h index de4801a655..77d62d34c3 100644 --- a/esphome/components/sun/sun.h +++ b/esphome/components/sun/sun.h @@ -59,6 +59,9 @@ class Sun { void set_latitude(double latitude) { location_.latitude = latitude; } void set_longitude(double longitude) { location_.longitude = longitude; } + // Check if the sun is above the horizon, with a default elevation angle of -0.83333 (standard for sunrise/set). + bool is_above_horizon(double elevation = -0.83333) { return this->elevation() > elevation; } + optional<ESPTime> sunrise(double elevation); optional<ESPTime> sunset(double elevation); optional<ESPTime> sunrise(ESPTime date, double elevation); From b367c01b4b27ac19c75d75774c7ce162894d6035 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt <kevin.ahrendt@nabucasa.com> Date: Tue, 12 Nov 2024 13:48:03 -0500 Subject: [PATCH 099/282] [core] Ring buffer write functions use const pointer parameter (#7750) --- esphome/core/ring_buffer.cpp | 4 ++-- esphome/core/ring_buffer.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/core/ring_buffer.cpp b/esphome/core/ring_buffer.cpp index f97c686684..6152ada314 100644 --- a/esphome/core/ring_buffer.cpp +++ b/esphome/core/ring_buffer.cpp @@ -46,7 +46,7 @@ size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { return bytes_read; } -size_t RingBuffer::write(void *data, size_t len) { +size_t RingBuffer::write(const void *data, size_t len) { size_t free = this->free(); if (free < len) { size_t needed = len - free; @@ -56,7 +56,7 @@ size_t RingBuffer::write(void *data, size_t len) { return xStreamBufferSend(this->handle_, data, len, 0); } -size_t RingBuffer::write_without_replacement(void *data, size_t len, TickType_t ticks_to_wait) { +size_t RingBuffer::write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait) { return xStreamBufferSend(this->handle_, data, len, ticks_to_wait); } diff --git a/esphome/core/ring_buffer.h b/esphome/core/ring_buffer.h index c0511fb52e..aade1b5f49 100644 --- a/esphome/core/ring_buffer.h +++ b/esphome/core/ring_buffer.h @@ -37,7 +37,7 @@ class RingBuffer { * @param len Number of bytes to write * @return Number of bytes written */ - size_t write(void *data, size_t len); + size_t write(const void *data, size_t len); /** * @brief Writes to the ring buffer without overwriting oldest data. @@ -50,7 +50,7 @@ class RingBuffer { * @param ticks_to_wait Maximum number of FreeRTOS ticks to wait (default: 0) * @return Number of bytes written */ - size_t write_without_replacement(void *data, size_t len, TickType_t ticks_to_wait = 0); + size_t write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait = 0); /** * @brief Returns the number of available bytes in the ring buffer. From 7d75c9157bd9ddaa7c56389d6b3126a461b1e52b Mon Sep 17 00:00:00 2001 From: TFGF <terciofilho@gmail.com> Date: Tue, 12 Nov 2024 17:48:40 -0300 Subject: [PATCH 100/282] [Modbus Controller] Added `on_online` and `on_offline` automation (#7417) --- .../components/modbus_controller/__init__.py | 32 ++++++++++++++++++- .../components/modbus_controller/automation.h | 16 ++++++++++ esphome/components/modbus_controller/const.py | 2 ++ .../modbus_controller/modbus_controller.cpp | 16 ++++++++-- .../modbus_controller/modbus_controller.h | 9 ++++++ .../modbus_controller/test.esp32-ard.yaml | 3 ++ .../modbus_controller/test.esp32-idf.yaml | 3 ++ 7 files changed, 78 insertions(+), 3 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 488baa245a..5c407d6fff 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -25,6 +25,8 @@ from .const import ( CONF_MODBUS_CONTROLLER_ID, CONF_OFFLINE_SKIP_UPDATES, CONF_ON_COMMAND_SENT, + CONF_ON_ONLINE, + CONF_ON_OFFLINE, CONF_REGISTER_COUNT, CONF_REGISTER_TYPE, CONF_RESPONSE_SIZE, @@ -114,6 +116,14 @@ ModbusCommandSentTrigger = modbus_controller_ns.class_( "ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_) ) +ModbusOnlineTrigger = modbus_controller_ns.class_( + "ModbusOnlineTrigger", automation.Trigger.template(cg.int_, cg.int_) +) + +ModbusOfflineTrigger = modbus_controller_ns.class_( + "ModbusOfflineTrigger", automation.Trigger.template(cg.int_, cg.int_) +) + _LOGGER = logging.getLogger(__name__) ModbusServerRegisterSchema = cv.Schema( @@ -146,6 +156,16 @@ CONFIG_SCHEMA = cv.All( ), } ), + cv.Optional(CONF_ON_ONLINE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOnlineTrigger), + } + ), + cv.Optional(CONF_ON_OFFLINE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOnlineTrigger), + } + ), } ) .extend(cv.polling_component_schema("60s")) @@ -284,7 +304,17 @@ async def to_code(config): for conf in config.get(CONF_ON_COMMAND_SENT, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation( - trigger, [(int, "function_code"), (int, "address")], conf + trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf + ) + for conf in config.get(CONF_ON_ONLINE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf + ) + for conf in config.get(CONF_ON_OFFLINE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf ) diff --git a/esphome/components/modbus_controller/automation.h b/esphome/components/modbus_controller/automation.h index ad8de4b05d..b3338192cc 100644 --- a/esphome/components/modbus_controller/automation.h +++ b/esphome/components/modbus_controller/automation.h @@ -15,5 +15,21 @@ class ModbusCommandSentTrigger : public Trigger<int, int> { } }; +class ModbusOnlineTrigger : public Trigger<int, int> { + public: + ModbusOnlineTrigger(ModbusController *a_modbuscontroller) { + a_modbuscontroller->add_on_online_callback( + [this](int function_code, int address) { this->trigger(function_code, address); }); + } +}; + +class ModbusOfflineTrigger : public Trigger<int, int> { + public: + ModbusOfflineTrigger(ModbusController *a_modbuscontroller) { + a_modbuscontroller->add_on_offline_callback( + [this](int function_code, int address) { this->trigger(function_code, address); }); + } +}; + } // namespace modbus_controller } // namespace esphome diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index 5cf7d230f1..4d39e48dcd 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -9,6 +9,8 @@ CONF_MAX_CMD_RETRIES = "max_cmd_retries" CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" CONF_ON_COMMAND_SENT = "on_command_sent" +CONF_ON_ONLINE = "on_online" +CONF_ON_OFFLINE = "on_offline" CONF_RAW_ENCODE = "raw_encode" CONF_REGISTER_COUNT = "register_count" CONF_REGISTER_TYPE = "register_type" diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 1dcb533629..e1102516ca 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -32,8 +32,10 @@ bool ModbusController::send_next_command_() { r.skip_updates_counter = this->offline_skip_updates_; } } + + this->module_offline_ = true; + this->offline_callback_.call((int) command->function_code, command->register_address); } - this->module_offline_ = true; ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X no response received - removed from send queue", this->address_, command->register_address); this->command_queue_.pop_front(); @@ -68,8 +70,10 @@ void ModbusController::on_modbus_data(const std::vector<uint8_t> &data) { r.skip_updates_counter = 0; } } + // Restore module online state + this->module_offline_ = false; + this->online_callback_.call((int) current_command->function_code, current_command->register_address); } - this->module_offline_ = false; // Move the commandItem to the response queue current_command->payload = data; @@ -670,5 +674,13 @@ void ModbusController::add_on_command_sent_callback(std::function<void(int, int) this->command_sent_callback_.add(std::move(callback)); } +void ModbusController::add_on_online_callback(std::function<void(int, int)> &&callback) { + this->online_callback_.add(std::move(callback)); +} + +void ModbusController::add_on_offline_callback(std::function<void(int, int)> &&callback) { + this->offline_callback_.add(std::move(callback)); +} + } // namespace modbus_controller } // namespace esphome diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 1fa35e1535..2a0b936bf5 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -468,6 +468,10 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { bool get_module_offline() { return module_offline_; } /// Set callback for commands void add_on_command_sent_callback(std::function<void(int, int)> &&callback); + /// Set callback for online changes + void add_on_online_callback(std::function<void(int, int)> &&callback); + /// Set callback for offline changes + void add_on_offline_callback(std::function<void(int, int)> &&callback); /// called by esphome generated code to set the max_cmd_retries. void set_max_cmd_retries(uint8_t max_cmd_retries) { this->max_cmd_retries_ = max_cmd_retries; } /// get how many times a command will be (re)sent if no response is received @@ -508,7 +512,12 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { uint16_t offline_skip_updates_; /// How many times we will retry a command if we get no response uint8_t max_cmd_retries_{4}; + /// Command sent callback CallbackManager<void(int, int)> command_sent_callback_{}; + /// Server online callback + CallbackManager<void(int, int)> online_callback_{}; + /// Server offline callback + CallbackManager<void(int, int)> offline_callback_{}; }; /** Convert vector<uint8_t> response payload to float. diff --git a/tests/components/modbus_controller/test.esp32-ard.yaml b/tests/components/modbus_controller/test.esp32-ard.yaml index cd95d149cb..f5c5c10125 100644 --- a/tests/components/modbus_controller/test.esp32-ard.yaml +++ b/tests/components/modbus_controller/test.esp32-ard.yaml @@ -21,6 +21,9 @@ modbus_controller: address: 0x2 modbus_id: mod_bus1 allow_duplicate_commands: false + on_online: + then: + logger.log: "Module Online" - id: modbus_controller2 address: 0x2 modbus_id: mod_bus2 diff --git a/tests/components/modbus_controller/test.esp32-idf.yaml b/tests/components/modbus_controller/test.esp32-idf.yaml index ba28e94d73..0e1849dd88 100644 --- a/tests/components/modbus_controller/test.esp32-idf.yaml +++ b/tests/components/modbus_controller/test.esp32-idf.yaml @@ -13,4 +13,7 @@ modbus_controller: address: 0x2 modbus_id: mod_bus1 allow_duplicate_commands: true + on_offline: + then: + logger.log: "Module Offline" max_cmd_retries: 10 From 053465d3f627809fc890eb94271419df0368f369 Mon Sep 17 00:00:00 2001 From: Kyle Cascade <kyle@xkyle.com> Date: Tue, 12 Nov 2024 14:54:25 -0800 Subject: [PATCH 101/282] Updated dfplayer logging to be more user-friendly (#7740) --- esphome/components/dfplayer/dfplayer.cpp | 138 ++++++++++++++++++++++- esphome/components/dfplayer/dfplayer.h | 72 ++++-------- 2 files changed, 151 insertions(+), 59 deletions(-) diff --git a/esphome/components/dfplayer/dfplayer.cpp b/esphome/components/dfplayer/dfplayer.cpp index aa2dc260e0..98c3e91e46 100644 --- a/esphome/components/dfplayer/dfplayer.cpp +++ b/esphome/components/dfplayer/dfplayer.cpp @@ -6,7 +6,104 @@ namespace dfplayer { static const char *const TAG = "dfplayer"; +void DFPlayer::next() { + this->ack_set_is_playing_ = true; + ESP_LOGD(TAG, "Playing next track"); + this->send_cmd_(0x01); +} + +void DFPlayer::previous() { + this->ack_set_is_playing_ = true; + ESP_LOGD(TAG, "Playing previous track"); + this->send_cmd_(0x02); +} +void DFPlayer::play_mp3(uint16_t file) { + this->ack_set_is_playing_ = true; + ESP_LOGD(TAG, "Playing file %d in mp3 folder", file); + this->send_cmd_(0x12, file); +} + +void DFPlayer::play_file(uint16_t file) { + this->ack_set_is_playing_ = true; + ESP_LOGD(TAG, "Playing file %d", file); + this->send_cmd_(0x03, file); +} + +void DFPlayer::play_file_loop(uint16_t file) { + this->ack_set_is_playing_ = true; + ESP_LOGD(TAG, "Playing file %d in loop", file); + this->send_cmd_(0x08, file); +} + +void DFPlayer::play_folder_loop(uint16_t folder) { + this->ack_set_is_playing_ = true; + ESP_LOGD(TAG, "Playing folder %d in loop", folder); + this->send_cmd_(0x17, folder); +} + +void DFPlayer::volume_up() { + ESP_LOGD(TAG, "Increasing volume"); + this->send_cmd_(0x04); +} + +void DFPlayer::volume_down() { + ESP_LOGD(TAG, "Decreasing volume"); + this->send_cmd_(0x05); +} + +void DFPlayer::set_device(Device device) { + ESP_LOGD(TAG, "Setting device to %d", device); + this->send_cmd_(0x09, device); +} + +void DFPlayer::set_volume(uint8_t volume) { + ESP_LOGD(TAG, "Setting volume to %d", volume); + this->send_cmd_(0x06, volume); +} + +void DFPlayer::set_eq(EqPreset preset) { + ESP_LOGD(TAG, "Setting EQ to %d", preset); + this->send_cmd_(0x07, preset); +} + +void DFPlayer::sleep() { + this->ack_reset_is_playing_ = true; + ESP_LOGD(TAG, "Putting DFPlayer to sleep"); + this->send_cmd_(0x0A); +} + +void DFPlayer::reset() { + this->ack_reset_is_playing_ = true; + ESP_LOGD(TAG, "Resetting DFPlayer"); + this->send_cmd_(0x0C); +} + +void DFPlayer::start() { + this->ack_set_is_playing_ = true; + ESP_LOGD(TAG, "Starting playback"); + this->send_cmd_(0x0D); +} + +void DFPlayer::pause() { + this->ack_reset_is_playing_ = true; + ESP_LOGD(TAG, "Pausing playback"); + this->send_cmd_(0x0E); +} + +void DFPlayer::stop() { + this->ack_reset_is_playing_ = true; + ESP_LOGD(TAG, "Stopping playback"); + this->send_cmd_(0x16); +} + +void DFPlayer::random() { + this->ack_set_is_playing_ = true; + ESP_LOGD(TAG, "Playing random file"); + this->send_cmd_(0x18); +} + void DFPlayer::play_folder(uint16_t folder, uint16_t file) { + ESP_LOGD(TAG, "Playing file %d in folder %d", file, folder); if (folder < 100 && file < 256) { this->ack_set_is_playing_ = true; this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file); @@ -29,7 +126,7 @@ void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) { this->sent_cmd_ = cmd; - ESP_LOGD(TAG, "Send Command %#02x arg %#04x", cmd, argument); + ESP_LOGV(TAG, "Send Command %#02x arg %#04x", cmd, argument); this->write_array(buffer, 10); } @@ -101,9 +198,37 @@ void DFPlayer::loop() { ESP_LOGV(TAG, "Nack"); this->ack_set_is_playing_ = false; this->ack_reset_is_playing_ = false; - if (argument == 6) { - ESP_LOGV(TAG, "File not found"); - this->is_playing_ = false; + switch (argument) { + case 0x01: + ESP_LOGE(TAG, "Module is busy or uninitialized"); + break; + case 0x02: + ESP_LOGE(TAG, "Module is in sleep mode"); + break; + case 0x03: + ESP_LOGE(TAG, "Serial receive error"); + break; + case 0x04: + ESP_LOGE(TAG, "Checksum incorrect"); + break; + case 0x05: + ESP_LOGE(TAG, "Specified track is out of current track scope"); + this->is_playing_ = false; + break; + case 0x06: + ESP_LOGE(TAG, "Specified track is not found"); + this->is_playing_ = false; + break; + case 0x07: + ESP_LOGE(TAG, "Insertion error (an inserting operation only can be done when a track is being played)"); + break; + case 0x08: + ESP_LOGE(TAG, "SD card reading failed (SD card pulled out or damaged)"); + break; + case 0x09: + ESP_LOGE(TAG, "Entered into sleep mode"); + this->is_playing_ = false; + break; } break; case 0x41: @@ -113,12 +238,13 @@ void DFPlayer::loop() { this->ack_set_is_playing_ = false; this->ack_reset_is_playing_ = false; break; - case 0x3D: // Playback finished + case 0x3D: + ESP_LOGV(TAG, "Playback finished"); this->is_playing_ = false; this->on_finished_playback_callback_.call(); break; default: - ESP_LOGD(TAG, "Command %#02x arg %#04x", cmd, argument); + ESP_LOGV(TAG, "Received unknown cmd %#02x arg %#04x", cmd, argument); } this->sent_cmd_ = 0; this->read_pos_ = 0; diff --git a/esphome/components/dfplayer/dfplayer.h b/esphome/components/dfplayer/dfplayer.h index 26e90fd410..d2ec0a2310 100644 --- a/esphome/components/dfplayer/dfplayer.h +++ b/esphome/components/dfplayer/dfplayer.h @@ -23,64 +23,30 @@ enum Device { TF_CARD = 2, }; +// See the datasheet here: +// https://github.com/DFRobot/DFRobotDFPlayerMini/blob/master/doc/FN-M16P%2BEmbedded%2BMP3%2BAudio%2BModule%2BDatasheet.pdf class DFPlayer : public uart::UARTDevice, public Component { public: void loop() override; - void next() { - this->ack_set_is_playing_ = true; - this->send_cmd_(0x01); - } - void previous() { - this->ack_set_is_playing_ = true; - this->send_cmd_(0x02); - } - void play_mp3(uint16_t file) { - this->ack_set_is_playing_ = true; - this->send_cmd_(0x12, file); - } - void play_file(uint16_t file) { - this->ack_set_is_playing_ = true; - this->send_cmd_(0x03, file); - } - void play_file_loop(uint16_t file) { - this->ack_set_is_playing_ = true; - this->send_cmd_(0x08, file); - } + void next(); + void previous(); + void play_mp3(uint16_t file); + void play_file(uint16_t file); + void play_file_loop(uint16_t file); void play_folder(uint16_t folder, uint16_t file); - void play_folder_loop(uint16_t folder) { - this->ack_set_is_playing_ = true; - this->send_cmd_(0x17, folder); - } - void volume_up() { this->send_cmd_(0x04); } - void volume_down() { this->send_cmd_(0x05); } - void set_device(Device device) { this->send_cmd_(0x09, device); } - void set_volume(uint8_t volume) { this->send_cmd_(0x06, volume); } - void set_eq(EqPreset preset) { this->send_cmd_(0x07, preset); } - void sleep() { - this->ack_reset_is_playing_ = true; - this->send_cmd_(0x0A); - } - void reset() { - this->ack_reset_is_playing_ = true; - this->send_cmd_(0x0C); - } - void start() { - this->ack_set_is_playing_ = true; - this->send_cmd_(0x0D); - } - void pause() { - this->ack_reset_is_playing_ = true; - this->send_cmd_(0x0E); - } - void stop() { - this->ack_reset_is_playing_ = true; - this->send_cmd_(0x16); - } - void random() { - this->ack_set_is_playing_ = true; - this->send_cmd_(0x18); - } + void play_folder_loop(uint16_t folder); + void volume_up(); + void volume_down(); + void set_device(Device device); + void set_volume(uint8_t volume); + void set_eq(EqPreset preset); + void sleep(); + void reset(); + void start(); + void pause(); + void stop(); + void random(); bool is_playing() { return is_playing_; } void dump_config() override; From 80226694d5d0c5d44f0bb8c2c39b070802c4b073 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:16:13 +1300 Subject: [PATCH 102/282] Bump version to 2024.11.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5645c9eaab..c3c8712677 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.0-dev" +__version__ = "2024.11.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 1f7f03f563e3500beeda9a3c163ee48e309e7617 Mon Sep 17 00:00:00 2001 From: luar123 <49960470+luar123@users.noreply.github.com> Date: Wed, 13 Nov 2024 01:18:10 +0100 Subject: [PATCH 103/282] Fix temperature and humidity for bme680 with bsec2 (#7728) --- .../components/bme68x_bsec2/bme68x_bsec2.cpp | 95 ++++++++++--------- .../components/bme68x_bsec2/bme68x_bsec2.h | 2 - 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp b/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp index 5425bbd5b7..f83f20f1a5 100644 --- a/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp +++ b/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp @@ -204,11 +204,11 @@ void BME68xBSEC2Component::update_subscription_() { } void BME68xBSEC2Component::run_() { + this->op_mode_ = this->bsec_settings_.op_mode; int64_t curr_time_ns = this->get_time_ns_(); - if (curr_time_ns < this->next_call_ns_) { + if (curr_time_ns < this->bsec_settings_.next_call) { return; } - this->op_mode_ = this->bsec_settings_.op_mode; uint8_t status; ESP_LOGV(TAG, "Performing sensor run"); @@ -219,57 +219,60 @@ void BME68xBSEC2Component::run_() { ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_); return; } - this->next_call_ns_ = this->bsec_settings_.next_call; - if (this->bsec_settings_.trigger_measurement) { - bme68x_get_conf(&bme68x_conf, &this->bme68x_); + switch (this->bsec_settings_.op_mode) { + case BME68X_FORCED_MODE: + bme68x_get_conf(&bme68x_conf, &this->bme68x_); - bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling; - bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling; - bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling; - bme68x_set_conf(&bme68x_conf, &this->bme68x_); + bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling; + bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling; + bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling; + bme68x_set_conf(&bme68x_conf, &this->bme68x_); + this->bme68x_heatr_conf_.enable = BME68X_ENABLE; + this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature; + this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration; + + // status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_); + status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); + status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_); + this->op_mode_ = BME68X_FORCED_MODE; + ESP_LOGV(TAG, "Using forced mode"); + + break; + case BME68X_PARALLEL_MODE: + if (this->op_mode_ != this->bsec_settings_.op_mode) { + bme68x_get_conf(&bme68x_conf, &this->bme68x_); + + bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling; + bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling; + bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling; + bme68x_set_conf(&bme68x_conf, &this->bme68x_); - switch (this->bsec_settings_.op_mode) { - case BME68X_FORCED_MODE: this->bme68x_heatr_conf_.enable = BME68X_ENABLE; - this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature; - this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration; + this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile; + this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile; + this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len; + this->bme68x_heatr_conf_.shared_heatr_dur = + BSEC_TOTAL_HEAT_DUR - + (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000)); - status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_); - status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); - status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_); - this->op_mode_ = BME68X_FORCED_MODE; - this->sleep_mode_ = false; - ESP_LOGV(TAG, "Using forced mode"); + status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); - break; - case BME68X_PARALLEL_MODE: - if (this->op_mode_ != this->bsec_settings_.op_mode) { - this->bme68x_heatr_conf_.enable = BME68X_ENABLE; - this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile; - this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile; - this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len; - this->bme68x_heatr_conf_.shared_heatr_dur = - BSEC_TOTAL_HEAT_DUR - - (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000)); - - status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); - - status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_); - this->op_mode_ = BME68X_PARALLEL_MODE; - this->sleep_mode_ = false; - ESP_LOGV(TAG, "Using parallel mode"); - } - break; - case BME68X_SLEEP_MODE: - if (!this->sleep_mode_) { - bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_); - this->sleep_mode_ = true; - ESP_LOGV(TAG, "Using sleep mode"); - } - break; - } + status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_); + this->op_mode_ = BME68X_PARALLEL_MODE; + ESP_LOGV(TAG, "Using parallel mode"); + } + break; + case BME68X_SLEEP_MODE: + if (this->op_mode_ != this->bsec_settings_.op_mode) { + bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_); + this->op_mode_ = BME68X_SLEEP_MODE; + ESP_LOGV(TAG, "Using sleep mode"); + } + break; + } + if (this->bsec_settings_.trigger_measurement && this->bsec_settings_.op_mode != BME68X_SLEEP_MODE) { uint32_t meas_dur = 0; meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_); ESP_LOGV(TAG, "Queueing read in %uus", meas_dur); diff --git a/esphome/components/bme68x_bsec2/bme68x_bsec2.h b/esphome/components/bme68x_bsec2/bme68x_bsec2.h index 7b9db2b7bf..86d3e5dfbf 100644 --- a/esphome/components/bme68x_bsec2/bme68x_bsec2.h +++ b/esphome/components/bme68x_bsec2/bme68x_bsec2.h @@ -113,13 +113,11 @@ class BME68xBSEC2Component : public Component { struct bme68x_heatr_conf bme68x_heatr_conf_; uint8_t op_mode_; // operating mode of sensor - bool sleep_mode_; bsec_library_return_t bsec_status_{BSEC_OK}; int8_t bme68x_status_{BME68X_OK}; int64_t last_time_ms_{0}; uint32_t millis_overflow_counter_{0}; - int64_t next_call_ns_{0}; std::queue<std::function<void()>> queue_; From a2cab960a9d2f138fe736ce8147b383f13146475 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:16:13 +1300 Subject: [PATCH 104/282] Bump version to 2024.12.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5645c9eaab..d42ee5ee72 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.0-dev" +__version__ = "2024.12.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From c7c8711c9c8380d9d90875973e2e8c0e70678667 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt <kevin.ahrendt@nabucasa.com> Date: Wed, 13 Nov 2024 12:39:02 -0500 Subject: [PATCH 105/282] [i2s_audio] Bugfix: Adjust I2S speaker setup priority (#7759) --- .../i2s_audio/speaker/i2s_audio_speaker.cpp | 11 +---------- .../components/i2s_audio/speaker/i2s_audio_speaker.h | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index c3f4566411..53b3cc8dc0 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -99,14 +99,6 @@ void I2SAudioSpeaker::setup() { this->mark_failed(); return; } - - this->i2s_event_queue_ = xQueueCreate(I2S_EVENT_QUEUE_COUNT, sizeof(i2s_event_t)); - - if (this->i2s_event_queue_ == nullptr) { - ESP_LOGE(TAG, "Failed to create I2S event queue"); - this->mark_failed(); - return; - } } void I2SAudioSpeaker::loop() { @@ -339,7 +331,7 @@ void I2SAudioSpeaker::speaker_task(void *params) { } void I2SAudioSpeaker::start() { - if (this->is_failed() || this->status_has_error()) + if (!this->is_ready() || this->is_failed() || this->status_has_error()) return; if ((this->state_ == speaker::STATE_STARTING) || (this->state_ == speaker::STATE_RUNNING)) return; @@ -519,7 +511,6 @@ void I2SAudioSpeaker::delete_task_(size_t buffer_size) { } xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::STATE_STOPPED); - xQueueReset(this->i2s_event_queue_); this->task_created_ = false; vTaskDelete(nullptr); diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index 8b7386ba58..2b90f39399 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -23,7 +23,7 @@ namespace i2s_audio { class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Component { public: - float get_setup_priority() const override { return esphome::setup_priority::LATE; } + float get_setup_priority() const override { return esphome::setup_priority::PROCESSOR; } void setup() override; void loop() override; From 39c889e6625a212b582d519012c6b69a45862bc6 Mon Sep 17 00:00:00 2001 From: Roving Ronin <108674933+Roving-Ronin@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:43:21 +1100 Subject: [PATCH 106/282] Update UNIT_VOLT_AMPS_REACTIVE = "var" (Currently 'VAR') (#7643) --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d42ee5ee72..6a643e1e30 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1095,7 +1095,7 @@ UNIT_STEPS = "steps" UNIT_VOLT = "V" UNIT_VOLT_AMPS = "VA" UNIT_VOLT_AMPS_HOURS = "VAh" -UNIT_VOLT_AMPS_REACTIVE = "VAR" +UNIT_VOLT_AMPS_REACTIVE = "var" UNIT_VOLT_AMPS_REACTIVE_HOURS = "VARh" UNIT_WATT = "W" UNIT_WATT_HOURS = "Wh" From d01508885531c31a2ebb518c8b07f550d5768ccc Mon Sep 17 00:00:00 2001 From: Felipe Santos <felipecassiors@gmail.com> Date: Wed, 13 Nov 2024 21:44:18 -0300 Subject: [PATCH 107/282] Fix reactive power unit of measurement from VAR to var (#7757) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/sdm_meter/sdm_meter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sdm_meter/sdm_meter.cpp b/esphome/components/sdm_meter/sdm_meter.cpp index 9c35d306ad..18e06e2b04 100644 --- a/esphome/components/sdm_meter/sdm_meter.cpp +++ b/esphome/components/sdm_meter/sdm_meter.cpp @@ -38,7 +38,7 @@ void SDMMeter::on_modbus_data(const std::vector<uint8_t> &data) { ESP_LOGD( TAG, - "SDMMeter Phase %c: V=%.3f V, I=%.3f A, Active P=%.3f W, Apparent P=%.3f VA, Reactive P=%.3f VAR, PF=%.3f, " + "SDMMeter Phase %c: V=%.3f V, I=%.3f A, Active P=%.3f W, Apparent P=%.3f VA, Reactive P=%.3f var, PF=%.3f, " "PA=%.3f °", i + 'A', voltage, current, active_power, apparent_power, reactive_power, power_factor, phase_angle); if (phase.voltage_sensor_ != nullptr) From 5e62c489b08a70295f3247c19c907de57507e263 Mon Sep 17 00:00:00 2001 From: Jordan Zucker <jordan.zucker@gmail.com> Date: Wed, 13 Nov 2024 16:57:09 -0800 Subject: [PATCH 108/282] Disable bluetooth proxy during update (#7695) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 3 +++ tests/components/esp32_ble_tracker/common.yaml | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 74b4b9aa89..b86d32ee61 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -65,6 +65,9 @@ void ESP32BLETracker::setup() { [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { if (state == ota::OTA_STARTED) { this->stop_scan(); + for (auto *client : this->clients_) { + client->disconnect(); + } } }); #endif diff --git a/tests/components/esp32_ble_tracker/common.yaml b/tests/components/esp32_ble_tracker/common.yaml index ef23635c9e..018bbb42b3 100644 --- a/tests/components/esp32_ble_tracker/common.yaml +++ b/tests/components/esp32_ble_tracker/common.yaml @@ -39,3 +39,10 @@ esp32_ble_tracker: - then: - lambda: |- ESP_LOGD("ble_auto", "The scan has ended!"); + +wifi: + ssid: MySSID + password: password1 + +ota: + - platform: esphome From 0b51ec2c88793191f8d28fecfe9b514b9195312f Mon Sep 17 00:00:00 2001 From: Fabio Bonelli <fbonelli@gmail.com> Date: Thu, 14 Nov 2024 01:57:51 +0100 Subject: [PATCH 109/282] ld2420: fix typo in log message (#7758) --- esphome/components/ld2420/ld2420.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index e57fdbc84e..9d628cc14f 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -180,7 +180,7 @@ void LD2420Component::apply_config_action() { } void LD2420Component::factory_reset_action() { - ESP_LOGCONFIG(TAG, "Setiing factory defaults..."); + ESP_LOGCONFIG(TAG, "Setting factory defaults..."); if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); this->mark_failed(); From 44545a18a0de2724d14e497357a4aea5a8f90d19 Mon Sep 17 00:00:00 2001 From: luar123 <49960470+luar123@users.noreply.github.com> Date: Wed, 13 Nov 2024 01:18:10 +0100 Subject: [PATCH 110/282] Fix temperature and humidity for bme680 with bsec2 (#7728) --- .../components/bme68x_bsec2/bme68x_bsec2.cpp | 95 ++++++++++--------- .../components/bme68x_bsec2/bme68x_bsec2.h | 2 - 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp b/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp index 5425bbd5b7..f83f20f1a5 100644 --- a/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp +++ b/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp @@ -204,11 +204,11 @@ void BME68xBSEC2Component::update_subscription_() { } void BME68xBSEC2Component::run_() { + this->op_mode_ = this->bsec_settings_.op_mode; int64_t curr_time_ns = this->get_time_ns_(); - if (curr_time_ns < this->next_call_ns_) { + if (curr_time_ns < this->bsec_settings_.next_call) { return; } - this->op_mode_ = this->bsec_settings_.op_mode; uint8_t status; ESP_LOGV(TAG, "Performing sensor run"); @@ -219,57 +219,60 @@ void BME68xBSEC2Component::run_() { ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_); return; } - this->next_call_ns_ = this->bsec_settings_.next_call; - if (this->bsec_settings_.trigger_measurement) { - bme68x_get_conf(&bme68x_conf, &this->bme68x_); + switch (this->bsec_settings_.op_mode) { + case BME68X_FORCED_MODE: + bme68x_get_conf(&bme68x_conf, &this->bme68x_); - bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling; - bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling; - bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling; - bme68x_set_conf(&bme68x_conf, &this->bme68x_); + bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling; + bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling; + bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling; + bme68x_set_conf(&bme68x_conf, &this->bme68x_); + this->bme68x_heatr_conf_.enable = BME68X_ENABLE; + this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature; + this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration; + + // status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_); + status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); + status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_); + this->op_mode_ = BME68X_FORCED_MODE; + ESP_LOGV(TAG, "Using forced mode"); + + break; + case BME68X_PARALLEL_MODE: + if (this->op_mode_ != this->bsec_settings_.op_mode) { + bme68x_get_conf(&bme68x_conf, &this->bme68x_); + + bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling; + bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling; + bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling; + bme68x_set_conf(&bme68x_conf, &this->bme68x_); - switch (this->bsec_settings_.op_mode) { - case BME68X_FORCED_MODE: this->bme68x_heatr_conf_.enable = BME68X_ENABLE; - this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature; - this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration; + this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile; + this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile; + this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len; + this->bme68x_heatr_conf_.shared_heatr_dur = + BSEC_TOTAL_HEAT_DUR - + (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000)); - status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_); - status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); - status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_); - this->op_mode_ = BME68X_FORCED_MODE; - this->sleep_mode_ = false; - ESP_LOGV(TAG, "Using forced mode"); + status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); - break; - case BME68X_PARALLEL_MODE: - if (this->op_mode_ != this->bsec_settings_.op_mode) { - this->bme68x_heatr_conf_.enable = BME68X_ENABLE; - this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile; - this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile; - this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len; - this->bme68x_heatr_conf_.shared_heatr_dur = - BSEC_TOTAL_HEAT_DUR - - (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000)); - - status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); - - status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_); - this->op_mode_ = BME68X_PARALLEL_MODE; - this->sleep_mode_ = false; - ESP_LOGV(TAG, "Using parallel mode"); - } - break; - case BME68X_SLEEP_MODE: - if (!this->sleep_mode_) { - bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_); - this->sleep_mode_ = true; - ESP_LOGV(TAG, "Using sleep mode"); - } - break; - } + status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_); + this->op_mode_ = BME68X_PARALLEL_MODE; + ESP_LOGV(TAG, "Using parallel mode"); + } + break; + case BME68X_SLEEP_MODE: + if (this->op_mode_ != this->bsec_settings_.op_mode) { + bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_); + this->op_mode_ = BME68X_SLEEP_MODE; + ESP_LOGV(TAG, "Using sleep mode"); + } + break; + } + if (this->bsec_settings_.trigger_measurement && this->bsec_settings_.op_mode != BME68X_SLEEP_MODE) { uint32_t meas_dur = 0; meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_); ESP_LOGV(TAG, "Queueing read in %uus", meas_dur); diff --git a/esphome/components/bme68x_bsec2/bme68x_bsec2.h b/esphome/components/bme68x_bsec2/bme68x_bsec2.h index 7b9db2b7bf..86d3e5dfbf 100644 --- a/esphome/components/bme68x_bsec2/bme68x_bsec2.h +++ b/esphome/components/bme68x_bsec2/bme68x_bsec2.h @@ -113,13 +113,11 @@ class BME68xBSEC2Component : public Component { struct bme68x_heatr_conf bme68x_heatr_conf_; uint8_t op_mode_; // operating mode of sensor - bool sleep_mode_; bsec_library_return_t bsec_status_{BSEC_OK}; int8_t bme68x_status_{BME68X_OK}; int64_t last_time_ms_{0}; uint32_t millis_overflow_counter_{0}; - int64_t next_call_ns_{0}; std::queue<std::function<void()>> queue_; From a0159a274662b22a3c791961742cdf019894f041 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt <kevin.ahrendt@nabucasa.com> Date: Wed, 13 Nov 2024 12:39:02 -0500 Subject: [PATCH 111/282] [i2s_audio] Bugfix: Adjust I2S speaker setup priority (#7759) --- .../i2s_audio/speaker/i2s_audio_speaker.cpp | 11 +---------- .../components/i2s_audio/speaker/i2s_audio_speaker.h | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index c3f4566411..53b3cc8dc0 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -99,14 +99,6 @@ void I2SAudioSpeaker::setup() { this->mark_failed(); return; } - - this->i2s_event_queue_ = xQueueCreate(I2S_EVENT_QUEUE_COUNT, sizeof(i2s_event_t)); - - if (this->i2s_event_queue_ == nullptr) { - ESP_LOGE(TAG, "Failed to create I2S event queue"); - this->mark_failed(); - return; - } } void I2SAudioSpeaker::loop() { @@ -339,7 +331,7 @@ void I2SAudioSpeaker::speaker_task(void *params) { } void I2SAudioSpeaker::start() { - if (this->is_failed() || this->status_has_error()) + if (!this->is_ready() || this->is_failed() || this->status_has_error()) return; if ((this->state_ == speaker::STATE_STARTING) || (this->state_ == speaker::STATE_RUNNING)) return; @@ -519,7 +511,6 @@ void I2SAudioSpeaker::delete_task_(size_t buffer_size) { } xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::STATE_STOPPED); - xQueueReset(this->i2s_event_queue_); this->task_created_ = false; vTaskDelete(nullptr); diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index 8b7386ba58..2b90f39399 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -23,7 +23,7 @@ namespace i2s_audio { class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Component { public: - float get_setup_priority() const override { return esphome::setup_priority::LATE; } + float get_setup_priority() const override { return esphome::setup_priority::PROCESSOR; } void setup() override; void loop() override; From 15bfc4c91f5c567dc89fd3daced0db0339ee85ff Mon Sep 17 00:00:00 2001 From: Roving Ronin <108674933+Roving-Ronin@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:43:21 +1100 Subject: [PATCH 112/282] Update UNIT_VOLT_AMPS_REACTIVE = "var" (Currently 'VAR') (#7643) --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index c3c8712677..bb0100d348 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1095,7 +1095,7 @@ UNIT_STEPS = "steps" UNIT_VOLT = "V" UNIT_VOLT_AMPS = "VA" UNIT_VOLT_AMPS_HOURS = "VAh" -UNIT_VOLT_AMPS_REACTIVE = "VAR" +UNIT_VOLT_AMPS_REACTIVE = "var" UNIT_VOLT_AMPS_REACTIVE_HOURS = "VARh" UNIT_WATT = "W" UNIT_WATT_HOURS = "Wh" From 9bc7b74d0137aa351c906e0c38a913afa5835fee Mon Sep 17 00:00:00 2001 From: Felipe Santos <felipecassiors@gmail.com> Date: Wed, 13 Nov 2024 21:44:18 -0300 Subject: [PATCH 113/282] Fix reactive power unit of measurement from VAR to var (#7757) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/sdm_meter/sdm_meter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sdm_meter/sdm_meter.cpp b/esphome/components/sdm_meter/sdm_meter.cpp index 9c35d306ad..18e06e2b04 100644 --- a/esphome/components/sdm_meter/sdm_meter.cpp +++ b/esphome/components/sdm_meter/sdm_meter.cpp @@ -38,7 +38,7 @@ void SDMMeter::on_modbus_data(const std::vector<uint8_t> &data) { ESP_LOGD( TAG, - "SDMMeter Phase %c: V=%.3f V, I=%.3f A, Active P=%.3f W, Apparent P=%.3f VA, Reactive P=%.3f VAR, PF=%.3f, " + "SDMMeter Phase %c: V=%.3f V, I=%.3f A, Active P=%.3f W, Apparent P=%.3f VA, Reactive P=%.3f var, PF=%.3f, " "PA=%.3f °", i + 'A', voltage, current, active_power, apparent_power, reactive_power, power_factor, phase_angle); if (phase.voltage_sensor_ != nullptr) From 67a4e56fcfd7bfd8017c179c8004e6f0c67920d3 Mon Sep 17 00:00:00 2001 From: Jordan Zucker <jordan.zucker@gmail.com> Date: Wed, 13 Nov 2024 16:57:09 -0800 Subject: [PATCH 114/282] Disable bluetooth proxy during update (#7695) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 3 +++ tests/components/esp32_ble_tracker/common.yaml | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 74b4b9aa89..b86d32ee61 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -65,6 +65,9 @@ void ESP32BLETracker::setup() { [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { if (state == ota::OTA_STARTED) { this->stop_scan(); + for (auto *client : this->clients_) { + client->disconnect(); + } } }); #endif diff --git a/tests/components/esp32_ble_tracker/common.yaml b/tests/components/esp32_ble_tracker/common.yaml index ef23635c9e..018bbb42b3 100644 --- a/tests/components/esp32_ble_tracker/common.yaml +++ b/tests/components/esp32_ble_tracker/common.yaml @@ -39,3 +39,10 @@ esp32_ble_tracker: - then: - lambda: |- ESP_LOGD("ble_auto", "The scan has ended!"); + +wifi: + ssid: MySSID + password: password1 + +ota: + - platform: esphome From 754352b4d7e717fa04615ff9ee86d43955ceaaa8 Mon Sep 17 00:00:00 2001 From: Fabio Bonelli <fbonelli@gmail.com> Date: Thu, 14 Nov 2024 01:57:51 +0100 Subject: [PATCH 115/282] ld2420: fix typo in log message (#7758) --- esphome/components/ld2420/ld2420.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index e57fdbc84e..9d628cc14f 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -180,7 +180,7 @@ void LD2420Component::apply_config_action() { } void LD2420Component::factory_reset_action() { - ESP_LOGCONFIG(TAG, "Setiing factory defaults..."); + ESP_LOGCONFIG(TAG, "Setting factory defaults..."); if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); this->mark_failed(); From f4dc11477fb62014bc4397acdfbce61bfa57a90a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:21:43 +1300 Subject: [PATCH 116/282] Bump version to 2024.11.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index bb0100d348..51c8090e91 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.0b1" +__version__ = "2024.11.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From b29c1194089f4770b7a3e9ab99b12ac890c3e2cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:43:52 +0100 Subject: [PATCH 117/282] Bump codecov/codecov-action from 4 to 5 (#7771) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82a55d0e2a..f5af3ec9e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,7 +219,7 @@ jobs: . venv/bin/activate pytest -vv --cov-report=xml --tb=native tests - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} From e81191ebd2583d44b4237db8b7dad3ed18877d0d Mon Sep 17 00:00:00 2001 From: pethans <38168907+pethans@users.noreply.github.com> Date: Sun, 17 Nov 2024 10:47:29 -0800 Subject: [PATCH 118/282] TuyaFan control should use oscillation_type (#7776) Co-authored-by: Peter Hanson <phanson@whistler.lan> --- esphome/components/tuya/fan/tuya_fan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 8a613d0bae..9b132e0de6 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -86,7 +86,7 @@ void TuyaFan::control(const fan::FanCall &call) { if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) { if (this->oscillation_type_ == TuyaDatapointType::ENUM) { this->parent_->set_enum_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); - } else if (this->speed_type_ == TuyaDatapointType::BOOLEAN) { + } else if (this->oscillation_type_ == TuyaDatapointType::BOOLEAN) { this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); } } From 6e41c22e9d7f777a5c556725b1a54cf33a669e39 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 18 Nov 2024 20:44:39 +1300 Subject: [PATCH 119/282] Bump esphome-dashboard to 20241118.0 (#7782) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e11e629743..4bea8cf4ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ pyserial==3.5 platformio==6.1.16 # When updating platformio, also update Dockerfile esptool==4.7.0 click==8.1.7 -esphome-dashboard==20241025.0 +esphome-dashboard==20241118.0 aioesphomeapi==24.6.2 zeroconf==0.132.2 puremagic==1.27 From 50aeefc66299f675f62a9eb09a77ceded7c61950 Mon Sep 17 00:00:00 2001 From: pethans <38168907+pethans@users.noreply.github.com> Date: Sun, 17 Nov 2024 10:47:29 -0800 Subject: [PATCH 120/282] TuyaFan control should use oscillation_type (#7776) Co-authored-by: Peter Hanson <phanson@whistler.lan> --- esphome/components/tuya/fan/tuya_fan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 8a613d0bae..9b132e0de6 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -86,7 +86,7 @@ void TuyaFan::control(const fan::FanCall &call) { if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) { if (this->oscillation_type_ == TuyaDatapointType::ENUM) { this->parent_->set_enum_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); - } else if (this->speed_type_ == TuyaDatapointType::BOOLEAN) { + } else if (this->oscillation_type_ == TuyaDatapointType::BOOLEAN) { this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); } } From 585586780bf2fe6645eff64ed9528da8bd57e042 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 18 Nov 2024 20:44:39 +1300 Subject: [PATCH 121/282] Bump esphome-dashboard to 20241118.0 (#7782) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e11e629743..4bea8cf4ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ pyserial==3.5 platformio==6.1.16 # When updating platformio, also update Dockerfile esptool==4.7.0 click==8.1.7 -esphome-dashboard==20241025.0 +esphome-dashboard==20241118.0 aioesphomeapi==24.6.2 zeroconf==0.132.2 puremagic==1.27 From 1ed27b7cc0556679344f8fbe5101f1e338cc21a0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:04:30 +1300 Subject: [PATCH 122/282] Bump version to 2024.11.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 51c8090e91..e7edd8337e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.0b2" +__version__ = "2024.11.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 49e9c4333979bfeae77f25ee32c3065bc610b7a5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:54:19 +1300 Subject: [PATCH 123/282] [http_request] Feed watchdog timeout around http request functions (#7786) --- esphome/components/http_request/http_request_arduino.cpp | 2 ++ esphome/components/http_request/http_request_idf.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp index af1eb6f459..85a1312aaa 100644 --- a/esphome/components/http_request/http_request_arduino.cpp +++ b/esphome/components/http_request/http_request_arduino.cpp @@ -104,7 +104,9 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s static const size_t HEADER_COUNT = sizeof(header_keys) / sizeof(header_keys[0]); container->client_.collectHeaders(header_keys, HEADER_COUNT); + App.feed_wdt(); container->status_code = container->client_.sendRequest(method.c_str(), body.c_str()); + App.feed_wdt(); if (container->status_code < 0) { ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url.c_str(), HTTPClient::errorToString(container->status_code).c_str()); diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index c6c567b620..b449f046ee 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -117,8 +117,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin return nullptr; } + App.feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); + App.feed_wdt(); container->status_code = esp_http_client_get_status_code(client); + App.feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; @@ -148,8 +151,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin return nullptr; } + App.feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); + App.feed_wdt(); container->status_code = esp_http_client_get_status_code(client); + App.feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; From cf63d627fee4224f3652a0d101fae4cad1039da4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:39:28 +1300 Subject: [PATCH 124/282] Bump esphome-dashboard to 20241120.0 (#7787) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4bea8cf4ef..7bc1c895df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ pyserial==3.5 platformio==6.1.16 # When updating platformio, also update Dockerfile esptool==4.7.0 click==8.1.7 -esphome-dashboard==20241118.0 +esphome-dashboard==20241120.0 aioesphomeapi==24.6.2 zeroconf==0.132.2 puremagic==1.27 From eb8a2326ad2a9f32c52c94b52fbb3dfb4090c6c2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:54:19 +1300 Subject: [PATCH 125/282] [http_request] Feed watchdog timeout around http request functions (#7786) --- esphome/components/http_request/http_request_arduino.cpp | 2 ++ esphome/components/http_request/http_request_idf.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp index af1eb6f459..85a1312aaa 100644 --- a/esphome/components/http_request/http_request_arduino.cpp +++ b/esphome/components/http_request/http_request_arduino.cpp @@ -104,7 +104,9 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s static const size_t HEADER_COUNT = sizeof(header_keys) / sizeof(header_keys[0]); container->client_.collectHeaders(header_keys, HEADER_COUNT); + App.feed_wdt(); container->status_code = container->client_.sendRequest(method.c_str(), body.c_str()); + App.feed_wdt(); if (container->status_code < 0) { ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url.c_str(), HTTPClient::errorToString(container->status_code).c_str()); diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index c6c567b620..b449f046ee 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -117,8 +117,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin return nullptr; } + App.feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); + App.feed_wdt(); container->status_code = esp_http_client_get_status_code(client); + App.feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; @@ -148,8 +151,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin return nullptr; } + App.feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); + App.feed_wdt(); container->status_code = esp_http_client_get_status_code(client); + App.feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; From 872b8ee753ef5799894289ac941392383145d1d7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:39:28 +1300 Subject: [PATCH 126/282] Bump esphome-dashboard to 20241120.0 (#7787) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4bea8cf4ef..7bc1c895df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ pyserial==3.5 platformio==6.1.16 # When updating platformio, also update Dockerfile esptool==4.7.0 click==8.1.7 -esphome-dashboard==20241118.0 +esphome-dashboard==20241120.0 aioesphomeapi==24.6.2 zeroconf==0.132.2 puremagic==1.27 From ae46dcef7e51eb148dd0858834b02dc855979a1b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:50:30 +1300 Subject: [PATCH 127/282] Bump version to 2024.11.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index e7edd8337e..659695465e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.0b3" +__version__ = "2024.11.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From ef78c404dd19bb35af412a9dab0bd8e294cd7469 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Nov 2024 21:29:42 +1300 Subject: [PATCH 128/282] Bump version to 2024.11.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 659695465e..408dc52869 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.0b4" +__version__ = "2024.11.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 372d68a177196d6efb688b9f15943c5fd05dd202 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:27:23 -0500 Subject: [PATCH 129/282] [remote_base] Fix extra comma in dump raw (#7774) Co-authored-by: Jonathan Swoboda <jonathan.swoboda> --- esphome/components/remote_base/raw_protocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/remote_base/raw_protocol.cpp b/esphome/components/remote_base/raw_protocol.cpp index bdeb935dc4..ef0cb8454e 100644 --- a/esphome/components/remote_base/raw_protocol.cpp +++ b/esphome/components/remote_base/raw_protocol.cpp @@ -28,7 +28,7 @@ bool RawDumper::dump(RemoteReceiveData src) { ESP_LOGI(TAG, "%s", buffer); buffer_offset = 0; written = sprintf(buffer, " "); - if (i + 1 < src.size()) { + if (i + 1 < src.size() - 1) { written += sprintf(buffer + written, "%" PRId32 ", ", value); } else { written += sprintf(buffer + written, "%" PRId32, value); From 846b091aacbe72078996cd0c89419a40c32cb1f8 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:28:21 +0100 Subject: [PATCH 130/282] [nextion] New trigger `on_buffer_overflow` (#7772) --- esphome/components/nextion/automation.h | 7 +++++++ esphome/components/nextion/base_component.py | 1 + esphome/components/nextion/display.py | 15 +++++++++++++++ esphome/components/nextion/nextion.cpp | 8 +++++++- esphome/components/nextion/nextion.h | 7 +++++++ tests/components/nextion/test.esp32-ard.yaml | 3 +++ tests/components/nextion/test.esp32-c3-ard.yaml | 3 +++ tests/components/nextion/test.esp32-c3-idf.yaml | 3 +++ tests/components/nextion/test.esp32-idf.yaml | 3 +++ tests/components/nextion/test.esp8266-ard.yaml | 3 +++ tests/components/nextion/test.rp2040-ard.yaml | 3 +++ 11 files changed, 55 insertions(+), 1 deletion(-) diff --git a/esphome/components/nextion/automation.h b/esphome/components/nextion/automation.h index f51fe6b4f8..5182e07229 100644 --- a/esphome/components/nextion/automation.h +++ b/esphome/components/nextion/automation.h @@ -42,5 +42,12 @@ class TouchTrigger : public Trigger<uint8_t, uint8_t, bool> { } }; +class BufferOverflowTrigger : public Trigger<> { + public: + explicit BufferOverflowTrigger(Nextion *nextion) { + nextion->add_buffer_overflow_event_callback([this]() { this->trigger(); }); + } +}; + } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py index 2924f66d3c..9708379861 100644 --- a/esphome/components/nextion/base_component.py +++ b/esphome/components/nextion/base_component.py @@ -18,6 +18,7 @@ CONF_ON_SLEEP = "on_sleep" CONF_ON_WAKE = "on_wake" CONF_ON_SETUP = "on_setup" CONF_ON_PAGE = "on_page" +CONF_ON_BUFFER_OVERFLOW = "on_buffer_overflow" CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout" CONF_WAKE_UP_PAGE = "wake_up_page" CONF_START_UP_PAGE = "start_up_page" diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index e403ba7ae8..6f284376af 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -13,6 +13,7 @@ from esphome.const import ( from esphome.core import CORE from . import Nextion, nextion_ns, nextion_ref from .base_component import ( + CONF_ON_BUFFER_OVERFLOW, CONF_ON_SLEEP, CONF_ON_WAKE, CONF_ON_SETUP, @@ -36,6 +37,9 @@ SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template()) WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template()) PageTrigger = nextion_ns.class_("PageTrigger", automation.Trigger.template()) TouchTrigger = nextion_ns.class_("TouchTrigger", automation.Trigger.template()) +BufferOverflowTrigger = nextion_ns.class_( + "BufferOverflowTrigger", automation.Trigger.template() +) CONFIG_SCHEMA = ( display.BASIC_DISPLAY_SCHEMA.extend( @@ -68,6 +72,13 @@ CONFIG_SCHEMA = ( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TouchTrigger), } ), + cv.Optional(CONF_ON_BUFFER_OVERFLOW): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BufferOverflowTrigger + ), + } + ), cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.int_range(min=3, max=65535), cv.Optional(CONF_WAKE_UP_PAGE): cv.uint8_t, cv.Optional(CONF_START_UP_PAGE): cv.uint8_t, @@ -151,3 +162,7 @@ async def to_code(config): ], conf, ) + + for conf in config.get(CONF_ON_BUFFER_OVERFLOW, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index a80f6efc91..984db09c57 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -190,6 +190,10 @@ void Nextion::add_touch_event_callback(std::function<void(uint8_t, uint8_t, bool this->touch_callback_.add(std::move(callback)); } +void Nextion::add_buffer_overflow_event_callback(std::function<void()> &&callback) { + this->buffer_overflow_callback_.add(std::move(callback)); +} + void Nextion::update_all_components() { if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) return; @@ -458,7 +462,9 @@ void Nextion::process_nextion_commands_() { this->remove_from_q_(); break; case 0x24: // Serial Buffer overflow occurs - ESP_LOGW(TAG, "Nextion reported Serial Buffer overflow!"); + // Buffer will continue to receive the current instruction, all previous instructions are lost. + ESP_LOGE(TAG, "Nextion reported Serial Buffer overflow!"); + this->buffer_overflow_callback_.call(); break; case 0x65: { // touch event return data if (to_process_length != 3) { diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 732ee9b455..f539c79718 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -1134,6 +1134,12 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ void add_touch_event_callback(std::function<void(uint8_t, uint8_t, bool)> &&callback); + /** Add a callback to be notified when the nextion reports a buffer overflow. + * + * @param callback The void() callback. + */ + void add_buffer_overflow_event_callback(std::function<void()> &&callback); + void update_all_components(); /** @@ -1323,6 +1329,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe CallbackManager<void()> wake_callback_{}; CallbackManager<void(uint8_t)> page_callback_{}; CallbackManager<void(uint8_t, uint8_t, bool)> touch_callback_{}; + CallbackManager<void()> buffer_overflow_callback_{}; optional<nextion_writer_t> writer_; float brightness_{1.0}; diff --git a/tests/components/nextion/test.esp32-ard.yaml b/tests/components/nextion/test.esp32-ard.yaml index 27568ebc2a..ba76236fc6 100644 --- a/tests/components/nextion/test.esp32-ard.yaml +++ b/tests/components/nextion/test.esp32-ard.yaml @@ -58,3 +58,6 @@ display: on_page: then: lambda: 'ESP_LOGD("display","Display shows new page %u", x);' + on_buffer_overflow: + then: + logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.esp32-c3-ard.yaml b/tests/components/nextion/test.esp32-c3-ard.yaml index 5881d6e165..5d253268f8 100644 --- a/tests/components/nextion/test.esp32-c3-ard.yaml +++ b/tests/components/nextion/test.esp32-c3-ard.yaml @@ -58,3 +58,6 @@ display: on_page: then: lambda: 'ESP_LOGD("display","Display shows new page %u", x);' + on_buffer_overflow: + then: + logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.esp32-c3-idf.yaml b/tests/components/nextion/test.esp32-c3-idf.yaml index 5881d6e165..5d253268f8 100644 --- a/tests/components/nextion/test.esp32-c3-idf.yaml +++ b/tests/components/nextion/test.esp32-c3-idf.yaml @@ -58,3 +58,6 @@ display: on_page: then: lambda: 'ESP_LOGD("display","Display shows new page %u", x);' + on_buffer_overflow: + then: + logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.esp32-idf.yaml b/tests/components/nextion/test.esp32-idf.yaml index 27568ebc2a..ba76236fc6 100644 --- a/tests/components/nextion/test.esp32-idf.yaml +++ b/tests/components/nextion/test.esp32-idf.yaml @@ -58,3 +58,6 @@ display: on_page: then: lambda: 'ESP_LOGD("display","Display shows new page %u", x);' + on_buffer_overflow: + then: + logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.esp8266-ard.yaml b/tests/components/nextion/test.esp8266-ard.yaml index 5881d6e165..5d253268f8 100644 --- a/tests/components/nextion/test.esp8266-ard.yaml +++ b/tests/components/nextion/test.esp8266-ard.yaml @@ -58,3 +58,6 @@ display: on_page: then: lambda: 'ESP_LOGD("display","Display shows new page %u", x);' + on_buffer_overflow: + then: + logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.rp2040-ard.yaml b/tests/components/nextion/test.rp2040-ard.yaml index a1c5848ce6..9b04433095 100644 --- a/tests/components/nextion/test.rp2040-ard.yaml +++ b/tests/components/nextion/test.rp2040-ard.yaml @@ -53,3 +53,6 @@ display: on_page: then: lambda: 'ESP_LOGD("display","Display shows new page %u", x);' + on_buffer_overflow: + then: + logger.log: "Nextion reported a buffer overflow!" From 5e27a8df1f5fc3978939521d421d6330d1296128 Mon Sep 17 00:00:00 2001 From: Kjell Braden <afflux@pentabarf.de> Date: Wed, 20 Nov 2024 19:29:48 +0100 Subject: [PATCH 131/282] enable rp2040 for online_image (#7769) --- esphome/components/online_image/__init__.py | 1 + .../online_image/common-rp2040.yaml | 19 +++++++++++++++++++ .../online_image/test.rp2040-ard.yaml | 4 ++++ 3 files changed, 24 insertions(+) create mode 100644 tests/components/online_image/common-rp2040.yaml create mode 100644 tests/components/online_image/test.rp2040-ard.yaml diff --git a/esphome/components/online_image/__init__.py b/esphome/components/online_image/__init__.py index dfb10137aa..be1bfb4a00 100644 --- a/esphome/components/online_image/__init__.py +++ b/esphome/components/online_image/__init__.py @@ -98,6 +98,7 @@ CONFIG_SCHEMA = cv.Schema( # esp8266_arduino=cv.Version(2, 7, 0), esp32_arduino=cv.Version(0, 0, 0), esp_idf=cv.Version(4, 0, 0), + rp2040_arduino=cv.Version(0, 0, 0), ), ) ) diff --git a/tests/components/online_image/common-rp2040.yaml b/tests/components/online_image/common-rp2040.yaml new file mode 100644 index 0000000000..16bb2b2c44 --- /dev/null +++ b/tests/components/online_image/common-rp2040.yaml @@ -0,0 +1,19 @@ +<<: !include common.yaml + +spi: + - id: spi_main_lcd + clk_pin: 18 + mosi_pin: 19 + miso_pin: 16 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 20 + dc_pin: 17 + reset_pin: 21 + invert_colors: true + lambda: |- + it.fill(Color(0, 0, 0)); + it.image(0, 0, id(online_rgba_image)); diff --git a/tests/components/online_image/test.rp2040-ard.yaml b/tests/components/online_image/test.rp2040-ard.yaml new file mode 100644 index 0000000000..d10f36b4e9 --- /dev/null +++ b/tests/components/online_image/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +<<: !include common-rp2040.yaml + +http_request: + verify_ssl: false From 6d4f787f67b6d4dc16fcdbdca3a1984db8d9df62 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:10:28 +1100 Subject: [PATCH 132/282] [http_request] Fix within context with parameters. (Bugfix) (#7790) --- esphome/components/http_request/http_request.h | 2 +- tests/components/http_request/common.yaml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 4ed2c834f8..b2ce718ec4 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -189,7 +189,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> { if (container == nullptr) { for (auto *trigger : this->error_triggers_) - trigger->trigger(x...); + trigger->trigger(); return; } diff --git a/tests/components/http_request/common.yaml b/tests/components/http_request/common.yaml index 593b85e435..8408f27a05 100644 --- a/tests/components/http_request/common.yaml +++ b/tests/components/http_request/common.yaml @@ -39,6 +39,14 @@ http_request: timeout: 10s verify_ssl: ${verify_ssl} +script: + - id: does_not_compile + parameters: + api_url: string + then: + - http_request.get: + url: "http://google.com" + ota: - platform: http_request on_begin: From fbb9967117d248951906b0bb19ef200e7f546360 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Nov 2024 19:22:02 +1300 Subject: [PATCH 133/282] [rtttl] Clamp gain between 0 and 1 (#7793) --- esphome/components/rtttl/rtttl.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/esphome/components/rtttl/rtttl.h b/esphome/components/rtttl/rtttl.h index 10c290c5fb..420948bfbf 100644 --- a/esphome/components/rtttl/rtttl.h +++ b/esphome/components/rtttl/rtttl.h @@ -40,13 +40,7 @@ class Rtttl : public Component { void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; } #endif float get_gain() { return gain_; } - void set_gain(float gain) { - if (gain < 0.1f) - gain = 0.1f; - if (gain > 1.0f) - gain = 1.0f; - this->gain_ = gain; - } + void set_gain(float gain) { this->gain_ = clamp(gain, 0.0f, 1.0f); } void play(std::string rtttl); void stop(); void dump_config() override; From 6bcbbcce02ca9441cffe6d7f559eceb92ab76811 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Nov 2024 21:10:20 +1300 Subject: [PATCH 134/282] [speaker] Add missing auto-load for ``audio`` (#7794) --- esphome/components/speaker/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/speaker/__init__.py b/esphome/components/speaker/__init__.py index 7a668dc2f3..948fe4b534 100644 --- a/esphome/components/speaker/__init__.py +++ b/esphome/components/speaker/__init__.py @@ -7,6 +7,7 @@ from esphome.const import CONF_DATA, CONF_ID, CONF_VOLUME from esphome.core import CORE from esphome.coroutine import coroutine_with_priority +AUTO_LOAD = ["audio"] CODEOWNERS = ["@jesserockz", "@kahrendt"] IS_PLATFORM_COMPONENT = True From 03ae6b2c1b6e4ae2a509c109c2381214e0cdb131 Mon Sep 17 00:00:00 2001 From: Manuel Kasper <mk@neon1.net> Date: Thu, 21 Nov 2024 10:46:49 +0100 Subject: [PATCH 135/282] [qspi_dbi] Fix garbled graphics on RM690B0 (#7795) --- esphome/components/qspi_dbi/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/qspi_dbi/models.py b/esphome/components/qspi_dbi/models.py index 071ea72d73..cbd9c4663f 100644 --- a/esphome/components/qspi_dbi/models.py +++ b/esphome/components/qspi_dbi/models.py @@ -55,6 +55,7 @@ chip.cmd(PAGESEL, 0x00) chip.cmd(0xC2, 0x00) chip.delay(10) chip.cmd(TEON, 0x00) +chip.cmd(PIXFMT, 0x55) chip = DriverChip("AXS15231") chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) From ccf2854b612a1659c27cedeb22eaf49b871f157c Mon Sep 17 00:00:00 2001 From: Spencer Owen <owenspencer@gmail.com> Date: Thu, 21 Nov 2024 12:24:10 -0700 Subject: [PATCH 136/282] Check for min_version earlier in validation (#7797) --- esphome/core/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index 8c130eb6db..367e61c413 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -184,6 +184,9 @@ PRELOAD_CONFIG_SCHEMA = cv.Schema( 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_MIN_VERSION, default=ESPHOME_VERSION): cv.All( + cv.version_number, cv.validate_esphome_version + ), }, extra=cv.ALLOW_EXTRA, ) From 3232866dc337b0e3332f9b7283d834f025e2a642 Mon Sep 17 00:00:00 2001 From: Alain Turbide <7193213+Dilbert66@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:39:32 -0500 Subject: [PATCH 137/282] Fix for OTA mode not activating in safe_mode when OTA section has an on_xxxx action (#7796) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esphome/ota/__init__.py | 8 ++++---- esphome/components/ota/__init__.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index a852d8d001..86006e3e18 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -1,10 +1,9 @@ import logging import esphome.codegen as cg -import esphome.config_validation as cv -import esphome.final_validate as fv -from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent +from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code from esphome.config_helpers import merge_config +import esphome.config_validation as cv from esphome.const import ( CONF_ESPHOME, CONF_ID, @@ -18,6 +17,7 @@ from esphome.const import ( CONF_VERSION, ) from esphome.core import coroutine_with_priority +import esphome.final_validate as fv _LOGGER = logging.getLogger(__name__) @@ -124,7 +124,6 @@ FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate @coroutine_with_priority(52.0) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await ota_to_code(var, config) cg.add(var.set_port(config[CONF_PORT])) if CONF_PASSWORD in config: cg.add(var.set_auth_password(config[CONF_PASSWORD])) @@ -132,3 +131,4 @@ async def to_code(config): cg.add_define("USE_OTA_VERSION", config[CONF_VERSION]) await cg.register_component(var, config) + await ota_to_code(var, config) diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index d9917a2aae..627c55e910 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -92,6 +92,7 @@ async def to_code(config): async def ota_to_code(var, config): + await cg.past_safe_mode() use_state_callback = False for conf in config.get(CONF_ON_STATE_CHANGE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) From 122ff731ef43206212976da35354494e928639f6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" <nick@koston.org> Date: Thu, 21 Nov 2024 14:41:31 -0600 Subject: [PATCH 138/282] Ensure storage I/O for ignored devices runs in the executor (#7792) --- esphome/dashboard/core.py | 2 +- esphome/dashboard/web_server.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/dashboard/core.py b/esphome/dashboard/core.py index 563ca1506d..f53cb7ffb1 100644 --- a/esphome/dashboard/core.py +++ b/esphome/dashboard/core.py @@ -103,7 +103,7 @@ class ESPHomeDashboard: self.loop = asyncio.get_running_loop() self.ping_request = asyncio.Event() self.entries = DashboardEntries(self) - self.load_ignored_devices() + await self.loop.run_in_executor(None, self.load_ignored_devices) def load_ignored_devices(self) -> None: storage_path = Path(ignored_devices_storage_path()) diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 07f7f019f8..0fed8e9c53 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -544,7 +544,7 @@ class ImportRequestHandler(BaseHandler): class IgnoreDeviceRequestHandler(BaseHandler): @authenticated - def post(self) -> None: + async def post(self) -> None: dashboard = DASHBOARD try: args = json.loads(self.request.body.decode()) @@ -576,7 +576,8 @@ class IgnoreDeviceRequestHandler(BaseHandler): else: dashboard.ignored_devices.discard(ignored_device.device_name) - dashboard.save_ignored_devices() + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, dashboard.save_ignored_devices) self.set_status(204) self.finish() From 888b2379647049712a94143074b95c79e1f8392b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:10:28 +1100 Subject: [PATCH 139/282] [http_request] Fix within context with parameters. (Bugfix) (#7790) --- esphome/components/http_request/http_request.h | 2 +- tests/components/http_request/common.yaml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 4ed2c834f8..b2ce718ec4 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -189,7 +189,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> { if (container == nullptr) { for (auto *trigger : this->error_triggers_) - trigger->trigger(x...); + trigger->trigger(); return; } diff --git a/tests/components/http_request/common.yaml b/tests/components/http_request/common.yaml index 593b85e435..8408f27a05 100644 --- a/tests/components/http_request/common.yaml +++ b/tests/components/http_request/common.yaml @@ -39,6 +39,14 @@ http_request: timeout: 10s verify_ssl: ${verify_ssl} +script: + - id: does_not_compile + parameters: + api_url: string + then: + - http_request.get: + url: "http://google.com" + ota: - platform: http_request on_begin: From a0693060e401153409a2095b0a29248bd23dee0e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Nov 2024 19:22:02 +1300 Subject: [PATCH 140/282] [rtttl] Clamp gain between 0 and 1 (#7793) --- esphome/components/rtttl/rtttl.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/esphome/components/rtttl/rtttl.h b/esphome/components/rtttl/rtttl.h index 10c290c5fb..420948bfbf 100644 --- a/esphome/components/rtttl/rtttl.h +++ b/esphome/components/rtttl/rtttl.h @@ -40,13 +40,7 @@ class Rtttl : public Component { void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; } #endif float get_gain() { return gain_; } - void set_gain(float gain) { - if (gain < 0.1f) - gain = 0.1f; - if (gain > 1.0f) - gain = 1.0f; - this->gain_ = gain; - } + void set_gain(float gain) { this->gain_ = clamp(gain, 0.0f, 1.0f); } void play(std::string rtttl); void stop(); void dump_config() override; From f04e3de7b8d103a21cbe06f1b5c78e7a78be3429 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Nov 2024 21:10:20 +1300 Subject: [PATCH 141/282] [speaker] Add missing auto-load for ``audio`` (#7794) --- esphome/components/speaker/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/speaker/__init__.py b/esphome/components/speaker/__init__.py index 7a668dc2f3..948fe4b534 100644 --- a/esphome/components/speaker/__init__.py +++ b/esphome/components/speaker/__init__.py @@ -7,6 +7,7 @@ from esphome.const import CONF_DATA, CONF_ID, CONF_VOLUME from esphome.core import CORE from esphome.coroutine import coroutine_with_priority +AUTO_LOAD = ["audio"] CODEOWNERS = ["@jesserockz", "@kahrendt"] IS_PLATFORM_COMPONENT = True From 489d0d20d2279257574038ab798755242441c18b Mon Sep 17 00:00:00 2001 From: Manuel Kasper <mk@neon1.net> Date: Thu, 21 Nov 2024 10:46:49 +0100 Subject: [PATCH 142/282] [qspi_dbi] Fix garbled graphics on RM690B0 (#7795) --- esphome/components/qspi_dbi/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/qspi_dbi/models.py b/esphome/components/qspi_dbi/models.py index 071ea72d73..cbd9c4663f 100644 --- a/esphome/components/qspi_dbi/models.py +++ b/esphome/components/qspi_dbi/models.py @@ -55,6 +55,7 @@ chip.cmd(PAGESEL, 0x00) chip.cmd(0xC2, 0x00) chip.delay(10) chip.cmd(TEON, 0x00) +chip.cmd(PIXFMT, 0x55) chip = DriverChip("AXS15231") chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) From ea424b069979e5c53438f95abdd43acdc521cd78 Mon Sep 17 00:00:00 2001 From: Spencer Owen <owenspencer@gmail.com> Date: Thu, 21 Nov 2024 12:24:10 -0700 Subject: [PATCH 143/282] Check for min_version earlier in validation (#7797) --- esphome/core/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index 8c130eb6db..367e61c413 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -184,6 +184,9 @@ PRELOAD_CONFIG_SCHEMA = cv.Schema( 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_MIN_VERSION, default=ESPHOME_VERSION): cv.All( + cv.version_number, cv.validate_esphome_version + ), }, extra=cv.ALLOW_EXTRA, ) From 1c1f3f7c55607c504226f7a1ed52794a0482b123 Mon Sep 17 00:00:00 2001 From: Alain Turbide <7193213+Dilbert66@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:39:32 -0500 Subject: [PATCH 144/282] Fix for OTA mode not activating in safe_mode when OTA section has an on_xxxx action (#7796) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esphome/ota/__init__.py | 8 ++++---- esphome/components/ota/__init__.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index a852d8d001..86006e3e18 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -1,10 +1,9 @@ import logging import esphome.codegen as cg -import esphome.config_validation as cv -import esphome.final_validate as fv -from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent +from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code from esphome.config_helpers import merge_config +import esphome.config_validation as cv from esphome.const import ( CONF_ESPHOME, CONF_ID, @@ -18,6 +17,7 @@ from esphome.const import ( CONF_VERSION, ) from esphome.core import coroutine_with_priority +import esphome.final_validate as fv _LOGGER = logging.getLogger(__name__) @@ -124,7 +124,6 @@ FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate @coroutine_with_priority(52.0) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await ota_to_code(var, config) cg.add(var.set_port(config[CONF_PORT])) if CONF_PASSWORD in config: cg.add(var.set_auth_password(config[CONF_PASSWORD])) @@ -132,3 +131,4 @@ async def to_code(config): cg.add_define("USE_OTA_VERSION", config[CONF_VERSION]) await cg.register_component(var, config) + await ota_to_code(var, config) diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index d9917a2aae..627c55e910 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -92,6 +92,7 @@ async def to_code(config): async def ota_to_code(var, config): + await cg.past_safe_mode() use_state_callback = False for conf in config.get(CONF_ON_STATE_CHANGE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) From e51f3d9498d377ed45d15665e1c91f91d6a2aae0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" <nick@koston.org> Date: Thu, 21 Nov 2024 14:41:31 -0600 Subject: [PATCH 145/282] Ensure storage I/O for ignored devices runs in the executor (#7792) --- esphome/dashboard/core.py | 2 +- esphome/dashboard/web_server.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/dashboard/core.py b/esphome/dashboard/core.py index 563ca1506d..f53cb7ffb1 100644 --- a/esphome/dashboard/core.py +++ b/esphome/dashboard/core.py @@ -103,7 +103,7 @@ class ESPHomeDashboard: self.loop = asyncio.get_running_loop() self.ping_request = asyncio.Event() self.entries = DashboardEntries(self) - self.load_ignored_devices() + await self.loop.run_in_executor(None, self.load_ignored_devices) def load_ignored_devices(self) -> None: storage_path = Path(ignored_devices_storage_path()) diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 07f7f019f8..0fed8e9c53 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -544,7 +544,7 @@ class ImportRequestHandler(BaseHandler): class IgnoreDeviceRequestHandler(BaseHandler): @authenticated - def post(self) -> None: + async def post(self) -> None: dashboard = DASHBOARD try: args = json.loads(self.request.body.decode()) @@ -576,7 +576,8 @@ class IgnoreDeviceRequestHandler(BaseHandler): else: dashboard.ignored_devices.discard(ignored_device.device_name) - dashboard.save_ignored_devices() + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, dashboard.save_ignored_devices) self.set_status(204) self.finish() From 2cc2a2153b2c219d1bd07a9fe427b16d38e2e6f7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:08:00 +1300 Subject: [PATCH 146/282] Bump version to 2024.11.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 408dc52869..d14cdecb23 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.0" +__version__ = "2024.11.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From dea297c8d7d73f0c4f366e58994cd6a9a3995e11 Mon Sep 17 00:00:00 2001 From: Petr Kejval <petr.kejval6@gmail.com> Date: Sat, 23 Nov 2024 05:52:02 +0100 Subject: [PATCH 147/282] [nextion] Add publish actions (#7646) Co-authored-by: Keith Burzinski <kbx81x@gmail.com> --- esphome/components/nextion/__init__.py | 2 + esphome/components/nextion/automation.h | 75 ++++- .../nextion/binary_sensor/__init__.py | 45 ++- esphome/components/nextion/sensor/__init__.py | 43 ++- esphome/components/nextion/switch/__init__.py | 40 ++- .../nextion/text_sensor/__init__.py | 39 ++- tests/components/nextion/common.yaml | 293 ++++++++++++++++++ tests/components/nextion/test.esp32-ard.yaml | 65 +--- .../components/nextion/test.esp32-c3-ard.yaml | 65 +--- .../components/nextion/test.esp32-c3-idf.yaml | 65 +--- tests/components/nextion/test.esp32-idf.yaml | 65 +--- .../components/nextion/test.esp8266-ard.yaml | 65 +--- tests/components/nextion/test.rp2040-ard.yaml | 61 +--- 13 files changed, 558 insertions(+), 365 deletions(-) create mode 100644 tests/components/nextion/common.yaml diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index 924d58198d..fb75daf4ba 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -6,3 +6,5 @@ Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) nextion_ref = Nextion.operator("ref") CONF_NEXTION_ID = "nextion_id" +CONF_PUBLISH_STATE = "publish_state" +CONF_SEND_TO_NEXTION = "send_to_nextion" diff --git a/esphome/components/nextion/automation.h b/esphome/components/nextion/automation.h index 5182e07229..65f1fd0058 100644 --- a/esphome/components/nextion/automation.h +++ b/esphome/components/nextion/automation.h @@ -5,6 +5,13 @@ namespace esphome { namespace nextion { +class BufferOverflowTrigger : public Trigger<> { + public: + explicit BufferOverflowTrigger(Nextion *nextion) { + nextion->add_buffer_overflow_event_callback([this]() { this->trigger(); }); + } +}; + class SetupTrigger : public Trigger<> { public: explicit SetupTrigger(Nextion *nextion) { @@ -42,11 +49,73 @@ class TouchTrigger : public Trigger<uint8_t, uint8_t, bool> { } }; -class BufferOverflowTrigger : public Trigger<> { +template<typename... Ts> class NextionPublishFloatAction : public Action<Ts...> { public: - explicit BufferOverflowTrigger(Nextion *nextion) { - nextion->add_buffer_overflow_event_callback([this]() { this->trigger(); }); + explicit NextionPublishFloatAction(NextionComponent *component) : component_(component) {} + + TEMPLATABLE_VALUE(float, state) + TEMPLATABLE_VALUE(bool, publish_state) + TEMPLATABLE_VALUE(bool, send_to_nextion) + + void play(Ts... x) override { + this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...), + this->send_to_nextion_.value(x...)); } + + void set_state(std::function<void(Ts..., float)> state) { this->state_ = state; } + void set_publish_state(std::function<void(Ts..., bool)> publish_state) { this->publish_state_ = publish_state; } + void set_send_to_nextion(std::function<void(Ts..., bool)> send_to_nextion) { + this->send_to_nextion_ = send_to_nextion; + } + + protected: + NextionComponent *component_; +}; + +template<typename... Ts> class NextionPublishTextAction : public Action<Ts...> { + public: + explicit NextionPublishTextAction(NextionComponent *component) : component_(component) {} + + TEMPLATABLE_VALUE(const char *, state) + TEMPLATABLE_VALUE(bool, publish_state) + TEMPLATABLE_VALUE(bool, send_to_nextion) + + void play(Ts... x) override { + this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...), + this->send_to_nextion_.value(x...)); + } + + void set_state(std::function<void(Ts..., const char *)> state) { this->state_ = state; } + void set_publish_state(std::function<void(Ts..., bool)> publish_state) { this->publish_state_ = publish_state; } + void set_send_to_nextion(std::function<void(Ts..., bool)> send_to_nextion) { + this->send_to_nextion_ = send_to_nextion; + } + + protected: + NextionComponent *component_; +}; + +template<typename... Ts> class NextionPublishBoolAction : public Action<Ts...> { + public: + explicit NextionPublishBoolAction(NextionComponent *component) : component_(component) {} + + TEMPLATABLE_VALUE(bool, state) + TEMPLATABLE_VALUE(bool, publish_state) + TEMPLATABLE_VALUE(bool, send_to_nextion) + + void play(Ts... x) override { + this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...), + this->send_to_nextion_.value(x...)); + } + + void set_state(std::function<void(Ts..., bool)> state) { this->state_ = state; } + void set_publish_state(std::function<void(Ts..., bool)> publish_state) { this->publish_state_ = publish_state; } + void set_send_to_nextion(std::function<void(Ts..., bool)> send_to_nextion) { + this->send_to_nextion_ = send_to_nextion; + } + + protected: + NextionComponent *component_; }; } // namespace nextion diff --git a/esphome/components/nextion/binary_sensor/__init__.py b/esphome/components/nextion/binary_sensor/__init__.py index 8b4a45cc60..a257587e13 100644 --- a/esphome/components/nextion/binary_sensor/__init__.py +++ b/esphome/components/nextion/binary_sensor/__init__.py @@ -1,9 +1,16 @@ +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID -from .. import nextion_ns, CONF_NEXTION_ID +from esphome.const import ( + CONF_ID, + CONF_STATE, + CONF_COMPONENT_ID, + CONF_PAGE_ID, +) + +from .. import nextion_ns, CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION from ..base_component import ( @@ -19,6 +26,10 @@ NextionBinarySensor = nextion_ns.class_( "NextionBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent ) +NextionPublishBoolAction = nextion_ns.class_( + "NextionPublishBoolAction", automation.Action +) + CONFIG_SCHEMA = cv.All( binary_sensor.binary_sensor_schema(NextionBinarySensor) .extend( @@ -52,3 +63,33 @@ async def to_code(config): if CONF_COMPONENT_NAME in config or CONF_VARIABLE_NAME in config: await setup_component_core_(var, config, ".val") cg.add(hub.register_binarysensor_component(var)) + + +@automation.register_action( + "binary_sensor.nextion.publish", + NextionPublishBoolAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(NextionBinarySensor), + cv.Required(CONF_STATE): cv.templatable(cv.boolean), + cv.Optional(CONF_PUBLISH_STATE, default="true"): cv.templatable(cv.boolean), + cv.Optional(CONF_SEND_TO_NEXTION, default="true"): cv.templatable( + cv.boolean + ), + } + ), +) +async def sensor_nextion_publish_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[CONF_STATE], args, bool) + cg.add(var.set_state(template_)) + + template_ = await cg.templatable(config[CONF_PUBLISH_STATE], args, bool) + cg.add(var.set_publish_state(template_)) + + template_ = await cg.templatable(config[CONF_SEND_TO_NEXTION], args, bool) + cg.add(var.set_send_to_nextion(template_)) + + return var diff --git a/esphome/components/nextion/sensor/__init__.py b/esphome/components/nextion/sensor/__init__.py index eefbe34d58..1058c2a04b 100644 --- a/esphome/components/nextion/sensor/__init__.py +++ b/esphome/components/nextion/sensor/__init__.py @@ -1,12 +1,11 @@ +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import ( - CONF_ID, - CONF_COMPONENT_ID, -) -from .. import nextion_ns, CONF_NEXTION_ID +from esphome.const import CONF_ID, CONF_COMPONENT_ID, CONF_STATE + +from .. import nextion_ns, CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION from ..base_component import ( setup_component_core_, @@ -25,6 +24,10 @@ CODEOWNERS = ["@senexcrenshaw"] NextionSensor = nextion_ns.class_("NextionSensor", sensor.Sensor, cg.PollingComponent) +NextionPublishFloatAction = nextion_ns.class_( + "NextionPublishFloatAction", automation.Action +) + def CheckWaveID(value): value = cv.int_(value) @@ -95,3 +98,33 @@ async def to_code(config): if CONF_WAVE_MAX_LENGTH in config: cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH])) + + +@automation.register_action( + "sensor.nextion.publish", + NextionPublishFloatAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(NextionSensor), + cv.Required(CONF_STATE): cv.templatable(cv.float_), + cv.Optional(CONF_PUBLISH_STATE, default="true"): cv.templatable(cv.boolean), + cv.Optional(CONF_SEND_TO_NEXTION, default="true"): cv.templatable( + cv.boolean + ), + } + ), +) +async def sensor_nextion_publish_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[CONF_STATE], args, float) + cg.add(var.set_state(template_)) + + template_ = await cg.templatable(config[CONF_PUBLISH_STATE], args, bool) + cg.add(var.set_publish_state(template_)) + + template_ = await cg.templatable(config[CONF_SEND_TO_NEXTION], args, bool) + cg.add(var.set_send_to_nextion(template_)) + + return var diff --git a/esphome/components/nextion/switch/__init__.py b/esphome/components/nextion/switch/__init__.py index 91ab0cc81f..de1a061478 100644 --- a/esphome/components/nextion/switch/__init__.py +++ b/esphome/components/nextion/switch/__init__.py @@ -1,9 +1,11 @@ +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch -from esphome.const import CONF_ID -from .. import nextion_ns, CONF_NEXTION_ID +from esphome.const import CONF_ID, CONF_STATE + +from .. import nextion_ns, CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION from ..base_component import ( setup_component_core_, @@ -16,6 +18,10 @@ CODEOWNERS = ["@senexcrenshaw"] NextionSwitch = nextion_ns.class_("NextionSwitch", switch.Switch, cg.PollingComponent) +NextionPublishBoolAction = nextion_ns.class_( + "NextionPublishBoolAction", automation.Action +) + CONFIG_SCHEMA = cv.All( switch.switch_schema(NextionSwitch) .extend(CONFIG_SWITCH_COMPONENT_SCHEMA) @@ -33,3 +39,33 @@ async def to_code(config): cg.add(hub.register_switch_component(var)) await setup_component_core_(var, config, ".val") + + +@automation.register_action( + "switch.nextion.publish", + NextionPublishBoolAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(NextionSwitch), + cv.Required(CONF_STATE): cv.templatable(cv.boolean), + cv.Optional(CONF_PUBLISH_STATE, default="true"): cv.templatable(cv.boolean), + cv.Optional(CONF_SEND_TO_NEXTION, default="true"): cv.templatable( + cv.boolean + ), + } + ), +) +async def sensor_nextion_publish_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[CONF_STATE], args, bool) + cg.add(var.set_state(template_)) + + template_ = await cg.templatable(config[CONF_PUBLISH_STATE], args, bool) + cg.add(var.set_publish_state(template_)) + + template_ = await cg.templatable(config[CONF_SEND_TO_NEXTION], args, bool) + cg.add(var.set_send_to_nextion(template_)) + + return var diff --git a/esphome/components/nextion/text_sensor/__init__.py b/esphome/components/nextion/text_sensor/__init__.py index 826ff2354e..793397b1f4 100644 --- a/esphome/components/nextion/text_sensor/__init__.py +++ b/esphome/components/nextion/text_sensor/__init__.py @@ -1,9 +1,10 @@ +from esphome import automation from esphome.components import text_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_STATE -from .. import nextion_ns, CONF_NEXTION_ID +from .. import nextion_ns, CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION from ..base_component import ( setup_component_core_, @@ -16,6 +17,10 @@ NextionTextSensor = nextion_ns.class_( "NextionTextSensor", text_sensor.TextSensor, cg.PollingComponent ) +NextionPublishTextAction = nextion_ns.class_( + "NextionPublishTextAction", automation.Action +) + CONFIG_SCHEMA = ( text_sensor.text_sensor_schema(NextionTextSensor) .extend(CONFIG_TEXT_COMPONENT_SCHEMA) @@ -32,3 +37,33 @@ async def to_code(config): cg.add(hub.register_textsensor_component(var)) await setup_component_core_(var, config, ".txt") + + +@automation.register_action( + "text_sensor.nextion.publish", + NextionPublishTextAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(NextionTextSensor), + cv.Required(CONF_STATE): cv.templatable(cv.string_strict), + cv.Optional(CONF_PUBLISH_STATE, default="true"): cv.templatable(cv.boolean), + cv.Optional(CONF_SEND_TO_NEXTION, default="true"): cv.templatable( + cv.boolean + ), + } + ), +) +async def sensor_nextion_publish_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[CONF_STATE], args, cg.const_char_ptr) + cg.add(var.set_state(template_)) + + template_ = await cg.templatable(config[CONF_PUBLISH_STATE], args, cg.bool_) + cg.add(var.set_publish_state(template_)) + + template_ = await cg.templatable(config[CONF_SEND_TO_NEXTION], args, cg.bool_) + cg.add(var.set_send_to_nextion(template_)) + + return var diff --git a/tests/components/nextion/common.yaml b/tests/components/nextion/common.yaml new file mode 100644 index 0000000000..e84cd08422 --- /dev/null +++ b/tests/components/nextion/common.yaml @@ -0,0 +1,293 @@ +esphome: + on_boot: + # Binary sensor publish action tests + - binary_sensor.nextion.publish: + id: r0_sensor + state: True + + - binary_sensor.nextion.publish: + id: r0_sensor + state: True + publish_state: True + send_to_nextion: True + + - binary_sensor.nextion.publish: + id: r0_sensor + state: True + publish_state: False + send_to_nextion: True + + - binary_sensor.nextion.publish: + id: r0_sensor + state: True + publish_state: True + send_to_nextion: False + + - binary_sensor.nextion.publish: + id: r0_sensor + state: True + publish_state: False + send_to_nextion: False + + # Templated + - binary_sensor.nextion.publish: + id: r0_sensor + state: !lambda 'return true;' + + - binary_sensor.nextion.publish: + id: r0_sensor + state: !lambda 'return true;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return true;' + + - binary_sensor.nextion.publish: + id: r0_sensor + state: !lambda 'return true;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return true;' + + - binary_sensor.nextion.publish: + id: r0_sensor + state: !lambda 'return true;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return false;' + + - binary_sensor.nextion.publish: + id: r0_sensor + state: !lambda 'return true;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return false;' + + # Sensor publish action tests + - sensor.nextion.publish: + id: testnumber + state: 42.0 + + - sensor.nextion.publish: + id: testnumber + state: 42.0 + publish_state: True + send_to_nextion: True + + - sensor.nextion.publish: + id: testnumber + state: 42.0 + publish_state: False + send_to_nextion: True + + - sensor.nextion.publish: + id: testnumber + state: 42.0 + publish_state: True + send_to_nextion: False + + - sensor.nextion.publish: + id: testnumber + state: 42.0 + publish_state: False + send_to_nextion: False + + # Templated + - sensor.nextion.publish: + id: testnumber + state: !lambda 'return 42.0;' + + - sensor.nextion.publish: + id: testnumber + state: !lambda 'return 42.0;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return true;' + + - sensor.nextion.publish: + id: testnumber + state: !lambda 'return 42.0;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return true;' + + - sensor.nextion.publish: + id: testnumber + state: !lambda 'return 42.0;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return false;' + + - sensor.nextion.publish: + id: testnumber + state: !lambda 'return 42.0;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return false;' + + # Switch publish action tests + - switch.nextion.publish: + id: r0 + state: True + + - switch.nextion.publish: + id: r0 + state: True + publish_state: true + send_to_nextion: true + + - switch.nextion.publish: + id: r0 + state: True + publish_state: false + send_to_nextion: true + + - switch.nextion.publish: + id: r0 + state: True + publish_state: true + send_to_nextion: false + + - switch.nextion.publish: + id: r0 + state: True + publish_state: false + send_to_nextion: false + + # Templated + - switch.nextion.publish: + id: r0 + state: !lambda 'return true;' + + - switch.nextion.publish: + id: r0 + state: !lambda 'return true;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return true;' + + - switch.nextion.publish: + id: r0 + state: !lambda 'return true;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return true;' + + - switch.nextion.publish: + id: r0 + state: !lambda 'return true;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return false;' + + - switch.nextion.publish: + id: r0 + state: !lambda 'return true;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return false;' + + # Test sensor publish action tests + - text_sensor.nextion.publish: + id: text0 + state: 'Test' + publish_state: true + send_to_nextion: true + + - text_sensor.nextion.publish: + id: text0 + state: 'Test' + publish_state: false + send_to_nextion: true + + - text_sensor.nextion.publish: + id: text0 + state: 'Test' + publish_state: true + send_to_nextion: false + + - text_sensor.nextion.publish: + id: text0 + state: 'Test' + publish_state: false + send_to_nextion: false + + # Templated + - text_sensor.nextion.publish: + id: text0 + state: !lambda 'return "Test";' + + - text_sensor.nextion.publish: + id: text0 + state: !lambda 'return "Test";' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return true;' + + - text_sensor.nextion.publish: + id: text0 + state: !lambda 'return "Test";' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return true;' + + - text_sensor.nextion.publish: + id: text0 + state: !lambda 'return "Test";' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return false;' + + - text_sensor.nextion.publish: + id: text0 + state: !lambda 'return "Test";' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return false;' + +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + id: main_lcd + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' + on_buffer_overflow: + then: + logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.esp32-ard.yaml b/tests/components/nextion/test.esp32-ard.yaml index ba76236fc6..d5e02b8b85 100644 --- a/tests/components/nextion/test.esp32-ard.yaml +++ b/tests/components/nextion/test.esp32-ard.yaml @@ -1,63 +1,10 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 -uart: - - id: uart_nextion - tx_pin: 17 - rx_pin: 16 - baud_rate: 115200 - -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 - -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 +packages: + base: !include common.yaml display: - - platform: nextion + - id: !extend main_lcd tft_url: http://esphome.io/default35.tft - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' - on_buffer_overflow: - then: - logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.esp32-c3-ard.yaml b/tests/components/nextion/test.esp32-c3-ard.yaml index 5d253268f8..5135c7e4f4 100644 --- a/tests/components/nextion/test.esp32-c3-ard.yaml +++ b/tests/components/nextion/test.esp32-c3-ard.yaml @@ -1,63 +1,10 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -uart: - - id: uart_nextion - tx_pin: 4 - rx_pin: 5 - baud_rate: 115200 - -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 - -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 +packages: + base: !include common.yaml display: - - platform: nextion + - id: !extend main_lcd tft_url: http://esphome.io/default35.tft - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' - on_buffer_overflow: - then: - logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.esp32-c3-idf.yaml b/tests/components/nextion/test.esp32-c3-idf.yaml index 5d253268f8..5135c7e4f4 100644 --- a/tests/components/nextion/test.esp32-c3-idf.yaml +++ b/tests/components/nextion/test.esp32-c3-idf.yaml @@ -1,63 +1,10 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -uart: - - id: uart_nextion - tx_pin: 4 - rx_pin: 5 - baud_rate: 115200 - -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 - -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 +packages: + base: !include common.yaml display: - - platform: nextion + - id: !extend main_lcd tft_url: http://esphome.io/default35.tft - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' - on_buffer_overflow: - then: - logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.esp32-idf.yaml b/tests/components/nextion/test.esp32-idf.yaml index ba76236fc6..d5e02b8b85 100644 --- a/tests/components/nextion/test.esp32-idf.yaml +++ b/tests/components/nextion/test.esp32-idf.yaml @@ -1,63 +1,10 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 -uart: - - id: uart_nextion - tx_pin: 17 - rx_pin: 16 - baud_rate: 115200 - -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 - -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 +packages: + base: !include common.yaml display: - - platform: nextion + - id: !extend main_lcd tft_url: http://esphome.io/default35.tft - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' - on_buffer_overflow: - then: - logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.esp8266-ard.yaml b/tests/components/nextion/test.esp8266-ard.yaml index 5d253268f8..5135c7e4f4 100644 --- a/tests/components/nextion/test.esp8266-ard.yaml +++ b/tests/components/nextion/test.esp8266-ard.yaml @@ -1,63 +1,10 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -uart: - - id: uart_nextion - tx_pin: 4 - rx_pin: 5 - baud_rate: 115200 - -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 - -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 +packages: + base: !include common.yaml display: - - platform: nextion + - id: !extend main_lcd tft_url: http://esphome.io/default35.tft - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' - on_buffer_overflow: - then: - logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.rp2040-ard.yaml b/tests/components/nextion/test.rp2040-ard.yaml index 9b04433095..20347c6eff 100644 --- a/tests/components/nextion/test.rp2040-ard.yaml +++ b/tests/components/nextion/test.rp2040-ard.yaml @@ -1,58 +1,7 @@ -uart: - - id: uart_nextion - tx_pin: 4 - rx_pin: 5 - baud_rate: 115200 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 +packages: + base: !include common.yaml -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 - -display: - - platform: nextion - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' - on_buffer_overflow: - then: - logger.log: "Nextion reported a buffer overflow!" From 2ecd5cff0701b807e5fa5558355c7beb7e6204ce Mon Sep 17 00:00:00 2001 From: NP v/d Spek <github_mail@lumensoft.nl> Date: Sun, 24 Nov 2024 21:16:51 +0100 Subject: [PATCH 148/282] [wifi] Make wifi_channel_() public (#7818) --- esphome/components/wifi/wifi_component.cpp | 4 ++-- esphome/components/wifi/wifi_component.h | 4 +++- esphome/components/wifi/wifi_component_esp32_arduino.cpp | 2 +- esphome/components/wifi/wifi_component_esp8266.cpp | 2 +- esphome/components/wifi/wifi_component_esp_idf.cpp | 2 +- esphome/components/wifi/wifi_component_libretiny.cpp | 2 +- esphome/components/wifi/wifi_component_pico_w.cpp | 2 +- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 8788711d5a..eef962b8c4 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -444,7 +444,7 @@ void WiFiComponent::print_connect_params_() { if (this->selected_ap_.get_bssid().has_value()) { ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*this->selected_ap_.get_bssid())); } - ESP_LOGCONFIG(TAG, " Channel: %" PRId32, wifi_channel_()); + ESP_LOGCONFIG(TAG, " Channel: %" PRId32, get_wifi_channel()); ESP_LOGCONFIG(TAG, " Subnet: %s", wifi_subnet_mask_().str().c_str()); ESP_LOGCONFIG(TAG, " Gateway: %s", wifi_gateway_ip_().str().c_str()); ESP_LOGCONFIG(TAG, " DNS1: %s", wifi_dns_ip_(0).str().c_str()); @@ -763,7 +763,7 @@ void WiFiComponent::load_fast_connect_settings_() { void WiFiComponent::save_fast_connect_settings_() { bssid_t bssid = wifi_bssid(); - uint8_t channel = wifi_channel_(); + uint8_t channel = get_wifi_channel(); if (bssid != this->selected_ap_.get_bssid() || channel != this->selected_ap_.get_channel()) { SavedWifiFastConnectSettings fast_connect_save{}; diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index dde0d1d5a5..5995f72e0b 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -317,6 +317,8 @@ class WiFiComponent : public Component { Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }; Trigger<> *get_disconnect_trigger() const { return this->disconnect_trigger_; }; + int32_t get_wifi_channel(); + protected: static std::string format_mac_addr(const uint8_t mac[6]); @@ -344,7 +346,7 @@ class WiFiComponent : public Component { #endif // USE_WIFI_AP bool wifi_disconnect_(); - int32_t wifi_channel_(); + network::IPAddress wifi_subnet_mask_(); network::IPAddress wifi_gateway_ip_(); network::IPAddress wifi_dns_ip_(int num); diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 88648093c6..18c706cb01 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -799,7 +799,7 @@ bssid_t WiFiComponent::wifi_bssid() { } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } -int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return network::IPAddress(WiFi.subnetMask()); } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return network::IPAddress(WiFi.gatewayIP()); } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return network::IPAddress(WiFi.dnsIP(num)); } diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 4568895950..a18d078967 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -825,7 +825,7 @@ bssid_t WiFiComponent::wifi_bssid() { } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } -int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 13870136d4..1bf14ff40b 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -973,7 +973,7 @@ int8_t WiFiComponent::wifi_rssi() { } return info.rssi; } -int32_t WiFiComponent::wifi_channel_() { +int32_t WiFiComponent::get_wifi_channel() { uint8_t primary; wifi_second_chan_t second; esp_err_t err = esp_wifi_get_channel(&primary, &second); diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index afb30c3bcf..b02f8ef0ce 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -473,7 +473,7 @@ bssid_t WiFiComponent::wifi_bssid() { } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } -int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index bac986d899..23fd766abe 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -189,7 +189,7 @@ bssid_t WiFiComponent::wifi_bssid() { } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } -int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { network::IPAddresses addresses; From 4936ca17003404796d388363469badfab340d8b8 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 25 Nov 2024 07:25:16 +1100 Subject: [PATCH 149/282] [lvgl] Bugfixes (#7803) --- esphome/components/lvgl/__init__.py | 2 +- esphome/components/lvgl/lv_validation.py | 3 ++- esphome/components/lvgl/schemas.py | 1 + esphome/components/lvgl/widgets/line.py | 5 ++++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index d03adc9624..8fdd03f647 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -322,8 +322,8 @@ async def to_code(configs): await encoders_to_code(lv_component, config, default_group) await keypads_to_code(lv_component, config, default_group) await theme_to_code(config) - await styles_to_code(config) await gradients_to_code(config) + await styles_to_code(config) await set_obj_properties(lv_scr_act, config) await add_widgets(lv_scr_act, config) await add_pages(lv_component, config) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index b91b0905df..766c010244 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -30,7 +30,7 @@ from .defines import ( call_lambda, literal, ) -from .helpers import esphome_fonts_used, lv_fonts_used, requires_component +from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component from .types import lv_font_t, lv_gradient_t, lv_img_t opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") @@ -326,6 +326,7 @@ def image_validator(value): value = requires_component("image")(value) value = cv.use_id(Image_)(value) lv_images_used.add(value) + add_lv_use("img", "label") return value diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 516627708e..3f56b3345f 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -341,6 +341,7 @@ FLEX_OBJ_SCHEMA = { cv.Optional(df.CONF_FLEX_GROW): cv.int_, } + DISP_BG_SCHEMA = cv.Schema( { cv.Optional(df.CONF_DISP_BG_IMAGE): lv_image, diff --git a/esphome/components/lvgl/widgets/line.py b/esphome/components/lvgl/widgets/line.py index 4c6439fde4..548dfa8452 100644 --- a/esphome/components/lvgl/widgets/line.py +++ b/esphome/components/lvgl/widgets/line.py @@ -39,7 +39,10 @@ LINE_SCHEMA = { class LineType(WidgetType): def __init__(self): super().__init__( - CONF_LINE, LvType("lv_line_t"), (CONF_MAIN,), LINE_SCHEMA, modify_schema={} + CONF_LINE, + LvType("lv_line_t"), + (CONF_MAIN,), + LINE_SCHEMA, ) async def to_code(self, w: Widget, config): From 4001d82ca269472b6ef813fa4b894e1071d5571e Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 25 Nov 2024 07:25:51 +1100 Subject: [PATCH 150/282] [docker] Leave run-time required libraries installed. (#7804) --- docker/Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index ed6ce083a8..c2902a9dd1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -99,15 +99,17 @@ BUILD_DEPS=" libfreetype-dev=2.12.1+dfsg-5+deb12u3 libssl-dev=3.0.15-1~deb12u1 libffi-dev=3.4.4-1 - libopenjp2-7=2.5.0-2 - libtiff6=4.5.0-6+deb12u1 cargo=0.66.0+ds1-1 pkg-config=1.8.1-1 " +LIB_DEPS=" + libtiff6=4.5.0-6+deb12u1 + libopenjp2-7=2.5.0-2 +" if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ] || [ "$TARGETARCH$TARGETVARIANT" = "armv7" ] then apt-get update - apt-get install -y --no-install-recommends $BUILD_DEPS + apt-get install -y --no-install-recommends $BUILD_DEPS $LIB_DEPS fi CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo From 13077095c2ece197c41c41324d578cb2a284edd8 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 25 Nov 2024 07:27:09 +1100 Subject: [PATCH 151/282] [qspi_dbi] Fix init sequences (Bugfix) (#7805) --- esphome/components/qspi_dbi/models.py | 2 ++ esphome/components/qspi_dbi/qspi_dbi.cpp | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/qspi_dbi/models.py b/esphome/components/qspi_dbi/models.py index cbd9c4663f..c1fe434853 100644 --- a/esphome/components/qspi_dbi/models.py +++ b/esphome/components/qspi_dbi/models.py @@ -1,6 +1,7 @@ # Commands SW_RESET_CMD = 0x01 SLEEP_OUT = 0x11 +NORON = 0x13 INVERT_OFF = 0x20 INVERT_ON = 0x21 ALL_ON = 0x23 @@ -56,6 +57,7 @@ chip.cmd(0xC2, 0x00) chip.delay(10) chip.cmd(TEON, 0x00) chip.cmd(PIXFMT, 0x55) +chip.cmd(NORON) chip = DriverChip("AXS15231") chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index a649a25ea6..785885d4ec 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -111,7 +111,6 @@ void QspiDbi::reset_params_(bool ready) { mad |= MADCTL_MY; this->write_command_(MADCTL_CMD, mad); this->write_command_(BRIGHTNESS, this->brightness_); - this->write_command_(NORON); this->write_command_(DISPLAY_ON); } From e3e3d9234756b94a74f3788f64aa4d9b37d40f7a Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Sun, 24 Nov 2024 10:42:46 -1000 Subject: [PATCH 152/282] fix modbus crashing when bad data returned (#7810) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- esphome/components/modbus/modbus.cpp | 3 +- .../modbus_controller/modbus_controller.cpp | 72 ++++++++++++++----- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 8544b50261..47deea83e6 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -38,8 +38,9 @@ void Modbus::loop() { // stop blocking new send commands after sent_wait_time_ ms after response received if (now - this->last_send_ > send_wait_time_) { - if (waiting_for_response > 0) + if (waiting_for_response > 0) { ESP_LOGV(TAG, "Stop waiting for response from %d", waiting_for_response); + } waiting_for_response = 0; } } diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index e1102516ca..f8b72af817 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -622,51 +622,87 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens uint32_t bitmask) { int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits + size_t size = data.size() - offset; + bool error = false; switch (sensor_value_type) { case SensorValueType::U_WORD: - value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ; + if (size >= 2) { + value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ; + } else { + error = true; + } break; case SensorValueType::U_DWORD: case SensorValueType::FP32: - value = get_data<uint32_t>(data, offset); - value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + if (size >= 4) { + value = get_data<uint32_t>(data, offset); + value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + } else { + error = true; + } break; case SensorValueType::U_DWORD_R: case SensorValueType::FP32_R: - value = get_data<uint32_t>(data, offset); - value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16; - value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + if (size >= 4) { + value = get_data<uint32_t>(data, offset); + value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16; + value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + } else { + error = true; + } break; case SensorValueType::S_WORD: - value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset), - bitmask); // default is 0xFFFF ; + if (size >= 2) { + value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset), + bitmask); // default is 0xFFFF ; + } else { + error = true; + } break; case SensorValueType::S_DWORD: - value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask); + if (size >= 4) { + value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask); + } else { + error = true; + } break; case SensorValueType::S_DWORD_R: { - value = get_data<uint32_t>(data, offset); - // Currently the high word is at the low position - // the sign bit is therefore at low before the switch - uint32_t sign_bit = (value & 0x8000) << 16; - value = mask_and_shift_by_rightbit( - static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask); + if (size >= 4) { + value = get_data<uint32_t>(data, offset); + // Currently the high word is at the low position + // the sign bit is therefore at low before the switch + uint32_t sign_bit = (value & 0x8000) << 16; + value = mask_and_shift_by_rightbit( + static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask); + } else { + error = true; + } } break; case SensorValueType::U_QWORD: case SensorValueType::S_QWORD: // Ignore bitmask for QWORD - value = get_data<uint64_t>(data, offset); + if (size >= 8) { + value = get_data<uint64_t>(data, offset); + } else { + error = true; + } break; case SensorValueType::U_QWORD_R: case SensorValueType::S_QWORD_R: { // Ignore bitmask for QWORD - uint64_t tmp = get_data<uint64_t>(data, offset); - value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000); + if (size >= 8) { + uint64_t tmp = get_data<uint64_t>(data, offset); + value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000); + } else { + error = true; + } } break; case SensorValueType::RAW: default: break; } + if (error) + ESP_LOGE(TAG, "not enough data for value"); return value; } From 9fc1377b448fab25c91e3342834a0b38df0c4a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Mart=C3=ADn?= <contact@rodrigomartin.dev> Date: Sun, 24 Nov 2024 23:06:21 +0100 Subject: [PATCH 153/282] feat(WiFi): Add wifi.configure action (#7335) --- esphome/components/wifi/__init__.py | 42 +++++++++++++ esphome/components/wifi/wifi_component.h | 79 ++++++++++++++++++++++++ tests/components/wifi/common.yaml | 7 +++ 3 files changed, 128 insertions(+) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index ea03cc16d1..ad1a4f5262 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -27,6 +27,7 @@ from esphome.const import ( CONF_NETWORKS, CONF_ON_CONNECT, CONF_ON_DISCONNECT, + CONF_ON_ERROR, CONF_PASSWORD, CONF_POWER_SAVE_MODE, CONF_PRIORITY, @@ -34,6 +35,7 @@ from esphome.const import ( CONF_SSID, CONF_STATIC_IP, CONF_SUBNET, + CONF_TIMEOUT, CONF_TTLS_PHASE_2, CONF_USE_ADDRESS, CONF_USERNAME, @@ -46,6 +48,7 @@ from . import wpa2_eap AUTO_LOAD = ["network"] NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2] +CONF_SAVE = "save" wifi_ns = cg.esphome_ns.namespace("wifi") EAPAuth = wifi_ns.struct("EAPAuth") @@ -63,6 +66,9 @@ WiFiConnectedCondition = wifi_ns.class_("WiFiConnectedCondition", Condition) WiFiEnabledCondition = wifi_ns.class_("WiFiEnabledCondition", Condition) WiFiEnableAction = wifi_ns.class_("WiFiEnableAction", automation.Action) WiFiDisableAction = wifi_ns.class_("WiFiDisableAction", automation.Action) +WiFiConfigureAction = wifi_ns.class_( + "WiFiConfigureAction", automation.Action, cg.Component +) def validate_password(value): @@ -483,3 +489,39 @@ async def wifi_enable_to_code(config, action_id, template_arg, args): @automation.register_action("wifi.disable", WiFiDisableAction, cv.Schema({})) async def wifi_disable_to_code(config, action_id, template_arg, args): return cg.new_Pvariable(action_id, template_arg) + + +@automation.register_action( + "wifi.configure", + WiFiConfigureAction, + cv.Schema( + { + cv.Required(CONF_SSID): cv.templatable(cv.ssid), + cv.Required(CONF_PASSWORD): cv.templatable(validate_password), + cv.Optional(CONF_SAVE, default=True): cv.templatable(cv.boolean), + cv.Optional(CONF_TIMEOUT, default="30000ms"): cv.templatable( + cv.positive_time_period_milliseconds + ), + cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True), + cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True), + } + ), +) +async def wifi_set_sta_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + ssid = await cg.templatable(config[CONF_SSID], args, cg.std_string) + password = await cg.templatable(config[CONF_PASSWORD], args, cg.std_string) + save = await cg.templatable(config[CONF_SAVE], args, cg.bool_) + timeout = await cg.templatable(config.get(CONF_TIMEOUT), args, cg.uint32) + cg.add(var.set_ssid(ssid)) + cg.add(var.set_password(password)) + cg.add(var.set_save(save)) + cg.add(var.set_connection_timeout(timeout)) + if on_connect_config := config.get(CONF_ON_CONNECT): + await automation.build_automation( + var.get_connect_trigger(), [], on_connect_config + ) + if on_error_config := config.get(CONF_ON_ERROR): + await automation.build_automation(var.get_error_trigger(), [], on_error_config) + await cg.register_component(var, config) + return var diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 5995f72e0b..abedfab3a6 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -209,6 +209,7 @@ class WiFiComponent : public Component { WiFiComponent(); void set_sta(const WiFiAP &ap); + WiFiAP get_sta() { return this->selected_ap_; } void add_sta(const WiFiAP &ap); void clear_sta(); @@ -443,6 +444,84 @@ template<typename... Ts> class WiFiDisableAction : public Action<Ts...> { void play(Ts... x) override { global_wifi_component->disable(); } }; +template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, public Component { + public: + TEMPLATABLE_VALUE(std::string, ssid) + TEMPLATABLE_VALUE(std::string, password) + TEMPLATABLE_VALUE(bool, save) + TEMPLATABLE_VALUE(uint32_t, connection_timeout) + + void play(Ts... x) override { + auto ssid = this->ssid_.value(x...); + auto password = this->password_.value(x...); + // Avoid multiple calls + if (this->connecting_) + return; + // If already connected to the same AP, do nothing + if (global_wifi_component->wifi_ssid() == ssid) { + // Callback to notify the user that the connection was successful + this->connect_trigger_->trigger(); + return; + } + // Create a new WiFiAP object with the new SSID and password + this->new_sta_.set_ssid(ssid); + this->new_sta_.set_password(password); + // Save the current STA + this->old_sta_ = global_wifi_component->get_sta(); + // Disable WiFi + global_wifi_component->disable(); + // Set the state to connecting + this->connecting_ = true; + // Store the new STA so once the WiFi is enabled, it will connect to it + // This is necessary because the WiFiComponent will raise an error and fallback to the saved STA + // if trying to connect to a new STA while already connected to another one + if (this->save_.value(x...)) { + global_wifi_component->save_wifi_sta(new_sta_.get_ssid(), new_sta_.get_password()); + } else { + global_wifi_component->set_sta(new_sta_); + } + // Enable WiFi + global_wifi_component->enable(); + // Set timeout for the connection + this->set_timeout("wifi-connect-timeout", this->connection_timeout_.value(x...), [this]() { + this->connecting_ = false; + // If the timeout is reached, stop connecting and revert to the old AP + global_wifi_component->disable(); + global_wifi_component->save_wifi_sta(old_sta_.get_ssid(), old_sta_.get_password()); + global_wifi_component->enable(); + // Callback to notify the user that the connection failed + this->error_trigger_->trigger(); + }); + } + + Trigger<> *get_connect_trigger() const { return this->connect_trigger_; } + Trigger<> *get_error_trigger() const { return this->error_trigger_; } + + void loop() override { + if (!this->connecting_) + return; + if (global_wifi_component->is_connected()) { + // The WiFi is connected, stop the timeout and reset the connecting flag + this->cancel_timeout("wifi-connect-timeout"); + this->connecting_ = false; + if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) { + // Callback to notify the user that the connection was successful + this->connect_trigger_->trigger(); + } else { + // Callback to notify the user that the connection failed + this->error_trigger_->trigger(); + } + } + } + + protected: + bool connecting_{false}; + WiFiAP new_sta_; + WiFiAP old_sta_; + Trigger<> *connect_trigger_{new Trigger<>()}; + Trigger<> *error_trigger_{new Trigger<>()}; +}; + } // namespace wifi } // namespace esphome #endif diff --git a/tests/components/wifi/common.yaml b/tests/components/wifi/common.yaml index 003f6347be..343d44b177 100644 --- a/tests/components/wifi/common.yaml +++ b/tests/components/wifi/common.yaml @@ -3,6 +3,13 @@ esphome: then: - wifi.disable - wifi.enable + - wifi.configure: + ssid: MySSID + password: password1 + on_connect: + - logger.log: "Connected to WiFi!" + on_error: + - logger.log: "Failed to connect to WiFi!" wifi: ssid: MySSID From d4d630823ccc3bdddb6e6b39176c188d353d0807 Mon Sep 17 00:00:00 2001 From: TFGF <terciofilho@gmail.com> Date: Sun, 24 Nov 2024 19:15:10 -0300 Subject: [PATCH 154/282] [Modbus Controller] Fix issue #6477. Online automation triggering Offline (#7801) --- esphome/components/modbus_controller/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 5c407d6fff..2a08075831 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -163,7 +163,7 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_ON_OFFLINE): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOnlineTrigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOfflineTrigger), } ), } From e02f3cdac7b7e8a58711763b5d7f9dfac8ea6991 Mon Sep 17 00:00:00 2001 From: Ramil Valitov <ramilvalitov@gmail.com> Date: Mon, 25 Nov 2024 01:23:30 +0300 Subject: [PATCH 155/282] [fix] Status sensor does not check if required network component is missing (#7734) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/status/binary_sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/status/binary_sensor.py b/esphome/components/status/binary_sensor.py index 1f2b7c9d18..adc342ed4d 100644 --- a/esphome/components/status/binary_sensor.py +++ b/esphome/components/status/binary_sensor.py @@ -6,6 +6,8 @@ from esphome.const import ( ENTITY_CATEGORY_DIAGNOSTIC, ) +DEPENDENCIES = ["network"] + status_ns = cg.esphome_ns.namespace("status") StatusBinarySensor = status_ns.class_( "StatusBinarySensor", binary_sensor.BinarySensor, cg.Component From 59653ec7853a349e9e736710867daedc017a725e Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Sun, 24 Nov 2024 12:40:28 -1000 Subject: [PATCH 156/282] allow multiple graphical menus (#7809) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- esphome/components/display_menu_base/__init__.py | 2 -- esphome/components/graphical_display_menu/__init__.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/display_menu_base/__init__.py b/esphome/components/display_menu_base/__init__.py index 8ae9cbc2a4..f9c0424104 100644 --- a/esphome/components/display_menu_base/__init__.py +++ b/esphome/components/display_menu_base/__init__.py @@ -68,8 +68,6 @@ IsActiveCondition = display_menu_base_ns.class_( "IsActiveCondition", automation.Condition ) -MULTI_CONF = True - MenuItemType = display_menu_base_ns.enum("MenuItemType") MENU_ITEM_TYPES = { diff --git a/esphome/components/graphical_display_menu/__init__.py b/esphome/components/graphical_display_menu/__init__.py index f4d59b22b8..56b720e75c 100644 --- a/esphome/components/graphical_display_menu/__init__.py +++ b/esphome/components/graphical_display_menu/__init__.py @@ -36,6 +36,8 @@ CODEOWNERS = ["@MrMDavidson"] AUTO_LOAD = ["display_menu_base"] +MULTI_CONF = True + CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend( cv.Schema( { From b95b4a069417ea6fd31f96b5e9150ca16dd1c3f5 Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Sun, 24 Nov 2024 12:40:51 -1000 Subject: [PATCH 157/282] keypad binary sensors should be initially off (#7808) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- esphome/components/binary_sensor/binary_sensor.h | 2 +- .../matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 301a472810..57cae9e2f5 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -58,7 +58,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass { void publish_initial_state(bool state); /// The current reported state of the binary sensor. - bool state; + bool state{false}; void add_filter(Filter *filter); void add_filters(const std::vector<Filter *> &filters); diff --git a/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h b/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h index d8a217f55e..2c1ce96f0a 100644 --- a/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h +++ b/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h @@ -6,7 +6,7 @@ namespace esphome { namespace matrix_keypad { -class MatrixKeypadBinarySensor : public MatrixKeypadListener, public binary_sensor::BinarySensor { +class MatrixKeypadBinarySensor : public MatrixKeypadListener, public binary_sensor::BinarySensorInitiallyOff { public: MatrixKeypadBinarySensor(uint8_t key) : has_key_(true), key_(key){}; MatrixKeypadBinarySensor(const char *key) : has_key_(true), key_((uint8_t) key[0]){}; From 71496574e9e104433ded1f4da497454b1b266e3d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:26:36 +1300 Subject: [PATCH 158/282] Move ``CONF_NAME_ADD_MAC_SUFFIX`` to ``const.py`` (#7820) --- esphome/const.py | 1 + esphome/core/config.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index 6a643e1e30..50528b7363 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -528,6 +528,7 @@ CONF_MULTIPLE = "multiple" CONF_MULTIPLEXER = "multiplexer" CONF_MULTIPLY = "multiply" CONF_NAME = "name" +CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" CONF_NAME_FONT = "name_font" CONF_NBITS = "nbits" CONF_NEC = "nec" diff --git a/esphome/core/config.py b/esphome/core/config.py index 367e61c413..eee8b73934 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -21,6 +21,7 @@ from esphome.const import ( CONF_LIBRARIES, CONF_MIN_VERSION, CONF_NAME, + CONF_NAME_ADD_MAC_SUFFIX, CONF_ON_BOOT, CONF_ON_LOOP, CONF_ON_SHUTDOWN, @@ -59,8 +60,6 @@ ProjectUpdateTrigger = cg.esphome_ns.class_( VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[ab]\d+)?$") -CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" - VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} From c49f7293fef3c052691f388b6c9af6f8709b7aca Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Sun, 24 Nov 2024 21:24:23 -1000 Subject: [PATCH 159/282] binary_sensor for switch state (#7819) --- CODEOWNERS | 1 + .../switch/binary_sensor/__init__.py | 31 +++++++++++++++++++ .../binary_sensor/switch_binary_sensor.cpp | 17 ++++++++++ .../binary_sensor/switch_binary_sensor.h | 22 +++++++++++++ tests/components/switch/common.yaml | 11 +++++++ tests/components/switch/test.bk72xx-ard.yaml | 2 ++ tests/components/switch/test.esp32-ard.yaml | 2 ++ .../components/switch/test.esp32-c3-ard.yaml | 2 ++ .../components/switch/test.esp32-c3-idf.yaml | 2 ++ tests/components/switch/test.esp32-idf.yaml | 2 ++ .../components/switch/test.esp32-s3-idf.yaml | 2 ++ tests/components/switch/test.esp8266-ard.yaml | 2 ++ tests/components/switch/test.rp2040-ard.yaml | 2 ++ 13 files changed, 98 insertions(+) create mode 100644 esphome/components/switch/binary_sensor/__init__.py create mode 100644 esphome/components/switch/binary_sensor/switch_binary_sensor.cpp create mode 100644 esphome/components/switch/binary_sensor/switch_binary_sensor.h create mode 100644 tests/components/switch/common.yaml create mode 100644 tests/components/switch/test.bk72xx-ard.yaml create mode 100644 tests/components/switch/test.esp32-ard.yaml create mode 100644 tests/components/switch/test.esp32-c3-ard.yaml create mode 100644 tests/components/switch/test.esp32-c3-idf.yaml create mode 100644 tests/components/switch/test.esp32-idf.yaml create mode 100644 tests/components/switch/test.esp32-s3-idf.yaml create mode 100644 tests/components/switch/test.esp8266-ard.yaml create mode 100644 tests/components/switch/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 8fbbacef59..dd3926d283 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -408,6 +408,7 @@ esphome/components/substitutions/* @esphome/core esphome/components/sun/* @OttoWinter esphome/components/sun_gtil2/* @Mat931 esphome/components/switch/* @esphome/core +esphome/components/switch/binary_sensor/* @ssieb esphome/components/t6615/* @tylermenezes esphome/components/tc74/* @sethgirvan esphome/components/tca9548a/* @andreashergert1984 diff --git a/esphome/components/switch/binary_sensor/__init__.py b/esphome/components/switch/binary_sensor/__init__.py new file mode 100644 index 0000000000..61ca1a14a2 --- /dev/null +++ b/esphome/components/switch/binary_sensor/__init__.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_SOURCE_ID + +from .. import Switch, switch_ns + +CODEOWNERS = ["@ssieb"] + +SwitchBinarySensor = switch_ns.class_( + "SwitchBinarySensor", binary_sensor.BinarySensor, cg.Component +) + + +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(SwitchBinarySensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(Switch), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp b/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp new file mode 100644 index 0000000000..ba57154446 --- /dev/null +++ b/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp @@ -0,0 +1,17 @@ +#include "switch_binary_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace switch_ { + +static const char *const TAG = "switch.binary_sensor"; + +void SwitchBinarySensor::setup() { + source_->add_on_state_callback([this](bool value) { this->publish_state(value); }); + this->publish_state(source_->state); +} + +void SwitchBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Switch Binary Sensor", this); } + +} // namespace switch_ +} // namespace esphome diff --git a/esphome/components/switch/binary_sensor/switch_binary_sensor.h b/esphome/components/switch/binary_sensor/switch_binary_sensor.h new file mode 100644 index 0000000000..5a947c2fb4 --- /dev/null +++ b/esphome/components/switch/binary_sensor/switch_binary_sensor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../switch.h" +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace switch_ { + +class SwitchBinarySensor : public binary_sensor::BinarySensor, public Component { + public: + void set_source(Switch *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + Switch *source_; +}; + +} // namespace switch_ +} // namespace esphome diff --git a/tests/components/switch/common.yaml b/tests/components/switch/common.yaml new file mode 100644 index 0000000000..8d6972f91b --- /dev/null +++ b/tests/components/switch/common.yaml @@ -0,0 +1,11 @@ +binary_sensor: + - platform: switch + id: some_binary_sensor + name: "Template Switch State" + source_id: the_switch + +switch: + - platform: template + name: "Template Switch" + id: the_switch + optimistic: true diff --git a/tests/components/switch/test.bk72xx-ard.yaml b/tests/components/switch/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.bk72xx-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp32-ard.yaml b/tests/components/switch/test.esp32-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp32-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp32-c3-ard.yaml b/tests/components/switch/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp32-c3-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp32-c3-idf.yaml b/tests/components/switch/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp32-c3-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp32-idf.yaml b/tests/components/switch/test.esp32-idf.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp32-s3-idf.yaml b/tests/components/switch/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp32-s3-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp8266-ard.yaml b/tests/components/switch/test.esp8266-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp8266-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.rp2040-ard.yaml b/tests/components/switch/test.rp2040-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.rp2040-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml From 7f75f2135d74c6daaaf5251c12a3a1f986ec6694 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 02:22:50 -0600 Subject: [PATCH 160/282] [nextion] Remove assignment within `if` (#7824) --- esphome/components/nextion/nextion_upload_idf.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/nextion/nextion_upload_idf.cpp b/esphome/components/nextion/nextion_upload_idf.cpp index b5bb5478c1..7541a57d56 100644 --- a/esphome/components/nextion/nextion_upload_idf.cpp +++ b/esphome/components/nextion/nextion_upload_idf.cpp @@ -36,8 +36,8 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r ESP_LOGV(TAG, "Requesting range: %s", range_header); esp_http_client_set_header(http_client, "Range", range_header); ESP_LOGV(TAG, "Opening HTTP connetion"); - esp_err_t err; - if ((err = esp_http_client_open(http_client, 0)) != ESP_OK) { + esp_err_t err = esp_http_client_open(http_client, 0); + if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); return -1; } From 6c548a15966b0486226d0fd7c33a105b6776d3f4 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 02:23:00 -0600 Subject: [PATCH 161/282] [ota] `void` functions should return nothing (#7825) --- esphome/components/ota/automation.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ota/automation.h b/esphome/components/ota/automation.h index 4605193480..7e1a60f3ce 100644 --- a/esphome/components/ota/automation.h +++ b/esphome/components/ota/automation.h @@ -12,7 +12,7 @@ class OTAStateChangeTrigger : public Trigger<OTAState> { explicit OTAStateChangeTrigger(OTAComponent *parent) { parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { if (!parent->is_failed()) { - return trigger(state); + trigger(state); } }); } From 46a435f5f2ec8f8dfb306f953280b5b2be265195 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 02:24:35 -0600 Subject: [PATCH 162/282] [safe_mode] Remove unused capture (#7826) --- esphome/components/safe_mode/automation.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/safe_mode/automation.h b/esphome/components/safe_mode/automation.h index d1388449ee..1ffa86a588 100644 --- a/esphome/components/safe_mode/automation.h +++ b/esphome/components/safe_mode/automation.h @@ -9,7 +9,7 @@ namespace safe_mode { class SafeModeTrigger : public Trigger<> { public: explicit SafeModeTrigger(SafeModeComponent *parent) { - parent->add_on_safe_mode_callback([this, parent]() { trigger(); }); + parent->add_on_safe_mode_callback([this]() { trigger(); }); } }; From ebf895990b494ec8c09b7dbab051b0d53cc2d029 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 02:25:04 -0600 Subject: [PATCH 163/282] [stepper] Remove unnecessary ``#include`` (#7827) --- esphome/components/stepper/stepper.h | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/stepper/stepper.h b/esphome/components/stepper/stepper.h index 560362e4d0..ba2b3182d7 100644 --- a/esphome/components/stepper/stepper.h +++ b/esphome/components/stepper/stepper.h @@ -2,7 +2,6 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" -#include "esphome/components/stepper/stepper.h" namespace esphome { namespace stepper { From aa6cea6f7e4ea4b656fa6d3a152581e623227bd3 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 02:27:36 -0600 Subject: [PATCH 164/282] [sx1509] Fix up includes (#7828) --- esphome/components/sx1509/sx1509_gpio_pin.cpp | 3 ++- esphome/components/sx1509/sx1509_gpio_pin.h | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp index 56b51ae311..a74c8b60b8 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.cpp +++ b/esphome/components/sx1509/sx1509_gpio_pin.cpp @@ -1,5 +1,6 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "sx1509.h" #include "sx1509_gpio_pin.h" namespace esphome { @@ -13,7 +14,7 @@ bool SX1509GPIOPin::digital_read() { return this->parent_->digital_read(this->pi void SX1509GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } std::string SX1509GPIOPin::dump_summary() const { char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via sx1509", pin_); + snprintf(buffer, sizeof(buffer), "%u via sx1509", this->pin_); return buffer; } diff --git a/esphome/components/sx1509/sx1509_gpio_pin.h b/esphome/components/sx1509/sx1509_gpio_pin.h index 4d8aa5ec83..1cfa341ee7 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.h +++ b/esphome/components/sx1509/sx1509_gpio_pin.h @@ -1,6 +1,6 @@ #pragma once -#include "sx1509.h" +#include "esphome/core/gpio.h" namespace esphome { namespace sx1509 { @@ -15,10 +15,10 @@ class SX1509GPIOPin : public GPIOPin { void digital_write(bool value) override; std::string dump_summary() const override; - void set_parent(SX1509Component *parent) { parent_ = parent; } - void set_pin(uint8_t pin) { pin_ = pin; } - void set_inverted(bool inverted) { inverted_ = inverted; } - void set_flags(gpio::Flags flags) { flags_ = flags; } + void set_parent(SX1509Component *parent) { this->parent_ = parent; } + void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_inverted(bool inverted) { this->inverted_ = inverted; } + void set_flags(gpio::Flags flags) { this->flags_ = flags; } protected: SX1509Component *parent_; From 1bd2d41ffd1273eefa03aa331944d7f47d5cd7c9 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 02:39:22 -0600 Subject: [PATCH 165/282] [uart] `void` functions should return nothing (#7829) --- esphome/components/uart/uart.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index d41dbe26e6..dc6962fbae 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -40,7 +40,7 @@ class UARTDevice { int available() { return this->parent_->available(); } - void flush() { return this->parent_->flush(); } + void flush() { this->parent_->flush(); } // Compat APIs int read() { From 17a09cd22151d95d510c858253b78dadae8e53e8 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 03:50:18 -0600 Subject: [PATCH 166/282] [audio] Header modernization (#7832) --- esphome/components/audio/audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/audio/audio.h b/esphome/components/audio/audio.h index b0968dc8da..caf325cf54 100644 --- a/esphome/components/audio/audio.h +++ b/esphome/components/audio/audio.h @@ -1,7 +1,7 @@ #pragma once +#include <cstddef> #include <cstdint> -#include <stddef.h> namespace esphome { namespace audio { From cf835d15806481c50361cd6d8d193cc7b0b0175e Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 03:50:24 -0600 Subject: [PATCH 167/282] [opentherm] Follow variable naming convention (#7833) --- esphome/components/opentherm/opentherm.cpp | 6 +++--- esphome/components/opentherm/opentherm.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 26c707f9a0..78ecb53428 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -29,7 +29,7 @@ using std::to_string; static const char *const TAG = "opentherm"; #ifdef ESP8266 -OpenTherm *OpenTherm::instance_ = nullptr; +OpenTherm *OpenTherm::instance = nullptr; #endif OpenTherm::OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout) @@ -53,7 +53,7 @@ OpenTherm::OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t bool OpenTherm::initialize() { #ifdef ESP8266 - OpenTherm::instance_ = this; + OpenTherm::instance = this; #endif this->in_pin_->pin_mode(gpio::FLAG_INPUT); this->out_pin_->pin_mode(gpio::FLAG_OUTPUT); @@ -216,7 +216,7 @@ bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) { } #ifdef ESP8266 -void IRAM_ATTR OpenTherm::esp8266_timer_isr() { OpenTherm::timer_isr(OpenTherm::instance_); } +void IRAM_ATTR OpenTherm::esp8266_timer_isr() { OpenTherm::timer_isr(OpenTherm::instance); } #endif void IRAM_ATTR OpenTherm::bit_read_(uint8_t value) { diff --git a/esphome/components/opentherm/opentherm.h b/esphome/components/opentherm/opentherm.h index 85f4611125..5088bb2aa3 100644 --- a/esphome/components/opentherm/opentherm.h +++ b/esphome/components/opentherm/opentherm.h @@ -371,7 +371,7 @@ class OpenTherm { #ifdef ESP8266 // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm - static OpenTherm *instance_; + static OpenTherm *instance; #endif }; From 89ecfc20049d2358ecdbe63dacaed9143c9d9ed5 Mon Sep 17 00:00:00 2001 From: Oleg Tarasov <me@olegtarasov.email> Date: Tue, 26 Nov 2024 00:47:01 +0300 Subject: [PATCH 168/282] [opentherm] Fix out of memory errors on ESP8266 (#7835) --- esphome/components/opentherm/hub.cpp | 13 ++++---- esphome/components/opentherm/opentherm.cpp | 36 ++++++---------------- esphome/components/opentherm/opentherm.h | 7 ++--- esphome/core/helpers.cpp | 12 ++++++++ esphome/core/helpers.h | 8 +++++ 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index dfa8ea95c5..aac2966ed1 100644 --- a/esphome/components/opentherm/hub.cpp +++ b/esphome/components/opentherm/hub.cpp @@ -138,7 +138,7 @@ OpenthermHub::OpenthermHub() : Component(), in_pin_{}, out_pin_{} {} void OpenthermHub::process_response(OpenthermData &data) { ESP_LOGD(TAG, "Received OpenTherm response with id %d (%s)", data.id, this->opentherm_->message_id_to_str((MessageId) data.id)); - ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(data).c_str()); + this->opentherm_->debug_data(data); switch (data.id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_RESPONSE_MESSAGE, OPENTHERM_MESSAGE_RESPONSE_ENTITY, , @@ -315,7 +315,7 @@ void OpenthermHub::start_conversation_() { ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id, this->opentherm_->message_id_to_str((MessageId) request.id)); - ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(request).c_str()); + this->opentherm_->debug_data(request); // Send the request this->last_conversation_start_ = millis(); this->opentherm_->send(request); @@ -340,19 +340,18 @@ void OpenthermHub::stop_opentherm_() { this->opentherm_->stop(); this->last_conversation_end_ = millis(); } - void OpenthermHub::handle_protocol_write_error_() { ESP_LOGW(TAG, "Error while sending request: %s", this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode())); - ESP_LOGW(TAG, "%s", this->opentherm_->debug_data(this->last_request_).c_str()); + this->opentherm_->debug_data(this->last_request_); } - void OpenthermHub::handle_protocol_read_error_() { OpenThermError error; this->opentherm_->get_protocol_error(error); - ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", this->opentherm_->debug_error(error).c_str()); + ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", + this->opentherm_->protocol_error_to_to_str(error.error_type)); + this->opentherm_->debug_error(error); } - void OpenthermHub::handle_timeout_error_() { ESP_LOGW(TAG, "Receive response timed out at a protocol level"); this->stop_opentherm_(); diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 78ecb53428..e40fc66b7d 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -15,15 +15,11 @@ #include "Arduino.h" #endif #include <string> -#include <sstream> -#include <bitset> namespace esphome { namespace opentherm { using std::string; -using std::bitset; -using std::stringstream; using std::to_string; static const char *const TAG = "opentherm"; @@ -545,29 +541,17 @@ const char *OpenTherm::message_id_to_str(MessageId id) { } } -string OpenTherm::debug_data(OpenthermData &data) { - stringstream result; - result << bitset<8>(data.type) << " " << bitset<8>(data.id) << " " << bitset<8>(data.valueHB) << " " - << bitset<8>(data.valueLB) << "\n"; - result << "type: " << this->message_type_to_str((MessageType) data.type) << "; "; - result << "id: " << to_string(data.id) << "; "; - result << "HB: " << to_string(data.valueHB) << "; "; - result << "LB: " << to_string(data.valueLB) << "; "; - result << "uint_16: " << to_string(data.u16()) << "; "; - result << "float: " << to_string(data.f88()); - - return result.str(); +void OpenTherm::debug_data(OpenthermData &data) { + ESP_LOGD(TAG, "%s %s %s %s", format_bin(data.type).c_str(), format_bin(data.id).c_str(), + format_bin(data.valueHB).c_str(), format_bin(data.valueLB).c_str()); + ESP_LOGD(TAG, "type: %s; id: %s; HB: %s; LB: %s; uint_16: %s; float: %s", + this->message_type_to_str((MessageType) data.type), to_string(data.id).c_str(), + to_string(data.valueHB).c_str(), to_string(data.valueLB).c_str(), to_string(data.u16()).c_str(), + to_string(data.f88()).c_str()); } -std::string OpenTherm::debug_error(OpenThermError &error) { - stringstream result; - result << "type: " << this->protocol_error_to_to_str(error.error_type) << "; "; - result << "data: "; - result << format_hex(error.data); - result << "; clock: " << to_string(clock_); - result << "; capture: " << bitset<32>(error.capture); - result << "; bit_pos: " << to_string(error.bit_pos); - - return result.str(); +void OpenTherm::debug_error(OpenThermError &error) const { + ESP_LOGD(TAG, "data: %s; clock: %s; capture: %s; bit_pos: %s", format_hex(error.data).c_str(), + to_string(clock_).c_str(), format_bin(error.capture).c_str(), to_string(error.bit_pos).c_str()); } float OpenthermData::f88() { return ((float) this->s16()) / 256.0; } diff --git a/esphome/components/opentherm/opentherm.h b/esphome/components/opentherm/opentherm.h index 5088bb2aa3..9532a77821 100644 --- a/esphome/components/opentherm/opentherm.h +++ b/esphome/components/opentherm/opentherm.h @@ -8,10 +8,9 @@ #pragma once #include <string> -#include <sstream> -#include <iomanip> #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #if defined(ESP32) || defined(USE_ESP_IDF) #include "driver/timer.h" @@ -318,8 +317,8 @@ class OpenTherm { OperationMode get_mode() { return mode_; } - std::string debug_data(OpenthermData &data); - std::string debug_error(OpenThermError &error); + void debug_data(OpenthermData &data); + void debug_error(OpenThermError &error) const; const char *protocol_error_to_to_str(ProtocolErrorType error_type); const char *message_type_to_str(MessageType message_type); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index dae60a4e1d..befc84516c 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -397,6 +397,18 @@ std::string format_hex_pretty(const uint16_t *data, size_t length) { } std::string format_hex_pretty(const std::vector<uint16_t> &data) { return format_hex_pretty(data.data(), data.size()); } +std::string format_bin(const uint8_t *data, size_t length) { + std::string result; + result.resize(length * 8); + for (size_t byte_idx = 0; byte_idx < length; byte_idx++) { + for (size_t bit_idx = 0; bit_idx < 8; bit_idx++) { + result[byte_idx * 8 + bit_idx] = ((data[byte_idx] >> (7 - bit_idx)) & 1) + '0'; + } + } + + return result; +} + ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { if (on == nullptr && strcasecmp(str, "on") == 0) return PARSE_ON; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 43001bafdd..305ec47f76 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -420,6 +420,14 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::stri return format_hex_pretty(reinterpret_cast<uint8_t *>(&val), sizeof(T)); } +/// Format the byte array \p data of length \p len in binary. +std::string format_bin(const uint8_t *data, size_t length); +/// Format an unsigned integer in binary, starting with the most significant byte. +template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_bin(T val) { + val = convert_big_endian(val); + return format_bin(reinterpret_cast<uint8_t *>(&val), sizeof(T)); +} + /// Return values for parse_on_off(). enum ParseOnOffState { PARSE_NONE = 0, From b027b6a711be401153847ca6607a4f7b90bc6688 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 15:57:40 -0600 Subject: [PATCH 169/282] [opentherm] Add nolint for 8266 static global (#7837) --- esphome/components/opentherm/opentherm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/opentherm/opentherm.h b/esphome/components/opentherm/opentherm.h index 9532a77821..3be0191c63 100644 --- a/esphome/components/opentherm/opentherm.h +++ b/esphome/components/opentherm/opentherm.h @@ -370,7 +370,7 @@ class OpenTherm { #ifdef ESP8266 // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm - static OpenTherm *instance; + static OpenTherm *instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) #endif }; From bdb91112eada52df7c31a0b86e2f6f3b16ab9a49 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 16:20:03 -0600 Subject: [PATCH 170/282] [helpers] Add NOLINT for Mutex private field ``handle_`` (#7838) --- esphome/core/helpers.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 305ec47f76..fcbd8d8683 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -565,7 +565,8 @@ class Mutex { #if defined(USE_ESP32) || defined(USE_LIBRETINY) SemaphoreHandle_t handle_; #else - void *handle_; // d-pointer to store private data on new platforms + // d-pointer to store private data on new platforms + void *handle_; // NOLINT(clang-diagnostic-unused-private-field) #endif }; From d6f4f0509081ea38cfe799806c8225903e6cd1be Mon Sep 17 00:00:00 2001 From: programmingbgloDE <47243850+programmingbgloDE@users.noreply.github.com> Date: Mon, 25 Nov 2024 23:26:48 +0100 Subject: [PATCH 171/282] Add waveshare 1 45 in v2 b support (#7052) --- .../components/waveshare_epaper/display.py | 4 + .../waveshare_epaper/waveshare_epaper.cpp | 84 +++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 18 ++++ 3 files changed, 106 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 8287788de5..fbb5e1353d 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -27,6 +27,9 @@ WaveshareEPaperBWR = waveshare_epaper_ns.class_( WaveshareEPaperTypeA = waveshare_epaper_ns.class_( "WaveshareEPaperTypeA", WaveshareEPaper ) +WaveshareEpaper1P54INBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper1P54InBV2", WaveshareEPaperBWR +) WaveshareEPaper2P7In = waveshare_epaper_ns.class_( "WaveshareEPaper2P7In", WaveshareEPaper ) @@ -105,6 +108,7 @@ WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel" MODELS = { "1.54in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN), "1.54inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN_V2), + "1.54inv2-b": ("b", WaveshareEpaper1P54INBV2), "2.13in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN), "2.13inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN_V2), "2.13in-ttgo": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN), diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 7c1d436673..1e27d594b8 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -808,6 +808,90 @@ void WaveshareEPaper2P7InV2::dump_config() { LOG_UPDATE_INTERVAL(this); } +// ======================================================== +// 1.54inch_v2_e-paper_b +// ======================================================== +// Datasheet: +// - https://files.waveshare.com/upload/9/9e/1.54inch-e-paper-b-v2-specification.pdf +// - https://www.waveshare.com/wiki/1.54inch_e-Paper_Module_(B)_Manual + +void WaveshareEPaper1P54InBV2::initialize() { + this->reset_(); + + this->wait_until_idle_(); + + this->command(0x12); + this->wait_until_idle_(); + + this->command(0x01); + this->data(0xC7); + this->data(0x00); + this->data(0x01); + + this->command(0x11); // data entry mode + this->data(0x01); + + this->command(0x44); // set Ram-X address start/end position + this->data(0x00); + this->data(0x18); // 0x18-->(24+1)*8=200 + + this->command(0x45); // set Ram-Y address start/end position + this->data(0xC7); // 0xC7-->(199+1)=200 + this->data(0x00); + this->data(0x00); + this->data(0x00); + + this->command(0x3C); // BorderWavefrom + this->data(0x05); + + this->command(0x18); // Read built-in temperature sensor + this->data(0x80); + + this->command(0x4E); // set RAM x address count to 0; + this->data(0x00); + this->command(0x4F); // set RAM y address count to 0X199; + this->data(0xC7); + this->data(0x00); + + this->wait_until_idle_(); +} + +void HOT WaveshareEPaper1P54InBV2::display() { + uint32_t buf_len_half = this->get_buffer_length_() >> 1; + this->initialize(); + + // COMMAND DATA START TRANSMISSION 1 (BLACK) + this->command(0x24); + delay(2); + for (uint32_t i = 0; i < buf_len_half; i++) { + this->data(~this->buffer_[i]); + } + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED) + this->command(0x26); + delay(2); + for (uint32_t i = buf_len_half; i < buf_len_half * 2u; i++) { + this->data(this->buffer_[i]); + } + this->command(0x22); + this->data(0xf7); + this->command(0x20); + this->wait_until_idle_(); + + this->deep_sleep(); +} +int WaveshareEPaper1P54InBV2::get_height_internal() { return 200; } +int WaveshareEPaper1P54InBV2::get_width_internal() { return 200; } +void WaveshareEPaper1P54InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 1.54in V2 B"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + // ======================================================== // 2.7inch_e-paper_b // ======================================================== diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 7572982a20..a319b078d0 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -166,6 +166,24 @@ enum WaveshareEPaperTypeBModel { WAVESHARE_EPAPER_13_3_IN_K, }; +class WaveshareEPaper1P54InBV2 : public WaveshareEPaperBWR { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + this->command(0x10); + this->data(0x01); + } + + protected: + int get_width_internal() override; + int get_height_internal() override; +}; + class WaveshareEPaper2P7In : public WaveshareEPaper { public: void initialize() override; From 140d77061b33895054b35c6e1245fe4217f81a1e Mon Sep 17 00:00:00 2001 From: JonasB2497 <45214989+JonasB2497@users.noreply.github.com> Date: Mon, 25 Nov 2024 23:29:58 +0100 Subject: [PATCH 172/282] added Waveshare BWR Mode for the 7.5in Display (#7687) --- .../components/waveshare_epaper/display.py | 4 + .../waveshare_epaper/waveshare_epaper.cpp | 107 ++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 38 +++++++ .../waveshare_epaper/test.esp32-ard.yaml | 17 +++ .../waveshare_epaper/test.esp32-c3-ard.yaml | 16 +++ .../waveshare_epaper/test.esp32-c3-idf.yaml | 16 +++ .../waveshare_epaper/test.esp32-idf.yaml | 16 +++ .../waveshare_epaper/test.esp8266-ard.yaml | 16 +++ .../waveshare_epaper/test.rp2040-ard.yaml | 16 +++ 9 files changed, 246 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index fbb5e1353d..d5240b2674 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -79,6 +79,9 @@ WaveshareEPaper7P5InBV2 = waveshare_epaper_ns.class_( WaveshareEPaper7P5InBV3 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InBV3", WaveshareEPaper ) +WaveshareEPaper7P5InBV3BWR = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InBV3BWR", WaveshareEPaperBWR +) WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) @@ -133,6 +136,7 @@ MODELS = { "7.50in": ("b", WaveshareEPaper7P5In), "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), "7.50in-bv3": ("b", WaveshareEPaper7P5InBV3), + "7.50in-bv3-bwr": ("b", WaveshareEPaper7P5InBV3BWR), "7.50in-bc": ("b", WaveshareEPaper7P5InBC), "7.50inv2": ("b", WaveshareEPaper7P5InV2), "7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt), diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 1e27d594b8..cb3b19aa1a 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -2399,6 +2399,113 @@ void WaveshareEPaper7P5InBV3::dump_config() { LOG_UPDATE_INTERVAL(this); } +void WaveshareEPaper7P5InBV3BWR::initialize() { this->init_display_(); } +bool WaveshareEPaper7P5InBV3BWR::wait_until_idle_() { + if (this->busy_pin_ == nullptr) { + return true; + } + + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + this->command(0x71); + if (millis() - start > this->idle_timeout_()) { + ESP_LOGI(TAG, "Timeout while displaying image!"); + return false; + } + App.feed_wdt(); + delay(10); + } + delay(200); // NOLINT + return true; +}; +void WaveshareEPaper7P5InBV3BWR::init_display_() { + this->reset_(); + + // COMMAND POWER SETTING + this->command(0x01); + + // 1-0=11: internal power + this->data(0x07); + this->data(0x17); // VGH&VGL + this->data(0x3F); // VSH + this->data(0x26); // VSL + this->data(0x11); // VSHR + + // VCOM DC Setting + this->command(0x82); + this->data(0x24); // VCOM + + // Booster Setting + this->command(0x06); + this->data(0x27); + this->data(0x27); + this->data(0x2F); + this->data(0x17); + + // POWER ON + this->command(0x04); + + delay(100); // NOLINT + this->wait_until_idle_(); + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0F); // KW-3f KWR-2F BWROTP 0f BWOTP 1f + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x03); // source 800 + this->data(0x20); + this->data(0x01); // gate 480 + this->data(0xE0); + // COMMAND ...? + this->command(0x15); + this->data(0x00); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x20); + this->data(0x00); + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + // Resolution setting + this->command(0x65); + this->data(0x00); + this->data(0x00); // 800*480 + this->data(0x00); + this->data(0x00); +}; +void HOT WaveshareEPaper7P5InBV3BWR::display() { + this->init_display_(); + const uint32_t buf_len = this->get_buffer_length_() / 2u; + + this->command(0x10); // Send BW data Transmission + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + + this->command(0x13); // Send red data Transmission + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i + buf_len]); + } + + this->command(0x12); // Display Refresh + delay(100); // NOLINT + this->wait_until_idle_(); + this->deep_sleep(); +} +int WaveshareEPaper7P5InBV3BWR::get_width_internal() { return 800; } +int WaveshareEPaper7P5InBV3BWR::get_height_internal() { return 480; } +void WaveshareEPaper7P5InBV3BWR::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-bv3 BWR-Mode"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + void WaveshareEPaper7P5In::initialize() { // COMMAND POWER SETTING this->command(0x01); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index a319b078d0..4544f7df59 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -637,6 +637,44 @@ class WaveshareEPaper7P5InBV3 : public WaveshareEPaper { void init_display_(); }; +class WaveshareEPaper7P5InBV3BWR : public WaveshareEPaperBWR { + public: + bool wait_until_idle_(); + + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + this->command(0x02); // Power off + this->wait_until_idle_(); + this->command(0x07); // Deep sleep + this->data(0xA5); + } + + void clear_screen(); + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + void reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + this->reset_pin_->digital_write(false); + delay(5); + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + } + }; + + void init_display_(); +}; + class WaveshareEPaper7P5InBC : public WaveshareEPaper { public: void initialize() override; diff --git a/tests/components/waveshare_epaper/test.esp32-ard.yaml b/tests/components/waveshare_epaper/test.esp32-ard.yaml index 2f06c5c51b..944f98a1e9 100644 --- a/tests/components/waveshare_epaper/test.esp32-ard.yaml +++ b/tests/components/waveshare_epaper/test.esp32-ard.yaml @@ -188,3 +188,20 @@ display: full_update_every: 30 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 7.50in-bv3-bwr + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32-c3-ard.yaml b/tests/components/waveshare_epaper/test.esp32-c3-ard.yaml index 1c4547b7b4..5d651bd180 100644 --- a/tests/components/waveshare_epaper/test.esp32-c3-ard.yaml +++ b/tests/components/waveshare_epaper/test.esp32-c3-ard.yaml @@ -105,3 +105,19 @@ display: model: 1.54in-m5coreink-m09 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 7.50in-bv3-bwr + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml b/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml index 1c4547b7b4..5d651bd180 100644 --- a/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml +++ b/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml @@ -105,3 +105,19 @@ display: model: 1.54in-m5coreink-m09 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 7.50in-bv3-bwr + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32-idf.yaml b/tests/components/waveshare_epaper/test.esp32-idf.yaml index b6082fcfbf..47f894d967 100644 --- a/tests/components/waveshare_epaper/test.esp32-idf.yaml +++ b/tests/components/waveshare_epaper/test.esp32-idf.yaml @@ -105,3 +105,19 @@ display: model: 1.54in-m5coreink-m09 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 7.50in-bv3-bwr + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp8266-ard.yaml b/tests/components/waveshare_epaper/test.esp8266-ard.yaml index 1f076a67be..ceda328598 100644 --- a/tests/components/waveshare_epaper/test.esp8266-ard.yaml +++ b/tests/components/waveshare_epaper/test.esp8266-ard.yaml @@ -105,3 +105,19 @@ display: model: 1.54in-m5coreink-m09 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 7.50in-bv3-bwr + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.rp2040-ard.yaml b/tests/components/waveshare_epaper/test.rp2040-ard.yaml index 6050062d7e..be7e780033 100644 --- a/tests/components/waveshare_epaper/test.rp2040-ard.yaml +++ b/tests/components/waveshare_epaper/test.rp2040-ard.yaml @@ -105,3 +105,19 @@ display: model: 1.54in-m5coreink-m09 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 7.50in-bv3-bwr + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); From 6ee02c47c29712c2b8cffa2ef5cebe25b29719a1 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 16:42:12 -0600 Subject: [PATCH 173/282] [homeassistant.number] Return when value not set (#7839) --- .../components/homeassistant/number/homeassistant_number.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp index d3e285f4ac..a7f71c3244 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.cpp +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -27,6 +27,7 @@ void HomeassistantNumber::min_retrieved_(const std::string &min) { auto min_value = parse_number<float>(min); if (!min_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_.c_str(), min.c_str()); + return; } ESP_LOGD(TAG, "'%s': Min retrieved: %s", get_name().c_str(), min.c_str()); this->traits.set_min_value(min_value.value()); @@ -36,6 +37,7 @@ void HomeassistantNumber::max_retrieved_(const std::string &max) { auto max_value = parse_number<float>(max); if (!max_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_.c_str(), max.c_str()); + return; } ESP_LOGD(TAG, "'%s': Max retrieved: %s", get_name().c_str(), max.c_str()); this->traits.set_max_value(max_value.value()); @@ -45,6 +47,7 @@ void HomeassistantNumber::step_retrieved_(const std::string &step) { auto step_value = parse_number<float>(step); if (!step_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_.c_str(), step.c_str()); + return; } ESP_LOGD(TAG, "'%s': Step Retrieved %s", get_name().c_str(), step.c_str()); this->traits.set_step(step_value.value()); From 4fbf41472a318bf923632508a3ba9473f06f3171 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 17:41:27 -0600 Subject: [PATCH 174/282] [CI] Add/update some system include paths (#7831) --- .clang-tidy | 6 ++++-- script/clang-tidy | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 946f2950d8..994416b2f1 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -63,15 +63,16 @@ Checks: >- -misc-non-private-member-variables-in-classes, -misc-no-recursion, -misc-unused-parameters, - -modernize-avoid-c-arrays, -modernize-avoid-bind, + -modernize-avoid-c-arrays, -modernize-concat-nested-namespaces, -modernize-return-braced-init-list, -modernize-use-auto, -modernize-use-default-member-init, -modernize-use-equals-default, - -modernize-use-trailing-return-type, -modernize-use-nodiscard, + -modernize-use-nullptr, + -modernize-use-trailing-return-type, -mpi-*, -objc-*, -readability-container-data-pointer, @@ -82,6 +83,7 @@ Checks: >- -readability-isolate-declaration, -readability-magic-numbers, -readability-make-member-function-const, + -readability-named-parameter, -readability-redundant-string-init, -readability-uppercase-literal-suffix, -readability-use-anyofallof, diff --git a/script/clang-tidy b/script/clang-tidy index 61199edce3..319fab70a2 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -100,10 +100,13 @@ def clang_options(idedata): # add library include directories using -isystem to suppress their errors for directory in list(idedata["includes"]["build"]): # skip our own directories, we add those later - if ( - not directory.startswith(f"{root_path}/") - or directory.startswith(f"{root_path}/.pio/") - or directory.startswith(f"{root_path}/managed_components/") + if not directory.startswith(f"{root_path}") or directory.startswith( + ( + f"{root_path}/.pio", + f"{root_path}/.platformio", + f"{root_path}/.temp", + f"{root_path}/managed_components", + ) ): cmd.extend(["-isystem", directory]) From f4766ab74fda60c5e43eaea00c1f91f2a5e77ce2 Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Mon, 25 Nov 2024 13:58:21 -1000 Subject: [PATCH 175/282] [wifi] fix 32 char SSIDs (#7834) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- .../wifi/wifi_component_esp32_arduino.cpp | 24 +++++++++++++++---- .../wifi/wifi_component_esp8266.cpp | 24 +++++++++++++++---- .../wifi/wifi_component_esp_idf.cpp | 24 +++++++++++++++---- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 18c706cb01..bc10bbd1e5 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -137,8 +137,16 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - snprintf(reinterpret_cast<char *>(conf.sta.ssid), sizeof(conf.sta.ssid), "%s", ap.get_ssid().c_str()); - snprintf(reinterpret_cast<char *>(conf.sta.password), sizeof(conf.sta.password), "%s", ap.get_password().c_str()); + if (ap.get_ssid().size() > sizeof(conf.sta.ssid)) { + ESP_LOGE(TAG, "SSID is too long"); + return false; + } + if (ap.get_password().size() > sizeof(conf.sta.password)) { + ESP_LOGE(TAG, "password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.sta.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); + memcpy(reinterpret_cast<char *>(conf.sta.password), ap.get_password().c_str(), ap.get_password().size()); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -746,7 +754,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - snprintf(reinterpret_cast<char *>(conf.ap.ssid), sizeof(conf.ap.ssid), "%s", ap.get_ssid().c_str()); + if (ap.get_ssid().size() > sizeof(conf.ap.ssid)) { + ESP_LOGE(TAG, "AP SSID is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ap.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -757,7 +769,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - snprintf(reinterpret_cast<char *>(conf.ap.password), sizeof(conf.ap.password), "%s", ap.get_password().c_str()); + if (ap.get_password().size() > sizeof(conf.ap.password)) { + ESP_LOGE(TAG, "AP password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ap.password), ap.get_password().c_str(), ap.get_password().size()); } // pairwise cipher of SoftAP, group cipher will be derived using this. diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index a18d078967..8e1c2e70d8 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -236,8 +236,16 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { struct station_config conf {}; memset(&conf, 0, sizeof(conf)); - snprintf(reinterpret_cast<char *>(conf.ssid), sizeof(conf.ssid), "%s", ap.get_ssid().c_str()); - snprintf(reinterpret_cast<char *>(conf.password), sizeof(conf.password), "%s", ap.get_password().c_str()); + if (ap.get_ssid().size() > sizeof(conf.ssid)) { + ESP_LOGE(TAG, "SSID is too long"); + return false; + } + if (ap.get_password().size() > sizeof(conf.password)) { + ESP_LOGE(TAG, "password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); + memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size()); if (ap.get_bssid().has_value()) { conf.bssid_set = 1; @@ -775,7 +783,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return false; struct softap_config conf {}; - snprintf(reinterpret_cast<char *>(conf.ssid), sizeof(conf.ssid), "%s", ap.get_ssid().c_str()); + if (ap.get_ssid().size() > sizeof(conf.ssid)) { + ESP_LOGE(TAG, "AP SSID is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); conf.ssid_len = static_cast<uint8>(ap.get_ssid().size()); conf.channel = ap.get_channel().value_or(1); conf.ssid_hidden = ap.get_hidden(); @@ -787,7 +799,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.password = 0; } else { conf.authmode = AUTH_WPA2_PSK; - snprintf(reinterpret_cast<char *>(conf.password), sizeof(conf.password), "%s", ap.get_password().c_str()); + if (ap.get_password().size() > sizeof(conf.password)) { + ESP_LOGE(TAG, "AP password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size()); } ETS_UART_INTR_DISABLE(); diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 1bf14ff40b..1af271345f 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -289,8 +289,16 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - snprintf(reinterpret_cast<char *>(conf.sta.ssid), sizeof(conf.sta.ssid), "%s", ap.get_ssid().c_str()); - snprintf(reinterpret_cast<char *>(conf.sta.password), sizeof(conf.sta.password), "%s", ap.get_password().c_str()); + if (ap.get_ssid().size() > sizeof(conf.sta.ssid)) { + ESP_LOGE(TAG, "SSID is too long"); + return false; + } + if (ap.get_password().size() > sizeof(conf.sta.password)) { + ESP_LOGE(TAG, "password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.sta.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); + memcpy(reinterpret_cast<char *>(conf.sta.password), ap.get_password().c_str(), ap.get_password().size()); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -902,7 +910,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast<char *>(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); + if (ap.get_ssid().size() > sizeof(conf.ap.ssid)) { + ESP_LOGE(TAG, "AP SSID is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ap.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -913,7 +925,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strncpy(reinterpret_cast<char *>(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password)); + if (ap.get_password().size() > sizeof(conf.ap.password)) { + ESP_LOGE(TAG, "AP password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ap.password), ap.get_password().c_str(), ap.get_password().size()); } // pairwise cipher of SoftAP, group cipher will be derived using this. From a70cee1dc1162da1ee5f1f078991aa2c68d421a3 Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Mon, 25 Nov 2024 14:15:01 -1000 Subject: [PATCH 176/282] fix local time timestamp calculation (#7807) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- .../components/datetime/datetime_entity.cpp | 4 +-- esphome/core/time.cpp | 34 +++++++++++-------- esphome/core/time.h | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/esphome/components/datetime/datetime_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index f215b7acb5..3d92194efa 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -60,9 +60,7 @@ ESPTime DateTimeEntity::state_as_esptime() const { obj.hour = this->hour_; obj.minute = this->minute_; obj.second = this->second_; - obj.day_of_week = 1; // Required to be valid for recalc_timestamp_local but not used. - obj.day_of_year = 1; // Required to be valid for recalc_timestamp_local but not used. - obj.recalc_timestamp_local(false); + obj.recalc_timestamp_local(); return obj; } diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index f7aa4fdddb..31977d972b 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -5,20 +5,18 @@ namespace esphome { -bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } - uint8_t days_in_month(uint8_t month, uint16_t year) { static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - uint8_t days = DAYS_IN_MONTH[month]; - if (month == 2 && is_leap_year(year)) + if (month == 2 && (year % 4 == 0)) return 29; - return days; + return DAYS_IN_MONTH[month]; } size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { struct tm c_tm = this->to_c_tm(); return ::strftime(buffer, buffer_len, format, &c_tm); } + ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) { ESPTime res{}; res.second = uint8_t(c_tm->tm_sec); @@ -33,6 +31,7 @@ ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) { res.timestamp = c_time; return res; } + struct tm ESPTime::to_c_tm() { struct tm c_tm {}; c_tm.tm_sec = this->second; @@ -46,6 +45,7 @@ struct tm ESPTime::to_c_tm() { c_tm.tm_isdst = this->is_dst; return c_tm; } + std::string ESPTime::strftime(const std::string &format) { std::string timestr; timestr.resize(format.size() * 4); @@ -142,6 +142,7 @@ void ESPTime::increment_second() { this->year++; } } + void ESPTime::increment_day() { this->timestamp += 86400; @@ -159,23 +160,22 @@ void ESPTime::increment_day() { this->year++; } } + void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { time_t res = 0; - if (!this->fields_in_range()) { this->timestamp = -1; return; } for (int i = 1970; i < this->year; i++) - res += is_leap_year(i) ? 366 : 365; + res += (year % 4 == 0) ? 366 : 365; if (use_day_of_year) { res += this->day_of_year - 1; } else { for (int i = 1; i < this->month; i++) res += days_in_month(i, this->year); - res += this->day_of_month - 1; } @@ -188,13 +188,17 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { this->timestamp = res; } -void ESPTime::recalc_timestamp_local(bool use_day_of_year) { - this->recalc_timestamp_utc(use_day_of_year); - this->timestamp -= ESPTime::timezone_offset(); - ESPTime temp = ESPTime::from_epoch_local(this->timestamp); - if (temp.is_dst) { - this->timestamp -= 3600; - } +void ESPTime::recalc_timestamp_local() { + struct tm tm; + + tm.tm_year = this->year - 1900; + tm.tm_mon = this->month - 1; + tm.tm_mday = this->day_of_month; + tm.tm_hour = this->hour; + tm.tm_min = this->minute; + tm.tm_sec = this->second; + + this->timestamp = mktime(&tm); } int32_t ESPTime::timezone_offset() { diff --git a/esphome/core/time.h b/esphome/core/time.h index bce1108d93..5cbd9369fb 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -9,8 +9,6 @@ namespace esphome { template<typename T> bool increment_time_value(T ¤t, uint16_t begin, uint16_t end); -bool is_leap_year(uint32_t year); - uint8_t days_in_month(uint8_t month, uint16_t year); /// A more user-friendly version of struct tm from time.h @@ -100,7 +98,7 @@ struct ESPTime { void recalc_timestamp_utc(bool use_day_of_year = true); /// Recalculate the timestamp field from the other fields of this ESPTime instance assuming local fields. - void recalc_timestamp_local(bool use_day_of_year = true); + void recalc_timestamp_local(); /// Convert this ESPTime instance back to a tm struct. struct tm to_c_tm(); From d9d368d38eff2024bf50ab9fc034d19ef91d6799 Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Mon, 25 Nov 2024 14:21:47 -1000 Subject: [PATCH 177/282] add on_key trigger to matrix_keypad (#7830) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- esphome/components/matrix_keypad/__init__.py | 18 +++++++++++++++--- .../components/matrix_keypad/matrix_keypad.cpp | 4 ++++ .../components/matrix_keypad/matrix_keypad.h | 5 +++++ esphome/components/wiegand/__init__.py | 7 +++---- esphome/const.py | 1 + tests/components/matrix_keypad/common.yaml | 8 ++++++++ .../matrix_keypad/test.esp32-ard.yaml | 12 ++++-------- .../matrix_keypad/test.esp32-c3-ard.yaml | 12 ++++-------- .../matrix_keypad/test.esp32-c3-idf.yaml | 12 ++++-------- .../matrix_keypad/test.esp32-idf.yaml | 12 ++++-------- .../matrix_keypad/test.esp32-s3-idf.yaml | 15 +++++++++++++++ .../matrix_keypad/test.esp8266-ard.yaml | 12 ++++-------- .../matrix_keypad/test.rp2040-ard.yaml | 12 ++++-------- 13 files changed, 75 insertions(+), 55 deletions(-) create mode 100644 tests/components/matrix_keypad/common.yaml create mode 100644 tests/components/matrix_keypad/test.esp32-s3-idf.yaml diff --git a/esphome/components/matrix_keypad/__init__.py b/esphome/components/matrix_keypad/__init__.py index 5250a45732..b2bcde98ec 100644 --- a/esphome/components/matrix_keypad/__init__.py +++ b/esphome/components/matrix_keypad/__init__.py @@ -1,8 +1,8 @@ +from esphome import automation, pins import esphome.codegen as cg -import esphome.config_validation as cv -from esphome import pins from esphome.components import key_provider -from esphome.const import CONF_ID, CONF_PIN +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID CODEOWNERS = ["@ssieb"] @@ -14,6 +14,9 @@ matrix_keypad_ns = cg.esphome_ns.namespace("matrix_keypad") MatrixKeypad = matrix_keypad_ns.class_( "MatrixKeypad", key_provider.KeyProvider, cg.Component ) +MatrixKeyTrigger = matrix_keypad_ns.class_( + "MatrixKeyTrigger", automation.Trigger.template(cg.uint8) +) CONF_KEYPAD_ID = "keypad_id" CONF_ROWS = "rows" @@ -47,6 +50,11 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DEBOUNCE_TIME, default=1): cv.int_range(min=1, max=100), cv.Optional(CONF_HAS_DIODES): cv.boolean, cv.Optional(CONF_HAS_PULLDOWNS): cv.boolean, + cv.Optional(CONF_ON_KEY): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MatrixKeyTrigger), + } + ), } ), check_keys, @@ -73,3 +81,7 @@ async def to_code(config): cg.add(var.set_has_diodes(config[CONF_HAS_DIODES])) if CONF_HAS_PULLDOWNS in config: cg.add(var.set_has_pulldowns(config[CONF_HAS_PULLDOWNS])) + for conf in config.get(CONF_ON_KEY, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_key_trigger(trigger)) + await automation.build_automation(trigger, [(cg.uint8, "x")], conf) diff --git a/esphome/components/matrix_keypad/matrix_keypad.cpp b/esphome/components/matrix_keypad/matrix_keypad.cpp index f62c75c869..8537997935 100644 --- a/esphome/components/matrix_keypad/matrix_keypad.cpp +++ b/esphome/components/matrix_keypad/matrix_keypad.cpp @@ -86,6 +86,8 @@ void MatrixKeypad::loop() { if (!this->keys_.empty()) { uint8_t keycode = this->keys_[key]; ESP_LOGD(TAG, "key '%c' pressed", keycode); + for (auto &trigger : this->key_triggers_) + trigger->trigger(keycode); for (auto &listener : this->listeners_) listener->key_pressed(keycode); this->send_key_(keycode); @@ -107,5 +109,7 @@ void MatrixKeypad::dump_config() { void MatrixKeypad::register_listener(MatrixKeypadListener *listener) { this->listeners_.push_back(listener); } +void MatrixKeypad::register_key_trigger(MatrixKeyTrigger *trig) { this->key_triggers_.push_back(trig); } + } // namespace matrix_keypad } // namespace esphome diff --git a/esphome/components/matrix_keypad/matrix_keypad.h b/esphome/components/matrix_keypad/matrix_keypad.h index d506040b7c..8b309b42c2 100644 --- a/esphome/components/matrix_keypad/matrix_keypad.h +++ b/esphome/components/matrix_keypad/matrix_keypad.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/components/key_provider/key_provider.h" +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -18,6 +19,8 @@ class MatrixKeypadListener { virtual void key_released(uint8_t key){}; }; +class MatrixKeyTrigger : public Trigger<uint8_t> {}; + class MatrixKeypad : public key_provider::KeyProvider, public Component { public: void setup() override; @@ -31,6 +34,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component { void set_has_pulldowns(int has_pulldowns) { has_pulldowns_ = has_pulldowns; }; void register_listener(MatrixKeypadListener *listener); + void register_key_trigger(MatrixKeyTrigger *trig); protected: std::vector<GPIOPin *> rows_; @@ -42,6 +46,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component { int pressed_key_ = -1; std::vector<MatrixKeypadListener *> listeners_{}; + std::vector<MatrixKeyTrigger *> key_triggers_; }; } // namespace matrix_keypad diff --git a/esphome/components/wiegand/__init__.py b/esphome/components/wiegand/__init__.py index 7b05c43198..962ac4c373 100644 --- a/esphome/components/wiegand/__init__.py +++ b/esphome/components/wiegand/__init__.py @@ -1,8 +1,8 @@ +from esphome import automation, pins import esphome.codegen as cg -import esphome.config_validation as cv -from esphome import pins, automation from esphome.components import key_provider -from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_ON_KEY, CONF_ON_TAG, CONF_TRIGGER_ID CODEOWNERS = ["@ssieb"] @@ -25,7 +25,6 @@ WiegandKeyTrigger = wiegand_ns.class_( CONF_D0 = "d0" CONF_D1 = "d1" -CONF_ON_KEY = "on_key" CONF_ON_RAW = "on_raw" CONFIG_SCHEMA = cv.Schema( diff --git a/esphome/const.py b/esphome/const.py index 50528b7363..d2df83aa43 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -575,6 +575,7 @@ CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched" CONF_ON_FINISHED_WRITE = "on_finished_write" CONF_ON_IDLE = "on_idle" CONF_ON_JSON_MESSAGE = "on_json_message" +CONF_ON_KEY = "on_key" CONF_ON_LOCK = "on_lock" CONF_ON_LOOP = "on_loop" CONF_ON_MESSAGE = "on_message" diff --git a/tests/components/matrix_keypad/common.yaml b/tests/components/matrix_keypad/common.yaml new file mode 100644 index 0000000000..32e334d890 --- /dev/null +++ b/tests/components/matrix_keypad/common.yaml @@ -0,0 +1,8 @@ +binary_sensor: + - platform: matrix_keypad + id: key4 + row: 1 + col: 1 + - platform: matrix_keypad + id: key1 + key: 1 diff --git a/tests/components/matrix_keypad/test.esp32-ard.yaml b/tests/components/matrix_keypad/test.esp32-ard.yaml index c8e9b54534..70bb70638d 100644 --- a/tests/components/matrix_keypad/test.esp32-ard.yaml +++ b/tests/components/matrix_keypad/test.esp32-ard.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 15 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.esp32-c3-ard.yaml b/tests/components/matrix_keypad/test.esp32-c3-ard.yaml index d15e6af21a..75d9c0b263 100644 --- a/tests/components/matrix_keypad/test.esp32-c3-ard.yaml +++ b/tests/components/matrix_keypad/test.esp32-c3-ard.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 4 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.esp32-c3-idf.yaml b/tests/components/matrix_keypad/test.esp32-c3-idf.yaml index d15e6af21a..75d9c0b263 100644 --- a/tests/components/matrix_keypad/test.esp32-c3-idf.yaml +++ b/tests/components/matrix_keypad/test.esp32-c3-idf.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 4 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.esp32-idf.yaml b/tests/components/matrix_keypad/test.esp32-idf.yaml index c8e9b54534..70bb70638d 100644 --- a/tests/components/matrix_keypad/test.esp32-idf.yaml +++ b/tests/components/matrix_keypad/test.esp32-idf.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 15 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.esp32-s3-idf.yaml b/tests/components/matrix_keypad/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..a491f2ed59 --- /dev/null +++ b/tests/components/matrix_keypad/test.esp32-s3-idf.yaml @@ -0,0 +1,15 @@ +packages: + common: !include common.yaml + +matrix_keypad: + id: keypad + rows: + - pin: 10 + - pin: 11 + columns: + - pin: 12 + - pin: 13 + keys: "1234" + has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.esp8266-ard.yaml b/tests/components/matrix_keypad/test.esp8266-ard.yaml index c8e9b54534..70bb70638d 100644 --- a/tests/components/matrix_keypad/test.esp8266-ard.yaml +++ b/tests/components/matrix_keypad/test.esp8266-ard.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 15 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.rp2040-ard.yaml b/tests/components/matrix_keypad/test.rp2040-ard.yaml index d15e6af21a..75d9c0b263 100644 --- a/tests/components/matrix_keypad/test.rp2040-ard.yaml +++ b/tests/components/matrix_keypad/test.rp2040-ard.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 4 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); From c0dcecc465fa8b0d3205880174ca820bc8bfd3d3 Mon Sep 17 00:00:00 2001 From: Citric Lee <37475446+limengdu@users.noreply.github.com> Date: Tue, 26 Nov 2024 08:53:21 +0800 Subject: [PATCH 178/282] Add: Seeed Studio mr60fda2 mmwave sensor (#7576) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Spencer Yan <spencer@spenyan.com> --- CODEOWNERS | 1 + esphome/components/seeed_mr60fda2/__init__.py | 41 ++ .../seeed_mr60fda2/binary_sensor.py | 33 ++ .../seeed_mr60fda2/button/__init__.py | 45 +++ .../button/get_radar_parameters_button.cpp | 9 + .../button/get_radar_parameters_button.h | 18 + .../button/reset_radar_button.cpp | 9 + .../button/reset_radar_button.h | 18 + .../seeed_mr60fda2/seeed_mr60fda2.cpp | 368 ++++++++++++++++++ .../seeed_mr60fda2/seeed_mr60fda2.h | 101 +++++ .../seeed_mr60fda2/select/__init__.py | 59 +++ .../select/height_threshold_select.cpp | 15 + .../select/height_threshold_select.h | 18 + .../select/install_height_select.cpp | 15 + .../select/install_height_select.h | 18 + .../select/sensitivity_select.cpp | 15 + .../select/sensitivity_select.h | 18 + tests/components/seeed_mr60fda2/common.yaml | 34 ++ .../seeed_mr60fda2/test.esp32-c3-ard.yaml | 5 + .../seeed_mr60fda2/test.esp32-c3-idf.yaml | 5 + 20 files changed, 845 insertions(+) create mode 100644 esphome/components/seeed_mr60fda2/__init__.py create mode 100644 esphome/components/seeed_mr60fda2/binary_sensor.py create mode 100644 esphome/components/seeed_mr60fda2/button/__init__.py create mode 100644 esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.cpp create mode 100644 esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.h create mode 100644 esphome/components/seeed_mr60fda2/button/reset_radar_button.cpp create mode 100644 esphome/components/seeed_mr60fda2/button/reset_radar_button.h create mode 100644 esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp create mode 100644 esphome/components/seeed_mr60fda2/seeed_mr60fda2.h create mode 100644 esphome/components/seeed_mr60fda2/select/__init__.py create mode 100644 esphome/components/seeed_mr60fda2/select/height_threshold_select.cpp create mode 100644 esphome/components/seeed_mr60fda2/select/height_threshold_select.h create mode 100644 esphome/components/seeed_mr60fda2/select/install_height_select.cpp create mode 100644 esphome/components/seeed_mr60fda2/select/install_height_select.h create mode 100644 esphome/components/seeed_mr60fda2/select/sensitivity_select.cpp create mode 100644 esphome/components/seeed_mr60fda2/select/sensitivity_select.h create mode 100644 tests/components/seeed_mr60fda2/common.yaml create mode 100644 tests/components/seeed_mr60fda2/test.esp32-c3-ard.yaml create mode 100644 tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml diff --git a/CODEOWNERS b/CODEOWNERS index dd3926d283..fb6d11d1fb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -354,6 +354,7 @@ esphome/components/sdl/* @clydebarrow esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/seeed_mr24hpc1/* @limengdu +esphome/components/seeed_mr60fda2/* @limengdu esphome/components/selec_meter/* @sourabhjaiswal esphome/components/select/* @esphome/core esphome/components/sen0321/* @notjj diff --git a/esphome/components/seeed_mr60fda2/__init__.py b/esphome/components/seeed_mr60fda2/__init__.py new file mode 100644 index 0000000000..e79134deec --- /dev/null +++ b/esphome/components/seeed_mr60fda2/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CODEOWNERS = ["@limengdu"] +DEPENDENCIES = ["uart"] +MULTI_CONF = True + +mr60fda2_ns = cg.esphome_ns.namespace("seeed_mr60fda2") + +MR60FDA2Component = mr60fda2_ns.class_( + "MR60FDA2Component", cg.Component, uart.UARTDevice +) + +CONF_MR60FDA2_ID = "mr60fda2_id" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MR60FDA2Component), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "seeed_mr60fda2", + require_tx=True, + require_rx=True, + baud_rate=115200, + parity="NONE", + stop_bits=1, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) diff --git a/esphome/components/seeed_mr60fda2/binary_sensor.py b/esphome/components/seeed_mr60fda2/binary_sensor.py new file mode 100644 index 0000000000..2860ac0100 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/binary_sensor.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import DEVICE_CLASS_OCCUPANCY, DEVICE_CLASS_SAFETY + +from . import CONF_MR60FDA2_ID, MR60FDA2Component + +DEPENDENCIES = ["seeed_mr60fda2"] + +CONF_PEOPLE_EXIST = "people_exist" +CONF_FALL_DETECTED = "fall_detected" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR60FDA2_ID): cv.use_id(MR60FDA2Component), + cv.Optional(CONF_PEOPLE_EXIST): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, icon="mdi:motion-sensor" + ), + cv.Optional(CONF_FALL_DETECTED): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_SAFETY, icon="mdi:emergency" + ), +} + + +async def to_code(config): + mr60fda2_component = await cg.get_variable(config[CONF_MR60FDA2_ID]) + + if people_exist_config := config.get(CONF_PEOPLE_EXIST): + sens = await binary_sensor.new_binary_sensor(people_exist_config) + cg.add(mr60fda2_component.set_people_exist_binary_sensor(sens)) + + if is_fall_config := config.get(CONF_FALL_DETECTED): + sens = await binary_sensor.new_binary_sensor(is_fall_config) + cg.add(mr60fda2_component.set_fall_detected_binary_sensor(sens)) diff --git a/esphome/components/seeed_mr60fda2/button/__init__.py b/esphome/components/seeed_mr60fda2/button/__init__.py new file mode 100644 index 0000000000..1415dc27ca --- /dev/null +++ b/esphome/components/seeed_mr60fda2/button/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_RESTART, + DEVICE_CLASS_UPDATE, + ENTITY_CATEGORY_DIAGNOSTIC, + ENTITY_CATEGORY_NONE, + CONF_FACTORY_RESET, +) + +from .. import CONF_MR60FDA2_ID, MR60FDA2Component, mr60fda2_ns + +DEPENDENCIES = ["seeed_mr60fda2"] + +GetRadarParametersButton = mr60fda2_ns.class_("GetRadarParametersButton", button.Button) +ResetRadarButton = mr60fda2_ns.class_("ResetRadarButton", button.Button) + +CONF_GET_RADAR_PARAMETERS = "get_radar_parameters" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR60FDA2_ID): cv.use_id(MR60FDA2Component), + cv.Optional(CONF_GET_RADAR_PARAMETERS): button.button_schema( + GetRadarParametersButton, + device_class=DEVICE_CLASS_UPDATE, + entity_category=ENTITY_CATEGORY_NONE, + ), + cv.Optional(CONF_FACTORY_RESET): button.button_schema( + ResetRadarButton, + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + + +async def to_code(config): + mr60fda2_component = await cg.get_variable(config[CONF_MR60FDA2_ID]) + if get_radar_parameters_config := config.get(CONF_GET_RADAR_PARAMETERS): + b = await button.new_button(get_radar_parameters_config) + await cg.register_parented(b, config[CONF_MR60FDA2_ID]) + cg.add(mr60fda2_component.set_get_radar_parameters_button(b)) + if factory_reset_config := config.get(CONF_FACTORY_RESET): + b = await button.new_button(factory_reset_config) + await cg.register_parented(b, config[CONF_MR60FDA2_ID]) + cg.add(mr60fda2_component.set_factory_reset_button(b)) diff --git a/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.cpp b/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.cpp new file mode 100644 index 0000000000..88be6dfe7c --- /dev/null +++ b/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.cpp @@ -0,0 +1,9 @@ +#include "get_radar_parameters_button.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +void GetRadarParametersButton::press_action() { this->parent_->get_radar_parameters(); } + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.h b/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.h new file mode 100644 index 0000000000..9d6d507383 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../seeed_mr60fda2.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +class GetRadarParametersButton : public button::Button, public Parented<MR60FDA2Component> { + public: + GetRadarParametersButton() = default; + + protected: + void press_action() override; +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/button/reset_radar_button.cpp b/esphome/components/seeed_mr60fda2/button/reset_radar_button.cpp new file mode 100644 index 0000000000..0a5833a18c --- /dev/null +++ b/esphome/components/seeed_mr60fda2/button/reset_radar_button.cpp @@ -0,0 +1,9 @@ +#include "reset_radar_button.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +void ResetRadarButton::press_action() { this->parent_->factory_reset(); } + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/button/reset_radar_button.h b/esphome/components/seeed_mr60fda2/button/reset_radar_button.h new file mode 100644 index 0000000000..66780fb8af --- /dev/null +++ b/esphome/components/seeed_mr60fda2/button/reset_radar_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../seeed_mr60fda2.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +class ResetRadarButton : public button::Button, public Parented<MR60FDA2Component> { + public: + ResetRadarButton() = default; + + protected: + void press_action() override; +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp new file mode 100644 index 0000000000..d183a1f77f --- /dev/null +++ b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp @@ -0,0 +1,368 @@ +#include "seeed_mr60fda2.h" +#include "esphome/core/log.h" + +#include <cinttypes> +#include <utility> + +namespace esphome { +namespace seeed_mr60fda2 { + +static const char *const TAG = "seeed_mr60fda2"; + +// Prints the component's configuration data. dump_config() prints all of the component's configuration +// items in an easy-to-read format, including the configuration key-value pairs. +void MR60FDA2Component::dump_config() { + ESP_LOGCONFIG(TAG, "MR60FDA2:"); +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "People Exist Binary Sensor", this->people_exist_binary_sensor_); + LOG_BINARY_SENSOR(" ", "Is Fall Binary Sensor", this->fall_detected_binary_sensor_); +#endif +#ifdef USE_BUTTON + LOG_BUTTON(" ", "Get Radar Parameters Button", this->get_radar_parameters_button_); + LOG_BUTTON(" ", "Reset Radar Button", this->factory_reset_button_); +#endif +#ifdef USE_SELECT + LOG_SELECT(" ", "Install Height Select", this->install_height_select_); + LOG_SELECT(" ", "Height Threshold Select", this->height_threshold_select_); + LOG_SELECT(" ", "Sensitivity Select", this->sensitivity_select_); +#endif +} + +// Initialisation functions +void MR60FDA2Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MR60FDA2..."); + this->check_uart_settings(115200); + + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + this->current_frame_id_ = 0; + this->current_frame_len_ = 0; + this->current_data_frame_len_ = 0; + this->current_frame_type_ = 0; + this->get_radar_parameters(); + + memset(this->current_frame_buf_, 0, FRAME_BUF_MAX_SIZE); + memset(this->current_data_buf_, 0, DATA_BUF_MAX_SIZE); + + ESP_LOGCONFIG(TAG, "Set up MR60FDA2 complete"); +} + +// main loop +void MR60FDA2Component::loop() { + uint8_t byte; + + // Is there data on the serial port + while (this->available()) { + this->read_byte(&byte); + this->split_frame_(byte); // split data frame + } +} + +/** + * @brief Calculate the checksum for a byte array. + * + * This function calculates the checksum for the provided byte array using an + * XOR-based checksum algorithm. + * + * @param data The byte array to calculate the checksum for. + * @param len The length of the byte array. + * @return The calculated checksum. + */ +static uint8_t calculate_checksum(const uint8_t *data, size_t len) { + uint8_t checksum = 0; + for (size_t i = 0; i < len; i++) { + checksum ^= data[i]; + } + checksum = ~checksum; + return checksum; +} + +/** + * @brief Validate the checksum of a byte array. + * + * This function validates the checksum of the provided byte array by comparing + * it to the expected checksum. + * + * @param data The byte array to validate. + * @param len The length of the byte array. + * @param expected_checksum The expected checksum. + * @return True if the checksum is valid, false otherwise. + */ +static bool validate_checksum(const uint8_t *data, size_t len, uint8_t expected_checksum) { + return calculate_checksum(data, len) == expected_checksum; +} + +static uint8_t find_nearest_index(float value, const float *arr, int size) { + int nearest_index = 0; + float min_diff = std::abs(value - arr[0]); + for (int i = 1; i < size; ++i) { + float diff = std::abs(value - arr[i]); + if (diff < min_diff) { + min_diff = diff; + nearest_index = i; + } + } + return nearest_index; +} + +/** + * @brief Convert a float value to a byte array. + * + * This function converts a float value to a byte array. + * + * @param value The float value to convert. + * @param bytes The byte array to store the converted value. + */ +static void float_to_bytes(float value, unsigned char *bytes) { + union { + float float_value; + unsigned char byte_array[4]; + } u; + + u.float_value = value; + memcpy(bytes, u.byte_array, 4); +} + +/** + * @brief Convert a 32-bit unsigned integer to a byte array. + * + * This function converts a 32-bit unsigned integer to a byte array. + * + * @param value The 32-bit unsigned integer to convert. + * @param bytes The byte array to store the converted value. + */ +static void int_to_bytes(uint32_t value, unsigned char *bytes) { + bytes[0] = value & 0xFF; + bytes[1] = (value >> 8) & 0xFF; + bytes[2] = (value >> 16) & 0xFF; + bytes[3] = (value >> 24) & 0xFF; +} + +void MR60FDA2Component::split_frame_(uint8_t buffer) { + switch (this->current_frame_locate_) { + case LOCATE_FRAME_HEADER: // starting buffer + if (buffer == FRAME_HEADER_BUFFER) { + this->current_frame_len_ = 1; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + } + break; + case LOCATE_ID_FRAME1: + this->current_frame_id_ = buffer << 8; + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + break; + case LOCATE_ID_FRAME2: + this->current_frame_id_ += buffer; + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + break; + case LOCATE_LENGTH_FRAME_H: + this->current_data_frame_len_ = buffer << 8; + if (this->current_data_frame_len_ == 0x00) { + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + } else { + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } + break; + case LOCATE_LENGTH_FRAME_L: + this->current_data_frame_len_ += buffer; + if (this->current_data_frame_len_ > DATA_BUF_MAX_SIZE) { + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } else { + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + } + break; + case LOCATE_TYPE_FRAME1: + this->current_frame_type_ = buffer << 8; + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + break; + case LOCATE_TYPE_FRAME2: + this->current_frame_type_ += buffer; + if ((this->current_frame_type_ == IS_FALL_TYPE_BUFFER) || + (this->current_frame_type_ == PEOPLE_EXIST_TYPE_BUFFER) || + (this->current_frame_type_ == RESULT_INSTALL_HEIGHT) || (this->current_frame_type_ == RESULT_PARAMETERS) || + (this->current_frame_type_ == RESULT_HEIGHT_THRESHOLD) || (this->current_frame_type_ == RESULT_SENSITIVITY)) { + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + } else { + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } + break; + case LOCATE_HEAD_CKSUM_FRAME: + if (validate_checksum(this->current_frame_buf_, this->current_frame_len_, buffer)) { + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + } else { + ESP_LOGD(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", buffer); + ESP_LOGV(TAG, "CURRENT_FRAME: %s %s", + format_hex_pretty(this->current_frame_buf_, this->current_frame_len_).c_str(), + format_hex_pretty(&buffer, 1).c_str()); + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } + break; + case LOCATE_DATA_FRAME: + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_data_buf_[this->current_frame_len_ - LEN_TO_DATA_FRAME] = buffer; + if (this->current_frame_len_ - LEN_TO_HEAD_CKSUM == this->current_data_frame_len_) { + this->current_frame_locate_++; + } + if (this->current_frame_len_ > FRAME_BUF_MAX_SIZE) { + ESP_LOGD(TAG, "PRACTICE_DATA_FRAME_LEN ERROR: %d", this->current_frame_len_ - LEN_TO_HEAD_CKSUM); + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } + break; + case LOCATE_DATA_CKSUM_FRAME: + if (validate_checksum(this->current_data_buf_, this->current_data_frame_len_, buffer)) { + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + this->process_frame_(); + } else { + ESP_LOGD(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", buffer); + ESP_LOGV(TAG, "GET CURRENT_FRAME: %s %s", + format_hex_pretty(this->current_frame_buf_, this->current_frame_len_).c_str(), + format_hex_pretty(&buffer, 1).c_str()); + + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } + break; + default: + break; + } +} + +void MR60FDA2Component::process_frame_() { + switch (this->current_frame_type_) { + case IS_FALL_TYPE_BUFFER: + if (this->fall_detected_binary_sensor_ != nullptr) { + this->fall_detected_binary_sensor_->publish_state(this->current_frame_buf_[LEN_TO_HEAD_CKSUM]); + } + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + + case PEOPLE_EXIST_TYPE_BUFFER: + if (this->people_exist_binary_sensor_ != nullptr) + this->people_exist_binary_sensor_->publish_state(this->current_frame_buf_[LEN_TO_HEAD_CKSUM]); + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + + case RESULT_INSTALL_HEIGHT: + if (this->current_data_buf_[0]) { + ESP_LOGD(TAG, "Successfully set the mounting height"); + } else { + ESP_LOGD(TAG, "Failed to set the mounting height"); + } + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + + case RESULT_HEIGHT_THRESHOLD: + if (this->current_data_buf_[0]) { + ESP_LOGD(TAG, "Successfully set the height threshold"); + } else { + ESP_LOGD(TAG, "Failed to set the height threshold"); + } + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + + case RESULT_SENSITIVITY: + if (this->current_data_buf_[0]) { + ESP_LOGD(TAG, "Successfully set the sensitivity"); + } else { + ESP_LOGD(TAG, "Failed to set the sensitivity"); + } + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + + case RESULT_PARAMETERS: { + float install_height_float = 0; + float height_threshold_float = 0; + uint32_t current_sensitivity = 0; + if (this->install_height_select_ != nullptr) { + uint32_t current_install_height_int = + encode_uint32(current_data_buf_[3], current_data_buf_[2], current_data_buf_[1], current_data_buf_[0]); + + install_height_float = bit_cast<float>(current_install_height_int); + uint32_t select_index = find_nearest_index(install_height_float, INSTALL_HEIGHT, 7); + this->install_height_select_->publish_state(this->install_height_select_->at(select_index).value()); + } + + if (this->height_threshold_select_ != nullptr) { + uint32_t current_height_threshold_int = + encode_uint32(current_data_buf_[7], current_data_buf_[6], current_data_buf_[5], current_data_buf_[4]); + + height_threshold_float = bit_cast<float>(current_height_threshold_int); + size_t select_index = find_nearest_index(height_threshold_float, HEIGHT_THRESHOLD, 7); + this->height_threshold_select_->publish_state(this->height_threshold_select_->at(select_index).value()); + } + + if (this->sensitivity_select_ != nullptr) { + current_sensitivity = + encode_uint32(current_data_buf_[11], current_data_buf_[10], current_data_buf_[9], current_data_buf_[8]); + + uint32_t select_index = find_nearest_index(current_sensitivity, SENSITIVITY, 3); + this->sensitivity_select_->publish_state(this->sensitivity_select_->at(select_index).value()); + } + + ESP_LOGD(TAG, "Mounting height: %.2f, Height threshold: %.2f, Sensitivity: %" PRIu32, install_height_float, + height_threshold_float, current_sensitivity); + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + } + default: + break; + } +} + +// Send Heartbeat Packet Command +void MR60FDA2Component::set_install_height(uint8_t index) { + uint8_t send_data[13] = {0x01, 0x00, 0x00, 0x00, 0x04, 0x0E, 0x04, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00}; + float_to_bytes(INSTALL_HEIGHT[index], &send_data[8]); + send_data[12] = calculate_checksum(send_data + 8, 4); + this->write_array(send_data, 13); + ESP_LOGV(TAG, "SEND INSTALL HEIGHT FRAME: %s", format_hex_pretty(send_data, 13).c_str()); +} + +void MR60FDA2Component::set_height_threshold(uint8_t index) { + uint8_t send_data[13] = {0x01, 0x00, 0x00, 0x00, 0x04, 0x0E, 0x08, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00}; + float_to_bytes(INSTALL_HEIGHT[index], &send_data[8]); + send_data[12] = calculate_checksum(send_data + 8, 4); + this->write_array(send_data, 13); + ESP_LOGV(TAG, "SEND HEIGHT THRESHOLD: %s", format_hex_pretty(send_data, 13).c_str()); +} + +void MR60FDA2Component::set_sensitivity(uint8_t index) { + uint8_t send_data[13] = {0x01, 0x00, 0x00, 0x00, 0x04, 0x0E, 0x0A, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00}; + + int_to_bytes(SENSITIVITY[index], &send_data[8]); + + send_data[12] = calculate_checksum(send_data + 8, 4); + this->write_array(send_data, 13); + ESP_LOGV(TAG, "SEND SET SENSITIVITY: %s", format_hex_pretty(send_data, 13).c_str()); +} + +void MR60FDA2Component::get_radar_parameters() { + uint8_t send_data[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x06, 0xF6}; + this->write_array(send_data, 8); + ESP_LOGV(TAG, "SEND GET PARAMETERS: %s", format_hex_pretty(send_data, 8).c_str()); +} + +void MR60FDA2Component::factory_reset() { + uint8_t send_data[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x21, 0x10, 0xCF}; + this->write_array(send_data, 8); + ESP_LOGV(TAG, "SEND RESET: %s", format_hex_pretty(send_data, 8).c_str()); + this->get_radar_parameters(); +} + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.h b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.h new file mode 100644 index 0000000000..e1ffa4f071 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.h @@ -0,0 +1,101 @@ +#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_BUTTON +#include "esphome/components/button/button.h" +#endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" + +#include <map> + +namespace esphome { +namespace seeed_mr60fda2 { + +static const uint8_t DATA_BUF_MAX_SIZE = 28; +static const uint8_t FRAME_BUF_MAX_SIZE = 37; +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 IS_FALL_TYPE_BUFFER = 0x0E02; +static const uint16_t PEOPLE_EXIST_TYPE_BUFFER = 0x0F09; +static const uint16_t RESULT_INSTALL_HEIGHT = 0x0E04; +static const uint16_t RESULT_PARAMETERS = 0x0E06; +static const uint16_t RESULT_HEIGHT_THRESHOLD = 0x0E08; +static const uint16_t RESULT_SENSITIVITY = 0x0E0A; + +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 float INSTALL_HEIGHT[7] = {2.4f, 2.5f, 2.6f, 2.7f, 2.8f, 2.9f, 3.0f}; +static const float HEIGHT_THRESHOLD[7] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f}; +static const float SENSITIVITY[3] = {3, 15, 30}; + +static const char *const INSTALL_HEIGHT_STR[7] = {"2.4m", "2.5m", "2.6", "2.7m", "2.8", "2.9m", "3.0m"}; +static const char *const HEIGHT_THRESHOLD_STR[7] = {"0.0m", "0.1m", "0.2m", "0.3m", "0.4m", "0.5m", "0.6m"}; +static const char *const SENSITIVITY_STR[3] = {"1", "2", "3"}; + +class MR60FDA2Component : public Component, + public uart::UARTDevice { // The class name must be the name defined by text_sensor.py +#ifdef USE_BINARY_SENSOR + SUB_BINARY_SENSOR(people_exist) + SUB_BINARY_SENSOR(fall_detected) +#endif +#ifdef USE_BUTTON + SUB_BUTTON(get_radar_parameters) + SUB_BUTTON(factory_reset) +#endif +#ifdef USE_SELECT + SUB_SELECT(install_height) + SUB_SELECT(height_threshold) + SUB_SELECT(sensitivity) +#endif + + protected: + uint8_t current_frame_locate_; + uint8_t current_frame_buf_[FRAME_BUF_MAX_SIZE]; + uint8_t current_data_buf_[DATA_BUF_MAX_SIZE]; + uint16_t current_frame_id_; + size_t current_frame_len_; + size_t current_data_frame_len_; + uint16_t current_frame_type_; + + void split_frame_(uint8_t buffer); + void process_frame_(); + + public: + float get_setup_priority() const override { return esphome::setup_priority::LATE; } + void setup() override; + void dump_config() override; + void loop() override; + void set_install_height(uint8_t index); + void set_height_threshold(uint8_t index); + void set_sensitivity(uint8_t index); + void get_radar_parameters(); + void factory_reset(); +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/__init__.py b/esphome/components/seeed_mr60fda2/select/__init__.py new file mode 100644 index 0000000000..a6f9eeb920 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/__init__.py @@ -0,0 +1,59 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.const import CONF_SENSITIVITY, ENTITY_CATEGORY_CONFIG, ICON_ACCELERATION_Z + +from .. import CONF_MR60FDA2_ID, MR60FDA2Component, mr60fda2_ns + + +DEPENDENCIES = ["seeed_mr60fda2"] + +InstallHeightSelect = mr60fda2_ns.class_("InstallHeightSelect", select.Select) +HeightThresholdSelect = mr60fda2_ns.class_("HeightThresholdSelect", select.Select) +SensitivitySelect = mr60fda2_ns.class_("SensitivitySelect", select.Select) + +CONF_INSTALL_HEIGHT = "install_height" +CONF_HEIGHT_THRESHOLD = "height_threshold" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR60FDA2_ID): cv.use_id(MR60FDA2Component), + cv.Optional(CONF_INSTALL_HEIGHT): select.select_schema( + InstallHeightSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_ACCELERATION_Z, + ), + cv.Optional(CONF_HEIGHT_THRESHOLD): select.select_schema( + HeightThresholdSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_ACCELERATION_Z, + ), + cv.Optional(CONF_SENSITIVITY): select.select_schema( + SensitivitySelect, + entity_category=ENTITY_CATEGORY_CONFIG, + ), +} + + +async def to_code(config): + mr60fda2_component = await cg.get_variable(config[CONF_MR60FDA2_ID]) + if install_height_config := config.get(CONF_INSTALL_HEIGHT): + s = await select.new_select( + install_height_config, + options=["2.4m", "2.5m", "2.6m", "2.7m", "2.8m", "2.9m", "3.0m"], + ) + await cg.register_parented(s, config[CONF_MR60FDA2_ID]) + cg.add(mr60fda2_component.set_install_height_select(s)) + if height_threshold_config := config.get(CONF_HEIGHT_THRESHOLD): + s = await select.new_select( + height_threshold_config, + options=["0.0m", "0.1m", "0.2m", "0.3m", "0.4m", "0.5m", "0.6m"], + ) + await cg.register_parented(s, config[CONF_MR60FDA2_ID]) + cg.add(mr60fda2_component.set_height_threshold_select(s)) + if sensitivity_config := config.get(CONF_SENSITIVITY): + s = await select.new_select( + sensitivity_config, + options=["1", "2", "3"], + ) + await cg.register_parented(s, config[CONF_MR60FDA2_ID]) + cg.add(mr60fda2_component.set_sensitivity_select(s)) diff --git a/esphome/components/seeed_mr60fda2/select/height_threshold_select.cpp b/esphome/components/seeed_mr60fda2/select/height_threshold_select.cpp new file mode 100644 index 0000000000..037f8c6036 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/height_threshold_select.cpp @@ -0,0 +1,15 @@ +#include "height_threshold_select.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +void HeightThresholdSelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_height_threshold(index.value()); + } +} + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/height_threshold_select.h b/esphome/components/seeed_mr60fda2/select/height_threshold_select.h new file mode 100644 index 0000000000..b856dbc89a --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/height_threshold_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr60fda2.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +class HeightThresholdSelect : public select::Select, public Parented<MR60FDA2Component> { + public: + HeightThresholdSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/install_height_select.cpp b/esphome/components/seeed_mr60fda2/select/install_height_select.cpp new file mode 100644 index 0000000000..e791911613 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/install_height_select.cpp @@ -0,0 +1,15 @@ +#include "install_height_select.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +void InstallHeightSelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_install_height(index.value()); + } +} + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/install_height_select.h b/esphome/components/seeed_mr60fda2/select/install_height_select.h new file mode 100644 index 0000000000..7430da3493 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/install_height_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr60fda2.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +class InstallHeightSelect : public select::Select, public Parented<MR60FDA2Component> { + public: + InstallHeightSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/sensitivity_select.cpp b/esphome/components/seeed_mr60fda2/select/sensitivity_select.cpp new file mode 100644 index 0000000000..e2507fb7cc --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/sensitivity_select.cpp @@ -0,0 +1,15 @@ +#include "sensitivity_select.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +void SensitivitySelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_sensitivity(index.value()); + } +} + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/sensitivity_select.h b/esphome/components/seeed_mr60fda2/select/sensitivity_select.h new file mode 100644 index 0000000000..d1accc1b5b --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/sensitivity_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr60fda2.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +class SensitivitySelect : public select::Select, public Parented<MR60FDA2Component> { + public: + SensitivitySelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/tests/components/seeed_mr60fda2/common.yaml b/tests/components/seeed_mr60fda2/common.yaml new file mode 100644 index 0000000000..55a7cc1ab3 --- /dev/null +++ b/tests/components/seeed_mr60fda2/common.yaml @@ -0,0 +1,34 @@ +uart: + - id: seeed_mr60fda2_uart + tx_pin: ${uart_tx_pin} + rx_pin: ${uart_rx_pin} + baud_rate: 115200 + parity: NONE + stop_bits: 1 + +seeed_mr60fda2: + id: my_seeed_mr60fda2 + uart_id: seeed_mr60fda2_uart + +binary_sensor: + - platform: seeed_mr60fda2 + people_exist: + name: "Person Information" + fall_detected: + name: "Falling Information" + +button: + - platform: seeed_mr60fda2 + get_radar_parameters: + name: "Get Radar Parameters" + factory_reset: + name: "Reset" + +select: + - platform: seeed_mr60fda2 + install_height: + name: "Set Install Height" + height_threshold: + name: "Set Height Threshold" + sensitivity: + name: "Set Sensitivity" diff --git a/tests/components/seeed_mr60fda2/test.esp32-c3-ard.yaml b/tests/components/seeed_mr60fda2/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..4fb884abf4 --- /dev/null +++ b/tests/components/seeed_mr60fda2/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml b/tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4fb884abf4 --- /dev/null +++ b/tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml From ae6736311a47ddc5c63dd36b0d2cb4af29f4dd14 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 25 Nov 2024 22:29:36 -0600 Subject: [PATCH 179/282] [lvgl] clang-tidy fixes for #7822 (#7843) --- esphome/components/lvgl/lvgl_esphome.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 41346bc732..61bdfe9755 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -279,7 +279,7 @@ std::string LvSelectable::get_selected_text() { static std::string join_string(std::vector<std::string> options) { return std::accumulate( options.begin(), options.end(), std::string(), - [](const std::string &a, const std::string &b) -> std::string { return a + (a.length() > 0 ? "\n" : "") + b; }); + [](const std::string &a, const std::string &b) -> std::string { return a + (!a.empty() ? "\n" : "") + b; }); } void LvSelectable::set_selected_text(const std::string &text, lv_anim_enable_t anim) { From 72df3d1606aaaf650d49c342439bb2294d9ca76c Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 03:37:20 -0600 Subject: [PATCH 180/282] [xiaomi_ble] clang-tidy fixes for #7822 (#7860) --- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 85434341cc..04e0724ba7 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -249,7 +249,7 @@ optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::Service } bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, const uint64_t &address) { - if (!((raw.size() == 19) || ((raw.size() >= 22) && (raw.size() <= 24)))) { + if ((raw.size() != 19) && ((raw.size() < 22) || (raw.size() > 24))) { ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size()); ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); return false; From 11076e4614075109ce8cc51d4aae70559fc2394b Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 03:47:24 -0600 Subject: [PATCH 181/282] [wireguard] clang-tidy fixes for #7822 (#7859) --- esphome/components/wireguard/wireguard.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp index 7b4011cb79..431bf52227 100644 --- a/esphome/components/wireguard/wireguard.cpp +++ b/esphome/components/wireguard/wireguard.cpp @@ -37,7 +37,7 @@ void Wireguard::setup() { this->wg_config_.netmask = this->netmask_.c_str(); this->wg_config_.persistent_keepalive = this->keepalive_; - if (this->preshared_key_.length() > 0) + if (!this->preshared_key_.empty()) this->wg_config_.preshared_key = this->preshared_key_.c_str(); this->publish_enabled_state(); @@ -137,7 +137,7 @@ void Wireguard::dump_config() { ESP_LOGCONFIG(TAG, " Peer Port: " LOG_SECRET("%d"), this->peer_port_); ESP_LOGCONFIG(TAG, " Peer Public Key: " LOG_SECRET("%s"), this->peer_public_key_.c_str()); ESP_LOGCONFIG(TAG, " Peer Pre-shared Key: " LOG_SECRET("%s"), - (this->preshared_key_.length() > 0 ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); + (!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); ESP_LOGCONFIG(TAG, " Peer Allowed IPs:"); for (auto &allowed_ip : this->allowed_ips_) { ESP_LOGCONFIG(TAG, " - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str()); From 841d278224d7a82ff8112cb3ee80dbe31347d84b Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 03:47:57 -0600 Subject: [PATCH 182/282] [dsmr] clang-tidy fixes for #7822 (#7848) --- esphome/components/dsmr/dsmr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 193ea1d4e5..c0a2883d79 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -296,7 +296,7 @@ void Dsmr::dump_config() { } void Dsmr::set_decryption_key(const std::string &decryption_key) { - if (decryption_key.length() == 0) { + if (decryption_key.empty()) { ESP_LOGI(TAG, "Disabling decryption"); this->decryption_key_.clear(); if (this->crypt_telegram_ != nullptr) { From 6e50e2aa6534c31dbc9e425781c76581178ea68c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 26 Nov 2024 22:50:16 +1300 Subject: [PATCH 183/282] Fix entity name validation to allow "Off" and "On" (#7821) --- esphome/config_validation.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 98b81ec328..ebfb2631c3 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1839,8 +1839,6 @@ def validate_registry_entry(name, registry): def none(value): if value in ("none", "None"): return None - if boolean(value) is False: - return None raise Invalid("Must be none") @@ -1912,17 +1910,23 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( } ) + +def _validate_entity_name(value): + value = string(value) + try: + value = none(value) # pylint: disable=assignment-from-none + except Invalid: + pass + else: + requires_friendly_name( + "Name cannot be None when esphome->friendly_name is not set!" + )(value) + return value + + ENTITY_BASE_SCHEMA = Schema( { - Optional(CONF_NAME): Any( - All( - none, - requires_friendly_name( - "Name cannot be None when esphome->friendly_name is not set!" - ), - ), - string, - ), + Optional(CONF_NAME): _validate_entity_name, Optional(CONF_INTERNAL): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, Optional(CONF_ICON): icon, From 2eac8b6c4643494640aa6889025ba5791c09dc36 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 03:50:33 -0600 Subject: [PATCH 184/282] [camera_web_server] Use header instead of mock struct (#7823) --- esphome/components/esp32_camera_web_server/camera_web_server.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.h b/esphome/components/esp32_camera_web_server/camera_web_server.h index f65625554c..3ba8f31dd7 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.h +++ b/esphome/components/esp32_camera_web_server/camera_web_server.h @@ -11,7 +11,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -struct httpd_req; +struct httpd_req; // NOLINT(readability-identifier-naming) namespace esphome { namespace esp32_camera_web_server { From 1c2d2bce5af36527abe758d88f68289b7bccd482 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 03:52:26 -0600 Subject: [PATCH 185/282] [display_menu_base] clang-tidy fixes for #7822 (#7847) --- esphome/components/display_menu_base/display_menu_base.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/display_menu_base/display_menu_base.cpp b/esphome/components/display_menu_base/display_menu_base.cpp index 5502623607..2d8e6ae5fc 100644 --- a/esphome/components/display_menu_base/display_menu_base.cpp +++ b/esphome/components/display_menu_base/display_menu_base.cpp @@ -280,7 +280,7 @@ bool DisplayMenuComponent::cursor_down_() { bool DisplayMenuComponent::enter_menu_() { this->displayed_item_->on_leave(); this->displayed_item_ = static_cast<MenuItemMenu *>(this->get_selected_item_()); - this->selection_stack_.push_front({this->top_index_, this->cursor_index_}); + this->selection_stack_.emplace_front(this->top_index_, this->cursor_index_); this->cursor_index_ = this->top_index_ = 0; this->displayed_item_->on_enter(); From 536bcab5def03c8177cdfc31c6deb3aa01d8ccf3 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 03:52:57 -0600 Subject: [PATCH 186/282] [nextion] clang-tidy fixes for #7822 (#7852) --- esphome/components/nextion/nextion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 984db09c57..7c41f8dfe2 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -343,7 +343,7 @@ void Nextion::process_serial_() { } // nextion.tech/instruction-set/ void Nextion::process_nextion_commands_() { - if (this->command_data_.length() == 0) { + if (this->command_data_.empty()) { return; } From 2d4688a2061e5734ad136f717ba77a7dc7a9fec7 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 03:53:23 -0600 Subject: [PATCH 187/282] [shelly_dimmer] clang-tidy fixes for #7822 (#7844) --- esphome/components/shelly_dimmer/shelly_dimmer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.cpp b/esphome/components/shelly_dimmer/shelly_dimmer.cpp index b415840bdc..d4229b2384 100644 --- a/esphome/components/shelly_dimmer/shelly_dimmer.cpp +++ b/esphome/components/shelly_dimmer/shelly_dimmer.cpp @@ -466,7 +466,7 @@ bool ShellyDimmer::handle_frame_() { } case SHELLY_DIMMER_PROTO_CMD_SWITCH: case SHELLY_DIMMER_PROTO_CMD_SETTINGS: { - return !(payload_len < 1 || payload[0] != 0x01); + return payload_len >= 1 && payload[0] == 0x01; } default: { return false; From e6bd2238ce1fceef7db2cadbae3736a5759e16b2 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 03:54:16 -0600 Subject: [PATCH 188/282] [sim800l] clang-tidy fixes for #7822 (#7856) --- esphome/components/sim800l/sim800l.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index 4f7aa228e9..38775b0062 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -324,7 +324,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->sms_received_callback_.call(this->message_, this->sender_); this->state_ = STATE_RECEIVED_SMS; } else { - if (this->message_.length() > 0) + if (!this->message_.empty()) this->message_ += "\n"; this->message_ += message; } From 6b59f55a5097f40c3db18ded853cc27888bd2885 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 03:58:18 -0600 Subject: [PATCH 189/282] [nfc, pn532, pn7150, pn7160] clang-tidy fixes for #7822 (#7853) --- esphome/components/nfc/ndef_record.cpp | 6 +++--- esphome/components/pn532/pn532_mifare_ultralight.cpp | 4 ++-- esphome/components/pn7150/pn7150_mifare_ultralight.cpp | 4 ++-- esphome/components/pn7160/pn7160_mifare_ultralight.cpp | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/esphome/components/nfc/ndef_record.cpp b/esphome/components/nfc/ndef_record.cpp index 8eb0c3b901..540ba62940 100644 --- a/esphome/components/nfc/ndef_record.cpp +++ b/esphome/components/nfc/ndef_record.cpp @@ -30,13 +30,13 @@ std::vector<uint8_t> NdefRecord::encode(bool first, bool last) { data.push_back(payload_length & 0xFF); } - if (this->id_.length()) { + if (!this->id_.empty()) { data.push_back(this->id_.length()); } data.insert(data.end(), this->type_.begin(), this->type_.end()); - if (this->id_.length()) { + if (!this->id_.empty()) { data.insert(data.end(), this->id_.begin(), this->id_.end()); } @@ -55,7 +55,7 @@ uint8_t NdefRecord::create_flag_byte(bool first, bool last, size_t payload_size) if (payload_size <= 255) { value = value | 0x10; // Set SR bit } - if (this->id_.length()) { + if (!this->id_.empty()) { value = value | 0x08; // Set IL bit } return value; diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index b08a7336c7..f823829a6c 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -80,8 +80,8 @@ bool PN532::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_t const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector return (page_3_to_6.size() > p4_offset + 3) && - !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) && - (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF)); + ((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) || + (page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF)); } uint16_t PN532::read_mifare_ultralight_capacity_() { diff --git a/esphome/components/pn7150/pn7150_mifare_ultralight.cpp b/esphome/components/pn7150/pn7150_mifare_ultralight.cpp index 791b0634d6..b107f6f79e 100644 --- a/esphome/components/pn7150/pn7150_mifare_ultralight.cpp +++ b/esphome/components/pn7150/pn7150_mifare_ultralight.cpp @@ -81,8 +81,8 @@ bool PN7150::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_ const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector return (page_3_to_6.size() > p4_offset + 3) && - !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) && - (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF)); + ((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) || + (page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF)); } uint16_t PN7150::read_mifare_ultralight_capacity_() { diff --git a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp index a74f23d4f2..65daac494f 100644 --- a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp +++ b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp @@ -81,8 +81,8 @@ bool PN7160::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_ const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector return (page_3_to_6.size() > p4_offset + 3) && - !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) && - (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF)); + ((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) || + (page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF)); } uint16_t PN7160::read_mifare_ultralight_capacity_() { From 31c13e4c16f18e3ae241b223a529ee5ed2a08fd8 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 03:59:29 -0600 Subject: [PATCH 190/282] [output] clang-tidy fixes for #7822 (#7854) --- esphome/components/output/float_output.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/output/float_output.cpp b/esphome/components/output/float_output.cpp index f120f86f1f..e7dba1d81d 100644 --- a/esphome/components/output/float_output.cpp +++ b/esphome/components/output/float_output.cpp @@ -32,7 +32,7 @@ void FloatOutput::set_level(float state) { } #endif - if (!(state == 0.0f && this->zero_means_zero_)) // regardless of min_power_, 0.0 means off + if (state != 0.0f || !this->zero_means_zero_) // regardless of min_power_, 0.0 means off state = (state * (this->max_power_ - this->min_power_)) + this->min_power_; if (this->is_inverted()) From bdc6302ea1b17319d2529d9d5e277ba416e18687 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 04:00:03 -0600 Subject: [PATCH 191/282] [sun_gtil2] clang-tidy fixes for #7822 (#7858) --- esphome/components/sun_gtil2/sun_gtil2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sun_gtil2/sun_gtil2.cpp b/esphome/components/sun_gtil2/sun_gtil2.cpp index 1653f937dd..46b4902654 100644 --- a/esphome/components/sun_gtil2/sun_gtil2.cpp +++ b/esphome/components/sun_gtil2/sun_gtil2.cpp @@ -83,7 +83,7 @@ void SunGTIL2::handle_char_(uint8_t c) { memcpy(&msg, this->rx_message_.data(), MESSAGE_SIZE); this->rx_message_.clear(); - if (!((msg.end[0] == 0) && (msg.end[38] == 0x08))) + if ((msg.end[0] != 0) || (msg.end[38] != 0x08)) return; ESP_LOGVV(TAG, "Frequency raw value: %02x", msg.frequency); From 4c383906c490192a692cc74da82fca5d81fe17fc Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 04:00:40 -0600 Subject: [PATCH 192/282] [pipsolar] clang-tidy fixes for #7822 (#7855) --- esphome/components/pipsolar/pipsolar.cpp | 4 ++-- esphome/components/pipsolar/switch/pipsolar_switch.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index c4bc018b75..03c699e7d4 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -790,7 +790,7 @@ uint8_t Pipsolar::check_incoming_crc_() { // send next command used uint8_t Pipsolar::send_next_command_() { uint16_t crc16; - if (this->command_queue_[this->command_queue_position_].length() != 0) { + if (!this->command_queue_[this->command_queue_position_].empty()) { const char *command = this->command_queue_[this->command_queue_position_].c_str(); uint8_t byte_command[16]; uint8_t length = this->command_queue_[this->command_queue_position_].length(); @@ -846,7 +846,7 @@ void Pipsolar::queue_command_(const char *command, uint8_t length) { uint8_t next_position = command_queue_position_; for (uint8_t i = 0; i < COMMAND_QUEUE_LENGTH; i++) { uint8_t testposition = (next_position + i) % COMMAND_QUEUE_LENGTH; - if (command_queue_[testposition].length() == 0) { + if (command_queue_[testposition].empty()) { command_queue_[testposition] = command; ESP_LOGD(TAG, "Command queued successfully: %s with length %u at position %d", command, command_queue_[testposition].length(), testposition); diff --git a/esphome/components/pipsolar/switch/pipsolar_switch.cpp b/esphome/components/pipsolar/switch/pipsolar_switch.cpp index 7eaeac1c2d..be7763226b 100644 --- a/esphome/components/pipsolar/switch/pipsolar_switch.cpp +++ b/esphome/components/pipsolar/switch/pipsolar_switch.cpp @@ -10,11 +10,11 @@ static const char *const TAG = "pipsolar.switch"; void PipsolarSwitch::dump_config() { LOG_SWITCH("", "Pipsolar Switch", this); } void PipsolarSwitch::write_state(bool state) { if (state) { - if (this->on_command_.length() > 0) { + if (!this->on_command_.empty()) { this->parent_->switch_command(this->on_command_); } } else { - if (this->off_command_.length() > 0) { + if (!this->off_command_.empty()) { this->parent_->switch_command(this->off_command_); } } From 2fa8d907b3506a0db2dc4ed28940bb7d7e30ca4f Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 04:01:34 -0600 Subject: [PATCH 193/282] [ltr501] clang-tidy fixes for #7822 (#7850) --- esphome/components/ltr501/ltr501.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ltr501/ltr501.cpp b/esphome/components/ltr501/ltr501.cpp index 4f4e26f44f..b30e520f2b 100644 --- a/esphome/components/ltr501/ltr501.cpp +++ b/esphome/components/ltr501/ltr501.cpp @@ -23,7 +23,7 @@ bool operator==(const GainTimePair &lhs, const GainTimePair &rhs) { } bool operator!=(const GainTimePair &lhs, const GainTimePair &rhs) { - return !(lhs.gain == rhs.gain && lhs.time == rhs.time); + return lhs.gain != rhs.gain || lhs.time != rhs.time; } template<typename T, size_t size> T get_next(const T (&array)[size], const T val) { From cd1ee9660644177c99302d0698cefed6a9802361 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 04:04:50 -0600 Subject: [PATCH 194/282] [cse7766] clang-tidy fixes for #7822 (#7846) --- esphome/components/cse7766/cse7766.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 48240464b3..3907c195d0 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -43,7 +43,7 @@ bool CSE7766Component::check_byte_() { uint8_t index = this->raw_data_index_; uint8_t byte = this->raw_data_[index]; if (index == 0) { - return !((byte != 0x55) && ((byte & 0xF0) != 0xF0) && (byte != 0xAA)); + return (byte == 0x55) || ((byte & 0xF0) == 0xF0) || (byte == 0xAA); } if (index == 1) { From be7882727448093efd3043364607ad925058606b Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Tue, 26 Nov 2024 00:05:20 -1000 Subject: [PATCH 195/282] [honeywell] use warning instead of failing (#7862) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- .../components/honeywellabp2_i2c/honeywellabp2.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp b/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp index e2910032cc..d111723669 100644 --- a/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp +++ b/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp @@ -15,7 +15,7 @@ static const char *const TAG = "honeywellabp2"; void HONEYWELLABP2Sensor::read_sensor_data() { if (this->read(raw_data_, 7) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Communication with ABP2 failed!"); - this->mark_failed(); + this->status_set_warning("couldn't read sensor data"); return; } float press_counts = encode_uint24(raw_data_[1], raw_data_[2], raw_data_[3]); // calculate digital pressure counts @@ -25,12 +25,13 @@ void HONEYWELLABP2Sensor::read_sensor_data() { (this->max_pressure_ - this->min_pressure_)) + this->min_pressure_; this->last_temperature_ = (temp_counts * 200 / 16777215) - 50; + this->status_clear_warning(); } void HONEYWELLABP2Sensor::start_measurement() { if (this->write(i2c_cmd_, 3) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Communication with ABP2 failed!"); - this->mark_failed(); + this->status_set_warning("couldn't start measurement"); return; } this->measurement_running_ = true; @@ -39,7 +40,7 @@ void HONEYWELLABP2Sensor::start_measurement() { bool HONEYWELLABP2Sensor::is_measurement_ready() { if (this->read(raw_data_, 1) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Communication with ABP2 failed!"); - this->mark_failed(); + this->status_set_warning("couldn't check measurement"); return false; } if ((raw_data_[0] & (0x1 << STATUS_BIT_BUSY)) > 0) { @@ -52,7 +53,7 @@ bool HONEYWELLABP2Sensor::is_measurement_ready() { void HONEYWELLABP2Sensor::measurement_timeout() { ESP_LOGE(TAG, "Timeout!"); this->measurement_running_ = false; - this->mark_failed(); + this->status_set_warning("measurement timed out"); } float HONEYWELLABP2Sensor::get_pressure() { return this->last_pressure_; } @@ -79,7 +80,7 @@ void HONEYWELLABP2Sensor::update() { ESP_LOGV(TAG, "Update Honeywell ABP2 Sensor"); this->start_measurement(); - this->set_timeout("meas_timeout", 50, [this] { this->measurement_timeout(); }); + this->set_timeout("meas_timeout", 100, [this] { this->measurement_timeout(); }); } void HONEYWELLABP2Sensor::dump_config() { From 2b9013699d841a7878a796f5621adfa3461f597d Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 04:05:39 -0600 Subject: [PATCH 196/282] [alarm_control_panel] clang-tidy fixes for #7822 (#7845) --- .../alarm_control_panel/alarm_control_panel_call.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp index b1d2b2a097..7bb9b9989c 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp @@ -72,10 +72,9 @@ void AlarmControlPanelCall::validate_() { this->state_.reset(); return; } - if (state == ACP_STATE_DISARMED && - !(this->parent_->is_state_armed(this->parent_->get_state()) || - this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_ARMING || - this->parent_->get_state() == ACP_STATE_TRIGGERED)) { + if (state == ACP_STATE_DISARMED && !this->parent_->is_state_armed(this->parent_->get_state()) && + this->parent_->get_state() != ACP_STATE_PENDING && this->parent_->get_state() != ACP_STATE_ARMING && + this->parent_->get_state() != ACP_STATE_TRIGGERED) { ESP_LOGW(TAG, "Cannot disarm when not armed"); this->state_.reset(); return; From 3730b0310b23c989c8c27d5701c3d423b6c749d6 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 12:07:36 -0600 Subject: [PATCH 197/282] [sprinkler] clang-tidy fixes for #7822 (#7857) --- esphome/components/sprinkler/sprinkler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 59565251c3..5384d29871 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -419,7 +419,7 @@ void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControll SprinklerValve *new_valve = &this->valve_[new_valve_number]; new_valve->controller_switch = valve_sw; - new_valve->controller_switch->set_state_lambda([=]() -> optional<bool> { + new_valve->controller_switch->set_state_lambda([this, new_valve_number]() -> optional<bool> { if (this->valve_pump_switch(new_valve_number) != nullptr) { return this->valve_switch(new_valve_number)->state() && this->valve_pump_switch(new_valve_number)->state(); } @@ -445,7 +445,7 @@ void Sprinkler::add_controller(Sprinkler *other_controller) { this->other_contro void Sprinkler::set_controller_main_switch(SprinklerControllerSwitch *controller_switch) { this->controller_sw_ = controller_switch; - controller_switch->set_state_lambda([=]() -> optional<bool> { + controller_switch->set_state_lambda([this]() -> optional<bool> { for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) { if (this->valve_[valve_number].controller_switch->state) { return true; From 53691d28a81644b84f82e4441da153b27cbfddd7 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 12:07:42 -0600 Subject: [PATCH 198/282] [haier] clang-tidy fixes for #7822 (#7849) --- esphome/components/haier/hon_climate.cpp | 135 ++++++++++------------- 1 file changed, 60 insertions(+), 75 deletions(-) diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index e7be1fa418..85e9cf37b9 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -1069,19 +1069,17 @@ void HonClimate::fill_control_messages_queue_() { climate_control = this->current_hvac_settings_; // Beeper command { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, - this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, + this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2); } // Health mode { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::HEALTH_MODE, - this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::HEALTH_MODE, + this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2); this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01); } // Climate mode @@ -1099,51 +1097,46 @@ void HonClimate::fill_control_messages_queue_() { case CLIMATE_MODE_HEAT_COOL: new_power = true; buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_MODE, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2); fan_mode_buf[1] = this->other_modes_fan_speed_; break; case CLIMATE_MODE_HEAT: new_power = true; buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_MODE, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2); fan_mode_buf[1] = this->other_modes_fan_speed_; break; case CLIMATE_MODE_DRY: new_power = true; buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_MODE, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2); fan_mode_buf[1] = this->other_modes_fan_speed_; break; case CLIMATE_MODE_FAN_ONLY: new_power = true; buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_MODE, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2); fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode break; case CLIMATE_MODE_COOL: new_power = true; buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_MODE, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2); fan_mode_buf[1] = this->other_modes_fan_speed_; break; default: @@ -1153,11 +1146,10 @@ void HonClimate::fill_control_messages_queue_() { } // Climate power { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_POWER, - new_power ? ONE_BUF : ZERO_BUF, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_POWER, + new_power ? ONE_BUF : ZERO_BUF, 2); } // CLimate preset { @@ -1199,36 +1191,32 @@ void HonClimate::fill_control_messages_queue_() { } auto presets = this->traits_.get_supported_presets(); if (quiet_mode_buf[1] != 0xFF) { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::QUIET_MODE, - quiet_mode_buf, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::QUIET_MODE, + quiet_mode_buf, 2); } if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::FAST_MODE, - fast_mode_buf, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::FAST_MODE, + fast_mode_buf, 2); } if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::TEN_DEGREE, - away_mode_buf, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::TEN_DEGREE, + away_mode_buf, 2); } } // Target temperature if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) { uint8_t buffer[2] = {0x00, 0x00}; buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::SET_POINT, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::SET_POINT, + buffer, 2); } // Vertical swing mode if (climate_control.swing_mode.has_value()) { @@ -1248,16 +1236,14 @@ void HonClimate::fill_control_messages_queue_() { case CLIMATE_SWING_BOTH: break; } - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::HORIZONTAL_SWING_MODE, - horizontal_swing_buf, 2)); - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::VERTICAL_SWING_MODE, - vertical_swing_buf, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::HORIZONTAL_SWING_MODE, + horizontal_swing_buf, 2); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::VERTICAL_SWING_MODE, + vertical_swing_buf, 2); } // Fan mode if (climate_control.fan_mode.has_value()) { @@ -1280,11 +1266,10 @@ void HonClimate::fill_control_messages_queue_() { break; } if (fan_mode_buf[1] != 0xFF) { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::FAN_MODE, - fan_mode_buf, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::FAN_MODE, + fan_mode_buf, 2); } } } From 39f3f795e2b927af404e80286d5af8100fc7c80d Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 12:07:53 -0600 Subject: [PATCH 199/282] [mqtt] clang-tidy fixes for #7822 (#7851) --- esphome/components/mqtt/mqtt_backend_esp32.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index ed500c6d44..2cccb957eb 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -151,11 +151,11 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) { break; case MQTT_EVENT_DATA: { static std::string topic; - if (event.topic.length() > 0) { + if (!event.topic.empty()) { topic = event.topic; } ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str()); - this->on_message_.call(event.topic.length() > 0 ? topic.c_str() : nullptr, event.data.data(), event.data.size(), + this->on_message_.call(!event.topic.empty() ? topic.c_str() : nullptr, event.data.data(), event.data.size(), event.current_data_offset, event.total_data_len); } break; case MQTT_EVENT_ERROR: @@ -184,7 +184,7 @@ void MQTTBackendESP32::mqtt_event_handler(void *handler_args, esp_event_base_t b // queue event to decouple processing if (instance) { auto event = *static_cast<esp_mqtt_event_t *>(event_data); - instance->mqtt_events_.push(Event(event)); + instance->mqtt_events_.emplace(event); } } From e3d673d16c766181c9cda046cca78bc17e0936a6 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 26 Nov 2024 12:08:02 -0600 Subject: [PATCH 200/282] [helpers, optional] clang-tidy fixes for #7822 (#7841) --- esphome/core/helpers.cpp | 2 +- esphome/core/optional.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index befc84516c..103173095b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -293,7 +293,7 @@ std::string str_sanitize(const std::string &str) { std::replace_if( out.begin(), out.end(), [](const char &c) { - return !(c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); + return c != '-' && c != '_' && (c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z'); }, '_'); return out; diff --git a/esphome/core/optional.h b/esphome/core/optional.h index 1e28ef1354..591bc7aa68 100644 --- a/esphome/core/optional.h +++ b/esphome/core/optional.h @@ -59,7 +59,7 @@ template<typename T> class optional { // NOLINT return *this; } - void swap(optional &rhs) { + void swap(optional &rhs) noexcept { using std::swap; if (has_value() && rhs.has_value()) { swap(**this, *rhs); @@ -206,7 +206,7 @@ template<typename T, typename U> inline bool operator>=(U const &v, optional<T> // Specialized algorithms -template<typename T> void swap(optional<T> &x, optional<T> &y) { x.swap(y); } +template<typename T> void swap(optional<T> &x, optional<T> &y) noexcept { x.swap(y); } // Convenience function to create an optional. From 921be1a17c5b8fdd638518e8c3c92f87ed0cca2d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 27 Nov 2024 07:09:16 +1300 Subject: [PATCH 201/282] Move ``USE_CAPTIVE_PORTAL`` into all define groups it can be used with (#7863) --- esphome/core/defines.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 3798ddba6a..eb3b20d007 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -84,7 +84,6 @@ // Arduino-specific feature flags #ifdef USE_ARDUINO -#define USE_CAPTIVE_PORTAL #define USE_PROMETHEUS #define USE_WIFI_WPA2_EAP #endif @@ -97,6 +96,7 @@ // ESP32-specific feature flags #ifdef USE_ESP32 #define USE_BLUETOOTH_PROXY +#define USE_CAPTIVE_PORTAL #define USE_ESP32_BLE #define USE_ESP32_BLE_CLIENT #define USE_ESP32_BLE_SERVER @@ -135,6 +135,7 @@ #ifdef USE_ESP8266 #define USE_ADC_SENSOR_VCC #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 2) +#define USE_CAPTIVE_PORTAL #define USE_ESP8266_PREFERENCES_FLASH #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_SOCKET_IMPL_LWIP_TCP @@ -159,6 +160,7 @@ #endif #ifdef USE_LIBRETINY +#define USE_CAPTIVE_PORTAL #define USE_SOCKET_IMPL_LWIP_SOCKETS #define USE_WEBSERVER #define USE_WEBSERVER_PORT 80 // NOLINT From 3a8b41daa3297b77175a2303be3036fb93c16954 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2024 21:06:56 +0100 Subject: [PATCH 202/282] Bump docker/build-push-action from 6.9.0 to 6.10.0 in /.github/actions/build-image (#7866) 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 5c686605c3..cc9894a657 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.9.0 + uses: docker/build-push-action@v6.10.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.9.0 + uses: docker/build-push-action@v6.10.0 env: DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_RECORD_UPLOAD: false From a3ef2ed7fd109fc6885de3dee7245d637d5ae2a9 Mon Sep 17 00:00:00 2001 From: tomaszduda23 <tomaszduda23@gmail.com> Date: Tue, 26 Nov 2024 21:56:43 +0100 Subject: [PATCH 203/282] python lint for platform components (#7864) --- esphome/components/bk72xx/__init__.py | 2 +- esphome/components/esp8266/__init__.py | 12 +++++------- esphome/components/rp2040/__init__.py | 2 +- esphome/components/rtl87xx/__init__.py | 2 +- script/build_language_schema.py | 16 +++++++--------- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/esphome/components/bk72xx/__init__.py b/esphome/components/bk72xx/__init__.py index 6737631ac7..b5122de956 100644 --- a/esphome/components/bk72xx/__init__.py +++ b/esphome/components/bk72xx/__init__.py @@ -15,7 +15,7 @@ from esphome.components.libretiny.const import ( ) from esphome.core import CORE -from .boards import BK72XX_BOARDS, BK72XX_BOARD_PINS +from .boards import BK72XX_BOARD_PINS, BK72XX_BOARDS CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["libretiny"] diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 64b127bda3..c73027fe1b 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -1,10 +1,13 @@ import logging import os +import esphome.codegen as cg +import esphome.config_validation as cv from esphome.const import ( CONF_BOARD, CONF_BOARD_FLASH_MODE, CONF_FRAMEWORK, + CONF_PLATFORM_VERSION, CONF_SOURCE, CONF_VERSION, KEY_CORE, @@ -12,27 +15,22 @@ from esphome.const import ( KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, PLATFORM_ESP8266, - CONF_PLATFORM_VERSION, ) from esphome.core import CORE, coroutine_with_priority -import esphome.config_validation as cv -import esphome.codegen as cg from esphome.helpers import copy_file_if_changed +from .boards import BOARDS, ESP8266_LD_SCRIPTS from .const import ( - CONF_RESTORE_FROM_FLASH, CONF_EARLY_PIN_INIT, + CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, KEY_FLASH_SIZE, KEY_PIN_INITIAL_STATES, esp8266_ns, ) -from .boards import BOARDS, ESP8266_LD_SCRIPTS - from .gpio import PinInitialState, add_pin_initial_states_array - CODEOWNERS = ["@esphome/core"] _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["preferences"] diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index d612631a4c..b04e539182 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -17,7 +17,7 @@ from esphome.const import ( PLATFORM_RP2040, ) from esphome.core import CORE, EsphomeError, coroutine_with_priority -from esphome.helpers import copy_file_if_changed, mkdir_p, write_file, read_file +from esphome.helpers import copy_file_if_changed, mkdir_p, read_file, write_file from .const import KEY_BOARD, KEY_PIO_FILES, KEY_RP2040, rp2040_ns diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py index 9060a7c4a6..4c1956f0f4 100644 --- a/esphome/components/rtl87xx/__init__.py +++ b/esphome/components/rtl87xx/__init__.py @@ -15,7 +15,7 @@ from esphome.components.libretiny.const import ( ) from esphome.core import CORE -from .boards import RTL87XX_BOARDS, RTL87XX_BOARD_PINS +from .boards import RTL87XX_BOARD_PINS, RTL87XX_BOARDS CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["libretiny"] diff --git a/script/build_language_schema.py b/script/build_language_schema.py index 8b2c28b06b..2023dc0402 100644 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -394,9 +394,8 @@ def add_referenced_recursive(referenced_schemas, config_var, path, eat_schema=Fa for k in schema.get(S_EXTENDS, []): if k not in referenced_schemas: referenced_schemas[k] = [path] - else: - if path not in referenced_schemas[k]: - referenced_schemas[k].append(path) + elif path not in referenced_schemas[k]: + referenced_schemas[k].append(path) s1 = get_str_path_schema(k) p = k.split(".") @@ -868,13 +867,12 @@ def convert(schema, config_var, path): config_var[S_TYPE] = "use_id" else: print("TODO deferred?") + elif isinstance(data, str): + # TODO: Figure out why pipsolar does this + config_var["use_id_type"] = data else: - if isinstance(data, str): - # TODO: Figure out why pipsolar does this - config_var["use_id_type"] = data - else: - config_var["use_id_type"] = str(data.base) - config_var[S_TYPE] = "use_id" + config_var["use_id_type"] = str(data.base) + config_var[S_TYPE] = "use_id" else: raise TypeError("Unknown extracted schema type") elif config_var.get("key") == "GeneratedID": From 4a97064b2cd2412414b2d57ec1b7e151ffcf92c2 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 25 Nov 2024 07:25:16 +1100 Subject: [PATCH 204/282] [lvgl] Bugfixes (#7803) --- esphome/components/lvgl/__init__.py | 2 +- esphome/components/lvgl/lv_validation.py | 3 ++- esphome/components/lvgl/schemas.py | 1 + esphome/components/lvgl/widgets/line.py | 5 ++++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index d03adc9624..8fdd03f647 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -322,8 +322,8 @@ async def to_code(configs): await encoders_to_code(lv_component, config, default_group) await keypads_to_code(lv_component, config, default_group) await theme_to_code(config) - await styles_to_code(config) await gradients_to_code(config) + await styles_to_code(config) await set_obj_properties(lv_scr_act, config) await add_widgets(lv_scr_act, config) await add_pages(lv_component, config) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index b91b0905df..766c010244 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -30,7 +30,7 @@ from .defines import ( call_lambda, literal, ) -from .helpers import esphome_fonts_used, lv_fonts_used, requires_component +from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component from .types import lv_font_t, lv_gradient_t, lv_img_t opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") @@ -326,6 +326,7 @@ def image_validator(value): value = requires_component("image")(value) value = cv.use_id(Image_)(value) lv_images_used.add(value) + add_lv_use("img", "label") return value diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 516627708e..3f56b3345f 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -341,6 +341,7 @@ FLEX_OBJ_SCHEMA = { cv.Optional(df.CONF_FLEX_GROW): cv.int_, } + DISP_BG_SCHEMA = cv.Schema( { cv.Optional(df.CONF_DISP_BG_IMAGE): lv_image, diff --git a/esphome/components/lvgl/widgets/line.py b/esphome/components/lvgl/widgets/line.py index 4c6439fde4..548dfa8452 100644 --- a/esphome/components/lvgl/widgets/line.py +++ b/esphome/components/lvgl/widgets/line.py @@ -39,7 +39,10 @@ LINE_SCHEMA = { class LineType(WidgetType): def __init__(self): super().__init__( - CONF_LINE, LvType("lv_line_t"), (CONF_MAIN,), LINE_SCHEMA, modify_schema={} + CONF_LINE, + LvType("lv_line_t"), + (CONF_MAIN,), + LINE_SCHEMA, ) async def to_code(self, w: Widget, config): From a4a71797d9790fe059210b79a35a95839ff2bef8 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 25 Nov 2024 07:25:51 +1100 Subject: [PATCH 205/282] [docker] Leave run-time required libraries installed. (#7804) --- docker/Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index ed6ce083a8..c2902a9dd1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -99,15 +99,17 @@ BUILD_DEPS=" libfreetype-dev=2.12.1+dfsg-5+deb12u3 libssl-dev=3.0.15-1~deb12u1 libffi-dev=3.4.4-1 - libopenjp2-7=2.5.0-2 - libtiff6=4.5.0-6+deb12u1 cargo=0.66.0+ds1-1 pkg-config=1.8.1-1 " +LIB_DEPS=" + libtiff6=4.5.0-6+deb12u1 + libopenjp2-7=2.5.0-2 +" if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ] || [ "$TARGETARCH$TARGETVARIANT" = "armv7" ] then apt-get update - apt-get install -y --no-install-recommends $BUILD_DEPS + apt-get install -y --no-install-recommends $BUILD_DEPS $LIB_DEPS fi CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo From 80fedbc1a556e121ca166519aa3aae7e44c05a1b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 25 Nov 2024 07:27:09 +1100 Subject: [PATCH 206/282] [qspi_dbi] Fix init sequences (Bugfix) (#7805) --- esphome/components/qspi_dbi/models.py | 2 ++ esphome/components/qspi_dbi/qspi_dbi.cpp | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/qspi_dbi/models.py b/esphome/components/qspi_dbi/models.py index cbd9c4663f..c1fe434853 100644 --- a/esphome/components/qspi_dbi/models.py +++ b/esphome/components/qspi_dbi/models.py @@ -1,6 +1,7 @@ # Commands SW_RESET_CMD = 0x01 SLEEP_OUT = 0x11 +NORON = 0x13 INVERT_OFF = 0x20 INVERT_ON = 0x21 ALL_ON = 0x23 @@ -56,6 +57,7 @@ chip.cmd(0xC2, 0x00) chip.delay(10) chip.cmd(TEON, 0x00) chip.cmd(PIXFMT, 0x55) +chip.cmd(NORON) chip = DriverChip("AXS15231") chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index a649a25ea6..785885d4ec 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -111,7 +111,6 @@ void QspiDbi::reset_params_(bool ready) { mad |= MADCTL_MY; this->write_command_(MADCTL_CMD, mad); this->write_command_(BRIGHTNESS, this->brightness_); - this->write_command_(NORON); this->write_command_(DISPLAY_ON); } From e9851e7eb227ceebb4925b59e03c22c402b29d06 Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Sun, 24 Nov 2024 10:42:46 -1000 Subject: [PATCH 207/282] fix modbus crashing when bad data returned (#7810) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- esphome/components/modbus/modbus.cpp | 3 +- .../modbus_controller/modbus_controller.cpp | 72 ++++++++++++++----- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 8544b50261..47deea83e6 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -38,8 +38,9 @@ void Modbus::loop() { // stop blocking new send commands after sent_wait_time_ ms after response received if (now - this->last_send_ > send_wait_time_) { - if (waiting_for_response > 0) + if (waiting_for_response > 0) { ESP_LOGV(TAG, "Stop waiting for response from %d", waiting_for_response); + } waiting_for_response = 0; } } diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index e1102516ca..f8b72af817 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -622,51 +622,87 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens uint32_t bitmask) { int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits + size_t size = data.size() - offset; + bool error = false; switch (sensor_value_type) { case SensorValueType::U_WORD: - value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ; + if (size >= 2) { + value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ; + } else { + error = true; + } break; case SensorValueType::U_DWORD: case SensorValueType::FP32: - value = get_data<uint32_t>(data, offset); - value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + if (size >= 4) { + value = get_data<uint32_t>(data, offset); + value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + } else { + error = true; + } break; case SensorValueType::U_DWORD_R: case SensorValueType::FP32_R: - value = get_data<uint32_t>(data, offset); - value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16; - value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + if (size >= 4) { + value = get_data<uint32_t>(data, offset); + value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16; + value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); + } else { + error = true; + } break; case SensorValueType::S_WORD: - value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset), - bitmask); // default is 0xFFFF ; + if (size >= 2) { + value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset), + bitmask); // default is 0xFFFF ; + } else { + error = true; + } break; case SensorValueType::S_DWORD: - value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask); + if (size >= 4) { + value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask); + } else { + error = true; + } break; case SensorValueType::S_DWORD_R: { - value = get_data<uint32_t>(data, offset); - // Currently the high word is at the low position - // the sign bit is therefore at low before the switch - uint32_t sign_bit = (value & 0x8000) << 16; - value = mask_and_shift_by_rightbit( - static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask); + if (size >= 4) { + value = get_data<uint32_t>(data, offset); + // Currently the high word is at the low position + // the sign bit is therefore at low before the switch + uint32_t sign_bit = (value & 0x8000) << 16; + value = mask_and_shift_by_rightbit( + static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask); + } else { + error = true; + } } break; case SensorValueType::U_QWORD: case SensorValueType::S_QWORD: // Ignore bitmask for QWORD - value = get_data<uint64_t>(data, offset); + if (size >= 8) { + value = get_data<uint64_t>(data, offset); + } else { + error = true; + } break; case SensorValueType::U_QWORD_R: case SensorValueType::S_QWORD_R: { // Ignore bitmask for QWORD - uint64_t tmp = get_data<uint64_t>(data, offset); - value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000); + if (size >= 8) { + uint64_t tmp = get_data<uint64_t>(data, offset); + value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000); + } else { + error = true; + } } break; case SensorValueType::RAW: default: break; } + if (error) + ESP_LOGE(TAG, "not enough data for value"); return value; } From 1b91e0027b8cf40f3ed5e897ccbada8fab5d3448 Mon Sep 17 00:00:00 2001 From: TFGF <terciofilho@gmail.com> Date: Sun, 24 Nov 2024 19:15:10 -0300 Subject: [PATCH 208/282] [Modbus Controller] Fix issue #6477. Online automation triggering Offline (#7801) --- esphome/components/modbus_controller/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 5c407d6fff..2a08075831 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -163,7 +163,7 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_ON_OFFLINE): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOnlineTrigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOfflineTrigger), } ), } From 72bf0086e486a71d5f44e99f3bed925f996e7aa3 Mon Sep 17 00:00:00 2001 From: Ramil Valitov <ramilvalitov@gmail.com> Date: Mon, 25 Nov 2024 01:23:30 +0300 Subject: [PATCH 209/282] [fix] Status sensor does not check if required network component is missing (#7734) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/status/binary_sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/status/binary_sensor.py b/esphome/components/status/binary_sensor.py index 1f2b7c9d18..adc342ed4d 100644 --- a/esphome/components/status/binary_sensor.py +++ b/esphome/components/status/binary_sensor.py @@ -6,6 +6,8 @@ from esphome.const import ( ENTITY_CATEGORY_DIAGNOSTIC, ) +DEPENDENCIES = ["network"] + status_ns = cg.esphome_ns.namespace("status") StatusBinarySensor = status_ns.class_( "StatusBinarySensor", binary_sensor.BinarySensor, cg.Component From 4c7552eca4af9c43f93e00e41e9ef7b1ed28db92 Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Sun, 24 Nov 2024 12:40:51 -1000 Subject: [PATCH 210/282] keypad binary sensors should be initially off (#7808) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- esphome/components/binary_sensor/binary_sensor.h | 2 +- .../matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 301a472810..57cae9e2f5 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -58,7 +58,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass { void publish_initial_state(bool state); /// The current reported state of the binary sensor. - bool state; + bool state{false}; void add_filter(Filter *filter); void add_filters(const std::vector<Filter *> &filters); diff --git a/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h b/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h index d8a217f55e..2c1ce96f0a 100644 --- a/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h +++ b/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h @@ -6,7 +6,7 @@ namespace esphome { namespace matrix_keypad { -class MatrixKeypadBinarySensor : public MatrixKeypadListener, public binary_sensor::BinarySensor { +class MatrixKeypadBinarySensor : public MatrixKeypadListener, public binary_sensor::BinarySensorInitiallyOff { public: MatrixKeypadBinarySensor(uint8_t key) : has_key_(true), key_(key){}; MatrixKeypadBinarySensor(const char *key) : has_key_(true), key_((uint8_t) key[0]){}; From 5ddbe5cdba7faf510b15821e896b6d5b88d8af4d Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Mon, 25 Nov 2024 13:58:21 -1000 Subject: [PATCH 211/282] [wifi] fix 32 char SSIDs (#7834) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- .../wifi/wifi_component_esp32_arduino.cpp | 24 +++++++++++++++---- .../wifi/wifi_component_esp8266.cpp | 24 +++++++++++++++---- .../wifi/wifi_component_esp_idf.cpp | 24 +++++++++++++++---- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 88648093c6..ee00e2ac6c 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -137,8 +137,16 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - snprintf(reinterpret_cast<char *>(conf.sta.ssid), sizeof(conf.sta.ssid), "%s", ap.get_ssid().c_str()); - snprintf(reinterpret_cast<char *>(conf.sta.password), sizeof(conf.sta.password), "%s", ap.get_password().c_str()); + if (ap.get_ssid().size() > sizeof(conf.sta.ssid)) { + ESP_LOGE(TAG, "SSID is too long"); + return false; + } + if (ap.get_password().size() > sizeof(conf.sta.password)) { + ESP_LOGE(TAG, "password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.sta.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); + memcpy(reinterpret_cast<char *>(conf.sta.password), ap.get_password().c_str(), ap.get_password().size()); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -746,7 +754,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - snprintf(reinterpret_cast<char *>(conf.ap.ssid), sizeof(conf.ap.ssid), "%s", ap.get_ssid().c_str()); + if (ap.get_ssid().size() > sizeof(conf.ap.ssid)) { + ESP_LOGE(TAG, "AP SSID is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ap.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -757,7 +769,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - snprintf(reinterpret_cast<char *>(conf.ap.password), sizeof(conf.ap.password), "%s", ap.get_password().c_str()); + if (ap.get_password().size() > sizeof(conf.ap.password)) { + ESP_LOGE(TAG, "AP password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ap.password), ap.get_password().c_str(), ap.get_password().size()); } // pairwise cipher of SoftAP, group cipher will be derived using this. diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 4568895950..14506f569c 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -236,8 +236,16 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { struct station_config conf {}; memset(&conf, 0, sizeof(conf)); - snprintf(reinterpret_cast<char *>(conf.ssid), sizeof(conf.ssid), "%s", ap.get_ssid().c_str()); - snprintf(reinterpret_cast<char *>(conf.password), sizeof(conf.password), "%s", ap.get_password().c_str()); + if (ap.get_ssid().size() > sizeof(conf.ssid)) { + ESP_LOGE(TAG, "SSID is too long"); + return false; + } + if (ap.get_password().size() > sizeof(conf.password)) { + ESP_LOGE(TAG, "password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); + memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size()); if (ap.get_bssid().has_value()) { conf.bssid_set = 1; @@ -775,7 +783,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return false; struct softap_config conf {}; - snprintf(reinterpret_cast<char *>(conf.ssid), sizeof(conf.ssid), "%s", ap.get_ssid().c_str()); + if (ap.get_ssid().size() > sizeof(conf.ssid)) { + ESP_LOGE(TAG, "AP SSID is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); conf.ssid_len = static_cast<uint8>(ap.get_ssid().size()); conf.channel = ap.get_channel().value_or(1); conf.ssid_hidden = ap.get_hidden(); @@ -787,7 +799,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.password = 0; } else { conf.authmode = AUTH_WPA2_PSK; - snprintf(reinterpret_cast<char *>(conf.password), sizeof(conf.password), "%s", ap.get_password().c_str()); + if (ap.get_password().size() > sizeof(conf.password)) { + ESP_LOGE(TAG, "AP password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size()); } ETS_UART_INTR_DISABLE(); diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 13870136d4..3074ffbe1b 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -289,8 +289,16 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - snprintf(reinterpret_cast<char *>(conf.sta.ssid), sizeof(conf.sta.ssid), "%s", ap.get_ssid().c_str()); - snprintf(reinterpret_cast<char *>(conf.sta.password), sizeof(conf.sta.password), "%s", ap.get_password().c_str()); + if (ap.get_ssid().size() > sizeof(conf.sta.ssid)) { + ESP_LOGE(TAG, "SSID is too long"); + return false; + } + if (ap.get_password().size() > sizeof(conf.sta.password)) { + ESP_LOGE(TAG, "password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.sta.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); + memcpy(reinterpret_cast<char *>(conf.sta.password), ap.get_password().c_str(), ap.get_password().size()); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -902,7 +910,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast<char *>(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); + if (ap.get_ssid().size() > sizeof(conf.ap.ssid)) { + ESP_LOGE(TAG, "AP SSID is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ap.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -913,7 +925,11 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strncpy(reinterpret_cast<char *>(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password)); + if (ap.get_password().size() > sizeof(conf.ap.password)) { + ESP_LOGE(TAG, "AP password is too long"); + return false; + } + memcpy(reinterpret_cast<char *>(conf.ap.password), ap.get_password().c_str(), ap.get_password().size()); } // pairwise cipher of SoftAP, group cipher will be derived using this. From 2539cba61047781de159d174f613d7ca0478b9e8 Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Tue, 26 Nov 2024 00:05:20 -1000 Subject: [PATCH 212/282] [honeywell] use warning instead of failing (#7862) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- .../components/honeywellabp2_i2c/honeywellabp2.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp b/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp index e2910032cc..d111723669 100644 --- a/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp +++ b/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp @@ -15,7 +15,7 @@ static const char *const TAG = "honeywellabp2"; void HONEYWELLABP2Sensor::read_sensor_data() { if (this->read(raw_data_, 7) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Communication with ABP2 failed!"); - this->mark_failed(); + this->status_set_warning("couldn't read sensor data"); return; } float press_counts = encode_uint24(raw_data_[1], raw_data_[2], raw_data_[3]); // calculate digital pressure counts @@ -25,12 +25,13 @@ void HONEYWELLABP2Sensor::read_sensor_data() { (this->max_pressure_ - this->min_pressure_)) + this->min_pressure_; this->last_temperature_ = (temp_counts * 200 / 16777215) - 50; + this->status_clear_warning(); } void HONEYWELLABP2Sensor::start_measurement() { if (this->write(i2c_cmd_, 3) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Communication with ABP2 failed!"); - this->mark_failed(); + this->status_set_warning("couldn't start measurement"); return; } this->measurement_running_ = true; @@ -39,7 +40,7 @@ void HONEYWELLABP2Sensor::start_measurement() { bool HONEYWELLABP2Sensor::is_measurement_ready() { if (this->read(raw_data_, 1) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Communication with ABP2 failed!"); - this->mark_failed(); + this->status_set_warning("couldn't check measurement"); return false; } if ((raw_data_[0] & (0x1 << STATUS_BIT_BUSY)) > 0) { @@ -52,7 +53,7 @@ bool HONEYWELLABP2Sensor::is_measurement_ready() { void HONEYWELLABP2Sensor::measurement_timeout() { ESP_LOGE(TAG, "Timeout!"); this->measurement_running_ = false; - this->mark_failed(); + this->status_set_warning("measurement timed out"); } float HONEYWELLABP2Sensor::get_pressure() { return this->last_pressure_; } @@ -79,7 +80,7 @@ void HONEYWELLABP2Sensor::update() { ESP_LOGV(TAG, "Update Honeywell ABP2 Sensor"); this->start_measurement(); - this->set_timeout("meas_timeout", 50, [this] { this->measurement_timeout(); }); + this->set_timeout("meas_timeout", 100, [this] { this->measurement_timeout(); }); } void HONEYWELLABP2Sensor::dump_config() { From c894645747d742937cfedc572dcc319f2a8caaa5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:06:21 +1300 Subject: [PATCH 213/282] Bump version to 2024.11.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d14cdecb23..4b19e2865d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.1" +__version__ = "2024.11.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From e6c730ab109ee2b896283399c3f7453c1591d1de Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:16:54 -0600 Subject: [PATCH 214/282] [max31865] clang-tidy fixes for #7822 (#7876) --- esphome/components/max31865/max31865.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/max31865/max31865.cpp b/esphome/components/max31865/max31865.cpp index b48aa2fdd3..4749874ac7 100644 --- a/esphome/components/max31865/max31865.cpp +++ b/esphome/components/max31865/max31865.cpp @@ -106,7 +106,8 @@ void MAX31865Sensor::read_data_() { // Check faults const uint8_t faults = this->read_register_(FAULT_STATUS_REG); - if ((has_fault_ = faults & 0b00111100)) { + has_fault_ = faults & 0b00111100; + if (has_fault_) { if (faults & (1 << 2)) { ESP_LOGE(TAG, "Overvoltage/undervoltage fault"); } @@ -125,7 +126,8 @@ void MAX31865Sensor::read_data_() { } else { this->status_clear_error(); } - if ((has_warn_ = faults & 0b11000000)) { + has_warn_ = faults & 0b11000000; + if (has_warn_) { if (faults & (1 << 6)) { ESP_LOGW(TAG, "RTD Low Threshold"); } From 8439232b11ad720c0896fa6bf66ab3594ef91a53 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:18:43 -0600 Subject: [PATCH 215/282] [esp32_ble] clang-tidy fixes for #7822 (#7883) --- esphome/components/esp32_ble/ble_uuid.cpp | 19 +++++++++---------- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 5 ++++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index 07ac719434..aa1edd96b2 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -34,7 +34,7 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) { ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) { ESPBTUUID ret; ret.uuid_.len = ESP_UUID_LEN_128; - for (int i = 0; i < ESP_UUID_LEN_128; i++) + for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++) ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i]; return ret; } @@ -43,30 +43,30 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { if (data.length() == 4) { ret.uuid_.len = ESP_UUID_LEN_16; ret.uuid_.uuid.uuid16 = 0; - for (int i = 0; i < data.length();) { + for (uint i = 0; i < data.length(); i += 2) { uint8_t msb = data.c_str()[i]; uint8_t lsb = data.c_str()[i + 1]; + uint8_t lsb_shift = i <= 2 ? (2 - i) * 4 : 0; if (msb > '9') msb -= 7; if (lsb > '9') lsb -= 7; - ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4; - i += 2; + ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << lsb_shift; } } else if (data.length() == 8) { ret.uuid_.len = ESP_UUID_LEN_32; ret.uuid_.uuid.uuid32 = 0; - for (int i = 0; i < data.length();) { + for (uint i = 0; i < data.length(); i += 2) { uint8_t msb = data.c_str()[i]; uint8_t lsb = data.c_str()[i + 1]; + uint8_t lsb_shift = i <= 6 ? (6 - i) * 4 : 0; if (msb > '9') msb -= 7; if (lsb > '9') lsb -= 7; - ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4; - i += 2; + ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << lsb_shift; } } else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be // investigated (lack of time) @@ -77,7 +77,7 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { // UUID format. ret.uuid_.len = ESP_UUID_LEN_128; int n = 0; - for (int i = 0; i < data.length();) { + for (uint i = 0; i < data.length(); i += 2) { if (data.c_str()[i] == '-') i++; uint8_t msb = data.c_str()[i]; @@ -88,7 +88,6 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { if (lsb > '9') lsb -= 7; ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F); - i += 2; } } else { ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str()); @@ -155,7 +154,7 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { } break; case ESP_UUID_LEN_128: - for (int i = 0; i < ESP_UUID_LEN_128; i++) { + for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++) { if (uuid.uuid_.uuid.uuid128[i] != this->uuid_.uuid.uuid128[i]) { return false; } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index b86d32ee61..6d051e3d4a 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -432,7 +432,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE ESP_LOGVV(TAG, "Parse Result:"); - const char *address_type = ""; + const char *address_type; switch (this->address_type_) { case BLE_ADDR_TYPE_PUBLIC: address_type = "PUBLIC"; @@ -446,6 +446,9 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e case BLE_ADDR_TYPE_RPA_RANDOM: address_type = "RPA_RANDOM"; break; + default: + address_type = "UNKNOWN"; + break; } ESP_LOGVV(TAG, " Address: %02X:%02X:%02X:%02X:%02X:%02X (%s)", this->address_[0], this->address_[1], this->address_[2], this->address_[3], this->address_[4], this->address_[5], address_type); From f2e8e655ba6476301842f260197e8e52f4ce71eb Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:19:41 -0600 Subject: [PATCH 216/282] [mqtt] clang-tidy fixes for #7822 (#7877) --- .../components/mqtt/mqtt_alarm_control_panel.cpp | 7 ++----- esphome/components/mqtt/mqtt_climate.cpp | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index 660a030d11..4cc4773bd3 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -80,8 +80,7 @@ const EntityBase *MQTTAlarmControlPanelComponent::get_entity() const { return th bool MQTTAlarmControlPanelComponent::send_initial_state() { return this->publish_state(); } bool MQTTAlarmControlPanelComponent::publish_state() { - bool success = true; - const char *state_s = ""; + const char *state_s; switch (this->alarm_control_panel_->get_state()) { case ACP_STATE_DISARMED: state_s = "disarmed"; @@ -116,9 +115,7 @@ bool MQTTAlarmControlPanelComponent::publish_state() { default: state_s = "unknown"; } - if (!this->publish(this->get_state_topic_(), state_s)) - success = false; - return success; + return this->publish(this->get_state_topic_(), state_s); } } // namespace mqtt diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 773d863835..f06574fa26 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -257,7 +257,7 @@ const EntityBase *MQTTClimateComponent::get_entity() const { return this->device bool MQTTClimateComponent::publish_state_() { auto traits = this->device_->get_traits(); // mode - const char *mode_s = ""; + const char *mode_s; switch (this->device_->mode) { case CLIMATE_MODE_OFF: mode_s = "off"; @@ -280,6 +280,8 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_MODE_HEAT_COOL: mode_s = "heat_cool"; break; + default: + mode_s = "unknown"; } bool success = true; if (!this->publish(this->get_mode_state_topic(), mode_s)) @@ -343,6 +345,8 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_PRESET_ACTIVITY: payload = "activity"; break; + default: + payload = "unknown"; } } if (this->device_->custom_preset.has_value()) @@ -352,7 +356,7 @@ bool MQTTClimateComponent::publish_state_() { } if (traits.get_supports_action()) { - const char *payload = "unknown"; + const char *payload; switch (this->device_->action) { case CLIMATE_ACTION_OFF: payload = "off"; @@ -372,6 +376,8 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_ACTION_FAN: payload = "fan"; break; + default: + payload = "unknown"; } if (!this->publish(this->get_action_state_topic(), payload)) success = false; @@ -411,6 +417,8 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_FAN_QUIET: payload = "quiet"; break; + default: + payload = "unknown"; } } if (this->device_->custom_fan_mode.has_value()) @@ -420,7 +428,7 @@ bool MQTTClimateComponent::publish_state_() { } if (traits.get_supports_swing_modes()) { - const char *payload = ""; + const char *payload; switch (this->device_->swing_mode) { case CLIMATE_SWING_OFF: payload = "off"; @@ -434,6 +442,8 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_SWING_HORIZONTAL: payload = "horizontal"; break; + default: + payload = "unknown"; } if (!this->publish(this->get_swing_mode_state_topic(), payload)) success = false; From 4da57c35d0c1b1a3ec413fe8352f71d7d7e12d0e Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:20:51 -0600 Subject: [PATCH 217/282] [uln2003] clang-tidy fixes for #7822 (#7881) --- esphome/components/uln2003/uln2003.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/uln2003/uln2003.cpp b/esphome/components/uln2003/uln2003.cpp index 1af9806906..991fe53487 100644 --- a/esphome/components/uln2003/uln2003.cpp +++ b/esphome/components/uln2003/uln2003.cpp @@ -40,7 +40,7 @@ void ULN2003::dump_config() { LOG_PIN(" Pin C: ", this->pin_c_); LOG_PIN(" Pin D: ", this->pin_d_); ESP_LOGCONFIG(TAG, " Sleep when done: %s", YESNO(this->sleep_when_done_)); - const char *step_mode_s = ""; + const char *step_mode_s; switch (this->step_mode_) { case ULN2003_STEP_MODE_FULL_STEP: step_mode_s = "FULL STEP"; @@ -51,6 +51,9 @@ void ULN2003::dump_config() { case ULN2003_STEP_MODE_WAVE_DRIVE: step_mode_s = "WAVE DRIVE"; break; + default: + step_mode_s = "UNKNOWN"; + break; } ESP_LOGCONFIG(TAG, " Step Mode: %s", step_mode_s); } From 567256bd62cb39ec9706af6b36ca405e584eae5d Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:21:10 -0600 Subject: [PATCH 218/282] [rotary_encoder] clang-tidy fixes for #7822 (#7880) --- esphome/components/rotary_encoder/rotary_encoder.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index a3631ffe27..e9a0eac3f5 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -162,7 +162,7 @@ void RotaryEncoderSensor::dump_config() { LOG_PIN(" Pin B: ", this->pin_b_); LOG_PIN(" Pin I: ", this->pin_i_); - const LogString *restore_mode = LOG_STR(""); + const LogString *restore_mode; switch (this->restore_mode_) { case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: restore_mode = LOG_STR("Restore (Defaults to zero)"); @@ -170,6 +170,8 @@ void RotaryEncoderSensor::dump_config() { case ROTARY_ENCODER_ALWAYS_ZERO: restore_mode = LOG_STR("Always zero"); break; + default: + restore_mode = LOG_STR(""); } ESP_LOGCONFIG(TAG, " Restore Mode: %s", LOG_STR_ARG(restore_mode)); From 65a5216d17b14fddb803906074faa3fb8ef1512f Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:22:18 -0600 Subject: [PATCH 219/282] [pca6416a, pca9554] clang-tidy fixes for #7822 (#7879) --- esphome/components/pca6416a/pca6416a.cpp | 8 +++++--- esphome/components/pca9554/pca9554.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/esphome/components/pca6416a/pca6416a.cpp b/esphome/components/pca6416a/pca6416a.cpp index 1f4e315644..53c0dcaf76 100644 --- a/esphome/components/pca6416a/pca6416a.cpp +++ b/esphome/components/pca6416a/pca6416a.cpp @@ -34,7 +34,7 @@ void PCA6416AComponent::setup() { } // Test to see if the device supports pull-up resistors - if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == esphome::i2c::ERROR_OK) { + if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == i2c::ERROR_OK) { this->has_pullup_ = true; } @@ -106,7 +106,8 @@ bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) { return false; } - if ((this->last_error_ = this->read_register(reg, value, 1, true)) != esphome::i2c::ERROR_OK) { + this->last_error_ = this->read_register(reg, value, 1, true); + if (this->last_error_ != i2c::ERROR_OK) { this->status_set_warning(); ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_); return false; @@ -122,7 +123,8 @@ bool PCA6416AComponent::write_register_(uint8_t reg, uint8_t value) { return false; } - if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) { + this->last_error_ = this->write_register(reg, &value, 1, true); + if (this->last_error_ != i2c::ERROR_OK) { this->status_set_warning(); ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_); return false; diff --git a/esphome/components/pca9554/pca9554.cpp b/esphome/components/pca9554/pca9554.cpp index c5a4bcfb09..78b877072a 100644 --- a/esphome/components/pca9554/pca9554.cpp +++ b/esphome/components/pca9554/pca9554.cpp @@ -95,8 +95,8 @@ bool PCA9554Component::read_inputs_() { return false; } - if ((this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_, true)) != - esphome::i2c::ERROR_OK) { + this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_, true); + if (this->last_error_ != i2c::ERROR_OK) { this->status_set_warning(); ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_); return false; @@ -113,8 +113,8 @@ bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) { uint8_t outputs[2]; outputs[0] = (uint8_t) value; outputs[1] = (uint8_t) (value >> 8); - if ((this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_, true)) != - esphome::i2c::ERROR_OK) { + this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_, true); + if (this->last_error_ != i2c::ERROR_OK) { this->status_set_warning(); ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_); return false; From a825ef59d47d0550983c733d5730d41ff52eaef7 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:22:37 -0600 Subject: [PATCH 220/282] [nextion] clang-tidy fixes for #7822 (#7878) --- esphome/components/nextion/nextion.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 7c41f8dfe2..50a5834347 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -563,13 +563,10 @@ void Nextion::process_nextion_commands_() { break; } - int dataindex = 0; - int value = 0; for (int i = 0; i < 4; ++i) { value += to_process[i] << (8 * i); - ++dataindex; } NextionQueue *nb = this->nextion_queue_.front(); From 12cdeca48a78169069e462c3dc8785268d9ac90f Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:23:20 -0600 Subject: [PATCH 221/282] [various] clang-tidy fixes for #7822 (#7874) --- .../hitachi_ac344/hitachi_ac344.cpp | 6 ++- .../hitachi_ac424/hitachi_ac424.cpp | 6 ++- .../components/ili9xxx/ili9xxx_display.cpp | 5 +- esphome/components/qspi_dbi/qspi_dbi.cpp | 3 +- esphome/components/st7735/st7735.cpp | 2 +- esphome/components/st7789v/st7789v.cpp | 2 +- esphome/components/udp/udp_component.cpp | 48 ++++++++++++------- esphome/core/component.cpp | 2 +- 8 files changed, 48 insertions(+), 26 deletions(-) diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 2825e4f04c..2bcb205644 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -133,8 +133,10 @@ bool HitachiClimate::get_swing_v_() { } void HitachiClimate::set_swing_h_(uint8_t position) { - if (position > HITACHI_AC344_SWINGH_LEFT_MAX) - return set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE); + if (position > HITACHI_AC344_SWINGH_LEFT_MAX) { + set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE); + return; + } set_bits(&remote_state_[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE, position); set_button_(HITACHI_AC344_BUTTON_SWINGH); } diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index 0bfc3ae564..64f23dfc17 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -133,8 +133,10 @@ bool HitachiClimate::get_swing_v_() { } void HitachiClimate::set_swing_h_(uint8_t position) { - if (position > HITACHI_AC424_SWINGH_LEFT_MAX) - return set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE); + if (position > HITACHI_AC424_SWINGH_LEFT_MAX) { + set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE); + return; + } set_bits(&remote_state_[HITACHI_AC424_SWINGH_BYTE], HITACHI_AC424_SWINGH_OFFSET, HITACHI_AC424_SWINGH_SIZE, position); set_button_(HITACHI_AC424_BUTTON_SWINGH); } diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 81976dd2c9..b9664067a9 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -313,8 +313,9 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons // do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not // configured the renderer well. if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian) { - return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, - x_pad); + display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); + return; } this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1); // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index 785885d4ec..f8fd5dd374 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -146,7 +146,8 @@ void QspiDbi::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8 return; if (bitness != display::COLOR_BITNESS_565 || order != this->color_mode_ || big_endian != (this->bit_order_ == spi::BIT_ORDER_MSB_FIRST)) { - return Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + return; } else if (this->draw_from_origin_) { auto stride = x_offset + w + x_pad; for (int y = 0; y != h; y++) { diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index a0c2d80d16..5985d8bfb3 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -483,7 +483,7 @@ void ST7735::spi_master_write_color_(uint16_t color, uint16_t size) { } this->dc_pin_->digital_write(true); - return write_array(byte, size * 2); + write_array(byte, size * 2); } } // namespace st7735 diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index 74c7a4e9e3..0d2f35aef5 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -252,7 +252,7 @@ void ST7789V::write_color_(uint16_t color, uint16_t size) { } this->dc_pin_->digital_write(true); - return write_array(byte, size * 2); + write_array(byte, size * 2); } size_t ST7789V::get_buffer_length_() { diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index a1c8889997..b8727ec423 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -434,7 +434,8 @@ static bool process_rolling_code(Provider &provider, uint8_t *&buf, const uint8_ void UDPComponent::process_(uint8_t *buf, const size_t len) { auto ping_key_seen = !this->ping_pong_enable_; if (len < 8) { - return ESP_LOGV(TAG, "Bad length %zu", len); + ESP_LOGV(TAG, "Bad length %zu", len); + return; } char namebuf[256]{}; uint8_t byte; @@ -442,31 +443,40 @@ void UDPComponent::process_(uint8_t *buf, const size_t len) { const uint8_t *end = buf + len; FuData rdata{}; auto magic = get_uint16(buf); - if (magic != MAGIC_NUMBER && magic != MAGIC_PING) - return ESP_LOGV(TAG, "Bad magic %X", magic); + if (magic != MAGIC_NUMBER && magic != MAGIC_PING) { + ESP_LOGV(TAG, "Bad magic %X", magic); + return; + } auto hlen = *buf++; if (hlen > len - 3) { - return ESP_LOGV(TAG, "Bad hostname length %u > %zu", hlen, len - 3); + ESP_LOGV(TAG, "Bad hostname length %u > %zu", hlen, len - 3); + return; } memcpy(namebuf, buf, hlen); if (strcmp(this->name_, namebuf) == 0) { - return ESP_LOGV(TAG, "Ignoring our own data"); + ESP_LOGV(TAG, "Ignoring our own data"); + return; } buf += hlen; - if (magic == MAGIC_PING) - return this->process_ping_request_(namebuf, buf, end - buf); + if (magic == MAGIC_PING) { + this->process_ping_request_(namebuf, buf, end - buf); + return; + } if (round4(len) != len) { - return ESP_LOGW(TAG, "Bad length %zu", len); + ESP_LOGW(TAG, "Bad length %zu", len); + return; } hlen = round4(hlen + 3); buf = start_ptr + hlen; if (buf == end) { - return ESP_LOGV(TAG, "No data after header"); + ESP_LOGV(TAG, "No data after header"); + return; } if (this->providers_.count(namebuf) == 0) { - return ESP_LOGVV(TAG, "Unknown hostname %s", namebuf); + ESP_LOGVV(TAG, "Unknown hostname %s", namebuf); + return; } auto &provider = this->providers_[namebuf]; // if encryption not used with this host, ping check is pointless since it would be easily spoofed. @@ -489,7 +499,8 @@ void UDPComponent::process_(uint8_t *buf, const size_t len) { if (!process_rolling_code(provider, buf, end)) return; } else if (byte != DATA_KEY) { - return ESP_LOGV(TAG, "Expected rolling_key or data_key, got %X", byte); + ESP_LOGV(TAG, "Expected rolling_key or data_key, got %X", byte); + return; } while (buf < end) { byte = *buf++; @@ -497,7 +508,8 @@ void UDPComponent::process_(uint8_t *buf, const size_t len) { continue; if (byte == PING_KEY) { if (end - buf < 4) { - return ESP_LOGV(TAG, "PING_KEY requires 4 more bytes"); + ESP_LOGV(TAG, "PING_KEY requires 4 more bytes"); + return; } auto key = get_uint32(buf); if (key == this->ping_key_) { @@ -515,21 +527,25 @@ void UDPComponent::process_(uint8_t *buf, const size_t len) { } if (byte == BINARY_SENSOR_KEY) { if (end - buf < 3) { - return ESP_LOGV(TAG, "Binary sensor key requires at least 3 more bytes"); + ESP_LOGV(TAG, "Binary sensor key requires at least 3 more bytes"); + return; } rdata.u32 = *buf++; } else if (byte == SENSOR_KEY) { if (end - buf < 6) { - return ESP_LOGV(TAG, "Sensor key requires at least 6 more bytes"); + ESP_LOGV(TAG, "Sensor key requires at least 6 more bytes"); + return; } rdata.u32 = get_uint32(buf); } else { - return ESP_LOGW(TAG, "Unknown key byte %X", byte); + ESP_LOGW(TAG, "Unknown key byte %X", byte); + return; } hlen = *buf++; if (end - buf < hlen) { - return ESP_LOGV(TAG, "Name length of %u not available", hlen); + ESP_LOGV(TAG, "Name length of %u not available", hlen); + return; } memset(namebuf, 0, sizeof namebuf); memcpy(namebuf, buf, hlen); diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index ae73a451d9..a6224a17c0 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -67,7 +67,7 @@ bool Component::cancel_retry(const std::string &name) { // NOLINT } void Component::set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f) { // NOLINT - return App.scheduler.set_timeout(this, name, timeout, std::move(f)); + App.scheduler.set_timeout(this, name, timeout, std::move(f)); } bool Component::cancel_timeout(const std::string &name) { // NOLINT From e229ed0da3f7113757907b1b7db1934517390c11 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:23:40 -0600 Subject: [PATCH 222/282] [logger] clang-tidy fixes for #7822 (#7875) --- esphome/components/logger/logger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index dac08fbbce..36934c7459 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -47,7 +47,7 @@ void Logger::write_header_(int level, const char *tag, int line) { if (current_task == main_task_) { this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line); } else { - const char *thread_name = ""; + const char *thread_name = ""; // NOLINT(clang-analyzer-deadcode.DeadStores) #if defined(USE_ESP32) thread_name = pcTaskGetName(current_task); #elif defined(USE_LIBRETINY) From e124151e5c4c6ca925baf382e7dca19592a257c9 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:24:43 -0600 Subject: [PATCH 223/282] [ezo] clang-tidy fixes for #7822 (#7873) --- esphome/components/ezo/ezo.cpp | 40 ++++++++++++---------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 8e4486dbf2..10f3d530ce 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -111,11 +111,11 @@ void EZOSensor::loop() { if (buf[0] == 1) { std::string payload = reinterpret_cast<char *>(&buf[1]); if (!payload.empty()) { + auto start_location = payload.find(','); switch (to_run->command_type) { case EzoCommandType::EZO_READ: { // some sensors return multiple comma-separated values, terminate string after first one - int start_location = 0; - if ((start_location = payload.find(',')) != std::string::npos) { + if (start_location != std::string::npos) { payload.erase(start_location); } auto val = parse_number<float>(payload); @@ -126,49 +126,37 @@ void EZOSensor::loop() { } break; } - case EzoCommandType::EZO_LED: { + case EzoCommandType::EZO_LED: this->led_callback_.call(payload.back() == '1'); break; - } - case EzoCommandType::EZO_DEVICE_INFORMATION: { - int start_location = 0; - if ((start_location = payload.find(',')) != std::string::npos) { + case EzoCommandType::EZO_DEVICE_INFORMATION: + if (start_location != std::string::npos) { this->device_infomation_callback_.call(payload.substr(start_location + 1)); } break; - } - case EzoCommandType::EZO_SLOPE: { - int start_location = 0; - if ((start_location = payload.find(',')) != std::string::npos) { + case EzoCommandType::EZO_SLOPE: + if (start_location != std::string::npos) { this->slope_callback_.call(payload.substr(start_location + 1)); } break; - } - case EzoCommandType::EZO_CALIBRATION: { - int start_location = 0; - if ((start_location = payload.find(',')) != std::string::npos) { + case EzoCommandType::EZO_CALIBRATION: + if (start_location != std::string::npos) { this->calibration_callback_.call(payload.substr(start_location + 1)); } break; - } - case EzoCommandType::EZO_T: { - int start_location = 0; - if ((start_location = payload.find(',')) != std::string::npos) { + case EzoCommandType::EZO_T: + if (start_location != std::string::npos) { this->t_callback_.call(payload.substr(start_location + 1)); } break; - } - case EzoCommandType::EZO_CUSTOM: { + case EzoCommandType::EZO_CUSTOM: this->custom_callback_.call(payload); break; - } - default: { + default: break; - } } } } - this->commands_.pop_front(); } @@ -178,7 +166,7 @@ void EZOSensor::add_command_(const std::string &command, EzoCommandType command_ ezo_command->command_type = command_type; ezo_command->delay_ms = delay_ms; this->commands_.push_back(std::move(ezo_command)); -}; +} void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) { std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value); From 7aa3a1a1ccdb8e3cf2df837817e18a7307b90640 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:25:00 -0600 Subject: [PATCH 224/282] [apds9306] clang-tidy fixes for #7822 (#7872) --- esphome/components/apds9306/apds9306.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/apds9306/apds9306.cpp b/esphome/components/apds9306/apds9306.cpp index 7b79b0964c..bbb3ba1910 100644 --- a/esphome/components/apds9306/apds9306.cpp +++ b/esphome/components/apds9306/apds9306.cpp @@ -122,7 +122,8 @@ void APDS9306::update() { this->status_clear_warning(); - if (!(status &= 0b00001000)) { // No new data + status &= 0b00001000; + if (!status) { // No new data return; } From ff5004d7db6514536dfd0e28ec3d114bb14b120c Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:25:15 -0600 Subject: [PATCH 225/282] [dht] clang-tidy fixes for #7822 (#7871) --- esphome/components/dht/dht.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index db1c851d5f..3f9f9c57f4 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -135,7 +135,8 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r // Wait for falling edge while (this->pin_->digital_read()) { - if ((end_time = micros()) - start_time > 90) { + end_time = micros(); + if (end_time - start_time > 90) { if (i < 0) { error_code = 3; } else { From d30587028412d80a02ea1fc4ef7cd0085b1508d8 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 27 Nov 2024 16:25:34 -0600 Subject: [PATCH 226/282] [network] clang-tidy fixes for #7822 (#7870) --- esphome/components/network/ip_address.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 941934cf0a..69d3788ca5 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -116,7 +116,7 @@ struct IPAddress { operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); } #endif - bool is_set() { return !ip_addr_isany(&ip_addr_); } + bool is_set() { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr) bool is_ip4() { return IP_IS_V4(&ip_addr_); } bool is_ip6() { return IP_IS_V6(&ip_addr_); } std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); } From c9b0490305fb402f13f7ee4ee449db9d207c978e Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:48:48 +1100 Subject: [PATCH 227/282] [lvgl] Make image update via lambda work (#7886) --- esphome/components/lvgl/defines.py | 8 +++++- esphome/components/lvgl/lv_validation.py | 11 +++++--- esphome/components/lvgl/lvgl_esphome.h | 16 ++++++++++++ esphome/components/lvgl/widgets/animimg.py | 29 +++++++++------------- esphome/components/lvgl/widgets/img.py | 2 -- tests/components/lvgl/lvgl-package.yaml | 12 ++++++--- tests/components/lvgl/test.esp32-ard.yaml | 2 +- 7 files changed, 52 insertions(+), 28 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index ea345fa55c..81984637bd 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -8,7 +8,7 @@ import logging from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS -from esphome.core import Lambda +from esphome.core import ID, Lambda from esphome.cpp_generator import LambdaExpression, MockObj from esphome.cpp_types import uint32 from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -72,6 +72,12 @@ class LValidator: ) if self.retmapper is not None: return self.retmapper(value) + if isinstance(value, ID): + return await cg.get_variable(value) + if isinstance(value, list): + value = [ + await cg.get_variable(x) if isinstance(x, ID) else x for x in value + ] return cg.safe_exp(value) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 766c010244..f91ed893f2 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,6 +1,7 @@ from typing import Union import esphome.codegen as cg +from esphome.components import image from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw from esphome.components.font import Font from esphome.components.image import Image_ @@ -31,7 +32,7 @@ from .defines import ( literal, ) from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component -from .types import lv_font_t, lv_gradient_t, lv_img_t +from .types import lv_font_t, lv_gradient_t opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") @@ -332,8 +333,12 @@ def image_validator(value): lv_image = LValidator( image_validator, - lv_img_t, - retmapper=lambda x: MockObj(x, "->").get_lv_img_dsc(), + image.Image_.operator("ptr"), + requires="image", +) +lv_image_list = LValidator( + cv.ensure_list(image_validator), + cg.std_vector.template(image.Image_.operator("ptr")), requires="image", ) lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal) diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 208cb1cbd5..921b7c109f 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -57,6 +57,22 @@ inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) { lv_img_set_src(obj, image->get_lv_img_dsc()); } #endif // USE_LVGL_IMAGE +#ifdef USE_LVGL_ANIMIMG +inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) { + auto *dsc = static_cast<std::vector<lv_img_dsc_t *> *>(lv_obj_get_user_data(img)); + if (dsc == nullptr) { + // object will be lazily allocated but never freed. + dsc = new std::vector<lv_img_dsc_t *>(images.size()); // NOLINT + lv_obj_set_user_data(img, dsc); + } + dsc->clear(); + for (auto &image : images) { + dsc->push_back(image->get_lv_img_dsc()); + } + lv_animimg_set_src(img, (const void **) dsc->data(), dsc->size()); +} + +#endif // USE_LVGL_ANIMIMG // Parent class for things that wrap an LVGL object class LvCompound { diff --git a/esphome/components/lvgl/widgets/animimg.py b/esphome/components/lvgl/widgets/animimg.py index 8adea72ad3..b824d28fb8 100644 --- a/esphome/components/lvgl/widgets/animimg.py +++ b/esphome/components/lvgl/widgets/animimg.py @@ -1,20 +1,18 @@ from esphome import automation -import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_DURATION, CONF_ID from ..automation import action_to_code from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC from ..helpers import lvgl_components_required -from ..lv_validation import lv_image, lv_milliseconds +from ..lv_validation import lv_image_list, lv_milliseconds from ..lvcode import lv -from ..types import LvType, ObjUpdateAction, void_ptr +from ..types import LvType, ObjUpdateAction from . import Widget, WidgetType, get_widgets from .img import CONF_IMAGE from .label import CONF_LABEL CONF_ANIMIMG = "animimg" -CONF_SRC_LIST_ID = "src_list_id" def lv_repeat_count(value): @@ -32,14 +30,14 @@ ANIMIMG_BASE_SCHEMA = cv.Schema( ANIMIMG_SCHEMA = ANIMIMG_BASE_SCHEMA.extend( { cv.Required(CONF_DURATION): lv_milliseconds, - cv.Required(CONF_SRC): cv.ensure_list(lv_image), - cv.GenerateID(CONF_SRC_LIST_ID): cv.declare_id(void_ptr), + cv.Required(CONF_SRC): lv_image_list, } ) ANIMIMG_MODIFY_SCHEMA = ANIMIMG_BASE_SCHEMA.extend( { cv.Optional(CONF_DURATION): lv_milliseconds, + cv.Optional(CONF_SRC): lv_image_list, } ) @@ -59,17 +57,14 @@ class AnimimgType(WidgetType): async def to_code(self, w: Widget, config): lvgl_components_required.add(CONF_IMAGE) lvgl_components_required.add(CONF_ANIMIMG) - if CONF_SRC in config: - srcs = [ - await lv_image.process(await cg.get_variable(x)) - for x in config[CONF_SRC] - ] - src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs) - count = len(config[CONF_SRC]) - lv.animimg_set_src(w.obj, src_id, count) - lv.animimg_set_repeat_count(w.obj, config[CONF_REPEAT_COUNT]) - lv.animimg_set_duration(w.obj, config[CONF_DURATION]) - if config.get(CONF_AUTO_START): + if srcs := config.get(CONF_SRC): + srcs = await lv_image_list.process(srcs) + lv.animimg_set_src(w.obj, srcs) + if repeat_count := config.get(CONF_REPEAT_COUNT): + lv.animimg_set_repeat_count(w.obj, repeat_count) + if duration := config.get(CONF_DURATION): + lv.animimg_set_duration(w.obj, duration) + if config[CONF_AUTO_START]: lv.animimg_start(w.obj) def get_uses(self): diff --git a/esphome/components/lvgl/widgets/img.py b/esphome/components/lvgl/widgets/img.py index 931d0c0b5b..59b2c97c63 100644 --- a/esphome/components/lvgl/widgets/img.py +++ b/esphome/components/lvgl/widgets/img.py @@ -1,4 +1,3 @@ -import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ANGLE, CONF_MODE @@ -65,7 +64,6 @@ class ImgType(WidgetType): async def to_code(self, w: Widget, config): if src := config.get(CONF_SRC): - src = await cg.get_variable(src) lv.img_set_src(w.obj, await lv_image.process(src)) if (cf_angle := config.get(CONF_ANGLE)) is not None: pivot_x = config[CONF_PIVOT_X] diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index db0443b3bb..81b18c4ff8 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -171,9 +171,13 @@ lvgl: duration: 1s auto_start: true on_all_events: - logger.log: - format: "Event %s" - args: ['lv_event_code_name_for(event->code).c_str()'] + - logger.log: + format: "Event %s" + args: ['lv_event_code_name_for(event->code).c_str()'] + - lvgl.animimg.update: + id: anim_img + src: !lambda "return {dog_image, cat_image};" + duration: 2s - label: id: hello_label text: Hello world @@ -635,7 +639,7 @@ lvgl: - image: grid_cell_row_pos: 0 grid_cell_column_pos: 0 - src: dog_image + src: !lambda return dog_image; on_click: then: - lvgl.tabview.select: diff --git a/tests/components/lvgl/test.esp32-ard.yaml b/tests/components/lvgl/test.esp32-ard.yaml index 80d5ce503f..5b09147de7 100644 --- a/tests/components/lvgl/test.esp32-ard.yaml +++ b/tests/components/lvgl/test.esp32-ard.yaml @@ -55,5 +55,5 @@ lvgl: packages: lvgl: !include lvgl-package.yaml + xvgl: !include common.yaml -<<: !include common.yaml From 7cdf5b55ef637e76d13e31b596d7b7d76a5b4ab8 Mon Sep 17 00:00:00 2001 From: Max Slotov <max@slotov.dev> Date: Thu, 28 Nov 2024 05:51:07 +0200 Subject: [PATCH 228/282] [deep_sleep] fix deep_sleep not keeping awake when sleep_duration is defined (#7885) --- esphome/components/deep_sleep/deep_sleep_esp32.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/deep_sleep/deep_sleep_esp32.cpp b/esphome/components/deep_sleep/deep_sleep_esp32.cpp index d54046bc11..d647140865 100644 --- a/esphome/components/deep_sleep/deep_sleep_esp32.cpp +++ b/esphome/components/deep_sleep/deep_sleep_esp32.cpp @@ -52,11 +52,11 @@ void DeepSleepComponent::dump_config_platform_() { bool DeepSleepComponent::prepare_to_sleep_() { if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_ != nullptr && - !this->sleep_duration_.has_value() && this->wakeup_pin_->digital_read()) { + this->wakeup_pin_->digital_read()) { // Defer deep sleep until inactive if (!this->next_enter_deep_sleep_) { this->status_set_warning(); - ESP_LOGW(TAG, "Waiting for pin_ to switch state to enter deep sleep..."); + ESP_LOGW(TAG, "Waiting wakeup pin state change to enter deep sleep..."); } this->next_enter_deep_sleep_ = true; return false; From beb8ab50e27f5df45e15af1cf3691309a5eb2e92 Mon Sep 17 00:00:00 2001 From: guillempages <guillempages@users.noreply.github.com> Date: Thu, 28 Nov 2024 04:55:20 +0100 Subject: [PATCH 229/282] [online_image]Don't access decoder if not initialized (#7882) --- esphome/components/online_image/png_image.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/online_image/png_image.cpp b/esphome/components/online_image/png_image.cpp index c8e215a91d..4c4c22f9b7 100644 --- a/esphome/components/online_image/png_image.cpp +++ b/esphome/components/online_image/png_image.cpp @@ -49,6 +49,10 @@ void PngDecoder::prepare(uint32_t download_size) { } int HOT PngDecoder::decode(uint8_t *buffer, size_t size) { + if (!this->pngle_) { + ESP_LOGE(TAG, "PNG decoder engine not initialized!"); + return -1; + } if (size < 256 && size < this->download_size_ - this->decoded_bytes_) { ESP_LOGD(TAG, "Waiting for data"); return 0; From 5486b40aab86f6505c62b9077b1d1737548ec554 Mon Sep 17 00:00:00 2001 From: FreeBear-nc <67865163+FreeBear-nc@users.noreply.github.com> Date: Thu, 28 Nov 2024 03:56:37 +0000 Subject: [PATCH 230/282] Add IRAM_ATTR to all functions used during interrupts on esp8266 chips. (#7840) --- esphome/components/opentherm/opentherm.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index e40fc66b7d..62ab1d3860 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -220,7 +220,7 @@ void IRAM_ATTR OpenTherm::bit_read_(uint8_t value) { this->bit_pos_++; } -ProtocolErrorType OpenTherm::verify_stop_bit_(uint8_t value) { +ProtocolErrorType IRAM_ATTR OpenTherm::verify_stop_bit_(uint8_t value) { if (value) { // stop bit detected return check_parity_(this->data_) ? ProtocolErrorType::NO_ERROR : ProtocolErrorType::PARITY_ERROR; } else { // no stop bit detected, error @@ -365,7 +365,7 @@ void IRAM_ATTR OpenTherm::stop_timer_() { #ifdef ESP8266 // 5 kHz timer_ -void OpenTherm::start_read_timer_() { +void IRAM_ATTR OpenTherm::start_read_timer_() { InterruptLock const lock; timer1_attachInterrupt(OpenTherm::esp8266_timer_isr); timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max) @@ -373,14 +373,14 @@ void OpenTherm::start_read_timer_() { } // 2 kHz timer_ -void OpenTherm::start_write_timer_() { +void IRAM_ATTR OpenTherm::start_write_timer_() { InterruptLock const lock; timer1_attachInterrupt(OpenTherm::esp8266_timer_isr); timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max) timer1_write(2500); // 2kHz } -void OpenTherm::stop_timer_() { +void IRAM_ATTR OpenTherm::stop_timer_() { InterruptLock const lock; timer1_disable(); timer1_detachInterrupt(); @@ -389,7 +389,7 @@ void OpenTherm::stop_timer_() { #endif // END ESP8266 // https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd -bool OpenTherm::check_parity_(uint32_t val) { +bool IRAM_ATTR OpenTherm::check_parity_(uint32_t val) { val ^= val >> 16; val ^= val >> 8; val ^= val >> 4; From 217a80a1789d235705c47862748ef2c0a8af5a1c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:57:11 +1300 Subject: [PATCH 231/282] [st7920] Remove unnecessary warning when drawing outside display bounds (#7868) --- esphome/components/st7920/st7920.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index f336d24e24..171e7095dd 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -1,7 +1,7 @@ #include "st7920.h" -#include "esphome/core/log.h" -#include "esphome/core/application.h" #include "esphome/components/display/display_buffer.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" namespace esphome { namespace st7920 { @@ -118,7 +118,6 @@ size_t ST7920::get_buffer_length_() { void HOT ST7920::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { - ESP_LOGW(TAG, "Position out of area: %dx%d", x, y); return; } int width = this->get_width_internal() / 8u; From 30477c764d9353381ef6e8bf186bae703cffbc7f Mon Sep 17 00:00:00 2001 From: Krzysztof Zdulski <krzys.zdulski@gmail.com> Date: Fri, 29 Nov 2024 22:05:00 +0100 Subject: [PATCH 232/282] Fix recalc_timestamp_utc (#7894) --- esphome/core/time.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index 31977d972b..66a0e1c0a7 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -169,7 +169,7 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { } for (int i = 1970; i < this->year; i++) - res += (year % 4 == 0) ? 366 : 365; + res += (i % 4 == 0) ? 366 : 365; if (use_day_of_year) { res += this->day_of_year - 1; From 8f69d070612d9ed7e862fc31c340568dffabf9f7 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Sun, 1 Dec 2024 10:08:52 -0600 Subject: [PATCH 233/282] [hx711] clang-tidy fixes for #7822 (#7900) --- esphome/components/hx711/hx711.cpp | 2 +- esphome/components/hx711/hx711.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/hx711/hx711.cpp b/esphome/components/hx711/hx711.cpp index 1a7169eed7..9643d0c411 100644 --- a/esphome/components/hx711/hx711.cpp +++ b/esphome/components/hx711/hx711.cpp @@ -53,7 +53,7 @@ bool HX711Sensor::read_sensor_(uint32_t *result) { } // Cycle clock pin for gain setting - for (uint8_t i = 0; i < this->gain_; i++) { + for (uint8_t i = 0; i < static_cast<uint8_t>(this->gain_); i++) { this->sck_pin_->digital_write(true); delayMicroseconds(1); this->sck_pin_->digital_write(false); diff --git a/esphome/components/hx711/hx711.h b/esphome/components/hx711/hx711.h index 0cb6868ab5..a92bb9945d 100644 --- a/esphome/components/hx711/hx711.h +++ b/esphome/components/hx711/hx711.h @@ -9,7 +9,7 @@ namespace esphome { namespace hx711 { -enum HX711Gain { +enum HX711Gain : uint8_t { HX711_GAIN_128 = 1, HX711_GAIN_32 = 2, HX711_GAIN_64 = 3, From 83d6834e277e9e9159f12653a2f39bc1113f9104 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Dec 2024 05:10:18 +1300 Subject: [PATCH 234/282] Cast port to int for ota pushing (#7888) --- esphome/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 86d529e1bf..dce041e5ac 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -363,7 +363,7 @@ def upload_program(config, args, host): from esphome import espota2 - remote_port = ota_conf[CONF_PORT] + remote_port = int(ota_conf[CONF_PORT]) password = ota_conf.get(CONF_PASSWORD, "") if ( From edd847ea403c4a0fba05d652b8e30e09fd3e6a85 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Sun, 1 Dec 2024 18:27:32 -0600 Subject: [PATCH 235/282] [modbus_controller] Clang fixes (#7899) --- .../modbus_controller/modbus_controller.cpp | 40 ++++++++--------- .../modbus_controller/modbus_controller.h | 44 +++++++++---------- .../number/modbus_number.cpp | 16 +++---- .../modbus_controller/number/modbus_number.h | 8 ++-- .../output/modbus_output.cpp | 13 +++--- .../modbus_controller/output/modbus_output.h | 10 ++--- .../select/modbus_select.cpp | 7 +-- .../modbus_controller/select/modbus_select.h | 8 ++-- .../switch/modbus_switch.cpp | 8 ++-- .../modbus_controller/switch/modbus_switch.h | 4 +- 10 files changed, 80 insertions(+), 78 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index f8b72af817..641ba68223 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -152,11 +152,11 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t } SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const { - auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { + auto reg_it = find_if(begin(this->register_ranges_), end(this->register_ranges_), [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); }); - if (reg_it == register_ranges_.end()) { + if (reg_it == this->register_ranges_.end()) { ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address); } else { return reg_it->sensors; @@ -240,18 +240,18 @@ void ModbusController::update() { // walk through the sensors and determine the register ranges to read size_t ModbusController::create_register_ranges_() { - register_ranges_.clear(); - if (this->parent_->role == modbus::ModbusRole::CLIENT && sensorset_.empty()) { + this->register_ranges_.clear(); + if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) { ESP_LOGW(TAG, "No sensors registered"); return 0; } // iterator is sorted see SensorItemsComparator for details - auto ix = sensorset_.begin(); + auto ix = this->sensorset_.begin(); RegisterRange r = {}; uint8_t buffer_offset = 0; SensorItem *prev = nullptr; - while (ix != sensorset_.end()) { + while (ix != this->sensorset_.end()) { SensorItem *curr = *ix; ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count, @@ -278,12 +278,12 @@ size_t ModbusController::create_register_ranges_() { // this register can re-use the data from the previous register // remove this sensore because start_address is changed (sort-order) - ix = sensorset_.erase(ix); + ix = this->sensorset_.erase(ix); curr->start_address = r.start_address; curr->offset += prev->offset; - sensorset_.insert(curr); + this->sensorset_.insert(curr); // move iterator backwards because it will be incremented later ix--; @@ -293,14 +293,14 @@ size_t ModbusController::create_register_ranges_() { // this register can extend the current range // remove this sensore because start_address is changed (sort-order) - ix = sensorset_.erase(ix); + ix = this->sensorset_.erase(ix); curr->start_address = r.start_address; curr->offset += buffer_offset; buffer_offset += curr->get_register_size(); r.register_count += curr->register_count; - sensorset_.insert(curr); + this->sensorset_.insert(curr); // move iterator backwards because it will be incremented later ix--; @@ -327,7 +327,7 @@ size_t ModbusController::create_register_ranges_() { ix++; } else { ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); - register_ranges_.push_back(r); + this->register_ranges_.push_back(r); r = {}; buffer_offset = 0; // do not increment the iterator here because the current sensor has to be re-evaluated @@ -339,10 +339,10 @@ size_t ModbusController::create_register_ranges_() { if (r.register_count > 0) { // Add the last range ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); - register_ranges_.push_back(r); + this->register_ranges_.push_back(r); } - return register_ranges_.size(); + return this->register_ranges_.size(); } void ModbusController::dump_config() { @@ -352,18 +352,18 @@ void ModbusController::dump_config() { ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE ESP_LOGCONFIG(TAG, "sensormap"); - for (auto &it : sensorset_) { + for (auto &it : this->sensorset_) { ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d", static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count, it->get_register_size()); } ESP_LOGCONFIG(TAG, "ranges"); - for (auto &it : register_ranges_) { + for (auto &it : this->register_ranges_) { ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type), it.start_address, it.register_count, it.skip_updates); } ESP_LOGCONFIG(TAG, "server registers"); - for (auto &r : server_registers_) { + for (auto &r : this->server_registers_) { ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address, static_cast<uint8_t>(r->value_type), r->register_count); } @@ -372,11 +372,11 @@ void ModbusController::dump_config() { void ModbusController::loop() { // Incoming data to process? - if (!incoming_queue_.empty()) { - auto &message = incoming_queue_.front(); + if (!this->incoming_queue_.empty()) { + auto &message = this->incoming_queue_.front(); if (message != nullptr) process_modbus_data_(message.get()); - incoming_queue_.pop(); + this->incoming_queue_.pop(); } else { // all messages processed send pending commands @@ -391,7 +391,7 @@ void ModbusController::on_write_register_response(ModbusRegisterType register_ty void ModbusController::dump_sensors_() { ESP_LOGV(TAG, "sensors"); - for (auto &it : sensorset_) { + for (auto &it : this->sensorset_) { ESP_LOGV(TAG, " Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count, it->get_register_size(), it->offset); } diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 2a0b936bf5..dfd52e44bc 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -240,14 +240,14 @@ class SensorItem { } // Override register size for modbus devices not using 1 register for one dword void set_register_size(uint8_t register_size) { response_bytes = register_size; } - ModbusRegisterType register_type; - SensorValueType sensor_value_type; - uint16_t start_address; - uint32_t bitmask; - uint8_t offset; - uint8_t register_count; + ModbusRegisterType register_type{ModbusRegisterType::CUSTOM}; + SensorValueType sensor_value_type{SensorValueType::RAW}; + uint16_t start_address{0}; + uint32_t bitmask{0}; + uint8_t offset{0}; + uint8_t register_count{0}; uint8_t response_bytes{0}; - uint16_t skip_updates; + uint16_t skip_updates{0}; std::vector<uint8_t> custom_data{}; bool force_new_range{false}; }; @@ -261,9 +261,9 @@ class ServerRegister { this->register_count = register_count; this->read_lambda = std::move(read_lambda); } - uint16_t address; - SensorValueType value_type; - uint8_t register_count; + uint16_t address{0}; + SensorValueType value_type{SensorValueType::RAW}; + uint8_t register_count{0}; std::function<float()> read_lambda; }; @@ -312,11 +312,11 @@ struct RegisterRange { class ModbusCommandItem { public: static const size_t MAX_PAYLOAD_BYTES = 240; - ModbusController *modbusdevice; - uint16_t register_address; - uint16_t register_count; - ModbusFunctionCode function_code; - ModbusRegisterType register_type; + ModbusController *modbusdevice{nullptr}; + uint16_t register_address{0}; + uint16_t register_count{0}; + ModbusFunctionCode function_code{ModbusFunctionCode::CUSTOM}; + ModbusRegisterType register_type{ModbusRegisterType::CUSTOM}; std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> on_data_func; std::vector<uint8_t> payload = {}; @@ -493,23 +493,23 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { /// Collection of all sensors for this component SensorSet sensorset_; /// Collection of all server registers for this component - std::vector<ServerRegister *> server_registers_; + std::vector<ServerRegister *> server_registers_{}; /// Continuous range of modbus registers - std::vector<RegisterRange> register_ranges_; + std::vector<RegisterRange> register_ranges_{}; /// Hold the pending requests to be sent std::list<std::unique_ptr<ModbusCommandItem>> command_queue_; /// modbus response data waiting to get processed std::queue<std::unique_ptr<ModbusCommandItem>> incoming_queue_; /// if duplicate commands can be sent - bool allow_duplicate_commands_; + bool allow_duplicate_commands_{false}; /// when was the last send operation - uint32_t last_command_timestamp_; + uint32_t last_command_timestamp_{0}; /// min time in ms between sending modbus commands - uint16_t command_throttle_; + uint16_t command_throttle_{0}; /// if module didn't respond the last command - bool module_offline_; + bool module_offline_{false}; /// how many updates to skip if module is offline - uint16_t offline_skip_updates_; + uint16_t offline_skip_updates_{0}; /// How many times we will retry a command if we get no response uint8_t max_cmd_retries_{4}; /// Command sent callback diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 001cfb5787..ea8467d5a3 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -8,7 +8,7 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) { - float result = payload_to_float(data, *this) / multiply_by_; + float result = payload_to_float(data, *this) / this->multiply_by_; // Is there a lambda registered // call it with the pre converted value and the raw data array @@ -43,7 +43,7 @@ void ModbusNumber::control(float value) { return; } } else { - write_value = multiply_by_ * write_value; + write_value = this->multiply_by_ * write_value; } if (!data.empty()) { @@ -63,21 +63,21 @@ void ModbusNumber::control(float value) { // Create and send the write command if (this->register_count == 1 && !this->use_write_multiple_) { // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 - write_cmd = - ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); + write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2, + data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, - this->register_count, data); + write_cmd = ModbusCommandItem::create_write_multiple_command( + this->parent_, this->start_address + this->offset / 2, this->register_count, data); } // publish new value write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) { // gets called when the write command is ack'd from the device - parent_->on_write_register_response(write_cmd.register_type, start_address, data); + this->parent_->on_write_register_response(write_cmd.register_type, start_address, data); this->publish_state(value); }; } - parent_->queue_command(write_cmd); + this->parent_->queue_command(write_cmd); this->publish_state(value); } void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); } diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 544d161cbc..8f77b2e014 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -29,7 +29,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem void parse_and_publish(const std::vector<uint8_t> &data) override; float get_setup_priority() const override { return setup_priority::HARDWARE; } void set_parent(ModbusController *parent) { this->parent_ = parent; } - void set_write_multiply(float factor) { multiply_by_ = factor; } + void set_write_multiply(float factor) { this->multiply_by_ = factor; } using transform_func_t = std::function<optional<float>(ModbusNumber *, float, const std::vector<uint8_t> &)>; using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_t> &)>; @@ -39,9 +39,9 @@ class ModbusNumber : public number::Number, public Component, public SensorItem protected: void control(float value) override; - optional<transform_func_t> transform_func_; - optional<write_transform_func_t> write_transform_func_; - ModbusController *parent_; + optional<transform_func_t> transform_func_{nullopt}; + optional<write_transform_func_t> write_transform_func_{nullopt}; + ModbusController *parent_{nullptr}; float multiply_by_{1.0}; bool use_write_multiple_{false}; }; diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index 79cd2d49c2..f0f6e64f10 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -27,7 +27,7 @@ void ModbusFloatOutput::write_state(float value) { return; } } else { - value = multiply_by_ * value; + value = this->multiply_by_ * value; } // lambda didn't set payload if (data.empty()) { @@ -40,12 +40,13 @@ void ModbusFloatOutput::write_state(float value) { // Create and send the write command ModbusCommandItem write_cmd; if (this->register_count == 1 && !this->use_write_multiple_) { - write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + write_cmd = + ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset, data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + write_cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset, this->register_count, data); } - parent_->queue_command(write_cmd); + this->parent_->queue_command(write_cmd); } void ModbusFloatOutput::dump_config() { @@ -90,9 +91,9 @@ void ModbusBinaryOutput::write_state(bool state) { // offset for coil and discrete inputs is the coil/register number not bytes if (this->use_write_multiple_) { std::vector<bool> states{state}; - cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states); + cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states); } else { - cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state); } } this->parent_->queue_command(cmd); diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index f424671cd1..bceb97affb 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -25,7 +25,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S void dump_config() override; void set_parent(ModbusController *parent) { this->parent_ = parent; } - void set_write_multiply(float factor) { multiply_by_ = factor; } + void set_write_multiply(float factor) { this->multiply_by_ = factor; } // Do nothing void parse_and_publish(const std::vector<uint8_t> &data) override{}; @@ -37,9 +37,9 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S void write_state(float value) override; optional<write_transform_func_t> write_transform_func_{nullopt}; - ModbusController *parent_; + ModbusController *parent_{nullptr}; float multiply_by_{1.0}; - bool use_write_multiple_; + bool use_write_multiple_{false}; }; class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem { @@ -68,8 +68,8 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public void write_state(bool state) override; optional<write_transform_func_t> write_transform_func_{nullopt}; - ModbusController *parent_; - bool use_write_multiple_; + ModbusController *parent_{nullptr}; + bool use_write_multiple_{false}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/select/modbus_select.cpp b/esphome/components/modbus_controller/select/modbus_select.cpp index 33cef39a18..56b8c783ed 100644 --- a/esphome/components/modbus_controller/select/modbus_select.cpp +++ b/esphome/components/modbus_controller/select/modbus_select.cpp @@ -74,12 +74,13 @@ void ModbusSelect::control(const std::string &value) { const uint16_t write_address = this->start_address + this->offset / 2; ModbusCommandItem write_cmd; if ((this->register_count == 1) && (!this->use_write_multiple_)) { - write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]); + write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, write_address, data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data); + write_cmd = + ModbusCommandItem::create_write_multiple_command(this->parent_, write_address, this->register_count, data); } - parent_->queue_command(write_cmd); + this->parent_->queue_command(write_cmd); if (this->optimistic_) this->publish_state(value); diff --git a/esphome/components/modbus_controller/select/modbus_select.h b/esphome/components/modbus_controller/select/modbus_select.h index 1c046b11d0..55fb2107dd 100644 --- a/esphome/components/modbus_controller/select/modbus_select.h +++ b/esphome/components/modbus_controller/select/modbus_select.h @@ -42,12 +42,12 @@ class ModbusSelect : public Component, public select::Select, public SensorItem void control(const std::string &value) override; protected: - std::vector<int64_t> mapping_; - ModbusController *parent_; + std::vector<int64_t> mapping_{}; + ModbusController *parent_{nullptr}; bool use_write_multiple_{false}; bool optimistic_{false}; - optional<transform_func_t> transform_func_; - optional<write_transform_func_t> write_transform_func_; + optional<transform_func_t> transform_func_{nullopt}; + optional<write_transform_func_t> write_transform_func_{nullopt}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index 3a679fbeb8..ec29eca7f8 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -80,18 +80,18 @@ void ModbusSwitch::write_state(bool state) { // offset for coil and discrete inputs is the coil/register number not bytes if (this->use_write_multiple_) { std::vector<bool> states{state}; - cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states); + cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states); } else { - cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state); } } else { // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 if (this->use_write_multiple_) { std::vector<uint16_t> bool_states(1, state ? (0xFFFF & this->bitmask) : 0); - cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, 1, + cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset / 2, 1, bool_states); } else { - cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, + cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2, state ? 0xFFFF & this->bitmask : 0u); } } diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h index bfe46f3ac8..fe4b7c1ad5 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.h +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -40,8 +40,8 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: - ModbusController *parent_; - bool use_write_multiple_; + ModbusController *parent_{nullptr}; + bool use_write_multiple_{false}; optional<transform_func_t> publish_transform_func_{nullopt}; optional<write_transform_func_t> write_transform_func_{nullopt}; }; From fb96e3588d1f771f7430beec570da2d09a83e2f2 Mon Sep 17 00:00:00 2001 From: David Woodhouse <dwmw2@infradead.org> Date: Mon, 2 Dec 2024 08:16:58 +0000 Subject: [PATCH 236/282] Add H-Bridge switch component (#7421) Co-authored-by: Keith Burzinski <kbx81x@gmail.com> --- CODEOWNERS | 1 + esphome/components/hbridge/switch/__init__.py | 44 +++++++++ .../hbridge/switch/hbridge_switch.cpp | 95 +++++++++++++++++++ .../hbridge/switch/hbridge_switch.h | 50 ++++++++++ tests/components/hbridge/common.yaml | 39 ++++++++ tests/components/hbridge/test.esp32-ard.yaml | 46 +++------ .../components/hbridge/test.esp32-c3-ard.yaml | 45 +++------ .../components/hbridge/test.esp32-c3-idf.yaml | 44 +++------ tests/components/hbridge/test.esp32-idf.yaml | 45 +++------ .../components/hbridge/test.esp8266-ard.yaml | 45 +++------ tests/components/hbridge/test.rp2040-ard.yaml | 45 +++------ 11 files changed, 313 insertions(+), 186 deletions(-) create mode 100644 esphome/components/hbridge/switch/__init__.py create mode 100644 esphome/components/hbridge/switch/hbridge_switch.cpp create mode 100644 esphome/components/hbridge/switch/hbridge_switch.h create mode 100644 tests/components/hbridge/common.yaml diff --git a/CODEOWNERS b/CODEOWNERS index fb6d11d1fb..74c205b302 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -179,6 +179,7 @@ esphome/components/haier/text_sensor/* @paveldn esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann +esphome/components/hbridge/switch/* @dwmw2 esphome/components/he60r/* @clydebarrow esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal diff --git a/esphome/components/hbridge/switch/__init__.py b/esphome/components/hbridge/switch/__init__.py new file mode 100644 index 0000000000..e26bd6b1d8 --- /dev/null +++ b/esphome/components/hbridge/switch/__init__.py @@ -0,0 +1,44 @@ +from esphome import pins +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import CONF_OPTIMISTIC, CONF_PULSE_LENGTH, CONF_WAIT_TIME + +from .. import hbridge_ns + +HBridgeSwitch = hbridge_ns.class_("HBridgeSwitch", switch.Switch, cg.Component) + +CODEOWNERS = ["@dwmw2"] + +CONF_OFF_PIN = "off_pin" +CONF_ON_PIN = "on_pin" + +CONFIG_SCHEMA = ( + switch.switch_schema(HBridgeSwitch) + .extend( + { + cv.Required(CONF_ON_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_OFF_PIN): pins.gpio_output_pin_schema, + cv.Optional( + CONF_PULSE_LENGTH, default="100ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_WAIT_TIME): cv.positive_time_period_milliseconds, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await switch.new_switch(config) + await cg.register_component(var, config) + + on_pin = await cg.gpio_pin_expression(config[CONF_ON_PIN]) + cg.add(var.set_on_pin(on_pin)) + off_pin = await cg.gpio_pin_expression(config[CONF_OFF_PIN]) + cg.add(var.set_off_pin(off_pin)) + cg.add(var.set_pulse_length(config[CONF_PULSE_LENGTH])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if wait_time := config.get(CONF_WAIT_TIME): + cg.add(var.set_wait_time(wait_time)) diff --git a/esphome/components/hbridge/switch/hbridge_switch.cpp b/esphome/components/hbridge/switch/hbridge_switch.cpp new file mode 100644 index 0000000000..12d1c01bca --- /dev/null +++ b/esphome/components/hbridge/switch/hbridge_switch.cpp @@ -0,0 +1,95 @@ +#include "hbridge_switch.h" +#include "esphome/core/log.h" + +#include <cinttypes> + +namespace esphome { +namespace hbridge { + +static const char *const TAG = "switch.hbridge"; + +float HBridgeSwitch::get_setup_priority() const { return setup_priority::HARDWARE; } +void HBridgeSwitch::setup() { + ESP_LOGCONFIG(TAG, "Setting up H-Bridge Switch '%s'...", this->name_.c_str()); + + optional<bool> initial_state = this->get_initial_state_with_restore_mode().value_or(false); + + // Like GPIOSwitch does, set the pin state both before and after pin setup() + this->on_pin_->digital_write(false); + this->on_pin_->setup(); + this->on_pin_->digital_write(false); + + this->off_pin_->digital_write(false); + this->off_pin_->setup(); + this->off_pin_->digital_write(false); + + if (initial_state.has_value()) + this->write_state(initial_state); +} + +void HBridgeSwitch::dump_config() { + LOG_SWITCH("", "H-Bridge Switch", this); + LOG_PIN(" On Pin: ", this->on_pin_); + LOG_PIN(" Off Pin: ", this->off_pin_); + ESP_LOGCONFIG(TAG, " Pulse length: %" PRId32 " ms", this->pulse_length_); + if (this->wait_time_) + ESP_LOGCONFIG(TAG, " Wait time %" PRId32 " ms", this->wait_time_); +} + +void HBridgeSwitch::write_state(bool state) { + this->desired_state_ = state; + if (!this->timer_running_) + this->timer_fn_(); +} + +void HBridgeSwitch::timer_fn_() { + uint32_t next_timeout = 0; + + while ((uint8_t) this->desired_state_ != this->relay_state_) { + switch (this->relay_state_) { + case RELAY_STATE_ON: + case RELAY_STATE_OFF: + case RELAY_STATE_UNKNOWN: + if (this->desired_state_) { + this->on_pin_->digital_write(true); + this->relay_state_ = RELAY_STATE_SWITCHING_ON; + } else { + this->off_pin_->digital_write(true); + this->relay_state_ = RELAY_STATE_SWITCHING_OFF; + } + next_timeout = this->pulse_length_; + if (!this->optimistic_) + this->publish_state(this->desired_state_); + break; + + case RELAY_STATE_SWITCHING_ON: + this->on_pin_->digital_write(false); + this->relay_state_ = RELAY_STATE_ON; + if (this->optimistic_) + this->publish_state(true); + next_timeout = this->wait_time_; + break; + + case RELAY_STATE_SWITCHING_OFF: + this->off_pin_->digital_write(false); + this->relay_state_ = RELAY_STATE_OFF; + if (this->optimistic_) + this->publish_state(false); + next_timeout = this->wait_time_; + break; + } + + if (next_timeout) { + this->timer_running_ = true; + this->set_timeout(next_timeout, [this]() { this->timer_fn_(); }); + return; + } + + // In the case where ON/OFF state has been reached but we need to + // immediately change back again to reach desired_state_, we loop. + } + this->timer_running_ = false; +} + +} // namespace hbridge +} // namespace esphome diff --git a/esphome/components/hbridge/switch/hbridge_switch.h b/esphome/components/hbridge/switch/hbridge_switch.h new file mode 100644 index 0000000000..ce00c6baa2 --- /dev/null +++ b/esphome/components/hbridge/switch/hbridge_switch.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/switch/switch.h" + +#include <vector> + +namespace esphome { +namespace hbridge { + +enum RelayState : uint8_t { + RELAY_STATE_OFF = 0, + RELAY_STATE_ON = 1, + RELAY_STATE_SWITCHING_ON = 2, + RELAY_STATE_SWITCHING_OFF = 3, + RELAY_STATE_UNKNOWN = 4, +}; + +class HBridgeSwitch : public switch_::Switch, public Component { + public: + void set_on_pin(GPIOPin *pin) { this->on_pin_ = pin; } + void set_off_pin(GPIOPin *pin) { this->off_pin_ = pin; } + void set_pulse_length(uint32_t pulse_length) { this->pulse_length_ = pulse_length; } + void set_wait_time(uint32_t wait_time) { this->wait_time_ = wait_time; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + float get_setup_priority() const override; + + void setup() override; + void dump_config() override; + + protected: + void write_state(bool state) override; + void timer_fn_(); + + bool timer_running_{false}; + bool desired_state_{false}; + RelayState relay_state_{RELAY_STATE_UNKNOWN}; + GPIOPin *on_pin_{nullptr}; + GPIOPin *off_pin_{nullptr}; + uint32_t pulse_length_{0}; + uint32_t wait_time_{0}; + bool optimistic_{false}; +}; + +} // namespace hbridge +} // namespace esphome diff --git a/tests/components/hbridge/common.yaml b/tests/components/hbridge/common.yaml new file mode 100644 index 0000000000..0504cdea03 --- /dev/null +++ b/tests/components/hbridge/common.yaml @@ -0,0 +1,39 @@ +output: + - platform: ${pwm_platform} + pin: ${output1_pin} + id: gpio_output1 + - platform: ${pwm_platform} + pin: ${output2_pin} + id: gpio_output2 + - platform: ${pwm_platform} + pin: ${output3_pin} + id: gpio_output3 + - platform: ${pwm_platform} + pin: ${output4_pin} + id: gpio_output4 + +light: + - platform: hbridge + name: Icicle Lights + pin_a: gpio_output3 + pin_b: gpio_output4 + +fan: + - platform: hbridge + id: fan_hbridge + speed_count: 4 + name: H-bridge Fan with Presets + pin_a: gpio_output1 + pin_b: gpio_output2 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! + +switch: + - platform: hbridge + id: switch_hbridge + on_pin: ${hbridge_on_pin} + off_pin: ${hbridge_off_pin} diff --git a/tests/components/hbridge/test.esp32-ard.yaml b/tests/components/hbridge/test.esp32-ard.yaml index 6a80aaaf3b..e50d537749 100644 --- a/tests/components/hbridge/test.esp32-ard.yaml +++ b/tests/components/hbridge/test.esp32-ard.yaml @@ -1,33 +1,17 @@ -output: - - platform: ledc - pin: 14 - id: gpio_output1 - - platform: ledc - pin: 15 - id: gpio_output2 - - platform: ledc - pin: 12 - id: gpio_output3 - - platform: ledc - pin: 13 - id: gpio_output4 +substitutions: + pwm_platform: ledc + output1_pin: "14" + output2_pin: "15" + output3_pin: "12" + output4_pin: "13" + hbridge_on_pin: "4" + hbridge_off_pin: "5" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms + wait_time: 10ms + optimistic: false diff --git a/tests/components/hbridge/test.esp32-c3-ard.yaml b/tests/components/hbridge/test.esp32-c3-ard.yaml index 70cfd6ab6f..b9e8738442 100644 --- a/tests/components/hbridge/test.esp32-c3-ard.yaml +++ b/tests/components/hbridge/test.esp32-c3-ard.yaml @@ -1,33 +1,16 @@ -output: - - platform: ledc - pin: 4 - id: gpio_output1 - - platform: ledc - pin: 5 - id: gpio_output2 - - platform: ledc - pin: 6 - id: gpio_output3 - - platform: ledc - pin: 7 - id: gpio_output4 +substitutions: + pwm_platform: "ledc" + output1_pin: "4" + output2_pin: "5" + output3_pin: "6" + output4_pin: "7" + hbridge_on_pin: "2" + hbridge_off_pin: "3" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + wait_time: 10ms + optimistic: true diff --git a/tests/components/hbridge/test.esp32-c3-idf.yaml b/tests/components/hbridge/test.esp32-c3-idf.yaml index 70cfd6ab6f..c73f08b6de 100644 --- a/tests/components/hbridge/test.esp32-c3-idf.yaml +++ b/tests/components/hbridge/test.esp32-c3-idf.yaml @@ -1,33 +1,15 @@ -output: - - platform: ledc - pin: 4 - id: gpio_output1 - - platform: ledc - pin: 5 - id: gpio_output2 - - platform: ledc - pin: 6 - id: gpio_output3 - - platform: ledc - pin: 7 - id: gpio_output4 +substitutions: + pwm_platform: "ledc" + output1_pin: "4" + output2_pin: "5" + output3_pin: "6" + output4_pin: "7" + hbridge_on_pin: "2" + hbridge_off_pin: "3" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms diff --git a/tests/components/hbridge/test.esp32-idf.yaml b/tests/components/hbridge/test.esp32-idf.yaml index 6a80aaaf3b..dbbfa738c7 100644 --- a/tests/components/hbridge/test.esp32-idf.yaml +++ b/tests/components/hbridge/test.esp32-idf.yaml @@ -1,33 +1,16 @@ -output: - - platform: ledc - pin: 14 - id: gpio_output1 - - platform: ledc - pin: 15 - id: gpio_output2 - - platform: ledc - pin: 12 - id: gpio_output3 - - platform: ledc - pin: 13 - id: gpio_output4 +substitutions: + pwm_platform: "ledc" + output1_pin: "14" + output2_pin: "15" + output3_pin: "12" + output4_pin: "13" + hbridge_on_pin: "4" + hbridge_off_pin: "5" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms + wait_time: 10ms diff --git a/tests/components/hbridge/test.esp8266-ard.yaml b/tests/components/hbridge/test.esp8266-ard.yaml index 4f8915879d..f560da5d38 100644 --- a/tests/components/hbridge/test.esp8266-ard.yaml +++ b/tests/components/hbridge/test.esp8266-ard.yaml @@ -1,33 +1,16 @@ -output: - - platform: esp8266_pwm - pin: 4 - id: gpio_output1 - - platform: esp8266_pwm - pin: 5 - id: gpio_output2 - - platform: esp8266_pwm - pin: 12 - id: gpio_output3 - - platform: esp8266_pwm - pin: 13 - id: gpio_output4 +substitutions: + pwm_platform: "esp8266_pwm" + output1_pin: "4" + output2_pin: "5" + output3_pin: "12" + output4_pin: "13" + hbridge_on_pin: "14" + hbridge_off_pin: "15" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms + wait_time: 10ms diff --git a/tests/components/hbridge/test.rp2040-ard.yaml b/tests/components/hbridge/test.rp2040-ard.yaml index e21b55091d..aa6e290cab 100644 --- a/tests/components/hbridge/test.rp2040-ard.yaml +++ b/tests/components/hbridge/test.rp2040-ard.yaml @@ -1,33 +1,16 @@ -output: - - platform: rp2040_pwm - pin: 4 - id: gpio_output1 - - platform: rp2040_pwm - pin: 5 - id: gpio_output2 - - platform: rp2040_pwm - pin: 6 - id: gpio_output3 - - platform: rp2040_pwm - pin: 7 - id: gpio_output4 +substitutions: + pwm_platform: "rp2040_pwm" + output1_pin: "4" + output2_pin: "5" + output3_pin: "6" + output4_pin: "7" + hbridge_on_pin: "2" + hbridge_off_pin: "3" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + wait_time: 10ms + optimistic: true From b79a3d672782fee909f929da8cb0a516ee52132d Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 2 Dec 2024 11:42:44 -0600 Subject: [PATCH 237/282] [CI] Bump GHA runners to ``ubuntu-24.04`` (#7905) --- .github/workflows/ci.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5af3ec9e9..82e823adde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ concurrency: jobs: common: name: Create common environment - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: cache-key: ${{ steps.cache-key.outputs.key }} steps: @@ -62,7 +62,7 @@ jobs: black: name: Check black - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -83,7 +83,7 @@ jobs: flake8: name: Check flake8 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -104,7 +104,7 @@ jobs: pylint: name: Check pylint - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -125,7 +125,7 @@ jobs: pyupgrade: name: Check pyupgrade - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -146,7 +146,7 @@ jobs: ci-custom: name: Run script/ci-custom - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -225,7 +225,7 @@ jobs: clang-format: name: Check clang-format - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -251,7 +251,7 @@ jobs: clang-tidy: name: ${{ matrix.name }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - black @@ -345,7 +345,7 @@ jobs: if: always() list-components: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common if: github.event_name == 'pull_request' @@ -387,7 +387,7 @@ jobs: test-build-components: name: Component test ${{ matrix.file }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - list-components @@ -421,7 +421,7 @@ jobs: test-build-components-splitter: name: Split components for testing into 20 groups maximum - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - list-components @@ -439,7 +439,7 @@ jobs: test-build-components-split: name: Test split components - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - list-components @@ -483,7 +483,7 @@ jobs: ci-status: name: CI Status - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - black From e08a9cc3a33ab431f6eeef869bdc49c46f17dcf1 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:27:51 +1100 Subject: [PATCH 238/282] [font et. al.] Remove explicit check for pillow installed. (#7891) --- esphome/components/animation/__init__.py | 3 +-- esphome/components/font/__init__.py | 24 ++---------------------- esphome/components/ili9xxx/display.py | 3 +-- esphome/components/image/__init__.py | 3 +-- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 5a308855de..21a82649f0 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -2,7 +2,6 @@ import logging from esphome import automation, core import esphome.codegen as cg -from esphome.components import font import esphome.components.image as espImage from esphome.components.image import ( CONF_USE_TRANSPARENCY, @@ -131,7 +130,7 @@ ANIMATION_SCHEMA = cv.Schema( ) ) -CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA) +CONFIG_SCHEMA = ANIMATION_SCHEMA NEXT_FRAME_SCHEMA = automation.maybe_simple_id( { diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 6fd2d7c310..1b4fe4bff0 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -1,4 +1,3 @@ -from collections.abc import Iterable import functools import hashlib import logging @@ -8,7 +7,6 @@ import re import freetype import glyphsets -from packaging import version import requests from esphome import core, external_files @@ -88,7 +86,7 @@ def flatten(lists) -> list: return list(chain.from_iterable(lists)) -def check_missing_glyphs(file, codepoints: Iterable, warning: bool = False): +def check_missing_glyphs(file, codepoints, warning: bool = False): """ Check that the given font file actually contains the requested glyphs :param file: A Truetype font file @@ -177,24 +175,6 @@ def validate_glyphs(config): return config -def validate_pillow_installed(value): - try: - import PIL - except ImportError as err: - raise cv.Invalid( - "Please install the pillow python package to use this feature. " - '(pip install "pillow==10.4.0")' - ) from err - - if version.parse(PIL.__version__) != version.parse("10.4.0"): - raise cv.Invalid( - "Please update your pillow installation to 10.4.0. " - '(pip install "pillow==10.4.0")' - ) - - return value - - FONT_EXTENSIONS = (".ttf", ".woff", ".otf") @@ -421,7 +401,7 @@ FONT_SCHEMA = cv.Schema( }, ) -CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, validate_glyphs) +CONFIG_SCHEMA = cv.All(FONT_SCHEMA, validate_glyphs) # PIL doesn't provide a consistent interface for both TrueType and bitmap diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 739ad07843..3c9dd2dab9 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -1,6 +1,6 @@ from esphome import core, pins import esphome.codegen as cg -from esphome.components import display, font, spi +from esphome.components import display, spi from esphome.components.display import validate_rotation import esphome.config_validation as cv from esphome.const import ( @@ -147,7 +147,6 @@ def _validate(config): CONFIG_SCHEMA = cv.All( - font.validate_pillow_installed, display.FULL_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ILI9XXXDisplay), diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index 8742540067..4669a3418a 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -10,7 +10,6 @@ import puremagic from esphome import core, external_files import esphome.codegen as cg -from esphome.components import font import esphome.config_validation as cv from esphome.const import ( CONF_DITHER, @@ -233,7 +232,7 @@ IMAGE_SCHEMA = cv.Schema( ) ) -CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA) +CONFIG_SCHEMA = IMAGE_SCHEMA def load_svg_image(file: bytes, resize: tuple[int, int]): From 9c8976be1328b4c5c1d019aa835f53f129262324 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Mon, 2 Dec 2024 16:29:45 -0600 Subject: [PATCH 239/282] [CI] Update clang-tidy to 18.1.3 (#7822) --- .clang-tidy | 29 +++++++++++++++++++++++++++-- .github/workflows/ci.yml | 5 ----- docker/Dockerfile | 8 ++++++-- requirements_dev.txt | 2 +- script/clang-tidy | 8 +++----- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 994416b2f1..8dba033e2d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -7,28 +7,38 @@ Checks: >- -boost-*, -bugprone-easily-swappable-parameters, -bugprone-implicit-widening-of-multiplication-result, + -bugprone-multi-level-implicit-pointer-conversion, -bugprone-narrowing-conversions, -bugprone-signed-char-misuse, + -bugprone-switch-missing-default-case, -cert-dcl50-cpp, -cert-err33-c, -cert-err58-cpp, -cert-oop57-cpp, -cert-str34-c, + -clang-analyzer-optin.core.EnumCastOutOfRange, -clang-analyzer-optin.cplusplus.UninitializedObject, -clang-analyzer-osx.*, -clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-ignored-optimization-argument, + -clang-diagnostic-missing-field-initializers, -clang-diagnostic-shadow-field, -clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-parameter, + -clang-diagnostic-vla-cxx-extension, -concurrency-*, -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-avoid-do-while, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-init-variables, + -cppcoreguidelines-macro-to-enum, -cppcoreguidelines-macro-usage, + -cppcoreguidelines-missing-std-forward, -cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-owning-memory, -cppcoreguidelines-prefer-member-initializer, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-constant-array-index, @@ -40,7 +50,9 @@ Checks: >- -cppcoreguidelines-pro-type-static-cast-downcast, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-rvalue-reference-param-not-moved, -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-use-default-member-init, -cppcoreguidelines-virtual-class-destructor, -fuchsia-multiple-inheritance, -fuchsia-overloaded-operator, @@ -60,21 +72,32 @@ Checks: >- -llvm-include-order, -llvm-qualified-auto, -llvmlibc-*, - -misc-non-private-member-variables-in-classes, + -misc-const-correctness, + -misc-include-cleaner, -misc-no-recursion, + -misc-non-private-member-variables-in-classes, -misc-unused-parameters, + -misc-use-anonymous-namespace, -modernize-avoid-bind, -modernize-avoid-c-arrays, -modernize-concat-nested-namespaces, + -modernize-macro-to-enum, -modernize-return-braced-init-list, + -modernize-type-traits, -modernize-use-auto, + -modernize-use-constraints, -modernize-use-default-member-init, -modernize-use-equals-default, -modernize-use-nodiscard, -modernize-use-nullptr, + -modernize-use-nodiscard, + -modernize-use-nullptr, -modernize-use-trailing-return-type, -mpi-*, -objc-*, + -performance-enum-size, + -readability-avoid-nested-conditional-operator, + -readability-container-contains, -readability-container-data-pointer, -readability-convert-member-functions-to-static, -readability-else-after-return, @@ -84,11 +107,13 @@ Checks: >- -readability-magic-numbers, -readability-make-member-function-const, -readability-named-parameter, + -readability-redundant-casting, + -readability-redundant-inline-specifier, + -readability-redundant-member-init, -readability-redundant-string-init, -readability-uppercase-literal-suffix, -readability-use-anyofallof, WarningsAsErrors: '*' -AnalyzeTemporaryDtors: false FormatStyle: google CheckOptions: - key: google-readability-function-size.StatementThreshold diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82e823adde..e4d3934c59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -314,11 +314,6 @@ jobs: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }} - - name: Install clang-tidy - run: | - sudo apt-get update - sudo apt-get install clang-tidy-14 - - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/gcc.json" diff --git a/docker/Dockerfile b/docker/Dockerfile index c2902a9dd1..c53856d0e8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -209,11 +209,12 @@ ENV \ PLATFORMIO_CORE_DIR=/esphome/.temp/platformio RUN \ - apt-get update \ + curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \ + && echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \ + && apt-get update \ # Use pinned versions so that we get updates with build caching && apt-get install -y --no-install-recommends \ clang-format-13=1:13.0.1-11+b2 \ - clang-tidy-14=1:14.0.6-12 \ patch=2.7.6-7 \ software-properties-common=0.99.30-4.1~deb12u1 \ nano=7.2-1+deb12u1 \ @@ -227,6 +228,9 @@ RUN \ COPY requirements_test.txt / RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ + else \ + # move this up into RUN above after armv7 is retired + apt-get install -y --no-install-recommends clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 ; \ fi; \ pip3 install \ --break-system-packages --no-cache-dir -r /requirements_test.txt diff --git a/requirements_dev.txt b/requirements_dev.txt index eb749a861d..2ef8330215 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,4 @@ # Useful stuff when working in a development environment clang-format==13.0.1 # also change in .pre-commit-config.yaml and Dockerfile when updating -clang-tidy==14.0.6 # When updating clang-tidy, also update Dockerfile +clang-tidy==18.1.3 # When updating clang-tidy, also update Dockerfile yamllint==1.35.1 # also change in .pre-commit-config.yaml when updating diff --git a/script/clang-tidy b/script/clang-tidy index 319fab70a2..5c19f81043 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -63,8 +63,6 @@ def clang_options(idedata): "-Ddeprecated(x)=", # allow to condition code on the presence of clang-tidy "-DCLANG_TIDY", - # (esp-idf) Disable this header because they use asm with registers clang-tidy doesn't know - "-D__XTENSA_API_H__", # (esp-idf) Fix __once_callable in some libstdc++ headers "-D_GLIBCXX_HAVE_TLS", ] @@ -238,7 +236,7 @@ def main(): failed_files = [] try: - executable = get_binary("clang-tidy", 14) + executable = get_binary("clang-tidy", 18) task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): @@ -283,12 +281,12 @@ def main(): print("Applying fixes ...") try: try: - subprocess.call(["clang-apply-replacements-14", tmpdir]) + subprocess.call(["clang-apply-replacements-18", tmpdir]) except FileNotFoundError: subprocess.call(["clang-apply-replacements", tmpdir]) except FileNotFoundError: print( - "Error please install clang-apply-replacements-14 or clang-apply-replacements.\n", + "Error please install clang-apply-replacements-18 or clang-apply-replacements.\n", file=sys.stderr, ) except: From 584dbf2668f888c774be7ad6ce4172d827965e9b Mon Sep 17 00:00:00 2001 From: kbullet <kbullet@users.noreply.github.com> Date: Tue, 3 Dec 2024 06:50:05 +0700 Subject: [PATCH 240/282] MQTT sensors handling of publishing NaN values (#7768) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/mqtt/__init__.py | 4 ++++ esphome/components/mqtt/mqtt_client.cpp | 4 ++++ esphome/components/mqtt/mqtt_client.h | 6 ++++++ esphome/components/mqtt/mqtt_sensor.cpp | 2 ++ esphome/const.py | 1 + tests/components/mqtt/common.yaml | 1 + 6 files changed, 18 insertions(+) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 86d163e61d..2b0d941220 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -49,6 +49,7 @@ from esphome.const import ( CONF_USE_ABBREVIATIONS, CONF_USERNAME, CONF_WILL_MESSAGE, + CONF_PUBLISH_NAN_AS_NONE, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -296,6 +297,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, } ), + cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean, } ), validate_config, @@ -449,6 +451,8 @@ async def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE])) + MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema( { diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 106192c0e3..c7ace505a8 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -608,6 +608,10 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; } void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix) { this->topic_prefix_ = topic_prefix; } const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; } +void MQTTClientComponent::set_publish_nan_as_none(bool publish_nan_as_none) { + this->publish_nan_as_none_ = publish_nan_as_none; +} +bool MQTTClientComponent::is_publish_nan_as_none() const { return this->publish_nan_as_none_; } void MQTTClientComponent::disable_birth_message() { this->birth_message_.topic = ""; this->recalculate_availability_(); diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 7ae3a6c5e8..34eac29464 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -263,6 +263,10 @@ class MQTTClientComponent : public Component { void set_on_connect(mqtt_on_connect_callback_t &&callback); void set_on_disconnect(mqtt_on_disconnect_callback_t &&callback); + // Publish None state instead of NaN for Home Assistant + void set_publish_nan_as_none(bool publish_nan_as_none); + bool is_publish_nan_as_none() const; + protected: void send_device_info_(); @@ -328,6 +332,8 @@ class MQTTClientComponent : public Component { uint32_t connect_begin_; uint32_t last_connected_{0}; optional<MQTTClientDisconnectReason> disconnect_reason_{}; + + bool publish_nan_as_none_{false}; }; extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index fff75a3c00..2cbc291ccf 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -69,6 +69,8 @@ bool MQTTSensorComponent::send_initial_state() { } } bool MQTTSensorComponent::publish_state(float value) { + if (mqtt::global_mqtt_client->is_publish_nan_as_none() && std::isnan(value)) + return this->publish(this->get_state_topic_(), "None"); int8_t accuracy = this->sensor_->get_accuracy_decimals(); return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); } diff --git a/esphome/const.py b/esphome/const.py index d2df83aa43..3d3bfcc244 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -692,6 +692,7 @@ CONF_PRIORITY = "priority" CONF_PROJECT = "project" CONF_PROTOCOL = "protocol" CONF_PUBLISH_INITIAL_STATE = "publish_initial_state" +CONF_PUBLISH_NAN_AS_NONE = "publish_nan_as_none" CONF_PULL_MODE = "pull_mode" CONF_PULLDOWN = "pulldown" CONF_PULLUP = "pullup" diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml index d22fe9579f..a4bdf58809 100644 --- a/tests/components/mqtt/common.yaml +++ b/tests/components/mqtt/common.yaml @@ -60,6 +60,7 @@ mqtt: - mqtt.publish: topic: some/topic payload: Good-bye + publish_nan_as_none: false binary_sensor: - platform: template From dc5942a59b521781279ff568057adf1dc01f11c8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:38:44 +1300 Subject: [PATCH 241/282] [ble] Allow setting shorter name for ble advertisements (#7867) Co-authored-by: Keith Burzinski <kbx81x@gmail.com> --- esphome/components/esp32_ble/__init__.py | 24 +++++++++++++++++-- esphome/components/esp32_ble/ble.cpp | 18 ++++++++++---- esphome/components/esp32_ble/ble.h | 2 ++ .../components/esp32_ble/ble_advertising.cpp | 2 +- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 75cf9d707d..08e30c9247 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -2,8 +2,10 @@ from esphome import automation import esphome.codegen as cg from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant import esphome.config_validation as cv -from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID +from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME from esphome.core import CORE +from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX +import esphome.final_validate as fv DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz", "@Rapsssito"] @@ -50,6 +52,7 @@ TX_POWER_LEVELS = { CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32BLE), + cv.Optional(CONF_NAME): cv.All(cv.string, cv.Length(max=20)), cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( IO_CAPABILITY, lower=True ), @@ -67,7 +70,22 @@ def validate_variant(_): raise cv.Invalid(f"{variant} does not support Bluetooth") -FINAL_VALIDATE_SCHEMA = validate_variant +def final_validation(config): + validate_variant(config) + if (name := config.get(CONF_NAME)) is not None: + full_config = fv.full_config.get() + max_length = 20 + if full_config[CONF_ESPHOME][CONF_NAME_ADD_MAC_SUFFIX]: + max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used + if len(name) > max_length: + raise cv.Invalid( + f"Name '{name}' is too long, maximum length is {max_length} characters" + ) + + return config + + +FINAL_VALIDATE_SCHEMA = final_validation async def to_code(config): @@ -75,6 +93,8 @@ async def to_code(config): cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME])) + if (name := config.get(CONF_NAME)) is not None: + cg.add(var.set_name(name)) await cg.register_component(var, config) if CORE.using_esp_idf: diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 5d08b6e973..d7dcf93f86 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -188,12 +188,20 @@ bool ESP32BLE::ble_setup_() { } } - std::string name = App.get_name(); - if (name.length() > 20) { + std::string name; + if (this->name_.has_value()) { + name = this->name_.value(); if (App.is_name_add_mac_suffix_enabled()) { - name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address - } else { - name = name.substr(0, 20); + name += "-" + get_mac_address().substr(6); + } + } else { + name = App.get_name(); + if (name.length() > 20) { + if (App.is_name_add_mac_suffix_enabled()) { + name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address + } else { + name = name.substr(0, 20); + } } } diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 7c55583852..ed7575f128 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -90,6 +90,7 @@ class ESP32BLE : public Component { void loop() override; void dump_config() override; float get_setup_priority() const override; + void set_name(const std::string &name) { this->name_ = name; } void advertising_start(); void advertising_set_service_data(const std::vector<uint8_t> &data); @@ -131,6 +132,7 @@ class ESP32BLE : public Component { esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; uint32_t advertising_cycle_time_; bool enable_on_boot_; + optional<std::string> name_; }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 1d340c76d9..92b7c60368 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() { esp_err_t err; this->advertising_data_.set_scan_rsp = false; - this->advertising_data_.include_name = !this->scan_response_; + this->advertising_data_.include_name = true; this->advertising_data_.include_txpower = !this->scan_response_; err = esp_ble_gap_config_adv_data(&this->advertising_data_); if (err != ESP_OK) { From c95887a14a61c8a5b9cab909ee9f9493eeef8cc3 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:50:11 +1100 Subject: [PATCH 242/282] [lvgl] Bugfixes (#7896) --- esphome/components/lvgl/defines.py | 2 +- esphome/components/lvgl/lvgl_esphome.h | 3 +++ esphome/components/lvgl/widgets/line.py | 6 ++++++ tests/components/lvgl/lvgl-package.yaml | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 81984637bd..5371f110a6 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -38,7 +38,7 @@ def literal(arg): def call_lambda(lamb: LambdaExpression): expr = lamb.content.strip() if expr.startswith("return") and expr.endswith(";"): - return expr[7:][:-1] + return expr[6:][:-1].strip() return f"{lamb}()" diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 921b7c109f..56413ad77e 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -56,6 +56,9 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) { lv_img_set_src(obj, image->get_lv_img_dsc()); } +inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image) { + lv_disp_set_bg_image(disp, image->get_lv_img_dsc()); +} #endif // USE_LVGL_IMAGE #ifdef USE_LVGL_ANIMIMG inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) { diff --git a/esphome/components/lvgl/widgets/line.py b/esphome/components/lvgl/widgets/line.py index 548dfa8452..0156fb1780 100644 --- a/esphome/components/lvgl/widgets/line.py +++ b/esphome/components/lvgl/widgets/line.py @@ -35,6 +35,11 @@ LINE_SCHEMA = { cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), } +LINE_MODIFY_SCHEMA = { + cv.Optional(CONF_POINTS): cv_point_list, + cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), +} + class LineType(WidgetType): def __init__(self): @@ -43,6 +48,7 @@ class LineType(WidgetType): LvType("lv_line_t"), (CONF_MAIN,), LINE_SCHEMA, + modify_schema=LINE_MODIFY_SCHEMA, ) async def to_code(self, w: Widget, config): diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 81b18c4ff8..6611209756 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -337,7 +337,7 @@ lvgl: id: button_button width: 20% height: 10% - transform_angle: !lambda return 180*100; + transform_angle: !lambda return(180*100); arc_width: !lambda return 4; border_width: !lambda return 6; shadow_ofs_x: !lambda return 6; @@ -581,7 +581,7 @@ lvgl: - 180, 60 - 240, 10 on_click: - - lvgl.widget.update: + - lvgl.line.update: id: lv_line_id line_color: 0xFFFF - lvgl.page.next: From 00ddb0a427b2cae4a0bda8dfc26066e6857d8e6d Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:50:56 +1100 Subject: [PATCH 243/282] [font] Restore correct default glyphs for bitmap fonts (#7907) --- esphome/components/font/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 1b4fe4bff0..c397ba8306 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -373,7 +373,9 @@ def font_file_schema(value): # Default if no glyphs or glyphsets are provided DEFAULT_GLYPHSET = "GF_Latin_Kernel" # default for bitmap fonts -DEFAULT_GLYPHS = ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz<C2><B0>' +DEFAULT_GLYPHS = ( + ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' +) CONF_RAW_GLYPH_ID = "raw_glyph_id" From a37ff2dbd97d17c5b9b522c2a4515fd8311b7003 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 4 Dec 2024 07:48:50 +1100 Subject: [PATCH 244/282] [lvgl] Fix msgbox content (#7912) --- esphome/components/lvgl/widgets/msgbox.py | 3 ++- tests/components/lvgl/lvgl-package.yaml | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/widgets/msgbox.py b/esphome/components/lvgl/widgets/msgbox.py index be0f2100d7..c3393940b6 100644 --- a/esphome/components/lvgl/widgets/msgbox.py +++ b/esphome/components/lvgl/widgets/msgbox.py @@ -29,7 +29,7 @@ from ..lvcode import ( ) from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema from ..types import LV_EVENT, char_ptr, lv_obj_t -from . import Widget, set_obj_properties +from . import Widget, add_widgets, set_obj_properties from .button import button_spec from .buttonmatrix import ( BUTTONMATRIX_BUTTON_SCHEMA, @@ -119,6 +119,7 @@ async def msgbox_to_code(top_layer, conf): button_style = {CONF_ITEMS: button_style} await set_obj_properties(buttonmatrix_widget, button_style) await set_obj_properties(msgbox_widget, conf) + await add_widgets(msgbox_widget, conf) async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action: outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN") if close_button: diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 6611209756..4b7e13db91 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -109,6 +109,10 @@ lvgl: close_button: true title: Messagebox bg_color: 0xffff + widgets: + - label: + text: Hello Msgbox + id: msgbox_label body: text: This is a sample messagebox bg_color: 0x808080 @@ -137,6 +141,9 @@ lvgl: - lvgl.widget.focus: mark - lvgl.widget.redraw: hello_label - lvgl.widget.redraw: + - lvgl.label.update: + id: msgbox_label + text: Unloaded on_all_events: logger.log: format: "Event %s" From d00ec7e544f26b3ec3844f531f4027b9947c8d14 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 3 Dec 2024 17:23:17 -0600 Subject: [PATCH 245/282] [helpers] clang-tidy fix for #7706 (#7909) --- esphome/core/helpers.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 103173095b..4e8caeae99 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -259,10 +259,15 @@ bool random_bytes(uint8_t *data, size_t len) { bool str_equals_case_insensitive(const std::string &a, const std::string &b) { return strcasecmp(a.c_str(), b.c_str()) == 0; } +#if ESP_IDF_VERSION_MAJOR >= 5 +bool str_startswith(const std::string &str, const std::string &start) { return str.starts_with(start); } +bool str_endswith(const std::string &str, const std::string &end) { return str.ends_with(end); } +#else bool str_startswith(const std::string &str, const std::string &start) { return str.rfind(start, 0) == 0; } bool str_endswith(const std::string &str, const std::string &end) { return str.rfind(end) == (str.size() - end.size()); } +#endif std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } From dbed74b50d76cb7a6feb903908018aa3b96b9825 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Tue, 3 Dec 2024 17:26:27 -0600 Subject: [PATCH 246/282] [docker] Fix clang-tidy installation (#7910) --- docker/Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index c53856d0e8..cc05849271 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -220,7 +220,11 @@ RUN \ nano=7.2-1+deb12u1 \ build-essential=12.9 \ python3-dev=3.11.2-1+b1 \ - && rm -rf \ + && if [ "$TARGETARCH$TARGETVARIANT" != "armv7" ]; then \ + # move this up after armv7 is retired + apt-get install -y --no-install-recommends clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 ; \ + fi; \ + rm -rf \ /tmp/* \ /var/{cache,log}/* \ /var/lib/apt/lists/* @@ -228,9 +232,6 @@ RUN \ COPY requirements_test.txt / RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ - else \ - # move this up into RUN above after armv7 is retired - apt-get install -y --no-install-recommends clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 ; \ fi; \ pip3 install \ --break-system-packages --no-cache-dir -r /requirements_test.txt From 79478cdb8a2731c23861da1c607e332117359179 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:13:07 +1100 Subject: [PATCH 247/282] [sntp] Resolve warnings from ESP-IDF 5.x (#7913) --- esphome/components/sntp/sntp_component.cpp | 34 +++++++++------------- esphome/components/sntp/sntp_component.h | 15 ++++------ esphome/components/sntp/time.py | 11 +++---- 3 files changed, 24 insertions(+), 36 deletions(-) diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 4ded98d483..21add1451d 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -9,11 +9,6 @@ #include "lwip/apps/sntp.h" #endif -// Yes, the server names are leaked, but that's fine. -#ifdef CLANG_TIDY -#define strdup(x) (const_cast<char *>(x)) -#endif - namespace esphome { namespace sntp { @@ -26,30 +21,29 @@ void SNTPComponent::setup() { esp_sntp_stop(); } esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL); + size_t i = 0; + for (auto &server : this->servers_) { + esp_sntp_setservername(i++, server.c_str()); + } + esp_sntp_set_sync_interval(this->get_update_interval()); + esp_sntp_init(); #else sntp_stop(); sntp_setoperatingmode(SNTP_OPMODE_POLL); -#endif - sntp_setservername(0, strdup(this->server_1_.c_str())); - if (!this->server_2_.empty()) { - sntp_setservername(1, strdup(this->server_2_.c_str())); + size_t i = 0; + for (auto &server : this->servers_) { + sntp_setservername(i++, server.c_str()); } - if (!this->server_3_.empty()) { - sntp_setservername(2, strdup(this->server_3_.c_str())); - } -#ifdef USE_ESP_IDF - esp_sntp_set_sync_interval(this->get_update_interval()); -#endif - sntp_init(); +#endif } void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, "SNTP Time:"); - ESP_LOGCONFIG(TAG, " Server 1: '%s'", this->server_1_.c_str()); - ESP_LOGCONFIG(TAG, " Server 2: '%s'", this->server_2_.c_str()); - ESP_LOGCONFIG(TAG, " Server 3: '%s'", this->server_3_.c_str()); - ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); + size_t i = 0; + for (auto &server : this->servers_) { + ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server.c_str()); + } } void SNTPComponent::update() { #if !defined(USE_ESP_IDF) diff --git a/esphome/components/sntp/sntp_component.h b/esphome/components/sntp/sntp_component.h index 987dd23a19..a4e8267383 100644 --- a/esphome/components/sntp/sntp_component.h +++ b/esphome/components/sntp/sntp_component.h @@ -14,23 +14,20 @@ namespace sntp { /// \see https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html class SNTPComponent : public time::RealTimeClock { public: + SNTPComponent(const std::vector<std::string> &servers) : servers_(servers) {} + + // Note: set_servers() has been removed and replaced by a constructor - calling set_servers after setup would + // have had no effect anyway, and making the strings immutable avoids the need to strdup their contents. + void setup() override; void dump_config() override; - /// Change the servers used by SNTP for timekeeping - void set_servers(const std::string &server_1, const std::string &server_2, const std::string &server_3) { - this->server_1_ = server_1; - this->server_2_ = server_2; - this->server_3_ = server_3; - } float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; } void update() override; void loop() override; protected: - std::string server_1_; - std::string server_2_; - std::string server_3_; + std::vector<std::string> servers_; bool has_time_{false}; }; diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index 7cc82e3dff..6f883d5bed 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -1,16 +1,16 @@ +import esphome.codegen as cg from esphome.components import time as time_ import esphome.config_validation as cv -import esphome.codegen as cg -from esphome.core import CORE from esphome.const import ( CONF_ID, CONF_SERVERS, + PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_RTL87XX, - PLATFORM_BK72XX, ) +from esphome.core import CORE DEPENDENCIES = ["network"] sntp_ns = cg.esphome_ns.namespace("sntp") @@ -40,11 +40,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - servers = config[CONF_SERVERS] - servers += [""] * (3 - len(servers)) - cg.add(var.set_servers(*servers)) + var = cg.new_Pvariable(config[CONF_ID], servers) await cg.register_component(var, config) await time_.register_time(var, config) From 016fac249649b475a918eb2c17f9daa0ad399485 Mon Sep 17 00:00:00 2001 From: mikosoft83 <63317931+mikosoft83@users.noreply.github.com> Date: Wed, 4 Dec 2024 01:18:00 +0100 Subject: [PATCH 248/282] Add strftime variant with background color (#7714) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/display/display.cpp | 14 +++++++++----- esphome/components/display/display.h | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index 145a4f5278..1d996bd59b 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -662,20 +662,24 @@ void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) this->trigger(from, to); } -void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { +void Display::strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, + ESPTime time) { char buffer[64]; size_t ret = time.strftime(buffer, sizeof(buffer), format); if (ret > 0) - this->print(x, y, font, color, align, buffer); + this->print(x, y, font, color, align, buffer, background); +} +void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { + this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); } void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) { - this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time); + this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); } void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) { - this->strftime(x, y, font, COLOR_ON, align, format, time); + this->strftime(x, y, font, COLOR_ON, COLOR_OFF, align, format, time); } void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) { - this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time); + this->strftime(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, time); } void Display::start_clipping(Rect rect) { diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 54e897cdec..43da08f4ac 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -437,6 +437,20 @@ class Display : public PollingComponent { */ void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6))); + /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. + * + * @param x The x coordinate of the text alignment anchor point. + * @param y The y coordinate of the text alignment anchor point. + * @param font The font to draw the text with. + * @param color The color to draw the text with. + * @param background The background color to draw the text with. + * @param align The alignment of the text. + * @param format The format to use. + * @param ... The arguments to use for the text formatting. + */ + void strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, + ESPTime time) __attribute__((format(strftime, 8, 0))); + /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. * * @param x The x coordinate of the text alignment anchor point. From 472402745d2f3821802bbdadf2ebc45d2ee6ccf5 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt <kevin.ahrendt@nabucasa.com> Date: Wed, 4 Dec 2024 16:18:14 -0500 Subject: [PATCH 249/282] [i2s_audio] Bugfix: Follow configured bits per sample (#7916) --- .../i2s_audio/speaker/i2s_audio_speaker.cpp | 123 +++++++++--------- .../i2s_audio/speaker/i2s_audio_speaker.h | 25 ++-- 2 files changed, 70 insertions(+), 78 deletions(-) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 53b3cc8dc0..194cc06a60 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -33,14 +33,15 @@ enum SpeakerEventGroupBits : uint32_t { STATE_RUNNING = (1 << 11), STATE_STOPPING = (1 << 12), STATE_STOPPED = (1 << 13), - ERR_INVALID_FORMAT = (1 << 14), - ERR_TASK_FAILED_TO_START = (1 << 15), - ERR_ESP_INVALID_STATE = (1 << 16), + ERR_TASK_FAILED_TO_START = (1 << 14), + ERR_ESP_INVALID_STATE = (1 << 15), + ERR_ESP_NOT_SUPPORTED = (1 << 16), ERR_ESP_INVALID_ARG = (1 << 17), ERR_ESP_INVALID_SIZE = (1 << 18), ERR_ESP_NO_MEM = (1 << 19), ERR_ESP_FAIL = (1 << 20), - ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | ERR_ESP_NO_MEM | ERR_ESP_FAIL, + ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_NOT_SUPPORTED | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | + ERR_ESP_NO_MEM | ERR_ESP_FAIL, ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits }; @@ -55,6 +56,8 @@ static esp_err_t err_bit_to_esp_err(uint32_t bit) { return ESP_ERR_INVALID_SIZE; case SpeakerEventGroupBits::ERR_ESP_NO_MEM: return ESP_ERR_NO_MEM; + case SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED: + return ESP_ERR_NOT_SUPPORTED; default: return ESP_FAIL; } @@ -135,19 +138,19 @@ void I2SAudioSpeaker::loop() { xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); } - if (event_group_bits & SpeakerEventGroupBits::ERR_INVALID_FORMAT) { + if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { + uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; + ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); + this->status_set_warning(); + } + + if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) { this->status_set_error("Failed to adjust I2S bus to match the incoming audio"); ESP_LOGE(TAG, "Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8, this->audio_stream_info_.sample_rate, this->audio_stream_info_.channels, this->audio_stream_info_.bits_per_sample); } - - if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { - uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; - ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); - this->status_set_warning(); - } } void I2SAudioSpeaker::set_volume(float volume) { @@ -236,13 +239,14 @@ void I2SAudioSpeaker::speaker_task(void *params) { xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING); audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_; - const ssize_t bytes_per_sample = audio_stream_info.get_bytes_per_sample(); - const uint8_t number_of_channels = audio_stream_info.channels; - const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * this_speaker->sample_rate_ / 1000 * - bytes_per_sample * number_of_channels; - const size_t ring_buffer_size = - this_speaker->buffer_duration_ms_ * this_speaker->sample_rate_ / 1000 * bytes_per_sample * number_of_channels; + const uint32_t bytes_per_ms = + audio_stream_info.channels * audio_stream_info.get_bytes_per_sample() * audio_stream_info.sample_rate / 1000; + + const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * bytes_per_ms; + + // Ensure ring buffer is at least as large as the total size of the DMA buffers + const size_t ring_buffer_size = std::min(dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { // Failed to allocate buffers @@ -250,14 +254,7 @@ void I2SAudioSpeaker::speaker_task(void *params) { this_speaker->delete_task_(dma_buffers_size); } - if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) { - // Failed to start I2S driver - this_speaker->delete_task_(dma_buffers_size); - } - - if (!this_speaker->send_esp_err_to_event_group_(this_speaker->reconfigure_i2s_stream_info_(audio_stream_info))) { - // Successfully set the I2S stream info, ready to write audio data to the I2S port - + if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) { xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING); bool stop_gracefully = false; @@ -275,6 +272,12 @@ void I2SAudioSpeaker::speaker_task(void *params) { stop_gracefully = true; } + if (this_speaker->audio_stream_info_ != audio_stream_info) { + // Audio stream info has changed, stop the speaker task so it will restart with the proper settings. + + break; + } + i2s_event_t i2s_event; while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) { if (i2s_event.type == I2S_EVENT_TX_Q_OVF) { @@ -316,17 +319,14 @@ void I2SAudioSpeaker::speaker_task(void *params) { } } } - } else { - // Couldn't configure the I2S port to be compatible with the incoming audio - xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_INVALID_FORMAT); + + xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); + + i2s_driver_uninstall(this_speaker->parent_->get_port()); + + this_speaker->parent_->unlock(); } - i2s_zero_dma_buffer(this_speaker->parent_->get_port()); - xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); - - i2s_driver_uninstall(this_speaker->parent_->get_port()); - - this_speaker->parent_->unlock(); this_speaker->delete_task_(dma_buffers_size); } @@ -382,6 +382,9 @@ bool I2SAudioSpeaker::send_esp_err_to_event_group_(esp_err_t err) { case ESP_ERR_NO_MEM: xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM); return true; + case ESP_ERR_NOT_SUPPORTED: + xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED); + return true; default: xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL); return true; @@ -411,18 +414,40 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin return ESP_OK; } -esp_err_t I2SAudioSpeaker::start_i2s_driver_() { +esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info) { + if ((this->i2s_mode_ & I2S_MODE_SLAVE) && (this->sample_rate_ != audio_stream_info.sample_rate)) { // NOLINT + // Can't reconfigure I2S bus, so the sample rate must match the configured value + return ESP_ERR_NOT_SUPPORTED; + } + + if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) { + // Currently can't handle the case when the incoming audio has more bits per sample than the configured value + return ESP_ERR_NOT_SUPPORTED; + } + if (!this->parent_->try_lock()) { return ESP_ERR_INVALID_STATE; } + i2s_channel_fmt_t channel = this->channel_; + + if (audio_stream_info.channels == 1) { + if (this->channel_ == I2S_CHANNEL_FMT_ONLY_LEFT) { + channel = I2S_CHANNEL_FMT_ONLY_LEFT; + } else { + channel = I2S_CHANNEL_FMT_ONLY_RIGHT; + } + } else if (audio_stream_info.channels == 2) { + channel = I2S_CHANNEL_FMT_RIGHT_LEFT; + } + int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000; i2s_driver_config_t config = { .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX), - .sample_rate = this->sample_rate_, + .sample_rate = audio_stream_info.sample_rate, .bits_per_sample = this->bits_per_sample_, - .channel_format = this->channel_, + .channel_format = channel, .communication_format = this->i2s_comm_fmt_, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = DMA_BUFFERS_COUNT, @@ -477,30 +502,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() { return err; } -esp_err_t I2SAudioSpeaker::reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info) { - if (this->i2s_mode_ & I2S_MODE_MASTER) { - // ESP controls for the the I2S bus, so adjust the sample rate and bits per sample to match the incoming audio - this->sample_rate_ = audio_stream_info.sample_rate; - this->bits_per_sample_ = (i2s_bits_per_sample_t) audio_stream_info.bits_per_sample; - } else if (this->sample_rate_ != audio_stream_info.sample_rate) { - // Can't reconfigure I2S bus, so the sample rate must match the configured value - return ESP_ERR_INVALID_ARG; - } - - if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) { - // Currently can't handle the case when the incoming audio has more bits per sample than the configured value - return ESP_ERR_INVALID_ARG; - } - - if (audio_stream_info.channels == 1) { - return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_MONO); - } else if (audio_stream_info.channels == 2) { - return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_STEREO); - } - - return ESP_ERR_INVALID_ARG; -} - void I2SAudioSpeaker::delete_task_(size_t buffer_size) { this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index 2b90f39399..d706deb0f4 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -91,24 +91,15 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size); /// @brief Starts the ESP32 I2S driver. - /// Attempts to lock the I2S port, starts the I2S driver, and sets the data out pin. If it fails, it will unlock - /// the I2S port and uninstall the driver, if necessary. - /// @return ESP_ERR_INVALID_STATE if the I2S port is already locked. - /// ESP_ERR_INVALID_ARG if installing the driver or setting the data out pin fails due to a parameter error. + /// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out + /// pin. If it fails, it will unlock the I2S port and uninstall the driver, if necessary. + /// @param audio_stream_info Stream information for the I2S driver. + /// @return ESP_ERR_NOT_ALLOWED if the I2S port can't play the incoming audio stream. + /// ESP_ERR_INVALID_STATE if the I2S port is already locked. + /// ESP_ERR_INVALID_ARG if nstalling the driver or setting the data outpin fails due to a parameter error. /// ESP_ERR_NO_MEM if the driver fails to install due to a memory allocation error. - /// ESP_FAIL if setting the data out pin fails due to an IO error - /// ESP_OK if successful - esp_err_t start_i2s_driver_(); - - /// @brief Adjusts the I2S driver configuration to match the incoming audio stream. - /// Modifies I2S driver's sample rate, bits per sample, and number of channel settings. If the I2S is in secondary - /// mode, it only modifies the number of channels. - /// @param audio_stream_info Describes the incoming audio stream - /// @return ESP_ERR_INVALID_ARG if there is a parameter error, if there is more than 2 channels in the stream, or if - /// the audio settings are incompatible with the configuration. - /// ESP_ERR_NO_MEM if the driver fails to reconfigure due to a memory allocation error. - /// ESP_OK if successful. - esp_err_t reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info); + /// ESP_FAIL if setting the data out pin fails due to an IO error ESP_OK if successful + esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info); /// @brief Deletes the speaker's task. /// Deallocates the data_buffer_ and audio_ring_buffer_, if necessary, and deletes the task. Should only be called by From d429aa8bb8d9c8375996c0ab2335d66132cb3472 Mon Sep 17 00:00:00 2001 From: Pavlo Dudnytskyi <paveldudn@gmail.com> Date: Wed, 4 Dec 2024 22:43:00 +0100 Subject: [PATCH 250/282] Haier AC quiet mode switch fix (#7902) --- esphome/components/haier/hon_climate.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index 85e9cf37b9..c95a87223d 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -35,7 +35,9 @@ void HonClimate::set_beeper_state(bool state) { if (state != this->settings_.beeper_state) { this->settings_.beeper_state = state; #ifdef USE_SWITCH - this->beeper_switch_->publish_state(state); + if (this->beeper_switch_ != nullptr) { + this->beeper_switch_->publish_state(state); + } #endif this->hon_rtc_.save(&this->settings_); } @@ -45,10 +47,17 @@ bool HonClimate::get_beeper_state() const { return this->settings_.beeper_state; void HonClimate::set_quiet_mode_state(bool state) { if (state != this->get_quiet_mode_state()) { - this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; + if ((this->mode != ClimateMode::CLIMATE_MODE_OFF) && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) { + this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; + this->force_send_control_ = true; + } else { + this->quiet_mode_state_ = state ? SwitchState::ON : SwitchState::OFF; + } this->settings_.quiet_mode_state = state; #ifdef USE_SWITCH - this->quiet_mode_switch_->publish_state(state); + if (this->quiet_mode_switch_ != nullptr) { + this->quiet_mode_switch_->publish_state(state); + } #endif this->hon_rtc_.save(&this->settings_); } @@ -509,7 +518,7 @@ void HonClimate::initialization() { } this->current_vertical_swing_ = this->settings_.last_vertiacal_swing; this->current_horizontal_swing_ = this->settings_.last_horizontal_swing; - this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; + this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::ON : SwitchState::OFF; } haier_protocol::HaierMessage HonClimate::get_control_message() { @@ -932,7 +941,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * if (this->mode == CLIMATE_MODE_OFF) { // AC just turned on from remote need to turn off display this->force_send_control_ = true; - } else if ((((uint8_t) this->health_mode_) & 0b10) == 0) { + } else if ((((uint8_t) this->display_status_) & 0b10) == 0) { this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF; } } @@ -1004,6 +1013,11 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * if (new_quiet_mode != this->get_quiet_mode_state()) { this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF; this->settings_.quiet_mode_state = new_quiet_mode; +#ifdef USE_SWITCH + if (this->quiet_mode_switch_ != nullptr) { + this->quiet_mode_switch_->publish_state(new_quiet_mode); + } +#endif // USE_SWITCH this->hon_rtc_.save(&this->settings_); } } From 4e839d42d050f3d6c83e045512fcf5bfab8d27ac Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski <basti@linkt.de> Date: Wed, 4 Dec 2024 22:44:34 +0100 Subject: [PATCH 251/282] [CI] Update clang-tidy to 18.1.8 (#7915) --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 2ef8330215..1a98a15ab2 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,4 @@ # Useful stuff when working in a development environment clang-format==13.0.1 # also change in .pre-commit-config.yaml and Dockerfile when updating -clang-tidy==18.1.3 # When updating clang-tidy, also update Dockerfile +clang-tidy==18.1.8 # When updating clang-tidy, also update Dockerfile yamllint==1.35.1 # also change in .pre-commit-config.yaml when updating From ece72c6b189e130bdc8a7dee7920abaa04cb61d9 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Wed, 4 Dec 2024 21:03:38 -0600 Subject: [PATCH 252/282] [i2s_audio] Speaker type fix (#7919) --- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 194cc06a60..d2a582c2cc 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -246,7 +246,8 @@ void I2SAudioSpeaker::speaker_task(void *params) { const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * bytes_per_ms; // Ensure ring buffer is at least as large as the total size of the DMA buffers - const size_t ring_buffer_size = std::min(dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); + const size_t ring_buffer_size = + std::min((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { // Failed to allocate buffers From f3cc1e541a904d5ef44b431300e5457e4d782dd5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:44:59 +1300 Subject: [PATCH 253/282] [esp32_rmt_led_strip] Add ``COMPONENT_SCHEMA`` extending (#7918) --- esphome/components/esp32_rmt_led_strip/light.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index 79f339e248..976f70e858 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -1,9 +1,9 @@ from dataclasses import dataclass -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import esp32_rmt, light +import esphome.config_validation as cv from esphome.const import ( CONF_CHIPSET, CONF_IS_RGBW, @@ -103,7 +103,7 @@ CONFIG_SCHEMA = cv.All( default="0 us", ): cv.positive_time_period_nanoseconds, } - ), + ).extend(cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), ) From acc8d24a325b34cd4af035bb2e26b26f1d6e4f83 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Thu, 5 Dec 2024 02:39:30 -0600 Subject: [PATCH 254/282] [esp32] Use pioarduino + IDF 5.1.5 as default for IDF builds (#7706) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .clang-tidy | 1 + esphome/components/esp32/__init__.py | 106 ++++++++++++++++-- platformio.ini | 4 +- ...build_components_base.esp32-c3-idf-51.yaml | 19 ---- .../build_components_base.esp32-idf-51.yaml | 19 ---- ...build_components_base.esp32-s2-idf-51.yaml | 20 ---- ...build_components_base.esp32-s3-idf-51.yaml | 20 ---- 7 files changed, 97 insertions(+), 92 deletions(-) delete mode 100644 tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml delete mode 100644 tests/test_build_components/build_components_base.esp32-idf-51.yaml delete mode 100644 tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml delete mode 100644 tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml diff --git a/.clang-tidy b/.clang-tidy index 8dba033e2d..5878028f48 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -21,6 +21,7 @@ Checks: >- -clang-analyzer-osx.*, -clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor, + -clang-diagnostic-deprecated-declarations, -clang-diagnostic-ignored-optimization-argument, -clang-diagnostic-missing-field-initializers, -clang-diagnostic-shadow-field, diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 61fbb53e3a..580c3fc081 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -65,6 +65,8 @@ _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["preferences"] +CONF_RELEASE = "release" + def set_core_data(config): CORE.data[KEY_ESP32] = {} @@ -216,11 +218,17 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" -def _format_framework_espidf_version(ver: cv.Version) -> str: +def _format_framework_espidf_version( + ver: cv.Version, release: str, for_platformio: bool +) -> str: # format the given arduino (https://github.com/espressif/esp-idf/releases) version to # a PIO platformio/framework-espidf value # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf - return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + if for_platformio: + return f"platformio/framework-espidf@~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + if release: + return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip" + return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip" # NOTE: Keep this in mind when updating the recommended version: @@ -241,11 +249,33 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0) # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf -RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 8) +RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 5) # The platformio/espressif32 version to use for esp-idf frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ESP_IDF_PLATFORM_VERSION = cv.Version(5, 4, 0) +ESP_IDF_PLATFORM_VERSION = cv.Version(51, 3, 7) + +# List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions +SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ + cv.Version(5, 3, 1), + cv.Version(5, 3, 0), + cv.Version(5, 2, 2), + cv.Version(5, 2, 1), + cv.Version(5, 1, 2), + cv.Version(5, 1, 1), + cv.Version(5, 1, 0), + cv.Version(5, 0, 2), + cv.Version(5, 0, 1), + cv.Version(5, 0, 0), +] + +# pioarduino versions that don't require a release number +# List based on https://github.com/pioarduino/esp-idf/releases +SUPPORTED_PIOARDUINO_ESP_IDF_5X = [ + cv.Version(5, 3, 1), + cv.Version(5, 3, 0), + cv.Version(5, 1, 5), +] def _arduino_check_versions(value): @@ -286,8 +316,8 @@ def _arduino_check_versions(value): def _esp_idf_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(5, 1, 2), "https://github.com/espressif/esp-idf.git"), - "latest": (cv.Version(5, 1, 2), None), + "dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"), + "latest": (cv.Version(5, 1, 5), None), "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), } @@ -305,13 +335,51 @@ def _esp_idf_check_versions(value): if version < cv.Version(4, 0, 0): raise cv.Invalid("Only ESP-IDF 4.0+ is supported.") - value[CONF_VERSION] = str(version) - value[CONF_SOURCE] = source or _format_framework_espidf_version(version) + # flag this for later *before* we set value[CONF_PLATFORM_VERSION] below + has_platform_ver = CONF_PLATFORM_VERSION in value value[CONF_PLATFORM_VERSION] = value.get( CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION)) ) + if ( + (is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION])) + and version.major >= 5 + and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X + ): + raise cv.Invalid( + f"ESP-IDF {str(version)} not supported by platformio/espressif32" + ) + + if ( + version.major < 5 + or ( + version in SUPPORTED_PLATFORMIO_ESP_IDF_5X + and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X + ) + ) and not has_platform_ver: + raise cv.Invalid( + f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'" + ) + + if ( + not is_platformio + and CONF_RELEASE not in value + and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X + ): + raise cv.Invalid( + f"ESP-IDF {value[CONF_VERSION]} is not available with pioarduino; you may need to specify '{CONF_RELEASE}'" + ) + + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_espidf_version( + version, value.get(CONF_RELEASE, None), is_platformio + ) + + if value[CONF_SOURCE].startswith("http"): + # prefix is necessary or platformio will complain with a cryptic error + value[CONF_SOURCE] = f"framework-espidf@{value[CONF_SOURCE]}" + if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: _LOGGER.warning( "The selected ESP-IDF framework version is not the recommended one. " @@ -323,6 +391,12 @@ def _esp_idf_check_versions(value): def _parse_platform_version(value): try: + ver = cv.Version.parse(cv.version_number(value)) + if ver.major >= 50: # a pioarduino version + if "-" in value: + # maybe a release candidate?...definitely not our default, just use it as-is... + return f"https://github.com/pioarduino/platform-espressif32.git#{value}" + return f"https://github.com/pioarduino/platform-espressif32.git#{ver.major}.{ver.minor:02d}.{ver.patch:02d}" # if platform version is a valid version constraint, prefix the default package cv.platformio_version_constraint(value) return f"platformio/espressif32@{value}" @@ -330,6 +404,14 @@ def _parse_platform_version(value): return value +def _platform_is_platformio(value): + try: + ver = cv.Version.parse(cv.version_number(value)) + return ver.major < 50 + except cv.Invalid: + return "platformio" in value + + def _detect_variant(value): board = value[CONF_BOARD] if board in BOARDS: @@ -412,6 +494,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_RELEASE): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { @@ -515,10 +598,9 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") cg.add_build_flag("-Wno-nonnull-compare") - cg.add_platformio_option( - "platform_packages", - [f"platformio/framework-espidf@{conf[CONF_SOURCE]}"], - ) + + cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) + # platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years # This is espressif's own published version which is more up to date. cg.add_platformio_option( diff --git a/platformio.ini b/platformio.ini index 04afc059af..b9b80e933f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -137,9 +137,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf -platform = platformio/espressif32@5.4.0 +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.06/platform-espressif32.zip platform_packages = - platformio/framework-espidf@~3.40408.0 + pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.1.5/esp-idf-v5.1.5.zip framework = espidf lib_deps = diff --git a/tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml deleted file mode 100644 index eb5b23a4ec..0000000000 --- a/tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml +++ /dev/null @@ -1,19 +0,0 @@ -esphome: - name: componenttestesp32c3idf51 - friendly_name: $component_name - -esp32: - board: lolin_c3_mini - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-idf-51.yaml deleted file mode 100644 index b5e3dd6d83..0000000000 --- a/tests/test_build_components/build_components_base.esp32-idf-51.yaml +++ /dev/null @@ -1,19 +0,0 @@ -esphome: - name: componenttestesp32idf51 - friendly_name: $component_name - -esp32: - board: nodemcu-32s - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml deleted file mode 100644 index 11b077509e..0000000000 --- a/tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml +++ /dev/null @@ -1,20 +0,0 @@ -esphome: - name: componenttestesp32s2idf51 - friendly_name: $component_name - -esp32: - board: esp32-s2-saola-1 - variant: ESP32S2 - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml deleted file mode 100644 index 4357b3581b..0000000000 --- a/tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml +++ /dev/null @@ -1,20 +0,0 @@ -esphome: - name: componenttestesp32s3idf51 - friendly_name: $component_name - -esp32: - board: esp32s3box - variant: ESP32S3 - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file From 555bdac604147b1f918c6c11609589ac88f5c368 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:11:31 +1300 Subject: [PATCH 255/282] Bump actions/cache from 4.1.2 to 4.2.0 (#7926) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4d3934c59..6ce4159da0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.1.2 + uses: actions/cache@v4.2.0 with: path: venv # yamllint disable-line rule:line-length @@ -302,14 +302,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@v4.1.2 + uses: actions/cache@v4.2.0 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@v4.1.2 + uses: actions/cache/restore@v4.2.0 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }} From d3a71a1d45fa3958fee3aac4e1afacb5408e726a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:11:46 +1300 Subject: [PATCH 256/282] Bump actions/cache from 4.1.2 to 4.2.0 in /.github/actions/restore-python (#7925) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/restore-python/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index 06c54578f5..6b87cf0170 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -22,7 +22,7 @@ runs: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.1.2 + uses: actions/cache/restore@v4.2.0 with: path: venv # yamllint disable-line rule:line-length From 4e3195b474e5ffeda909e9d8933f8611da758779 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:16:59 +1100 Subject: [PATCH 257/282] [esp32] Fix crash with empty `platformio_options:` value (#7920) --- esphome/components/esp32/__init__.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 580c3fc081..b0bde75451 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -437,24 +437,20 @@ def _detect_variant(value): def final_validate(config): - if CONF_PLATFORMIO_OPTIONS not in fv.full_config.get()[CONF_ESPHOME]: + if not ( + pio_options := fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS) + ): + # Not specified or empty return config pio_flash_size_key = "board_upload.flash_size" pio_partitions_key = "board_build.partitions" - if ( - CONF_PARTITIONS in config - and pio_partitions_key - in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS] - ): + if CONF_PARTITIONS in config and pio_partitions_key in pio_options: raise cv.Invalid( f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32" ) - if ( - pio_flash_size_key - in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS] - ): + if pio_flash_size_key in pio_options: raise cv.Invalid( f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only" ) From bfd75d736c3693f7b055e890bf9a36d51543f646 Mon Sep 17 00:00:00 2001 From: alorente <gitmaster@passific.fr> Date: Fri, 6 Dec 2024 01:21:14 +0100 Subject: [PATCH 258/282] Add OCI Image Labels (#7924) --- docker/Dockerfile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index cc05849271..1754d951e5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -196,8 +196,16 @@ LABEL \ io.hass.name="ESPHome" \ io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ io.hass.type="addon" \ - io.hass.version="${BUILD_VERSION}" + io.hass.version="${BUILD_VERSION}" \ # io.hass.arch is inherited from addon-debian-base + org.opencontainers.image.authors="The ESPHome Authors" \ + org.opencontainers.image.title="ESPHome" \ + org.opencontainers.image.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + org.opencontainers.image.url="https://esphome.io/" \ + org.opencontainers.image.documentation="https://esphome.io/" \ + org.opencontainers.image.source="https://github.com/esphome/esphome" \ + org.opencontainers.image.licenses="ESPHome" \ + org.opencontainers.image.version=${BUILD_VERSION} From 58123845ff5f5a61139b9144b266fc1d32136926 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:11:11 +1300 Subject: [PATCH 259/282] Move docker oci labels to correct image (#7927) --- docker/Dockerfile | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1754d951e5..947e410fe1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -163,6 +163,18 @@ ENTRYPOINT ["/entrypoint.sh"] CMD ["dashboard", "/config"] +ARG BUILD_VERSION=dev + +# Labels +LABEL \ + org.opencontainers.image.authors="The ESPHome Authors" \ + org.opencontainers.image.title="ESPHome" \ + org.opencontainers.image.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + org.opencontainers.image.url="https://esphome.io/" \ + org.opencontainers.image.documentation="https://esphome.io/" \ + org.opencontainers.image.source="https://github.com/esphome/esphome" \ + org.opencontainers.image.licenses="ESPHome" \ + org.opencontainers.image.version=${BUILD_VERSION} # ======================= hassio-type image ======================= @@ -196,16 +208,8 @@ LABEL \ io.hass.name="ESPHome" \ io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ io.hass.type="addon" \ - io.hass.version="${BUILD_VERSION}" \ + io.hass.version="${BUILD_VERSION}" # io.hass.arch is inherited from addon-debian-base - org.opencontainers.image.authors="The ESPHome Authors" \ - org.opencontainers.image.title="ESPHome" \ - org.opencontainers.image.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ - org.opencontainers.image.url="https://esphome.io/" \ - org.opencontainers.image.documentation="https://esphome.io/" \ - org.opencontainers.image.source="https://github.com/esphome/esphome" \ - org.opencontainers.image.licenses="ESPHome" \ - org.opencontainers.image.version=${BUILD_VERSION} From b0e3ac01e83258329244ca0efc557c2225c7d790 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:24:20 +1300 Subject: [PATCH 260/282] Update project description (#7928) --- docker/Dockerfile | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 947e410fe1..0bb558d35e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -169,7 +169,7 @@ ARG BUILD_VERSION=dev LABEL \ org.opencontainers.image.authors="The ESPHome Authors" \ org.opencontainers.image.title="ESPHome" \ - org.opencontainers.image.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \ org.opencontainers.image.url="https://esphome.io/" \ org.opencontainers.image.documentation="https://esphome.io/" \ org.opencontainers.image.source="https://github.com/esphome/esphome" \ @@ -206,7 +206,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ # Labels LABEL \ io.hass.name="ESPHome" \ - io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \ io.hass.type="addon" \ io.hass.version="${BUILD_VERSION}" # io.hass.arch is inherited from addon-debian-base diff --git a/pyproject.toml b/pyproject.toml index cfc279845f..7789f6d645 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "esphome" license = {text = "MIT"} -description = "Make creating custom firmwares for ESP32/ESP8266 super easy." +description = "ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems." readme = "README.md" authors = [ {name = "The ESPHome Authors", email = "esphome@nabucasa.com"} From 749a5e3348e5da84b8a199ef49ea5122d993d6c1 Mon Sep 17 00:00:00 2001 From: Keith Burzinski <kbx81x@gmail.com> Date: Thu, 5 Dec 2024 20:41:53 -0600 Subject: [PATCH 261/282] [modbus] More clean-up (#7921) --- .../components/modbus_controller/modbus_controller.cpp | 10 +++++----- .../modbus_controller/switch/modbus_switch.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 641ba68223..3f487abc94 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -152,9 +152,9 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t } SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const { - auto reg_it = find_if(begin(this->register_ranges_), end(this->register_ranges_), [=](RegisterRange const &r) { - return (r.start_address == start_address && r.register_type == register_type); - }); + auto reg_it = std::find_if( + std::begin(this->register_ranges_), std::end(this->register_ranges_), + [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); }); if (reg_it == this->register_ranges_.end()) { ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address); @@ -375,12 +375,12 @@ void ModbusController::loop() { if (!this->incoming_queue_.empty()) { auto &message = this->incoming_queue_.front(); if (message != nullptr) - process_modbus_data_(message.get()); + this->process_modbus_data_(message.get()); this->incoming_queue_.pop(); } else { // all messages processed send pending commands - send_next_command_(); + this->send_next_command_(); } } diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index ec29eca7f8..b729e2659f 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -97,7 +97,7 @@ void ModbusSwitch::write_state(bool state) { } } this->parent_->queue_command(cmd); - publish_state(state); + this->publish_state(state); } // ModbusSwitch end } // namespace modbus_controller From 39cbc6b183c69fce10eb77c1c9393687335b7a30 Mon Sep 17 00:00:00 2001 From: Oleg Tarasov <me@olegtarasov.email> Date: Tue, 26 Nov 2024 00:47:01 +0300 Subject: [PATCH 262/282] [opentherm] Fix out of memory errors on ESP8266 (#7835) --- esphome/components/opentherm/hub.cpp | 13 ++++---- esphome/components/opentherm/opentherm.cpp | 36 ++++++---------------- esphome/components/opentherm/opentherm.h | 7 ++--- esphome/core/helpers.cpp | 12 ++++++++ esphome/core/helpers.h | 8 +++++ 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index dfa8ea95c5..aac2966ed1 100644 --- a/esphome/components/opentherm/hub.cpp +++ b/esphome/components/opentherm/hub.cpp @@ -138,7 +138,7 @@ OpenthermHub::OpenthermHub() : Component(), in_pin_{}, out_pin_{} {} void OpenthermHub::process_response(OpenthermData &data) { ESP_LOGD(TAG, "Received OpenTherm response with id %d (%s)", data.id, this->opentherm_->message_id_to_str((MessageId) data.id)); - ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(data).c_str()); + this->opentherm_->debug_data(data); switch (data.id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_RESPONSE_MESSAGE, OPENTHERM_MESSAGE_RESPONSE_ENTITY, , @@ -315,7 +315,7 @@ void OpenthermHub::start_conversation_() { ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id, this->opentherm_->message_id_to_str((MessageId) request.id)); - ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(request).c_str()); + this->opentherm_->debug_data(request); // Send the request this->last_conversation_start_ = millis(); this->opentherm_->send(request); @@ -340,19 +340,18 @@ void OpenthermHub::stop_opentherm_() { this->opentherm_->stop(); this->last_conversation_end_ = millis(); } - void OpenthermHub::handle_protocol_write_error_() { ESP_LOGW(TAG, "Error while sending request: %s", this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode())); - ESP_LOGW(TAG, "%s", this->opentherm_->debug_data(this->last_request_).c_str()); + this->opentherm_->debug_data(this->last_request_); } - void OpenthermHub::handle_protocol_read_error_() { OpenThermError error; this->opentherm_->get_protocol_error(error); - ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", this->opentherm_->debug_error(error).c_str()); + ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", + this->opentherm_->protocol_error_to_to_str(error.error_type)); + this->opentherm_->debug_error(error); } - void OpenthermHub::handle_timeout_error_() { ESP_LOGW(TAG, "Receive response timed out at a protocol level"); this->stop_opentherm_(); diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 26c707f9a0..62cfcdceea 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -15,15 +15,11 @@ #include "Arduino.h" #endif #include <string> -#include <sstream> -#include <bitset> namespace esphome { namespace opentherm { using std::string; -using std::bitset; -using std::stringstream; using std::to_string; static const char *const TAG = "opentherm"; @@ -545,29 +541,17 @@ const char *OpenTherm::message_id_to_str(MessageId id) { } } -string OpenTherm::debug_data(OpenthermData &data) { - stringstream result; - result << bitset<8>(data.type) << " " << bitset<8>(data.id) << " " << bitset<8>(data.valueHB) << " " - << bitset<8>(data.valueLB) << "\n"; - result << "type: " << this->message_type_to_str((MessageType) data.type) << "; "; - result << "id: " << to_string(data.id) << "; "; - result << "HB: " << to_string(data.valueHB) << "; "; - result << "LB: " << to_string(data.valueLB) << "; "; - result << "uint_16: " << to_string(data.u16()) << "; "; - result << "float: " << to_string(data.f88()); - - return result.str(); +void OpenTherm::debug_data(OpenthermData &data) { + ESP_LOGD(TAG, "%s %s %s %s", format_bin(data.type).c_str(), format_bin(data.id).c_str(), + format_bin(data.valueHB).c_str(), format_bin(data.valueLB).c_str()); + ESP_LOGD(TAG, "type: %s; id: %s; HB: %s; LB: %s; uint_16: %s; float: %s", + this->message_type_to_str((MessageType) data.type), to_string(data.id).c_str(), + to_string(data.valueHB).c_str(), to_string(data.valueLB).c_str(), to_string(data.u16()).c_str(), + to_string(data.f88()).c_str()); } -std::string OpenTherm::debug_error(OpenThermError &error) { - stringstream result; - result << "type: " << this->protocol_error_to_to_str(error.error_type) << "; "; - result << "data: "; - result << format_hex(error.data); - result << "; clock: " << to_string(clock_); - result << "; capture: " << bitset<32>(error.capture); - result << "; bit_pos: " << to_string(error.bit_pos); - - return result.str(); +void OpenTherm::debug_error(OpenThermError &error) const { + ESP_LOGD(TAG, "data: %s; clock: %s; capture: %s; bit_pos: %s", format_hex(error.data).c_str(), + to_string(clock_).c_str(), format_bin(error.capture).c_str(), to_string(error.bit_pos).c_str()); } float OpenthermData::f88() { return ((float) this->s16()) / 256.0; } diff --git a/esphome/components/opentherm/opentherm.h b/esphome/components/opentherm/opentherm.h index 85f4611125..76710af5f5 100644 --- a/esphome/components/opentherm/opentherm.h +++ b/esphome/components/opentherm/opentherm.h @@ -8,10 +8,9 @@ #pragma once #include <string> -#include <sstream> -#include <iomanip> #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #if defined(ESP32) || defined(USE_ESP_IDF) #include "driver/timer.h" @@ -318,8 +317,8 @@ class OpenTherm { OperationMode get_mode() { return mode_; } - std::string debug_data(OpenthermData &data); - std::string debug_error(OpenThermError &error); + void debug_data(OpenthermData &data); + void debug_error(OpenThermError &error) const; const char *protocol_error_to_to_str(ProtocolErrorType error_type); const char *message_type_to_str(MessageType message_type); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index dae60a4e1d..befc84516c 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -397,6 +397,18 @@ std::string format_hex_pretty(const uint16_t *data, size_t length) { } std::string format_hex_pretty(const std::vector<uint16_t> &data) { return format_hex_pretty(data.data(), data.size()); } +std::string format_bin(const uint8_t *data, size_t length) { + std::string result; + result.resize(length * 8); + for (size_t byte_idx = 0; byte_idx < length; byte_idx++) { + for (size_t bit_idx = 0; bit_idx < 8; bit_idx++) { + result[byte_idx * 8 + bit_idx] = ((data[byte_idx] >> (7 - bit_idx)) & 1) + '0'; + } + } + + return result; +} + ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { if (on == nullptr && strcasecmp(str, "on") == 0) return PARSE_ON; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 43001bafdd..305ec47f76 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -420,6 +420,14 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::stri return format_hex_pretty(reinterpret_cast<uint8_t *>(&val), sizeof(T)); } +/// Format the byte array \p data of length \p len in binary. +std::string format_bin(const uint8_t *data, size_t length); +/// Format an unsigned integer in binary, starting with the most significant byte. +template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_bin(T val) { + val = convert_big_endian(val); + return format_bin(reinterpret_cast<uint8_t *>(&val), sizeof(T)); +} + /// Return values for parse_on_off(). enum ParseOnOffState { PARSE_NONE = 0, From e623989878b3f73b5512c6380b4d4446183443da Mon Sep 17 00:00:00 2001 From: Samuel Sieb <samuel-github@sieb.net> Date: Mon, 25 Nov 2024 14:15:01 -1000 Subject: [PATCH 263/282] fix local time timestamp calculation (#7807) Co-authored-by: Samuel Sieb <samuel@sieb.net> --- .../components/datetime/datetime_entity.cpp | 4 +-- esphome/core/time.cpp | 34 +++++++++++-------- esphome/core/time.h | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/esphome/components/datetime/datetime_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index f215b7acb5..3d92194efa 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -60,9 +60,7 @@ ESPTime DateTimeEntity::state_as_esptime() const { obj.hour = this->hour_; obj.minute = this->minute_; obj.second = this->second_; - obj.day_of_week = 1; // Required to be valid for recalc_timestamp_local but not used. - obj.day_of_year = 1; // Required to be valid for recalc_timestamp_local but not used. - obj.recalc_timestamp_local(false); + obj.recalc_timestamp_local(); return obj; } diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index f7aa4fdddb..31977d972b 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -5,20 +5,18 @@ namespace esphome { -bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } - uint8_t days_in_month(uint8_t month, uint16_t year) { static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - uint8_t days = DAYS_IN_MONTH[month]; - if (month == 2 && is_leap_year(year)) + if (month == 2 && (year % 4 == 0)) return 29; - return days; + return DAYS_IN_MONTH[month]; } size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { struct tm c_tm = this->to_c_tm(); return ::strftime(buffer, buffer_len, format, &c_tm); } + ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) { ESPTime res{}; res.second = uint8_t(c_tm->tm_sec); @@ -33,6 +31,7 @@ ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) { res.timestamp = c_time; return res; } + struct tm ESPTime::to_c_tm() { struct tm c_tm {}; c_tm.tm_sec = this->second; @@ -46,6 +45,7 @@ struct tm ESPTime::to_c_tm() { c_tm.tm_isdst = this->is_dst; return c_tm; } + std::string ESPTime::strftime(const std::string &format) { std::string timestr; timestr.resize(format.size() * 4); @@ -142,6 +142,7 @@ void ESPTime::increment_second() { this->year++; } } + void ESPTime::increment_day() { this->timestamp += 86400; @@ -159,23 +160,22 @@ void ESPTime::increment_day() { this->year++; } } + void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { time_t res = 0; - if (!this->fields_in_range()) { this->timestamp = -1; return; } for (int i = 1970; i < this->year; i++) - res += is_leap_year(i) ? 366 : 365; + res += (year % 4 == 0) ? 366 : 365; if (use_day_of_year) { res += this->day_of_year - 1; } else { for (int i = 1; i < this->month; i++) res += days_in_month(i, this->year); - res += this->day_of_month - 1; } @@ -188,13 +188,17 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { this->timestamp = res; } -void ESPTime::recalc_timestamp_local(bool use_day_of_year) { - this->recalc_timestamp_utc(use_day_of_year); - this->timestamp -= ESPTime::timezone_offset(); - ESPTime temp = ESPTime::from_epoch_local(this->timestamp); - if (temp.is_dst) { - this->timestamp -= 3600; - } +void ESPTime::recalc_timestamp_local() { + struct tm tm; + + tm.tm_year = this->year - 1900; + tm.tm_mon = this->month - 1; + tm.tm_mday = this->day_of_month; + tm.tm_hour = this->hour; + tm.tm_min = this->minute; + tm.tm_sec = this->second; + + this->timestamp = mktime(&tm); } int32_t ESPTime::timezone_offset() { diff --git a/esphome/core/time.h b/esphome/core/time.h index bce1108d93..5cbd9369fb 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -9,8 +9,6 @@ namespace esphome { template<typename T> bool increment_time_value(T ¤t, uint16_t begin, uint16_t end); -bool is_leap_year(uint32_t year); - uint8_t days_in_month(uint8_t month, uint16_t year); /// A more user-friendly version of struct tm from time.h @@ -100,7 +98,7 @@ struct ESPTime { void recalc_timestamp_utc(bool use_day_of_year = true); /// Recalculate the timestamp field from the other fields of this ESPTime instance assuming local fields. - void recalc_timestamp_local(bool use_day_of_year = true); + void recalc_timestamp_local(); /// Convert this ESPTime instance back to a tm struct. struct tm to_c_tm(); From 3bac45e737655af9f8fbf7734e3a17620fcdec47 Mon Sep 17 00:00:00 2001 From: guillempages <guillempages@users.noreply.github.com> Date: Thu, 28 Nov 2024 04:55:20 +0100 Subject: [PATCH 264/282] [online_image]Don't access decoder if not initialized (#7882) --- esphome/components/online_image/png_image.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/online_image/png_image.cpp b/esphome/components/online_image/png_image.cpp index c8e215a91d..4c4c22f9b7 100644 --- a/esphome/components/online_image/png_image.cpp +++ b/esphome/components/online_image/png_image.cpp @@ -49,6 +49,10 @@ void PngDecoder::prepare(uint32_t download_size) { } int HOT PngDecoder::decode(uint8_t *buffer, size_t size) { + if (!this->pngle_) { + ESP_LOGE(TAG, "PNG decoder engine not initialized!"); + return -1; + } if (size < 256 && size < this->download_size_ - this->decoded_bytes_) { ESP_LOGD(TAG, "Waiting for data"); return 0; From 5717d557f5f421ae3402a5d13510dda81ac4bebd Mon Sep 17 00:00:00 2001 From: FreeBear-nc <67865163+FreeBear-nc@users.noreply.github.com> Date: Thu, 28 Nov 2024 03:56:37 +0000 Subject: [PATCH 265/282] Add IRAM_ATTR to all functions used during interrupts on esp8266 chips. (#7840) --- esphome/components/opentherm/opentherm.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 62cfcdceea..c56b49ccb8 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -220,7 +220,7 @@ void IRAM_ATTR OpenTherm::bit_read_(uint8_t value) { this->bit_pos_++; } -ProtocolErrorType OpenTherm::verify_stop_bit_(uint8_t value) { +ProtocolErrorType IRAM_ATTR OpenTherm::verify_stop_bit_(uint8_t value) { if (value) { // stop bit detected return check_parity_(this->data_) ? ProtocolErrorType::NO_ERROR : ProtocolErrorType::PARITY_ERROR; } else { // no stop bit detected, error @@ -365,7 +365,7 @@ void IRAM_ATTR OpenTherm::stop_timer_() { #ifdef ESP8266 // 5 kHz timer_ -void OpenTherm::start_read_timer_() { +void IRAM_ATTR OpenTherm::start_read_timer_() { InterruptLock const lock; timer1_attachInterrupt(OpenTherm::esp8266_timer_isr); timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max) @@ -373,14 +373,14 @@ void OpenTherm::start_read_timer_() { } // 2 kHz timer_ -void OpenTherm::start_write_timer_() { +void IRAM_ATTR OpenTherm::start_write_timer_() { InterruptLock const lock; timer1_attachInterrupt(OpenTherm::esp8266_timer_isr); timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max) timer1_write(2500); // 2kHz } -void OpenTherm::stop_timer_() { +void IRAM_ATTR OpenTherm::stop_timer_() { InterruptLock const lock; timer1_disable(); timer1_detachInterrupt(); @@ -389,7 +389,7 @@ void OpenTherm::stop_timer_() { #endif // END ESP8266 // https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd -bool OpenTherm::check_parity_(uint32_t val) { +bool IRAM_ATTR OpenTherm::check_parity_(uint32_t val) { val ^= val >> 16; val ^= val >> 8; val ^= val >> 4; From 5fcd26bfe964f670759e08bc89386ee9c2b1c0d4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:57:11 +1300 Subject: [PATCH 266/282] [st7920] Remove unnecessary warning when drawing outside display bounds (#7868) --- esphome/components/st7920/st7920.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index f336d24e24..171e7095dd 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -1,7 +1,7 @@ #include "st7920.h" -#include "esphome/core/log.h" -#include "esphome/core/application.h" #include "esphome/components/display/display_buffer.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" namespace esphome { namespace st7920 { @@ -118,7 +118,6 @@ size_t ST7920::get_buffer_length_() { void HOT ST7920::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { - ESP_LOGW(TAG, "Position out of area: %dx%d", x, y); return; } int width = this->get_width_internal() / 8u; From f042c6e643a0c1b67816cc1824611b98d6317ac4 Mon Sep 17 00:00:00 2001 From: Krzysztof Zdulski <krzys.zdulski@gmail.com> Date: Fri, 29 Nov 2024 22:05:00 +0100 Subject: [PATCH 267/282] Fix recalc_timestamp_utc (#7894) --- esphome/core/time.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index 31977d972b..66a0e1c0a7 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -169,7 +169,7 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { } for (int i = 1970; i < this->year; i++) - res += (year % 4 == 0) ? 366 : 365; + res += (i % 4 == 0) ? 366 : 365; if (use_day_of_year) { res += this->day_of_year - 1; From 982ce1db727b103250b69c91e3edaac918bd5f37 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Dec 2024 05:10:18 +1300 Subject: [PATCH 268/282] Cast port to int for ota pushing (#7888) --- esphome/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 86d529e1bf..dce041e5ac 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -363,7 +363,7 @@ def upload_program(config, args, host): from esphome import espota2 - remote_port = ota_conf[CONF_PORT] + remote_port = int(ota_conf[CONF_PORT]) password = ota_conf.get(CONF_PASSWORD, "") if ( From d0958f7cf28dd68e7149ae32d06b3489caf55c11 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:50:11 +1100 Subject: [PATCH 269/282] [lvgl] Bugfixes (#7896) --- esphome/components/lvgl/defines.py | 2 +- esphome/components/lvgl/lvgl_esphome.h | 3 +++ esphome/components/lvgl/widgets/line.py | 6 ++++++ tests/components/lvgl/lvgl-package.yaml | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index ea345fa55c..bb7d25c2c7 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -38,7 +38,7 @@ def literal(arg): def call_lambda(lamb: LambdaExpression): expr = lamb.content.strip() if expr.startswith("return") and expr.endswith(";"): - return expr[7:][:-1] + return expr[6:][:-1].strip() return f"{lamb}()" diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 208cb1cbd5..7bc6b00cf5 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -56,6 +56,9 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) { lv_img_set_src(obj, image->get_lv_img_dsc()); } +inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image) { + lv_disp_set_bg_image(disp, image->get_lv_img_dsc()); +} #endif // USE_LVGL_IMAGE // Parent class for things that wrap an LVGL object diff --git a/esphome/components/lvgl/widgets/line.py b/esphome/components/lvgl/widgets/line.py index 548dfa8452..0156fb1780 100644 --- a/esphome/components/lvgl/widgets/line.py +++ b/esphome/components/lvgl/widgets/line.py @@ -35,6 +35,11 @@ LINE_SCHEMA = { cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), } +LINE_MODIFY_SCHEMA = { + cv.Optional(CONF_POINTS): cv_point_list, + cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), +} + class LineType(WidgetType): def __init__(self): @@ -43,6 +48,7 @@ class LineType(WidgetType): LvType("lv_line_t"), (CONF_MAIN,), LINE_SCHEMA, + modify_schema=LINE_MODIFY_SCHEMA, ) async def to_code(self, w: Widget, config): diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index db0443b3bb..b83ff2a3d5 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -333,7 +333,7 @@ lvgl: id: button_button width: 20% height: 10% - transform_angle: !lambda return 180*100; + transform_angle: !lambda return(180*100); arc_width: !lambda return 4; border_width: !lambda return 6; shadow_ofs_x: !lambda return 6; @@ -577,7 +577,7 @@ lvgl: - 180, 60 - 240, 10 on_click: - - lvgl.widget.update: + - lvgl.line.update: id: lv_line_id line_color: 0xFFFF - lvgl.page.next: From 86ae1c59315261fc36c207b4b76d32385e68a3ba Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 4 Dec 2024 07:48:50 +1100 Subject: [PATCH 270/282] [lvgl] Fix msgbox content (#7912) --- esphome/components/lvgl/widgets/msgbox.py | 3 ++- tests/components/lvgl/lvgl-package.yaml | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/widgets/msgbox.py b/esphome/components/lvgl/widgets/msgbox.py index be0f2100d7..c3393940b6 100644 --- a/esphome/components/lvgl/widgets/msgbox.py +++ b/esphome/components/lvgl/widgets/msgbox.py @@ -29,7 +29,7 @@ from ..lvcode import ( ) from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema from ..types import LV_EVENT, char_ptr, lv_obj_t -from . import Widget, set_obj_properties +from . import Widget, add_widgets, set_obj_properties from .button import button_spec from .buttonmatrix import ( BUTTONMATRIX_BUTTON_SCHEMA, @@ -119,6 +119,7 @@ async def msgbox_to_code(top_layer, conf): button_style = {CONF_ITEMS: button_style} await set_obj_properties(buttonmatrix_widget, button_style) await set_obj_properties(msgbox_widget, conf) + await add_widgets(msgbox_widget, conf) async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action: outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN") if close_button: diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index b83ff2a3d5..e5df30f136 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -109,6 +109,10 @@ lvgl: close_button: true title: Messagebox bg_color: 0xffff + widgets: + - label: + text: Hello Msgbox + id: msgbox_label body: text: This is a sample messagebox bg_color: 0x808080 @@ -137,6 +141,9 @@ lvgl: - lvgl.widget.focus: mark - lvgl.widget.redraw: hello_label - lvgl.widget.redraw: + - lvgl.label.update: + id: msgbox_label + text: Unloaded on_all_events: logger.log: format: "Event %s" From c8ec0bb7eab3d77cb5b62a227d4041677f1225ad Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:16:59 +1100 Subject: [PATCH 271/282] [esp32] Fix crash with empty `platformio_options:` value (#7920) --- esphome/components/esp32/__init__.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 61fbb53e3a..aaef68fa27 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -355,24 +355,20 @@ def _detect_variant(value): def final_validate(config): - if CONF_PLATFORMIO_OPTIONS not in fv.full_config.get()[CONF_ESPHOME]: + if not ( + pio_options := fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS) + ): + # Not specified or empty return config pio_flash_size_key = "board_upload.flash_size" pio_partitions_key = "board_build.partitions" - if ( - CONF_PARTITIONS in config - and pio_partitions_key - in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS] - ): + if CONF_PARTITIONS in config and pio_partitions_key in pio_options: raise cv.Invalid( f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32" ) - if ( - pio_flash_size_key - in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS] - ): + if pio_flash_size_key in pio_options: raise cv.Invalid( f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only" ) From c80e035bd56fbfe89475fdb149b0ff26fb279e61 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:55:51 +1300 Subject: [PATCH 272/282] Bump version to 2024.11.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 4b19e2865d..ae7feda6d8 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.2" +__version__ = "2024.11.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 9d000e9abf3832497e8a6640e66c0eb4c8568060 Mon Sep 17 00:00:00 2001 From: Citric Lee <37475446+limengdu@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:28:41 +0800 Subject: [PATCH 273/282] Add: Seeed Studio MR60BHA2 mmWave Sensor (#7589) Co-authored-by: Spencer Yan <spencer@spenyan.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/seeed_mr60bha2/__init__.py | 41 +++++ .../seeed_mr60bha2/seeed_mr60bha2.cpp | 173 ++++++++++++++++++ .../seeed_mr60bha2/seeed_mr60bha2.h | 61 ++++++ esphome/components/seeed_mr60bha2/sensor.py | 57 ++++++ esphome/const.py | 2 + tests/components/seeed_mr60bha2/common.yaml | 19 ++ .../seeed_mr60bha2/test.esp32-c3-ard.yaml | 5 + .../seeed_mr60bha2/test.esp32-c3-idf.yaml | 5 + 9 files changed, 364 insertions(+) create mode 100644 esphome/components/seeed_mr60bha2/__init__.py create mode 100644 esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp create mode 100644 esphome/components/seeed_mr60bha2/seeed_mr60bha2.h create mode 100644 esphome/components/seeed_mr60bha2/sensor.py create mode 100644 tests/components/seeed_mr60bha2/common.yaml create mode 100644 tests/components/seeed_mr60bha2/test.esp32-c3-ard.yaml create mode 100644 tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 74c205b302..404ad35efc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -355,6 +355,7 @@ esphome/components/sdl/* @clydebarrow esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/seeed_mr24hpc1/* @limengdu +esphome/components/seeed_mr60bha2/* @limengdu esphome/components/seeed_mr60fda2/* @limengdu esphome/components/selec_meter/* @sourabhjaiswal esphome/components/select/* @esphome/core diff --git a/esphome/components/seeed_mr60bha2/__init__.py b/esphome/components/seeed_mr60bha2/__init__.py new file mode 100644 index 0000000000..87bdbbd003 --- /dev/null +++ b/esphome/components/seeed_mr60bha2/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CODEOWNERS = ["@limengdu"] +DEPENDENCIES = ["uart"] +MULTI_CONF = True + +mr60bha2_ns = cg.esphome_ns.namespace("seeed_mr60bha2") + +MR60BHA2Component = mr60bha2_ns.class_( + "MR60BHA2Component", cg.Component, uart.UARTDevice +) + +CONF_MR60BHA2_ID = "mr60bha2_id" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MR60BHA2Component), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "seeed_mr60bha2", + require_tx=True, + require_rx=True, + baud_rate=115200, + parity="NONE", + stop_bits=1, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp new file mode 100644 index 0000000000..50d709c3b0 --- /dev/null +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp @@ -0,0 +1,173 @@ +#include "seeed_mr60bha2.h" +#include "esphome/core/log.h" + +#include <utility> + +namespace esphome { +namespace seeed_mr60bha2 { + +static const char *const TAG = "seeed_mr60bha2"; + +// Prints the component's configuration data. dump_config() prints all of the component's configuration +// items in an easy-to-read format, including the configuration key-value pairs. +void MR60BHA2Component::dump_config() { + ESP_LOGCONFIG(TAG, "MR60BHA2:"); +#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_); +#endif +} + +// main loop +void MR60BHA2Component::loop() { + uint8_t byte; + + // Is there data on the serial port + while (this->available()) { + this->read_byte(&byte); + this->rx_message_.push_back(byte); + if (!this->validate_message_()) { + this->rx_message_.clear(); + } + } +} + +/** + * @brief Calculate the checksum for a byte array. + * + * This function calculates the checksum for the provided byte array using an + * XOR-based checksum algorithm. + * + * @param data The byte array to calculate the checksum for. + * @param len The length of the byte array. + * @return The calculated checksum. + */ +static uint8_t calculate_checksum(const uint8_t *data, size_t len) { + uint8_t checksum = 0; + for (size_t i = 0; i < len; i++) { + checksum ^= data[i]; + } + checksum = ~checksum; + return checksum; +} + +/** + * @brief Validate the checksum of a byte array. + * + * This function validates the checksum of the provided byte array by comparing + * it to the expected checksum. + * + * @param data The byte array to validate. + * @param len The length of the byte array. + * @param expected_checksum The expected checksum. + * @return True if the checksum is valid, false otherwise. + */ +static bool validate_checksum(const uint8_t *data, size_t len, uint8_t expected_checksum) { + return calculate_checksum(data, len) == expected_checksum; +} + +bool MR60BHA2Component::validate_message_() { + size_t at = this->rx_message_.size() - 1; + auto *data = &this->rx_message_[0]; + uint8_t new_byte = data[at]; + + if (at == 0) { + return new_byte == FRAME_HEADER_BUFFER; + } + + if (at <= 2) { + return true; + } + uint16_t frame_id = encode_uint16(data[1], data[2]); + + if (at <= 4) { + return true; + } + + uint16_t length = encode_uint16(data[3], data[4]); + + if (at <= 6) { + return true; + } + + 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) { + return false; + } + + uint8_t header_checksum = new_byte; + + if (at == 7) { + if (!validate_checksum(data, 7, header_checksum)) { + ESP_LOGE(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", header_checksum); + ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8).c_str()); + return false; + } + return true; + } + + // Wait until all data is read + if (at - 8 < length) { + return true; + } + + uint8_t data_checksum = new_byte; + if (at == 8 + length) { + if (!validate_checksum(data + 8, length, data_checksum)) { + ESP_LOGE(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", data_checksum); + ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8 + length).c_str()); + return false; + } + } + + const uint8_t *frame_data = data + 8; + ESP_LOGV(TAG, "Received Frame: ID: 0x%04x, Type: 0x%04x, Data: [%s] Raw Data: [%s]", frame_id, frame_type, + format_hex_pretty(frame_data, length).c_str(), format_hex_pretty(this->rx_message_).c_str()); + this->process_frame_(frame_id, frame_type, data + 8, length); + + // Return false to reset rx buffer + return false; +} + +void MR60BHA2Component::process_frame_(uint16_t frame_id, uint16_t frame_type, const uint8_t *data, size_t length) { + switch (frame_type) { + case BREATH_RATE_TYPE_BUFFER: + if (this->breath_rate_sensor_ != nullptr && length >= 4) { + uint32_t current_breath_rate_int = encode_uint32(data[3], data[2], data[1], data[0]); + if (current_breath_rate_int != 0) { + float breath_rate_float; + memcpy(&breath_rate_float, ¤t_breath_rate_int, sizeof(float)); + this->breath_rate_sensor_->publish_state(breath_rate_float); + } + } + 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]); + if (current_heart_rate_int != 0) { + float heart_rate_float; + memcpy(&heart_rate_float, ¤t_heart_rate_int, sizeof(float)); + this->heart_rate_sensor_->publish_state(heart_rate_float); + } + } + break; + case DISTANCE_TYPE_BUFFER: + if (!data[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; + memcpy(&distance_float, ¤t_distance_int, sizeof(float)); + this->distance_sensor_->publish_state(distance_float); + } + } + break; + default: + break; + } +} + +} // namespace seeed_mr60bha2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.h b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.h new file mode 100644 index 0000000000..0a4f21f1ad --- /dev/null +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.h @@ -0,0 +1,61 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" + +#include <map> + +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 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, +}; + +class MR60BHA2Component : public Component, + public uart::UARTDevice { // The class name must be the name defined by text_sensor.py +#ifdef USE_SENSOR + SUB_SENSOR(breath_rate); + SUB_SENSOR(heart_rate); + SUB_SENSOR(distance); +#endif + + public: + float get_setup_priority() const override { return esphome::setup_priority::LATE; } + void dump_config() override; + void loop() override; + + protected: + bool validate_message_(); + void process_frame_(uint16_t frame_id, uint16_t frame_type, const uint8_t *data, size_t length); + + std::vector<uint8_t> rx_message_; +}; + +} // namespace seeed_mr60bha2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60bha2/sensor.py b/esphome/components/seeed_mr60bha2/sensor.py new file mode 100644 index 0000000000..5f30b363bf --- /dev/null +++ b/esphome/components/seeed_mr60bha2/sensor.py @@ -0,0 +1,57 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_DISTANCE, + DEVICE_CLASS_DISTANCE, + ICON_HEART_PULSE, + ICON_PULSE, + ICON_SIGNAL, + STATE_CLASS_MEASUREMENT, + UNIT_BEATS_PER_MINUTE, + UNIT_CENTIMETER, +) + +from . import CONF_MR60BHA2_ID, MR60BHA2Component + +DEPENDENCIES = ["seeed_mr60bha2"] + +CONF_BREATH_RATE = "breath_rate" +CONF_HEART_RATE = "heart_rate" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MR60BHA2_ID): cv.use_id(MR60BHA2Component), + cv.Optional(CONF_BREATH_RATE): sensor.sensor_schema( + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + icon=ICON_PULSE, + ), + cv.Optional(CONF_HEART_RATE): sensor.sensor_schema( + accuracy_decimals=0, + icon=ICON_HEART_PULSE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_BEATS_PER_MINUTE, + ), + cv.Optional(CONF_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CENTIMETER, + accuracy_decimals=2, + icon=ICON_SIGNAL, + ), + } +) + + +async def to_code(config): + mr60bha2_component = await cg.get_variable(config[CONF_MR60BHA2_ID]) + if breath_rate_config := config.get(CONF_BREATH_RATE): + sens = await sensor.new_sensor(breath_rate_config) + cg.add(mr60bha2_component.set_breath_rate_sensor(sens)) + if heart_rate_config := config.get(CONF_HEART_RATE): + sens = await sensor.new_sensor(heart_rate_config) + cg.add(mr60bha2_component.set_heart_rate_sensor(sens)) + if distance_config := config.get(CONF_DISTANCE): + sens = await sensor.new_sensor(distance_config) + cg.add(mr60bha2_component.set_distance_sensor(sens)) diff --git a/esphome/const.py b/esphome/const.py index 3d3bfcc244..b9397aa1bd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1001,6 +1001,7 @@ ICON_GRAIN = "mdi:grain" ICON_GYROSCOPE_X = "mdi:axis-x-rotate-clockwise" ICON_GYROSCOPE_Y = "mdi:axis-y-rotate-clockwise" ICON_GYROSCOPE_Z = "mdi:axis-z-rotate-clockwise" +ICON_HEART_PULSE = "mdi:heart-pulse" ICON_HEATING_COIL = "mdi:heating-coil" ICON_KEY_PLUS = "mdi:key-plus" ICON_LIGHTBULB = "mdi:lightbulb" @@ -1040,6 +1041,7 @@ ICON_WEATHER_WINDY = "mdi:weather-windy" ICON_WIFI = "mdi:wifi" UNIT_AMPERE = "A" +UNIT_BEATS_PER_MINUTE = "bpm" UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³" UNIT_BYTES = "B" UNIT_CELSIUS = "°C" diff --git a/tests/components/seeed_mr60bha2/common.yaml b/tests/components/seeed_mr60bha2/common.yaml new file mode 100644 index 0000000000..e9d0c735af --- /dev/null +++ b/tests/components/seeed_mr60bha2/common.yaml @@ -0,0 +1,19 @@ +uart: + - id: seeed_mr60fda2_uart + tx_pin: ${uart_tx_pin} + rx_pin: ${uart_rx_pin} + baud_rate: 115200 + parity: NONE + stop_bits: 1 + +seeed_mr60bha2: + id: my_seeed_mr60bha2 + +sensor: + - platform: seeed_mr60bha2 + breath_rate: + name: "Real-time respiratory rate" + heart_rate: + name: "Real-time heart rate" + distance: + name: "Distance to detection object" diff --git a/tests/components/seeed_mr60bha2/test.esp32-c3-ard.yaml b/tests/components/seeed_mr60bha2/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..4fb884abf4 --- /dev/null +++ b/tests/components/seeed_mr60bha2/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml b/tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4fb884abf4 --- /dev/null +++ b/tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml From f15e3cfb9b9cc26b402d1098549a79e87c00d9bc Mon Sep 17 00:00:00 2001 From: David Schneider <dnschneid@gmail.com> Date: Sun, 8 Dec 2024 18:51:37 -0800 Subject: [PATCH 274/282] Optimize QMC5883L reads (#7889) --- esphome/components/qmc5883l/qmc5883l.cpp | 62 +++++++++++++++++------- esphome/components/qmc5883l/qmc5883l.h | 2 +- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/esphome/components/qmc5883l/qmc5883l.cpp b/esphome/components/qmc5883l/qmc5883l.cpp index 49a67d4e09..36286244fb 100644 --- a/esphome/components/qmc5883l/qmc5883l.cpp +++ b/esphome/components/qmc5883l/qmc5883l.cpp @@ -81,16 +81,39 @@ void QMC5883LComponent::dump_config() { } float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } void QMC5883LComponent::update() { + i2c::ErrorCode err; uint8_t status = false; - this->read_byte(QMC5883L_REGISTER_STATUS, &status); + // Status byte gets cleared when data is read, so we have to read this first. + // If status and two axes are desired, it's possible to save one byte of traffic by enabling + // ROL_PNT in setup and reading 7 bytes starting at the status register. + // If status and all three axes are desired, using ROL_PNT saves you 3 bytes. + // But simply not reading status saves you 4 bytes always and is much simpler. + if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) { + err = this->read_register(QMC5883L_REGISTER_STATUS, &status, 1); + if (err != i2c::ERROR_OK) { + this->status_set_warning(str_sprintf("status read failed (%d)", err).c_str()); + return; + } + } - // Always request X,Y,Z regardless if there are sensors for them - // to avoid https://github.com/esphome/issues/issues/5731 - uint16_t raw_x, raw_y, raw_z; - if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x) || - !this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y) || - !this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) { - this->status_set_warning(); + uint16_t raw[3] = {0}; + // Z must always be requested, otherwise the data registers will remain locked against updates. + // Skipping the Y axis if X and Z are needed actually requires an additional byte of comms. + // Starting partway through the axes does save you traffic. + uint8_t start, dest; + if (this->heading_sensor_ != nullptr || this->x_sensor_ != nullptr) { + start = QMC5883L_REGISTER_DATA_X_LSB; + dest = 0; + } else if (this->y_sensor_ != nullptr) { + start = QMC5883L_REGISTER_DATA_Y_LSB; + dest = 1; + } else { + start = QMC5883L_REGISTER_DATA_Z_LSB; + dest = 2; + } + err = this->read_bytes_16_le_(start, &raw[dest], 3 - dest); + if (err != i2c::ERROR_OK) { + this->status_set_warning(str_sprintf("mag read failed (%d)", err).c_str()); return; } @@ -107,17 +130,18 @@ void QMC5883LComponent::update() { } // in µT - const float x = int16_t(raw_x) * mg_per_bit * 0.1f; - const float y = int16_t(raw_y) * mg_per_bit * 0.1f; - const float z = int16_t(raw_z) * mg_per_bit * 0.1f; + const float x = int16_t(raw[0]) * mg_per_bit * 0.1f; + const float y = int16_t(raw[1]) * mg_per_bit * 0.1f; + const float z = int16_t(raw[2]) * mg_per_bit * 0.1f; float heading = atan2f(0.0f - x, y) * 180.0f / M_PI; float temp = NAN; if (this->temperature_sensor_ != nullptr) { uint16_t raw_temp; - if (!this->read_byte_16_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp)) { - this->status_set_warning(); + err = this->read_bytes_16_le_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp); + if (err != i2c::ERROR_OK) { + this->status_set_warning(str_sprintf("temp read failed (%d)", err).c_str()); return; } temp = int16_t(raw_temp) * 0.01f; @@ -138,11 +162,13 @@ void QMC5883LComponent::update() { this->temperature_sensor_->publish_state(temp); } -bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) { - if (!this->read_byte_16(a_register, data)) - return false; - *data = (*data & 0x00FF) << 8 | (*data & 0xFF00) >> 8; // Flip Byte order, LSB first; - return true; +i2c::ErrorCode QMC5883LComponent::read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len) { + i2c::ErrorCode err = this->read_register(a_register, reinterpret_cast<uint8_t *>(data), len * 2); + if (err != i2c::ERROR_OK) + return err; + for (size_t i = 0; i < len; i++) + data[i] = convert_little_endian(data[i]); + return err; } } // namespace qmc5883l diff --git a/esphome/components/qmc5883l/qmc5883l.h b/esphome/components/qmc5883l/qmc5883l.h index dd2008d453..3202e37780 100644 --- a/esphome/components/qmc5883l/qmc5883l.h +++ b/esphome/components/qmc5883l/qmc5883l.h @@ -55,7 +55,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice { NONE = 0, COMMUNICATION_FAILED, } error_code_; - bool read_byte_16_(uint8_t a_register, uint16_t *data); + i2c::ErrorCode read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len = 1); HighFrequencyLoopRequester high_freq_; }; From 440080a753ffeed63616f3c3601acd911f239e56 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:09:29 +1300 Subject: [PATCH 275/282] [display] Fix strftime overload ignoring alignment (#7937) --- esphome/components/display/display.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index 1d996bd59b..f00c2936a8 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -1,6 +1,6 @@ #include "display.h" -#include "display_color_utils.h" #include <utility> +#include "display_color_utils.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" @@ -670,7 +670,7 @@ void Display::strftime(int x, int y, BaseFont *font, Color color, Color backgrou this->print(x, y, font, color, align, buffer, background); } void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { - this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); + this->strftime(x, y, font, color, COLOR_OFF, align, format, time); } void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) { this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); From 132a096ae75c39e6d882b616b79bffd0cc50ea69 Mon Sep 17 00:00:00 2001 From: Yoonji Park <koreapyj@dcmys.kr> Date: Mon, 9 Dec 2024 20:13:21 +0900 Subject: [PATCH 276/282] Add font anti-aliasing for grayscale display (#7934) --- esphome/components/font/font.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/font/font.cpp b/esphome/components/font/font.cpp index aeca0f5cc0..8c4cba34b3 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -133,9 +133,11 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo auto diff_r = (float) color.r - (float) background.r; auto diff_g = (float) color.g - (float) background.g; auto diff_b = (float) color.b - (float) background.b; + auto diff_w = (float) color.w - (float) background.w; auto b_r = (float) background.r; auto b_g = (float) background.g; - auto b_b = (float) background.g; + auto b_b = (float) background.b; + auto b_w = (float) background.w; for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) { for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) { uint8_t pixel = 0; @@ -153,8 +155,8 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo display->draw_pixel_at(glyph_x, glyph_y, color); } else if (pixel != 0) { auto on = (float) pixel / (float) bpp_max; - auto blended = - Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), (uint8_t) (diff_b * on + b_b)); + auto blended = Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), + (uint8_t) (diff_b * on + b_b), (uint8_t) (diff_w * on + b_w)); display->draw_pixel_at(glyph_x, glyph_y, blended); } } From 14eac3dbce15acf6857c68db1e931130ae6bd462 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 23:44:39 +0100 Subject: [PATCH 277/282] Bump pypa/gh-action-pypi-publish from 1.12.2 to 1.12.3 (#7941) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 096b00f0f1..a4e4305207 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,7 @@ jobs: pip3 install build python3 -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.12.2 + uses: pypa/gh-action-pypi-publish@v1.12.3 deploy-docker: name: Build ESPHome ${{ matrix.platform }} From 437b236a4d5b7e2eb39a66b828b8dcfc1d51d162 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 10 Dec 2024 01:38:45 +0100 Subject: [PATCH 278/282] [adc] Split files by platform (#7940) --- esphome/components/adc/adc_sensor.h | 15 +- esphome/components/adc/adc_sensor_common.cpp | 24 +++ .../{adc_sensor.cpp => adc_sensor_esp32.cpp} | 189 +----------------- esphome/components/adc/adc_sensor_esp8266.cpp | 58 ++++++ esphome/components/adc/adc_sensor_rp2040.cpp | 93 +++++++++ 5 files changed, 192 insertions(+), 187 deletions(-) create mode 100644 esphome/components/adc/adc_sensor_common.cpp rename esphome/components/adc/{adc_sensor.cpp => adc_sensor_esp32.cpp} (53%) create mode 100644 esphome/components/adc/adc_sensor_esp8266.cpp create mode 100644 esphome/components/adc/adc_sensor_rp2040.cpp diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index b697d6dd7e..7a3e1c8da7 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -3,13 +3,12 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" #include "esphome/core/component.h" -#include "esphome/core/defines.h" #include "esphome/core/hal.h" #ifdef USE_ESP32 #include <esp_adc_cal.h> #include "driver/adc.h" -#endif +#endif // USE_ESP32 namespace esphome { namespace adc { @@ -43,7 +42,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage this->channel1_ = ADC1_CHANNEL_MAX; } void set_autorange(bool autorange) { this->autorange_ = autorange; } -#endif +#endif // USE_ESP32 /// Update ADC values void update() override; @@ -59,11 +58,11 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_ESP8266 std::string unique_id() override; -#endif +#endif // USE_ESP8266 #ifdef USE_RP2040 void set_is_temperature() { this->is_temperature_ = true; } -#endif +#endif // USE_RP2040 protected: InternalGPIOPin *pin_; @@ -72,7 +71,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_RP2040 bool is_temperature_{false}; -#endif +#endif // USE_RP2040 #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; @@ -83,8 +82,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {}; #else esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {}; -#endif -#endif +#endif // ESP_IDF_VERSION_MAJOR +#endif // USE_ESP32 }; } // namespace adc diff --git a/esphome/components/adc/adc_sensor_common.cpp b/esphome/components/adc/adc_sensor_common.cpp new file mode 100644 index 0000000000..2dccd55fcd --- /dev/null +++ b/esphome/components/adc/adc_sensor_common.cpp @@ -0,0 +1,24 @@ +#include "adc_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace adc { + +static const char *const TAG = "adc.common"; + +void ADCSensor::update() { + float value_v = this->sample(); + ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); + this->publish_state(value_v); +} + +void ADCSensor::set_sample_count(uint8_t sample_count) { + if (sample_count != 0) { + this->sample_count_ = sample_count; + } +} + +float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace adc +} // namespace esphome diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor_esp32.cpp similarity index 53% rename from esphome/components/adc/adc_sensor.cpp rename to esphome/components/adc/adc_sensor_esp32.cpp index 7257793016..24e3750091 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -1,30 +1,13 @@ +#ifdef USE_ESP32 + #include "adc_sensor.h" -#include "esphome/core/helpers.h" #include "esphome/core/log.h" -#ifdef USE_ESP8266 -#ifdef USE_ADC_SENSOR_VCC -#include <Esp.h> -ADC_MODE(ADC_VCC) -#else -#include <Arduino.h> -#endif -#endif - -#ifdef USE_RP2040 -#ifdef CYW43_USES_VSYS_PIN -#include "pico/cyw43_arch.h" -#endif -#include <hardware/adc.h> -#endif - namespace esphome { namespace adc { -static const char *const TAG = "adc"; +static const char *const TAG = "adc.esp32"; -// 13-bit for S2, 12-bit for all other ESP32 variants -#ifdef USE_ESP32 static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1); #ifndef SOC_ADC_RTC_MAX_BITWIDTH @@ -32,24 +15,15 @@ static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_widt static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13; #else static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12; -#endif -#endif +#endif // USE_ESP32_VARIANT_ESP32S2 +#endif // SOC_ADC_RTC_MAX_BITWIDTH -static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; // 4095 (12 bit) or 8191 (13 bit) -static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit) -#endif +static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; +static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; -#ifdef USE_RP2040 -extern "C" -#endif - void - ADCSensor::setup() { +void ADCSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); -#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040) - this->pin_->setup(); -#endif -#ifdef USE_ESP32 if (this->channel1_ != ADC1_CHANNEL_MAX) { adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); if (!this->autorange_) { @@ -61,7 +35,6 @@ extern "C" } } - // load characteristics for each attenuation for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) { auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2; auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, @@ -79,31 +52,10 @@ extern "C" break; } } - -#endif // USE_ESP32 - -#ifdef USE_RP2040 - static bool initialized = false; - if (!initialized) { - adc_init(); - initialized = true; - } -#endif - - ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str()); } void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); -#if defined(USE_ESP8266) || defined(USE_LIBRETINY) -#ifdef USE_ADC_SENSOR_VCC - ESP_LOGCONFIG(TAG, " Pin: VCC"); -#else - LOG_PIN(" Pin: ", this->pin_); -#endif -#endif // USE_ESP8266 || USE_LIBRETINY - -#ifdef USE_ESP32 LOG_PIN(" Pin: ", this->pin_); if (this->autorange_) { ESP_LOGCONFIG(TAG, " Attenuation: auto"); @@ -125,55 +77,10 @@ void ADCSensor::dump_config() { break; } } -#endif // USE_ESP32 - -#ifdef USE_RP2040 - if (this->is_temperature_) { - ESP_LOGCONFIG(TAG, " Pin: Temperature"); - } else { -#ifdef USE_ADC_SENSOR_VCC - ESP_LOGCONFIG(TAG, " Pin: VCC"); -#else - LOG_PIN(" Pin: ", this->pin_); -#endif // USE_ADC_SENSOR_VCC - } -#endif // USE_RP2040 ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); LOG_UPDATE_INTERVAL(this); } -float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } -void ADCSensor::update() { - float value_v = this->sample(); - ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); - this->publish_state(value_v); -} - -void ADCSensor::set_sample_count(uint8_t sample_count) { - if (sample_count != 0) { - this->sample_count_ = sample_count; - } -} - -#ifdef USE_ESP8266 -float ADCSensor::sample() { - uint32_t raw = 0; - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { -#ifdef USE_ADC_SENSOR_VCC - raw += ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) -#else - raw += analogRead(this->pin_->get_pin()); // NOLINT -#endif - } - raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) - if (this->output_raw_) { - return raw; - } - return raw / 1024.0f; -} -#endif - -#ifdef USE_ESP32 float ADCSensor::sample() { if (!this->autorange_) { uint32_t sum = 0; @@ -240,93 +147,17 @@ float ADCSensor::sample() { uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]); uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]); - // Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC) uint32_t c12 = std::min(raw12, ADC_HALF); uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF); uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF); uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF); - // max theoretical csum value is 4096*4 = 16384 uint32_t csum = c12 + c6 + c2 + c0; - // each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32 uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); return mv_scaled / (float) (csum * 1000U); } -#endif // USE_ESP32 - -#ifdef USE_RP2040 -float ADCSensor::sample() { - if (this->is_temperature_) { - adc_set_temp_sensor_enabled(true); - delay(1); - adc_select_input(4); - uint32_t raw = 0; - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { - raw += adc_read(); - } - raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) - adc_set_temp_sensor_enabled(false); - if (this->output_raw_) { - return raw; - } - return raw * 3.3f / 4096.0f; - } else { - uint8_t pin = this->pin_->get_pin(); -#ifdef CYW43_USES_VSYS_PIN - if (pin == PICO_VSYS_PIN) { - // Measuring VSYS on Raspberry Pico W needs to be wrapped with - // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in - // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and - // VSYS ADC both share GPIO29 - cyw43_thread_enter(); - } -#endif // CYW43_USES_VSYS_PIN - - adc_gpio_init(pin); - adc_select_input(pin - 26); - - uint32_t raw = 0; - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { - raw += adc_read(); - } - raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) - -#ifdef CYW43_USES_VSYS_PIN - if (pin == PICO_VSYS_PIN) { - cyw43_thread_exit(); - } -#endif // CYW43_USES_VSYS_PIN - - if (this->output_raw_) { - return raw; - } - float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0; - return raw * 3.3f / 4096.0f * coeff; - } -} -#endif - -#ifdef USE_LIBRETINY -float ADCSensor::sample() { - uint32_t raw = 0; - if (this->output_raw_) { - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { - raw += analogRead(this->pin_->get_pin()); // NOLINT - } - raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) - return raw; - } - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { - raw += analogReadVoltage(this->pin_->get_pin()); // NOLINT - } - raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) - return raw / 1000.0f; -} -#endif // USE_LIBRETINY - -#ifdef USE_ESP8266 -std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } -#endif } // namespace adc } // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/adc/adc_sensor_esp8266.cpp b/esphome/components/adc/adc_sensor_esp8266.cpp new file mode 100644 index 0000000000..c9b6f8b652 --- /dev/null +++ b/esphome/components/adc/adc_sensor_esp8266.cpp @@ -0,0 +1,58 @@ +#ifdef USE_ESP8266 + +#include "adc_sensor.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ADC_SENSOR_VCC +#include <Esp.h> +ADC_MODE(ADC_VCC) +#else +#include <Arduino.h> +#endif // USE_ADC_SENSOR_VCC + +namespace esphome { +namespace adc { + +static const char *const TAG = "adc.esp8266"; + +void ADCSensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); +#ifndef USE_ADC_SENSOR_VCC + this->pin_->setup(); +#endif +} + +void ADCSensor::dump_config() { + LOG_SENSOR("", "ADC Sensor", this); +#ifdef USE_ADC_SENSOR_VCC + ESP_LOGCONFIG(TAG, " Pin: VCC"); +#else + LOG_PIN(" Pin: ", this->pin_); +#endif // USE_ADC_SENSOR_VCC + ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); + LOG_UPDATE_INTERVAL(this); +} + +float ADCSensor::sample() { + uint32_t raw = 0; + for (uint8_t sample = 0; sample < this->sample_count_; sample++) { +#ifdef USE_ADC_SENSOR_VCC + raw += ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) +#else + raw += analogRead(this->pin_->get_pin()); // NOLINT +#endif // USE_ADC_SENSOR_VCC + } + raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) + if (this->output_raw_) { + return raw; + } + return raw / 1024.0f; +} + +std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } + +} // namespace adc +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/adc/adc_sensor_rp2040.cpp b/esphome/components/adc/adc_sensor_rp2040.cpp new file mode 100644 index 0000000000..520ff3bacc --- /dev/null +++ b/esphome/components/adc/adc_sensor_rp2040.cpp @@ -0,0 +1,93 @@ +#ifdef USE_RP2040 + +#include "adc_sensor.h" +#include "esphome/core/log.h" + +#ifdef CYW43_USES_VSYS_PIN +#include "pico/cyw43_arch.h" +#endif // CYW43_USES_VSYS_PIN +#include <hardware/adc.h> + +namespace esphome { +namespace adc { + +static const char *const TAG = "adc.rp2040"; + +void ADCSensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); + static bool initialized = false; + if (!initialized) { + adc_init(); + initialized = true; + } +} + +void ADCSensor::dump_config() { + LOG_SENSOR("", "ADC Sensor", this); + if (this->is_temperature_) { + ESP_LOGCONFIG(TAG, " Pin: Temperature"); + } else { +#ifdef USE_ADC_SENSOR_VCC + ESP_LOGCONFIG(TAG, " Pin: VCC"); +#else + LOG_PIN(" Pin: ", this->pin_); +#endif // USE_ADC_SENSOR_VCC + } + ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); + LOG_UPDATE_INTERVAL(this); +} + +float ADCSensor::sample() { + if (this->is_temperature_) { + adc_set_temp_sensor_enabled(true); + delay(1); + adc_select_input(4); + uint32_t raw = 0; + for (uint8_t sample = 0; sample < this->sample_count_; sample++) { + raw += adc_read(); + } + raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) + adc_set_temp_sensor_enabled(false); + if (this->output_raw_) { + return raw; + } + return raw * 3.3f / 4096.0f; + } + + uint8_t pin = this->pin_->get_pin(); +#ifdef CYW43_USES_VSYS_PIN + if (pin == PICO_VSYS_PIN) { + // Measuring VSYS on Raspberry Pico W needs to be wrapped with + // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in + // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and + // VSYS ADC both share GPIO29 + cyw43_thread_enter(); + } +#endif // CYW43_USES_VSYS_PIN + + adc_gpio_init(pin); + adc_select_input(pin - 26); + + uint32_t raw = 0; + for (uint8_t sample = 0; sample < this->sample_count_; sample++) { + raw += adc_read(); + } + raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) + +#ifdef CYW43_USES_VSYS_PIN + if (pin == PICO_VSYS_PIN) { + cyw43_thread_exit(); + } +#endif // CYW43_USES_VSYS_PIN + + if (this->output_raw_) { + return raw; + } + float coeff = pin == PICO_VSYS_PIN ? 3.0f : 1.0f; + return raw * 3.3f / 4096.0f * coeff; +} + +} // namespace adc +} // namespace esphome + +#endif // USE_RP2040 From 5a92e2466238fa2627e9a16a1e29ec8d41375c92 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:22:30 +1300 Subject: [PATCH 279/282] [const] Move ``CONF_TEMPERATURE_COMPENSATION`` to common const.py (#7943) --- esphome/components/sen5x/sensor.py | 11 +++++------ esphome/components/ufire_ec/sensor.py | 8 ++++---- esphome/const.py | 1 + 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/components/sen5x/sensor.py b/esphome/components/sen5x/sensor.py index 67bd627f7f..a8a796853e 100644 --- a/esphome/components/sen5x/sensor.py +++ b/esphome/components/sen5x/sensor.py @@ -1,19 +1,19 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import i2c, sensor, sensirion_common from esphome import automation from esphome.automation import maybe_simple_id - +import esphome.codegen as cg +from esphome.components import i2c, sensirion_common, sensor +import esphome.config_validation as cv from esphome.const import ( CONF_HUMIDITY, CONF_ID, CONF_OFFSET, CONF_PM_1_0, - CONF_PM_10_0, CONF_PM_2_5, CONF_PM_4_0, + CONF_PM_10_0, CONF_STORE_BASELINE, CONF_TEMPERATURE, + CONF_TEMPERATURE_COMPENSATION, DEVICE_CLASS_AQI, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PM1, @@ -51,7 +51,6 @@ CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope" CONF_NOX = "nox" CONF_STD_INITIAL = "std_initial" -CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" CONF_TIME_CONSTANT = "time_constant" CONF_VOC = "voc" CONF_VOC_BASELINE = "voc_baseline" diff --git a/esphome/components/ufire_ec/sensor.py b/esphome/components/ufire_ec/sensor.py index 9602d0c2d0..944fdfdee9 100644 --- a/esphome/components/ufire_ec/sensor.py +++ b/esphome/components/ufire_ec/sensor.py @@ -1,11 +1,12 @@ -import esphome.codegen as cg from esphome import automation -import esphome.config_validation as cv +import esphome.codegen as cg from esphome.components import i2c, sensor +import esphome.config_validation as cv from esphome.const import ( - CONF_ID, CONF_EC, + CONF_ID, CONF_TEMPERATURE, + CONF_TEMPERATURE_COMPENSATION, DEVICE_CLASS_EMPTY, DEVICE_CLASS_TEMPERATURE, ICON_EMPTY, @@ -18,7 +19,6 @@ DEPENDENCIES = ["i2c"] CONF_SOLUTION = "solution" CONF_TEMPERATURE_SENSOR = "temperature_sensor" -CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient" ufire_ec_ns = cg.esphome_ns.namespace("ufire_ec") diff --git a/esphome/const.py b/esphome/const.py index b9397aa1bd..4d3818cb23 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -864,6 +864,7 @@ CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topi CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic" CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic" CONF_TEMPERATURE = "temperature" +CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" CONF_TEMPERATURE_OFFSET = "temperature_offset" CONF_TEMPERATURE_SOURCE = "temperature_source" CONF_TEMPERATURE_STEP = "temperature_step" From 517f659da86e111e5c203564aa91f54471e3ceca Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:23:30 +1100 Subject: [PATCH 280/282] [lvgl] Fix image `mode` property (Bugfix) (#7938) --- esphome/components/lvgl/widgets/img.py | 2 +- tests/components/lvgl/lvgl-package.yaml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/widgets/img.py b/esphome/components/lvgl/widgets/img.py index 59b2c97c63..46077190d0 100644 --- a/esphome/components/lvgl/widgets/img.py +++ b/esphome/components/lvgl/widgets/img.py @@ -79,7 +79,7 @@ class ImgType(WidgetType): if CONF_ANTIALIAS in config: lv.img_set_antialias(w.obj, config[CONF_ANTIALIAS]) if mode := config.get(CONF_MODE): - lv.img_set_mode(w.obj, mode) + await w.set_property("size_mode", mode) img_spec = ImgType() diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 4b7e13db91..556ce105ee 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -451,6 +451,7 @@ lvgl: src: cat_image align: top_left y: "50" + mode: real - tileview: id: tileview_id scrollbar_mode: active @@ -647,6 +648,7 @@ lvgl: grid_cell_row_pos: 0 grid_cell_column_pos: 0 src: !lambda return dog_image; + mode: virtual on_click: then: - lvgl.tabview.select: From bb27eaaf1e58316c6124f2d16a1fba6b0a033141 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:25:29 +1100 Subject: [PATCH 281/282] [lvgl] Add `on_change` event (#7939) --- esphome/components/lvgl/defines.py | 1 + tests/components/lvgl/lvgl-package.yaml | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 5371f110a6..02323f9655 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -168,6 +168,7 @@ LV_EVENT_MAP = { "READY": "READY", "CANCEL": "CANCEL", "ALL_EVENTS": "ALL", + "CHANGE": "VALUE_CHANGED", } LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP) diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 556ce105ee..b1b89adfe0 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -165,6 +165,11 @@ lvgl: - Nov - Dec selected_index: 1 + on_change: + then: + - logger.log: + format: "Roller changed = %d: %s" + args: [x, text.c_str()] on_value: then: - logger.log: From 444e162c92c3148a14493f66d3de256679a6eedf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 10 Dec 2024 19:39:00 +1300 Subject: [PATCH 282/282] Synchronise esp32 boards with platform version 51.03.07 (#7945) --- esphome/components/esp32/boards.py | 236 +++++++++++++++++++++++++++-- 1 file changed, 222 insertions(+), 14 deletions(-) diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index 02744ecb6f..81400eb9c3 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -1,4 +1,12 @@ -from .const import VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3 +from .const import ( + VARIANT_ESP32, + VARIANT_ESP32C2, + VARIANT_ESP32C3, + VARIANT_ESP32C6, + VARIANT_ESP32H2, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) ESP32_BASE_PINS = { "TX": 1, @@ -1344,6 +1352,26 @@ done | sort """ BOARDS = { + "4d_systems_esp32s3_gen4_r8n16": { + "name": "4D Systems GEN4-ESP32 16MB (ESP32S3-R8N16)", + "variant": VARIANT_ESP32S3, + }, + "adafruit_camera_esp32s3": { + "name": "Adafruit pyCamera S3", + "variant": VARIANT_ESP32S3, + }, + "adafruit_feather_esp32c6": { + "name": "Adafruit Feather ESP32-C6", + "variant": VARIANT_ESP32C6, + }, + "adafruit_feather_esp32s2": { + "name": "Adafruit Feather ESP32-S2", + "variant": VARIANT_ESP32S2, + }, + "adafruit_feather_esp32s2_reversetft": { + "name": "Adafruit Feather ESP32-S2 Reverse TFT", + "variant": VARIANT_ESP32S2, + }, "adafruit_feather_esp32s2_tft": { "name": "Adafruit Feather ESP32-S2 TFT", "variant": VARIANT_ESP32S2, @@ -1356,6 +1384,10 @@ BOARDS = { "name": "Adafruit Feather ESP32-S3 No PSRAM", "variant": VARIANT_ESP32S3, }, + "adafruit_feather_esp32s3_reversetft": { + "name": "Adafruit Feather ESP32-S3 Reverse TFT", + "variant": VARIANT_ESP32S3, + }, "adafruit_feather_esp32s3_tft": { "name": "Adafruit Feather ESP32-S3 TFT", "variant": VARIANT_ESP32S3, @@ -1376,10 +1408,18 @@ BOARDS = { "name": "Adafruit MagTag 2.9", "variant": VARIANT_ESP32S2, }, + "adafruit_matrixportal_esp32s3": { + "name": "Adafruit MatrixPortal ESP32-S3", + "variant": VARIANT_ESP32S3, + }, "adafruit_metro_esp32s2": { "name": "Adafruit Metro ESP32-S2", "variant": VARIANT_ESP32S2, }, + "adafruit_metro_esp32s3": { + "name": "Adafruit Metro ESP32-S3", + "variant": VARIANT_ESP32S3, + }, "adafruit_qtpy_esp32c3": { "name": "Adafruit QT Py ESP32-C3", "variant": VARIANT_ESP32C3, @@ -1392,10 +1432,18 @@ BOARDS = { "name": "Adafruit QT Py ESP32-S2", "variant": VARIANT_ESP32S2, }, + "adafruit_qtpy_esp32s3_n4r2": { + "name": "Adafruit QT Py ESP32-S3 (4M Flash 2M PSRAM)", + "variant": VARIANT_ESP32S3, + }, "adafruit_qtpy_esp32s3_nopsram": { "name": "Adafruit QT Py ESP32-S3 No PSRAM", "variant": VARIANT_ESP32S3, }, + "adafruit_qualia_s3_rgb666": { + "name": "Adafruit Qualia ESP32-S3 RGB666", + "variant": VARIANT_ESP32S3, + }, "airm2m_core_esp32c3": { "name": "AirM2M CORE ESP32C3", "variant": VARIANT_ESP32C3, @@ -1404,14 +1452,30 @@ BOARDS = { "name": "ALKS ESP32", "variant": VARIANT_ESP32, }, + "arduino_nano_esp32": { + "name": "Arduino Nano ESP32", + "variant": VARIANT_ESP32S3, + }, + "atd147_s3": { + "name": "ArtronShop ATD1.47-S3", + "variant": VARIANT_ESP32S3, + }, "atmegazero_esp32s2": { "name": "EspinalLab ATMegaZero ESP32-S2", "variant": VARIANT_ESP32S2, }, + "aventen_s3_sync": { + "name": "Aventen S3 Sync", + "variant": VARIANT_ESP32S3, + }, "az-delivery-devkit-v4": { "name": "AZ-Delivery ESP-32 Dev Kit C V4", "variant": VARIANT_ESP32, }, + "bee_data_logger": { + "name": "Smart Bee Data Logger", + "variant": VARIANT_ESP32S3, + }, "bee_motion_mini": { "name": "Smart Bee Motion Mini", "variant": VARIANT_ESP32C3, @@ -1436,14 +1500,6 @@ BOARDS = { "name": "BPI-Leaf-S3", "variant": VARIANT_ESP32S3, }, - "briki_abc_esp32": { - "name": "Briki ABC (MBC-WB) - ESP32", - "variant": VARIANT_ESP32, - }, - "briki_mbc-wb_esp32": { - "name": "Briki MBC-WB - ESP32", - "variant": VARIANT_ESP32, - }, "cnrs_aw2eth": { "name": "CNRS AW2ETH", "variant": VARIANT_ESP32, @@ -1496,18 +1552,38 @@ BOARDS = { "name": "DFRobot Beetle ESP32-C3", "variant": VARIANT_ESP32C3, }, + "dfrobot_firebeetle2_esp32e": { + "name": "DFRobot Firebeetle 2 ESP32-E", + "variant": VARIANT_ESP32, + }, "dfrobot_firebeetle2_esp32s3": { "name": "DFRobot Firebeetle 2 ESP32-S3", "variant": VARIANT_ESP32S3, }, + "dfrobot_romeo_esp32s3": { + "name": "DFRobot Romeo ESP32-S3", + "variant": VARIANT_ESP32S3, + }, "dpu_esp32": { "name": "TAMC DPU ESP32", "variant": VARIANT_ESP32, }, + "edgebox-esp-100": { + "name": "Seeed Studio Edgebox-ESP-100", + "variant": VARIANT_ESP32S3, + }, "esp320": { "name": "Electronic SweetPeas ESP320", "variant": VARIANT_ESP32, }, + "esp32-c2-devkitm-1": { + "name": "Espressif ESP32-C2-DevKitM-1", + "variant": VARIANT_ESP32C2, + }, + "esp32-c3-devkitc-02": { + "name": "Espressif ESP32-C3-DevKitC-02", + "variant": VARIANT_ESP32C3, + }, "esp32-c3-devkitm-1": { "name": "Espressif ESP32-C3-DevKitM-1", "variant": VARIANT_ESP32C3, @@ -1516,6 +1592,14 @@ BOARDS = { "name": "Ai-Thinker ESP-C3-M1-I-Kit", "variant": VARIANT_ESP32C3, }, + "esp32-c6-devkitc-1": { + "name": "Espressif ESP32-C6-DevKitC-1", + "variant": VARIANT_ESP32C6, + }, + "esp32-c6-devkitm-1": { + "name": "Espressif ESP32-C6-DevKitM-1", + "variant": VARIANT_ESP32C6, + }, "esp32cam": { "name": "AI Thinker ESP32-CAM", "variant": VARIANT_ESP32, @@ -1544,6 +1628,14 @@ BOARDS = { "name": "OLIMEX ESP32-GATEWAY", "variant": VARIANT_ESP32, }, + "esp32-h2-devkitm-1": { + "name": "Espressif ESP32-H2-DevKit", + "variant": VARIANT_ESP32H2, + }, + "esp32-pico-devkitm-2": { + "name": "Espressif ESP32-PICO-DevKitM-2", + "variant": VARIANT_ESP32, + }, "esp32-poe-iso": { "name": "OLIMEX ESP32-PoE-ISO", "variant": VARIANT_ESP32, @@ -1580,10 +1672,22 @@ BOARDS = { "name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)", "variant": VARIANT_ESP32S3, }, - "esp32-s3-korvo-2": { - "name": "Espressif ESP32-S3-Korvo-2", + "esp32-s3-devkitm-1": { + "name": "Espressif ESP32-S3-DevKitM-1", "variant": VARIANT_ESP32S3, }, + "esp32s3_powerfeather": { + "name": "ESP32-S3 PowerFeather", + "variant": VARIANT_ESP32S3, + }, + "esp32s3usbotg": { + "name": "Espressif ESP32-S3-USB-OTG", + "variant": VARIANT_ESP32S3, + }, + "esp32-solo1": { + "name": "Espressif Generic ESP32-solo1 4M Flash", + "variant": VARIANT_ESP32, + }, "esp32thing": { "name": "SparkFun ESP32 Thing", "variant": VARIANT_ESP32, @@ -1652,9 +1756,9 @@ BOARDS = { "name": "Heltec WiFi Kit 32", "variant": VARIANT_ESP32, }, - "heltec_wifi_kit_32_v2": { - "name": "Heltec WiFi Kit 32 (V2)", - "variant": VARIANT_ESP32, + "heltec_wifi_kit_32_V3": { + "name": "Heltec WiFi Kit 32 (V3)", + "variant": VARIANT_ESP32S3, }, "heltec_wifi_lora_32": { "name": "Heltec WiFi LoRa 32", @@ -1664,6 +1768,10 @@ BOARDS = { "name": "Heltec WiFi LoRa 32 (V2)", "variant": VARIANT_ESP32, }, + "heltec_wifi_lora_32_V3": { + "name": "Heltec WiFi LoRa 32 (V3)", + "variant": VARIANT_ESP32S3, + }, "heltec_wireless_stick_lite": { "name": "Heltec Wireless Stick Lite", "variant": VARIANT_ESP32, @@ -1708,6 +1816,14 @@ BOARDS = { "name": "oddWires IoT-Bus Proteus", "variant": VARIANT_ESP32, }, + "ioxesp32": { + "name": "ArtronShop IOXESP32", + "variant": VARIANT_ESP32, + }, + "ioxesp32ps": { + "name": "ArtronShop IOXESP32PS", + "variant": VARIANT_ESP32, + }, "kb32-ft": { "name": "MakerAsia KB32-FT", "variant": VARIANT_ESP32, @@ -1720,10 +1836,26 @@ BOARDS = { "name": "Labplus mPython", "variant": VARIANT_ESP32, }, + "lilka_v2": { + "name": "Lilka v2", + "variant": VARIANT_ESP32S3, + }, + "lilygo-t-display": { + "name": "LilyGo T-Display", + "variant": VARIANT_ESP32, + }, + "lilygo-t-display-s3": { + "name": "LilyGo T-Display-S3", + "variant": VARIANT_ESP32S3, + }, "lionbit": { "name": "Lion:Bit Dev Board", "variant": VARIANT_ESP32, }, + "lionbits3": { + "name": "Lion:Bit S3 STEM Dev Board", + "variant": VARIANT_ESP32S3, + }, "lolin32_lite": { "name": "WEMOS LOLIN32 Lite", "variant": VARIANT_ESP32, @@ -1752,10 +1884,18 @@ BOARDS = { "name": "WEMOS LOLIN S2 PICO", "variant": VARIANT_ESP32S2, }, + "lolin_s3_mini": { + "name": "WEMOS LOLIN S3 Mini", + "variant": VARIANT_ESP32S3, + }, "lolin_s3": { "name": "WEMOS LOLIN S3", "variant": VARIANT_ESP32S3, }, + "lolin_s3_pro": { + "name": "WEMOS LOLIN S3 PRO", + "variant": VARIANT_ESP32S3, + }, "lopy4": { "name": "Pycom LoPy4", "variant": VARIANT_ESP32, @@ -1768,10 +1908,18 @@ BOARDS = { "name": "M5Stack-ATOM", "variant": VARIANT_ESP32, }, + "m5stack-atoms3": { + "name": "M5Stack AtomS3", + "variant": VARIANT_ESP32S3, + }, "m5stack-core2": { "name": "M5Stack Core2", "variant": VARIANT_ESP32, }, + "m5stack-core-esp32-16M": { + "name": "M5Stack Core ESP32 16M", + "variant": VARIANT_ESP32, + }, "m5stack-core-esp32": { "name": "M5Stack Core ESP32", "variant": VARIANT_ESP32, @@ -1780,6 +1928,10 @@ BOARDS = { "name": "M5Stack-Core Ink", "variant": VARIANT_ESP32, }, + "m5stack-cores3": { + "name": "M5Stack CoreS3", + "variant": VARIANT_ESP32S3, + }, "m5stack-fire": { "name": "M5Stack FIRE", "variant": VARIANT_ESP32, @@ -1788,6 +1940,14 @@ BOARDS = { "name": "M5Stack GREY ESP32", "variant": VARIANT_ESP32, }, + "m5stack_paper": { + "name": "M5Stack Paper", + "variant": VARIANT_ESP32, + }, + "m5stack-stamps3": { + "name": "M5Stack StampS3", + "variant": VARIANT_ESP32S3, + }, "m5stack-station": { "name": "M5Stack Station", "variant": VARIANT_ESP32, @@ -1796,6 +1956,10 @@ BOARDS = { "name": "M5Stack Timer CAM", "variant": VARIANT_ESP32, }, + "m5stamp-pico": { + "name": "M5Stamp-Pico", + "variant": VARIANT_ESP32, + }, "m5stick-c": { "name": "M5Stick-C", "variant": VARIANT_ESP32, @@ -1832,10 +1996,26 @@ BOARDS = { "name": "Deparment of Alchemy MiniMain ESP32-S2", "variant": VARIANT_ESP32S2, }, + "motorgo_mini_1": { + "name": "MotorGo Mini 1 (ESP32-S3)", + "variant": VARIANT_ESP32S3, + }, + "namino_arancio": { + "name": "Namino Arancio", + "variant": VARIANT_ESP32S3, + }, + "namino_rosso": { + "name": "Namino Rosso", + "variant": VARIANT_ESP32S3, + }, "nano32": { "name": "MakerAsia Nano32", "variant": VARIANT_ESP32, }, + "nebulas3": { + "name": "Kinetic Dynamics Nebula S3", + "variant": VARIANT_ESP32S3, + }, "nina_w10": { "name": "u-blox NINA-W10 series", "variant": VARIANT_ESP32, @@ -1896,10 +2076,22 @@ BOARDS = { "name": "Munich Labs RedPill ESP32-S3", "variant": VARIANT_ESP32S3, }, + "roboheart_hercules": { + "name": "RoboHeart Hercules", + "variant": VARIANT_ESP32, + }, "seeed_xiao_esp32c3": { "name": "Seeed Studio XIAO ESP32C3", "variant": VARIANT_ESP32C3, }, + "seeed_xiao_esp32s3": { + "name": "Seeed Studio XIAO ESP32S3", + "variant": VARIANT_ESP32S3, + }, + "sensebox_mcu_esp32s2": { + "name": "senseBox MCU-S2 ESP32-S2", + "variant": VARIANT_ESP32S2, + }, "sensesiot_weizen": { "name": "LOGISENSES Senses Weizen", "variant": VARIANT_ESP32, @@ -1912,6 +2104,10 @@ BOARDS = { "name": "S.ODI Ultra v1", "variant": VARIANT_ESP32, }, + "sparkfun_esp32c6_thing_plus": { + "name": "Sparkfun ESP32-C6 Thing Plus", + "variant": VARIANT_ESP32C6, + }, "sparkfun_esp32_iot_redboard": { "name": "SparkFun ESP32 IoT RedBoard", "variant": VARIANT_ESP32, @@ -2004,6 +2200,10 @@ BOARDS = { "name": "Unexpected Maker FeatherS3", "variant": VARIANT_ESP32S3, }, + "um_nanos3": { + "name": "Unexpected Maker NanoS3", + "variant": VARIANT_ESP32S3, + }, "um_pros3": { "name": "Unexpected Maker PROS3", "variant": VARIANT_ESP32S3, @@ -2040,6 +2240,14 @@ BOARDS = { "name": "uPesy ESP32 Wrover DevKit", "variant": VARIANT_ESP32, }, + "valtrack_v4_mfw_esp32_c3": { + "name": "Valetron Systems VALTRACK-V4MVF", + "variant": VARIANT_ESP32C3, + }, + "valtrack_v4_vts_esp32_c3": { + "name": "Valetron Systems VALTRACK-V4VTS", + "variant": VARIANT_ESP32C3, + }, "vintlabs-devkit-v1": { "name": "VintLabs ESP32 Devkit", "variant": VARIANT_ESP32,