Merge remote-tracking branch 'origin/dev' into nrf52_core

This commit is contained in:
Tomasz Duda 2024-08-02 18:08:57 +02:00
commit 53c9248e40
122 changed files with 2013 additions and 778 deletions

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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
@ -33,21 +33,21 @@ from esphome.const import (
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_NRF52,
PLATFORM_RP2040,
PLATFORM_RTL87XX,
SECRETS_FILES,
PLATFORM_NRF52,
)
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__)
@ -117,6 +117,7 @@ def get_port_type(port):
def run_miniterm(config, port):
import serial
from esphome import platformio_api
if CONF_LOGGER not in config:
@ -604,9 +605,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)
@ -755,7 +757,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)
@ -956,67 +965,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)
@ -1031,13 +979,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 args.command in PRE_CONFIG_ACTIONS:
try:

View file

@ -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

View file

@ -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,
)

View file

@ -60,7 +60,7 @@ bool AdE7953Spi::ade_read_16(uint16_t reg, uint16_t *value) {
this->write_byte16(reg);
this->transfer_byte(0x80);
uint8_t recv[2];
this->read_array(recv, 4);
this->read_array(recv, 2);
*value = encode_uint16(recv[0], recv[1]);
this->disable();
return false;

View file

@ -1,25 +1,27 @@
import base64
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import Condition
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTION,
CONF_ACTIONS,
CONF_DATA,
CONF_DATA_TEMPLATE,
CONF_EVENT,
CONF_ID,
CONF_KEY,
CONF_ON_CLIENT_CONNECTED,
CONF_ON_CLIENT_DISCONNECTED,
CONF_PASSWORD,
CONF_PORT,
CONF_REBOOT_TIMEOUT,
CONF_SERVICE,
CONF_VARIABLES,
CONF_SERVICES,
CONF_TRIGGER_ID,
CONF_EVENT,
CONF_TAG,
CONF_ON_CLIENT_CONNECTED,
CONF_ON_CLIENT_DISCONNECTED,
CONF_TRIGGER_ID,
CONF_VARIABLES,
)
from esphome.core import coroutine_with_priority
@ -63,7 +65,25 @@ def validate_encryption_key(value):
return value
CONFIG_SCHEMA = cv.Schema(
ACTIONS_SCHEMA = automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.valid_name,
cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.valid_name,
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
{
cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
}
),
},
cv.All(
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
cv.rename_key(CONF_SERVICE, CONF_ACTION),
),
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(APIServer),
cv.Optional(CONF_PORT, default=6053): cv.port,
@ -71,19 +91,10 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(
CONF_REBOOT_TIMEOUT, default="15min"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_SERVICES): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
cv.Required(CONF_SERVICE): cv.valid_name,
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
{
cv.validate_id_name: cv.one_of(
*SERVICE_ARG_NATIVE_TYPES, lower=True
),
}
),
}
),
cv.Exclusive(
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
): ACTIONS_SCHEMA,
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
cv.Optional(CONF_ENCRYPTION): cv.Schema(
{
cv.Required(CONF_KEY): validate_encryption_key,
@ -96,7 +107,9 @@ CONFIG_SCHEMA = cv.Schema(
single=True
),
}
).extend(cv.COMPONENT_SCHEMA)
).extend(cv.COMPONENT_SCHEMA),
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
)
@coroutine_with_priority(40.0)
@ -108,7 +121,7 @@ async def to_code(config):
cg.add(var.set_password(config[CONF_PASSWORD]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
for conf in config.get(CONF_SERVICES, []):
for conf in config.get(CONF_ACTIONS, []):
template_args = []
func_args = []
service_arg_names = []
@ -119,7 +132,7 @@ async def to_code(config):
service_arg_names.append(name)
templ = cg.TemplateArguments(*template_args)
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
)
cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf)
@ -152,28 +165,43 @@ async def to_code(config):
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.use_id(APIServer),
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.templatable(
cv.string
),
cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.templatable(
cv.string
),
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
{cv.string: cv.returning_lambda}
),
}
),
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
cv.rename_key(CONF_SERVICE, CONF_ACTION),
)
@automation.register_action(
"homeassistant.action",
HomeAssistantServiceCallAction,
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
)
@automation.register_action(
"homeassistant.service",
HomeAssistantServiceCallAction,
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
)
async def homeassistant_service_to_code(config, action_id, template_arg, args):
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, False)
templ = await cg.templatable(config[CONF_SERVICE], args, None)
templ = await cg.templatable(config[CONF_ACTION], args, None)
cg.add(var.set_service(templ))
for key, value in config[CONF_DATA].items():
templ = await cg.templatable(value, args, None)

View file

@ -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"]

View file

@ -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

View file

@ -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"]

View file

@ -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_(

View file

@ -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"]

View file

@ -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,
)

View file

@ -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,

View file

@ -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"]

View file

@ -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"]

View file

@ -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"]

View file

@ -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,

View file

@ -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)

View file

@ -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",

View file

@ -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__)

View file

@ -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 = {

View file

@ -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 = {

View file

@ -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}

View file

@ -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 = {

View file

@ -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 = {

View file

@ -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"]

View file

@ -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"]

View file

@ -1,5 +1,4 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker
AUTO_LOAD = ["esp32_ble_tracker"]

View file

@ -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"]

View file

@ -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,

View file

@ -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"]

View file

@ -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,
)

View file

@ -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)

View file

@ -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 {

View file

@ -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 {

View file

@ -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"

View file

@ -138,8 +138,8 @@ void HttpRequestUpdate::update() {
this->publish_state();
}
void HttpRequestUpdate::perform() {
if (this->state_ != update::UPDATE_STATE_AVAILABLE) {
void HttpRequestUpdate::perform(bool force) {
if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
return;
}

View file

@ -15,7 +15,7 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
void setup() override;
void update() override;
void perform() override;
void perform(bool force) override;
void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }

View file

@ -1,8 +1,7 @@
import re
import esphome.config_validation as cv
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import __version__
CODEOWNERS = ["@esphome/core"]
@ -39,4 +38,4 @@ def _process_next_url(url: str):
async def setup_improv_core(var, config):
if CONF_NEXT_URL in config:
cg.add(var.set_next_url(_process_next_url(config[CONF_NEXT_URL])))
cg.add_library("esphome/Improv", "1.2.3")
cg.add_library("improv/Improv", "1.2.4")

View file

@ -1,5 +1,6 @@
import logging
from esphome.automation import build_automation, register_action, validate_automation
import esphome.codegen as cg
from esphome.components.display import Display
import esphome.config_validation as cv
@ -8,7 +9,11 @@ from esphome.const import (
CONF_BUFFER_SIZE,
CONF_ID,
CONF_LAMBDA,
CONF_ON_IDLE,
CONF_PAGES,
CONF_TIMEOUT,
CONF_TRIGGER_ID,
CONF_TYPE,
)
from esphome.core import CORE, ID, Lambda
from esphome.cpp_generator import MockObj
@ -16,14 +21,26 @@ 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 .automation import update_to_code
from .btn import btn_spec
from .label import label_spec
from .lvcode import ConstantLiteral, LvContext
# from .menu import menu_spec
from .lv_validation import lv_images_used
from .lvcode import LvContext
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 .widget import LvScrActType, Widget, add_widgets, set_obj_properties
from .rotary_encoders import ROTARY_ENCODER_CONFIG, rotary_encoders_to_code
from .schemas import any_widget_schema, create_modify_schema, obj_schema
from .touchscreens import touchscreen_schema, touchscreens_to_code
from .trigger import generate_triggers
from .types import (
WIDGET_TYPES,
FontEngine,
IdleTrigger,
LvglComponent,
ObjUpdateAction,
lv_font_t,
lvgl_ns,
)
from .widget import Widget, add_widgets, lv_scr_act, set_obj_properties
DOMAIN = "lvgl"
DEPENDENCIES = ("display",)
@ -31,23 +48,24 @@ AUTO_LOAD = ("key_provider",)
CODEOWNERS = ("@clydebarrow",)
LOGGER = logging.getLogger(__name__)
for widg in (
label_spec,
obj_spec,
):
WIDGET_TYPES[widg.name] = widg
lv_scr_act_spec = LvScrActType()
lv_scr_act = Widget.create(
None, ConstantLiteral("lv_scr_act()"), lv_scr_act_spec, {}, parent=None
)
for w_type in (label_spec, obj_spec, btn_spec):
WIDGET_TYPES[w_type.name] = w_type
WIDGET_SCHEMA = any_widget_schema()
for w_type in WIDGET_TYPES.values():
register_action(
f"lvgl.{w_type.name}.update",
ObjUpdateAction,
create_modify_schema(w_type),
)(update_to_code)
async def add_init_lambda(lv_component, init):
if init:
lamb = await cg.process_lambda(Lambda(init), [(lv_disp_t_ptr, "lv_disp")])
lamb = await cg.process_lambda(
Lambda(init), [(LvglComponent.operator("ptr"), "lv_component")]
)
cg.add(lv_component.add_init_lambda(lamb))
@ -93,8 +111,15 @@ 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")
for image_id in lv_images_used:
path = global_config.get_path_for_id(image_id)[:-1]
image_conf = global_config.get_config_for_path(path)
if image_conf[CONF_TYPE] in ("RGBA", "RGB24"):
raise cv.Invalid(
"Using RGBA or RGB24 in image config not compatible with LVGL", path
)
async def to_code(config):
@ -132,7 +157,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 +177,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,15 +186,24 @@ 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 rotary_encoders_to_code(lv_component, config)
await set_obj_properties(lv_scr_act, config)
await add_widgets(lv_scr_act, config)
Widget.set_completed()
await generate_triggers(lv_component)
for conf in config.get(CONF_ON_IDLE, ()):
templ = await cg.templatable(conf[CONF_TIMEOUT], [], cg.uint32)
idle_trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], lv_component, templ)
await build_automation(idle_trigger, [], conf)
await add_init_lambda(lv_component, LvContext.get_code())
for comp in helpers.lvgl_components_required:
CORE.add_define(f"USE_LVGL_{comp.upper()}")
@ -190,7 +224,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),
@ -205,8 +239,18 @@ CONFIG_SCHEMA = (
cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of(
"big_endian", "little_endian"
),
cv.Optional(CONF_ON_IDLE): validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(IdleTrigger),
cv.Required(CONF_TIMEOUT): cv.templatable(
cv.positive_time_period_milliseconds
),
}
),
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,
cv.GenerateID(df.CONF_ROTARY_ENCODERS): ROTARY_ENCODER_CONFIG,
}
)
).add_extra(cv.has_at_least_one_key(CONF_PAGES, df.CONF_WIDGETS))

