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

This commit is contained in:
Tomasz Duda 2024-08-07 18:34:52 +02:00
commit 42176f5f60
296 changed files with 14324 additions and 4798 deletions

View file

@ -46,7 +46,7 @@ runs:
- name: Build and push to ghcr by digest - name: Build and push to ghcr by digest
id: build-ghcr id: build-ghcr
uses: docker/build-push-action@v6.4.1 uses: docker/build-push-action@v6.5.0
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
@ -69,7 +69,7 @@ runs:
- name: Build and push to dockerhub by digest - name: Build and push to dockerhub by digest
id: build-dockerhub id: build-dockerhub
uses: docker/build-push-action@v6.4.1 uses: docker/build-push-action@v6.5.0
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile

View file

@ -13,6 +13,13 @@ updates:
schedule: schedule:
interval: daily interval: daily
open-pull-requests-limit: 10 open-pull-requests-limit: 10
groups:
docker-actions:
applies-to: version-updates
patterns:
- "docker/setup-qemu-action"
- "docker/login-action"
- "docker/setup-buildx-action"
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: "/.github/actions/build-image" directory: "/.github/actions/build-image"
schedule: schedule:

View file

@ -46,9 +46,9 @@ jobs:
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.4.0 uses: docker/setup-buildx-action@v3.6.1
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.1.0 uses: docker/setup-qemu-action@v3.2.0
- name: Set TAG - name: Set TAG
run: | run: |

View file

@ -468,6 +468,8 @@ jobs:
- name: Compile config - name: Compile config
run: | run: |
. venv/bin/activate . venv/bin/activate
mkdir build_cache
export PLATFORMIO_BUILD_CACHE_DIR=$PWD/build_cache
for component in ${{ matrix.components }}; do for component in ${{ matrix.components }}; do
./script/test_build_components -e compile -c $component ./script/test_build_components -e compile -c $component
done done

View file

@ -90,18 +90,18 @@ jobs:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.4.0 uses: docker/setup-buildx-action@v3.6.1
- name: Set up QEMU - name: Set up QEMU
if: matrix.platform != 'linux/amd64' if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.1.0 uses: docker/setup-qemu-action@v3.2.0
- name: Log in to docker hub - name: Log in to docker hub
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@ -184,17 +184,17 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.4.0 uses: docker/setup-buildx-action@v3.6.1
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
if: matrix.registry == 'ghcr' if: matrix.registry == 'ghcr'
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}

2
.gitignore vendored
View file

@ -138,3 +138,5 @@ sdkconfig.*
.tests/ .tests/
/components /components
/managed_components

View file

@ -2,6 +2,15 @@
# See https://pre-commit.com for more information # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.5.4
hooks:
# Run the linter.
- id: ruff
args: [--fix]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/psf/black-pre-commit-mirror - repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2 rev: 24.4.2
hooks: hooks:

View file

