1
0
mirror of https://github.com/esphome/esphome.git synced 2025-03-15 15:18:16 +00:00

Merge branch 'dev' into vornado-ir

This commit is contained in:
Jordan Zucker 2024-12-06 08:51:23 -08:00
commit 5257da28c1
66 changed files with 803 additions and 576 deletions

View File

@ -7,28 +7,39 @@ Checks: >-
-boost-*,
-bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-multi-level-implicit-pointer-conversion,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-bugprone-switch-missing-default-case,
-cert-dcl50-cpp,
-cert-err33-c,
-cert-err58-cpp,
-cert-oop57-cpp,
-cert-str34-c,
-clang-analyzer-optin.core.EnumCastOutOfRange,
-clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-deprecated-declarations,
-clang-diagnostic-ignored-optimization-argument,
-clang-diagnostic-missing-field-initializers,
-clang-diagnostic-shadow-field,
-clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter,
-clang-diagnostic-vla-cxx-extension,
-concurrency-*,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-missing-std-forward,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
@ -40,7 +51,9 @@ Checks: >-
-cppcoreguidelines-pro-type-static-cast-downcast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-rvalue-reference-param-not-moved,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-use-default-member-init,
-cppcoreguidelines-virtual-class-destructor,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
@ -60,21 +73,32 @@ Checks: >-
-llvm-include-order,
-llvm-qualified-auto,
-llvmlibc-*,
-misc-non-private-member-variables-in-classes,
-misc-const-correctness,
-misc-include-cleaner,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-unused-parameters,
-misc-use-anonymous-namespace,
-modernize-avoid-bind,
-modernize-avoid-c-arrays,
-modernize-concat-nested-namespaces,
-modernize-macro-to-enum,
-modernize-return-braced-init-list,
-modernize-type-traits,
-modernize-use-auto,
-modernize-use-constraints,
-modernize-use-default-member-init,
-modernize-use-equals-default,
-modernize-use-nodiscard,
-modernize-use-nullptr,
-modernize-use-nodiscard,
-modernize-use-nullptr,
-modernize-use-trailing-return-type,
-mpi-*,
-objc-*,
-performance-enum-size,
-readability-avoid-nested-conditional-operator,
-readability-container-contains,
-readability-container-data-pointer,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
@ -84,11 +108,13 @@ Checks: >-
-readability-magic-numbers,
-readability-make-member-function-const,
-readability-named-parameter,
-readability-redundant-casting,
-readability-redundant-inline-specifier,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:
- key: google-readability-function-size.StatementThreshold

View File

@ -22,7 +22,7 @@ runs:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.1.2
uses: actions/cache/restore@v4.2.0
with:
path: venv
# yamllint disable-line rule:line-length

View File

@ -30,7 +30,7 @@ concurrency:
jobs:
common:
name: Create common environment
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
@ -46,7 +46,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.0
with:
path: venv
# yamllint disable-line rule:line-length
@ -62,7 +62,7 @@ jobs:
black:
name: Check black
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -83,7 +83,7 @@ jobs:
flake8:
name: Check flake8
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -104,7 +104,7 @@ jobs:
pylint:
name: Check pylint
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -125,7 +125,7 @@ jobs:
pyupgrade:
name: Check pyupgrade
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -146,7 +146,7 @@ jobs:
ci-custom:
name: Run script/ci-custom
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -225,7 +225,7 @@ jobs:
clang-format:
name: Check clang-format
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -251,7 +251,7 @@ jobs:
clang-tidy:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
- black
@ -302,23 +302,18 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.0
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@v4.1.2
uses: actions/cache/restore@v4.2.0
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}
- name: Install clang-tidy
run: |
sudo apt-get update
sudo apt-get install clang-tidy-14
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
@ -345,7 +340,7 @@ jobs:
if: always()
list-components:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
if: github.event_name == 'pull_request'
@ -387,7 +382,7 @@ jobs:
test-build-components:
name: Component test ${{ matrix.file }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
- list-components
@ -421,7 +416,7 @@ jobs:
test-build-components-splitter:
name: Split components for testing into 20 groups maximum
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
- list-components
@ -439,7 +434,7 @@ jobs:
test-build-components-split:
name: Test split components
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
- list-components
@ -483,7 +478,7 @@ jobs:
ci-status:
name: CI Status
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
- black

View File

@ -179,6 +179,7 @@ esphome/components/haier/text_sensor/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
esphome/components/hbridge/switch/* @dwmw2
esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal

View File

@ -163,6 +163,18 @@ ENTRYPOINT ["/entrypoint.sh"]
CMD ["dashboard", "/config"]
ARG BUILD_VERSION=dev
# Labels
LABEL \
org.opencontainers.image.authors="The ESPHome Authors" \
org.opencontainers.image.title="ESPHome" \
org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
org.opencontainers.image.url="https://esphome.io/" \
org.opencontainers.image.documentation="https://esphome.io/" \
org.opencontainers.image.source="https://github.com/esphome/esphome" \
org.opencontainers.image.licenses="ESPHome" \
org.opencontainers.image.version=${BUILD_VERSION}
# ======================= hassio-type image =======================
@ -194,7 +206,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
# Labels
LABEL \
io.hass.name="ESPHome" \
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
io.hass.type="addon" \
io.hass.version="${BUILD_VERSION}"
# io.hass.arch is inherited from addon-debian-base
@ -209,17 +221,22 @@ ENV \
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
RUN \
apt-get update \
curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
&& echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \
&& apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
clang-format-13=1:13.0.1-11+b2 \
clang-tidy-14=1:14.0.6-12 \
patch=2.7.6-7 \
software-properties-common=0.99.30-4.1~deb12u1 \
nano=7.2-1+deb12u1 \
build-essential=12.9 \
python3-dev=3.11.2-1+b1 \
&& rm -rf \
&& if [ "$TARGETARCH$TARGETVARIANT" != "armv7" ]; then \
# move this up after armv7 is retired
apt-get install -y --no-install-recommends clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 ; \
fi; \
rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*

View File

@ -363,7 +363,7 @@ def upload_program(config, args, host):
from esphome import espota2
remote_port = ota_conf[CONF_PORT]
remote_port = int(ota_conf[CONF_PORT])
password = ota_conf.get(CONF_PASSWORD, "")
if (

View File

@ -2,7 +2,6 @@ import logging
from esphome import automation, core
import esphome.codegen as cg
from esphome.components import font
import esphome.components.image as espImage
from esphome.components.image import (
CONF_USE_TRANSPARENCY,
@ -131,7 +130,7 @@ ANIMATION_SCHEMA = cv.Schema(
)
)
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
CONFIG_SCHEMA = ANIMATION_SCHEMA
NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
{

View File

@ -662,20 +662,24 @@ void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
this->trigger(from, to);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
void Display::strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
ESPTime time) {
char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0)
this->print(x, y, font, color, align, buffer);
this->print(x, y, font, color, align, buffer, background);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, align, format, time);
this->strftime(x, y, font, COLOR_ON, COLOR_OFF, align, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
this->strftime(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
}
void Display::start_clipping(Rect rect) {

View File

@ -437,6 +437,20 @@ class Display : public PollingComponent {
*/
void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param background The background color to draw the text with.
* @param align The alignment of the text.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
ESPTime time) __attribute__((format(strftime, 8, 0)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.

View File

@ -65,6 +65,8 @@ _LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["preferences"]
CONF_RELEASE = "release"
def set_core_data(config):
CORE.data[KEY_ESP32] = {}
@ -216,11 +218,17 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
def _format_framework_espidf_version(ver: cv.Version) -> str:
def _format_framework_espidf_version(
ver: cv.Version, release: str, for_platformio: bool
) -> str:
# format the given arduino (https://github.com/espressif/esp-idf/releases) version to
# a PIO platformio/framework-espidf value
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
if for_platformio:
return f"platformio/framework-espidf@~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
if release:
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip"
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip"
# NOTE: Keep this in mind when updating the recommended version:
@ -241,11 +249,33 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 8)
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 5)
# The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 4, 0)
ESP_IDF_PLATFORM_VERSION = cv.Version(51, 3, 7)
# List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions
SUPPORTED_PLATFORMIO_ESP_IDF_5X = [
cv.Version(5, 3, 1),
cv.Version(5, 3, 0),
cv.Version(5, 2, 2),
cv.Version(5, 2, 1),
cv.Version(5, 1, 2),
cv.Version(5, 1, 1),
cv.Version(5, 1, 0),
cv.Version(5, 0, 2),
cv.Version(5, 0, 1),
cv.Version(5, 0, 0),
]
# pioarduino versions that don't require a release number
# List based on https://github.com/pioarduino/esp-idf/releases
SUPPORTED_PIOARDUINO_ESP_IDF_5X = [
cv.Version(5, 3, 1),
cv.Version(5, 3, 0),
cv.Version(5, 1, 5),
]
def _arduino_check_versions(value):
@ -286,8 +316,8 @@ def _arduino_check_versions(value):
def _esp_idf_check_versions(value):
value = value.copy()
lookups = {
"dev": (cv.Version(5, 1, 2), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 1, 2), None),
"dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 1, 5), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
}
@ -305,13 +335,51 @@ def _esp_idf_check_versions(value):
if version < cv.Version(4, 0, 0):
raise cv.Invalid("Only ESP-IDF 4.0+ is supported.")
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_espidf_version(version)
# flag this for later *before* we set value[CONF_PLATFORM_VERSION] below
has_platform_ver = CONF_PLATFORM_VERSION in value
value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION))
)
if (
(is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION]))
and version.major >= 5
and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X
):
raise cv.Invalid(
f"ESP-IDF {str(version)} not supported by platformio/espressif32"
)
if (
version.major < 5
or (
version in SUPPORTED_PLATFORMIO_ESP_IDF_5X
and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X
)
) and not has_platform_ver:
raise cv.Invalid(
f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'"
)
if (
not is_platformio
and CONF_RELEASE not in value
and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X
):
raise cv.Invalid(
f"ESP-IDF {value[CONF_VERSION]} is not available with pioarduino; you may need to specify '{CONF_RELEASE}'"
)
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_espidf_version(
version, value.get(CONF_RELEASE, None), is_platformio
)
if value[CONF_SOURCE].startswith("http"):
# prefix is necessary or platformio will complain with a cryptic error
value[CONF_SOURCE] = f"framework-espidf@{value[CONF_SOURCE]}"
if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
_LOGGER.warning(
"The selected ESP-IDF framework version is not the recommended one. "
@ -323,6 +391,12 @@ def _esp_idf_check_versions(value):
def _parse_platform_version(value):
try:
ver = cv.Version.parse(cv.version_number(value))
if ver.major >= 50: # a pioarduino version
if "-" in value:
# maybe a release candidate?...definitely not our default, just use it as-is...
return f"https://github.com/pioarduino/platform-espressif32.git#{value}"
return f"https://github.com/pioarduino/platform-espressif32.git#{ver.major}.{ver.minor:02d}.{ver.patch:02d}"
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/espressif32@{value}"
@ -330,6 +404,14 @@ def _parse_platform_version(value):
return value
def _platform_is_platformio(value):
try:
ver = cv.Version.parse(cv.version_number(value))
return ver.major < 50
except cv.Invalid:
return "platformio" in value
def _detect_variant(value):
board = value[CONF_BOARD]
if board in BOARDS:
@ -355,24 +437,20 @@ def _detect_variant(value):
def final_validate(config):
if CONF_PLATFORMIO_OPTIONS not in fv.full_config.get()[CONF_ESPHOME]:
if not (
pio_options := fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS)
):
# Not specified or empty
return config
pio_flash_size_key = "board_upload.flash_size"
pio_partitions_key = "board_build.partitions"
if (
CONF_PARTITIONS in config
and pio_partitions_key
in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS]
):
if CONF_PARTITIONS in config and pio_partitions_key in pio_options:
raise cv.Invalid(
f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32"
)
if (
pio_flash_size_key
in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS]
):
if pio_flash_size_key in pio_options:
raise cv.Invalid(
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
)
@ -412,6 +490,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_RELEASE): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
@ -515,10 +594,9 @@ async def to_code(config):
cg.add_build_flag("-DUSE_ESP_IDF")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-espidf@{conf[CONF_SOURCE]}"],
)
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
# This is espressif's own published version which is more up to date.
cg.add_platformio_option(

View File

@ -2,8 +2,10 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
import esphome.config_validation as cv
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME
from esphome.core import CORE
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
import esphome.final_validate as fv
DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@jesserockz", "@Rapsssito"]
@ -50,6 +52,7 @@ TX_POWER_LEVELS = {
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32BLE),
cv.Optional(CONF_NAME): cv.All(cv.string, cv.Length(max=20)),
cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
IO_CAPABILITY, lower=True
),
@ -67,7 +70,22 @@ def validate_variant(_):
raise cv.Invalid(f"{variant} does not support Bluetooth")
FINAL_VALIDATE_SCHEMA = validate_variant
def final_validation(config):
validate_variant(config)
if (name := config.get(CONF_NAME)) is not None:
full_config = fv.full_config.get()
max_length = 20
if full_config[CONF_ESPHOME][CONF_NAME_ADD_MAC_SUFFIX]:
max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used
if len(name) > max_length:
raise cv.Invalid(
f"Name '{name}' is too long, maximum length is {max_length} characters"
)
return config
FINAL_VALIDATE_SCHEMA = final_validation
async def to_code(config):
@ -75,6 +93,8 @@ async def to_code(config):
cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT]))
cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME]))
if (name := config.get(CONF_NAME)) is not None:
cg.add(var.set_name(name))
await cg.register_component(var, config)
if CORE.using_esp_idf:

View File

@ -188,12 +188,20 @@ bool ESP32BLE::ble_setup_() {
}
}
std::string name = App.get_name();
if (name.length() > 20) {
std::string name;
if (this->name_.has_value()) {
name = this->name_.value();
if (App.is_name_add_mac_suffix_enabled()) {
name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address
} else {
name = name.substr(0, 20);
name += "-" + get_mac_address().substr(6);
}
} else {
name = App.get_name();
if (name.length() > 20) {
if (App.is_name_add_mac_suffix_enabled()) {
name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address
} else {
name = name.substr(0, 20);
}
}
}

View File

@ -90,6 +90,7 @@ class ESP32BLE : public Component {
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
void set_name(const std::string &name) { this->name_ = name; }
void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data);
@ -131,6 +132,7 @@ class ESP32BLE : public Component {
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
uint32_t advertising_cycle_time_;
bool enable_on_boot_;
optional<std::string> name_;
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() {
esp_err_t err;
this->advertising_data_.set_scan_rsp = false;
this->advertising_data_.include_name = !this->scan_response_;
this->advertising_data_.include_name = true;
this->advertising_data_.include_txpower = !this->scan_response_;
err = esp_ble_gap_config_adv_data(&this->advertising_data_);
if (err != ESP_OK) {

View File

@ -1,9 +1,9 @@
from dataclasses import dataclass
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32_rmt, light
import esphome.config_validation as cv
from esphome.const import (
CONF_CHIPSET,
CONF_IS_RGBW,
@ -103,7 +103,7 @@ CONFIG_SCHEMA = cv.All(
default="0 us",
): cv.positive_time_period_nanoseconds,
}
),
).extend(cv.COMPONENT_SCHEMA),
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
)

View File

@ -1,4 +1,3 @@
from collections.abc import Iterable
import functools
import hashlib
import logging
@ -8,7 +7,6 @@ import re
import freetype
import glyphsets
from packaging import version
import requests
from esphome import core, external_files
@ -88,7 +86,7 @@ def flatten(lists) -> list:
return list(chain.from_iterable(lists))
def check_missing_glyphs(file, codepoints: Iterable, warning: bool = False):
def check_missing_glyphs(file, codepoints, warning: bool = False):
"""
Check that the given font file actually contains the requested glyphs
:param file: A Truetype font file
@ -177,24 +175,6 @@ def validate_glyphs(config):
return config
def validate_pillow_installed(value):
try:
import PIL
except ImportError as err:
raise cv.Invalid(
"Please install the pillow python package to use this feature. "
'(pip install "pillow==10.4.0")'
) from err
if version.parse(PIL.__version__) != version.parse("10.4.0"):
raise cv.Invalid(
"Please update your pillow installation to 10.4.0. "
'(pip install "pillow==10.4.0")'
)
return value
FONT_EXTENSIONS = (".ttf", ".woff", ".otf")
@ -393,7 +373,9 @@ def font_file_schema(value):
# Default if no glyphs or glyphsets are provided
DEFAULT_GLYPHSET = "GF_Latin_Kernel"
# default for bitmap fonts
DEFAULT_GLYPHS = ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz<C2><B0>'
DEFAULT_GLYPHS = (
' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
)
CONF_RAW_GLYPH_ID = "raw_glyph_id"
@ -421,7 +403,7 @@ FONT_SCHEMA = cv.Schema(
},
)
CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, validate_glyphs)
CONFIG_SCHEMA = cv.All(FONT_SCHEMA, validate_glyphs)
# PIL doesn't provide a consistent interface for both TrueType and bitmap

View File

@ -35,7 +35,9 @@ void HonClimate::set_beeper_state(bool state) {
if (state != this->settings_.beeper_state) {
this->settings_.beeper_state = state;
#ifdef USE_SWITCH
this->beeper_switch_->publish_state(state);
if (this->beeper_switch_ != nullptr) {
this->beeper_switch_->publish_state(state);
}
#endif
this->hon_rtc_.save(&this->settings_);
}
@ -45,10 +47,17 @@ bool HonClimate::get_beeper_state() const { return this->settings_.beeper_state;
void HonClimate::set_quiet_mode_state(bool state) {
if (state != this->get_quiet_mode_state()) {
this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
if ((this->mode != ClimateMode::CLIMATE_MODE_OFF) && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
this->force_send_control_ = true;
} else {
this->quiet_mode_state_ = state ? SwitchState::ON : SwitchState::OFF;
}
this->settings_.quiet_mode_state = state;
#ifdef USE_SWITCH
this->quiet_mode_switch_->publish_state(state);
if (this->quiet_mode_switch_ != nullptr) {
this->quiet_mode_switch_->publish_state(state);
}
#endif
this->hon_rtc_.save(&this->settings_);
}
@ -509,7 +518,7 @@ void HonClimate::initialization() {
}
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::ON : SwitchState::OFF;
}
haier_protocol::HaierMessage HonClimate::get_control_message() {
@ -932,7 +941,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
if (this->mode == CLIMATE_MODE_OFF) {
// AC just turned on from remote need to turn off display
this->force_send_control_ = true;
} else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
} else if ((((uint8_t) this->display_status_) & 0b10) == 0) {
this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
}
}
@ -1004,6 +1013,11 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
if (new_quiet_mode != this->get_quiet_mode_state()) {
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
this->settings_.quiet_mode_state = new_quiet_mode;
#ifdef USE_SWITCH
if (this->quiet_mode_switch_ != nullptr) {
this->quiet_mode_switch_->publish_state(new_quiet_mode);
}
#endif // USE_SWITCH
this->hon_rtc_.save(&this->settings_);
}
}

View File

@ -0,0 +1,44 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import CONF_OPTIMISTIC, CONF_PULSE_LENGTH, CONF_WAIT_TIME
from .. import hbridge_ns
HBridgeSwitch = hbridge_ns.class_("HBridgeSwitch", switch.Switch, cg.Component)
CODEOWNERS = ["@dwmw2"]
CONF_OFF_PIN = "off_pin"
CONF_ON_PIN = "on_pin"
CONFIG_SCHEMA = (
switch.switch_schema(HBridgeSwitch)
.extend(
{
cv.Required(CONF_ON_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_OFF_PIN): pins.gpio_output_pin_schema,
cv.Optional(
CONF_PULSE_LENGTH, default="100ms"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_WAIT_TIME): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = await switch.new_switch(config)
await cg.register_component(var, config)
on_pin = await cg.gpio_pin_expression(config[CONF_ON_PIN])
cg.add(var.set_on_pin(on_pin))
off_pin = await cg.gpio_pin_expression(config[CONF_OFF_PIN])
cg.add(var.set_off_pin(off_pin))
cg.add(var.set_pulse_length(config[CONF_PULSE_LENGTH]))
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
if wait_time := config.get(CONF_WAIT_TIME):
cg.add(var.set_wait_time(wait_time))

View File

@ -0,0 +1,95 @@
#include "hbridge_switch.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace hbridge {
static const char *const TAG = "switch.hbridge";
float HBridgeSwitch::get_setup_priority() const { return setup_priority::HARDWARE; }
void HBridgeSwitch::setup() {
ESP_LOGCONFIG(TAG, "Setting up H-Bridge Switch '%s'...", this->name_.c_str());
optional<bool> initial_state = this->get_initial_state_with_restore_mode().value_or(false);
// Like GPIOSwitch does, set the pin state both before and after pin setup()
this->on_pin_->digital_write(false);
this->on_pin_->setup();
this->on_pin_->digital_write(false);
this->off_pin_->digital_write(false);
this->off_pin_->setup();
this->off_pin_->digital_write(false);
if (initial_state.has_value())
this->write_state(initial_state);
}
void HBridgeSwitch::dump_config() {
LOG_SWITCH("", "H-Bridge Switch", this);
LOG_PIN(" On Pin: ", this->on_pin_);
LOG_PIN(" Off Pin: ", this->off_pin_);
ESP_LOGCONFIG(TAG, " Pulse length: %" PRId32 " ms", this->pulse_length_);
if (this->wait_time_)
ESP_LOGCONFIG(TAG, " Wait time %" PRId32 " ms", this->wait_time_);
}
void HBridgeSwitch::write_state(bool state) {
this->desired_state_ = state;
if (!this->timer_running_)
this->timer_fn_();
}
void HBridgeSwitch::timer_fn_() {
uint32_t next_timeout = 0;
while ((uint8_t) this->desired_state_ != this->relay_state_) {
switch (this->relay_state_) {
case RELAY_STATE_ON:
case RELAY_STATE_OFF:
case RELAY_STATE_UNKNOWN:
if (this->desired_state_) {
this->on_pin_->digital_write(true);
this->relay_state_ = RELAY_STATE_SWITCHING_ON;
} else {
this->off_pin_->digital_write(true);
this->relay_state_ = RELAY_STATE_SWITCHING_OFF;
}
next_timeout = this->pulse_length_;
if (!this->optimistic_)
this->publish_state(this->desired_state_);
break;
case RELAY_STATE_SWITCHING_ON:
this->on_pin_->digital_write(false);
this->relay_state_ = RELAY_STATE_ON;
if (this->optimistic_)
this->publish_state(true);
next_timeout = this->wait_time_;
break;
case RELAY_STATE_SWITCHING_OFF:
this->off_pin_->digital_write(false);
this->relay_state_ = RELAY_STATE_OFF;
if (this->optimistic_)
this->publish_state(false);
next_timeout = this->wait_time_;
break;
}
if (next_timeout) {
this->timer_running_ = true;
this->set_timeout(next_timeout, [this]() { this->timer_fn_(); });
return;
}
// In the case where ON/OFF state has been reached but we need to
// immediately change back again to reach desired_state_, we loop.
}
this->timer_running_ = false;
}
} // namespace hbridge
} // namespace esphome

View File

@ -0,0 +1,50 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/switch/switch.h"
#include <vector>
namespace esphome {
namespace hbridge {
enum RelayState : uint8_t {
RELAY_STATE_OFF = 0,
RELAY_STATE_ON = 1,
RELAY_STATE_SWITCHING_ON = 2,
RELAY_STATE_SWITCHING_OFF = 3,
RELAY_STATE_UNKNOWN = 4,
};
class HBridgeSwitch : public switch_::Switch, public Component {
public:
void set_on_pin(GPIOPin *pin) { this->on_pin_ = pin; }
void set_off_pin(GPIOPin *pin) { this->off_pin_ = pin; }
void set_pulse_length(uint32_t pulse_length) { this->pulse_length_ = pulse_length; }
void set_wait_time(uint32_t wait_time) { this->wait_time_ = wait_time; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
float get_setup_priority() const override;
void setup() override;
void dump_config() override;
protected:
void write_state(bool state) override;
void timer_fn_();
bool timer_running_{false};
bool desired_state_{false};
RelayState relay_state_{RELAY_STATE_UNKNOWN};
GPIOPin *on_pin_{nullptr};
GPIOPin *off_pin_{nullptr};
uint32_t pulse_length_{0};
uint32_t wait_time_{0};
bool optimistic_{false};
};
} // namespace hbridge
} // namespace esphome

View File

@ -53,7 +53,7 @@ bool HX711Sensor::read_sensor_(uint32_t *result) {
}
// Cycle clock pin for gain setting
for (uint8_t i = 0; i < this->gain_; i++) {
for (uint8_t i = 0; i < static_cast<uint8_t>(this->gain_); i++) {
this->sck_pin_->digital_write(true);
delayMicroseconds(1);
this->sck_pin_->digital_write(false);

View File

@ -9,7 +9,7 @@
namespace esphome {
namespace hx711 {
enum HX711Gain {
enum HX711Gain : uint8_t {
HX711_GAIN_128 = 1,
HX711_GAIN_32 = 2,
HX711_GAIN_64 = 3,

View File

@ -33,14 +33,15 @@ enum SpeakerEventGroupBits : uint32_t {
STATE_RUNNING = (1 << 11),
STATE_STOPPING = (1 << 12),
STATE_STOPPED = (1 << 13),
ERR_INVALID_FORMAT = (1 << 14),
ERR_TASK_FAILED_TO_START = (1 << 15),
ERR_ESP_INVALID_STATE = (1 << 16),
ERR_TASK_FAILED_TO_START = (1 << 14),
ERR_ESP_INVALID_STATE = (1 << 15),
ERR_ESP_NOT_SUPPORTED = (1 << 16),
ERR_ESP_INVALID_ARG = (1 << 17),
ERR_ESP_INVALID_SIZE = (1 << 18),
ERR_ESP_NO_MEM = (1 << 19),
ERR_ESP_FAIL = (1 << 20),
ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | ERR_ESP_NO_MEM | ERR_ESP_FAIL,
ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_NOT_SUPPORTED | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE |
ERR_ESP_NO_MEM | ERR_ESP_FAIL,
ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits
};
@ -55,6 +56,8 @@ static esp_err_t err_bit_to_esp_err(uint32_t bit) {
return ESP_ERR_INVALID_SIZE;
case SpeakerEventGroupBits::ERR_ESP_NO_MEM:
return ESP_ERR_NO_MEM;
case SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED:
return ESP_ERR_NOT_SUPPORTED;
default:
return ESP_FAIL;
}
@ -135,19 +138,19 @@ void I2SAudioSpeaker::loop() {
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START);
}
if (event_group_bits & SpeakerEventGroupBits::ERR_INVALID_FORMAT) {
if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) {
uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS;
ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits)));
this->status_set_warning();
}
if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) {
this->status_set_error("Failed to adjust I2S bus to match the incoming audio");
ESP_LOGE(TAG,
"Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8,
this->audio_stream_info_.sample_rate, this->audio_stream_info_.channels,
this->audio_stream_info_.bits_per_sample);
}
if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) {
uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS;
ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits)));
this->status_set_warning();
}
}
void I2SAudioSpeaker::set_volume(float volume) {
@ -236,13 +239,15 @@ void I2SAudioSpeaker::speaker_task(void *params) {
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING);
audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_;
const ssize_t bytes_per_sample = audio_stream_info.get_bytes_per_sample();
const uint8_t number_of_channels = audio_stream_info.channels;
const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * this_speaker->sample_rate_ / 1000 *
bytes_per_sample * number_of_channels;
const uint32_t bytes_per_ms =
audio_stream_info.channels * audio_stream_info.get_bytes_per_sample() * audio_stream_info.sample_rate / 1000;
const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * bytes_per_ms;
// Ensure ring buffer is at least as large as the total size of the DMA buffers
const size_t ring_buffer_size =
this_speaker->buffer_duration_ms_ * this_speaker->sample_rate_ / 1000 * bytes_per_sample * number_of_channels;
std::min((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms);
if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) {
// Failed to allocate buffers
@ -250,14 +255,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
this_speaker->delete_task_(dma_buffers_size);
}
if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) {
// Failed to start I2S driver
this_speaker->delete_task_(dma_buffers_size);
}
if (!this_speaker->send_esp_err_to_event_group_(this_speaker->reconfigure_i2s_stream_info_(audio_stream_info))) {
// Successfully set the I2S stream info, ready to write audio data to the I2S port
if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) {
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING);
bool stop_gracefully = false;
@ -275,6 +273,12 @@ void I2SAudioSpeaker::speaker_task(void *params) {
stop_gracefully = true;
}
if (this_speaker->audio_stream_info_ != audio_stream_info) {
// Audio stream info has changed, stop the speaker task so it will restart with the proper settings.
break;
}
i2s_event_t i2s_event;
while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) {
if (i2s_event.type == I2S_EVENT_TX_Q_OVF) {
@ -316,17 +320,14 @@ void I2SAudioSpeaker::speaker_task(void *params) {
}
}
}
} else {
// Couldn't configure the I2S port to be compatible with the incoming audio
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_INVALID_FORMAT);
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
i2s_driver_uninstall(this_speaker->parent_->get_port());
this_speaker->parent_->unlock();
}
i2s_zero_dma_buffer(this_speaker->parent_->get_port());
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
i2s_driver_uninstall(this_speaker->parent_->get_port());
this_speaker->parent_->unlock();
this_speaker->delete_task_(dma_buffers_size);
}
@ -382,6 +383,9 @@ bool I2SAudioSpeaker::send_esp_err_to_event_group_(esp_err_t err) {
case ESP_ERR_NO_MEM:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
return true;
case ESP_ERR_NOT_SUPPORTED:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED);
return true;
default:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL);
return true;
@ -411,18 +415,40 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin
return ESP_OK;
}
esp_err_t I2SAudioSpeaker::start_i2s_driver_() {
esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info) {
if ((this->i2s_mode_ & I2S_MODE_SLAVE) && (this->sample_rate_ != audio_stream_info.sample_rate)) { // NOLINT
// Can't reconfigure I2S bus, so the sample rate must match the configured value
return ESP_ERR_NOT_SUPPORTED;
}
if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) {
// Currently can't handle the case when the incoming audio has more bits per sample than the configured value
return ESP_ERR_NOT_SUPPORTED;
}
if (!this->parent_->try_lock()) {
return ESP_ERR_INVALID_STATE;
}
i2s_channel_fmt_t channel = this->channel_;
if (audio_stream_info.channels == 1) {
if (this->channel_ == I2S_CHANNEL_FMT_ONLY_LEFT) {
channel = I2S_CHANNEL_FMT_ONLY_LEFT;
} else {
channel = I2S_CHANNEL_FMT_ONLY_RIGHT;
}
} else if (audio_stream_info.channels == 2) {
channel = I2S_CHANNEL_FMT_RIGHT_LEFT;
}
int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000;
i2s_driver_config_t config = {
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX),
.sample_rate = this->sample_rate_,
.sample_rate = audio_stream_info.sample_rate,
.bits_per_sample = this->bits_per_sample_,
.channel_format = this->channel_,
.channel_format = channel,
.communication_format = this->i2s_comm_fmt_,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = DMA_BUFFERS_COUNT,
@ -477,30 +503,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() {
return err;
}
esp_err_t I2SAudioSpeaker::reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info) {
if (this->i2s_mode_ & I2S_MODE_MASTER) {
// ESP controls for the the I2S bus, so adjust the sample rate and bits per sample to match the incoming audio
this->sample_rate_ = audio_stream_info.sample_rate;
this->bits_per_sample_ = (i2s_bits_per_sample_t) audio_stream_info.bits_per_sample;
} else if (this->sample_rate_ != audio_stream_info.sample_rate) {
// Can't reconfigure I2S bus, so the sample rate must match the configured value
return ESP_ERR_INVALID_ARG;
}
if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) {
// Currently can't handle the case when the incoming audio has more bits per sample than the configured value
return ESP_ERR_INVALID_ARG;
}
if (audio_stream_info.channels == 1) {
return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_MONO);
} else if (audio_stream_info.channels == 2) {
return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_STEREO);
}
return ESP_ERR_INVALID_ARG;
}
void I2SAudioSpeaker::delete_task_(size_t buffer_size) {
this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr

View File

@ -91,24 +91,15 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size);
/// @brief Starts the ESP32 I2S driver.
/// Attempts to lock the I2S port, starts the I2S driver, and sets the data out pin. If it fails, it will unlock
/// the I2S port and uninstall the driver, if necessary.
/// @return ESP_ERR_INVALID_STATE if the I2S port is already locked.
/// ESP_ERR_INVALID_ARG if installing the driver or setting the data out pin fails due to a parameter error.
/// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out
/// pin. If it fails, it will unlock the I2S port and uninstall the driver, if necessary.
/// @param audio_stream_info Stream information for the I2S driver.
/// @return ESP_ERR_NOT_ALLOWED if the I2S port can't play the incoming audio stream.
/// ESP_ERR_INVALID_STATE if the I2S port is already locked.
/// ESP_ERR_INVALID_ARG if nstalling the driver or setting the data outpin fails due to a parameter error.
/// ESP_ERR_NO_MEM if the driver fails to install due to a memory allocation error.
/// ESP_FAIL if setting the data out pin fails due to an IO error
/// ESP_OK if successful
esp_err_t start_i2s_driver_();
/// @brief Adjusts the I2S driver configuration to match the incoming audio stream.
/// Modifies I2S driver's sample rate, bits per sample, and number of channel settings. If the I2S is in secondary
/// mode, it only modifies the number of channels.
/// @param audio_stream_info Describes the incoming audio stream
/// @return ESP_ERR_INVALID_ARG if there is a parameter error, if there is more than 2 channels in the stream, or if
/// the audio settings are incompatible with the configuration.
/// ESP_ERR_NO_MEM if the driver fails to reconfigure due to a memory allocation error.
/// ESP_OK if successful.
esp_err_t reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info);
/// ESP_FAIL if setting the data out pin fails due to an IO error ESP_OK if successful
esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info);
/// @brief Deletes the speaker's task.
/// Deallocates the data_buffer_ and audio_ring_buffer_, if necessary, and deletes the task. Should only be called by

View File

@ -1,6 +1,6 @@
from esphome import core, pins
import esphome.codegen as cg
from esphome.components import display, font, spi
from esphome.components import display, spi
from esphome.components.display import validate_rotation
import esphome.config_validation as cv
from esphome.const import (
@ -147,7 +147,6 @@ def _validate(config):
CONFIG_SCHEMA = cv.All(
font.validate_pillow_installed,
display.FULL_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ILI9XXXDisplay),

View File

@ -10,7 +10,6 @@ import puremagic
from esphome import core, external_files
import esphome.codegen as cg
from esphome.components import font
import esphome.config_validation as cv
from esphome.const import (
CONF_DITHER,
@ -233,7 +232,7 @@ IMAGE_SCHEMA = cv.Schema(
)
)
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
CONFIG_SCHEMA = IMAGE_SCHEMA
def load_svg_image(file: bytes, resize: tuple[int, int]):

View File

@ -38,7 +38,7 @@ def literal(arg):
def call_lambda(lamb: LambdaExpression):
expr = lamb.content.strip()
if expr.startswith("return") and expr.endswith(";"):
return expr[7:][:-1]
return expr[6:][:-1].strip()
return f"{lamb}()"

View File

@ -56,6 +56,9 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT
inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) {
lv_img_set_src(obj, image->get_lv_img_dsc());
}
inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image) {
lv_disp_set_bg_image(disp, image->get_lv_img_dsc());
}
#endif // USE_LVGL_IMAGE
#ifdef USE_LVGL_ANIMIMG
inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) {

View File

@ -35,6 +35,11 @@ LINE_SCHEMA = {
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
}
LINE_MODIFY_SCHEMA = {
cv.Optional(CONF_POINTS): cv_point_list,
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
}
class LineType(WidgetType):
def __init__(self):
@ -43,6 +48,7 @@ class LineType(WidgetType):
LvType("lv_line_t"),
(CONF_MAIN,),
LINE_SCHEMA,
modify_schema=LINE_MODIFY_SCHEMA,
)
async def to_code(self, w: Widget, config):

View File

@ -29,7 +29,7 @@ from ..lvcode import (
)
from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema
from ..types import LV_EVENT, char_ptr, lv_obj_t
from . import Widget, set_obj_properties
from . import Widget, add_widgets, set_obj_properties
from .button import button_spec
from .buttonmatrix import (
BUTTONMATRIX_BUTTON_SCHEMA,
@ -119,6 +119,7 @@ async def msgbox_to_code(top_layer, conf):
button_style = {CONF_ITEMS: button_style}
await set_obj_properties(buttonmatrix_widget, button_style)
await set_obj_properties(msgbox_widget, conf)
await add_widgets(msgbox_widget, conf)
async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action:
outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN")
if close_button:

View File

@ -152,11 +152,11 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
}
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
return (r.start_address == start_address && r.register_type == register_type);
});
auto reg_it = std::find_if(
std::begin(this->register_ranges_), std::end(this->register_ranges_),
[=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); });
if (reg_it == register_ranges_.end()) {
if (reg_it == this->register_ranges_.end()) {
ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
} else {
return reg_it->sensors;
@ -240,18 +240,18 @@ void ModbusController::update() {
// walk through the sensors and determine the register ranges to read
size_t ModbusController::create_register_ranges_() {
register_ranges_.clear();
if (this->parent_->role == modbus::ModbusRole::CLIENT && sensorset_.empty()) {
this->register_ranges_.clear();
if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) {
ESP_LOGW(TAG, "No sensors registered");
return 0;
}
// iterator is sorted see SensorItemsComparator for details
auto ix = sensorset_.begin();
auto ix = this->sensorset_.begin();
RegisterRange r = {};
uint8_t buffer_offset = 0;
SensorItem *prev = nullptr;
while (ix != sensorset_.end()) {
while (ix != this->sensorset_.end()) {
SensorItem *curr = *ix;
ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count,
@ -278,12 +278,12 @@ size_t ModbusController::create_register_ranges_() {
// this register can re-use the data from the previous register
// remove this sensore because start_address is changed (sort-order)
ix = sensorset_.erase(ix);
ix = this->sensorset_.erase(ix);
curr->start_address = r.start_address;
curr->offset += prev->offset;
sensorset_.insert(curr);
this->sensorset_.insert(curr);
// move iterator backwards because it will be incremented later
ix--;
@ -293,14 +293,14 @@ size_t ModbusController::create_register_ranges_() {
// this register can extend the current range
// remove this sensore because start_address is changed (sort-order)
ix = sensorset_.erase(ix);
ix = this->sensorset_.erase(ix);
curr->start_address = r.start_address;
curr->offset += buffer_offset;
buffer_offset += curr->get_register_size();
r.register_count += curr->register_count;
sensorset_.insert(curr);
this->sensorset_.insert(curr);
// move iterator backwards because it will be incremented later
ix--;
@ -327,7 +327,7 @@ size_t ModbusController::create_register_ranges_() {
ix++;
} else {
ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
register_ranges_.push_back(r);
this->register_ranges_.push_back(r);
r = {};
buffer_offset = 0;
// do not increment the iterator here because the current sensor has to be re-evaluated
@ -339,10 +339,10 @@ size_t ModbusController::create_register_ranges_() {
if (r.register_count > 0) {
// Add the last range
ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
register_ranges_.push_back(r);
this->register_ranges_.push_back(r);
}
return register_ranges_.size();
return this->register_ranges_.size();
}
void ModbusController::dump_config() {
@ -352,18 +352,18 @@ void ModbusController::dump_config() {
ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGCONFIG(TAG, "sensormap");
for (auto &it : sensorset_) {
for (auto &it : this->sensorset_) {
ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d",
static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count,
it->get_register_size());
}
ESP_LOGCONFIG(TAG, "ranges");
for (auto &it : register_ranges_) {
for (auto &it : this->register_ranges_) {
ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type),
it.start_address, it.register_count, it.skip_updates);
}
ESP_LOGCONFIG(TAG, "server registers");
for (auto &r : server_registers_) {
for (auto &r : this->server_registers_) {
ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address,
static_cast<uint8_t>(r->value_type), r->register_count);
}
@ -372,15 +372,15 @@ void ModbusController::dump_config() {
void ModbusController::loop() {
// Incoming data to process?
if (!incoming_queue_.empty()) {
auto &message = incoming_queue_.front();
if (!this->incoming_queue_.empty()) {
auto &message = this->incoming_queue_.front();
if (message != nullptr)
process_modbus_data_(message.get());
incoming_queue_.pop();
this->process_modbus_data_(message.get());
this->incoming_queue_.pop();
} else {
// all messages processed send pending commands
send_next_command_();
this->send_next_command_();
}
}
@ -391,7 +391,7 @@ void ModbusController::on_write_register_response(ModbusRegisterType register_ty
void ModbusController::dump_sensors_() {
ESP_LOGV(TAG, "sensors");
for (auto &it : sensorset_) {
for (auto &it : this->sensorset_) {
ESP_LOGV(TAG, " Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count,
it->get_register_size(), it->offset);
}

View File

@ -240,14 +240,14 @@ class SensorItem {
}
// Override register size for modbus devices not using 1 register for one dword
void set_register_size(uint8_t register_size) { response_bytes = register_size; }
ModbusRegisterType register_type;
SensorValueType sensor_value_type;
uint16_t start_address;
uint32_t bitmask;
uint8_t offset;
uint8_t register_count;
ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
SensorValueType sensor_value_type{SensorValueType::RAW};
uint16_t start_address{0};
uint32_t bitmask{0};
uint8_t offset{0};
uint8_t register_count{0};
uint8_t response_bytes{0};
uint16_t skip_updates;
uint16_t skip_updates{0};
std::vector<uint8_t> custom_data{};
bool force_new_range{false};
};
@ -261,9 +261,9 @@ class ServerRegister {
this->register_count = register_count;
this->read_lambda = std::move(read_lambda);
}
uint16_t address;
SensorValueType value_type;
uint8_t register_count;
uint16_t address{0};
SensorValueType value_type{SensorValueType::RAW};
uint8_t register_count{0};
std::function<float()> read_lambda;
};
@ -312,11 +312,11 @@ struct RegisterRange {
class ModbusCommandItem {
public:
static const size_t MAX_PAYLOAD_BYTES = 240;
ModbusController *modbusdevice;
uint16_t register_address;
uint16_t register_count;
ModbusFunctionCode function_code;
ModbusRegisterType register_type;
ModbusController *modbusdevice{nullptr};
uint16_t register_address{0};
uint16_t register_count{0};
ModbusFunctionCode function_code{ModbusFunctionCode::CUSTOM};
ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
on_data_func;
std::vector<uint8_t> payload = {};
@ -493,23 +493,23 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
/// Collection of all sensors for this component
SensorSet sensorset_;
/// Collection of all server registers for this component
std::vector<ServerRegister *> server_registers_;
std::vector<ServerRegister *> server_registers_{};
/// Continuous range of modbus registers
std::vector<RegisterRange> register_ranges_;
std::vector<RegisterRange> register_ranges_{};
/// Hold the pending requests to be sent
std::list<std::unique_ptr<ModbusCommandItem>> command_queue_;
/// modbus response data waiting to get processed
std::queue<std::unique_ptr<ModbusCommandItem>> incoming_queue_;
/// if duplicate commands can be sent
bool allow_duplicate_commands_;
bool allow_duplicate_commands_{false};
/// when was the last send operation
uint32_t last_command_timestamp_;
uint32_t last_command_timestamp_{0};
/// min time in ms between sending modbus commands
uint16_t command_throttle_;
uint16_t command_throttle_{0};
/// if module didn't respond the last command
bool module_offline_;
bool module_offline_{false};
/// how many updates to skip if module is offline
uint16_t offline_skip_updates_;
uint16_t offline_skip_updates_{0};
/// How many times we will retry a command if we get no response
uint8_t max_cmd_retries_{4};
/// Command sent callback

View File

@ -8,7 +8,7 @@ namespace modbus_controller {
static const char *const TAG = "modbus.number";
void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) {
float result = payload_to_float(data, *this) / multiply_by_;
float result = payload_to_float(data, *this) / this->multiply_by_;
// Is there a lambda registered
// call it with the pre converted value and the raw data array
@ -43,7 +43,7 @@ void ModbusNumber::control(float value) {
return;
}
} else {
write_value = multiply_by_ * write_value;
write_value = this->multiply_by_ * write_value;
}
if (!data.empty()) {
@ -63,21 +63,21 @@ void ModbusNumber::control(float value) {
// Create and send the write command
if (this->register_count == 1 && !this->use_write_multiple_) {
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2
write_cmd =
ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]);
write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2,
data[0]);
} else {
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2,
this->register_count, data);
write_cmd = ModbusCommandItem::create_write_multiple_command(
this->parent_, this->start_address + this->offset / 2, this->register_count, data);
}
// publish new value
write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &data) {
// gets called when the write command is ack'd from the device
parent_->on_write_register_response(write_cmd.register_type, start_address, data);
this->parent_->on_write_register_response(write_cmd.register_type, start_address, data);
this->publish_state(value);
};
}
parent_->queue_command(write_cmd);
this->parent_->queue_command(write_cmd);
this->publish_state(value);
}
void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); }

View File

@ -29,7 +29,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
void parse_and_publish(const std::vector<uint8_t> &data) override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void set_parent(ModbusController *parent) { this->parent_ = parent; }
void set_write_multiply(float factor) { multiply_by_ = factor; }
void set_write_multiply(float factor) { this->multiply_by_ = factor; }
using transform_func_t = std::function<optional<float>(ModbusNumber *, float, const std::vector<uint8_t> &)>;
using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_t> &)>;
@ -39,9 +39,9 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
protected:
void control(float value) override;
optional<transform_func_t> transform_func_;
optional<write_transform_func_t> write_transform_func_;
ModbusController *parent_;
optional<transform_func_t> transform_func_{nullopt};
optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_{nullptr};
float multiply_by_{1.0};
bool use_write_multiple_{false};
};

View File

@ -27,7 +27,7 @@ void ModbusFloatOutput::write_state(float value) {
return;
}
} else {
value = multiply_by_ * value;
value = this->multiply_by_ * value;
}
// lambda didn't set payload
if (data.empty()) {
@ -40,12 +40,13 @@ void ModbusFloatOutput::write_state(float value) {
// Create and send the write command
ModbusCommandItem write_cmd;
if (this->register_count == 1 && !this->use_write_multiple_) {
write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]);
write_cmd =
ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset, data[0]);
} else {
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset,
write_cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset,
this->register_count, data);
}
parent_->queue_command(write_cmd);
this->parent_->queue_command(write_cmd);
}
void ModbusFloatOutput::dump_config() {
@ -90,9 +91,9 @@ void ModbusBinaryOutput::write_state(bool state) {
// offset for coil and discrete inputs is the coil/register number not bytes
if (this->use_write_multiple_) {
std::vector<bool> states{state};
cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states);
cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states);
} else {
cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state);
}
}
this->parent_->queue_command(cmd);

View File

@ -25,7 +25,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
void dump_config() override;
void set_parent(ModbusController *parent) { this->parent_ = parent; }
void set_write_multiply(float factor) { multiply_by_ = factor; }
void set_write_multiply(float factor) { this->multiply_by_ = factor; }
// Do nothing
void parse_and_publish(const std::vector<uint8_t> &data) override{};
@ -37,9 +37,9 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
void write_state(float value) override;
optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_;
ModbusController *parent_{nullptr};
float multiply_by_{1.0};
bool use_write_multiple_;
bool use_write_multiple_{false};
};
class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem {
@ -68,8 +68,8 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public
void write_state(bool state) override;
optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_;
bool use_write_multiple_;
ModbusController *parent_{nullptr};
bool use_write_multiple_{false};
};
} // namespace modbus_controller

View File

@ -74,12 +74,13 @@ void ModbusSelect::control(const std::string &value) {
const uint16_t write_address = this->start_address + this->offset / 2;
ModbusCommandItem write_cmd;
if ((this->register_count == 1) && (!this->use_write_multiple_)) {
write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]);
write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, write_address, data[0]);
} else {
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data);
write_cmd =
ModbusCommandItem::create_write_multiple_command(this->parent_, write_address, this->register_count, data);
}
parent_->queue_command(write_cmd);
this->parent_->queue_command(write_cmd);
if (this->optimistic_)
this->publish_state(value);

View File

@ -42,12 +42,12 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
void control(const std::string &value) override;
protected:
std::vector<int64_t> mapping_;
ModbusController *parent_;
std::vector<int64_t> mapping_{};
ModbusController *parent_{nullptr};
bool use_write_multiple_{false};
bool optimistic_{false};
optional<transform_func_t> transform_func_;
optional<write_transform_func_t> write_transform_func_;
optional<transform_func_t> transform_func_{nullopt};
optional<write_transform_func_t> write_transform_func_{nullopt};
};
} // namespace modbus_controller

View File

@ -80,24 +80,24 @@ void ModbusSwitch::write_state(bool state) {
// offset for coil and discrete inputs is the coil/register number not bytes
if (this->use_write_multiple_) {
std::vector<bool> states{state};
cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states);
cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states);
} else {
cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state);
}
} else {
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2
if (this->use_write_multiple_) {
std::vector<uint16_t> bool_states(1, state ? (0xFFFF & this->bitmask) : 0);
cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, 1,
cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset / 2, 1,
bool_states);
} else {
cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2,
cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2,
state ? 0xFFFF & this->bitmask : 0u);
}
}
}
this->parent_->queue_command(cmd);
publish_state(state);
this->publish_state(state);
}
// ModbusSwitch end
} // namespace modbus_controller

View File

@ -40,8 +40,8 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected:
ModbusController *parent_;
bool use_write_multiple_;
ModbusController *parent_{nullptr};
bool use_write_multiple_{false};
optional<transform_func_t> publish_transform_func_{nullopt};
optional<write_transform_func_t> write_transform_func_{nullopt};
};

View File

@ -49,6 +49,7 @@ from esphome.const import (
CONF_USE_ABBREVIATIONS,
CONF_USERNAME,
CONF_WILL_MESSAGE,
CONF_PUBLISH_NAN_AS_NONE,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
@ -296,6 +297,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_QOS, default=0): cv.mqtt_qos,
}
),
cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean,
}
),
validate_config,
@ -449,6 +451,8 @@ async def to_code(config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE]))
MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema(
{

View File

@ -608,6 +608,10 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this
const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; }
void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix) { this->topic_prefix_ = topic_prefix; }
const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; }
void MQTTClientComponent::set_publish_nan_as_none(bool publish_nan_as_none) {
this->publish_nan_as_none_ = publish_nan_as_none;
}
bool MQTTClientComponent::is_publish_nan_as_none() const { return this->publish_nan_as_none_; }
void MQTTClientComponent::disable_birth_message() {
this->birth_message_.topic = "";
this->recalculate_availability_();

View File

@ -263,6 +263,10 @@ class MQTTClientComponent : public Component {
void set_on_connect(mqtt_on_connect_callback_t &&callback);
void set_on_disconnect(mqtt_on_disconnect_callback_t &&callback);
// Publish None state instead of NaN for Home Assistant
void set_publish_nan_as_none(bool publish_nan_as_none);
bool is_publish_nan_as_none() const;
protected:
void send_device_info_();
@ -328,6 +332,8 @@ class MQTTClientComponent : public Component {
uint32_t connect_begin_;
uint32_t last_connected_{0};
optional<MQTTClientDisconnectReason> disconnect_reason_{};
bool publish_nan_as_none_{false};
};
extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -69,6 +69,8 @@ bool MQTTSensorComponent::send_initial_state() {
}
}
bool MQTTSensorComponent::publish_state(float value) {
if (mqtt::global_mqtt_client->is_publish_nan_as_none() && std::isnan(value))
return this->publish(this->get_state_topic_(), "None");
int8_t accuracy = this->sensor_->get_accuracy_decimals();
return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy));
}

View File

@ -9,11 +9,6 @@
#include "lwip/apps/sntp.h"
#endif
// Yes, the server names are leaked, but that's fine.
#ifdef CLANG_TIDY
#define strdup(x) (const_cast<char *>(x))
#endif
namespace esphome {
namespace sntp {
@ -26,30 +21,29 @@ void SNTPComponent::setup() {
esp_sntp_stop();
}
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
size_t i = 0;
for (auto &server : this->servers_) {
esp_sntp_setservername(i++, server.c_str());
}
esp_sntp_set_sync_interval(this->get_update_interval());
esp_sntp_init();
#else
sntp_stop();
sntp_setoperatingmode(SNTP_OPMODE_POLL);
#endif
sntp_setservername(0, strdup(this->server_1_.c_str()));
if (!this->server_2_.empty()) {
sntp_setservername(1, strdup(this->server_2_.c_str()));
size_t i = 0;
for (auto &server : this->servers_) {
sntp_setservername(i++, server.c_str());
}
if (!this->server_3_.empty()) {
sntp_setservername(2, strdup(this->server_3_.c_str()));
}
#ifdef USE_ESP_IDF
esp_sntp_set_sync_interval(this->get_update_interval());
#endif
sntp_init();
#endif
}
void SNTPComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SNTP Time:");
ESP_LOGCONFIG(TAG, " Server 1: '%s'", this->server_1_.c_str());
ESP_LOGCONFIG(TAG, " Server 2: '%s'", this->server_2_.c_str());
ESP_LOGCONFIG(TAG, " Server 3: '%s'", this->server_3_.c_str());
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
size_t i = 0;
for (auto &server : this->servers_) {
ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server.c_str());
}
}
void SNTPComponent::update() {
#if !defined(USE_ESP_IDF)

View File

@ -14,23 +14,20 @@ namespace sntp {
/// \see https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
class SNTPComponent : public time::RealTimeClock {
public:
SNTPComponent(const std::vector<std::string> &servers) : servers_(servers) {}
// Note: set_servers() has been removed and replaced by a constructor - calling set_servers after setup would
// have had no effect anyway, and making the strings immutable avoids the need to strdup their contents.
void setup() override;
void dump_config() override;
/// Change the servers used by SNTP for timekeeping
void set_servers(const std::string &server_1, const std::string &server_2, const std::string &server_3) {
this->server_1_ = server_1;
this->server_2_ = server_2;
this->server_3_ = server_3;
}
float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
void update() override;
void loop() override;
protected:
std::string server_1_;
std::string server_2_;
std::string server_3_;
std::vector<std::string> servers_;
bool has_time_{false};
};

View File

@ -1,16 +1,16 @@
import esphome.codegen as cg
from esphome.components import time as time_
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.core import CORE
from esphome.const import (
CONF_ID,
CONF_SERVERS,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
PLATFORM_RTL87XX,
PLATFORM_BK72XX,
)
from esphome.core import CORE
DEPENDENCIES = ["network"]
sntp_ns = cg.esphome_ns.namespace("sntp")
@ -40,11 +40,8 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
servers = config[CONF_SERVERS]
servers += [""] * (3 - len(servers))
cg.add(var.set_servers(*servers))
var = cg.new_Pvariable(config[CONF_ID], servers)
await cg.register_component(var, config)
await time_.register_time(var, config)

View File

@ -692,6 +692,7 @@ CONF_PRIORITY = "priority"
CONF_PROJECT = "project"
CONF_PROTOCOL = "protocol"
CONF_PUBLISH_INITIAL_STATE = "publish_initial_state"
CONF_PUBLISH_NAN_AS_NONE = "publish_nan_as_none"
CONF_PULL_MODE = "pull_mode"
CONF_PULLDOWN = "pulldown"
CONF_PULLUP = "pullup"

View File

@ -259,10 +259,15 @@ bool random_bytes(uint8_t *data, size_t len) {
bool str_equals_case_insensitive(const std::string &a, const std::string &b) {
return strcasecmp(a.c_str(), b.c_str()) == 0;
}
#if ESP_IDF_VERSION_MAJOR >= 5
bool str_startswith(const std::string &str, const std::string &start) { return str.starts_with(start); }
bool str_endswith(const std::string &str, const std::string &end) { return str.ends_with(end); }
#else
bool str_startswith(const std::string &str, const std::string &start) { return str.rfind(start, 0) == 0; }
bool str_endswith(const std::string &str, const std::string &end) {
return str.rfind(end) == (str.size() - end.size());
}
#endif
std::string str_truncate(const std::string &str, size_t length) {
return str.length() > length ? str.substr(0, length) : str;
}

View File

@ -137,9 +137,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script
; This are common settings for the ESP32 (all variants) using IDF.
[common:esp32-idf]
extends = common:idf
platform = platformio/espressif32@5.4.0
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.06/platform-espressif32.zip
platform_packages =
platformio/framework-espidf@~3.40408.0
pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.1.5/esp-idf-v5.1.5.zip
framework = espidf
lib_deps =

View File

@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "esphome"
license = {text = "MIT"}
description = "Make creating custom firmwares for ESP32/ESP8266 super easy."
description = "ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems."
readme = "README.md"
authors = [
{name = "The ESPHome Authors", email = "esphome@nabucasa.com"}

View File

@ -1,4 +1,4 @@
# Useful stuff when working in a development environment
clang-format==13.0.1 # also change in .pre-commit-config.yaml and Dockerfile when updating
clang-tidy==14.0.6 # When updating clang-tidy, also update Dockerfile
clang-tidy==18.1.8 # When updating clang-tidy, also update Dockerfile
yamllint==1.35.1 # also change in .pre-commit-config.yaml when updating

View File

@ -63,8 +63,6 @@ def clang_options(idedata):
"-Ddeprecated(x)=",
# allow to condition code on the presence of clang-tidy
"-DCLANG_TIDY",
# (esp-idf) Disable this header because they use asm with registers clang-tidy doesn't know
"-D__XTENSA_API_H__",
# (esp-idf) Fix __once_callable in some libstdc++ headers
"-D_GLIBCXX_HAVE_TLS",
]
@ -238,7 +236,7 @@ def main():
failed_files = []
try:
executable = get_binary("clang-tidy", 14)
executable = get_binary("clang-tidy", 18)
task_queue = queue.Queue(args.jobs)
lock = threading.Lock()
for _ in range(args.jobs):
@ -283,12 +281,12 @@ def main():
print("Applying fixes ...")
try:
try:
subprocess.call(["clang-apply-replacements-14", tmpdir])
subprocess.call(["clang-apply-replacements-18", tmpdir])
except FileNotFoundError:
subprocess.call(["clang-apply-replacements", tmpdir])
except FileNotFoundError:
print(
"Error please install clang-apply-replacements-14 or clang-apply-replacements.\n",
"Error please install clang-apply-replacements-18 or clang-apply-replacements.\n",
file=sys.stderr,
)
except:

View File

@ -0,0 +1,39 @@
output:
- platform: ${pwm_platform}
pin: ${output1_pin}
id: gpio_output1
- platform: ${pwm_platform}
pin: ${output2_pin}
id: gpio_output2
- platform: ${pwm_platform}
pin: ${output3_pin}
id: gpio_output3
- platform: ${pwm_platform}
pin: ${output4_pin}
id: gpio_output4
light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!
switch:
- platform: hbridge
id: switch_hbridge
on_pin: ${hbridge_on_pin}
off_pin: ${hbridge_off_pin}

View File

@ -1,33 +1,17 @@
output:
- platform: ledc
pin: 14
id: gpio_output1
- platform: ledc
pin: 15
id: gpio_output2
- platform: ledc
pin: 12
id: gpio_output3
- platform: ledc
pin: 13
id: gpio_output4
substitutions:
pwm_platform: ledc
output1_pin: "14"
output2_pin: "15"
output3_pin: "12"
output4_pin: "13"
hbridge_on_pin: "4"
hbridge_off_pin: "5"
light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
packages:
common: !include common.yaml
fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!
switch:
- id: !extend switch_hbridge
pulse_length: 60ms
wait_time: 10ms
optimistic: false

View File

@ -1,33 +1,16 @@
output:
- platform: ledc
pin: 4
id: gpio_output1
- platform: ledc
pin: 5
id: gpio_output2
- platform: ledc
pin: 6
id: gpio_output3
- platform: ledc
pin: 7
id: gpio_output4
substitutions:
pwm_platform: "ledc"
output1_pin: "4"
output2_pin: "5"
output3_pin: "6"
output4_pin: "7"
hbridge_on_pin: "2"
hbridge_off_pin: "3"
light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
packages:
common: !include common.yaml
fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!
switch:
- id: !extend switch_hbridge
wait_time: 10ms
optimistic: true

View File

@ -1,33 +1,15 @@
output:
- platform: ledc
pin: 4
id: gpio_output1
- platform: ledc
pin: 5
id: gpio_output2
- platform: ledc
pin: 6
id: gpio_output3
- platform: ledc
pin: 7
id: gpio_output4
substitutions:
pwm_platform: "ledc"
output1_pin: "4"
output2_pin: "5"
output3_pin: "6"
output4_pin: "7"
hbridge_on_pin: "2"
hbridge_off_pin: "3"
light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
packages:
common: !include common.yaml
fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!
switch:
- id: !extend switch_hbridge
pulse_length: 60ms

View File

@ -1,33 +1,16 @@
output:
- platform: ledc
pin: 14
id: gpio_output1
- platform: ledc
pin: 15
id: gpio_output2
- platform: ledc
pin: 12
id: gpio_output3
- platform: ledc
pin: 13
id: gpio_output4
substitutions:
pwm_platform: "ledc"
output1_pin: "14"
output2_pin: "15"
output3_pin: "12"
output4_pin: "13"
hbridge_on_pin: "4"
hbridge_off_pin: "5"
light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
packages:
common: !include common.yaml
fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!
switch:
- id: !extend switch_hbridge
pulse_length: 60ms
wait_time: 10ms

View File

@ -1,33 +1,16 @@
output:
- platform: esp8266_pwm
pin: 4
id: gpio_output1
- platform: esp8266_pwm
pin: 5
id: gpio_output2
- platform: esp8266_pwm
pin: 12
id: gpio_output3
- platform: esp8266_pwm
pin: 13
id: gpio_output4
substitutions:
pwm_platform: "esp8266_pwm"
output1_pin: "4"
output2_pin: "5"
output3_pin: "12"
output4_pin: "13"
hbridge_on_pin: "14"
hbridge_off_pin: "15"
light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
packages:
common: !include common.yaml
fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!
switch:
- id: !extend switch_hbridge
pulse_length: 60ms
wait_time: 10ms

View File

@ -1,33 +1,16 @@
output:
- platform: rp2040_pwm
pin: 4
id: gpio_output1
- platform: rp2040_pwm
pin: 5
id: gpio_output2
- platform: rp2040_pwm
pin: 6
id: gpio_output3
- platform: rp2040_pwm
pin: 7
id: gpio_output4
substitutions:
pwm_platform: "rp2040_pwm"
output1_pin: "4"
output2_pin: "5"
output3_pin: "6"
output4_pin: "7"
hbridge_on_pin: "2"
hbridge_off_pin: "3"
light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
packages:
common: !include common.yaml
fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!
switch:
- id: !extend switch_hbridge
wait_time: 10ms
optimistic: true

View File

@ -109,6 +109,10 @@ lvgl:
close_button: true
title: Messagebox
bg_color: 0xffff
widgets:
- label:
text: Hello Msgbox
id: msgbox_label
body:
text: This is a sample messagebox
bg_color: 0x808080
@ -137,6 +141,9 @@ lvgl:
- lvgl.widget.focus: mark
- lvgl.widget.redraw: hello_label
- lvgl.widget.redraw:
- lvgl.label.update:
id: msgbox_label
text: Unloaded
on_all_events:
logger.log:
format: "Event %s"
@ -337,7 +344,7 @@ lvgl:
id: button_button
width: 20%
height: 10%
transform_angle: !lambda return 180*100;
transform_angle: !lambda return(180*100);
arc_width: !lambda return 4;
border_width: !lambda return 6;
shadow_ofs_x: !lambda return 6;
@ -581,7 +588,7 @@ lvgl:
- 180, 60
- 240, 10
on_click:
- lvgl.widget.update:
- lvgl.line.update:
id: lv_line_id
line_color: 0xFFFF
- lvgl.page.next:

View File

@ -60,6 +60,7 @@ mqtt:
- mqtt.publish:
topic: some/topic
payload: Good-bye
publish_nan_as_none: false
binary_sensor:
- platform: template

View File

@ -1,19 +0,0 @@
esphome:
name: componenttestesp32c3idf51
friendly_name: $component_name
esp32:
board: lolin_c3_mini
framework:
type: esp-idf
version: 5.1.2
platform_version: 6.5.0
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_test_file: $component_test_file

View File

@ -1,19 +0,0 @@
esphome:
name: componenttestesp32idf51
friendly_name: $component_name
esp32:
board: nodemcu-32s
framework:
type: esp-idf
version: 5.1.2
platform_version: 6.5.0
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_test_file: $component_test_file

View File

@ -1,20 +0,0 @@
esphome:
name: componenttestesp32s2idf51
friendly_name: $component_name
esp32:
board: esp32-s2-saola-1
variant: ESP32S2
framework:
type: esp-idf
version: 5.1.2
platform_version: 6.5.0
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_test_file: $component_test_file

View File

@ -1,20 +0,0 @@
esphome:
name: componenttestesp32s3idf51
friendly_name: $component_name
esp32:
board: esp32s3box
variant: ESP32S3
framework:
type: esp-idf
version: 5.1.2
platform_version: 6.5.0
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_test_file: $component_test_file