View file

@ -0,0 +1,188 @@
from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TIMEOUT
from esphome.core import Lambda
from esphome.cpp_generator import RawStatement
from esphome.cpp_types import nullptr
from .defines import CONF_LVGL_ID, CONF_SHOW_SNOW, literal
from .lv_validation import lv_bool
from .lvcode import (
LambdaContext,
ReturnStatement,
add_line_marks,
lv,
lv_add,
lv_obj,
lvgl_comp,
)
from .schemas import ACTION_SCHEMA, LVGL_SCHEMA
from .types import (
LvglAction,
LvglComponent,
LvglComponentPtr,
LvglCondition,
ObjUpdateAction,
lv_obj_t,
)
from .widget import Widget, get_widget, lv_scr_act, set_obj_properties
async def action_to_code(action: list, action_id, widget: Widget, template_arg, args):
with LambdaContext() as context:
lv.cond_if(widget.obj == nullptr)
lv_add(RawStatement(" return;"))
lv.cond_endif()
code = context.get_code()
code.extend(action)
action = "\n".join(code) + "\n\n"
lamb = await cg.process_lambda(Lambda(action), args)
var = cg.new_Pvariable(action_id, template_arg, lamb)
return var
async def update_to_code(config, action_id, template_arg, args):
if config is not None:
widget = await get_widget(config)
with LambdaContext() as context:
add_line_marks(action_id)
await set_obj_properties(widget, config)
await widget.type.to_code(widget, config)
if (
widget.type.w_type.value_property is not None
and widget.type.w_type.value_property in config
):
lv.event_send(widget.obj, literal("LV_EVENT_VALUE_CHANGED"), nullptr)
return await action_to_code(
context.get_code(), action_id, widget, template_arg, args
)
@automation.register_condition(
"lvgl.is_paused",
LvglCondition,
LVGL_SCHEMA,
)
async def lvgl_is_paused(config, condition_id, template_arg, args):
lvgl = config[CONF_LVGL_ID]
with LambdaContext(
[(LvglComponentPtr, "lvgl_comp")], return_type=cg.bool_
) as context:
lv_add(ReturnStatement(lvgl_comp.is_paused()))
var = cg.new_Pvariable(condition_id, template_arg, await context.get_lambda())
await cg.register_parented(var, lvgl)
return var
@automation.register_condition(
"lvgl.is_idle",
LvglCondition,
LVGL_SCHEMA.extend(
{
cv.Required(CONF_TIMEOUT): cv.templatable(
cv.positive_time_period_milliseconds
)
}
),
)
async def lvgl_is_idle(config, condition_id, template_arg, args):
lvgl = config[CONF_LVGL_ID]
timeout = await cg.templatable(config[CONF_TIMEOUT], [], cg.uint32)
with LambdaContext(
[(LvglComponentPtr, "lvgl_comp")], return_type=cg.bool_
) as context:
lv_add(ReturnStatement(lvgl_comp.is_idle(timeout)))
var = cg.new_Pvariable(condition_id, template_arg, await context.get_lambda())
await cg.register_parented(var, lvgl)
return var
@automation.register_action(
"lvgl.widget.redraw",
ObjUpdateAction,
cv.Schema(
{
cv.Optional(CONF_ID): cv.use_id(lv_obj_t),
cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent),
}
),
)
async def obj_invalidate_to_code(config, action_id, template_arg, args):
if CONF_ID in config:
w = await get_widget(config)
else:
w = lv_scr_act
with LambdaContext() as context:
add_line_marks(action_id)
lv_obj.invalidate(w.obj)
return await action_to_code(context.get_code(), action_id, w, template_arg, args)
@automation.register_action(
"lvgl.pause",
LvglAction,
{
cv.GenerateID(): cv.use_id(LvglComponent),
cv.Optional(CONF_SHOW_SNOW, default=False): lv_bool,
},
)
async def pause_action_to_code(config, action_id, template_arg, args):
with LambdaContext([(LvglComponentPtr, "lvgl_comp")]) as context:
add_line_marks(action_id)
lv_add(lvgl_comp.set_paused(True, config[CONF_SHOW_SNOW]))
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"lvgl.resume",
LvglAction,
{
cv.GenerateID(): cv.use_id(LvglComponent),
},
)
async def resume_action_to_code(config, action_id, template_arg, args):
with LambdaContext([(LvglComponentPtr, "lvgl_comp")]) as context:
add_line_marks(action_id)
lv_add(lvgl_comp.set_paused(False, False))
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action("lvgl.widget.disable", ObjUpdateAction, ACTION_SCHEMA)
async def obj_disable_to_code(config, action_id, template_arg, args):
w = await get_widget(config)
with LambdaContext() as context:
add_line_marks(action_id)
w.add_state("LV_STATE_DISABLED")
return await action_to_code(context.get_code(), action_id, w, template_arg, args)
@automation.register_action("lvgl.widget.enable", ObjUpdateAction, ACTION_SCHEMA)
async def obj_enable_to_code(config, action_id, template_arg, args):
w = await get_widget(config)
with LambdaContext() as context:
add_line_marks(action_id)
w.clear_state("LV_STATE_DISABLED")
return await action_to_code(context.get_code(), action_id, w, template_arg, args)
@automation.register_action("lvgl.widget.hide", ObjUpdateAction, ACTION_SCHEMA)
async def obj_hide_to_code(config, action_id, template_arg, args):
w = await get_widget(config)
with LambdaContext() as context:
add_line_marks(action_id)
w.add_flag("LV_OBJ_FLAG_HIDDEN")
return await action_to_code(context.get_code(), action_id, w, template_arg, args)
@automation.register_action("lvgl.widget.show", ObjUpdateAction, ACTION_SCHEMA)
async def obj_show_to_code(config, action_id, template_arg, args):
w = await get_widget(config)
with LambdaContext() as context:
add_line_marks(action_id)
w.clear_flag("LV_OBJ_FLAG_HIDDEN")
return await action_to_code(context.get_code(), action_id, w, template_arg, args)