@ -37,6 +37,7 @@ esphome/components/am43/sensor/* @buxtronix
esphome/components/analog_threshold/* @ianchi esphome/components/analog_threshold/* @ianchi
esphome/components/animation/* @syndlex esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix esphome/components/anova/* @buxtronix
esphome/components/apds9306/* @aodrenah
esphome/components/api/* @OttoWinter esphome/components/api/* @OttoWinter
esphome/components/as5600/* @ammmze esphome/components/as5600/* @ammmze
esphome/components/as5600/sensor/* @ammmze esphome/components/as5600/sensor/* @ammmze
@ -216,6 +217,8 @@ esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
esphome/components/ltr390/* @latonita @sjtrny esphome/components/ltr390/* @latonita @sjtrny
esphome/components/ltr_als_ps/* @latonita esphome/components/ltr_als_ps/* @latonita
esphome/components/lvgl/* @clydebarrow
esphome/components/m5stack_8angle/* @rnauber
esphome/components/matrix_keypad/* @ssieb esphome/components/matrix_keypad/* @ssieb
esphome/components/max31865/* @DAVe3283 esphome/components/max31865/* @DAVe3283
esphome/components/max44009/* @berfenger esphome/components/max44009/* @berfenger
@ -273,6 +276,7 @@ esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra esphome/components/noblex/* @AGalfra
esphome/components/number/* @esphome/core esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb esphome/components/one_wire/* @ssieb
esphome/components/online_image/* @guillempages
esphome/components/ota/* @esphome/core esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931 esphome/components/pca6416a/* @Mat931
@ -424,6 +428,7 @@ esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/waveshare_epaper/* @clydebarrow esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra esphome/components/web_server_idf/* @dentra

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# If /cache is mounted, use that as PIO's coredir # If /cache is mounted, use that as PIO's coredir
# otherwise use path in /config (so that PIO packages aren't downloaded on each compile) # otherwise use path in /config (so that PIO packages aren't downloaded on each compile)

View file

@ -1,13 +1,13 @@
# PYTHON_ARGCOMPLETE_OK # PYTHON_ARGCOMPLETE_OK
import argparse import argparse
import asyncio
from datetime import datetime
import functools import functools
import logging import logging
import os import os
import re import re
import sys import sys
import time import time
import asyncio
from datetime import datetime
import argcomplete import argcomplete
@ -34,27 +34,28 @@ from esphome.const import (
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_NRF52,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
SECRETS_FILES, SECRETS_FILES,
PLATFORM_NRF52,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent, is_ip_address from esphome.helpers import indent, is_ip_address
from esphome.log import Fore, color, setup_log
from esphome.util import ( from esphome.util import (
get_serial_ports,
list_yaml_files,
run_external_command, run_external_command,
run_external_process, run_external_process,
safe_print, safe_print,
list_yaml_files,
get_serial_ports,
) )
from esphome.log import color, setup_log, Fore
from .zephyr_tools import ( from .zephyr_tools import (
logger_scan, is_mac_address,
logger_connect, logger_connect,
logger_scan,
smpmgr_scan, smpmgr_scan,
smpmgr_upload, smpmgr_upload,
is_mac_address,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -167,6 +168,7 @@ def get_port_type(port):
def run_miniterm(config, port): def run_miniterm(config, port):
import serial import serial
from esphome import platformio_api from esphome import platformio_api
if CONF_LOGGER not in config: if CONF_LOGGER not in config:
@ -663,9 +665,10 @@ def command_update_all(args):
def command_idedata(args, config): def command_idedata(args, config):
from esphome import platformio_api
import json import json
from esphome import platformio_api
logging.disable(logging.INFO) logging.disable(logging.INFO)
logging.disable(logging.WARNING) logging.disable(logging.WARNING)
@ -814,7 +817,14 @@ def parse_args(argv):
) )
parser = argparse.ArgumentParser( 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) mqtt_options = argparse.ArgumentParser(add_help=False)
@ -1015,67 +1025,6 @@ def parse_args(argv):
# a deprecation warning). # a deprecation warning).
arguments = argv[1:] 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) argcomplete.autocomplete(parser)
return parser.parse_args(arguments) return parser.parse_args(arguments)
@ -1090,20 +1039,6 @@ def run_esphome(argv):
# Show timestamp for dashboard access logs # Show timestamp for dashboard access logs
args.command == "dashboard", args.command == "dashboard",
) )
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
_LOGGER.warning(
"Calling ESPHome with the configuration before the command is deprecated "
"and will be removed in the future. "
)
_LOGGER.warning("Please instead use:")
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion))
if sys.version_info < (3, 8, 0):
_LOGGER.error(
"You're running ESPHome with Python <3.8. ESPHome is no longer compatible "
"with this Python version. Please reinstall ESPHome with Python 3.8+"
)
return 1
if args.command in PRE_CONFIG_ACTIONS: if args.command in PRE_CONFIG_ACTIONS:
try: try:

View file

@ -7,10 +7,10 @@ from esphome.const import (
CONF_ELSE, CONF_ELSE,
CONF_ID, CONF_ID,
CONF_THEN, CONF_THEN,
CONF_TIME,
CONF_TIMEOUT, CONF_TIMEOUT,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE_ID, CONF_TYPE_ID,
CONF_TIME,
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
) )
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor 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). # want to break suddenly due to a rename (this file will get backports for features).
# pylint: disable=unused-import # pylint: disable=unused-import
from esphome.cpp_generator import ( # noqa from esphome.cpp_generator import ( # noqa: F401
ArrayInitializer,
Expression, Expression,
LineComment,
MockObj,
MockObjClass,
Pvariable,
RawExpression, RawExpression,
RawStatement, RawStatement,
TemplateArguments,
StructInitializer,
ArrayInitializer,
safe_exp,
Statement, Statement,
LineComment, StructInitializer,
progmem_array, TemplateArguments,
static_const_array,
statement,
variable,
with_local_variable,
new_variable,
Pvariable,
new_Pvariable,
add, add,
add_global,
add_library,
add_build_flag, add_build_flag,
add_define, add_define,
add_global,
add_library,
add_platformio_option, add_platformio_option,
get_variable, get_variable,
get_variable_with_full_id, get_variable_with_full_id,
process_lambda,
is_template, is_template,
new_Pvariable,
new_variable,
process_lambda,
progmem_array,
safe_exp,
statement,
static_const_array,
templatable, templatable,
MockObj, variable,
MockObjClass, with_local_variable,
) )
from esphome.cpp_helpers import ( # noqa from esphome.cpp_helpers import ( # noqa: F401
gpio_pin_expression,
register_component,
build_registry_entry, build_registry_entry,
build_registry_list, build_registry_list,
extract_registry_entry_config, extract_registry_entry_config,
register_parented, gpio_pin_expression,
past_safe_mode, past_safe_mode,
register_component,
register_parented,
) )
from esphome.cpp_types import ( # noqa from esphome.cpp_types import ( # noqa: F401
global_ns, NAN,
void, App,
nullptr, Application,
float_, Component,
double, ComponentPtr,
Controller,
EntityBase,
EntityCategory,
ESPTime,
GPIOPin,
InternalGPIOPin,
JsonObject,
JsonObjectConst,
Parented,
PollingComponent,
arduino_json_ns,
bool_, bool_,
const_char_ptr,
double,
esphome_ns,
float_,
global_ns,
gpio_Flags,
int16,
int32,
int64,
int_, int_,
nullptr,
optional,
size_t,
std_ns, std_ns,
std_shared_ptr, std_shared_ptr,
std_string, std_string,
@ -66,28 +89,5 @@ from esphome.cpp_types import ( # noqa
uint16, uint16,
uint32, uint32,
uint64, uint64,
int16, void,
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,
) )

View file

@ -1,5 +1,5 @@
import esphome.config_validation as cv import esphome.config_validation as cv
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( CONFIG_SCHEMA = cv.invalid(
"The ade7953 sensor component has been renamed to ade7953_i2c." "The ade7953 sensor component has been renamed to ade7953_i2c."
) )

View file

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

View file

@ -1,16 +1,17 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import web_server
from esphome import automation from esphome import automation
from esphome.automation import maybe_simple_id from esphome.automation import maybe_simple_id
from esphome.core import CORE, coroutine_with_priority import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_CODE,
CONF_ID, CONF_ID,
CONF_MQTT_ID,
CONF_ON_STATE, CONF_ON_STATE,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_CODE,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER_ID,
) )
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@grahambrown11", "@hwstar"] CODEOWNERS = ["@grahambrown11", "@hwstar"]
@ -77,67 +78,72 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
"AlarmControlPanelCondition", automation.Condition "AlarmControlPanelCondition", automation.Condition
) )
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( ALARM_CONTROL_PANEL_SCHEMA = (
web_server.WEBSERVER_SORTING_SCHEMA cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
).extend( .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
{ .extend(
cv.GenerateID(): cv.declare_id(AlarmControlPanel), {
cv.Optional(CONF_ON_STATE): automation.validate_automation( cv.GenerateID(): cv.declare_id(AlarmControlPanel),
{ cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), mqtt.MQTTAlarmControlPanelComponent
} ),
), cv.Optional(CONF_ON_STATE): automation.validate_automation(
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger), }
} ),
), cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
cv.Optional(CONF_ON_ARMING): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger), }
} ),
), cv.Optional(CONF_ON_ARMING): automation.validate_automation(
cv.Optional(CONF_ON_PENDING): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger), }
} ),
), cv.Optional(CONF_ON_PENDING): automation.validate_automation(
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger), }
} ),
), cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger), }
} ),
), cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger), }
} ),
), cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
cv.Optional(CONF_ON_DISARMED): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger), }
} ),
), cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
cv.Optional(CONF_ON_CLEARED): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), }
} ),
), cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
cv.Optional(CONF_ON_CHIME): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger), }
} ),
), cv.Optional(CONF_ON_CHIME): automation.validate_automation(
cv.Optional(CONF_ON_READY): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger), }
} ),
), cv.Optional(CONF_ON_READY): automation.validate_automation(
} {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger),
}
),
}
)
) )
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id( ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
@ -192,6 +198,9 @@ async def setup_alarm_control_panel_core_(var, config):
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id) web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config) web_server.add_entity_to_sorting_list(web_server_, var, config)
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_alarm_control_panel(var, config): async def register_alarm_control_panel(var, config):

View file

@ -0,0 +1,4 @@
# Based on this datasheet:
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
CODEOWNERS = ["@aodrenah"]

View file

@ -0,0 +1,151 @@
// Based on this datasheet:
// https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
#include "apds9306.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace apds9306 {
static const char *const TAG = "apds9306";
enum { // APDS9306 registers
APDS9306_MAIN_CTRL = 0x00,
APDS9306_ALS_MEAS_RATE = 0x04,
APDS9306_ALS_GAIN = 0x05,
APDS9306_PART_ID = 0x06,
APDS9306_MAIN_STATUS = 0x07,
APDS9306_CLEAR_DATA_0 = 0x0A, // LSB
APDS9306_CLEAR_DATA_1 = 0x0B,
APDS9306_CLEAR_DATA_2 = 0x0C, // MSB
APDS9306_ALS_DATA_0 = 0x0D, // LSB
APDS9306_ALS_DATA_1 = 0x0E,
APDS9306_ALS_DATA_2 = 0x0F, // MSB
APDS9306_INT_CFG = 0x19,
APDS9306_INT_PERSISTENCE = 0x1A,
APDS9306_ALS_THRES_UP_0 = 0x21, // LSB
APDS9306_ALS_THRES_UP_1 = 0x22,
APDS9306_ALS_THRES_UP_2 = 0x23, // MSB
APDS9306_ALS_THRES_LOW_0 = 0x24, // LSB
APDS9306_ALS_THRES_LOW_1 = 0x25,
APDS9306_ALS_THRES_LOW_2 = 0x26, // MSB
APDS9306_ALS_THRES_VAR = 0x27
};
#define APDS9306_ERROR_CHECK(func, error) \
if (!(func)) { \
ESP_LOGE(TAG, error); \
this->mark_failed(); \
return; \
}
#define APDS9306_WARNING_CHECK(func, warning) \
if (!(func)) { \
ESP_LOGW(TAG, warning); \
this->status_set_warning(); \
return; \
}
#define APDS9306_WRITE_BYTE(reg, value) \
ESP_LOGV(TAG, "Writing 0x%02x to 0x%02x", value, reg); \
if (!this->write_byte(reg, value)) { \
ESP_LOGE(TAG, "Failed writing 0x%02x to 0x%02x", value, reg); \
this->mark_failed(); \
return; \
}
void APDS9306::setup() {
ESP_LOGCONFIG(TAG, "Setting up APDS9306...");
uint8_t id;
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (id != 0xB1 && id != 0xB3) { // 0xB1 for APDS9306 0xB3 for APDS9306-065
this->error_code_ = WRONG_ID;
this->mark_failed();
return;
}
// ALS resolution and measurement, see datasheet or init.py for options
uint8_t als_meas_rate = ((this->bit_width_ & 0x07) << 4) | (this->measurement_rate_ & 0x07);
APDS9306_WRITE_BYTE(APDS9306_ALS_MEAS_RATE, als_meas_rate);
// ALS gain, see datasheet or init.py for options
uint8_t als_gain = (this->gain_ & 0x07);
APDS9306_WRITE_BYTE(APDS9306_ALS_GAIN, als_gain);
// Set to standby mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00);
// Check for data, clear main status
uint8_t status;
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
// Set to active mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
ESP_LOGCONFIG(TAG, "APDS9306 setup complete");
}
void APDS9306::dump_config() {
LOG_SENSOR("", "APDS9306", this);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with APDS9306 failed!");
break;
case WRONG_ID:
ESP_LOGE(TAG, "APDS9306 has invalid id!");
break;
default:
ESP_LOGE(TAG, "Setting up APDS9306 registers failed!");
break;
}
}
ESP_LOGCONFIG(TAG, " Gain: %u", AMBIENT_LIGHT_GAIN_VALUES[this->gain_]);
ESP_LOGCONFIG(TAG, " Measurement rate: %u", MEASUREMENT_RATE_VALUES[this->measurement_rate_]);
ESP_LOGCONFIG(TAG, " Measurement Resolution/Bit width: %d", MEASUREMENT_BIT_WIDTH_VALUES[this->bit_width_]);
LOG_UPDATE_INTERVAL(this);
}
void APDS9306::update() {
// Check for new data
uint8_t status;
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
this->status_clear_warning();
if (!(status &= 0b00001000)) { // No new data
return;
}
// Set to standby mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00);
// Clear MAIN STATUS
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
uint8_t als_data[3];
APDS9306_WARNING_CHECK(this->read_bytes(APDS9306_ALS_DATA_0, als_data, 3), "Reading ALS data has failed.");
// Set to active mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
uint32_t light_level = 0x00 | encode_uint24(als_data[2], als_data[1], als_data[0]);
float lux = ((float) light_level / AMBIENT_LIGHT_GAIN_VALUES[this->gain_]) *
(100.0f / MEASUREMENT_RATE_VALUES[this->measurement_rate_]);
ESP_LOGD(TAG, "Got illuminance=%.1flx from", lux);
this->publish_state(lux);
}
} // namespace apds9306
} // namespace esphome

View file

@ -0,0 +1,66 @@
// Based on this datasheet:
// https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace apds9306 {
enum MeasurementBitWidth : uint8_t {
MEASUREMENT_BIT_WIDTH_20 = 0,
MEASUREMENT_BIT_WIDTH_19 = 1,
MEASUREMENT_BIT_WIDTH_18 = 2,
MEASUREMENT_BIT_WIDTH_17 = 3,
MEASUREMENT_BIT_WIDTH_16 = 4,
MEASUREMENT_BIT_WIDTH_13 = 5,
};
static const uint8_t MEASUREMENT_BIT_WIDTH_VALUES[] = {20, 19, 18, 17, 16, 13};
enum MeasurementRate : uint8_t {
MEASUREMENT_RATE_25 = 0,
MEASUREMENT_RATE_50 = 1,
MEASUREMENT_RATE_100 = 2,
MEASUREMENT_RATE_200 = 3,
MEASUREMENT_RATE_500 = 4,
MEASUREMENT_RATE_1000 = 5,
MEASUREMENT_RATE_2000 = 6,
};
static const uint16_t MEASUREMENT_RATE_VALUES[] = {25, 50, 100, 200, 500, 1000, 2000};
enum AmbientLightGain : uint8_t {
AMBIENT_LIGHT_GAIN_1 = 0,
AMBIENT_LIGHT_GAIN_3 = 1,
AMBIENT_LIGHT_GAIN_6 = 2,
AMBIENT_LIGHT_GAIN_9 = 3,
AMBIENT_LIGHT_GAIN_18 = 4,
};
static const uint8_t AMBIENT_LIGHT_GAIN_VALUES[] = {1, 3, 6, 9, 18};
class APDS9306 : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void dump_config() override;
void update() override;
void set_bit_width(MeasurementBitWidth bit_width) { this->bit_width_ = bit_width; }
void set_measurement_rate(MeasurementRate measurement_rate) { this->measurement_rate_ = measurement_rate; }
void set_ambient_light_gain(AmbientLightGain gain) { this->gain_ = gain; }
protected:
enum ErrorCode {
NONE = 0,
COMMUNICATION_FAILED,
WRONG_ID,
} error_code_{NONE};
MeasurementBitWidth bit_width_;
MeasurementRate measurement_rate_;
AmbientLightGain gain_;
};
} // namespace apds9306
} // namespace esphome

View file

@ -0,0 +1,95 @@
# Based on this datasheet:
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_GAIN,
DEVICE_CLASS_ILLUMINANCE,
ICON_LIGHTBULB,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
)
DEPENDENCIES = ["i2c"]
CONF_APDS9306_ID = "apds9306_id"
CONF_BIT_WIDTH = "bit_width"
CONF_MEASUREMENT_RATE = "measurement_rate"
apds9306_ns = cg.esphome_ns.namespace("apds9306")
APDS9306 = apds9306_ns.class_(
"APDS9306", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
MeasurementBitWidth = apds9306_ns.enum("MeasurementBitWidth")
MeasurementRate = apds9306_ns.enum("MeasurementRate")
AmbientLightGain = apds9306_ns.enum("AmbientLightGain")
MEASUREMENT_BIT_WIDTHS = {
20: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_20,
19: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_19,
18: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_18,
17: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_17,
16: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_16,
13: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_13,
}
MEASUREMENT_RATES = {
25: MeasurementRate.MEASUREMENT_RATE_25,
50: MeasurementRate.MEASUREMENT_RATE_50,
100: MeasurementRate.MEASUREMENT_RATE_100,
200: MeasurementRate.MEASUREMENT_RATE_200,
500: MeasurementRate.MEASUREMENT_RATE_500,
1000: MeasurementRate.MEASUREMENT_RATE_1000,
2000: MeasurementRate.MEASUREMENT_RATE_2000,
}
AMBIENT_LIGHT_GAINS = {
1: AmbientLightGain.AMBIENT_LIGHT_GAIN_1,
3: AmbientLightGain.AMBIENT_LIGHT_GAIN_3,
6: AmbientLightGain.AMBIENT_LIGHT_GAIN_6,
9: AmbientLightGain.AMBIENT_LIGHT_GAIN_9,
18: AmbientLightGain.AMBIENT_LIGHT_GAIN_18,
}
def _validate_measurement_rate(value):
value = cv.positive_time_period_milliseconds(value)
return cv.enum(MEASUREMENT_RATES, int=True)(value.total_milliseconds)
CONFIG_SCHEMA = (
sensor.sensor_schema(
APDS9306,
unit_of_measurement=UNIT_LUX,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_LIGHTBULB,
)
.extend(
{
cv.Optional(CONF_GAIN, default="1"): cv.enum(AMBIENT_LIGHT_GAINS, int=True),
cv.Optional(CONF_BIT_WIDTH, default="18"): cv.enum(
MEASUREMENT_BIT_WIDTHS, int=True
),
cv.Optional(
CONF_MEASUREMENT_RATE, default="100ms"
): _validate_measurement_rate,
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x52))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_bit_width(config[CONF_BIT_WIDTH]))
cg.add(var.set_measurement_rate(config[CONF_MEASUREMENT_RATE]))
cg.add(var.set_ambient_light_gain(config[CONF_GAIN]))

View file

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

View file

@ -1872,6 +1872,11 @@ message UpdateStateResponse {
string release_summary = 9; string release_summary = 9;
string release_url = 10; string release_url = 10;
} }
enum UpdateCommand {
UPDATE_COMMAND_NONE = 0;
UPDATE_COMMAND_UPDATE = 1;
UPDATE_COMMAND_CHECK = 2;
}
message UpdateCommandRequest { message UpdateCommandRequest {
option (id) = 118; option (id) = 118;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
@ -1879,5 +1884,5 @@ message UpdateCommandRequest {
option (no_delay) = true; option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
bool install = 2; UpdateCommand command = 2;
} }

View file

@ -1328,7 +1328,17 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
if (update == nullptr) if (update == nullptr)
return; return;
update->perform(); switch (msg.command) {
case enums::UPDATE_COMMAND_UPDATE:
update->perform();
break;
case enums::UPDATE_COMMAND_CHECK:
update->check();
break;
default:
ESP_LOGW(TAG, "Unknown update command: %d", msg.command);
break;
}
} }
#endif #endif

View file

@ -567,6 +567,20 @@ template<> const char *proto_enum_to_string<enums::ValveOperation>(enums::ValveO
} }
} }
#endif #endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::UpdateCommand>(enums::UpdateCommand value) {
switch (value) {
case enums::UPDATE_COMMAND_NONE:
return "UPDATE_COMMAND_NONE";
case enums::UPDATE_COMMAND_UPDATE:
return "UPDATE_COMMAND_UPDATE";
case enums::UPDATE_COMMAND_CHECK:
return "UPDATE_COMMAND_CHECK";
default:
return "UNKNOWN";
}
}
#endif
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 2: { case 2: {
@ -8596,7 +8610,7 @@ void UpdateStateResponse::dump_to(std::string &out) const {
bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 2: { case 2: {
this->install = value.as_bool(); this->command = value.as_enum<enums::UpdateCommand>();
return true; return true;
} }
default: default:
@ -8615,7 +8629,7 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
} }
void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const { void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key); buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->install); buffer.encode_enum<enums::UpdateCommand>(2, this->command);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void UpdateCommandRequest::dump_to(std::string &out) const { void UpdateCommandRequest::dump_to(std::string &out) const {
@ -8626,8 +8640,8 @@ void UpdateCommandRequest::dump_to(std::string &out) const {
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" install: "); out.append(" command: ");
out.append(YESNO(this->install)); out.append(proto_enum_to_string<enums::UpdateCommand>(this->command));
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
} }

View file

@ -227,6 +227,11 @@ enum ValveOperation : uint32_t {
VALVE_OPERATION_IS_OPENING = 1, VALVE_OPERATION_IS_OPENING = 1,
VALVE_OPERATION_IS_CLOSING = 2, VALVE_OPERATION_IS_CLOSING = 2,
}; };
enum UpdateCommand : uint32_t {
UPDATE_COMMAND_NONE = 0,
UPDATE_COMMAND_UPDATE = 1,
UPDATE_COMMAND_CHECK = 2,
};
} // namespace enums } // namespace enums
@ -2175,7 +2180,7 @@ class UpdateStateResponse : public ProtoMessage {
class UpdateCommandRequest : public ProtoMessage { class UpdateCommandRequest : public ProtoMessage {
public: public:
uint32_t key{0}; uint32_t key{0};
bool install{false}; enums::UpdateCommand command{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;

View file

@ -1,10 +1,8 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome import automation, core from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id from esphome.automation import Condition, maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DELAY, CONF_DELAY,
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
@ -16,6 +14,7 @@ from esphome.const import (
CONF_INVERTED, CONF_INVERTED,
CONF_MAX_LENGTH, CONF_MAX_LENGTH,
CONF_MIN_LENGTH, CONF_MIN_LENGTH,
CONF_MQTT_ID,
CONF_ON_CLICK, CONF_ON_CLICK,
CONF_ON_DOUBLE_CLICK, CONF_ON_DOUBLE_CLICK,
CONF_ON_MULTI_CLICK, CONF_ON_MULTI_CLICK,
@ -26,7 +25,6 @@ from esphome.const import (
CONF_STATE, CONF_STATE,
CONF_TIMING, CONF_TIMING,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER_ID,
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING, DEVICE_CLASS_BATTERY_CHARGING,
@ -59,6 +57,8 @@ from esphome.const import (
DEVICE_CLASS_WINDOW, DEVICE_CLASS_WINDOW,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome.util import Registry from esphome.util import Registry
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]

View file

@ -1,7 +1,8 @@
import esphome.codegen as cg from esphome import automation
import esphome.config_validation as cv
from esphome.automation import maybe_simple_id 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 ( from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_ID, CONF_ID,
@ -13,7 +14,6 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VALUE, CONF_VALUE,
) )
from esphome import automation
AUTO_LOAD = ["esp32_ble_client"] AUTO_LOAD = ["esp32_ble_client"]
CODEOWNERS = ["@buxtronix", "@clydebarrow"] CODEOWNERS = ["@buxtronix", "@clydebarrow"]

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import ble_client, esp32_ble_tracker, output 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 esphome.const import CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_SERVICE_UUID
from .. import ble_client_ns from .. import ble_client_ns

View file

@ -1,17 +1,18 @@
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, esp32_ble_tracker, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, ble_client, esp32_ble_tracker
from esphome.const import ( from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_LAMBDA, CONF_LAMBDA,
CONF_SERVICE_UUID,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE, CONF_TYPE,
CONF_SERVICE_UUID,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT, UNIT_DECIBEL_MILLIWATT,
) )
from esphome import automation
from .. import ble_client_ns from .. import ble_client_ns
DEPENDENCIES = ["ble_client"] DEPENDENCIES = ["ble_client"]

View file

@ -1,7 +1,8 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, switch
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import switch, ble_client
from esphome.const import ICON_BLUETOOTH from esphome.const import ICON_BLUETOOTH
from .. import ble_client_ns from .. import ble_client_ns
BLEClientSwitch = ble_client_ns.class_( BLEClientSwitch = ble_client_ns.class_(

View file

@ -1,13 +1,14 @@
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, esp32_ble_tracker, text_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import text_sensor, ble_client, esp32_ble_tracker
from esphome.const import ( from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_ID, CONF_ID,
CONF_TRIGGER_ID,
CONF_SERVICE_UUID, CONF_SERVICE_UUID,
CONF_TRIGGER_ID,
) )
from esphome import automation
from .. import ble_client_ns from .. import ble_client_ns
DEPENDENCIES = ["ble_client"] DEPENDENCIES = ["ble_client"]

View file

@ -1,13 +1,13 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor, esp32_ble_tracker from esphome.components import binary_sensor, esp32_ble_tracker
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_MAC_ADDRESS,
CONF_SERVICE_UUID,
CONF_IBEACON_MAJOR, CONF_IBEACON_MAJOR,
CONF_IBEACON_MINOR, CONF_IBEACON_MINOR,
CONF_IBEACON_UUID, CONF_IBEACON_UUID,
CONF_MAC_ADDRESS,
CONF_MIN_RSSI, CONF_MIN_RSSI,
CONF_SERVICE_UUID,
CONF_TIMEOUT, CONF_TIMEOUT,
) )

View file

@ -1,12 +1,12 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_ble_tracker, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, esp32_ble_tracker
from esphome.const import ( from esphome.const import (
CONF_IBEACON_MAJOR, CONF_IBEACON_MAJOR,
CONF_IBEACON_MINOR, CONF_IBEACON_MINOR,
CONF_IBEACON_UUID, CONF_IBEACON_UUID,
CONF_SERVICE_UUID,
CONF_MAC_ADDRESS, CONF_MAC_ADDRESS,
CONF_SERVICE_UUID,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT, UNIT_DECIBEL_MILLIWATT,

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_ble_tracker, text_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import text_sensor, esp32_ble_tracker
DEPENDENCIES = ["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 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 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"] AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
DEPENDENCIES = ["api", "esp32"] DEPENDENCIES = ["api", "esp32"]

View file

@ -2,6 +2,6 @@ import esphome.config_validation as cv
CODEOWNERS = ["@latonita"] CODEOWNERS = ["@latonita"]
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( CONFIG_SCHEMA = cv.invalid(
"The bmp3xx sensor component has been renamed to bmp3xx_i2c." "The bmp3xx sensor component has been renamed to bmp3xx_i2c."
) )

View file

@ -1,16 +1,16 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.automation import maybe_simple_id from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY, CONF_ENTITY_CATEGORY,
CONF_ICON, CONF_ICON,
CONF_ID, CONF_ID,
CONF_MQTT_ID,
CONF_ON_PRESS, CONF_ON_PRESS,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER_ID,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY, DEVICE_CLASS_IDENTIFY,
@ -18,8 +18,8 @@ from esphome.const import (
DEVICE_CLASS_UPDATE, DEVICE_CLASS_UPDATE,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True

View file

@ -1,8 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.cpp_helpers import setup_entity
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components import mqtt, web_server from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ACTION_STATE_TOPIC, CONF_ACTION_STATE_TOPIC,
CONF_AWAY, CONF_AWAY,
@ -21,6 +20,7 @@ from esphome.const import (
CONF_MODE, CONF_MODE,
CONF_MODE_COMMAND_TOPIC, CONF_MODE_COMMAND_TOPIC,
CONF_MODE_STATE_TOPIC, CONF_MODE_STATE_TOPIC,
CONF_MQTT_ID,
CONF_ON_CONTROL, CONF_ON_CONTROL,
CONF_ON_STATE, CONF_ON_STATE,
CONF_PRESET, CONF_PRESET,
@ -33,20 +33,20 @@ from esphome.const import (
CONF_TARGET_HUMIDITY_STATE_TOPIC, CONF_TARGET_HUMIDITY_STATE_TOPIC,
CONF_TARGET_TEMPERATURE, CONF_TARGET_TEMPERATURE,
CONF_TARGET_TEMPERATURE_COMMAND_TOPIC, CONF_TARGET_TEMPERATURE_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_STATE_TOPIC,
CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_HIGH,
CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC, CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC, CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC,
CONF_TARGET_TEMPERATURE_LOW, CONF_TARGET_TEMPERATURE_LOW,
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
CONF_TARGET_TEMPERATURE_STATE_TOPIC,
CONF_TEMPERATURE_STEP, CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VISUAL, CONF_VISUAL,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER_ID,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True

View file

@ -1,23 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.automation import maybe_simple_id, Condition from esphome.automation import Condition, maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID,
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
CONF_STATE, CONF_ID,
CONF_MQTT_ID,
CONF_ON_OPEN, CONF_ON_OPEN,
CONF_POSITION, CONF_POSITION,
CONF_POSITION_COMMAND_TOPIC, CONF_POSITION_COMMAND_TOPIC,
CONF_POSITION_STATE_TOPIC, CONF_POSITION_STATE_TOPIC,
CONF_STATE,
CONF_STOP,
CONF_TILT, CONF_TILT,
CONF_TILT_COMMAND_TOPIC, CONF_TILT_COMMAND_TOPIC,
CONF_TILT_STATE_TOPIC, CONF_TILT_STATE_TOPIC,
CONF_STOP,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_AWNING, DEVICE_CLASS_AWNING,
DEVICE_CLASS_BLIND, DEVICE_CLASS_BLIND,
DEVICE_CLASS_CURTAIN, DEVICE_CLASS_CURTAIN,

View file

@ -5,13 +5,17 @@ namespace cst226 {
void CST226Touchscreen::setup() { void CST226Touchscreen::setup() {
esph_log_config(TAG, "Setting up CST226 Touchscreen..."); esph_log_config(TAG, "Setting up CST226 Touchscreen...");
this->reset_pin_->setup(); if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(true); this->reset_pin_->setup();
delay(5); this->reset_pin_->digital_write(true);
this->reset_pin_->digital_write(false); delay(5);
delay(5); this->reset_pin_->digital_write(false);
this->reset_pin_->digital_write(true); delay(5);
this->set_timeout(30, [this] { this->continue_setup_(); }); this->reset_pin_->digital_write(true);
this->set_timeout(30, [this] { this->continue_setup_(); });
} else {
this->continue_setup_();
}
} }
void CST226Touchscreen::update_touches() { void CST226Touchscreen::update_touches() {

View file

@ -35,7 +35,7 @@ class CST226Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
void continue_setup_(); void continue_setup_();
InternalGPIOPin *interrupt_pin_{}; InternalGPIOPin *interrupt_pin_{};
GPIOPin *reset_pin_{NULL_PIN}; GPIOPin *reset_pin_{};
uint8_t chip_id_{}; uint8_t chip_id_{};
bool setup_complete_{}; bool setup_complete_{};
}; };

View file

@ -1,32 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.components import mqtt, web_server, time import esphome.codegen as cg
from esphome.components import mqtt, time, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DATE,
CONF_DATETIME,
CONF_DAY,
CONF_HOUR,
CONF_ID, CONF_ID,
CONF_MINUTE,
CONF_MONTH,
CONF_MQTT_ID,
CONF_ON_TIME, CONF_ON_TIME,
CONF_ON_VALUE, CONF_ON_VALUE,
CONF_SECOND,
CONF_TIME,
CONF_TIME_ID, CONF_TIME_ID,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE, CONF_TYPE,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER_ID,
CONF_DATE,
CONF_DATETIME,
CONF_TIME,
CONF_YEAR, CONF_YEAR,
CONF_MONTH,
CONF_DAY,
CONF_SECOND,
CONF_HOUR,
CONF_MINUTE,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@rfdarter", "@jesserockz"] CODEOWNERS = ["@rfdarter", "@jesserockz"]
DEPENDENCIES = ["time"] DEPENDENCIES = ["time"]

View file

@ -1,23 +1,26 @@
import re import re
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation, core from esphome import automation, core
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components.number import Number
from esphome.components.select import Select
from esphome.components.switch import Switch
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ACTIVE,
CONF_TYPE,
CONF_TRIGGER_ID,
CONF_ON_VALUE,
CONF_COMMAND, CONF_COMMAND,
CONF_CUSTOM, CONF_CUSTOM,
CONF_NUMBER,
CONF_FORMAT, CONF_FORMAT,
CONF_ID,
CONF_ITEMS,
CONF_MODE, CONF_MODE,
CONF_ACTIVE, CONF_NUMBER,
CONF_ON_VALUE,
CONF_TEXT,
CONF_TRIGGER_ID,
CONF_TYPE,
) )
from esphome.automation import maybe_simple_id
from esphome.components.select import Select
from esphome.components.number import Number
from esphome.components.switch import Switch
CODEOWNERS = ["@numo68"] CODEOWNERS = ["@numo68"]
@ -29,10 +32,8 @@ CONF_JOYSTICK = "joystick"
CONF_LABEL = "label" CONF_LABEL = "label"
CONF_MENU = "menu" CONF_MENU = "menu"
CONF_BACK = "back" CONF_BACK = "back"
CONF_TEXT = "text"
CONF_SELECT = "select" CONF_SELECT = "select"
CONF_SWITCH = "switch" CONF_SWITCH = "switch"
CONF_ITEMS = "items"
CONF_ON_TEXT = "on_text" CONF_ON_TEXT = "on_text"
CONF_OFF_TEXT = "off_text" CONF_OFF_TEXT = "off_text"
CONF_VALUE_LAMBDA = "value_lambda" CONF_VALUE_LAMBDA = "value_lambda"

View file

@ -2,6 +2,6 @@ import esphome.config_validation as cv
CODEOWNERS = ["@latonita"] CODEOWNERS = ["@latonita"]
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( CONFIG_SCHEMA = cv.invalid(
"The ens160 sensor component has been renamed to ens160_i2c." "The ens160 sensor component has been renamed to ens160_i2c."
) )

View file

@ -1,11 +1,12 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Union, Optional
from pathlib import Path
import logging import logging
import os 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 ( from esphome.const import (
CONF_ADVANCED, CONF_ADVANCED,
CONF_BOARD, CONF_BOARD,
@ -15,6 +16,7 @@ from esphome.const import (
CONF_IGNORE_EFUSE_MAC_CRC, CONF_IGNORE_EFUSE_MAC_CRC,
CONF_NAME, CONF_NAME,
CONF_PATH, CONF_PATH,
CONF_PLATFORM_VERSION,
CONF_PLATFORMIO_OPTIONS, CONF_PLATFORMIO_OPTIONS,
CONF_REF, CONF_REF,
CONF_REFRESH, CONF_REFRESH,
@ -32,13 +34,12 @@ from esphome.const import (
TYPE_GIT, TYPE_GIT,
TYPE_LOCAL, TYPE_LOCAL,
__version__, __version__,
CONF_PLATFORM_VERSION,
) )
from esphome.core import CORE, HexInt, TimePeriod from esphome.core import CORE, HexInt, TimePeriod
import esphome.config_validation as cv import esphome.final_validate as fv
import esphome.codegen as cg from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
from esphome import git
from .boards import BOARDS
from .const import ( # noqa from .const import ( # noqa
KEY_BOARD, KEY_BOARD,
KEY_COMPONENTS, KEY_COMPONENTS,
@ -54,12 +55,10 @@ from .const import ( # noqa
VARIANT_FRIENDLY, VARIANT_FRIENDLY,
VARIANTS, VARIANTS,
) )
from .boards import BOARDS
# force import gpio to register pin schema # force import gpio to register pin schema
from .gpio import esp32_pin_to_code # noqa from .gpio import esp32_pin_to_code # noqa
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["preferences"] 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 = { ESP32_BASE_PINS = {
"TX": 1, "TX": 1,

View file

@ -1,22 +1,22 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
import logging 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 ( from esphome.const import (
CONF_ID, CONF_ID,
CONF_IGNORE_PIN_VALIDATION_ERROR,
CONF_IGNORE_STRAPPING_WARNING,
CONF_INVERTED, CONF_INVERTED,
CONF_MODE, CONF_MODE,
CONF_NUMBER, CONF_NUMBER,
CONF_OPEN_DRAIN, CONF_OPEN_DRAIN,
CONF_OUTPUT, CONF_OUTPUT,
CONF_IGNORE_PIN_VALIDATION_ERROR,
CONF_IGNORE_STRAPPING_WARNING,
PLATFORM_ESP32, PLATFORM_ESP32,
) )
from esphome import pins
from esphome.core import CORE from esphome.core import CORE
import esphome.config_validation as cv
import esphome.codegen as cg
from . import boards from . import boards
from .const import ( from .const import (
@ -24,22 +24,21 @@ from .const import (
KEY_ESP32, KEY_ESP32,
KEY_VARIANT, KEY_VARIANT,
VARIANT_ESP32, VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32C2, VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C6, VARIANT_ESP32C6,
VARIANT_ESP32H2, VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
esp32_ns, esp32_ns,
) )
from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports 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_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_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_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) ESP32InternalGPIOPin = esp32_ns.class_("ESP32InternalGPIOPin", cg.InternalGPIOPin)

View file

@ -1,5 +1,6 @@
import logging import logging
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_INPUT, CONF_INPUT,
CONF_MODE, CONF_MODE,
@ -8,10 +9,8 @@ from esphome.const import (
CONF_PULLDOWN, CONF_PULLDOWN,
CONF_PULLUP, CONF_PULLUP,
) )
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
_ESP_SDIO_PINS = { _ESP_SDIO_PINS = {
6: "Flash Clock", 6: "Flash Clock",
7: "Flash Data 0", 7: "Flash Data 0",

View file

@ -1,10 +1,9 @@
import logging import logging
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
import esphome.config_validation as cv
_ESP32C2_STRAPPING_PINS = {8, 9} _ESP32C2_STRAPPING_PINS = {8, 9}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View file

@ -1,11 +1,7 @@
import logging import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
)
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
_ESP32C3_SPI_PSRAM_PINS = { _ESP32C3_SPI_PSRAM_PINS = {

View file

@ -1,8 +1,7 @@
import logging import logging
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
_ESP32C6_SPI_PSRAM_PINS = { _ESP32C6_SPI_PSRAM_PINS = {

View file

@ -1,8 +1,7 @@
import logging import logging
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
import esphome.config_validation as cv 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} _ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21}

View file

@ -1,5 +1,6 @@
import logging import logging
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_INPUT, CONF_INPUT,
CONF_MODE, CONF_MODE,
@ -8,8 +9,6 @@ from esphome.const import (
CONF_PULLDOWN, CONF_PULLDOWN,
CONF_PULLUP, CONF_PULLUP,
) )
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
_ESP32S2_SPI_PSRAM_PINS = { _ESP32S2_SPI_PSRAM_PINS = {

View file

@ -1,12 +1,7 @@
import logging import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
)
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
_ESP_32S3_SPI_PSRAM_PINS = { _ESP_32S3_SPI_PSRAM_PINS = {

View file

@ -1,16 +1,16 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
import esphome.config_validation as cv
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID
from esphome.core import CORE from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@jesserockz", "@Rapsssito"] CODEOWNERS = ["@jesserockz", "@Rapsssito"]
CONFLICTS_WITH = ["esp32_ble_beacon"]
CONF_BLE_ID = "ble_id" CONF_BLE_ID = "ble_id"
CONF_IO_CAPABILITY = "io_capability" CONF_IO_CAPABILITY = "io_capability"
CONF_ADVERTISING_CYCLE_TIME = "advertising_cycle_time"
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
@ -34,6 +34,19 @@ IO_CAPABILITY = {
"display_yes_no": IoCapability.IO_CAP_IO, "display_yes_no": IoCapability.IO_CAP_IO,
} }
esp_power_level_t = cg.global_ns.enum("esp_power_level_t")
TX_POWER_LEVELS = {
-12: esp_power_level_t.ESP_PWR_LVL_N12,
-9: esp_power_level_t.ESP_PWR_LVL_N9,
-6: esp_power_level_t.ESP_PWR_LVL_N6,
-3: esp_power_level_t.ESP_PWR_LVL_N3,
0: esp_power_level_t.ESP_PWR_LVL_N0,
3: esp_power_level_t.ESP_PWR_LVL_P3,
6: esp_power_level_t.ESP_PWR_LVL_P6,
9: esp_power_level_t.ESP_PWR_LVL_P9,
}
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(ESP32BLE), cv.GenerateID(): cv.declare_id(ESP32BLE),
@ -41,6 +54,9 @@ CONFIG_SCHEMA = cv.Schema(
IO_CAPABILITY, lower=True IO_CAPABILITY, lower=True
), ),
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
cv.Optional(
CONF_ADVERTISING_CYCLE_TIME, default="10s"
): cv.positive_time_period_milliseconds,
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
@ -58,6 +74,7 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT]))
cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME]))
await cg.register_component(var, config) await cg.register_component(var, config)
if CORE.using_esp_idf: if CORE.using_esp_idf:

View file

@ -78,6 +78,11 @@ void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &dat
this->advertising_start(); this->advertising_start();
} }
void ESP32BLE::advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback) {
this->advertising_init_();
this->advertising_->register_raw_advertisement_callback(std::move(callback));
}
void ESP32BLE::advertising_add_service_uuid(ESPBTUUID uuid) { void ESP32BLE::advertising_add_service_uuid(ESPBTUUID uuid) {
this->advertising_init_(); this->advertising_init_();
this->advertising_->add_service_uuid(uuid); this->advertising_->add_service_uuid(uuid);
@ -102,7 +107,7 @@ bool ESP32BLE::ble_pre_setup_() {
void ESP32BLE::advertising_init_() { void ESP32BLE::advertising_init_() {
if (this->advertising_ != nullptr) if (this->advertising_ != nullptr)
return; return;
this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory) this->advertising_ = new BLEAdvertising(this->advertising_cycle_time_); // NOLINT(cppcoreguidelines-owning-memory)
this->advertising_->set_scan_response(true); this->advertising_->set_scan_response(true);
this->advertising_->set_min_preferred_interval(0x06); this->advertising_->set_min_preferred_interval(0x06);
@ -312,6 +317,9 @@ void ESP32BLE::loop() {
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
ble_event = this->ble_events_.pop(); ble_event = this->ble_events_.pop();
} }
if (this->advertising_ != nullptr) {
this->advertising_->loop();
}
} }
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {

View file

@ -3,6 +3,8 @@
#include "ble_advertising.h" #include "ble_advertising.h"
#include "ble_uuid.h" #include "ble_uuid.h"
#include <functional>
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
@ -76,6 +78,11 @@ class ESP32BLE : public Component {
public: public:
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
void set_advertising_cycle_time(uint32_t advertising_cycle_time) {
this->advertising_cycle_time_ = advertising_cycle_time;
}
uint32_t get_advertising_cycle_time() const { return this->advertising_cycle_time_; }
void enable(); void enable();
void disable(); void disable();
bool is_active(); bool is_active();
@ -89,6 +96,7 @@ class ESP32BLE : public Component {
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data); void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
void advertising_add_service_uuid(ESPBTUUID uuid); void advertising_add_service_uuid(ESPBTUUID uuid);
void advertising_remove_service_uuid(ESPBTUUID uuid); void advertising_remove_service_uuid(ESPBTUUID uuid);
void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback);
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); } void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); } void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); }
@ -121,6 +129,7 @@ class ESP32BLE : public Component {
Queue<BLEEvent> ble_events_; Queue<BLEEvent> ble_events_;
BLEAdvertising *advertising_; BLEAdvertising *advertising_;
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
uint32_t advertising_cycle_time_;
bool enable_on_boot_; bool enable_on_boot_;
}; };

View file

@ -10,9 +10,9 @@
namespace esphome { namespace esphome {
namespace esp32_ble { namespace esp32_ble {
static const char *const TAG = "esp32_ble"; static const char *const TAG = "esp32_ble.advertising";
BLEAdvertising::BLEAdvertising() { BLEAdvertising::BLEAdvertising(uint32_t advertising_cycle_time) : advertising_cycle_time_(advertising_cycle_time) {
this->advertising_data_.set_scan_rsp = false; this->advertising_data_.set_scan_rsp = false;
this->advertising_data_.include_name = true; this->advertising_data_.include_name = true;
this->advertising_data_.include_txpower = true; this->advertising_data_.include_txpower = true;
@ -64,7 +64,7 @@ void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) {
} }
} }
void BLEAdvertising::start() { esp_err_t BLEAdvertising::services_advertisement_() {
int num_services = this->advertising_uuids_.size(); int num_services = this->advertising_uuids_.size();
if (num_services == 0) { if (num_services == 0) {
this->advertising_data_.service_uuid_len = 0; this->advertising_data_.service_uuid_len = 0;
@ -87,8 +87,8 @@ void BLEAdvertising::start() {
this->advertising_data_.include_txpower = !this->scan_response_; this->advertising_data_.include_txpower = !this->scan_response_;
err = esp_ble_gap_config_adv_data(&this->advertising_data_); err = esp_ble_gap_config_adv_data(&this->advertising_data_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Advertising): %d", err); ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Advertising): %s", esp_err_to_name(err));
return; return err;
} }
if (this->scan_response_) { if (this->scan_response_) {
@ -101,8 +101,8 @@ void BLEAdvertising::start() {
this->scan_response_data_.flag = 0; this->scan_response_data_.flag = 0;
err = esp_ble_gap_config_adv_data(&this->scan_response_data_); err = esp_ble_gap_config_adv_data(&this->scan_response_data_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Scan response): %d", err); ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Scan response): %s", esp_err_to_name(err));
return; return err;
} }
} }
@ -113,8 +113,18 @@ void BLEAdvertising::start() {
err = esp_ble_gap_start_advertising(&this->advertising_params_); err = esp_ble_gap_start_advertising(&this->advertising_params_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %d", err); ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %s", esp_err_to_name(err));
return; return err;
}
return ESP_OK;
}
void BLEAdvertising::start() {
if (this->current_adv_index_ == -1) {
this->services_advertisement_();
} else {
this->raw_advertisements_callbacks_[this->current_adv_index_](true);
} }
} }
@ -124,6 +134,29 @@ void BLEAdvertising::stop() {
ESP_LOGE(TAG, "esp_ble_gap_stop_advertising failed: %d", err); ESP_LOGE(TAG, "esp_ble_gap_stop_advertising failed: %d", err);
return; return;
} }
if (this->current_adv_index_ != -1) {
this->raw_advertisements_callbacks_[this->current_adv_index_](false);
}
}
void BLEAdvertising::loop() {
if (this->raw_advertisements_callbacks_.empty()) {
return;
}
const uint32_t now = millis();
if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) {
this->stop();
this->current_adv_index_ += 1;
if (this->current_adv_index_ >= this->raw_advertisements_callbacks_.size()) {
this->current_adv_index_ = -1;
}
this->start();
this->last_advertisement_time_ = now;
}
}
void BLEAdvertising::register_raw_advertisement_callback(std::function<void(bool)> &&callback) {
this->raw_advertisements_callbacks_.push_back(std::move(callback));
} }
} // namespace esp32_ble } // namespace esp32_ble

View file

@ -1,20 +1,31 @@
#pragma once #pragma once
#include <array>
#include <functional>
#include <vector> #include <vector>
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_bt.h>
#include <esp_gap_ble_api.h> #include <esp_gap_ble_api.h>
#include <esp_gatts_api.h> #include <esp_gatts_api.h>
namespace esphome { namespace esphome {
namespace esp32_ble { namespace esp32_ble {
using raw_adv_data_t = struct {
uint8_t *data;
size_t length;
esp_power_level_t power_level;
};
class ESPBTUUID; class ESPBTUUID;
class BLEAdvertising { class BLEAdvertising {
public: public:
BLEAdvertising(); BLEAdvertising(uint32_t advertising_cycle_time);
void loop();
void add_service_uuid(ESPBTUUID uuid); void add_service_uuid(ESPBTUUID uuid);
void remove_service_uuid(ESPBTUUID uuid); void remove_service_uuid(ESPBTUUID uuid);
@ -22,16 +33,25 @@ class BLEAdvertising {
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(const std::vector<uint8_t> &data); void set_manufacturer_data(const std::vector<uint8_t> &data);
void set_service_data(const std::vector<uint8_t> &data); void set_service_data(const std::vector<uint8_t> &data);
void register_raw_advertisement_callback(std::function<void(bool)> &&callback);
void start(); void start();
void stop(); void stop();
protected: protected:
esp_err_t services_advertisement_();
bool scan_response_; bool scan_response_;
esp_ble_adv_data_t advertising_data_; esp_ble_adv_data_t advertising_data_;
esp_ble_adv_data_t scan_response_data_; esp_ble_adv_data_t scan_response_data_;
esp_ble_adv_params_t advertising_params_; esp_ble_adv_params_t advertising_params_;
std::vector<ESPBTUUID> advertising_uuids_; std::vector<ESPBTUUID> advertising_uuids_;
std::vector<std::function<void(bool)>> raw_advertisements_callbacks_;
const uint32_t advertising_cycle_time_;
uint32_t last_advertisement_time_{0};
int8_t current_adv_index_{-1}; // -1 means standard scan response
}; };
} // namespace esp32_ble } // namespace esp32_ble

View file

@ -1,16 +1,21 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
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 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"] DEPENDENCIES = ["esp32"]
CONFLICTS_WITH = ["esp32_ble_tracker"]
esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon") esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon")
ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component) ESP32BLEBeacon = esp32_ble_beacon_ns.class_(
"ESP32BLEBeacon",
cg.Component,
esp32_ble.GAPEventHandler,
cg.Parented.template(esp32_ble.ESP32BLE),
)
CONF_MAJOR = "major" CONF_MAJOR = "major"
CONF_MINOR = "minor" CONF_MINOR = "minor"
CONF_MIN_INTERVAL = "min_interval" CONF_MIN_INTERVAL = "min_interval"
@ -28,6 +33,7 @@ CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(ESP32BLEBeacon), cv.GenerateID(): cv.declare_id(ESP32BLEBeacon),
cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True), cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True),
cv.Required(CONF_UUID): cv.uuid, cv.Required(CONF_UUID): cv.uuid,
cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t, cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t,
@ -48,7 +54,7 @@ CONFIG_SCHEMA = cv.All(
min=-128, max=0 min=-128, max=0
), ),
cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All( cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All(
cv.decibel, cv.one_of(-12, -9, -6, -3, 0, 3, 6, 9, int=True) cv.decibel, cv.enum(esp32_ble.TX_POWER_LEVELS, int=True)
), ),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
@ -62,6 +68,10 @@ async def to_code(config):
uuid = config[CONF_UUID].hex uuid = config[CONF_UUID].hex
uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)] uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)]
var = cg.new_Pvariable(config[CONF_ID], uuid_arr) var = cg.new_Pvariable(config[CONF_ID], uuid_arr)
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])
cg.add(parent.register_gap_event_handler(var))
await cg.register_component(var, config) await cg.register_component(var, config)
cg.add(var.set_major(config[CONF_MAJOR])) cg.add(var.set_major(config[CONF_MAJOR]))
cg.add(var.set_minor(config[CONF_MINOR])) cg.add(var.set_minor(config[CONF_MINOR]))

View file

@ -3,14 +3,16 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <nvs_flash.h>
#include <freertos/FreeRTOS.h>
#include <esp_bt_main.h>
#include <esp_bt.h> #include <esp_bt.h>
#include <freertos/task.h> #include <esp_bt_main.h>
#include <esp_gap_ble_api.h> #include <esp_gap_ble_api.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <nvs_flash.h>
#include <cstring> #include <cstring>
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include <esp32-hal-bt.h> #include <esp32-hal-bt.h>
@ -21,20 +23,6 @@ namespace esp32_ble_beacon {
static const char *const TAG = "esp32_ble_beacon"; static const char *const TAG = "esp32_ble_beacon";
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static esp_ble_adv_params_t ble_adv_params = {
.adv_int_min = 0x20,
.adv_int_max = 0x40,
.adv_type = ADV_TYPE_NONCONN_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.peer_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.peer_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
#define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8))
static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = { static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = {
.flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}}; .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}};
@ -53,117 +41,62 @@ void ESP32BLEBeacon::dump_config() {
" UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d" " UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d"
", TX Power: %ddBm", ", TX Power: %ddBm",
uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_, uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_,
this->tx_power_); (this->tx_power_ * 3) - 12);
} }
float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void ESP32BLEBeacon::setup() { void ESP32BLEBeacon::setup() {
ESP_LOGCONFIG(TAG, "Setting up ESP32 BLE beacon..."); this->ble_adv_params_ = {
global_esp32_ble_beacon = this; .adv_int_min = static_cast<uint16_t>(this->min_interval_ / 0.625f),
.adv_int_max = static_cast<uint16_t>(this->max_interval_ / 0.625f),
.adv_type = ADV_TYPE_NONCONN_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.peer_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.peer_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
xTaskCreatePinnedToCore(ESP32BLEBeacon::ble_core_task, global_ble->advertising_register_raw_advertisement_callback([this](bool advertise) {
"ble_task", // name this->advertising_ = advertise;
10000, // stack size (in words) if (advertise) {
nullptr, // input params this->on_advertise_();
1, // priority }
nullptr, // Handle, not needed });
0 // core
);
} }
float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::BLUETOOTH; } void ESP32BLEBeacon::on_advertise_() {
void ESP32BLEBeacon::ble_core_task(void *params) {
ble_setup();
while (true) {
delay(1000); // NOLINT
}
}
void ESP32BLEBeacon::ble_setup() {
ble_adv_params.adv_int_min = static_cast<uint16_t>(global_esp32_ble_beacon->min_interval_ / 0.625f);
ble_adv_params.adv_int_max = static_cast<uint16_t>(global_esp32_ble_beacon->max_interval_ / 0.625f);
// Initialize non-volatile storage for the bluetooth controller
esp_err_t err = nvs_flash_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "nvs_flash_init failed: %d", err);
return;
}
#ifdef USE_ARDUINO
if (!btStart()) {
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
return;
}
#else
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
// start bt controller
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
err = esp_bt_controller_init(&cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
return;
}
while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE)
;
}
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err));
return;
}
}
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
ESP_LOGE(TAG, "esp bt controller enable failed");
return;
}
}
#endif
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
err = esp_bluedroid_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);
return;
}
err = esp_bluedroid_enable();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
return;
}
err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV,
static_cast<esp_power_level_t>((global_esp32_ble_beacon->tx_power_ + 12) / 3));
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
return;
}
err = esp_ble_gap_register_callback(ESP32BLEBeacon::gap_event_handler);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
return;
}
esp_ble_ibeacon_t ibeacon_adv_data; esp_ble_ibeacon_t ibeacon_adv_data;
memcpy(&ibeacon_adv_data.ibeacon_head, &IBEACON_COMMON_HEAD, sizeof(esp_ble_ibeacon_head_t)); memcpy(&ibeacon_adv_data.ibeacon_head, &IBEACON_COMMON_HEAD, sizeof(esp_ble_ibeacon_head_t));
memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, global_esp32_ble_beacon->uuid_.data(), memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, this->uuid_.data(),
sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid)); sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid));
ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_); ibeacon_adv_data.ibeacon_vendor.minor = byteswap(this->minor_);
ibeacon_adv_data.ibeacon_vendor.major = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->major_); ibeacon_adv_data.ibeacon_vendor.major = byteswap(this->major_);
ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast<uint8_t>(global_esp32_ble_beacon->measured_power_); ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast<uint8_t>(this->measured_power_);
esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data)); ESP_LOGD(TAG, "Setting BLE TX power");
esp_err_t err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, this->tx_power_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
}
err = esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data));
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_config_adv_data_raw failed: %s", esp_err_to_name(err));
return;
}
} }
void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
if (!this->advertising_)
return;
esp_err_t err; esp_err_t err;
switch (event) { switch (event) {
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: {
err = esp_ble_gap_start_advertising(&ble_adv_params); err = esp_ble_gap_start_advertising(&this->ble_adv_params_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %d", err); ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %s", esp_err_to_name(err));
} }
break; break;
} }
@ -181,6 +114,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap
} else { } else {
ESP_LOGD(TAG, "BLE stopped advertising successfully"); ESP_LOGD(TAG, "BLE stopped advertising successfully");
} }
// this->advertising_ = false;
break; break;
} }
default: default:
@ -188,8 +122,6 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap
} }
} }
ESP32BLEBeacon *global_esp32_ble_beacon = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace esp32_ble_beacon } // namespace esp32_ble_beacon
} // namespace esphome } // namespace esphome

View file

@ -1,39 +1,39 @@
#pragma once #pragma once
#include "esphome/components/esp32_ble/ble.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_gap_ble_api.h>
#include <esp_bt.h> #include <esp_bt.h>
#include <esp_gap_ble_api.h>
namespace esphome { namespace esphome {
namespace esp32_ble_beacon { namespace esp32_ble_beacon {
// NOLINTNEXTLINE(modernize-use-using) using esp_ble_ibeacon_head_t = struct {
typedef struct {
uint8_t flags[3]; uint8_t flags[3];
uint8_t length; uint8_t length;
uint8_t type; uint8_t type;
uint8_t company_id[2]; uint8_t company_id[2];
uint8_t beacon_type[2]; uint8_t beacon_type[2];
} __attribute__((packed)) esp_ble_ibeacon_head_t; } __attribute__((packed));
// NOLINTNEXTLINE(modernize-use-using) using esp_ble_ibeacon_vendor_t = struct {
typedef struct {
uint8_t proximity_uuid[16]; uint8_t proximity_uuid[16];
uint16_t major; uint16_t major;
uint16_t minor; uint16_t minor;
uint8_t measured_power; uint8_t measured_power;
} __attribute__((packed)) esp_ble_ibeacon_vendor_t; } __attribute__((packed));
// NOLINTNEXTLINE(modernize-use-using) using esp_ble_ibeacon_t = struct {
typedef struct {
esp_ble_ibeacon_head_t ibeacon_head; esp_ble_ibeacon_head_t ibeacon_head;
esp_ble_ibeacon_vendor_t ibeacon_vendor; esp_ble_ibeacon_vendor_t ibeacon_vendor;
} __attribute__((packed)) esp_ble_ibeacon_t; } __attribute__((packed));
class ESP32BLEBeacon : public Component { using namespace esp32_ble;
class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented<ESP32BLE> {
public: public:
explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {} explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {}
@ -46,12 +46,11 @@ class ESP32BLEBeacon : public Component {
void set_min_interval(uint16_t val) { this->min_interval_ = val; } void set_min_interval(uint16_t val) { this->min_interval_ = val; }
void set_max_interval(uint16_t val) { this->max_interval_ = val; } void set_max_interval(uint16_t val) { this->max_interval_ = val; }
void set_measured_power(int8_t val) { this->measured_power_ = val; } void set_measured_power(int8_t val) { this->measured_power_ = val; }
void set_tx_power(int8_t val) { this->tx_power_ = val; } void set_tx_power(esp_power_level_t val) { this->tx_power_ = val; }
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
protected: protected:
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); void on_advertise_();
static void ble_core_task(void *params);
static void ble_setup();
std::array<uint8_t, 16> uuid_; std::array<uint8_t, 16> uuid_;
uint16_t major_{}; uint16_t major_{};
@ -59,12 +58,11 @@ class ESP32BLEBeacon : public Component {
uint16_t min_interval_{}; uint16_t min_interval_{};
uint16_t max_interval_{}; uint16_t max_interval_{};
int8_t measured_power_{}; int8_t measured_power_{};
int8_t tx_power_{}; esp_power_level_t tx_power_{};
esp_ble_adv_params_t ble_adv_params_;
bool advertising_{false};
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern ESP32BLEBeacon *global_esp32_ble_beacon;
} // namespace esp32_ble_beacon } // namespace esp32_ble_beacon
} // namespace esphome } // namespace esphome

View file

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

View file

@ -1,13 +1,12 @@
import esphome.codegen as cg 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 import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODEL from esphome.const import CONF_ID, CONF_MODEL
from esphome.components import esp32_ble
from esphome.core import CORE from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option
AUTO_LOAD = ["esp32_ble"] AUTO_LOAD = ["esp32_ble"]
CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"]
CONFLICTS_WITH = ["esp32_ble_beacon"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CONF_MANUFACTURER = "manufacturer" CONF_MANUFACTURER = "manufacturer"

View file

@ -1,10 +1,10 @@
import re import re
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components import esp32_ble from esphome.components import esp32_ble
from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ACTIVE, CONF_ACTIVE,
CONF_DURATION, CONF_DURATION,

View file

@ -1,12 +1,10 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import binary_sensor, esp32_ble_server, output
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import binary_sensor, output, esp32_ble_server
from esphome.const import CONF_ID from esphome.const import CONF_ID
AUTO_LOAD = ["esp32_ble_server"] AUTO_LOAD = ["esp32_ble_server"]
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
CONFLICTS_WITH = ["esp32_ble_beacon"]
DEPENDENCIES = ["wifi", "esp32"] DEPENDENCIES = ["wifi", "esp32"]
CONF_AUTHORIZED_DURATION = "authorized_duration" CONF_AUTHORIZED_DURATION = "authorized_duration"
@ -51,7 +49,7 @@ async def to_code(config):
cg.add(ble_server.register_service_component(var)) cg.add(ble_server.register_service_component(var))
cg.add_define("USE_IMPROV") cg.add_define("USE_IMPROV")
cg.add_library("esphome/Improv", "1.2.3") cg.add_library("improv/Improv", "1.2.4")
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION])) cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION])) cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View file

@ -1,6 +1,4 @@
from esphome import pins from esphome import pins
import esphome.config_validation as cv
import esphome.final_validate as fv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
@ -8,31 +6,33 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, 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 ( from esphome.const import (
CONF_DOMAIN, CONF_ADDRESS,
CONF_ID, CONF_CLK_PIN,
CONF_VALUE, CONF_CS_PIN,
CONF_MANUAL_IP,
CONF_STATIC_IP,
CONF_TYPE,
CONF_USE_ADDRESS,
CONF_GATEWAY,
CONF_SUBNET,
CONF_DNS1, CONF_DNS1,
CONF_DNS2, CONF_DNS2,
CONF_CLK_PIN, CONF_DOMAIN,
CONF_GATEWAY,
CONF_ID,
CONF_INTERRUPT_PIN,
CONF_MANUAL_IP,
CONF_MISO_PIN, CONF_MISO_PIN,
CONF_MOSI_PIN, CONF_MOSI_PIN,
CONF_CS_PIN, CONF_PAGE_ID,
CONF_INTERRUPT_PIN,
CONF_RESET_PIN, CONF_RESET_PIN,
CONF_SPI, CONF_SPI,
CONF_PAGE_ID, CONF_STATIC_IP,
CONF_ADDRESS, CONF_SUBNET,
CONF_TYPE,
CONF_USE_ADDRESS,
CONF_VALUE,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.components.network import IPAddress import esphome.final_validate as fv
from esphome.components.spi import get_spi_interface, CONF_INTERFACE_INDEX
CONFLICTS_WITH = ["wifi"] CONFLICTS_WITH = ["wifi"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]

View file

@ -1,9 +1,9 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_IP_ADDRESS,
CONF_DNS_ADDRESS, CONF_DNS_ADDRESS,
CONF_IP_ADDRESS,
CONF_MAC_ADDRESS, CONF_MAC_ADDRESS,
ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_DIAGNOSTIC,
) )

View file

@ -1,24 +1,24 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components import mqtt from esphome.components import mqtt
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY, CONF_ENTITY_CATEGORY,
CONF_EVENT_TYPE,
CONF_ICON, CONF_ICON,
CONF_ID, CONF_ID,
CONF_MQTT_ID,
CONF_ON_EVENT, CONF_ON_EVENT,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_EVENT_TYPE,
DEVICE_CLASS_BUTTON, DEVICE_CLASS_BUTTON,
DEVICE_CLASS_DOORBELL, DEVICE_CLASS_DOORBELL,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
DEVICE_CLASS_MOTION, DEVICE_CLASS_MOTION,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@nohat"] CODEOWNERS = ["@nohat"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True

View file

@ -1,31 +1,31 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.automation import maybe_simple_id from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DIRECTION,
CONF_ID, CONF_ID,
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_OSCILLATING,
CONF_OSCILLATION_COMMAND_TOPIC,
CONF_OSCILLATION_STATE_TOPIC,
CONF_SPEED,
CONF_SPEED_LEVEL_COMMAND_TOPIC,
CONF_SPEED_LEVEL_STATE_TOPIC,
CONF_SPEED_COMMAND_TOPIC,
CONF_SPEED_STATE_TOPIC,
CONF_OFF_SPEED_CYCLE, CONF_OFF_SPEED_CYCLE,
CONF_ON_DIRECTION_SET, CONF_ON_DIRECTION_SET,
CONF_ON_OSCILLATING_SET, CONF_ON_OSCILLATING_SET,
CONF_ON_PRESET_SET,
CONF_ON_SPEED_SET, CONF_ON_SPEED_SET,
CONF_ON_STATE, CONF_ON_STATE,
CONF_ON_TURN_OFF, CONF_ON_TURN_OFF,
CONF_ON_TURN_ON, CONF_ON_TURN_ON,
CONF_ON_PRESET_SET, CONF_OSCILLATING,
CONF_TRIGGER_ID, CONF_OSCILLATION_COMMAND_TOPIC,
CONF_DIRECTION, CONF_OSCILLATION_STATE_TOPIC,
CONF_RESTORE_MODE, CONF_RESTORE_MODE,
CONF_SPEED,
CONF_SPEED_COMMAND_TOPIC,
CONF_SPEED_LEVEL_COMMAND_TOPIC,
CONF_SPEED_LEVEL_STATE_TOPIC,
CONF_SPEED_STATE_TOPIC,
CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity

View file

@ -45,8 +45,8 @@ void FanCall::validate_() {
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count()); this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
if (this->binary_state_.has_value() && *this->binary_state_) { if (this->binary_state_.has_value() && *this->binary_state_) {
// when turning on, if current speed is zero, set speed to 100% // when turning on, if neither current nor new speed available, set speed to 100%
if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0) { if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0 && !this->speed_.has_value()) {
this->speed_ = traits.supported_speed_count(); this->speed_ = traits.supported_speed_count();
} }
} }

View file

@ -1,19 +1,22 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import display, font, color
from esphome.const import CONF_DISPLAY, CONF_ID, CONF_TRIGGER_ID
from esphome import automation, core from esphome import automation, core
import esphome.codegen as cg
from esphome.components import color, display, font
from esphome.components.display_menu_base import ( from esphome.components.display_menu_base import (
DISPLAY_MENU_BASE_SCHEMA, DISPLAY_MENU_BASE_SCHEMA,
DisplayMenuComponent, DisplayMenuComponent,
display_menu_to_code, display_menu_to_code,
) )
import esphome.config_validation as cv
from esphome.const import (
CONF_BACKGROUND_COLOR,
CONF_DISPLAY,
CONF_FONT,
CONF_FOREGROUND_COLOR,
CONF_ID,
CONF_TRIGGER_ID,
)
CONF_FONT = "font"
CONF_MENU_ITEM_VALUE = "menu_item_value" CONF_MENU_ITEM_VALUE = "menu_item_value"
CONF_FOREGROUND_COLOR = "foreground_color"
CONF_BACKGROUND_COLOR = "background_color"
CONF_ON_REDRAW = "on_redraw" CONF_ON_REDRAW = "on_redraw"
graphical_display_menu_ns = cg.esphome_ns.namespace("graphical_display_menu") graphical_display_menu_ns = cg.esphome_ns.namespace("graphical_display_menu")

View file

@ -8,6 +8,7 @@ from esphome.const import (
CONF_PROTOCOL, CONF_PROTOCOL,
CONF_VISUAL, CONF_VISUAL,
) )
from esphome.core import CORE
CODEOWNERS = ["@rob-deutsch"] CODEOWNERS = ["@rob-deutsch"]
@ -127,3 +128,5 @@ def to_code(config):
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
cg.add_library("tonia/HeatpumpIR", "1.0.27") cg.add_library("tonia/HeatpumpIR", "1.0.27")
if CORE.is_libretiny:
CORE.add_platformio_option("lib_ignore", "IRremoteESP8266")

View file

@ -1,20 +1,20 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components import esp32
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
__version__, CONF_ESP8266_DISABLE_SSL_SUPPORT,
CONF_ID, CONF_ID,
CONF_TIMEOUT,
CONF_METHOD, CONF_METHOD,
CONF_TIMEOUT,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_URL, CONF_URL,
CONF_ESP8266_DISABLE_SSL_SUPPORT, __version__,
) )
from esphome.core import Lambda, CORE from esphome.core import CORE, Lambda
from esphome.components import esp32
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
AUTO_LOAD = ["json"] AUTO_LOAD = ["json", "watchdog"]
http_request_ns = cg.esphome_ns.namespace("http_request") http_request_ns = cg.esphome_ns.namespace("http_request")
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component) HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
@ -40,6 +40,8 @@ CONF_VERIFY_SSL = "verify_ssl"
CONF_FOLLOW_REDIRECTS = "follow_redirects" CONF_FOLLOW_REDIRECTS = "follow_redirects"
CONF_REDIRECT_LIMIT = "redirect_limit" CONF_REDIRECT_LIMIT = "redirect_limit"
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout" CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
CONF_BUFFER_SIZE_RX = "buffer_size_rx"
CONF_BUFFER_SIZE_TX = "buffer_size_tx"
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size" CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
CONF_ON_RESPONSE = "on_response" CONF_ON_RESPONSE = "on_response"
@ -99,7 +101,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean, cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean,
cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_, cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_,
cv.Optional( cv.Optional(
CONF_TIMEOUT, default="5s" CONF_TIMEOUT, default="4.5s"
): cv.positive_time_period_milliseconds, ): cv.positive_time_period_milliseconds,
cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All( cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All(
cv.only_on_esp8266, cv.boolean cv.only_on_esp8266, cv.boolean
@ -110,6 +112,12 @@ CONFIG_SCHEMA = cv.All(
cv.positive_not_null_time_period, cv.positive_not_null_time_period,
cv.positive_time_period_milliseconds, cv.positive_time_period_milliseconds,
), ),
cv.SplitDefault(CONF_BUFFER_SIZE_RX, esp32_idf=512): cv.All(
cv.uint16_t, cv.only_with_esp_idf
),
cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32_idf=512): cv.All(
cv.uint16_t, cv.only_with_esp_idf
),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.require_framework_version( cv.require_framework_version(
@ -137,6 +145,9 @@ async def to_code(config):
if CORE.is_esp32: if CORE.is_esp32:
if CORE.using_esp_idf: if CORE.using_esp_idf:
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
esp32.add_idf_sdkconfig_option( esp32.add_idf_sdkconfig_option(
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", "CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
config.get(CONF_VERIFY_SSL), config.get(CONF_VERIFY_SSL),

View file

@ -80,7 +80,7 @@ class HttpRequestComponent : public Component {
const char *useragent_{nullptr}; const char *useragent_{nullptr};
bool follow_redirects_; bool follow_redirects_;
uint16_t redirect_limit_; uint16_t redirect_limit_;
uint16_t timeout_{5000}; uint16_t timeout_{4500};
uint32_t watchdog_timeout_{0}; uint32_t watchdog_timeout_{0};
}; };

View file

@ -3,12 +3,12 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include "esphome/components/network/util.h" #include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "watchdog.h"
namespace esphome { namespace esphome {
namespace http_request { namespace http_request {

View file

@ -3,6 +3,8 @@
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
#include "esphome/components/network/util.h" #include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@ -11,13 +13,17 @@
#include "esp_crt_bundle.h" #include "esp_crt_bundle.h"
#endif #endif
#include "watchdog.h"
namespace esphome { namespace esphome {
namespace http_request { namespace http_request {
static const char *const TAG = "http_request.idf"; static const char *const TAG = "http_request.idf";
void HttpRequestIDF::dump_config() {
HttpRequestComponent::dump_config();
ESP_LOGCONFIG(TAG, " Buffer Size RX: %u", this->buffer_size_rx_);
ESP_LOGCONFIG(TAG, " Buffer Size TX: %u", this->buffer_size_tx_);
}
std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::string method, std::string body, std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::string method, std::string body,
std::list<Header> headers) { std::list<Header> headers) {
if (!network::is_connected()) { if (!network::is_connected()) {
@ -52,6 +58,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
config.timeout_ms = this->timeout_; config.timeout_ms = this->timeout_;
config.disable_auto_redirect = !this->follow_redirects_; config.disable_auto_redirect = !this->follow_redirects_;
config.max_redirection_count = this->redirect_limit_; config.max_redirection_count = this->redirect_limit_;
config.auth_type = HTTP_AUTH_TYPE_BASIC;
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
if (secure) { if (secure) {
config.crt_bundle_attach = esp_crt_bundle_attach; config.crt_bundle_attach = esp_crt_bundle_attach;
@ -62,6 +69,9 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
config.user_agent = this->useragent_; config.user_agent = this->useragent_;
} }
config.buffer_size = this->buffer_size_rx_;
config.buffer_size_tx = this->buffer_size_tx_;
const uint32_t start = millis(); const uint32_t start = millis();
watchdog::WatchdogManager wdm(this->get_watchdog_timeout()); watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
@ -76,7 +86,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
esp_http_client_set_header(client, header.name, header.value); esp_http_client_set_header(client, header.name, header.value);
} }
int body_len = body.length(); const int body_len = body.length();
esp_err_t err = esp_http_client_open(client, body_len); esp_err_t err = esp_http_client_open(client, body_len);
if (err != ESP_OK) { if (err != ESP_OK) {
@ -108,18 +118,62 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
return nullptr; return nullptr;
} }
container->content_length = esp_http_client_fetch_headers(client); auto is_ok = [](int code) { return code >= HttpStatus_Ok && code < HttpStatus_MultipleChoices; };
const auto status_code = esp_http_client_get_status_code(client);
container->status_code = status_code;
if (status_code < 200 || status_code >= 300) { container->content_length = esp_http_client_fetch_headers(client);
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), status_code); container->status_code = esp_http_client_get_status_code(client);
this->status_momentary_error("failed", 1000); if (is_ok(container->status_code)) {
esp_http_client_cleanup(client); container->duration_ms = millis() - start;
return nullptr; return container;
} }
container->duration_ms = millis() - start;
return container; if (this->follow_redirects_) {
auto is_redirect = [](int code) {
return code == HttpStatus_MovedPermanently || code == HttpStatus_Found || code == HttpStatus_SeeOther ||
code == HttpStatus_TemporaryRedirect || code == HttpStatus_PermanentRedirect;
};
auto num_redirects = this->redirect_limit_;
while (is_redirect(container->status_code) && num_redirects > 0) {
err = esp_http_client_set_redirection(client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_http_client_set_redirection failed: %s", esp_err_to_name(err));
this->status_momentary_error("failed", 1000);
esp_http_client_cleanup(client);
return nullptr;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char url[256]{};
if (esp_http_client_get_url(client, url, sizeof(url) - 1) == ESP_OK) {
ESP_LOGV(TAG, "redirecting to url: %s", url);
}
#endif
err = esp_http_client_open(client, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_http_client_open failed: %s", esp_err_to_name(err));
this->status_momentary_error("failed", 1000);
esp_http_client_cleanup(client);
return nullptr;
}
container->content_length = esp_http_client_fetch_headers(client);
container->status_code = esp_http_client_get_status_code(client);
if (is_ok(container->status_code)) {
container->duration_ms = millis() - start;
return container;
}
num_redirects--;
}
if (num_redirects == 0) {
ESP_LOGW(TAG, "Reach redirect limit count=%d", this->redirect_limit_);
}
}
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
this->status_momentary_error("failed", 1000);
esp_http_client_cleanup(client);
return nullptr;
} }
int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {

View file

@ -24,8 +24,18 @@ class HttpContainerIDF : public HttpContainer {
class HttpRequestIDF : public HttpRequestComponent { class HttpRequestIDF : public HttpRequestComponent {
public: public:
void dump_config() override;
std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body, std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
std::list<Header> headers) override; std::list<Header> headers) override;
void set_buffer_size_rx(uint16_t buffer_size_rx) { this->buffer_size_rx_ = buffer_size_rx; }
void set_buffer_size_tx(uint16_t buffer_size_tx) { this->buffer_size_tx_ = buffer_size_tx; }
protected:
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
uint16_t buffer_size_rx_{};
uint16_t buffer_size_tx_{};
}; };
} // namespace http_request } // namespace http_request

View file

@ -1,11 +1,11 @@
#include "ota_http_request.h" #include "ota_http_request.h"
#include "../watchdog.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/components/md5/md5.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.h"
#include "esphome/components/ota/ota_backend_arduino_esp32.h" #include "esphome/components/ota/ota_backend_arduino_esp32.h"
#include "esphome/components/ota/ota_backend_arduino_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_esp8266.h"

View file

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

View file

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

View file

@ -236,7 +236,7 @@ void HydreonRGxxComponent::process_line_() {
} }
bool is_data_line = false; bool is_data_line = false;
for (int i = 0; i < NUM_SENSORS; i++) { for (int i = 0; i < NUM_SENSORS; i++) {
if (this->sensors_[i] != nullptr && this->buffer_starts_with_(PROTOCOL_NAMES[i])) { if (this->sensors_[i] != nullptr && this->buffer_.find(PROTOCOL_NAMES[i]) != std::string::npos) {
is_data_line = true; is_data_line = true;
break; break;
} }

View file

@ -174,7 +174,8 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
size_t samples_read = bytes_read / sizeof(int32_t); size_t samples_read = bytes_read / sizeof(int32_t);
samples.resize(samples_read); samples.resize(samples_read);
for (size_t i = 0; i < samples_read; i++) { for (size_t i = 0; i < samples_read; i++) {
samples[i] = reinterpret_cast<int32_t *>(buf)[i] >> 16; int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14;
samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX);
} }
memcpy(buf, samples.data(), samples_read * sizeof(int16_t)); memcpy(buf, samples.data(), samples_read * sizeof(int16_t));
return samples_read * sizeof(int16_t); return samples_read * sizeof(int16_t);

View file

@ -233,6 +233,7 @@ void I2SAudioSpeaker::loop() {
switch (this->state_) { switch (this->state_) {
case speaker::STATE_STARTING: case speaker::STATE_STARTING:
this->start_(); this->start_();
[[fallthrough]];
case speaker::STATE_RUNNING: case speaker::STATE_RUNNING:
case speaker::STATE_STOPPING: case speaker::STATE_STOPPING:
this->watch_(); this->watch_();

View file

@ -92,7 +92,9 @@ static const uint8_t ILI9XXX_GMCTRN1 = 0xE1;
static const uint8_t ILI9XXX_CSCON = 0xF0; static const uint8_t ILI9XXX_CSCON = 0xF0;
static const uint8_t ILI9XXX_ADJCTL3 = 0xF7; static const uint8_t ILI9XXX_ADJCTL3 = 0xF7;
static const uint8_t ILI9XXX_DELAY = 0xFF; // followed by one byte of delay time in ms static const uint8_t ILI9XXX_DELAY_FLAG = 0xFF;
// special marker for delay - command byte reprents ms, length byte is an impossible value
#define ILI9XXX_DELAY(ms) ((uint8_t) ((ms) | 0x80)), ILI9XXX_DELAY_FLAG
} // namespace ili9xxx } // namespace ili9xxx
} // namespace esphome } // namespace esphome

View file

@ -34,8 +34,8 @@ void ILI9XXXDisplay::setup() {
ESP_LOGD(TAG, "Setting up ILI9xxx"); ESP_LOGD(TAG, "Setting up ILI9xxx");
this->setup_pins_(); this->setup_pins_();
this->init_lcd(this->init_sequence_); this->init_lcd_(this->init_sequence_);
this->init_lcd(this->extra_init_sequence_.data()); this->init_lcd_(this->extra_init_sequence_.data());
switch (this->pixel_mode_) { switch (this->pixel_mode_) {
case PIXEL_MODE_16: case PIXEL_MODE_16:
if (this->is_18bitdisplay_) { if (this->is_18bitdisplay_) {
@ -405,42 +405,29 @@ void ILI9XXXDisplay::reset_() {
} }
} }
void ILI9XXXDisplay::init_lcd(const uint8_t *addr) { void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
if (addr == nullptr) if (addr == nullptr)
return; return;
uint8_t cmd, x, num_args; uint8_t cmd, x, num_args;
while ((cmd = *addr++) != 0) { while ((cmd = *addr++) != 0) {
x = *addr++; x = *addr++;
if (cmd == ILI9XXX_DELAY) { if (x == ILI9XXX_DELAY_FLAG) {
ESP_LOGD(TAG, "Delay %dms", x); cmd &= 0x7F;
delay(x); ESP_LOGV(TAG, "Delay %dms", cmd);
delay(cmd);
} else { } else {
num_args = x & 0x7F; num_args = x & 0x7F;
ESP_LOGD(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr); ESP_LOGV(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr);
this->send_command(cmd, addr, num_args); this->send_command(cmd, addr, num_args);
addr += num_args; addr += num_args;
if (x & 0x80) { if (x & 0x80) {
ESP_LOGD(TAG, "Delay 150ms"); ESP_LOGV(TAG, "Delay 150ms");
delay(150); // NOLINT delay(150); // NOLINT
} }
} }
} }
} }
void ILI9XXXGC9A01A::init_lcd(const uint8_t *addr) {
if (addr == nullptr)
return;
uint8_t cmd, x, num_args;
while ((cmd = *addr++) != 0) {
x = *addr++;
num_args = x & 0x7F;
this->send_command(cmd, addr, num_args);
addr += num_args;
if (x & 0x80)
delay(150); // NOLINT
}
}
// Tell the display controller where we want to draw pixels. // Tell the display controller where we want to draw pixels.
void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
x1 += this->offset_x_; x1 += this->offset_x_;

View file

@ -33,7 +33,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
uint8_t cmd, num_args, bits; uint8_t cmd, num_args, bits;
const uint8_t *addr = init_sequence; const uint8_t *addr = init_sequence;
while ((cmd = *addr++) != 0) { while ((cmd = *addr++) != 0) {
num_args = *addr++ & 0x7F; num_args = *addr++;
if (num_args == ILI9XXX_DELAY_FLAG)
continue;
bits = *addr; bits = *addr;
switch (cmd) { switch (cmd) {
case ILI9XXX_MADCTL: { case ILI9XXX_MADCTL: {
@ -50,13 +52,10 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
break; break;
} }
case ILI9XXX_DELAY:
continue; // no args to skip
default: default:
break; break;
} }
addr += num_args; addr += (num_args & 0x7F);
} }
} }
@ -109,7 +108,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
virtual void set_madctl(); virtual void set_madctl();
void display_(); void display_();
virtual void init_lcd(const uint8_t *addr); void init_lcd_(const uint8_t *addr);
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2); void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
void reset_(); void reset_();
@ -269,7 +268,6 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay {
class ILI9XXXGC9A01A : public ILI9XXXDisplay { class ILI9XXXGC9A01A : public ILI9XXXDisplay {
public: public:
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {} ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {}
void init_lcd(const uint8_t *addr) override;
}; };
//----------- ILI9XXX_24_TFT display -------------- //----------- ILI9XXX_24_TFT display --------------

View file

@ -372,9 +372,9 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
static const uint8_t PROGMEM INITCMD_ST7735[] = { static const uint8_t PROGMEM INITCMD_ST7735[] = {
ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms
ILI9XXX_DELAY, 10, ILI9XXX_DELAY(10),
ILI9XXX_SLPOUT , 0, // Exit Sleep, delay ILI9XXX_SLPOUT , 0, // Exit Sleep, delay
ILI9XXX_DELAY, 10, ILI9XXX_DELAY(10),
ILI9XXX_PIXFMT , 1, 0x05, ILI9XXX_PIXFMT , 1, 0x05,
ILI9XXX_FRMCTR1, 3, // 4: Frame rate control, 3 args + delay: ILI9XXX_FRMCTR1, 3, // 4: Frame rate control, 3 args + delay:
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) 0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
@ -415,9 +415,9 @@ static const uint8_t PROGMEM INITCMD_ST7735[] = {
0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x02, 0x10,
ILI9XXX_MADCTL , 1, 0x00, // Memory Access Control, BGR ILI9XXX_MADCTL , 1, 0x00, // Memory Access Control, BGR
ILI9XXX_NORON , 0, ILI9XXX_NORON , 0,
ILI9XXX_DELAY, 10, ILI9XXX_DELAY(10),
ILI9XXX_DISPON , 0, // Display on ILI9XXX_DISPON , 0, // Display on
ILI9XXX_DELAY, 10, ILI9XXX_DELAY(10),
00, // endo of list 00, // endo of list
}; };

View file

@ -1,8 +1,7 @@
import re import re
import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import __version__ from esphome.const import __version__
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@ -39,4 +38,4 @@ def _process_next_url(url: str):
async def setup_improv_core(var, config): async def setup_improv_core(var, config):
if CONF_NEXT_URL in config: if CONF_NEXT_URL in config:
cg.add(var.set_next_url(_process_next_url(config[CONF_NEXT_URL]))) 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,6 +1,6 @@
import esphome.config_validation as cv import esphome.config_validation as cv
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( CONFIG_SCHEMA = cv.invalid(
"The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n" "The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n"
"See https://esphome.io/components/sensor/combination.html" "See https://esphome.io/components/sensor/combination.html"
) )

View file

@ -1,8 +1,9 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.automation as auto import esphome.automation as auto
import esphome.codegen as cg
from esphome.components import mqtt, power_supply, web_server from esphome.components import mqtt, power_supply, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_COLD_WHITE_COLOR_TEMPERATURE,
CONF_COLOR_CORRECT, CONF_COLOR_CORRECT,
CONF_DEFAULT_TRANSITION_LENGTH, CONF_DEFAULT_TRANSITION_LENGTH,
CONF_EFFECTS, CONF_EFFECTS,
@ -10,36 +11,36 @@ from esphome.const import (
CONF_GAMMA_CORRECT, CONF_GAMMA_CORRECT,
CONF_ID, CONF_ID,
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_WEB_SERVER_ID, CONF_ON_STATE,
CONF_POWER_SUPPLY,
CONF_RESTORE_MODE,
CONF_ON_TURN_OFF, CONF_ON_TURN_OFF,
CONF_ON_TURN_ON, CONF_ON_TURN_ON,
CONF_ON_STATE, CONF_POWER_SUPPLY,
CONF_RESTORE_MODE,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_COLD_WHITE_COLOR_TEMPERATURE,
CONF_WARM_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE,
CONF_WEB_SERVER_ID,
) )
from esphome.core import coroutine_with_priority from esphome.core import coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
from .automation import light_control_to_code # noqa from .automation import light_control_to_code # noqa
from .effects import ( from .effects import (
validate_effects, ADDRESSABLE_EFFECTS,
BINARY_EFFECTS, BINARY_EFFECTS,
EFFECTS_REGISTRY,
MONOCHROMATIC_EFFECTS, MONOCHROMATIC_EFFECTS,
RGB_EFFECTS, RGB_EFFECTS,
ADDRESSABLE_EFFECTS, validate_effects,
EFFECTS_REGISTRY,
) )
from .types import ( # noqa from .types import ( # noqa
LightState,
AddressableLightState,
light_ns,
LightOutput,
AddressableLight, AddressableLight,
LightTurnOnTrigger, AddressableLightState,
LightTurnOffTrigger, LightOutput,
LightState,
LightStateTrigger, LightStateTrigger,
LightTurnOffTrigger,
LightTurnOnTrigger,
light_ns,
) )
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]

View file

@ -1,14 +1,14 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.automation import Condition, maybe_simple_id from esphome.automation import Condition, maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
CONF_MQTT_ID,
CONF_ON_LOCK, CONF_ON_LOCK,
CONF_ON_UNLOCK, CONF_ON_UNLOCK,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER_ID,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority

View file

@ -1,9 +1,21 @@
import re import re
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.automation import LambdaAction from esphome.automation import LambdaAction
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.components.libretiny import get_libretiny_component, get_libretiny_family
from esphome.components.libretiny.const import COMPONENT_BK72XX, COMPONENT_RTL87XX
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ARGS, CONF_ARGS,
CONF_BAUD_RATE, CONF_BAUD_RATE,
@ -18,27 +30,12 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TX_BUFFER_SIZE, CONF_TX_BUFFER_SIZE,
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_RTL87XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_RTL87XX,
) )
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32S2,
VARIANT_ESP32C3,
VARIANT_ESP32S3,
VARIANT_ESP32C2,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
)
from esphome.components.libretiny import get_libretiny_component, get_libretiny_family
from esphome.components.libretiny.const import (
COMPONENT_BK72XX,
COMPONENT_RTL87XX,
)
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
logger_ns = cg.esphome_ns.namespace("logger") logger_ns = cg.esphome_ns.namespace("logger")

View file

@ -0,0 +1,343 @@
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
from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
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
from esphome.cpp_generator import MockObj
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 disp_update, update_to_code
from .defines import CONF_SKIP
from .encoders import ENCODERS_CONFIG, encoders_to_code
from .lv_validation import lv_bool, lv_images_used
from .lvcode import LvContext, LvglComponent
from .schemas import (
DISP_BG_SCHEMA,
FLEX_OBJ_SCHEMA,
GRID_CELL_SCHEMA,
LAYOUT_SCHEMAS,
STYLE_SCHEMA,
WIDGET_TYPES,
any_widget_schema,
container_schema,
create_modify_schema,
grid_alignments,
obj_schema,
)
from .styles import add_top_layer, styles_to_code, theme_to_code
from .touchscreens import touchscreen_schema, touchscreens_to_code
from .trigger import generate_triggers
from .types import (
FontEngine,
IdleTrigger,
ObjUpdateAction,
lv_font_t,
lv_style_t,
lvgl_ns,
)
from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties
from .widgets.animimg import animimg_spec
from .widgets.arc import arc_spec
from .widgets.button import button_spec
from .widgets.buttonmatrix import buttonmatrix_spec
from .widgets.checkbox import checkbox_spec
from .widgets.dropdown import dropdown_spec
from .widgets.img import img_spec
from .widgets.keyboard import keyboard_spec
from .widgets.label import label_spec
from .widgets.led import led_spec
from .widgets.line import line_spec
from .widgets.lv_bar import bar_spec
from .widgets.meter import meter_spec
from .widgets.msgbox import MSGBOX_SCHEMA, msgboxes_to_code
from .widgets.obj import obj_spec
from .widgets.page import add_pages, page_spec
from .widgets.roller import roller_spec
from .widgets.slider import slider_spec
from .widgets.spinbox import spinbox_spec
from .widgets.spinner import spinner_spec
from .widgets.switch import switch_spec
from .widgets.tabview import tabview_spec
from .widgets.textarea import textarea_spec
from .widgets.tileview import tileview_spec
DOMAIN = "lvgl"
DEPENDENCIES = ["display"]
AUTO_LOAD = ["key_provider"]
CODEOWNERS = ["@clydebarrow"]
LOGGER = logging.getLogger(__name__)
for w_type in (
label_spec,
obj_spec,
button_spec,
bar_spec,
slider_spec,
arc_spec,
line_spec,
spinner_spec,
led_spec,
animimg_spec,
checkbox_spec,
img_spec,
switch_spec,
tabview_spec,
buttonmatrix_spec,
meter_spec,
dropdown_spec,
roller_spec,
textarea_spec,
spinbox_spec,
keyboard_spec,
tileview_spec,
):
WIDGET_TYPES[w_type.name] = w_type
WIDGET_SCHEMA = any_widget_schema()
LAYOUT_SCHEMAS[df.TYPE_GRID] = {
cv.Optional(df.CONF_WIDGETS): cv.ensure_list(any_widget_schema(GRID_CELL_SCHEMA))
}
LAYOUT_SCHEMAS[df.TYPE_FLEX] = {
cv.Optional(df.CONF_WIDGETS): cv.ensure_list(any_widget_schema(FLEX_OBJ_SCHEMA))
}
LAYOUT_SCHEMAS[df.TYPE_NONE] = {
cv.Optional(df.CONF_WIDGETS): cv.ensure_list(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)
lv_defines = {} # Dict of #defines to provide as build flags
def add_define(macro, value="1"):
if macro in lv_defines and lv_defines[macro] != value:
LOGGER.error(
"Redefinition of %s - was %s now %s", macro, lv_defines[macro], value
)
lv_defines[macro] = value
def as_macro(macro, value):
if value is None:
return f"#define {macro}"
return f"#define {macro} {value}"
LV_CONF_FILENAME = "lv_conf.h"
LV_CONF_H_FORMAT = """\
#pragma once
{}
"""
def generate_lv_conf_h():
definitions = [as_macro(m, v) for m, v in lv_defines.items()]
definitions.sort()
return LV_CONF_H_FORMAT.format("\n".join(definitions))
def final_validation(config):
if pages := config.get(CONF_PAGES):
if all(p[CONF_SKIP] for p in pages):
raise cv.Invalid("At least one page must not be skipped")
global_config = full_config.get()
for display_id in config[df.CONF_DISPLAYS]:
path = global_config.get_path_for_id(display_id)[:-1]
display = global_config.get_config_for_path(path)
if CONF_LAMBDA in display:
raise cv.Invalid("Using lambda: in display config not compatible with LVGL")
if display[CONF_AUTO_CLEAR_ENABLED]:
raise cv.Invalid(
"Using auto_clear_enabled: true in display config not compatible with LVGL"
)
buffer_frac = config[CONF_BUFFER_SIZE]
if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config:
LOGGER.warning("buffer_size: may need to be reduced without PSRAM")
for image_id in lv_images_used:
path = global_config.get_path_for_id(image_id)[:-1]
image_conf = global_config.get_config_for_path(path)
if image_conf[CONF_TYPE] in ("RGBA", "RGB24"):
raise cv.Invalid(
"Using RGBA or RGB24 in image config not compatible with LVGL", path
)
async def to_code(config):
cg.add_library("lvgl/lvgl", "8.4.0")
CORE.add_define("USE_LVGL")
# suppress default enabling of extra widgets
add_define("_LV_KCONFIG_PRESENT")
# Always enable - lots of things use it.
add_define("LV_DRAW_COMPLEX", "1")
add_define("LV_TICK_CUSTOM", "1")
add_define("LV_TICK_CUSTOM_INCLUDE", '"esphome/components/lvgl/lvgl_hal.h"')
add_define("LV_TICK_CUSTOM_SYS_TIME_EXPR", "(lv_millis())")
add_define("LV_MEM_CUSTOM", "1")
add_define("LV_MEM_CUSTOM_ALLOC", "lv_custom_mem_alloc")
add_define("LV_MEM_CUSTOM_FREE", "lv_custom_mem_free")
add_define("LV_MEM_CUSTOM_REALLOC", "lv_custom_mem_realloc")
add_define("LV_MEM_CUSTOM_INCLUDE", '"esphome/components/lvgl/lvgl_hal.h"')
add_define("LV_LOG_LEVEL", f"LV_LOG_LEVEL_{config[df.CONF_LOG_LEVEL]}")
add_define("LV_COLOR_DEPTH", config[df.CONF_COLOR_DEPTH])
for font in helpers.lv_fonts_used:
add_define(f"LV_FONT_{font.upper()}")
if config[df.CONF_COLOR_DEPTH] == 16:
add_define(
"LV_COLOR_16_SWAP",
"1" if config[df.CONF_BYTE_ORDER] == "big_endian" else "0",
)
add_define(
"LV_COLOR_CHROMA_KEY",
await lvalid.lv_color.process(config[df.CONF_TRANSPARENCY_KEY]),
)
CORE.add_build_flag("-Isrc")
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, obj_spec, config)
for display in config[df.CONF_DISPLAYS]:
cg.add(lv_component.add_display(await cg.get_variable(display)))
frac = config[CONF_BUFFER_SIZE]
if frac >= 0.75:
frac = 1
elif frac >= 0.375:
frac = 2
elif frac > 0.19:
frac = 4
else:
frac = 8
cg.add(lv_component.set_buffer_frac(int(frac)))
cg.add(lv_component.set_full_refresh(config[df.CONF_FULL_REFRESH]))
for font in helpers.esphome_fonts_used:
await cg.get_variable(font)
cg.new_Pvariable(ID(f"{font}_engine", True, type=FontEngine), MockObj(font))
default_font = config[df.CONF_DEFAULT_FONT]
if not lvalid.is_lv_font(default_font):
add_define(
"LV_FONT_CUSTOM_DECLARE", f"LV_FONT_DECLARE(*{df.DEFAULT_ESPHOME_FONT})"
)
globfont_id = ID(
df.DEFAULT_ESPHOME_FONT,
True,
type=lv_font_t.operator("ptr").operator("const"),
)
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", await lvalid.lv_font.process(default_font))
async with LvContext(lv_component):
await touchscreens_to_code(lv_component, config)
await encoders_to_code(lv_component, config)
await theme_to_code(config)
await styles_to_code(config)
await set_obj_properties(lv_scr_act, config)
await add_widgets(lv_scr_act, config)
await add_pages(lv_component, config)
await add_top_layer(config)
await msgboxes_to_code(config)
await disp_update(f"{lv_component}->get_disp()", 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)
for comp in helpers.lvgl_components_required:
CORE.add_define(f"USE_LVGL_{comp.upper()}")
for use in helpers.lv_uses:
add_define(f"LV_USE_{use.upper()}")
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
write_file_if_changed(lv_conf_h_file, generate_lv_conf_h())
CORE.add_build_flag("-DLV_CONF_H=1")
CORE.add_build_flag(f'-DLV_CONF_PATH="{LV_CONF_FILENAME}"')
def display_schema(config):
value = cv.ensure_list(cv.use_id(Display))(config)
return value or [cv.use_id(Display)(config)]
FINAL_VALIDATE_SCHEMA = final_validation
CONFIG_SCHEMA = (
cv.polling_component_schema("1s")
.extend(obj_schema(obj_spec))
.extend(
{
cv.GenerateID(CONF_ID): cv.declare_id(LvglComponent),
cv.GenerateID(df.CONF_DISPLAYS): display_schema,
cv.Optional(df.CONF_COLOR_DEPTH, default=16): cv.one_of(16),
cv.Optional(df.CONF_DEFAULT_FONT, default="montserrat_14"): lvalid.lv_font,
cv.Optional(df.CONF_FULL_REFRESH, default=False): cv.boolean,
cv.Optional(CONF_BUFFER_SIZE, default="100%"): cv.percentage,
cv.Optional(df.CONF_LOG_LEVEL, default="WARN"): cv.one_of(
*df.LOG_LEVELS, upper=True
),
cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of(
"big_endian", "little_endian"
),
cv.Optional(df.CONF_STYLE_DEFINITIONS): cv.ensure_list(
cv.Schema({cv.Required(CONF_ID): cv.declare_id(lv_style_t)})
.extend(STYLE_SCHEMA)
.extend(
{
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
cv.Optional(df.CONF_GRID_CELL_Y_ALIGN): grid_alignments,
}
)
),
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.Exclusive(df.CONF_WIDGETS, CONF_PAGES): cv.ensure_list(WIDGET_SCHEMA),
cv.Exclusive(CONF_PAGES, CONF_PAGES): cv.ensure_list(
container_schema(page_spec)
),
cv.Optional(df.CONF_MSGBOXES): cv.ensure_list(MSGBOX_SCHEMA),
cv.Optional(df.CONF_PAGE_WRAP, default=True): lv_bool,
cv.Optional(df.CONF_TOP_LAYER): container_schema(obj_spec),
cv.Optional(df.CONF_TRANSPARENCY_KEY, default=0x000400): lvalid.lv_color,
cv.Optional(df.CONF_THEME): cv.Schema(
{cv.Optional(name): obj_schema(w) for name, w in WIDGET_TYPES.items()}
),
cv.GenerateID(df.CONF_TOUCHSCREENS): touchscreen_schema,
cv.GenerateID(df.CONF_ENCODERS): ENCODERS_CONFIG,
}
)
.extend(DISP_BG_SCHEMA)
).add_extra(cv.has_at_least_one_key(CONF_PAGES, df.CONF_WIDGETS))

View file

@ -0,0 +1,226 @@
from collections.abc import Awaitable
from typing import Callable
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.cpp_types import nullptr
from .defines import (
CONF_DISP_BG_COLOR,
CONF_DISP_BG_IMAGE,
CONF_LVGL_ID,
CONF_SHOW_SNOW,
literal,
)
from .lv_validation import lv_bool, lv_color, lv_image
from .lvcode import (
LVGL_COMP_ARG,
LambdaContext,
LocalVariable,
LvConditional,
LvglComponent,
ReturnStatement,
add_line_marks,
lv,
lv_add,
lv_obj,
lvgl_comp,
)
from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA
from .types import (
LV_EVENT,
LV_STATE,
LvglAction,
LvglCondition,
ObjUpdateAction,
lv_disp_t,
lv_obj_t,
)
from .widgets import Widget, get_widgets, lv_scr_act, set_obj_properties
async def action_to_code(
widgets: list[Widget],
action: Callable[[Widget], Awaitable[None]],
action_id,
template_arg,
args,
):
async with LambdaContext(parameters=args, where=action_id) as context:
for widget in widgets:
with LvConditional(widget.obj != nullptr):
await action(widget)
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
return var
async def update_to_code(config, action_id, template_arg, args):
async def do_update(widget: Widget):
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, LV_EVENT.VALUE_CHANGED, nullptr)
widgets = await get_widgets(config[CONF_ID])
return await action_to_code(widgets, do_update, action_id, 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]
async with LambdaContext(LVGL_COMP_ARG, 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)
async with LambdaContext(LVGL_COMP_ARG, 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
async def disp_update(disp, config: dict):
if CONF_DISP_BG_COLOR not in config and CONF_DISP_BG_IMAGE not in config:
return
with LocalVariable("lv_disp_tmp", lv_disp_t, literal(disp)) as disp_temp:
if (bg_color := config.get(CONF_DISP_BG_COLOR)) is not None:
lv.disp_set_bg_color(disp_temp, await lv_color.process(bg_color))
if bg_image := config.get(CONF_DISP_BG_IMAGE):
lv.disp_set_bg_image(disp_temp, await lv_image.process(bg_image))
@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):
widgets = await get_widgets(config) or [lv_scr_act]
async def do_invalidate(widget: Widget):
lv_obj.invalidate(widget.obj)
return await action_to_code(widgets, do_invalidate, action_id, template_arg, args)
@automation.register_action(
"lvgl.update",
LvglAction,
DISP_BG_SCHEMA.extend(
{
cv.GenerateID(): cv.use_id(LvglComponent),
}
).add_extra(cv.has_at_least_one_key(CONF_DISP_BG_COLOR, CONF_DISP_BG_IMAGE)),
)
async def lvgl_update_to_code(config, action_id, template_arg, args):
widgets = await get_widgets(config)
w = widgets[0]
disp = f"{w.obj}->get_disp()"
async with LambdaContext(parameters=args, where=action_id) as context:
await disp_update(disp, config)
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
await cg.register_parented(var, w.var)
return var
@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):
async with LambdaContext(LVGL_COMP_ARG) as context:
add_line_marks(where=action_id)
lv_add(lvgl_comp.set_paused(True, config[CONF_SHOW_SNOW]))
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
await cg.register_parented(var, config[CONF_ID])
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):
async with LambdaContext(LVGL_COMP_ARG, where=action_id) as context:
lv_add(lvgl_comp.set_paused(False, False))
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action("lvgl.widget.disable", ObjUpdateAction, LIST_ACTION_SCHEMA)
async def obj_disable_to_code(config, action_id, template_arg, args):
async def do_disable(widget: Widget):
widget.add_state(LV_STATE.DISABLED)
return await action_to_code(
await get_widgets(config), do_disable, action_id, template_arg, args
)
@automation.register_action("lvgl.widget.enable", ObjUpdateAction, LIST_ACTION_SCHEMA)
async def obj_enable_to_code(config, action_id, template_arg, args):
async def do_enable(widget: Widget):
widget.clear_state(LV_STATE.DISABLED)
return await action_to_code(
await get_widgets(config), do_enable, action_id, template_arg, args
)
@automation.register_action("lvgl.widget.hide", ObjUpdateAction, LIST_ACTION_SCHEMA)
async def obj_hide_to_code(config, action_id, template_arg, args):
async def do_hide(widget: Widget):
widget.add_flag("LV_OBJ_FLAG_HIDDEN")
return await action_to_code(
await get_widgets(config), do_hide, action_id, template_arg, args
)
@automation.register_action("lvgl.widget.show", ObjUpdateAction, LIST_ACTION_SCHEMA)
async def obj_show_to_code(config, action_id, template_arg, args):
async def do_show(widget: Widget):
widget.clear_flag("LV_OBJ_FLAG_HIDDEN")
return await action_to_code(
await get_widgets(config), do_show, action_id, template_arg, args
)

View file

@ -0,0 +1,43 @@
import esphome.codegen as cg
from esphome.components.binary_sensor import (
BinarySensor,
binary_sensor_schema,
new_binary_sensor,
)
import esphome.config_validation as cv
from ..defines import CONF_LVGL_ID, CONF_WIDGET
from ..lvcode import EVENT_ARG, LambdaContext, LvContext
from ..schemas import LVGL_SCHEMA
from ..types import LV_EVENT, lv_pseudo_button_t
from ..widgets import Widget, get_widgets
CONFIG_SCHEMA = (
binary_sensor_schema(BinarySensor)
.extend(LVGL_SCHEMA)
.extend(
{
cv.Required(CONF_WIDGET): cv.use_id(lv_pseudo_button_t),
}
)
)
async def to_code(config):
sensor = await new_binary_sensor(config)
paren = await cg.get_variable(config[CONF_LVGL_ID])
widget = await get_widgets(config, CONF_WIDGET)
widget = widget[0]
assert isinstance(widget, Widget)
async with LambdaContext(EVENT_ARG) as pressed_ctx:
pressed_ctx.add(sensor.publish_state(widget.is_pressed()))
async with LvContext(paren) as ctx:
ctx.add(sensor.publish_initial_state(widget.is_pressed()))
ctx.add(
paren.add_event_cb(
widget.obj,
await pressed_ctx.get_lambda(),
LV_EVENT.PRESSING,
LV_EVENT.RELEASED,
)
)

View file

@ -0,0 +1,501 @@
"""
This is the base of the import tree for LVGL. It contains constant definitions used elsewhere.
Constants already defined in esphome.const are not duplicated here and must be imported where used.
"""
from esphome import codegen as cg, config_validation as cv
from esphome.const import CONF_ITEMS
from esphome.core import ID, Lambda
from esphome.cpp_generator import MockObj
from esphome.cpp_types import uint32
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from .helpers import requires_component
lvgl_ns = cg.esphome_ns.namespace("lvgl")
def literal(arg):
if isinstance(arg, str):
return MockObj(arg)
return arg
class LValidator:
"""
A validator for a particular type used in LVGL. Usable in configs as a validator, also
has `process()` to convert a value during code generation
"""
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):
return cv.use_id(self.idtype)(value)
return self.validator(value)
async def process(self, value, args=()):
if value is None:
return None
if isinstance(value, Lambda):
return cg.RawExpression(
f"{await cg.process_lambda(value, args, return_type=self.rtype)}()"
)
if self.idtype is not None and isinstance(value, ID):
return cg.RawExpression(f"{value}->{self.idexpr}")
if self.retmapper is not None:
return self.retmapper(value)
return cg.safe_exp(value)
class LvConstant(LValidator):
"""
Allow one of a list of choices, mapped to upper case, and prepend the choice with the prefix.
It's also permitted to include the prefix in the value
The property `one_of` has the single case validator, and `several_of` allows a list of constants.
"""
def __init__(self, prefix: str, *choices):
self.prefix = prefix
self.choices = choices
prefixed_choices = [prefix + v for v in choices]
prefixed_validator = cv.one_of(*prefixed_choices, upper=True)
@schema_extractor("one_of")
def validator(value):
if value == SCHEMA_EXTRACT:
return self.choices
if isinstance(value, str) and value.startswith(self.prefix):
return prefixed_validator(value)
return self.prefix + cv.one_of(*choices, upper=True)(value)
super().__init__(validator, rtype=uint32)
self.retmapper = self.mapper
self.one_of = LValidator(validator, uint32, retmapper=self.mapper)
self.several_of = LValidator(
cv.ensure_list(self.one_of), uint32, retmapper=self.mapper
)
def mapper(self, value, args=()):
if not isinstance(value, list):
value = [value]
return literal(
"|".join(
[
str(v) if str(v).startswith(self.prefix) else self.prefix + str(v)
for v in value
]
).upper()
)
def extend(self, *choices):
"""
Extend an LVCconstant with additional choices.
:param choices: The extra choices
:return: A new LVConstant instance
"""
return LvConstant(self.prefix, *(self.choices + choices))
# Parts
CONF_MAIN = "main"
CONF_SCROLLBAR = "scrollbar"
CONF_INDICATOR = "indicator"
CONF_KNOB = "knob"
CONF_SELECTED = "selected"
CONF_TICKS = "ticks"
CONF_CURSOR = "cursor"
CONF_TEXTAREA_PLACEHOLDER = "textarea_placeholder"
# Layout types
TYPE_FLEX = "flex"
TYPE_GRID = "grid"
TYPE_NONE = "none"
LV_FONTS = list(f"montserrat_{s}" for s in range(8, 50, 2)) + [
"dejavu_16_persian_hebrew",
"simsun_16_cjk",
"unscii_8",
"unscii_16",
]
LV_EVENT_MAP = {
"PRESS": "PRESSED",
"SHORT_CLICK": "SHORT_CLICKED",
"LONG_PRESS": "LONG_PRESSED",
"LONG_PRESS_REPEAT": "LONG_PRESSED_REPEAT",
"CLICK": "CLICKED",
"RELEASE": "RELEASED",
"SCROLL_BEGIN": "SCROLL_BEGIN",
"SCROLL_END": "SCROLL_END",
"SCROLL": "SCROLL",
"FOCUS": "FOCUSED",
"DEFOCUS": "DEFOCUSED",
"READY": "READY",
"CANCEL": "CANCEL",
}
LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP)
LV_ANIM = LvConstant(
"LV_SCR_LOAD_ANIM_",
"NONE",
"OVER_LEFT",
"OVER_RIGHT",
"OVER_TOP",
"OVER_BOTTOM",
"MOVE_LEFT",
"MOVE_RIGHT",
"MOVE_TOP",
"MOVE_BOTTOM",
"FADE_IN",
"FADE_OUT",
"OUT_LEFT",
"OUT_RIGHT",
"OUT_TOP",
"OUT_BOTTOM",
)
LOG_LEVELS = (
"TRACE",
"INFO",
"WARN",
"ERROR",
"USER",
"NONE",
)
LV_LONG_MODES = LvConstant(
"LV_LABEL_LONG_",
"WRAP",
"DOT",
"SCROLL",
"SCROLL_CIRCULAR",
"CLIP",
)
STATES = (
"default",
"checked",
"focused",
"focus_key",
"edited",
"hovered",
"pressed",
"scrolled",
"disabled",
"user_1",
"user_2",
"user_3",
"user_4",
)
PARTS = (
CONF_MAIN,
CONF_SCROLLBAR,
CONF_INDICATOR,
CONF_KNOB,
CONF_SELECTED,
CONF_ITEMS,
CONF_TICKS,
CONF_CURSOR,
CONF_TEXTAREA_PLACEHOLDER,
)
KEYBOARD_MODES = LvConstant(
"LV_KEYBOARD_MODE_",
"TEXT_LOWER",
"TEXT_UPPER",
"SPECIAL",
"NUMBER",
)
ROLLER_MODES = LvConstant("LV_ROLLER_MODE_", "NORMAL", "INFINITE")
DIRECTIONS = LvConstant("LV_DIR_", "LEFT", "RIGHT", "BOTTOM", "TOP")
TILE_DIRECTIONS = DIRECTIONS.extend("HOR", "VER", "ALL")
CHILD_ALIGNMENTS = LvConstant(
"LV_ALIGN_",
"TOP_LEFT",
"TOP_MID",
"TOP_RIGHT",
"LEFT_MID",
"CENTER",
"RIGHT_MID",
"BOTTOM_LEFT",
"BOTTOM_MID",
"BOTTOM_RIGHT",
)
SIBLING_ALIGNMENTS = LvConstant(
"LV_ALIGN_",
"OUT_LEFT_TOP",
"OUT_TOP_LEFT",
"OUT_TOP_MID",
"OUT_TOP_RIGHT",
"OUT_RIGHT_TOP",
"OUT_LEFT_MID",
"OUT_RIGHT_MID",
"OUT_LEFT_BOTTOM",
"OUT_BOTTOM_LEFT",
"OUT_BOTTOM_MID",
"OUT_BOTTOM_RIGHT",
"OUT_RIGHT_BOTTOM",
)
ALIGN_ALIGNMENTS = CHILD_ALIGNMENTS.extend(*SIBLING_ALIGNMENTS.choices)
FLEX_FLOWS = LvConstant(
"LV_FLEX_FLOW_",
"ROW",
"COLUMN",
"ROW_WRAP",
"COLUMN_WRAP",
"ROW_REVERSE",
"COLUMN_REVERSE",
"ROW_WRAP_REVERSE",
"COLUMN_WRAP_REVERSE",
)
OBJ_FLAGS = (
"hidden",
"clickable",
"click_focusable",
"checkable",
"scrollable",
"scroll_elastic",
"scroll_momentum",
"scroll_one",
"scroll_chain_hor",
"scroll_chain_ver",
"scroll_chain",
"scroll_on_focus",
"scroll_with_arrow",
"snappable",
"press_lock",
"event_bubble",
"gesture_bubble",
"adv_hittest",
"ignore_layout",
"floating",
"overflow_visible",
"layout_1",
"layout_2",
"widget_1",
"widget_2",
"user_1",
"user_2",
"user_3",
"user_4",
)
ARC_MODES = LvConstant("LV_ARC_MODE_", "NORMAL", "REVERSE", "SYMMETRICAL")
BAR_MODES = LvConstant("LV_BAR_MODE_", "NORMAL", "SYMMETRICAL", "RANGE")
BUTTONMATRIX_CTRLS = LvConstant(
"LV_BTNMATRIX_CTRL_",
"HIDDEN",
"NO_REPEAT",
"DISABLED",
"CHECKABLE",
"CHECKED",
"CLICK_TRIG",
"POPOVER",
"RECOLOR",
"CUSTOM_1",
"CUSTOM_2",
)
LV_BASE_ALIGNMENTS = (
"START",
"CENTER",
"END",
)
LV_CELL_ALIGNMENTS = LvConstant(
"LV_GRID_ALIGN_",
*LV_BASE_ALIGNMENTS,
)
LV_GRID_ALIGNMENTS = LV_CELL_ALIGNMENTS.extend(
"STRETCH",
"SPACE_EVENLY",
"SPACE_AROUND",
"SPACE_BETWEEN",
)
LV_FLEX_ALIGNMENTS = LvConstant(
"LV_FLEX_ALIGN_",
*LV_BASE_ALIGNMENTS,
"SPACE_EVENLY",
"SPACE_AROUND",
"SPACE_BETWEEN",
)
LV_MENU_MODES = LvConstant(
"LV_MENU_HEADER_",
"TOP_FIXED",
"TOP_UNFIXED",
"BOTTOM_FIXED",
)
LV_CHART_TYPES = (
"NONE",
"LINE",
"BAR",
"SCATTER",
)
LV_CHART_AXES = (
"PRIMARY_Y",
"SECONDARY_Y",
"PRIMARY_X",
"SECONDARY_X",
)
CONF_ACCEPTED_CHARS = "accepted_chars"
CONF_ADJUSTABLE = "adjustable"
CONF_ALIGN = "align"
CONF_ALIGN_TO = "align_to"
CONF_ANIMATED = "animated"
CONF_ANIMATION = "animation"
CONF_ANTIALIAS = "antialias"
CONF_ARC_LENGTH = "arc_length"
CONF_AUTO_START = "auto_start"
CONF_BACKGROUND_STYLE = "background_style"
CONF_DECIMAL_PLACES = "decimal_places"
CONF_COLUMN = "column"
CONF_DIGITS = "digits"
CONF_DISP_BG_COLOR = "disp_bg_color"
CONF_DISP_BG_IMAGE = "disp_bg_image"
CONF_BODY = "body"
CONF_BUTTONS = "buttons"
CONF_BYTE_ORDER = "byte_order"
CONF_CHANGE_RATE = "change_rate"
CONF_CLOSE_BUTTON = "close_button"
CONF_COLOR_DEPTH = "color_depth"
CONF_CONTROL = "control"
CONF_DEFAULT = "default"
CONF_DEFAULT_FONT = "default_font"
CONF_DIR = "dir"
CONF_DISPLAYS = "displays"
CONF_ENCODERS = "encoders"
CONF_END_ANGLE = "end_angle"
CONF_END_VALUE = "end_value"
CONF_ENTER_BUTTON = "enter_button"
CONF_ENTRIES = "entries"
CONF_FLAGS = "flags"
CONF_FLEX_FLOW = "flex_flow"
CONF_FLEX_ALIGN_MAIN = "flex_align_main"
CONF_FLEX_ALIGN_CROSS = "flex_align_cross"
CONF_FLEX_ALIGN_TRACK = "flex_align_track"
CONF_FLEX_GROW = "flex_grow"
CONF_FULL_REFRESH = "full_refresh"
CONF_GRID_CELL_ROW_POS = "grid_cell_row_pos"
CONF_GRID_CELL_COLUMN_POS = "grid_cell_column_pos"
CONF_GRID_CELL_ROW_SPAN = "grid_cell_row_span"
CONF_GRID_CELL_COLUMN_SPAN = "grid_cell_column_span"
CONF_GRID_CELL_X_ALIGN = "grid_cell_x_align"
CONF_GRID_CELL_Y_ALIGN = "grid_cell_y_align"
CONF_GRID_COLUMN_ALIGN = "grid_column_align"
CONF_GRID_COLUMNS = "grid_columns"
CONF_GRID_ROW_ALIGN = "grid_row_align"
CONF_GRID_ROWS = "grid_rows"
CONF_HEADER_MODE = "header_mode"
CONF_HOME = "home"
CONF_KEY_CODE = "key_code"
CONF_LAYOUT = "layout"
CONF_LEFT_BUTTON = "left_button"
CONF_LINE_WIDTH = "line_width"
CONF_LOG_LEVEL = "log_level"
CONF_LONG_PRESS_TIME = "long_press_time"
CONF_LONG_PRESS_REPEAT_TIME = "long_press_repeat_time"
CONF_LVGL_ID = "lvgl_id"
CONF_LONG_MODE = "long_mode"
CONF_MSGBOXES = "msgboxes"
CONF_OBJ = "obj"
CONF_OFFSET_X = "offset_x"
CONF_OFFSET_Y = "offset_y"
CONF_ONE_LINE = "one_line"
CONF_ON_SELECT = "on_select"
CONF_ONE_CHECKED = "one_checked"
CONF_NEXT = "next"
CONF_PAGE = "page"
CONF_PAGE_WRAP = "page_wrap"
CONF_PASSWORD_MODE = "password_mode"
CONF_PIVOT_X = "pivot_x"
CONF_PIVOT_Y = "pivot_y"
CONF_PLACEHOLDER_TEXT = "placeholder_text"
CONF_POINTS = "points"
CONF_PREVIOUS = "previous"
CONF_REPEAT_COUNT = "repeat_count"
CONF_RECOLOR = "recolor"
CONF_RIGHT_BUTTON = "right_button"
CONF_ROLLOVER = "rollover"
CONF_ROOT_BACK_BTN = "root_back_btn"
CONF_ROWS = "rows"
CONF_SCALE_LINES = "scale_lines"
CONF_SCROLLBAR_MODE = "scrollbar_mode"
CONF_SELECTED_INDEX = "selected_index"
CONF_SHOW_SNOW = "show_snow"
CONF_SPIN_TIME = "spin_time"
CONF_SRC = "src"
CONF_START_ANGLE = "start_angle"
CONF_START_VALUE = "start_value"
CONF_STATES = "states"
CONF_STYLE = "style"
CONF_STYLES = "styles"
CONF_STYLE_DEFINITIONS = "style_definitions"
CONF_STYLE_ID = "style_id"
CONF_SKIP = "skip"
CONF_SYMBOL = "symbol"
CONF_TAB_ID = "tab_id"
CONF_TABS = "tabs"
CONF_TILE = "tile"
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"
CONF_WIDGET = "widget"
CONF_WIDGETS = "widgets"
CONF_X = "x"
CONF_Y = "y"
CONF_ZOOM = "zoom"
# Keypad keys
LV_KEYS = LvConstant(
"LV_KEY_",
"UP",
"DOWN",
"RIGHT",
"LEFT",
"ESC",
"DEL",
"BACKSPACE",
"ENTER",
"NEXT",
"PREV",
"HOME",
"END",
)
DEFAULT_ESPHOME_FONT = "esphome_lv_default_font"
def join_enums(enums, prefix=""):
return literal("|".join(f"(int){prefix}{e.upper()}" for e in enums))

View file

@ -0,0 +1,65 @@
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_ENCODERS,
CONF_ENTER_BUTTON,
CONF_LEFT_BUTTON,
CONF_LONG_PRESS_REPEAT_TIME,
CONF_LONG_PRESS_TIME,
CONF_RIGHT_BUTTON,
)
from .helpers import lvgl_components_required, requires_component
from .lvcode import lv, lv_add, lv_assign, lv_expr, lv_Pvariable
from .schemas import ENCODER_SCHEMA
from .types import lv_group_t, lv_indev_type_t
ENCODERS_CONFIG = cv.ensure_list(
ENCODER_SCHEMA.extend(
{
cv.Required(CONF_ENTER_BUTTON): cv.use_id(BinarySensor),
cv.Required(CONF_SENSOR): cv.Any(
cv.All(
cv.use_id(RotaryEncoderSensor), requires_component("rotary_encoder")
),
cv.Schema(
{
cv.Required(CONF_LEFT_BUTTON): cv.use_id(BinarySensor),
cv.Required(CONF_RIGHT_BUTTON): cv.use_id(BinarySensor),
}
),
),
}
)
)
async def encoders_to_code(var, config):
for enc_conf in config.get(CONF_ENCODERS, ()):
lvgl_components_required.add("KEY_LISTENER")
lpt = enc_conf[CONF_LONG_PRESS_TIME].total_milliseconds
lprt = enc_conf[CONF_LONG_PRESS_REPEAT_TIME].total_milliseconds
listener = cg.new_Pvariable(
enc_conf[CONF_ID], lv_indev_type_t.LV_INDEV_TYPE_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 := enc_conf.get(CONF_GROUP):
group = lv_Pvariable(lv_group_t, group)
lv_assign(group, lv_expr.group_create())
lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group)
else:
lv.indev_drv_register(listener.get_drv())

View file

@ -0,0 +1,76 @@
#include "lvgl_esphome.h"
#ifdef USE_LVGL_FONT
namespace esphome {
namespace lvgl {
static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) {
auto *fe = (FontEngine *) font->dsc;
const auto *gd = fe->get_glyph_data(unicode_letter);
if (gd == nullptr)
return nullptr;
// esph_log_d(TAG, "Returning bitmap @ %X", (uint32_t)gd->data);
return gd->data;
}
static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
auto *fe = (FontEngine *) font->dsc;
const auto *gd = fe->get_glyph_data(unicode_letter);
if (gd == nullptr)
return false;
dsc->adv_w = gd->offset_x + gd->width;
dsc->ofs_x = gd->offset_x;
dsc->ofs_y = fe->height - gd->height - gd->offset_y - fe->baseline;
dsc->box_w = gd->width;
dsc->box_h = gd->height;
dsc->is_placeholder = 0;
dsc->bpp = fe->bpp;
return true;
}
FontEngine::FontEngine(font::Font *esp_font) : font_(esp_font) {
this->bpp = esp_font->get_bpp();
this->lv_font_.dsc = this;
this->lv_font_.line_height = this->height = esp_font->get_height();
this->lv_font_.base_line = this->baseline = this->lv_font_.line_height - esp_font->get_baseline();
this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb;
this->lv_font_.get_glyph_bitmap = get_glyph_bitmap;
this->lv_font_.subpx = LV_FONT_SUBPX_NONE;
this->lv_font_.underline_position = -1;
this->lv_font_.underline_thickness = 1;
}
const lv_font_t *FontEngine::get_lv_font() { return &this->lv_font_; }
const font::GlyphData *FontEngine::get_glyph_data(uint32_t unicode_letter) {
if (unicode_letter == last_letter_)
return this->last_data_;
uint8_t unicode[5];
memset(unicode, 0, sizeof unicode);
if (unicode_letter > 0xFFFF) {
unicode[0] = 0xF0 + ((unicode_letter >> 18) & 0x7);
unicode[1] = 0x80 + ((unicode_letter >> 12) & 0x3F);
unicode[2] = 0x80 + ((unicode_letter >> 6) & 0x3F);
unicode[3] = 0x80 + (unicode_letter & 0x3F);
} else if (unicode_letter > 0x7FF) {
unicode[0] = 0xE0 + ((unicode_letter >> 12) & 0xF);
unicode[1] = 0x80 + ((unicode_letter >> 6) & 0x3F);
unicode[2] = 0x80 + (unicode_letter & 0x3F);
} else if (unicode_letter > 0x7F) {
unicode[0] = 0xC0 + ((unicode_letter >> 6) & 0x1F);
unicode[1] = 0x80 + (unicode_letter & 0x3F);
} else {
unicode[0] = unicode_letter;
}
int match_length;
int glyph_n = this->font_->match_next_glyph(unicode, &match_length);
if (glyph_n < 0)
return nullptr;
this->last_data_ = this->font_->get_glyphs()[glyph_n].get_glyph_data();
this->last_letter_ = unicode_letter;
return this->last_data_;
}
} // namespace lvgl
} // namespace esphome
#endif // USES_LVGL_FONT

View file

@ -0,0 +1,49 @@
import re
from esphome import config_validation as cv
from esphome.const import CONF_ARGS, CONF_FORMAT
lv_uses = {
"USER_DATA",
"LOG",
"STYLE",
"FONT_PLACEHOLDER",
"THEME_DEFAULT",
}
def add_lv_use(*names):
for name in names:
lv_uses.add(name)
lv_fonts_used = set()
esphome_fonts_used = set()
lvgl_components_required = set()
def validate_printf(value):
cfmt = r"""
( # start of capture group 1
% # literal "%"
(?:[-+0 #]{0,5}) # optional flags
(?:\d+|\*)? # width
(?:\.(?:\d+|\*))? # precision
(?:h|l|ll|w|I|I32|I64)? # size
[cCdiouxXeEfgGaAnpsSZ] # type
)
""" # noqa
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X)
if len(matches) != len(value[CONF_ARGS]):
raise cv.Invalid(
f"Found {len(matches)} printf-patterns ({', '.join(matches)}), but {len(value[CONF_ARGS])} args were given!"
)
return value
def requires_component(comp):
def validator(value):
lvgl_components_required.add(comp)
return cv.requires_component(comp)(value)
return validator

View file

@ -0,0 +1,32 @@
import esphome.codegen as cg
from esphome.components import light
from esphome.components.light import LightOutput
import esphome.config_validation as cv
from esphome.const import CONF_GAMMA_CORRECT, CONF_LED, CONF_OUTPUT_ID
from ..defines import CONF_LVGL_ID
from ..lvcode import LvContext
from ..schemas import LVGL_SCHEMA
from ..types import LvType, lvgl_ns
from ..widgets import get_widgets
lv_led_t = LvType("lv_led_t")
LVLight = lvgl_ns.class_("LVLight", LightOutput)
CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend(
{
cv.Optional(CONF_GAMMA_CORRECT, default=0.0): cv.positive_float,
cv.Required(CONF_LED): cv.use_id(lv_led_t),
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(LVLight),
}
).extend(LVGL_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
await light.register_light(var, config)
paren = await cg.get_variable(config[CONF_LVGL_ID])
widget = await get_widgets(config, CONF_LED)
widget = widget[0]
async with LvContext(paren) as ctx:
ctx.add(var.set_obj(widget.obj))

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