diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 2b4539105b..91c02b0a17 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -46,7 +46,7 @@ jobs: with: python-version: "3.9" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.5.0 + uses: docker/setup-buildx-action@v3.6.1 - name: Set up QEMU uses: docker/setup-qemu-action@v3.2.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index efec556059..d454076c84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,7 +90,7 @@ jobs: python-version: "3.9" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.5.0 + uses: docker/setup-buildx-action@v3.6.1 - name: Set up QEMU if: matrix.platform != 'linux/amd64' uses: docker/setup-qemu-action@v3.2.0 @@ -184,7 +184,7 @@ jobs: merge-multiple: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.5.0 + uses: docker/setup-buildx-action@v3.6.1 - name: Log in to docker hub if: matrix.registry == 'dockerhub' diff --git a/CODEOWNERS b/CODEOWNERS index 533473ce49..c321743d81 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -428,6 +428,7 @@ esphome/components/veml7700/* @latonita esphome/components/version/* @esphome/core esphome/components/voice_assistant/* @jesserockz esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 +esphome/components/watchdog/* @oarcher esphome/components/waveshare_epaper/* @clydebarrow esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_idf/* @dentra diff --git a/esphome/__main__.py b/esphome/__main__.py index b13f96daf7..7237a04717 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -1,12 +1,12 @@ # PYTHON_ARGCOMPLETE_OK import argparse +from datetime import datetime import functools import logging import os import re import sys import time -from datetime import datetime import argcomplete @@ -39,14 +39,14 @@ from esphome.const import ( ) from esphome.core import CORE, EsphomeError, coroutine from esphome.helpers import indent, is_ip_address +from esphome.log import Fore, color, setup_log from esphome.util import ( + get_serial_ports, + list_yaml_files, run_external_command, run_external_process, safe_print, - list_yaml_files, - get_serial_ports, ) -from esphome.log import color, setup_log, Fore _LOGGER = logging.getLogger(__name__) @@ -116,6 +116,7 @@ def get_port_type(port): def run_miniterm(config, port): import serial + from esphome import platformio_api if CONF_LOGGER not in config: @@ -596,9 +597,10 @@ def command_update_all(args): def command_idedata(args, config): - from esphome import platformio_api import json + from esphome import platformio_api + logging.disable(logging.INFO) logging.disable(logging.WARNING) @@ -747,7 +749,14 @@ def parse_args(argv): ) parser = argparse.ArgumentParser( - description=f"ESPHome v{const.__version__}", parents=[options_parser] + description=f"ESPHome {const.__version__}", parents=[options_parser] + ) + + parser.add_argument( + "--version", + action="version", + version=f"Version: {const.__version__}", + help="Print the ESPHome version and exit.", ) mqtt_options = argparse.ArgumentParser(add_help=False) @@ -948,67 +957,6 @@ def parse_args(argv): # a deprecation warning). arguments = argv[1:] - # On Python 3.9+ we can simply set exit_on_error=False in the constructor - def _raise(x): - raise argparse.ArgumentError(None, x) - - # First, try new-style parsing, but don't exit in case of failure - try: - # duplicate parser so that we can use the original one to raise errors later on - current_parser = argparse.ArgumentParser(add_help=False, parents=[parser]) - current_parser.set_defaults(deprecated_argv_suggestion=None) - current_parser.error = _raise - return current_parser.parse_args(arguments) - except argparse.ArgumentError: - pass - - # Second, try compat parsing and rearrange the command-line if it succeeds - # Disable argparse's built-in help option and add it manually to prevent this - # parser from printing the help messagefor the old format when invoked with -h. - compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False) - compat_parser.add_argument("-h", "--help", action="store_true") - compat_parser.add_argument("configuration", nargs="*") - compat_parser.add_argument( - "command", - choices=[ - "config", - "compile", - "upload", - "logs", - "run", - "clean-mqtt", - "wizard", - "mqtt-fingerprint", - "version", - "clean", - "dashboard", - "vscode", - "update-all", - ], - ) - - try: - compat_parser.error = _raise - result, unparsed = compat_parser.parse_known_args(argv[1:]) - last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration) - unparsed = [ - "--device" if arg in ("--upload-port", "--serial-port") else arg - for arg in unparsed - ] - arguments = ( - arguments[0:last_option] - + [result.command] - + result.configuration - + unparsed - ) - deprecated_argv_suggestion = arguments - except argparse.ArgumentError: - # old-style parsing failed, don't suggest any argument - deprecated_argv_suggestion = None - - # Finally, run the new-style parser again with the possibly swapped arguments, - # and let it error out if the command is unparsable. - parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion) argcomplete.autocomplete(parser) return parser.parse_args(arguments) @@ -1023,13 +971,6 @@ def run_esphome(argv): # Show timestamp for dashboard access logs args.command == "dashboard", ) - if args.deprecated_argv_suggestion is not None and args.command != "vscode": - _LOGGER.warning( - "Calling ESPHome with the configuration before the command is deprecated " - "and will be removed in the future. " - ) - _LOGGER.warning("Please instead use:") - _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion)) if sys.version_info < (3, 8, 0): _LOGGER.error( diff --git a/esphome/automation.py b/esphome/automation.py index b25ffa5abe..0bd6cf0af0 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -7,10 +7,10 @@ from esphome.const import ( CONF_ELSE, CONF_ID, CONF_THEN, + CONF_TIME, CONF_TIMEOUT, CONF_TRIGGER_ID, CONF_TYPE_ID, - CONF_TIME, CONF_UPDATE_INTERVAL, ) from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor diff --git a/esphome/codegen.py b/esphome/codegen.py index 6b000b53a1..bfa1683ce7 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -8,55 +8,78 @@ # want to break suddenly due to a rename (this file will get backports for features). # pylint: disable=unused-import -from esphome.cpp_generator import ( # noqa +from esphome.cpp_generator import ( # noqa: F401 + ArrayInitializer, Expression, + LineComment, + MockObj, + MockObjClass, + Pvariable, RawExpression, RawStatement, - TemplateArguments, - StructInitializer, - ArrayInitializer, - safe_exp, Statement, - LineComment, - progmem_array, - static_const_array, - statement, - variable, - with_local_variable, - new_variable, - Pvariable, - new_Pvariable, + StructInitializer, + TemplateArguments, add, - add_global, - add_library, add_build_flag, add_define, + add_global, + add_library, add_platformio_option, get_variable, get_variable_with_full_id, - process_lambda, is_template, + new_Pvariable, + new_variable, + process_lambda, + progmem_array, + safe_exp, + statement, + static_const_array, templatable, - MockObj, - MockObjClass, + variable, + with_local_variable, ) -from esphome.cpp_helpers import ( # noqa - gpio_pin_expression, - register_component, +from esphome.cpp_helpers import ( # noqa: F401 build_registry_entry, build_registry_list, extract_registry_entry_config, - register_parented, + gpio_pin_expression, past_safe_mode, + register_component, + register_parented, ) -from esphome.cpp_types import ( # noqa - global_ns, - void, - nullptr, - float_, - double, +from esphome.cpp_types import ( # noqa: F401 + NAN, + App, + Application, + Component, + ComponentPtr, + Controller, + EntityBase, + EntityCategory, + ESPTime, + GPIOPin, + InternalGPIOPin, + JsonObject, + JsonObjectConst, + Parented, + PollingComponent, + arduino_json_ns, bool_, + const_char_ptr, + double, + esphome_ns, + float_, + global_ns, + gpio_Flags, + int16, + int32, + int64, int_, + nullptr, + optional, + size_t, std_ns, std_shared_ptr, std_string, @@ -66,28 +89,5 @@ from esphome.cpp_types import ( # noqa uint16, uint32, uint64, - int16, - int32, - int64, - size_t, - const_char_ptr, - NAN, - esphome_ns, - App, - EntityBase, - Component, - ComponentPtr, - PollingComponent, - Application, - optional, - arduino_json_ns, - JsonObject, - JsonObjectConst, - Controller, - GPIOPin, - InternalGPIOPin, - gpio_Flags, - EntityCategory, - Parented, - ESPTime, + void, ) diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 34b9868edc..6bf4ff739e 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -1,7 +1,8 @@ -import esphome.codegen as cg -import esphome.config_validation as cv +from esphome import automation from esphome.automation import maybe_simple_id -from esphome.components import esp32_ble_tracker, esp32_ble_client +import esphome.codegen as cg +from esphome.components import esp32_ble_client, esp32_ble_tracker +import esphome.config_validation as cv from esphome.const import ( CONF_CHARACTERISTIC_UUID, CONF_ID, @@ -13,7 +14,6 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_VALUE, ) -from esphome import automation AUTO_LOAD = ["esp32_ble_client"] CODEOWNERS = ["@buxtronix", "@clydebarrow"] diff --git a/esphome/components/ble_client/output/__init__.py b/esphome/components/ble_client/output/__init__.py index fd847d80b8..729885eb8b 100644 --- a/esphome/components/ble_client/output/__init__.py +++ b/esphome/components/ble_client/output/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import ble_client, esp32_ble_tracker, output +import esphome.config_validation as cv from esphome.const import CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_SERVICE_UUID from .. import ble_client_ns diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py index d0b27c30a9..0c48902a90 100644 --- a/esphome/components/ble_client/sensor/__init__.py +++ b/esphome/components/ble_client/sensor/__init__.py @@ -1,17 +1,18 @@ +from esphome import automation import esphome.codegen as cg +from esphome.components import ble_client, esp32_ble_tracker, sensor import esphome.config_validation as cv -from esphome.components import sensor, ble_client, esp32_ble_tracker from esphome.const import ( CONF_CHARACTERISTIC_UUID, CONF_LAMBDA, + CONF_SERVICE_UUID, CONF_TRIGGER_ID, CONF_TYPE, - CONF_SERVICE_UUID, DEVICE_CLASS_SIGNAL_STRENGTH, STATE_CLASS_MEASUREMENT, UNIT_DECIBEL_MILLIWATT, ) -from esphome import automation + from .. import ble_client_ns DEPENDENCIES = ["ble_client"] diff --git a/esphome/components/ble_client/switch/__init__.py b/esphome/components/ble_client/switch/__init__.py index 2304d65c01..70314e8f30 100644 --- a/esphome/components/ble_client/switch/__init__.py +++ b/esphome/components/ble_client/switch/__init__.py @@ -1,7 +1,8 @@ import esphome.codegen as cg +from esphome.components import ble_client, switch import esphome.config_validation as cv -from esphome.components import switch, ble_client from esphome.const import ICON_BLUETOOTH + from .. import ble_client_ns BLEClientSwitch = ble_client_ns.class_( diff --git a/esphome/components/ble_client/text_sensor/__init__.py b/esphome/components/ble_client/text_sensor/__init__.py index 7a93c4e4ae..479af1a57e 100644 --- a/esphome/components/ble_client/text_sensor/__init__.py +++ b/esphome/components/ble_client/text_sensor/__init__.py @@ -1,13 +1,14 @@ +from esphome import automation import esphome.codegen as cg +from esphome.components import ble_client, esp32_ble_tracker, text_sensor import esphome.config_validation as cv -from esphome.components import text_sensor, ble_client, esp32_ble_tracker from esphome.const import ( CONF_CHARACTERISTIC_UUID, CONF_ID, - CONF_TRIGGER_ID, CONF_SERVICE_UUID, + CONF_TRIGGER_ID, ) -from esphome import automation + from .. import ble_client_ns DEPENDENCIES = ["ble_client"] diff --git a/esphome/components/ble_presence/binary_sensor.py b/esphome/components/ble_presence/binary_sensor.py index 9c24a91a05..d1fdc80289 100644 --- a/esphome/components/ble_presence/binary_sensor.py +++ b/esphome/components/ble_presence/binary_sensor.py @@ -1,13 +1,13 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import binary_sensor, esp32_ble_tracker +import esphome.config_validation as cv from esphome.const import ( - CONF_MAC_ADDRESS, - CONF_SERVICE_UUID, CONF_IBEACON_MAJOR, CONF_IBEACON_MINOR, CONF_IBEACON_UUID, + CONF_MAC_ADDRESS, CONF_MIN_RSSI, + CONF_SERVICE_UUID, CONF_TIMEOUT, ) diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index 0543eb0578..e3ba1abfd7 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -1,12 +1,12 @@ import esphome.codegen as cg +from esphome.components import esp32_ble_tracker, sensor import esphome.config_validation as cv -from esphome.components import sensor, esp32_ble_tracker from esphome.const import ( CONF_IBEACON_MAJOR, CONF_IBEACON_MINOR, CONF_IBEACON_UUID, - CONF_SERVICE_UUID, CONF_MAC_ADDRESS, + CONF_SERVICE_UUID, DEVICE_CLASS_SIGNAL_STRENGTH, STATE_CLASS_MEASUREMENT, UNIT_DECIBEL_MILLIWATT, diff --git a/esphome/components/ble_scanner/text_sensor.py b/esphome/components/ble_scanner/text_sensor.py index 743403c6a4..96d71a0399 100644 --- a/esphome/components/ble_scanner/text_sensor.py +++ b/esphome/components/ble_scanner/text_sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg +from esphome.components import esp32_ble_tracker, text_sensor import esphome.config_validation as cv -from esphome.components import text_sensor, esp32_ble_tracker DEPENDENCIES = ["esp32_ble_tracker"] diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index bec1579d8e..5a4ba36666 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -1,8 +1,8 @@ -from esphome.components import esp32_ble_tracker, esp32_ble_client -import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ACTIVE, CONF_ID +from esphome.components import esp32_ble_client, esp32_ble_tracker from esphome.components.esp32 import add_idf_sdkconfig_option +import esphome.config_validation as cv +from esphome.const import CONF_ACTIVE, CONF_ID AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"] DEPENDENCIES = ["api", "esp32"] diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1effea708f..0a5dd46478 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -1,11 +1,12 @@ from dataclasses import dataclass -from typing import Union, Optional -from pathlib import Path import logging import os -import esphome.final_validate as fv +from pathlib import Path +from typing import Optional, Union -from esphome.helpers import copy_file_if_changed, write_file_if_changed, mkdir_p +from esphome import git +import esphome.codegen as cg +import esphome.config_validation as cv from esphome.const import ( CONF_ADVANCED, CONF_BOARD, @@ -15,6 +16,7 @@ from esphome.const import ( CONF_IGNORE_EFUSE_MAC_CRC, CONF_NAME, CONF_PATH, + CONF_PLATFORM_VERSION, CONF_PLATFORMIO_OPTIONS, CONF_REF, CONF_REFRESH, @@ -32,13 +34,12 @@ from esphome.const import ( TYPE_GIT, TYPE_LOCAL, __version__, - CONF_PLATFORM_VERSION, ) from esphome.core import CORE, HexInt, TimePeriod -import esphome.config_validation as cv -import esphome.codegen as cg -from esphome import git +import esphome.final_validate as fv +from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed +from .boards import BOARDS from .const import ( # noqa KEY_BOARD, KEY_COMPONENTS, @@ -54,12 +55,10 @@ from .const import ( # noqa VARIANT_FRIENDLY, VARIANTS, ) -from .boards import BOARDS # force import gpio to register pin schema from .gpio import esp32_pin_to_code # noqa - _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["preferences"] diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index cd85f3da97..60abcd447c 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -1,4 +1,4 @@ -from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3, VARIANT_ESP32S3 +from .const import VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3 ESP32_BASE_PINS = { "TX": 1, diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 0d9cb5daf0..558ff51af8 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -1,22 +1,22 @@ from dataclasses import dataclass -from typing import Any import logging +from typing import Any +from esphome import pins +import esphome.codegen as cg +import esphome.config_validation as cv from esphome.const import ( CONF_ID, + CONF_IGNORE_PIN_VALIDATION_ERROR, + CONF_IGNORE_STRAPPING_WARNING, CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_OPEN_DRAIN, CONF_OUTPUT, - CONF_IGNORE_PIN_VALIDATION_ERROR, - CONF_IGNORE_STRAPPING_WARNING, PLATFORM_ESP32, ) -from esphome import pins from esphome.core import CORE -import esphome.config_validation as cv -import esphome.codegen as cg from . import boards from .const import ( @@ -24,22 +24,21 @@ from .const import ( KEY_ESP32, KEY_VARIANT, VARIANT_ESP32, - VARIANT_ESP32C3, - VARIANT_ESP32S2, - VARIANT_ESP32S3, VARIANT_ESP32C2, + VARIANT_ESP32C3, VARIANT_ESP32C6, VARIANT_ESP32H2, + VARIANT_ESP32S2, + VARIANT_ESP32S3, esp32_ns, ) - from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports -from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports -from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports -from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports from .gpio_esp32_c2 import esp32_c2_validate_gpio_pin, esp32_c2_validate_supports +from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports from .gpio_esp32_c6 import esp32_c6_validate_gpio_pin, esp32_c6_validate_supports from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports +from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports +from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports ESP32InternalGPIOPin = esp32_ns.class_("ESP32InternalGPIOPin", cg.InternalGPIOPin) diff --git a/esphome/components/esp32/gpio_esp32.py b/esphome/components/esp32/gpio_esp32.py index d10b266c7a..e4d3b6aaf3 100644 --- a/esphome/components/esp32/gpio_esp32.py +++ b/esphome/components/esp32/gpio_esp32.py @@ -1,5 +1,6 @@ import logging +import esphome.config_validation as cv from esphome.const import ( CONF_INPUT, CONF_MODE, @@ -8,10 +9,8 @@ from esphome.const import ( CONF_PULLDOWN, CONF_PULLUP, ) -import esphome.config_validation as cv from esphome.pins import check_strapping_pin - _ESP_SDIO_PINS = { 6: "Flash Clock", 7: "Flash Data 0", diff --git a/esphome/components/esp32/gpio_esp32_c2.py b/esphome/components/esp32/gpio_esp32_c2.py index 0bee7d82bf..abdcb1b655 100644 --- a/esphome/components/esp32/gpio_esp32_c2.py +++ b/esphome/components/esp32/gpio_esp32_c2.py @@ -1,10 +1,9 @@ import logging +import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.pins import check_strapping_pin -import esphome.config_validation as cv - _ESP32C2_STRAPPING_PINS = {8, 9} _LOGGER = logging.getLogger(__name__) diff --git a/esphome/components/esp32/gpio_esp32_c3.py b/esphome/components/esp32/gpio_esp32_c3.py index 6c70c09f9e..5b9ec0ebd9 100644 --- a/esphome/components/esp32/gpio_esp32_c3.py +++ b/esphome/components/esp32/gpio_esp32_c3.py @@ -1,11 +1,7 @@ import logging -from esphome.const import ( - CONF_INPUT, - CONF_MODE, - CONF_NUMBER, -) import esphome.config_validation as cv +from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.pins import check_strapping_pin _ESP32C3_SPI_PSRAM_PINS = { diff --git a/esphome/components/esp32/gpio_esp32_c6.py b/esphome/components/esp32/gpio_esp32_c6.py index a1f777c625..bc735f85c4 100644 --- a/esphome/components/esp32/gpio_esp32_c6.py +++ b/esphome/components/esp32/gpio_esp32_c6.py @@ -1,8 +1,7 @@ import logging -from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER - import esphome.config_validation as cv +from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.pins import check_strapping_pin _ESP32C6_SPI_PSRAM_PINS = { diff --git a/esphome/components/esp32/gpio_esp32_h2.py b/esphome/components/esp32/gpio_esp32_h2.py index d18ee8a2a6..7413bf4db5 100644 --- a/esphome/components/esp32/gpio_esp32_h2.py +++ b/esphome/components/esp32/gpio_esp32_h2.py @@ -1,8 +1,7 @@ import logging -from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER - import esphome.config_validation as cv +from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER _ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21} diff --git a/esphome/components/esp32/gpio_esp32_s2.py b/esphome/components/esp32/gpio_esp32_s2.py index 82449532ec..331aeb9d94 100644 --- a/esphome/components/esp32/gpio_esp32_s2.py +++ b/esphome/components/esp32/gpio_esp32_s2.py @@ -1,5 +1,6 @@ import logging +import esphome.config_validation as cv from esphome.const import ( CONF_INPUT, CONF_MODE, @@ -8,8 +9,6 @@ from esphome.const import ( CONF_PULLDOWN, CONF_PULLUP, ) - -import esphome.config_validation as cv from esphome.pins import check_strapping_pin _ESP32S2_SPI_PSRAM_PINS = { diff --git a/esphome/components/esp32/gpio_esp32_s3.py b/esphome/components/esp32/gpio_esp32_s3.py index 8dcbf8c7bb..7120504693 100644 --- a/esphome/components/esp32/gpio_esp32_s3.py +++ b/esphome/components/esp32/gpio_esp32_s3.py @@ -1,12 +1,7 @@ import logging -from esphome.const import ( - CONF_INPUT, - CONF_MODE, - CONF_NUMBER, -) - import esphome.config_validation as cv +from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.pins import check_strapping_pin _ESP_32S3_SPI_PSRAM_PINS = { diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 472669a381..75cf9d707d 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -1,9 +1,9 @@ -import esphome.codegen as cg -import esphome.config_validation as cv 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.core import CORE -from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz", "@Rapsssito"] diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index d063209478..f97f289a0a 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -1,10 +1,10 @@ import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components.esp32_ble import CONF_BLE_ID -from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER -from esphome.core import CORE, TimePeriod -from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components import esp32_ble +from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.components.esp32_ble import CONF_BLE_ID +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_TX_POWER, CONF_TYPE, CONF_UUID +from esphome.core import CORE, TimePeriod AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] diff --git a/esphome/components/esp32_ble_client/__init__.py b/esphome/components/esp32_ble_client/__init__.py index 94a5576d0b..25957ed0da 100644 --- a/esphome/components/esp32_ble_client/__init__.py +++ b/esphome/components/esp32_ble_client/__init__.py @@ -1,5 +1,4 @@ import esphome.codegen as cg - from esphome.components import esp32_ble_tracker AUTO_LOAD = ["esp32_ble_tracker"] diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index ce9fdc2cf3..9da7d13999 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -1,9 +1,9 @@ import esphome.codegen as cg +from esphome.components import esp32_ble +from esphome.components.esp32 import add_idf_sdkconfig_option import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_MODEL -from esphome.components import esp32_ble from esphome.core import CORE -from esphome.components.esp32 import add_idf_sdkconfig_option AUTO_LOAD = ["esp32_ble"] CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 1edeaadbfd..0aa8eadd0a 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -1,10 +1,10 @@ import re -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import automation +import esphome.codegen as cg from esphome.components import esp32_ble from esphome.components.esp32 import add_idf_sdkconfig_option +import esphome.config_validation as cv from esphome.const import ( CONF_ACTIVE, CONF_DURATION, diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 697436415b..1c6acda724 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -1,6 +1,4 @@ from esphome import pins -import esphome.config_validation as cv -import esphome.final_validate as fv import esphome.codegen as cg from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant from esphome.components.esp32.const import ( @@ -8,31 +6,33 @@ from esphome.components.esp32.const import ( VARIANT_ESP32S2, VARIANT_ESP32S3, ) +from esphome.components.network import IPAddress +from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface +import esphome.config_validation as cv from esphome.const import ( - CONF_DOMAIN, - CONF_ID, - CONF_VALUE, - CONF_MANUAL_IP, - CONF_STATIC_IP, - CONF_TYPE, - CONF_USE_ADDRESS, - CONF_GATEWAY, - CONF_SUBNET, + CONF_ADDRESS, + CONF_CLK_PIN, + CONF_CS_PIN, CONF_DNS1, CONF_DNS2, - CONF_CLK_PIN, + CONF_DOMAIN, + CONF_GATEWAY, + CONF_ID, + CONF_INTERRUPT_PIN, + CONF_MANUAL_IP, CONF_MISO_PIN, CONF_MOSI_PIN, - CONF_CS_PIN, - CONF_INTERRUPT_PIN, + CONF_PAGE_ID, CONF_RESET_PIN, CONF_SPI, - CONF_PAGE_ID, - CONF_ADDRESS, + CONF_STATIC_IP, + CONF_SUBNET, + CONF_TYPE, + CONF_USE_ADDRESS, + CONF_VALUE, ) from esphome.core import CORE, coroutine_with_priority -from esphome.components.network import IPAddress -from esphome.components.spi import get_spi_interface, CONF_INTERFACE_INDEX +import esphome.final_validate as fv CONFLICTS_WITH = ["wifi"] DEPENDENCIES = ["esp32"] diff --git a/esphome/components/ethernet_info/text_sensor.py b/esphome/components/ethernet_info/text_sensor.py index a545475870..31da516e44 100644 --- a/esphome/components/ethernet_info/text_sensor.py +++ b/esphome/components/ethernet_info/text_sensor.py @@ -1,9 +1,9 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import text_sensor +import esphome.config_validation as cv from esphome.const import ( - CONF_IP_ADDRESS, CONF_DNS_ADDRESS, + CONF_IP_ADDRESS, CONF_MAC_ADDRESS, ENTITY_CATEGORY_DIAGNOSTIC, ) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 3ca97b9611..0407bbd326 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -14,7 +14,7 @@ from esphome.const import ( from esphome.core import CORE, Lambda DEPENDENCIES = ["network"] -AUTO_LOAD = ["json"] +AUTO_LOAD = ["json", "watchdog"] http_request_ns = cg.esphome_ns.namespace("http_request") HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component) diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp index 95b1cdc38e..2148d92ad2 100644 --- a/esphome/components/http_request/http_request_arduino.cpp +++ b/esphome/components/http_request/http_request_arduino.cpp @@ -3,12 +3,12 @@ #ifdef USE_ARDUINO #include "esphome/components/network/util.h" +#include "esphome/components/watchdog/watchdog.h" + #include "esphome/core/application.h" #include "esphome/core/defines.h" #include "esphome/core/log.h" -#include "watchdog.h" - namespace esphome { namespace http_request { diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index 4fe5585723..3819f5544e 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -3,6 +3,8 @@ #ifdef USE_ESP_IDF #include "esphome/components/network/util.h" +#include "esphome/components/watchdog/watchdog.h" + #include "esphome/core/application.h" #include "esphome/core/defines.h" #include "esphome/core/log.h" @@ -11,8 +13,6 @@ #include "esp_crt_bundle.h" #endif -#include "watchdog.h" - namespace esphome { namespace http_request { diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index dcc783ea47..1553de0bc1 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -1,11 +1,11 @@ #include "ota_http_request.h" -#include "../watchdog.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" #include "esphome/core/log.h" #include "esphome/components/md5/md5.h" +#include "esphome/components/watchdog/watchdog.h" #include "esphome/components/ota/ota_backend.h" #include "esphome/components/ota/ota_backend_arduino_esp32.h" #include "esphome/components/ota/ota_backend_arduino_esp8266.h" diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 2f3bd69546..c454a61957 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -16,13 +16,20 @@ from esphome.final_validate import full_config from esphome.helpers import write_file_if_changed from . import defines as df, helpers, lv_validation as lvalid +from .btn import btn_spec from .label import label_spec from .lvcode import ConstantLiteral, LvContext - -# from .menu import menu_spec from .obj import obj_spec -from .schemas import WIDGET_TYPES, any_widget_schema, obj_schema -from .types import FontEngine, LvglComponent, lv_disp_t_ptr, lv_font_t, lvgl_ns +from .schemas import any_widget_schema, obj_schema +from .touchscreens import touchscreen_schema, touchscreens_to_code +from .types import ( + WIDGET_TYPES, + FontEngine, + LvglComponent, + lv_disp_t_ptr, + lv_font_t, + lvgl_ns, +) from .widget import LvScrActType, Widget, add_widgets, set_obj_properties DOMAIN = "lvgl" @@ -31,11 +38,8 @@ AUTO_LOAD = ("key_provider",) CODEOWNERS = ("@clydebarrow",) LOGGER = logging.getLogger(__name__) -for widg in ( - label_spec, - obj_spec, -): - WIDGET_TYPES[widg.name] = widg +for w_type in (label_spec, obj_spec, btn_spec): + WIDGET_TYPES[w_type.name] = w_type lv_scr_act_spec = LvScrActType() lv_scr_act = Widget.create( @@ -93,7 +97,7 @@ def final_validation(config): "Using auto_clear_enabled: true in display config not compatible with LVGL" ) buffer_frac = config[CONF_BUFFER_SIZE] - if not CORE.is_host and buffer_frac > 0.5 and "psram" not in global_config: + 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") @@ -132,7 +136,7 @@ async def to_code(config): cg.add_global(lvgl_ns.using) lv_component = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(lv_component, config) - Widget.create(config[CONF_ID], lv_component, WIDGET_TYPES[df.CONF_OBJ], config) + Widget.create(config[CONF_ID], lv_component, obj_spec, config) for display in config[df.CONF_DISPLAYS]: cg.add(lv_component.add_display(await cg.get_variable(display))) @@ -152,7 +156,7 @@ async def to_code(config): await cg.get_variable(font) cg.new_Pvariable(ID(f"{font}_engine", True, type=FontEngine), MockObj(font)) default_font = config[df.CONF_DEFAULT_FONT] - if default_font not in helpers.lv_fonts_used: + if not lvalid.is_lv_font(default_font): add_define( "LV_FONT_CUSTOM_DECLARE", f"LV_FONT_DECLARE(*{df.DEFAULT_ESPHOME_FONT})" ) @@ -161,12 +165,15 @@ async def to_code(config): True, type=lv_font_t.operator("ptr").operator("const"), ) - cg.new_variable(globfont_id, MockObj(default_font)) + cg.new_variable( + globfont_id, MockObj(await lvalid.lv_font.process(default_font)) + ) add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT) else: - add_define("LV_FONT_DEFAULT", default_font) + add_define("LV_FONT_DEFAULT", await lvalid.lv_font.process(default_font)) with LvContext(): + await touchscreens_to_code(lv_component, config) await set_obj_properties(lv_scr_act, config) await add_widgets(lv_scr_act, config) Widget.set_completed() @@ -190,7 +197,7 @@ FINAL_VALIDATE_SCHEMA = final_validation CONFIG_SCHEMA = ( cv.polling_component_schema("1s") - .extend(obj_schema("obj")) + .extend(obj_schema(obj_spec)) .extend( { cv.GenerateID(CONF_ID): cv.declare_id(LvglComponent), @@ -207,6 +214,7 @@ CONFIG_SCHEMA = ( ), cv.Optional(df.CONF_WIDGETS): cv.ensure_list(WIDGET_SCHEMA), cv.Optional(df.CONF_TRANSPARENCY_KEY, default=0x000400): lvalid.lv_color, + cv.GenerateID(df.CONF_TOUCHSCREENS): touchscreen_schema, } ) ).add_extra(cv.has_at_least_one_key(CONF_PAGES, df.CONF_WIDGETS)) diff --git a/esphome/components/lvgl/btn.py b/esphome/components/lvgl/btn.py new file mode 100644 index 0000000000..4f5f88d9e6 --- /dev/null +++ b/esphome/components/lvgl/btn.py @@ -0,0 +1,25 @@ +from esphome.const import CONF_BUTTON +from esphome.cpp_generator import MockObjClass + +from .defines import CONF_MAIN +from .types import LvBoolean, WidgetType + + +class BtnType(WidgetType): + def __init__(self): + super().__init__(CONF_BUTTON, LvBoolean("lv_btn_t"), (CONF_MAIN,)) + + async def to_code(self, w, config): + return [] + + def obj_creator(self, parent: MockObjClass, config: dict): + """ + LVGL 8 calls buttons `btn` + """ + return f"lv_btn_create({parent})" + + def get_uses(self): + return ("btn",) + + +btn_spec = BtnType() diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 50bdac3865..a2b4ac13fb 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -446,6 +446,7 @@ CONF_TILE_ID = "tile_id" CONF_TILES = "tiles" CONF_TITLE = "title" CONF_TOP_LAYER = "top_layer" +CONF_TOUCHSCREENS = "touchscreens" CONF_TRANSPARENCY_KEY = "transparency_key" CONF_THEME = "theme" CONF_VISIBLE_ROW_COUNT = "visible_row_count" @@ -474,14 +475,8 @@ LV_KEYS = LvConstant( ) -# list of widgets and the parts allowed -WIDGET_PARTS = { - CONF_LABEL: (CONF_MAIN, CONF_SCROLLBAR, CONF_SELECTED), - CONF_OBJ: (CONF_MAIN,), -} - DEFAULT_ESPHOME_FONT = "esphome_lv_default_font" def join_enums(enums, prefix=""): - return "|".join(f"(int){prefix}{e.upper()}" for e in enums) + return ConstantLiteral("|".join(f"(int){prefix}{e.upper()}" for e in enums)) diff --git a/esphome/components/lvgl/helpers.py b/esphome/components/lvgl/helpers.py index c8d4948fb1..d67739155c 100644 --- a/esphome/components/lvgl/helpers.py +++ b/esphome/components/lvgl/helpers.py @@ -22,7 +22,6 @@ def add_lv_use(*names): lv_fonts_used = set() esphome_fonts_used = set() -REQUIRED_COMPONENTS = {} lvgl_components_required = set() diff --git a/esphome/components/lvgl/label.py b/esphome/components/lvgl/label.py index 5c4ae6ab0d..0498f39474 100644 --- a/esphome/components/lvgl/label.py +++ b/esphome/components/lvgl/label.py @@ -1,16 +1,27 @@ import esphome.config_validation as cv -from .defines import CONF_LABEL, CONF_LONG_MODE, CONF_RECOLOR, CONF_TEXT, LV_LONG_MODES +from .defines import ( + CONF_LABEL, + CONF_LONG_MODE, + CONF_MAIN, + CONF_RECOLOR, + CONF_SCROLLBAR, + CONF_SELECTED, + CONF_TEXT, + LV_LONG_MODES, +) from .lv_validation import lv_bool, lv_text from .schemas import TEXT_SCHEMA -from .types import lv_label_t -from .widget import Widget, WidgetType +from .types import LvText, WidgetType +from .widget import Widget class LabelType(WidgetType): def __init__(self): super().__init__( CONF_LABEL, + LvText("lv_label_t"), + (CONF_MAIN, CONF_SCROLLBAR, CONF_SELECTED), TEXT_SCHEMA.extend( { cv.Optional(CONF_RECOLOR): lv_bool, @@ -19,10 +30,6 @@ class LabelType(WidgetType): ), ) - @property - def w_type(self): - return lv_label_t - async def to_code(self, w: Widget, config): """For a text object, create and set text""" if value := config.get(CONF_TEXT): diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 1de63c30ce..533dc582f0 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -8,6 +8,7 @@ import esphome.config_validation as cv from esphome.const import CONF_ARGS, CONF_COLOR, CONF_FORMAT from esphome.core import HexInt from esphome.cpp_generator import MockObj +from esphome.cpp_types import uint32 from esphome.helpers import cpp_string_escape from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -23,6 +24,28 @@ from .lvcode import ConstantLiteral, lv_expr from .types import lv_font_t +def literal_mapper(value, args=()): + if isinstance(value, str): + return ConstantLiteral(value) + return value + + +opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") + + +@schema_extractor("one_of") +def opacity_validator(value): + if value == SCHEMA_EXTRACT: + return opacity_consts.choices + value = cv.Any(cv.percentage, opacity_consts.one_of)(value) + if isinstance(value, float): + return int(value * 255) + return value + + +opacity = LValidator(opacity_validator, uint32, retmapper=literal_mapper) + + @schema_extractor("one_of") def color(value): if value == SCHEMA_EXTRACT: @@ -43,16 +66,24 @@ def color_retmapper(value): return lv_expr.color_from(MockObj(value)) -def pixels_or_percent(value): +lv_color = LValidator(color, ty.lv_color_t, retmapper=color_retmapper) + + +def pixels_or_percent_validator(value): """A length in one axis - either a number (pixels) or a percentage""" if value == SCHEMA_EXTRACT: return ["pixels", "..%"] if isinstance(value, int): - return str(cv.int_(value)) + return cv.int_(value) # Will throw an exception if not a percentage. return f"lv_pct({int(cv.percentage(value) * 100)})" +pixels_or_percent = LValidator( + pixels_or_percent_validator, uint32, retmapper=literal_mapper +) + + def zoom(value): value = cv.float_range(0.1, 10.0)(value) return int(value * 256) @@ -68,7 +99,7 @@ def angle(value): @schema_extractor("one_of") -def size(value): +def size_validator(value): """A size in one axis - one of "size_content", a number (pixels) or a percentage""" if value == SCHEMA_EXTRACT: return ["size_content", "pixels", "..%"] @@ -79,28 +110,42 @@ def size(value): return "LV_SIZE_CONTENT" raise cv.Invalid("must be 'size_content', a pixel position or a percentage") if isinstance(value, int): - return str(cv.int_(value)) + return cv.int_(value) # Will throw an exception if not a percentage. return f"lv_pct({int(cv.percentage(value) * 100)})" +size = LValidator(size_validator, uint32, retmapper=literal_mapper) + +radius_consts = LvConstant("LV_RADIUS_", "CIRCLE") + + @schema_extractor("one_of") -def opacity(value): - consts = LvConstant("LV_OPA_", "TRANSP", "COVER") +def radius_validator(value): if value == SCHEMA_EXTRACT: - return consts.choices - value = cv.Any(cv.percentage, consts.one_of)(value) + return radius_consts.choices + value = cv.Any(size, cv.percentage, radius_consts.one_of)(value) if isinstance(value, float): return int(value * 255) return value +def id_name(value): + if value == SCHEMA_EXTRACT: + return "id" + return cv.validate_id_name(value) + + +radius = LValidator(radius_validator, uint32, retmapper=literal_mapper) + + def stop_value(value): return cv.int_range(0, 255)(value) -lv_color = LValidator(color, ty.lv_color_t, retmapper=color_retmapper) -lv_bool = LValidator(cv.boolean, cg.bool_, BinarySensor, "get_state()") +lv_bool = LValidator( + cv.boolean, cg.bool_, BinarySensor, "get_state()", retmapper=literal_mapper +) def lvms_validator_(value): @@ -145,26 +190,32 @@ lv_float = LValidator(cv.float_, cg.float_, Sensor, "get_state()") lv_int = LValidator(cv.int_, cg.int_, Sensor, "get_state()") +def is_lv_font(font): + return isinstance(font, str) and font.lower() in LV_FONTS + + class LvFont(LValidator): def __init__(self): def lv_builtin_font(value): fontval = cv.one_of(*LV_FONTS, lower=True)(value) lv_fonts_used.add(fontval) - return "&lv_font_" + fontval + return fontval def validator(value): if value == SCHEMA_EXTRACT: return LV_FONTS - if isinstance(value, str) and value.lower() in LV_FONTS: + if is_lv_font(value): return lv_builtin_font(value) fontval = cv.use_id(Font)(value) esphome_fonts_used.add(fontval) - return requires_component("font")(f"{fontval}_engine->get_lv_font()") + return requires_component("font")(fontval) super().__init__(validator, lv_font_t) async def process(self, value, args=()): - return ConstantLiteral(value) + if is_lv_font(value): + return ConstantLiteral(f"&lv_font_{value}") + return ConstantLiteral(f"{value}_engine->get_lv_font()") lv_font = LvFont() diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index bdaf8a4f18..74a1b0e7af 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -38,7 +38,9 @@ void LvglComponent::setup() { auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8; auto *buf = lv_custom_mem_alloc(buf_bytes); if (buf == nullptr) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR ESP_LOGE(TAG, "Malloc failed to allocate %zu bytes", buf_bytes); +#endif this->mark_failed(); this->status_set_error("Memory allocation failure"); return; @@ -85,7 +87,9 @@ size_t lv_millis(void) { return esphome::millis(); } void *lv_custom_mem_alloc(size_t size) { auto *ptr = malloc(size); // NOLINT if (ptr == nullptr) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR esphome::ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size); +#endif } return ptr; } @@ -102,7 +106,9 @@ void *lv_custom_mem_alloc(size_t size) { ptr = heap_caps_malloc(size, cap_bits); } if (ptr == nullptr) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR esphome::ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size); +#endif return nullptr; } #ifdef ESPHOME_LOG_HAS_VERBOSE diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 988c22917b..a884a27042 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -18,23 +18,27 @@ #ifdef USE_LVGL_FONT #include "esphome/components/font/font.h" #endif +#ifdef USE_LVGL_TOUCHSCREEN +#include "esphome/components/touchscreen/touchscreen.h" +#endif // USE_LVGL_TOUCHSCREEN + namespace esphome { namespace lvgl { extern lv_event_code_t lv_custom_event; // NOLINT #ifdef USE_LVGL_COLOR -static lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); } -#endif +inline lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); } +#endif // USE_LVGL_COLOR #if LV_COLOR_DEPTH == 16 static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565; #elif LV_COLOR_DEPTH == 32 static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_888; -#else +#else // LV_COLOR_DEPTH static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_332; -#endif +#endif // LV_COLOR_DEPTH // Parent class for things that wrap an LVGL object -class LvCompound { +class LvCompound final { public: virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; } lv_obj_t *obj{}; @@ -99,6 +103,14 @@ class LvglComponent : public PollingComponent { void set_full_refresh(bool full_refresh) { this->full_refresh_ = full_refresh; } void set_buffer_frac(size_t frac) { this->buffer_frac_ = frac; } lv_disp_t *get_disp() { return this->disp_; } + void set_paused(bool paused, bool show_snow) { + this->paused_ = paused; + if (!paused && lv_scr_act() != nullptr) { + lv_disp_trig_activity(this->disp_); // resets the inactivity time + lv_obj_invalidate(lv_scr_act()); + } + } + bool is_paused() const { return this->paused_; } protected: void draw_buffer_(const lv_area_t *area, const uint8_t *ptr); @@ -107,13 +119,48 @@ class LvglComponent : public PollingComponent { lv_disp_draw_buf_t draw_buf_{}; lv_disp_drv_t disp_drv_{}; lv_disp_t *disp_{}; + bool paused_{}; std::vector> init_lambdas_; size_t buffer_frac_{1}; bool full_refresh_{}; }; +#ifdef USE_LVGL_TOUCHSCREEN +class LVTouchListener : public touchscreen::TouchListener, public Parented { + public: + LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time) { + lv_indev_drv_init(&this->drv_); + 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; + this->drv_.user_data = this; + this->drv_.read_cb = [](lv_indev_drv_t *d, lv_indev_data_t *data) { + auto *l = static_cast(d->user_data); + if (l->touch_pressed_) { + data->point.x = l->touch_point_.x; + data->point.y = l->touch_point_.y; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + }; + } + void update(const touchscreen::TouchPoints_t &tpoints) override { + this->touch_pressed_ = !this->parent_->is_paused() && !tpoints.empty(); + if (this->touch_pressed_) + this->touch_point_ = tpoints[0]; + } + void release() override { touch_pressed_ = false; } + lv_indev_drv_t *get_drv() { return &this->drv_; } + + protected: + lv_indev_drv_t drv_{}; + touchscreen::TouchPoint touch_point_{}; + bool touch_pressed_{}; +}; +#endif // USE_LVGL_TOUCHSCREEN } // namespace lvgl } // namespace esphome -#endif +#endif // USE_LVGL diff --git a/esphome/components/lvgl/obj.py b/esphome/components/lvgl/obj.py index fba20bef36..92c4f63d2d 100644 --- a/esphome/components/lvgl/obj.py +++ b/esphome/components/lvgl/obj.py @@ -1,6 +1,5 @@ -from .defines import CONF_OBJ -from .types import lv_obj_t -from .widget import WidgetType +from .defines import CONF_MAIN, CONF_OBJ +from .types import WidgetType, lv_obj_t class ObjType(WidgetType): @@ -9,11 +8,7 @@ class ObjType(WidgetType): """ def __init__(self): - super().__init__(CONF_OBJ, schema={}, modify_schema={}) - - @property - def w_type(self): - return lv_obj_t + super().__init__(CONF_OBJ, lv_obj_t, (CONF_MAIN,), schema={}, modify_schema={}) async def to_code(self, w, config): return [] diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 4ae5824151..9f6d984545 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -3,15 +3,9 @@ from esphome.const import CONF_ARGS, CONF_FORMAT, CONF_ID, CONF_STATE, CONF_TYPE from esphome.schema_extractors import SCHEMA_EXTRACT from . import defines as df, lv_validation as lvalid, types as ty -from .defines import WIDGET_PARTS -from .helpers import ( - REQUIRED_COMPONENTS, - add_lv_use, - requires_component, - validate_printf, -) +from .helpers import add_lv_use, requires_component, validate_printf from .lv_validation import lv_font -from .types import WIDGET_TYPES, get_widget_type +from .types import WIDGET_TYPES, WidgetType # A schema for text properties TEXT_SCHEMA = cv.Schema( @@ -46,9 +40,9 @@ STYLE_PROPS = { "bg_dither_mode": df.LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF").one_of, "bg_grad_dir": df.LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER").one_of, "bg_grad_stop": lvalid.stop_value, - "bg_img_opa": lvalid.opacity, - "bg_img_recolor": lvalid.lv_color, - "bg_img_recolor_opa": lvalid.opacity, + "bg_image_opa": lvalid.opacity, + "bg_image_recolor": lvalid.lv_color, + "bg_image_recolor_opa": lvalid.opacity, "bg_main_stop": lvalid.stop_value, "bg_opa": lvalid.opacity, "border_color": lvalid.lv_color, @@ -60,8 +54,8 @@ STYLE_PROPS = { "border_width": cv.positive_int, "clip_corner": lvalid.lv_bool, "height": lvalid.size, - "img_recolor": lvalid.lv_color, - "img_recolor_opa": lvalid.opacity, + "image_recolor": lvalid.lv_color, + "image_recolor_opa": lvalid.opacity, "line_width": cv.positive_int, "line_dash_width": cv.positive_int, "line_dash_gap": cv.positive_int, @@ -108,12 +102,21 @@ STYLE_PROPS = { "max_width": lvalid.pixels_or_percent, "min_height": lvalid.pixels_or_percent, "min_width": lvalid.pixels_or_percent, - "radius": cv.Any(lvalid.size, df.LvConstant("LV_RADIUS_", "CIRCLE").one_of), + "radius": lvalid.radius, "width": lvalid.size, "x": lvalid.pixels_or_percent, "y": lvalid.pixels_or_percent, } +STYLE_REMAP = { + "bg_image_opa": "bg_img_opa", + "bg_image_recolor": "bg_img_recolor", + "bg_image_recolor_opa": "bg_img_recolor_opa", + "bg_image_src": "bg_img_src", + "image_recolor": "img_recolor", + "image_recolor_opa": "img_recolor_opa", +} + # Complete object style schema STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).extend( { @@ -132,25 +135,23 @@ SET_STATE_SCHEMA = cv.Schema( {cv.Optional(state): lvalid.lv_bool for state in df.STATES} ) # Setting object flags -FLAG_SCHEMA = cv.Schema({cv.Optional(flag): cv.boolean for flag in df.OBJ_FLAGS}) +FLAG_SCHEMA = cv.Schema({cv.Optional(flag): lvalid.lv_bool for flag in df.OBJ_FLAGS}) FLAG_LIST = cv.ensure_list(df.LvConstant("LV_OBJ_FLAG_", *df.OBJ_FLAGS).one_of) -def part_schema(widget_type): +def part_schema(widget_type: WidgetType): """ Generate a schema for the various parts (e.g. main:, indicator:) of a widget type :param widget_type: The type of widget to generate for :return: """ - parts = WIDGET_PARTS.get(widget_type) - if parts is None: - parts = (df.CONF_MAIN,) + parts = widget_type.parts return cv.Schema({cv.Optional(part): STATE_SCHEMA for part in parts}).extend( STATE_SCHEMA ) -def obj_schema(widget_type: str): +def obj_schema(widget_type: WidgetType): """ Create a schema for a widget type itself i.e. no allowance for children :param widget_type: @@ -187,13 +188,12 @@ STYLED_TEXT_SCHEMA = cv.maybe_simple_value( STYLE_SCHEMA.extend(TEXT_SCHEMA), key=df.CONF_TEXT ) - ALL_STYLES = { **STYLE_PROPS, } -def container_validator(schema, widget_type): +def container_validator(schema, widget_type: WidgetType): """ Create a validator for a container given the widget type :param schema: Base schema to extend @@ -203,13 +203,16 @@ def container_validator(schema, widget_type): def validator(value): result = schema - if w_sch := WIDGET_TYPES[widget_type].schema: + if w_sch := widget_type.schema: result = result.extend(w_sch) if value and (layout := value.get(df.CONF_LAYOUT)): if not isinstance(layout, dict): raise cv.Invalid("Layout value must be a dict") ltype = layout.get(CONF_TYPE) add_lv_use(ltype) + result = result.extend( + {cv.Optional(df.CONF_WIDGETS): cv.ensure_list(any_widget_schema())} + ) if value == SCHEMA_EXTRACT: return result return result(value) @@ -217,7 +220,7 @@ def container_validator(schema, widget_type): return validator -def container_schema(widget_type, extras=None): +def container_schema(widget_type: WidgetType, extras=None): """ Create a schema for a container widget of a given type. All obj properties are available, plus the extras passed in, plus any defined for the specific widget being specified. @@ -225,15 +228,16 @@ def container_schema(widget_type, extras=None): :param extras: Additional options to be made available, e.g. layout properties for children :return: The schema for this type of widget. """ - lv_type = get_widget_type(widget_type) - schema = obj_schema(widget_type).extend({cv.GenerateID(): cv.declare_id(lv_type)}) + schema = obj_schema(widget_type).extend( + {cv.GenerateID(): cv.declare_id(widget_type.w_type)} + ) if extras: schema = schema.extend(extras) # Delayed evaluation for recursion return container_validator(schema, widget_type) -def widget_schema(widget_type, extras=None): +def widget_schema(widget_type: WidgetType, extras=None): """ Create a schema for a given widget type :param widget_type: The name of the widget @@ -241,9 +245,9 @@ def widget_schema(widget_type, extras=None): :return: """ validator = container_schema(widget_type, extras=extras) - if required := REQUIRED_COMPONENTS.get(widget_type): + if required := widget_type.required_component: validator = cv.All(validator, requires_component(required)) - return cv.Exclusive(widget_type, df.CONF_WIDGETS), validator + return cv.Exclusive(widget_type.name, df.CONF_WIDGETS), validator # All widget schemas must be defined before this is called. @@ -257,4 +261,4 @@ def any_widget_schema(extras=None): :param extras: Additional schema to be applied to each generated one :return: """ - return cv.Any(dict(widget_schema(wt, extras) for wt in WIDGET_PARTS)) + return cv.Any(dict(widget_schema(wt, extras) for wt in WIDGET_TYPES.values())) diff --git a/esphome/components/lvgl/touchscreens.py b/esphome/components/lvgl/touchscreens.py new file mode 100644 index 0000000000..a0d4a3e4ad --- /dev/null +++ b/esphome/components/lvgl/touchscreens.py @@ -0,0 +1,46 @@ +import esphome.codegen as cg +from esphome.components.touchscreen import CONF_TOUCHSCREEN_ID, Touchscreen +import esphome.config_validation as cv +from esphome.const import CONF_ID +from esphome.core import CORE, TimePeriod + +from .defines import ( + CONF_LONG_PRESS_REPEAT_TIME, + CONF_LONG_PRESS_TIME, + CONF_TOUCHSCREENS, +) +from .helpers import lvgl_components_required +from .lv_validation import lv_milliseconds +from .lvcode import lv +from .types import LVTouchListener + +PRESS_TIME = cv.All(lv_milliseconds, cv.Range(max=TimePeriod(milliseconds=65535))) +CONF_TOUCHSCREEN = "touchscreen" +TOUCHSCREENS_CONFIG = cv.maybe_simple_value( + { + cv.Required(CONF_TOUCHSCREEN_ID): cv.use_id(Touchscreen), + cv.Optional(CONF_LONG_PRESS_TIME, default="400ms"): PRESS_TIME, + cv.Optional(CONF_LONG_PRESS_REPEAT_TIME, default="100ms"): PRESS_TIME, + cv.GenerateID(): cv.declare_id(LVTouchListener), + }, + key=CONF_TOUCHSCREEN_ID, +) + + +def touchscreen_schema(config): + value = cv.ensure_list(TOUCHSCREENS_CONFIG)(config) + if value or CONF_TOUCHSCREEN not in CORE.loaded_integrations: + return value + return [TOUCHSCREENS_CONFIG(config)] + + +async def touchscreens_to_code(var, config): + for tconf in config.get(CONF_TOUCHSCREENS) or (): + 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) + lv.indev_drv_register(listener.get_drv()) + cg.add(touchscreen.register_listener(listener)) diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index 3c043d266d..60291ea54a 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -1,7 +1,22 @@ from esphome import codegen as cg from esphome.core import ID +from esphome.cpp_generator import MockObjClass + +from .defines import CONF_TEXT + + +class LvType(cg.MockObjClass): + def __init__(self, *args, **kwargs): + parens = kwargs.pop("parents", ()) + super().__init__(*args, parents=parens + (lv_obj_base_t,)) + self.args = kwargs.pop("largs", [(lv_obj_t_ptr, "obj")]) + self.value = kwargs.pop("lvalue", lambda w: w.obj) + self.has_on_value = kwargs.pop("has_on_value", False) + self.value_property = None + + def get_arg_type(self): + return self.args[0][0] if len(self.args) else None -from .defines import CONF_LABEL, CONF_OBJ, CONF_TEXT uint16_t_ptr = cg.uint16.operator("ptr") lvgl_ns = cg.esphome_ns.namespace("lvgl") @@ -18,25 +33,15 @@ lv_obj_base_t = cg.global_ns.class_("lv_obj_t", lv_pseudo_button_t) lv_obj_t_ptr = lv_obj_base_t.operator("ptr") lv_disp_t_ptr = cg.global_ns.struct("lv_disp_t").operator("ptr") lv_color_t = cg.global_ns.struct("lv_color_t") +LVTouchListener = lvgl_ns.class_("LVTouchListener") +LVEncoderListener = lvgl_ns.class_("LVEncoderListener") +lv_obj_t = LvType("lv_obj_t") # this will be populated later, in __init__.py to avoid circular imports. WIDGET_TYPES: dict = {} -class LvType(cg.MockObjClass): - def __init__(self, *args, **kwargs): - parens = kwargs.pop("parents", ()) - super().__init__(*args, parents=parens + (lv_obj_base_t,)) - self.args = kwargs.pop("largs", [(lv_obj_t_ptr, "obj")]) - self.value = kwargs.pop("lvalue", lambda w: w.obj) - self.has_on_value = kwargs.pop("has_on_value", False) - self.value_property = None - - def get_arg_type(self): - return self.args[0][0] if len(self.args) else None - - class LvText(LvType): def __init__(self, *args, **kwargs): super().__init__( @@ -48,17 +53,74 @@ class LvText(LvType): self.value_property = CONF_TEXT -lv_obj_t = LvType("lv_obj_t") -lv_label_t = LvText("lv_label_t") - -LV_TYPES = { - CONF_LABEL: lv_label_t, - CONF_OBJ: lv_obj_t, -} - - -def get_widget_type(typestr: str) -> LvType: - return LV_TYPES[typestr] +class LvBoolean(LvType): + def __init__(self, *args, **kwargs): + super().__init__( + *args, + largs=[(cg.bool_, "x")], + lvalue=lambda w: w.is_checked(), + has_on_value=True, + **kwargs, + ) CUSTOM_EVENT = ID("lv_custom_event", False, type=lv_event_code_t) + + +class WidgetType: + """ + Describes a type of Widget, e.g. "bar" or "line" + """ + + def __init__(self, name, w_type, parts, schema=None, modify_schema=None): + """ + :param name: The widget name, e.g. "bar" + :param w_type: The C type of the widget + :param parts: What parts this widget supports + :param schema: The config schema for defining a widget + :param modify_schema: A schema to update the widget + """ + self.name = name + self.w_type = w_type + self.parts = parts + self.schema = schema or {} + if modify_schema is None: + self.modify_schema = schema + else: + self.modify_schema = modify_schema + + @property + def animated(self): + return False + + @property + def required_component(self): + return None + + def is_compound(self): + return self.w_type.inherits_from(LvCompound) + + async def to_code(self, w, config: dict): + """ + Generate code for a given widget + :param w: The widget + :param config: Its configuration + :return: Generated code as a list of text lines + """ + raise NotImplementedError(f"No to_code defined for {self.name}") + + def obj_creator(self, parent: MockObjClass, config: dict): + """ + Create an instance of the widget type + :param parent: The parent to which it should be attached + :param config: Its configuration + :return: Generated code as a single text line + """ + return f"lv_{self.name}_create({parent})" + + def get_uses(self): + """ + Get a list of other widgets used by this one + :return: + """ + return () diff --git a/esphome/components/lvgl/widget.py b/esphome/components/lvgl/widget.py index 44f277f1c3..4755d8b21d 100644 --- a/esphome/components/lvgl/widget.py +++ b/esphome/components/lvgl/widget.py @@ -21,78 +21,19 @@ from .defines import ( ) from .helpers import add_lv_use from .lvcode import ConstantLiteral, add_line_marks, lv, lv_add, lv_assign, lv_obj -from .schemas import ALL_STYLES -from .types import WIDGET_TYPES, LvCompound, lv_obj_t +from .schemas import ALL_STYLES, STYLE_REMAP +from .types import WIDGET_TYPES, WidgetType, lv_obj_t EVENT_LAMB = "event_lamb__" -class WidgetType: - """ - Describes a type of Widget, e.g. "bar" or "line" - """ - - def __init__(self, name, schema=None, modify_schema=None): - """ - :param name: The widget name, e.g. "bar" - :param schema: The config schema for defining a widget - :param modify_schema: A schema to update the widget - """ - self.name = name - self.schema = schema or {} - if modify_schema is None: - self.modify_schema = schema - else: - self.modify_schema = modify_schema - - @property - def animated(self): - return False - - @property - def w_type(self): - """ - Get the type associated with this widget - :return: - """ - return lv_obj_t - - def is_compound(self): - return self.w_type.inherits_from(LvCompound) - - async def to_code(self, w, config: dict): - """ - Generate code for a given widget - :param w: The widget - :param config: Its configuration - :return: Generated code as a list of text lines - """ - raise NotImplementedError(f"No to_code defined for {self.name}") - - def obj_creator(self, parent: MockObjClass, config: dict): - """ - Create an instance of the widget type - :param parent: The parent to which it should be attached - :param config: Its configuration - :return: Generated code as a single text line - """ - return f"lv_{self.name}_create({parent})" - - def get_uses(self): - """ - Get a list of other widgets used by this one - :return: - """ - return () - - class LvScrActType(WidgetType): """ A "widget" representing the active screen. """ def __init__(self): - super().__init__("lv_scr_act()") + super().__init__("lv_scr_act()", lv_obj_t, ()) def obj_creator(self, parent: MockObjClass, config: dict): return [] @@ -263,7 +204,9 @@ async def set_obj_properties(w: Widget, config): }.items(): if isinstance(ALL_STYLES[prop], LValidator): value = await ALL_STYLES[prop].process(value) - w.set_style(prop, value, lv_state) + # Remapping for backwards compatibility of style names + prop_r = STYLE_REMAP.get(prop, prop) + w.set_style(prop_r, value, lv_state) flag_clr = set() flag_set = set() props = parts[CONF_MAIN][CONF_DEFAULT] @@ -291,10 +234,10 @@ async def set_obj_properties(w: Widget, config): else: clears.add(key) if adds: - adds = ConstantLiteral(join_enums(adds, "LV_STATE_")) + adds = join_enums(adds, "LV_STATE_") w.add_state(adds) if clears: - clears = ConstantLiteral(join_enums(clears, "LV_STATE_")) + clears = join_enums(clears, "LV_STATE_") w.clear_state(clears) for key, value in lambs.items(): lamb = await cg.process_lambda(value, [], return_type=cg.bool_) diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index a02aabf14d..449c8fc712 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -110,7 +110,7 @@ void MitsubishiClimate::transmit_state() { // Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00 // Byte 16: Constant 0x00 // Byte 17: Checksum: SUM[Byte0...Byte16] - uint8_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00, + uint8_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; switch (this->mode) { @@ -136,6 +136,12 @@ void MitsubishiClimate::transmit_state() { break; case climate::CLIMATE_MODE_OFF: default: + remote_state[6] = MITSUBISHI_MODE_COOL; + remote_state[8] = MITSUBISHI_MODE_A_COOL; + if (this->supports_heat_) { + remote_state[6] = MITSUBISHI_MODE_HEAT; + remote_state[8] = MITSUBISHI_MODE_A_HEAT; + } remote_state[5] = MITSUBISHI_OFF; break; } diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 4e447bfb2d..d9917a2aae 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -1,10 +1,15 @@ +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv -from esphome import automation +from esphome.const import ( + CONF_ESPHOME, + CONF_ON_ERROR, + CONF_OTA, + CONF_PLATFORM, + CONF_TRIGGER_ID, +) from esphome.core import CORE, coroutine_with_priority -from esphome.const import CONF_ESPHOME, CONF_OTA, CONF_PLATFORM, CONF_TRIGGER_ID - CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["md5", "safe_mode"] @@ -13,7 +18,6 @@ IS_PLATFORM_COMPONENT = True CONF_ON_ABORT = "on_abort" CONF_ON_BEGIN = "on_begin" CONF_ON_END = "on_end" -CONF_ON_ERROR = "on_error" CONF_ON_PROGRESS = "on_progress" CONF_ON_STATE_CHANGE = "on_state_change" diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index 5ae97ee10b..b5275e9775 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -44,6 +44,8 @@ class PIDClimate : public climate::Climate, public Component { float get_kp() { return controller_.kp_; } float get_ki() { return controller_.ki_; } float get_kd() { return controller_.kd_; } + float get_min_integral() { return controller_.min_integral_; } + float get_max_integral() { return controller_.max_integral_; } float get_proportional_term() const { return controller_.proportional_term_; } float get_integral_term() const { return controller_.integral_term_; } float get_derivative_term() const { return controller_.derivative_term_; } diff --git a/esphome/components/touchscreen/binary_sensor/__init__.py b/esphome/components/touchscreen/binary_sensor/__init__.py index 800bc4c2a9..45fefbf814 100644 --- a/esphome/components/touchscreen/binary_sensor/__init__.py +++ b/esphome/components/touchscreen/binary_sensor/__init__.py @@ -1,10 +1,9 @@ import esphome.codegen as cg -import esphome.config_validation as cv - from esphome.components import binary_sensor, display -from esphome.const import CONF_PAGE_ID +import esphome.config_validation as cv +from esphome.const import CONF_PAGE_ID, CONF_PAGES -from .. import touchscreen_ns, CONF_TOUCHSCREEN_ID, Touchscreen, TouchListener +from .. import CONF_TOUCHSCREEN_ID, TouchListener, Touchscreen, touchscreen_ns DEPENDENCIES = ["touchscreen"] @@ -22,7 +21,7 @@ CONF_Y_MIN = "y_min" CONF_Y_MAX = "y_max" -def validate_coords(config): +def _validate_coords(config): if ( config[CONF_X_MAX] < config[CONF_X_MIN] or config[CONF_Y_MAX] < config[CONF_Y_MIN] @@ -33,6 +32,15 @@ def validate_coords(config): return config +def _set_pages(config: dict) -> dict: + if CONF_PAGES in config or CONF_PAGE_ID not in config: + return config + + config = config.copy() + config[CONF_PAGES] = [config.pop(CONF_PAGE_ID)] + return config + + CONFIG_SCHEMA = cv.All( binary_sensor.binary_sensor_schema(TouchscreenBinarySensor) .extend( @@ -42,11 +50,17 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_X_MAX): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=2000), - cv.Optional(CONF_PAGE_ID): cv.use_id(display.DisplayPage), + cv.Exclusive(CONF_PAGE_ID, group_of_exclusion=CONF_PAGES): cv.use_id( + display.DisplayPage + ), + cv.Exclusive(CONF_PAGES, group_of_exclusion=CONF_PAGES): cv.ensure_list( + cv.use_id(display.DisplayPage) + ), } ) .extend(cv.COMPONENT_SCHEMA), - validate_coords, + _validate_coords, + _set_pages, ) @@ -64,6 +78,6 @@ async def to_code(config): ) ) - if CONF_PAGE_ID in config: - page = await cg.get_variable(config[CONF_PAGE_ID]) - cg.add(var.set_page(page)) + for page_id in config.get(CONF_PAGES, []): + page = await cg.get_variable(page_id) + cg.add(var.add_page(page)) diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp index 6c26ae3626..6cd12d4d0d 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp @@ -11,8 +11,9 @@ void TouchscreenBinarySensor::setup() { void TouchscreenBinarySensor::touch(TouchPoint tp) { bool touched = (tp.x >= this->x_min_ && tp.x <= this->x_max_ && tp.y >= this->y_min_ && tp.y <= this->y_max_); - if (this->page_ != nullptr) { - touched &= this->page_ == this->parent_->get_display()->get_active_page(); + if (!this->pages_.empty()) { + auto *current_page = this->parent_->get_display()->get_active_page(); + touched &= std::find(this->pages_.begin(), this->pages_.end(), current_page) != this->pages_.end(); } if (touched) { this->publish_state(true); diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h index b56ae562b1..862f41064c 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h @@ -6,6 +6,8 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include + namespace esphome { namespace touchscreen { @@ -30,14 +32,14 @@ class TouchscreenBinarySensor : public binary_sensor::BinarySensor, int16_t get_width() { return this->x_max_ - this->x_min_; } int16_t get_height() { return this->y_max_ - this->y_min_; } - void set_page(display::DisplayPage *page) { this->page_ = page; } + void add_page(display::DisplayPage *page) { this->pages_.push_back(page); } void touch(TouchPoint tp) override; void release() override; protected: int16_t x_min_, x_max_, y_min_, y_max_; - display::DisplayPage *page_{nullptr}; + std::vector pages_{}; }; } // namespace touchscreen diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index c18f0a6850..031edbf27a 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -1,18 +1,18 @@ -import esphome.config_validation as cv -import esphome.codegen as cg - -from esphome.const import ( - CONF_ID, - CONF_MICROPHONE, - CONF_SPEAKER, - CONF_MEDIA_PLAYER, - CONF_ON_CLIENT_CONNECTED, - CONF_ON_CLIENT_DISCONNECTED, - CONF_ON_IDLE, -) from esphome import automation from esphome.automation import register_action, register_condition -from esphome.components import microphone, speaker, media_player +import esphome.codegen as cg +from esphome.components import media_player, microphone, speaker +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_MEDIA_PLAYER, + CONF_MICROPHONE, + CONF_ON_CLIENT_CONNECTED, + CONF_ON_CLIENT_DISCONNECTED, + CONF_ON_ERROR, + CONF_ON_IDLE, + CONF_SPEAKER, +) AUTO_LOAD = ["socket"] DEPENDENCIES = ["api", "microphone"] @@ -20,7 +20,6 @@ DEPENDENCIES = ["api", "microphone"] CODEOWNERS = ["@jesserockz"] CONF_ON_END = "on_end" -CONF_ON_ERROR = "on_error" CONF_ON_INTENT_END = "on_intent_end" CONF_ON_INTENT_START = "on_intent_start" CONF_ON_LISTENING = "on_listening" diff --git a/esphome/components/watchdog/__init__.py b/esphome/components/watchdog/__init__.py new file mode 100644 index 0000000000..6fb87e45a4 --- /dev/null +++ b/esphome/components/watchdog/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@oarcher"] diff --git a/esphome/components/http_request/watchdog.cpp b/esphome/components/watchdog/watchdog.cpp similarity index 96% rename from esphome/components/http_request/watchdog.cpp rename to esphome/components/watchdog/watchdog.cpp index a8519c59ed..3a94a658e8 100644 --- a/esphome/components/http_request/watchdog.cpp +++ b/esphome/components/watchdog/watchdog.cpp @@ -15,7 +15,6 @@ #endif namespace esphome { -namespace http_request { namespace watchdog { static const char *const TAG = "http_request.watchdog"; @@ -72,5 +71,4 @@ uint32_t WatchdogManager::get_timeout_() { } } // namespace watchdog -} // namespace http_request } // namespace esphome diff --git a/esphome/components/http_request/watchdog.h b/esphome/components/watchdog/watchdog.h similarity index 88% rename from esphome/components/http_request/watchdog.h rename to esphome/components/watchdog/watchdog.h index 9b54ae6c82..899ec3fde0 100644 --- a/esphome/components/http_request/watchdog.h +++ b/esphome/components/watchdog/watchdog.h @@ -5,7 +5,6 @@ #include namespace esphome { -namespace http_request { namespace watchdog { class WatchdogManager { @@ -22,5 +21,4 @@ class WatchdogManager { }; } // namespace watchdog -} // namespace http_request } // namespace esphome diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 624bcdabdc..ea03cc16d1 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -1,15 +1,19 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -import esphome.final_validate as fv from esphome import automation from esphome.automation import Condition +import esphome.codegen as cg +from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant +from esphome.components.network import IPAddress +import esphome.config_validation as cv from esphome.const import ( CONF_AP, CONF_BSSID, + CONF_CERTIFICATE, + CONF_CERTIFICATE_AUTHORITY, CONF_CHANNEL, CONF_DNS1, CONF_DNS2, CONF_DOMAIN, + CONF_EAP, CONF_ENABLE_BTM, CONF_ENABLE_ON_BOOT, CONF_ENABLE_RRM, @@ -17,29 +21,26 @@ from esphome.const import ( CONF_GATEWAY, CONF_HIDDEN, CONF_ID, + CONF_IDENTITY, + CONF_KEY, CONF_MANUAL_IP, CONF_NETWORKS, + CONF_ON_CONNECT, + CONF_ON_DISCONNECT, CONF_PASSWORD, CONF_POWER_SAVE_MODE, + CONF_PRIORITY, CONF_REBOOT_TIMEOUT, CONF_SSID, CONF_STATIC_IP, CONF_SUBNET, - CONF_USE_ADDRESS, - CONF_PRIORITY, - CONF_IDENTITY, - CONF_CERTIFICATE_AUTHORITY, - CONF_CERTIFICATE, - CONF_KEY, - CONF_USERNAME, - CONF_EAP, CONF_TTLS_PHASE_2, - CONF_ON_CONNECT, - CONF_ON_DISCONNECT, + CONF_USE_ADDRESS, + CONF_USERNAME, ) from esphome.core import CORE, HexInt, coroutine_with_priority -from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const -from esphome.components.network import IPAddress +import esphome.final_validate as fv + from . import wpa2_eap AUTO_LOAD = ["network"] diff --git a/esphome/components/wifi/wpa2_eap.py b/esphome/components/wifi/wpa2_eap.py index 3985dfef18..5d5bd8dca3 100644 --- a/esphome/components/wifi/wpa2_eap.py +++ b/esphome/components/wifi/wpa2_eap.py @@ -7,16 +7,15 @@ so that it doesn't crash if it's not installed. import logging from pathlib import Path -from esphome.core import CORE import esphome.config_validation as cv from esphome.const import ( - CONF_USERNAME, - CONF_IDENTITY, - CONF_PASSWORD, CONF_CERTIFICATE, + CONF_IDENTITY, CONF_KEY, + CONF_PASSWORD, + CONF_USERNAME, ) - +from esphome.core import CORE _LOGGER = logging.getLogger(__name__) @@ -49,8 +48,8 @@ def wrapped_load_pem_x509_certificate(value): def wrapped_load_pem_private_key(value, password): validate_cryptography_installed() - from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.serialization import load_pem_private_key if password: password = password.encode("UTF-8") @@ -91,7 +90,7 @@ def _validate_load_private_key(key, cert_pw): def _check_private_key_cert_match(key, cert): - from cryptography.hazmat.primitives.asymmetric import rsa, ec + from cryptography.hazmat.primitives.asymmetric import ec, rsa def check_match_a(): return key.public_key().public_numbers() == cert.public_key().public_numbers() diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 75513712dd..4ceb73a695 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -1,13 +1,13 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import text_sensor +import esphome.config_validation as cv from esphome.const import ( CONF_BSSID, + CONF_DNS_ADDRESS, CONF_IP_ADDRESS, + CONF_MAC_ADDRESS, CONF_SCAN_RESULTS, CONF_SSID, - CONF_MAC_ADDRESS, - CONF_DNS_ADDRESS, ENTITY_CATEGORY_DIAGNOSTIC, ) diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py index 77fabf272e..99b51adea0 100644 --- a/esphome/components/wifi_signal/sensor.py +++ b/esphome/components/wifi_signal/sensor.py @@ -1,6 +1,6 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import sensor +import esphome.config_validation as cv from esphome.const import ( DEVICE_CLASS_SIGNAL_STRENGTH, ENTITY_CATEGORY_DIAGNOSTIC, diff --git a/esphome/config.py b/esphome/config.py index 925a31fed0..a2d0d15477 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -1,40 +1,38 @@ from __future__ import annotations + import abc +from contextlib import contextmanager +import contextvars import functools import heapq import logging import re - -from typing import Union, Any - -from contextlib import contextmanager -import contextvars +from typing import Any, Union import voluptuous as vol -from esphome import core, yaml_util, loader, pins -import esphome.core.config as core_config +from esphome import core, loader, pins, yaml_util +from esphome.config_helpers import Extend, Remove +import esphome.config_validation as cv from esphome.const import ( CONF_ESPHOME, - CONF_ID, - CONF_PLATFORM, - CONF_PACKAGES, - CONF_SUBSTITUTIONS, CONF_EXTERNAL_COMPONENTS, + CONF_ID, + CONF_PACKAGES, + CONF_PLATFORM, + CONF_SUBSTITUTIONS, TARGET_PLATFORMS, ) -from esphome.core import CORE, EsphomeError, DocumentRange -from esphome.helpers import indent -from esphome.util import safe_print, OrderedDict - -from esphome.config_helpers import Extend, Remove -from esphome.loader import get_component, get_platform, ComponentManifest -from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue -from esphome.voluptuous_schema import ExtraKeysInvalid -from esphome.log import color, Fore +from esphome.core import CORE, DocumentRange, EsphomeError +import esphome.core.config as core_config import esphome.final_validate as fv -import esphome.config_validation as cv -from esphome.types import ConfigType, ConfigFragmentType +from esphome.helpers import indent +from esphome.loader import ComponentManifest, get_component, get_platform +from esphome.log import Fore, color +from esphome.types import ConfigFragmentType, ConfigType +from esphome.util import OrderedDict, safe_print +from esphome.voluptuous_schema import ExtraKeysInvalid +from esphome.yaml_util import ESPForceValue, ESPHomeDataBase, is_secret _LOGGER = logging.getLogger(__name__) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 3ef92ad460..1cd1d6aa31 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1,13 +1,13 @@ """Helpers for config validation using voluptuous.""" +from contextlib import contextmanager from dataclasses import dataclass +from datetime import datetime import logging import os import re -from contextlib import contextmanager -import uuid as uuid_ -from datetime import datetime from string import ascii_letters, digits +import uuid as uuid_ import voluptuous as vol @@ -17,37 +17,37 @@ from esphome.config_helpers import Extend, Remove from esphome.const import ( ALLOWED_NAME_CHARS, CONF_AVAILABILITY, - CONF_COMMAND_TOPIC, CONF_COMMAND_RETAIN, + CONF_COMMAND_TOPIC, + CONF_DAY, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, CONF_ENTITY_CATEGORY, + CONF_HOUR, CONF_ICON, CONF_ID, CONF_INTERNAL, + CONF_MINUTE, + CONF_MONTH, CONF_NAME, + CONF_PASSWORD, + CONF_PATH, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, - CONF_RETAIN, CONF_QOS, + CONF_REF, + CONF_RETAIN, + CONF_SECOND, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_TOPIC, - CONF_YEAR, - CONF_MONTH, - CONF_DAY, - CONF_HOUR, - CONF_MINUTE, - CONF_SECOND, - CONF_VALUE, - CONF_UPDATE_INTERVAL, - CONF_TYPE_ID, CONF_TYPE, - CONF_REF, + CONF_TYPE_ID, + CONF_UPDATE_INTERVAL, CONF_URL, - CONF_PATH, CONF_USERNAME, - CONF_PASSWORD, + CONF_VALUE, + CONF_YEAR, ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_NONE, @@ -71,15 +71,15 @@ from esphome.core import ( TimePeriod, TimePeriodMicroseconds, TimePeriodMilliseconds, + TimePeriodMinutes, TimePeriodNanoseconds, TimePeriodSeconds, - TimePeriodMinutes, ) -from esphome.helpers import list_starts_with, add_class_to_obj +from esphome.helpers import add_class_to_obj, list_starts_with from esphome.schema_extractors import ( SCHEMA_EXTRACT, - schema_extractor_list, schema_extractor, + schema_extractor_list, schema_extractor_registry, schema_extractor_typed, ) @@ -1686,9 +1686,9 @@ class SplitDefault(Optional): if CORE.is_esp32: from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32.const import ( + VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3, - VARIANT_ESP32C3, ) variant = get_esp32_variant() diff --git a/esphome/const.py b/esphome/const.py index faf6ce19fa..37844e1047 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -539,6 +539,7 @@ CONF_ON_DOUBLE_CLICK = "on_double_click" CONF_ON_ENROLLMENT_DONE = "on_enrollment_done" CONF_ON_ENROLLMENT_FAILED = "on_enrollment_failed" CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan" +CONF_ON_ERROR = "on_error" CONF_ON_EVENT = "on_event" CONF_ON_FINGER_SCAN_INVALID = "on_finger_scan_invalid" CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched" @@ -1032,11 +1033,13 @@ UNIT_KILOWATT_HOURS = "kWh" UNIT_LUX = "lx" UNIT_METER = "m" UNIT_METER_PER_SECOND_SQUARED = "m/s²" +UNIT_MICROAMP = "µA" UNIT_MICROGRAMS_PER_CUBIC_METER = "µg/m³" UNIT_MICROMETER = "µm" UNIT_MICROSIEMENS_PER_CENTIMETER = "µS/cm" UNIT_MICROSILVERTS_PER_HOUR = "µSv/h" UNIT_MICROTESLA = "µT" +UNIT_MILLIAMP = "mA" UNIT_MILLIGRAMS_PER_CUBIC_METER = "mg/m³" UNIT_MILLIMETER = "mm" UNIT_MILLISECOND = "ms" diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index f25891965a..9d3d14492e 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -7,26 +7,29 @@ from typing import TYPE_CHECKING, Optional, Union from esphome.const import ( CONF_COMMENT, CONF_ESPHOME, - CONF_USE_ADDRESS, CONF_ETHERNET, + CONF_PORT, + CONF_USE_ADDRESS, CONF_WEB_SERVER, CONF_WIFI, - CONF_PORT, KEY_CORE, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, + PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, - PLATFORM_BK72XX, - PLATFORM_RTL87XX, - PLATFORM_RP2040, PLATFORM_HOST, + PLATFORM_RP2040, + PLATFORM_RTL87XX, ) -from esphome.coroutine import FakeAwaitable as _FakeAwaitable -from esphome.coroutine import FakeEventLoop as _FakeEventLoop # pylint: disable=unused-import -from esphome.coroutine import coroutine, coroutine_with_priority # noqa +from esphome.coroutine import ( # noqa: F401 + FakeAwaitable as _FakeAwaitable, + FakeEventLoop as _FakeEventLoop, + coroutine, + coroutine_with_priority, +) from esphome.helpers import ensure_unique_string, get_str_env, is_ha_addon from esphome.util import OrderedDict diff --git a/esphome/core/config.py b/esphome/core/config.py index 80b731b905..739a8a1aea 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -3,9 +3,9 @@ import multiprocessing import os import re +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv -from esphome import automation from esphome.const import ( CONF_ARDUINO_VERSION, CONF_AREA, @@ -16,11 +16,11 @@ from esphome.const import ( CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, CONF_FRAMEWORK, + CONF_FRIENDLY_NAME, CONF_INCLUDES, CONF_LIBRARIES, CONF_MIN_VERSION, CONF_NAME, - CONF_FRIENDLY_NAME, CONF_ON_BOOT, CONF_ON_LOOP, CONF_ON_SHUTDOWN, @@ -34,8 +34,8 @@ from esphome.const import ( CONF_TYPE, CONF_VERSION, KEY_CORE, - TARGET_PLATFORMS, PLATFORM_ESP8266, + TARGET_PLATFORMS, __version__ as ESPHOME_VERSION, ) from esphome.core import CORE, coroutine_with_priority diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 9d453260ab..6ba5b64761 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -41,6 +41,7 @@ #define USE_LVGL #define USE_LVGL_FONT #define USE_LVGL_IMAGE +#define USE_LVGL_TOUCHSCREEN #define USE_MDNS #define USE_MEDIA_PLAYER #define USE_MQTT diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index f921711ec2..7f6a9b48ab 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -1,6 +1,5 @@ -import esphome.final_validate as fv - from esphome.const import CONF_ID +import esphome.final_validate as fv def inherit_property_from(property_to_inherit, parent_id_property, transform=None): diff --git a/esphome/coroutine.py b/esphome/coroutine.py index 5f391dc7ad..30ebb8147e 100644 --- a/esphome/coroutine.py +++ b/esphome/coroutine.py @@ -43,13 +43,13 @@ the last `yield` expression defines what is returned. """ import collections +from collections.abc import Awaitable, Generator, Iterator import functools import heapq import inspect import logging import types from typing import Any, Callable -from collections.abc import Awaitable, Generator, Iterator _LOGGER = logging.getLogger(__name__) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 9a4cb2269a..7a82d5cba1 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -1,8 +1,8 @@ import abc +from collections.abc import Sequence import inspect import math import re -from collections.abc import Sequence from typing import Any, Callable, Optional, Union from esphome.core import ( diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 825224bb9d..9a775bad33 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -12,15 +12,13 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, KEY_PAST_SAFE_MODE, ) - -from esphome.core import coroutine, ID, CORE +from esphome.core import CORE, ID, coroutine from esphome.coroutine import FakeAwaitable -from esphome.types import ConfigType, ConfigFragmentType from esphome.cpp_generator import add, get_variable from esphome.cpp_types import App +from esphome.helpers import sanitize, snake_case +from esphome.types import ConfigFragmentType, ConfigType from esphome.util import Registry, RegistryEntry -from esphome.helpers import snake_case, sanitize - _LOGGER = logging.getLogger(__name__) diff --git a/esphome/dashboard/core.py b/esphome/dashboard/core.py index 875ff6b91f..eec0777da6 100644 --- a/esphome/dashboard/core.py +++ b/esphome/dashboard/core.py @@ -1,13 +1,13 @@ from __future__ import annotations import asyncio +from collections.abc import Coroutine import contextlib -import logging -import threading from dataclasses import dataclass from functools import partial +import logging +import threading from typing import TYPE_CHECKING, Any, Callable -from collections.abc import Coroutine from ..zeroconf import DiscoveredImport from .dns import DNSCache diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 2be98ab3e4..9de2d39ce2 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -1,14 +1,14 @@ from __future__ import annotations import asyncio +from asyncio import events +from concurrent.futures import ThreadPoolExecutor import logging import os import socket import threading -import traceback -from asyncio import events -from concurrent.futures import ThreadPoolExecutor from time import monotonic +import traceback from typing import Any from esphome.storage_json import EsphomeStorageJSON, esphome_storage_path diff --git a/esphome/dashboard/entries.py b/esphome/dashboard/entries.py index 7a9bff4ec1..cb0d4a3772 100644 --- a/esphome/dashboard/entries.py +++ b/esphome/dashboard/entries.py @@ -1,9 +1,9 @@ from __future__ import annotations import asyncio +from collections import defaultdict import logging import os -from collections import defaultdict from typing import TYPE_CHECKING, Any from esphome import const, util diff --git a/esphome/dashboard/util/file.py b/esphome/dashboard/util/file.py index 661d5f34cf..bb263f9ad7 100644 --- a/esphome/dashboard/util/file.py +++ b/esphome/dashboard/util/file.py @@ -1,7 +1,7 @@ import logging import os -import tempfile from pathlib import Path +import tempfile _LOGGER = logging.getLogger(__name__) diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 33c83ffb1a..e4b7b8d342 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio import base64 +from collections.abc import Iterable import datetime import functools import gzip @@ -9,13 +10,12 @@ import hashlib import json import logging import os +from pathlib import Path import secrets import shutil import subprocess import threading import time -from collections.abc import Iterable -from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, TypeVar from urllib.parse import urlparse @@ -26,13 +26,13 @@ import tornado.httpserver import tornado.httputil import tornado.ioloop import tornado.iostream +from tornado.log import access_log import tornado.netutil import tornado.process import tornado.queues import tornado.web import tornado.websocket import yaml -from tornado.log import access_log from yaml.nodes import Node from esphome import const, platformio_api, yaml_util diff --git a/esphome/external_files.py b/esphome/external_files.py index f8eb1dcabe..baf62286e4 100644 --- a/esphome/external_files.py +++ b/esphome/external_files.py @@ -1,13 +1,15 @@ from __future__ import annotations -import logging -from pathlib import Path -import os from datetime import datetime +import logging +import os +from pathlib import Path + import requests + import esphome.config_validation as cv -from esphome.core import CORE, TimePeriodSeconds from esphome.const import __version__ +from esphome.core import CORE, TimePeriodSeconds _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@landonr"] diff --git a/esphome/final_validate.py b/esphome/final_validate.py index 5e9d2207b0..cebd2f1cda 100644 --- a/esphome/final_validate.py +++ b/esphome/final_validate.py @@ -1,9 +1,9 @@ from abc import ABC, abstractmethod -from typing import Any import contextvars +from typing import Any -from esphome.types import ConfigFragmentType, ID, ConfigPathType import esphome.config_validation as cv +from esphome.types import ID, ConfigFragmentType, ConfigPathType class FinalValidateConfig(ABC): diff --git a/esphome/git.py b/esphome/git.py index e41777f425..144c160b20 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -1,12 +1,12 @@ -import hashlib -import logging -import re -import subprocess -import urllib.parse from dataclasses import dataclass from datetime import datetime +import hashlib +import logging from pathlib import Path +import re +import subprocess from typing import Callable, Optional +import urllib.parse import esphome.config_validation as cv from esphome.core import CORE, TimePeriodSeconds diff --git a/esphome/helpers.py b/esphome/helpers.py index 4c8cb4e2cc..2a7e5cd9b6 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -1,14 +1,13 @@ import codecs from contextlib import suppress - import logging import os -import platform from pathlib import Path -from typing import Union -import tempfile -from urllib.parse import urlparse +import platform import re +import tempfile +from typing import Union +from urllib.parse import urlparse _LOGGER = logging.getLogger(__name__) @@ -129,9 +128,10 @@ def _resolve_with_zeroconf(host): def resolve_ip_address(host): - from esphome.core import EsphomeError import socket + from esphome.core import EsphomeError + errs = [] if host.endswith(".local"): diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 667a20bcf8..d7e14a1d08 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -1,10 +1,10 @@ from datetime import datetime import hashlib +import json import logging import ssl import sys import time -import json import paho.mqtt.client as mqtt @@ -24,9 +24,9 @@ from esphome.const import ( CONF_USERNAME, ) from esphome.core import CORE, EsphomeError -from esphome.log import color, Fore +from esphome.helpers import get_int_env, get_str_env +from esphome.log import Fore, color from esphome.util import safe_print -from esphome.helpers import get_str_env, get_int_env _LOGGER = logging.getLogger(__name__) diff --git a/esphome/pins.py b/esphome/pins.py index 5ccb696738..724cd25d82 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -1,20 +1,20 @@ -import operator from functools import reduce -import esphome.config_validation as cv -from esphome.core import CORE +import operator +import esphome.config_validation as cv from esphome.const import ( + CONF_ALLOW_OTHER_USES, + CONF_IGNORE_STRAPPING_WARNING, CONF_INPUT, + CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_OPEN_DRAIN, CONF_OUTPUT, CONF_PULLDOWN, CONF_PULLUP, - CONF_IGNORE_STRAPPING_WARNING, - CONF_ALLOW_OTHER_USES, - CONF_INVERTED, ) +from esphome.core import CORE class PinRegistry(dict): diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index c46a3fc767..b81ec4ab37 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -1,12 +1,11 @@ from dataclasses import dataclass import json -from typing import Union -from pathlib import Path - import logging import os +from pathlib import Path import re import subprocess +from typing import Union from esphome.const import CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, KEY_CORE from esphome.core import CORE, EsphomeError @@ -20,9 +19,10 @@ def patch_structhash(): # removed/added. This might have unintended consequences, but this improves compile # times greatly when adding/removing components and a simple clean build solves # all issues - from platformio.run import helpers, cli - from os.path import join, isdir, getmtime from os import makedirs + from os.path import getmtime, isdir, join + + from platformio.run import cli, helpers def patched_clean_build_dir(build_dir, *args): from platformio import fs diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 0a41a4f738..e2e7514904 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -1,10 +1,11 @@ from __future__ import annotations + import binascii import codecs +from datetime import datetime import json import logging import os -from datetime import datetime from esphome import const from esphome.const import CONF_DISABLED, CONF_MDNS diff --git a/esphome/types.py b/esphome/types.py index 27ec61ceff..4e69e3cbd7 100644 --- a/esphome/types.py +++ b/esphome/types.py @@ -2,7 +2,7 @@ from typing import Union -from esphome.core import ID, Lambda, EsphomeCore +from esphome.core import ID, EsphomeCore, Lambda ConfigFragmentType = Union[ str, diff --git a/esphome/util.py b/esphome/util.py index d5a4c60570..32fd90cd25 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -1,13 +1,12 @@ -from typing import Union - import collections import io import logging import os +from pathlib import Path import re import subprocess import sys -from pathlib import Path +from typing import Union from esphome import const diff --git a/esphome/voluptuous_schema.py b/esphome/voluptuous_schema.py index 9af6cb717c..7f1573b443 100644 --- a/esphome/voluptuous_schema.py +++ b/esphome/voluptuous_schema.py @@ -2,6 +2,7 @@ import difflib import itertools import voluptuous as vol + from esphome.schema_extractors import schema_extractor_extended diff --git a/esphome/vscode.py b/esphome/vscode.py index 8198d2659a..907ed88216 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -1,13 +1,14 @@ from __future__ import annotations + +from io import StringIO import json import os -from io import StringIO from typing import Any -from esphome.yaml_util import parse_yaml -from esphome.config import validate_config, _format_vol_invalid, Config -from esphome.core import CORE, DocumentRange +from esphome.config import Config, _format_vol_invalid, validate_config import esphome.config_validation as cv +from esphome.core import CORE, DocumentRange +from esphome.yaml_util import parse_yaml def _get_invalid_range(res: Config, invalid: cv.Invalid) -> DocumentRange | None: diff --git a/esphome/wizard.py b/esphome/wizard.py index f8911ae844..319fb31938 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -276,8 +276,8 @@ def wizard(path): from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.esp32 import boards as esp32_boards from esphome.components.esp8266 import boards as esp8266_boards - from esphome.components.rtl87xx import boards as rtl87xx_boards from esphome.components.rp2040 import boards as rp2040_boards + from esphome.components.rtl87xx import boards as rtl87xx_boards if not path.endswith(".yaml") and not path.endswith(".yml"): safe_print( diff --git a/esphome/writer.py b/esphome/writer.py index 3ad0e60d31..c6111cbe3f 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -1,27 +1,27 @@ import logging import os -import re from pathlib import Path +import re from typing import Union -from esphome.config import iter_components, iter_component_configs +from esphome import loader +from esphome.config import iter_component_configs, iter_components from esphome.const import ( + ENV_NOGITIGNORE, HEADER_FILE_EXTENSIONS, SOURCE_FILE_EXTENSIONS, __version__, - ENV_NOGITIGNORE, ) from esphome.core import CORE, EsphomeError from esphome.helpers import ( - mkdir_p, - read_file, - write_file_if_changed, - walk_files, copy_file_if_changed, get_bool_env, + mkdir_p, + read_file, + walk_files, + write_file_if_changed, ) from esphome.storage_json import StorageJSON, storage_path -from esphome import loader _LOGGER = logging.getLogger(__name__) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index 06bfd8b217..d67511dfec 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -3,16 +3,16 @@ from __future__ import annotations import fnmatch import functools import inspect +from io import TextIOWrapper import logging import math import os -import uuid -from io import TextIOWrapper from typing import Any +import uuid import yaml -import yaml.constructor from yaml import SafeLoader as PurePythonLoader +import yaml.constructor try: from yaml import CSafeLoader as FastestAvailableSafeLoader diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index b67ea41323..b3ee64e259 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -1,8 +1,8 @@ from __future__ import annotations import asyncio -import logging from dataclasses import dataclass +import logging from typing import Callable from zeroconf import IPVersion, ServiceInfo, ServiceStateChange, Zeroconf diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index e69de29bb2..8b92f8caa7 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -0,0 +1,10 @@ +touchscreen: + - platform: ft63x6 + id: tft_touch + display: tft_display + update_interval: 50ms + threshold: 1 + calibration: + x_max: 240 + y_max: 320 + diff --git a/tests/components/lvgl/logo-text.svg b/tests/components/lvgl/logo-text.svg new file mode 100644 index 0000000000..4950806a36 --- /dev/null +++ b/tests/components/lvgl/logo-text.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 856e7c3e9d..696c749876 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -1,9 +1,10 @@ -color: - - id: light_blue - hex: "3340FF" - lvgl: + log_level: TRACE bg_color: light_blue + touchscreens: + - touchscreen_id: tft_touch + long_press_repeat_time: 200ms + long_press_time: 500ms widgets: - label: text: Hello world @@ -17,8 +18,101 @@ lvgl: text_color: 0xFFFFFF align: bottom_mid text_font: space16 + - obj: + align: center + arc_opa: COVER + arc_color: 0xFF0000 + arc_rounded: false + arc_width: 3 + anim_time: 1s + bg_color: light_blue + bg_grad_color: light_blue + bg_dither_mode: ordered + bg_grad_dir: hor + bg_grad_stop: 128 + bg_image_opa: transp + bg_image_recolor: light_blue + bg_image_recolor_opa: 50% + bg_main_stop: 0 + bg_opa: 20% + border_color: 0x00FF00 + border_opa: cover + border_post: true + border_side: [bottom, left] + border_width: 4 + clip_corner: false + height: 50% + image_recolor: light_blue + image_recolor_opa: cover + line_width: 10 + line_dash_width: 10 + line_dash_gap: 10 + line_rounded: false + line_color: light_blue + opa: cover + opa_layered: cover + outline_color: light_blue + outline_opa: cover + outline_pad: 10px + outline_width: 10px + pad_all: 10px + pad_bottom: 10px + pad_column: 10px + pad_left: 10px + pad_right: 10px + pad_row: 10px + pad_top: 10px + shadow_color: light_blue + shadow_ofs_x: 5 + shadow_ofs_y: 5 + shadow_opa: cover + shadow_spread: 5 + shadow_width: 10 + text_align: auto + text_color: light_blue + text_decor: [underline, strikethrough] + text_font: montserrat_18 + text_letter_space: 4 + text_line_space: 4 + text_opa: cover + transform_angle: 180 + transform_height: 100 + transform_pivot_x: 50% + transform_pivot_y: 50% + transform_zoom: 0.5 + translate_x: 10 + translate_y: 10 + max_height: 100 + max_width: 200 + min_height: 20% + min_width: 20% + radius: circle + width: 10px + x: 100 + y: 120 + - button: + width: 20% + height: 10% + pressed: + bg_color: light_blue + widgets: + - label: + text: Button font: - file: "gfonts://Roboto" id: space16 bpp: 4 + +image: + - id: cat_img + resize: 256x48 + file: $component_dir/logo-text.svg + - id: dog_img + file: $component_dir/logo-text.svg + resize: 256x48 + type: TRANSPARENT_BINARY + +color: + - id: light_blue + hex: "3340FF" diff --git a/tests/components/lvgl/test.esp32-idf.yaml b/tests/components/lvgl/test.esp32-idf.yaml index f159431b99..eab75b05f3 100644 --- a/tests/components/lvgl/test.esp32-idf.yaml +++ b/tests/components/lvgl/test.esp32-idf.yaml @@ -19,7 +19,9 @@ display: mirror_y: true data_rate: 80MHz cs_pin: GPIO20 - dc_pin: GPIO15 + dc_pin: + number: GPIO15 + ignore_strapping_warning: true auto_clear_enabled: false invert_colors: false update_interval: never