View file

@ -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,))
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",)
async def to_code(self, w, config):
return []
btn_spec = BtnType()

View file

@ -4,12 +4,32 @@ Constants already defined in esphome.const are not duplicated here and must be i
"""
from typing import Union
from esphome import codegen as cg, config_validation as cv
from esphome.core import ID, Lambda
from esphome.cpp_generator import Literal
from esphome.cpp_types import uint32
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from .lvcode import ConstantLiteral
from .helpers import requires_component
class ConstantLiteral(Literal):
__slots__ = ("constant",)
def __init__(self, constant: str):
super().__init__()
self.constant = constant
def __str__(self):
return self.constant
def literal(arg: Union[str, ConstantLiteral]):
if isinstance(arg, str):
return ConstantLiteral(arg)
return arg
class LValidator:
@ -18,14 +38,19 @@ class LValidator:
has `process()` to convert a value during code generation
"""
def __init__(self, validator, rtype, idtype=None, idexpr=None, retmapper=None):
def __init__(
self, validator, rtype, idtype=None, idexpr=None, retmapper=None, requires=None
):
self.validator = validator
self.rtype = rtype
self.idtype = idtype
self.idexpr = idexpr
self.retmapper = retmapper
self.requires = requires
def __call__(self, value):
if self.requires:
value = requires_component(self.requires)(value)
if isinstance(value, cv.Lambda):
return cv.returning_lambda(value)
if self.idtype is not None and isinstance(value, ID):
@ -422,6 +447,7 @@ CONF_RECOLOR = "recolor"
CONF_RIGHT_BUTTON = "right_button"
CONF_ROLLOVER = "rollover"
CONF_ROOT_BACK_BTN = "root_back_btn"
CONF_ROTARY_ENCODERS = "rotary_encoders"
CONF_ROWS = "rows"
CONF_SCALES = "scales"
CONF_SCALE_LINES = "scale_lines"
@ -446,6 +472,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 +501,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))

View file

@ -22,7 +22,6 @@ def add_lv_use(*names):
lv_fonts_used = set()
esphome_fonts_used = set()
REQUIRED_COMPONENTS = {}
lvgl_components_required = set()

View file

@ -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):

View file

@ -2,25 +2,42 @@ import esphome.codegen as cg
from esphome.components.binary_sensor import BinarySensor
from esphome.components.color import ColorStruct
from esphome.components.font import Font
from esphome.components.image import Image_
from esphome.components.sensor import Sensor
from esphome.components.text_sensor import TextSensor
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
from . import types as ty
from .defines import LV_FONTS, LValidator, LvConstant
from .defines import LV_FONTS, ConstantLiteral, LValidator, LvConstant, literal
from .helpers import (
esphome_fonts_used,
lv_fonts_used,
lvgl_components_required,
requires_component,
)
from .lvcode import ConstantLiteral, lv_expr
from .types import lv_font_t
from .lvcode import lv_expr
from .types import lv_font_t, lv_img_t
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)
@schema_extractor("one_of")
@ -43,16 +60,22 @@ 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)
def zoom(value):
value = cv.float_range(0.1, 10.0)(value)
return int(value * 256)
@ -68,7 +91,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 +102,58 @@ 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)
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
radius = LValidator(radius_validator, uint32, retmapper=literal)
def id_name(value):
if value == SCHEMA_EXTRACT:
return "id"
return cv.validate_id_name(value)
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_images_used = set()
def image_validator(value):
value = requires_component("image")(value)
value = cv.use_id(Image_)(value)
lv_images_used.add(value)
return value
lv_image = LValidator(
image_validator,
lv_img_t,
retmapper=lambda x: lv_expr.img_from(MockObj(x)),
requires="image",
)
lv_bool = LValidator(
cv.boolean, cg.bool_, BinarySensor, "get_state()", retmapper=literal
)
def lvms_validator_(value):
@ -145,26 +198,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()

View file

@ -8,8 +8,8 @@ from esphome.cpp_generator import (
AssignmentExpression,
CallExpression,
Expression,
ExpressionStatement,
LambdaExpression,
Literal,
MockObj,
RawExpression,
RawStatement,
@ -19,7 +19,9 @@ from esphome.cpp_generator import (
statement,
)
from .defines import ConstantLiteral
from .helpers import get_line_marks
from .types import lv_group_t
_LOGGER = logging.getLogger(__name__)
@ -105,29 +107,40 @@ class LambdaContext(CodeContext):
def __init__(
self,
parameters: list[tuple[SafeExpType, str]],
return_type: SafeExpType = None,
parameters: list[tuple[SafeExpType, str]] = None,
return_type: SafeExpType = cg.void,
capture: str = "",
):
super().__init__()
self.code_list: list[Statement] = []
self.parameters = parameters
self.return_type = return_type
self.capture = capture
def add(self, expression: Union[Expression, Statement]):
self.code_list.append(expression)
return expression
async def code(self) -> LambdaExpression:
async def get_lambda(self) -> LambdaExpression:
code_text = self.get_code()
return await cg.process_lambda(
Lambda("\n".join(code_text) + "\n\n"),
self.parameters,
capture=self.capture,
return_type=self.return_type,
)
def get_code(self):
code_text = []
for exp in self.code_list:
text = str(statement(exp))
text = text.rstrip()
code_text.append(text)
return await cg.process_lambda(
Lambda("\n".join(code_text) + "\n\n"),
self.parameters,
return_type=self.return_type,
)
return code_text
def __enter__(self):
super().__enter__()
return self
class LocalVariable(MockObj):
@ -187,13 +200,18 @@ class MockLv:
return result
def cond_if(self, expression: Expression):
CodeContext.append(RawExpression(f"if({expression}) {{"))
CodeContext.append(RawStatement(f"if {expression} {{"))
def cond_else(self):
CodeContext.append(RawExpression("} else {"))
CodeContext.append(RawStatement("} else {"))
def cond_endif(self):
CodeContext.append(RawExpression("}"))
CodeContext.append(RawStatement("}"))
class ReturnStatement(ExpressionStatement):
def __str__(self):
return f"return {self.expression};"
class LvExpr(MockLv):
@ -210,6 +228,7 @@ lv = MockLv("lv_")
lv_expr = LvExpr("lv_")
# Mock for lv_obj_ calls
lv_obj = MockLv("lv_obj_")
lvgl_comp = MockObj("lvgl_comp", "->")
# equivalent to cg.add() for the lvgl init context
@ -226,12 +245,19 @@ def lv_assign(target, expression):
lv_add(RawExpression(f"{target} = {expression}"))
class ConstantLiteral(Literal):
__slots__ = ("constant",)
lv_groups = {} # Widget group names
def __init__(self, constant: str):
super().__init__()
self.constant = constant
def __str__(self):
return self.constant
def add_group(name):
if name is None:
return None
fullname = f"lv_esp_group_{name}"
if name not in lv_groups:
gid = ID(fullname, True, type=lv_group_t.operator("ptr"))
lv_add(
AssignmentExpression(
type_=gid.type, modifier="", name=fullname, rhs=lv_expr.group_create()
)
)
lv_groups[name] = ConstantLiteral(fullname)
return lv_groups[name]

View file

@ -19,13 +19,35 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, const uint8_t *ptr) {
}
void LvglComponent::flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
if (!this->paused_) {
auto now = millis();
this->draw_buffer_(area, (const uint8_t *) color_p);
ESP_LOGV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", area->x1, area->y1, lv_area_get_width(area),
lv_area_get_height(area), (int) (millis() - now));
}
lv_disp_flush_ready(disp_drv);
}
void LvglComponent::write_random_() {
// length of 2 lines in 32 bit units
// we write 2 lines for the benefit of displays that won't write one line at a time.
size_t line_len = this->disp_drv_.hor_res * LV_COLOR_DEPTH / 8 / 4 * 2;
for (size_t i = 0; i != line_len; i++) {
((uint32_t *) (this->draw_buf_.buf1))[i] = random_uint32();
}
lv_area_t area;
area.x1 = 0;
area.x2 = this->disp_drv_.hor_res - 1;
if (this->snow_line_ == this->disp_drv_.ver_res / 2) {
area.y1 = static_cast<lv_coord_t>(random_uint32() % (this->disp_drv_.ver_res / 2) * 2);
} else {
area.y1 = this->snow_line_++ * 2;
}
// write 2 lines
area.y2 = area.y1 + 1;
this->draw_buffer_(&area, (const uint8_t *) this->draw_buf_.buf1);
}
void LvglComponent::setup() {
ESP_LOGCONFIG(TAG, "LVGL Setup starts");
#if LV_USE_LOG
@ -38,7 +60,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;
@ -72,10 +96,53 @@ void LvglComponent::setup() {
ESP_LOGV(TAG, "sw_rotate = %d, rotated=%d", this->disp_drv_.sw_rotate, this->disp_drv_.rotated);
this->disp_ = lv_disp_drv_register(&this->disp_drv_);
for (const auto &v : this->init_lambdas_)
v(this->disp_);
v(this);
lv_disp_trig_activity(this->disp_);
ESP_LOGCONFIG(TAG, "LVGL Setup complete");
}
#ifdef USE_LVGL_IMAGE
lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc) {
if (img_dsc == nullptr)
img_dsc = new lv_img_dsc_t(); // NOLINT
img_dsc->header.always_zero = 0;
img_dsc->header.reserved = 0;
img_dsc->header.w = src->get_width();
img_dsc->header.h = src->get_height();
img_dsc->data = src->get_data_start();
img_dsc->data_size = image_type_to_width_stride(img_dsc->header.w * img_dsc->header.h, src->get_type());
switch (src->get_type()) {
case image::IMAGE_TYPE_BINARY:
img_dsc->header.cf = LV_IMG_CF_ALPHA_1BIT;
break;
case image::IMAGE_TYPE_GRAYSCALE:
img_dsc->header.cf = LV_IMG_CF_ALPHA_8BIT;
break;
case image::IMAGE_TYPE_RGB24:
img_dsc->header.cf = LV_IMG_CF_RGB888;
break;
case image::IMAGE_TYPE_RGB565:
#if LV_COLOR_DEPTH == 16
img_dsc->header.cf = src->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR;
#else
img_dsc->header.cf = LV_IMG_CF_RGB565;
#endif
break;
case image::IMAGE_TYPE_RGBA:
#if LV_COLOR_DEPTH == 32
img_dsc->header.cf = LV_IMG_CF_TRUE_COLOR;
#else
img_dsc->header.cf = LV_IMG_CF_RGBA8888;
#endif
break;
}
return img_dsc;
}
#endif
} // namespace lvgl
} // namespace esphome
@ -85,7 +152,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 +171,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

View file

@ -1,42 +1,55 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_LVGL
#ifdef USE_LVGL_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif // USE_LVGL_BINARY_SENSOR
#ifdef USE_LVGL_ROTARY_ENCODER
#include "esphome/components/rotary_encoder/rotary_encoder.h"
#endif // USE_LVGL_ROTARY_ENCODER
// required for clang-tidy
#ifndef LV_CONF_H
#define LV_CONF_SKIP 1 // NOLINT
#endif
#endif // LV_CONF_H
#include "esphome/components/display/display.h"
#include "esphome/components/display/display_color_utils.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <lvgl.h>
#include <utility>
#include <vector>
#ifdef USE_LVGL_IMAGE
#include "esphome/components/image/image.h"
#endif // USE_LVGL_IMAGE
#ifdef USE_LVGL_FONT
#include "esphome/components/font/font.h"
#endif
#endif // USE_LVGL_FONT
#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; }
void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; }
lv_obj_t *obj{};
};
@ -45,6 +58,15 @@ using set_value_lambda_t = std::function<void(float)>;
using event_callback_t = void(_lv_event_t *);
using text_lambda_t = std::function<const char *()>;
template<typename... Ts> class ObjUpdateAction : public Action<Ts...> {
public:
explicit ObjUpdateAction(std::function<void(Ts...)> &&lamb) : lamb_(std::move(lamb)) {}
void play(Ts... x) override { this->lamb_(x...); }
protected:
std::function<void(Ts...)> lamb_;
};
#ifdef USE_LVGL_FONT
class FontEngine {
public:
@ -63,6 +85,9 @@ class FontEngine {
lv_font_t lv_font_{};
};
#endif // USE_LVGL_FONT
#ifdef USE_LVGL_IMAGE
lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc = nullptr);
#endif // USE_LVGL_IMAGE
class LvglComponent : public PollingComponent {
constexpr static const char *const TAG = "lvgl";
@ -88,32 +113,197 @@ class LvglComponent : public PollingComponent {
area->y2++;
}
void loop() override { lv_timer_handler_run_in_period(5); }
void setup() override;
void update() override {}
void update() override {
// update indicators
if (this->paused_) {
return;
}
this->idle_callbacks_.call(lv_disp_get_inactive_time(this->disp_));
}
void loop() override {
if (this->paused_) {
if (this->show_snow_)
this->write_random_();
}
lv_timer_handler_run_in_period(5);
}
void add_on_idle_callback(std::function<void(uint32_t)> &&callback) {
this->idle_callbacks_.add(std::move(callback));
}
void add_display(display::Display *display) { this->displays_.push_back(display); }
void add_init_lambda(const std::function<void(lv_disp_t *)> &lamb) { this->init_lambdas_.push_back(lamb); }
void add_init_lambda(const std::function<void(LvglComponent *)> &lamb) { this->init_lambdas_.push_back(lamb); }
void dump_config() override;
void set_full_refresh(bool full_refresh) { this->full_refresh_ = full_refresh; }
bool is_idle(uint32_t idle_ms) { return lv_disp_get_inactive_time(this->disp_) > idle_ms; }
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;
this->show_snow_ = show_snow;
this->snow_line_ = 0;
if (!paused && lv_scr_act() != nullptr) {
lv_disp_trig_activity(this->disp_); // resets the inactivity time
lv_obj_invalidate(lv_scr_act());
}
}
void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event) {
lv_obj_add_event_cb(obj, callback, event, this);
if (event == LV_EVENT_VALUE_CHANGED) {
lv_obj_add_event_cb(obj, callback, lv_custom_event, this);
}
}
bool is_paused() const { return this->paused_; }
protected:
void write_random_();
void draw_buffer_(const lv_area_t *area, const uint8_t *ptr);
void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
std::vector<display::Display *> displays_{};
lv_disp_draw_buf_t draw_buf_{};
lv_disp_drv_t disp_drv_{};
lv_disp_t *disp_{};
bool paused_{};
bool show_snow_{};
lv_coord_t snow_line_{};
std::vector<std::function<void(lv_disp_t *)>> init_lambdas_;
std::vector<std::function<void(LvglComponent *lv_component)>> init_lambdas_;
CallbackManager<void(uint32_t)> idle_callbacks_{};
size_t buffer_frac_{1};
bool full_refresh_{};
};
class IdleTrigger : public Trigger<> {
public:
explicit IdleTrigger(LvglComponent *parent, TemplatableValue<uint32_t> timeout) : timeout_(std::move(timeout)) {
parent->add_on_idle_callback([this](uint32_t idle_time) {
if (!this->is_idle_ && idle_time > this->timeout_.value()) {
this->is_idle_ = true;
this->trigger();
} else if (this->is_idle_ && idle_time < this->timeout_.value()) {
this->is_idle_ = false;
}
});
}
protected:
TemplatableValue<uint32_t> timeout_;
bool is_idle_{};
};
template<typename... Ts> class LvglAction : public Action<Ts...>, public Parented<LvglComponent> {
public:
explicit LvglAction(std::function<void(LvglComponent *)> &&lamb) : action_(std::move(lamb)) {}
void play(Ts... x) override { this->action_(this->parent_); }
protected:
std::function<void(LvglComponent *)> action_{};
};
template<typename... Ts> class LvglCondition : public Condition<Ts...>, public Parented<LvglComponent> {
public:
LvglCondition(std::function<bool(LvglComponent *)> &&condition_lambda)
: condition_lambda_(std::move(condition_lambda)) {}
bool check(Ts... x) override { return this->condition_lambda_(this->parent_); }
protected:
std::function<bool(LvglComponent *)> condition_lambda_{};
};
#ifdef USE_LVGL_TOUCHSCREEN
class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglComponent> {
public:
LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time) {
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<LVTouchListener *>(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
#ifdef USE_LVGL_KEY_LISTENER
class LVEncoderListener : public Parented<LvglComponent> {
public:
LVEncoderListener(lv_indev_type_t type, uint16_t lpt, uint16_t lprt) {
lv_indev_drv_init(&this->drv_);
this->drv_.type = type;
this->drv_.user_data = this;
this->drv_.long_press_time = lpt;
this->drv_.long_press_repeat_time = lprt;
this->drv_.read_cb = [](lv_indev_drv_t *d, lv_indev_data_t *data) {
auto *l = static_cast<LVEncoderListener *>(d->user_data);
data->state = l->pressed_ ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
data->key = l->key_;
data->enc_diff = (int16_t) (l->count_ - l->last_count_);
l->last_count_ = l->count_;
data->continue_reading = false;
};
}
void set_left_button(binary_sensor::BinarySensor *left_button) {
left_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_LEFT, state); });
}
void set_right_button(binary_sensor::BinarySensor *right_button) {
right_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_RIGHT, state); });
}
void set_enter_button(binary_sensor::BinarySensor *enter_button) {
enter_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_ENTER, state); });
}
void set_sensor(rotary_encoder::RotaryEncoderSensor *sensor) {
sensor->register_listener([this](int32_t count) { this->set_count(count); });
}
void event(int key, bool pressed) {
if (!this->parent_->is_paused()) {
this->pressed_ = pressed;
this->key_ = key;
}
}
void set_count(int32_t count) {
if (!this->parent_->is_paused())
this->count_ = count;
}
lv_indev_drv_t *get_drv() { return &this->drv_; }
protected:
lv_indev_drv_t drv_{};
bool pressed_{};
int32_t count_{};
int32_t last_count_{};
int key_{};
};
#endif // USE_LVGL_KEY_LISTENER
} // namespace lvgl
} // namespace esphome
#endif

View file

@ -1,6 +1,9 @@
from .defines import CONF_OBJ
from .types import lv_obj_t
from .widget import WidgetType
from esphome import automation
from .automation import update_to_code
from .defines import CONF_MAIN, CONF_OBJ
from .schemas import create_modify_schema
from .types import ObjUpdateAction, WidgetType, lv_obj_t
class ObjType(WidgetType):
@ -9,14 +12,17 @@ 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 []
obj_spec = ObjType()
@automation.register_action(
"lvgl.widget.update", ObjUpdateAction, create_modify_schema(obj_spec)
)
async def obj_update_to_code(config, action_id, template_arg, args):
return await update_to_code(config, action_id, template_arg, args)

View file

@ -0,0 +1,62 @@
import esphome.codegen as cg
from esphome.components.binary_sensor import BinarySensor
from esphome.components.rotary_encoder.sensor import RotaryEncoderSensor
import esphome.config_validation as cv
from esphome.const import CONF_GROUP, CONF_ID, CONF_SENSOR
from .defines import (
CONF_ENTER_BUTTON,
CONF_LEFT_BUTTON,
CONF_LONG_PRESS_REPEAT_TIME,
CONF_LONG_PRESS_TIME,
CONF_RIGHT_BUTTON,
CONF_ROTARY_ENCODERS,
)
from .helpers import lvgl_components_required
from .lvcode import add_group, lv, lv_add, lv_expr
from .schemas import ENCODER_SCHEMA
from .types import lv_indev_type_t
ROTARY_ENCODER_CONFIG = cv.ensure_list(
ENCODER_SCHEMA.extend(
{
cv.Required(CONF_ENTER_BUTTON): cv.use_id(BinarySensor),
cv.Required(CONF_SENSOR): cv.Any(
cv.use_id(RotaryEncoderSensor),
cv.Schema(
{
cv.Required(CONF_LEFT_BUTTON): cv.use_id(BinarySensor),
cv.Required(CONF_RIGHT_BUTTON): cv.use_id(BinarySensor),
}
),
),
}
)
)
async def rotary_encoders_to_code(var, config):
for enc_conf in config.get(CONF_ROTARY_ENCODERS, ()):
lvgl_components_required.add("KEY_LISTENER")
lvgl_components_required.add("ROTARY_ENCODER")
lpt = enc_conf[CONF_LONG_PRESS_TIME].total_milliseconds
lprt = enc_conf[CONF_LONG_PRESS_REPEAT_TIME].total_milliseconds
listener = cg.new_Pvariable(
enc_conf[CONF_ID], lv_indev_type_t.LV_INDEV_TYPE_ENCODER, lpt, lprt
)
await cg.register_parented(listener, var)
if sensor_config := enc_conf.get(CONF_SENSOR):
if isinstance(sensor_config, dict):
b_sensor = await cg.get_variable(sensor_config[CONF_LEFT_BUTTON])
cg.add(listener.set_left_button(b_sensor))
b_sensor = await cg.get_variable(sensor_config[CONF_RIGHT_BUTTON])
cg.add(listener.set_right_button(b_sensor))
else:
sensor_config = await cg.get_variable(sensor_config)
lv_add(listener.set_sensor(sensor_config))
b_sensor = await cg.get_variable(enc_conf[CONF_ENTER_BUTTON])
cg.add(listener.set_enter_button(b_sensor))
if group := add_group(enc_conf.get(CONF_GROUP)):
lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group)
else:
lv.indev_drv_register(listener.get_drv())

View file

@ -1,17 +1,22 @@
from esphome import config_validation as cv
from esphome.const import CONF_ARGS, CONF_FORMAT, CONF_ID, CONF_STATE, CONF_TYPE
from esphome.automation import Trigger, validate_automation
from esphome.const import (
CONF_ARGS,
CONF_FORMAT,
CONF_GROUP,
CONF_ID,
CONF_ON_VALUE,
CONF_STATE,
CONF_TRIGGER_ID,
CONF_TYPE,
)
from esphome.core import TimePeriod
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 .lv_validation import lv_font
from .types import WIDGET_TYPES, get_widget_type
from .helpers import add_lv_use, requires_component, validate_printf
from .lv_validation import id_name, lv_font
from .types import WIDGET_TYPES, WidgetType
# A schema for text properties
TEXT_SCHEMA = cv.Schema(
@ -33,6 +38,28 @@ TEXT_SCHEMA = cv.Schema(
}
)
ACTION_SCHEMA = cv.maybe_simple_value(
{
cv.Required(CONF_ID): cv.use_id(ty.lv_pseudo_button_t),
},
key=CONF_ID,
)
PRESS_TIME = cv.All(
lvalid.lv_milliseconds, cv.Range(max=TimePeriod(milliseconds=65535))
)
ENCODER_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.All(
cv.declare_id(ty.LVEncoderListener), requires_component("binary_sensor")
),
cv.Optional(CONF_GROUP): lvalid.id_name,
cv.Optional(df.CONF_LONG_PRESS_TIME, default="400ms"): PRESS_TIME,
cv.Optional(df.CONF_LONG_PRESS_REPEAT_TIME, default="100ms"): PRESS_TIME,
}
)
# All LVGL styles and their validators
STYLE_PROPS = {
"align": df.CHILD_ALIGNMENTS.one_of,
@ -46,9 +73,10 @@ 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_image_src": lvalid.lv_image,
"bg_main_stop": lvalid.stop_value,
"bg_opa": lvalid.opacity,
"border_color": lvalid.lv_color,
@ -60,8 +88,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 +136,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 +169,56 @@ 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 automation_schema(typ: ty.LvType):
if typ.has_on_value:
events = df.LV_EVENT_TRIGGERS + (CONF_ON_VALUE,)
else:
events = df.LV_EVENT_TRIGGERS
if isinstance(typ, ty.LvType):
template = Trigger.template(typ.get_arg_type())
else:
template = Trigger.template()
return {
cv.Optional(event): validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(template),
}
)
for event in events
}
def create_modify_schema(widget_type):
return (
part_schema(widget_type)
.extend(
{
cv.Required(CONF_ID): cv.use_id(widget_type),
cv.Optional(CONF_STATE): SET_STATE_SCHEMA,
}
)
.extend(FLAG_SCHEMA)
.extend(widget_type.modify_schema)
)
def obj_schema(widget_type: WidgetType):
"""
Create a schema for a widget type itself i.e. no allowance for children
:param widget_type:
@ -160,10 +228,12 @@ def obj_schema(widget_type: str):
part_schema(widget_type)
.extend(FLAG_SCHEMA)
.extend(ALIGN_TO_SCHEMA)
.extend(automation_schema(widget_type.w_type))
.extend(
cv.Schema(
{
cv.Optional(CONF_STATE): SET_STATE_SCHEMA,
cv.Optional(CONF_GROUP): id_name,
}
)
)
@ -187,13 +257,19 @@ STYLED_TEXT_SCHEMA = cv.maybe_simple_value(
STYLE_SCHEMA.extend(TEXT_SCHEMA), key=df.CONF_TEXT
)
# For use by platform components
LVGL_SCHEMA = cv.Schema(
{
cv.GenerateID(df.CONF_LVGL_ID): cv.use_id(ty.LvglComponent),
}
)
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 +279,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 +296,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 +304,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 +321,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 +337,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()))

View file

@ -0,0 +1,45 @@
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
from .defines import (
CONF_LONG_PRESS_REPEAT_TIME,
CONF_LONG_PRESS_TIME,
CONF_TOUCHSCREENS,
)
from .helpers import lvgl_components_required
from .lvcode import lv
from .schemas import PRESS_TIME
from .types import LVTouchListener
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))

View file

@ -0,0 +1,61 @@
from esphome import automation
import esphome.codegen as cg
from esphome.const import CONF_ID, CONF_ON_VALUE, CONF_TRIGGER_ID
from .defines import (
CONF_ALIGN,
CONF_ALIGN_TO,
CONF_X,
CONF_Y,
LV_EVENT,
LV_EVENT_TRIGGERS,
literal,
)
from .lvcode import LambdaContext, add_line_marks, lv, lv_add
from .widget import widget_map
lv_event_t_ptr = cg.global_ns.namespace("lv_event_t").operator("ptr")
async def generate_triggers(lv_component):
"""
Generate LVGL triggers for all defined widgets
Must be done after all widgets completed
:param lv_component: The parent component
:return:
"""
for w in widget_map.values():
if w.config:
for event, conf in {
event: conf
for event, conf in w.config.items()
if event in LV_EVENT_TRIGGERS
}.items():
conf = conf[0]
w.add_flag("LV_OBJ_FLAG_CLICKABLE")
event = "LV_EVENT_" + LV_EVENT[event[3:].upper()]
await add_trigger(conf, event, lv_component, w)
for conf in w.config.get(CONF_ON_VALUE, ()):
await add_trigger(conf, "LV_EVENT_VALUE_CHANGED", lv_component, w)
# Generate align to directives while we're here
if align_to := w.config.get(CONF_ALIGN_TO):
target = widget_map[align_to[CONF_ID]].obj
align = align_to[CONF_ALIGN]
x = align_to[CONF_X]
y = align_to[CONF_Y]
lv.obj_align_to(w.obj, target, align, x, y)
async def add_trigger(conf, event, lv_component, w):
tid = conf[CONF_TRIGGER_ID]
add_line_marks(tid)
trigger = cg.new_Pvariable(tid)
args = w.get_args()
value = w.get_value()
await automation.build_automation(trigger, args, conf)
with LambdaContext([(lv_event_t_ptr, "event_data")]) as context:
add_line_marks(tid)
lv_add(trigger.trigger(value))
lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), literal(event)))

View file

@ -1,27 +1,8 @@
from esphome import codegen as cg
from esphome import automation, codegen as cg
from esphome.core import ID
from esphome.cpp_generator import MockObjClass
from .defines import CONF_LABEL, CONF_OBJ, CONF_TEXT
uint16_t_ptr = cg.uint16.operator("ptr")
lvgl_ns = cg.esphome_ns.namespace("lvgl")
char_ptr = cg.global_ns.namespace("char").operator("ptr")
void_ptr = cg.void.operator("ptr")
LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent)
lv_event_code_t = cg.global_ns.namespace("lv_event_code_t")
FontEngine = lvgl_ns.class_("FontEngine")
LvCompound = lvgl_ns.class_("LvCompound")
lv_font_t = cg.global_ns.class_("lv_font_t")
lv_style_t = cg.global_ns.struct("lv_style_t")
lv_pseudo_button_t = lvgl_ns.class_("LvPseudoButton")
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")
# this will be populated later, in __init__.py to avoid circular imports.
WIDGET_TYPES: dict = {}
from .defines import CONF_TEXT
class LvType(cg.MockObjClass):
@ -37,6 +18,38 @@ class LvType(cg.MockObjClass):
return self.args[0][0] if len(self.args) else None
uint16_t_ptr = cg.uint16.operator("ptr")
lvgl_ns = cg.esphome_ns.namespace("lvgl")
char_ptr = cg.global_ns.namespace("char").operator("ptr")
void_ptr = cg.void.operator("ptr")
LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent)
LvglComponentPtr = LvglComponent.operator("ptr")
lv_event_code_t = cg.global_ns.namespace("lv_event_code_t")
lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t")
FontEngine = lvgl_ns.class_("FontEngine")
IdleTrigger = lvgl_ns.class_("IdleTrigger", automation.Trigger.template())
ObjUpdateAction = lvgl_ns.class_("ObjUpdateAction", automation.Action)
LvglCondition = lvgl_ns.class_("LvglCondition", automation.Condition)
LvglAction = lvgl_ns.class_("LvglAction", automation.Action)
LvCompound = lvgl_ns.class_("LvCompound")
lv_font_t = cg.global_ns.class_("lv_font_t")
lv_style_t = cg.global_ns.struct("lv_style_t")
lv_pseudo_button_t = lvgl_ns.class_("LvPseudoButton")
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")
lv_group_t = cg.global_ns.struct("lv_group_t")
LVTouchListener = lvgl_ns.class_("LVTouchListener")
LVEncoderListener = lvgl_ns.class_("LVEncoderListener")
lv_obj_t = LvType("lv_obj_t")
lv_img_t = LvType("lv_img_t")
# this will be populated later, in __init__.py to avoid circular imports.
WIDGET_TYPES: dict = {}
class LvText(LvType):
def __init__(self, *args, **kwargs):
super().__init__(
@ -48,17 +61,77 @@ 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.has_state("LV_STATE_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
if schema is None:
self.schema = {}
else:
self.schema = schema
if modify_schema is None:
self.modify_schema = self.schema
else:
self.modify_schema = self.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 ()

View file

@ -4,9 +4,9 @@ from typing import Any
from esphome import codegen as cg, config_validation as cv
from esphome.config_validation import Invalid
from esphome.const import CONF_GROUP, CONF_ID, CONF_STATE
from esphome.core import ID, TimePeriod
from esphome.core import CORE, TimePeriod
from esphome.coroutine import FakeAwaitable
from esphome.cpp_generator import MockObjClass
from esphome.cpp_generator import MockObj, MockObjClass, VariableDeclarationExpression
from .defines import (
CONF_DEFAULT,
@ -16,83 +16,26 @@ from .defines import (
OBJ_FLAGS,
PARTS,
STATES,
ConstantLiteral,
LValidator,
join_enums,
literal,
)
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 .lvcode import add_group, add_line_marks, lv, lv_add, lv_assign, lv_expr, lv_obj
from .schemas import ALL_STYLES, STYLE_REMAP
from .types import WIDGET_TYPES, LvType, WidgetType, lv_obj_t, lv_obj_t_ptr
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 []
@ -135,17 +78,20 @@ class Widget:
return f"{self.var}->obj"
return self.var
def add_state(self, *args):
return lv_obj.add_state(self.obj, *args)
def add_state(self, state):
return lv_obj.add_state(self.obj, literal(state))
def clear_state(self, *args):
return lv_obj.clear_state(self.obj, *args)
def clear_state(self, state):
return lv_obj.clear_state(self.obj, literal(state))
def add_flag(self, *args):
return lv_obj.add_flag(self.obj, *args)
def has_state(self, state):
return lv_expr.obj_get_state(self.obj) & literal(state) != 0
def clear_flag(self, *args):
return lv_obj.clear_flag(self.obj, *args)
def add_flag(self, flag):
return lv_obj.add_flag(self.obj, literal(flag))
def clear_flag(self, flag):
return lv_obj.clear_flag(self.obj, literal(flag))
def set_property(self, prop, value, animated: bool = None, ltype=None):
if isinstance(value, dict):
@ -184,6 +130,16 @@ class Widget:
def __str__(self):
return f"({self.var}, {self.type})"
def get_args(self):
if isinstance(self.type.w_type, LvType):
return self.type.w_type.args
return [(lv_obj_t_ptr, "obj")]
def get_value(self):
if isinstance(self.type.w_type, LvType):
return self.type.w_type.value(self)
return self.obj
# Map of widgets to their config, used for trigger generation
widget_map: dict[Any, Widget] = {}
@ -205,7 +161,8 @@ def get_widget_generator(wid):
yield
async def get_widget(wid: ID) -> Widget:
async def get_widget(config: dict, id: str = CONF_ID) -> Widget:
wid = config[id]
if obj := widget_map.get(wid):
return obj
return await FakeAwaitable(get_widget_generator(wid))
@ -263,7 +220,10 @@ 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)
prop_r = STYLE_REMAP.get(prop, prop)
w.set_style(prop_r, value, lv_state)
if group := add_group(config.get(CONF_GROUP)):
lv.group_add_obj(group, w.obj)
flag_clr = set()
flag_set = set()
props = parts[CONF_MAIN][CONF_DEFAULT]
@ -291,14 +251,14 @@ 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_)
state = ConstantLiteral(f"LV_STATE_{key.upper}")
state = f"LV_STATE_{key.upper}"
lv.cond_if(lamb)
w.add_state(state)
lv.cond_else()
@ -338,10 +298,19 @@ async def widget_to_code(w_cnfig, w_type, parent):
var = cg.new_Pvariable(wid)
lv_add(var.set_obj(creator))
else:
var = cg.Pvariable(wid, cg.nullptr, type_=lv_obj_t)
var = MockObj(wid, "->")
decl = VariableDeclarationExpression(lv_obj_t, "*", wid)
CORE.add_global(decl)
CORE.register_variable(wid, var)
lv_assign(var, creator)
widget = Widget.create(wid, var, spec, w_cnfig, parent)
await set_obj_properties(widget, w_cnfig)
await add_widgets(widget, w_cnfig)
await spec.to_code(widget, w_cnfig)
lv_scr_act_spec = LvScrActType()
lv_scr_act = Widget.create(
None, ConstantLiteral("lv_scr_act()"), lv_scr_act_spec, {}, parent=None
)

View file

@ -8,6 +8,7 @@ static const char *const TAG = "matrix_keypad";
void MatrixKeypad::setup() {
for (auto *pin : this->rows_) {
pin->setup();
if (!has_diodes_) {
pin->pin_mode(gpio::FLAG_INPUT);
} else {
@ -15,6 +16,7 @@ void MatrixKeypad::setup() {
}
}
for (auto *pin : this->columns_) {
pin->setup();
if (has_pulldowns_) {
pin->pin_mode(gpio::FLAG_INPUT);
} else {

View file

@ -148,7 +148,7 @@ WakeWordModel::WakeWordModel(const uint8_t *model_start, float probability_cutof
};
bool WakeWordModel::determine_detected() {
int32_t sum = 0;
uint32_t sum = 0;
for (auto &prob : this->recent_streaming_probabilities_) {
sum += prob;
}
@ -175,12 +175,14 @@ VADModel::VADModel(const uint8_t *model_start, float probability_cutoff, size_t
};
bool VADModel::determine_detected() {
uint8_t max = 0;
uint32_t sum = 0;
for (auto &prob : this->recent_streaming_probabilities_) {
max = std::max(prob, max);
sum += prob;
}
return max > this->probability_cutoff_;
float sliding_window_average = static_cast<float>(sum) / static_cast<float>(255 * this->sliding_window_size_);
return sliding_window_average > this->probability_cutoff_;
}
} // namespace micro_wake_word

View file

@ -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;
}

View file

@ -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"

View file

@ -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_; }

View file

@ -17,6 +17,7 @@ from esphome.const import (
CONF_ICON,
CONF_ID,
CONF_IGNORE_OUT_OF_RANGE,
CONF_MULTIPLE,
CONF_ON_RAW_VALUE,
CONF_ON_VALUE,
CONF_ON_VALUE_RANGE,
@ -249,6 +250,7 @@ CalibratePolynomialFilter = sensor_ns.class_("CalibratePolynomialFilter", Filter
SensorInRangeCondition = sensor_ns.class_("SensorInRangeCondition", Filter)
ClampFilter = sensor_ns.class_("ClampFilter", Filter)
RoundFilter = sensor_ns.class_("RoundFilter", Filter)
RoundMultipleFilter = sensor_ns.class_("RoundMultipleFilter", Filter)
validate_unit_of_measurement = cv.string_strict
validate_accuracy_decimals = cv.int_
@ -734,6 +736,23 @@ async def round_filter_to_code(config, filter_id):
)
@FILTER_REGISTRY.register(
"round_to_multiple_of",
RoundMultipleFilter,
cv.maybe_simple_value(
{
cv.Required(CONF_MULTIPLE): cv.positive_not_null_float,
},
key=CONF_MULTIPLE,
),
)
async def round_multiple_filter_to_code(config, filter_id):
return cg.new_Pvariable(
filter_id,
config[CONF_MULTIPLE],
)
async def build_filters(config):
return await cg.build_registry_list(FILTER_REGISTRY, config)

View file

@ -472,5 +472,13 @@ optional<float> RoundFilter::new_value(float value) {
return value;
}
RoundMultipleFilter::RoundMultipleFilter(float multiple) : multiple_(multiple) {}
optional<float> RoundMultipleFilter::new_value(float value) {
if (std::isfinite(value)) {
return value - remainderf(value, this->multiple_);
}
return value;
}
} // namespace sensor
} // namespace esphome

View file

@ -431,5 +431,14 @@ class RoundFilter : public Filter {
uint8_t precision_;
};
class RoundMultipleFilter : public Filter {
public:
explicit RoundMultipleFilter(float multiple);
optional<float> new_value(float value) override;
protected:
float multiple_;
};
} // namespace sensor
} // namespace esphome

View file

@ -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))

View file

@ -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);

View file

@ -6,6 +6,8 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include <vector>
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<display::DisplayPage *> pages_{};
};
} // namespace touchscreen

View file

@ -1,10 +1,11 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_FORCE_UPDATE,
CONF_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
@ -23,8 +24,12 @@ UpdateEntity = update_ns.class_("UpdateEntity", cg.EntityBase)
UpdateInfo = update_ns.struct("UpdateInfo")
PerformAction = update_ns.class_("PerformAction", automation.Action)
IsAvailableCondition = update_ns.class_("IsAvailableCondition", automation.Condition)
PerformAction = update_ns.class_(
"PerformAction", automation.Action, cg.Parented.template(UpdateEntity)
)
IsAvailableCondition = update_ns.class_(
"IsAvailableCondition", automation.Condition, cg.Parented.template(UpdateEntity)
)
DEVICE_CLASSES = [
DEVICE_CLASS_EMPTY,
@ -92,24 +97,37 @@ async def to_code(config):
cg.add_global(update_ns.using)
UPDATE_AUTOMATION_SCHEMA = cv.Schema(
@automation.register_action(
"update.perform",
PerformAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(UpdateEntity),
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.templatable(cv.boolean),
}
),
)
@automation.register_action("update.perform", PerformAction, UPDATE_AUTOMATION_SCHEMA)
async def update_perform_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, paren, paren)
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
force = await cg.templatable(config[CONF_FORCE_UPDATE], args, cg.bool_)
cg.add(var.set_force(force))
return var
@automation.register_condition(
"update.is_available", IsAvailableCondition, UPDATE_AUTOMATION_SCHEMA
"update.is_available",
IsAvailableCondition,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(UpdateEntity),
}
),
)
async def update_is_available_condition_to_code(
config, condition_id, template_arg, args
):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, paren, paren)
var = cg.new_Pvariable(condition_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var

View file

@ -0,0 +1,23 @@
#pragma once
#include "update_entity.h"
#include "esphome/core/automation.h"
namespace esphome {
namespace update {
template<typename... Ts> class PerformAction : public Action<Ts...>, public Parented<UpdateEntity> {
TEMPLATABLE_VALUE(bool, force)
public:
void play(Ts... x) override { this->parent_->perform(this->force_.value(x...)); }
};
template<typename... Ts> class IsAvailableCondition : public Condition<Ts...>, public Parented<UpdateEntity> {
public:
bool check(Ts... x) override { return this->parent_->state == UPDATE_STATE_AVAILABLE; }
};
} // namespace update
} // namespace esphome

View file

@ -32,7 +32,9 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
void publish_state();
virtual void perform() = 0;
void perform() { this->perform(false); }
virtual void perform(bool force) = 0;
const UpdateInfo &update_info = update_info_;
const UpdateState &state = state_;

View file

@ -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"

View file

@ -0,0 +1 @@
CODEOWNERS = ["@oarcher"]

View file

@ -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

View file

@ -5,7 +5,6 @@
#include <cstdint>
namespace esphome {
namespace http_request {
namespace watchdog {
class WatchdogManager {
@ -22,5 +21,4 @@ class WatchdogManager {
};
} // namespace watchdog
} // namespace http_request
} // namespace esphome

View file

@ -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"]

View file

@ -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()

View file

@ -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,
)

View file

@ -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,

View file

@ -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__)

View file

@ -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,
@ -72,15 +72,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,
)
@ -92,7 +92,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=consider-using-f-string
VARIABLE_PROG = re.compile(
"\\$([{0}]+|\\{{[{0}]*\\}})".format(VALID_SUBSTITUTIONS_CHARACTERS)
f"\\$([{VALID_SUBSTITUTIONS_CHARACTERS}]+|\\{{[{VALID_SUBSTITUTIONS_CHARACTERS}]*\\}})"
)
# pylint: disable=invalid-name
@ -465,6 +465,7 @@ zero_to_one_float = float_range(min=0, max=1)
negative_one_to_one_float = float_range(min=-1, max=1)
positive_int = int_range(min=0)
positive_not_null_int = int_range(min=0, min_included=False)
positive_not_null_float = float_range(min=0, min_included=False)
def validate_id_name(value):
@ -1691,9 +1692,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()
@ -2188,3 +2189,13 @@ SOURCE_SCHEMA = Any(
}
),
)
def rename_key(old_key, new_key):
def validator(config: dict) -> dict:
config = config.copy()
if old_key in config:
config[new_key] = config.pop(old_key)
return config
return validator

View file

@ -39,8 +39,10 @@ CONF_ACCELERATION_Y = "acceleration_y"
CONF_ACCELERATION_Z = "acceleration_z"
CONF_ACCURACY = "accuracy"
CONF_ACCURACY_DECIMALS = "accuracy_decimals"
CONF_ACTION = "action"
CONF_ACTION_ID = "action_id"
CONF_ACTION_STATE_TOPIC = "action_state_topic"
CONF_ACTIONS = "actions"
CONF_ACTIVE = "active"
CONF_ACTIVE_POWER = "active_power"
CONF_ACTUAL_GAIN = "actual_gain"
@ -503,6 +505,7 @@ CONF_MOTION = "motion"
CONF_MOVEMENT_COUNTER = "movement_counter"
CONF_MQTT = "mqtt"
CONF_MQTT_ID = "mqtt_id"
CONF_MULTIPLE = "multiple"
CONF_MULTIPLEXER = "multiplexer"
CONF_MULTIPLY = "multiply"
CONF_NAME = "name"
@ -541,6 +544,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"
@ -1034,11 +1038,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"

View file

@ -7,11 +7,11 @@ 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,
@ -23,11 +23,14 @@ from esphome.const import (
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
@ -334,7 +337,7 @@ class ID:
else:
self.is_manual = is_manual
self.is_declaration = is_declaration
self.type: Optional["MockObjClass"] = type
self.type: Optional[MockObjClass] = type
def resolve(self, registered_ids):
from esphome.config_validation import RESERVED_IDS
@ -498,7 +501,7 @@ class EsphomeCore:
# The relative path to where all build files are stored
self.build_path: Optional[str] = None
# The validated configuration, this is None until the config has been validated
self.config: Optional["ConfigType"] = None
self.config: Optional[ConfigType] = None
# The pending tasks in the task queue (mostly for C++ generation)
# This is a priority queue (with heapq)
# Each item is a tuple of form: (-priority, unique number, task)
@ -506,17 +509,17 @@ class EsphomeCore:
# Task counter for pending tasks
self.task_counter = 0
# The variable cache, for each ID this holds a MockObj of the variable obj
self.variables: dict[str, "MockObj"] = {}
self.variables: dict[str, MockObj] = {}
# A list of statements that go in the main setup() block
self.main_statements: list["Statement"] = []
self.main_statements: list[Statement] = []
# A list of statements to insert in the global block (includes and global variables)
self.global_statements: list["Statement"] = []
self.global_statements: list[Statement] = []
# A set of platformio libraries to add to the project
self.libraries: list[Library] = []
# A set of build flags to set in the platformio project
self.build_flags: set[str] = set()
# A set of defines to set for the compile process in esphome/core/defines.h
self.defines: set["Define"] = set()
self.defines: set[Define] = set()
# A map of all platformio options to apply
self.platformio_options: dict[str, Union[str, list[str]]] = {}
# A set of strings of names of loaded integrations, used to find namespace ID conflicts

View file

@ -3,9 +3,10 @@ import multiprocessing
import os
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.zephyr import zephyr_add_prj_conf
import esphome.config_validation as cv
from esphome.const import (
CONF_ARDUINO_VERSION,
CONF_AREA,
@ -16,11 +17,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,13 +35,12 @@ 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
from esphome.helpers import copy_file_if_changed, get_str_env, walk_files
from esphome.components.zephyr import zephyr_add_prj_conf
_LOGGER = logging.getLogger(__name__)

View file

@ -39,8 +39,12 @@
#define USE_LOCK
#define USE_LOGGER
#define USE_LVGL
#define USE_LVGL_BINARY_SENSOR
#define USE_LVGL_FONT
#define USE_LVGL_IMAGE
#define USE_LVGL_KEY_LISTENER
#define USE_LVGL_TOUCHSCREEN
#define USE_LVGL_ROTARY_ENCODER
#define USE_MDNS
#define USE_MEDIA_PLAYER
#define USE_MQTT

View file

@ -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):

View file

@ -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__)

View file

@ -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 (

View file

@ -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__)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,7 +1,7 @@
import logging
import os
import tempfile
from pathlib import Path
import tempfile
_LOGGER = logging.getLogger(__name__)

View file

@ -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

View file

@ -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"]

View file

@ -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):

View file

@ -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

View file

@ -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"):

View file

@ -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__)

Some files were not shown because too many files have changed in this diff Show more