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

View File

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

View File

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

View File

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

View File

@ -163,6 +163,18 @@ ENTRYPOINT ["/entrypoint.sh"]
CMD ["dashboard", "/config"] 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 ======================= # ======================= hassio-type image =======================
@ -194,7 +206,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
# Labels # Labels
LABEL \ LABEL \
io.hass.name="ESPHome" \ 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.type="addon" \
io.hass.version="${BUILD_VERSION}" io.hass.version="${BUILD_VERSION}"
# io.hass.arch is inherited from addon-debian-base # io.hass.arch is inherited from addon-debian-base
@ -209,17 +221,22 @@ ENV \
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
RUN \ 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 # Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
clang-format-13=1:13.0.1-11+b2 \ clang-format-13=1:13.0.1-11+b2 \
clang-tidy-14=1:14.0.6-12 \
patch=2.7.6-7 \ patch=2.7.6-7 \
software-properties-common=0.99.30-4.1~deb12u1 \ software-properties-common=0.99.30-4.1~deb12u1 \
nano=7.2-1+deb12u1 \ nano=7.2-1+deb12u1 \
build-essential=12.9 \ build-essential=12.9 \
python3-dev=3.11.2-1+b1 \ 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/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /var/lib/apt/lists/*

View File

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

View File

@ -2,7 +2,6 @@ import logging
from esphome import automation, core from esphome import automation, core
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import font
import esphome.components.image as espImage import esphome.components.image as espImage
from esphome.components.image import ( from esphome.components.image import (
CONF_USE_TRANSPARENCY, 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( 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)) if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
this->trigger(from, 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]; char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format); size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0) 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) { 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) { 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) { 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) { 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))); 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`. /** 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 x The x coordinate of the text alignment anchor point.

View File

@ -65,6 +65,8 @@ _LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["preferences"] AUTO_LOAD = ["preferences"]
CONF_RELEASE = "release"
def set_core_data(config): def set_core_data(config):
CORE.data[KEY_ESP32] = {} 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" 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 # format the given arduino (https://github.com/espressif/esp-idf/releases) version to
# a PIO platformio/framework-espidf value # a PIO platformio/framework-espidf value
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf # 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: # 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 # The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases # - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf # - 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 # The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases # - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 # - 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): def _arduino_check_versions(value):
@ -286,8 +316,8 @@ def _arduino_check_versions(value):
def _esp_idf_check_versions(value): def _esp_idf_check_versions(value):
value = value.copy() value = value.copy()
lookups = { lookups = {
"dev": (cv.Version(5, 1, 2), "https://github.com/espressif/esp-idf.git"), "dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 1, 2), None), "latest": (cv.Version(5, 1, 5), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, 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): if version < cv.Version(4, 0, 0):
raise cv.Invalid("Only ESP-IDF 4.0+ is supported.") raise cv.Invalid("Only ESP-IDF 4.0+ is supported.")
value[CONF_VERSION] = str(version) # flag this for later *before* we set value[CONF_PLATFORM_VERSION] below
value[CONF_SOURCE] = source or _format_framework_espidf_version(version) has_platform_ver = CONF_PLATFORM_VERSION in value
value[CONF_PLATFORM_VERSION] = value.get( value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION)) 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: if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
_LOGGER.warning( _LOGGER.warning(
"The selected ESP-IDF framework version is not the recommended one. " "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): def _parse_platform_version(value):
try: 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 # if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value) cv.platformio_version_constraint(value)
return f"platformio/espressif32@{value}" return f"platformio/espressif32@{value}"
@ -330,6 +404,14 @@ def _parse_platform_version(value):
return 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): def _detect_variant(value):
board = value[CONF_BOARD] board = value[CONF_BOARD]
if board in BOARDS: if board in BOARDS:
@ -355,24 +437,20 @@ def _detect_variant(value):
def final_validate(config): 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 return config
pio_flash_size_key = "board_upload.flash_size" pio_flash_size_key = "board_upload.flash_size"
pio_partitions_key = "board_build.partitions" pio_partitions_key = "board_build.partitions"
if ( if CONF_PARTITIONS in config and pio_partitions_key in pio_options:
CONF_PARTITIONS in config
and pio_partitions_key
in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS]
):
raise cv.Invalid( raise cv.Invalid(
f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32" f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32"
) )
if ( if pio_flash_size_key in pio_options:
pio_flash_size_key
in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS]
):
raise cv.Invalid( raise cv.Invalid(
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only" f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
) )
@ -412,6 +490,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, 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_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { 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_ESP_IDF")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
cg.add_build_flag("-Wno-nonnull-compare") cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option(
"platform_packages", cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
[f"platformio/framework-espidf@{conf[CONF_SOURCE]}"],
)
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years # 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. # This is espressif's own published version which is more up to date.
cg.add_platformio_option( cg.add_platformio_option(

View File

@ -2,8 +2,10 @@ from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
import esphome.config_validation as cv 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 import CORE
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
import esphome.final_validate as fv
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@jesserockz", "@Rapsssito"] CODEOWNERS = ["@jesserockz", "@Rapsssito"]
@ -50,6 +52,7 @@ TX_POWER_LEVELS = {
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(ESP32BLE), 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( cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
IO_CAPABILITY, lower=True IO_CAPABILITY, lower=True
), ),
@ -67,7 +70,22 @@ def validate_variant(_):
raise cv.Invalid(f"{variant} does not support Bluetooth") 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): 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_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])) 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) await cg.register_component(var, config)
if CORE.using_esp_idf: if CORE.using_esp_idf:

View File

@ -188,12 +188,20 @@ bool ESP32BLE::ble_setup_() {
} }
} }
std::string name = App.get_name(); std::string name;
if (name.length() > 20) { if (this->name_.has_value()) {
name = this->name_.value();
if (App.is_name_add_mac_suffix_enabled()) { if (App.is_name_add_mac_suffix_enabled()) {
name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address name += "-" + get_mac_address().substr(6);
} else { }
name = name.substr(0, 20); } 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 loop() override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void set_name(const std::string &name) { this->name_ = name; }
void advertising_start(); void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data); 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}; esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
uint32_t advertising_cycle_time_; uint32_t advertising_cycle_time_;
bool enable_on_boot_; bool enable_on_boot_;
optional<std::string> name_;
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() {
esp_err_t err; esp_err_t err;
this->advertising_data_.set_scan_rsp = false; 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_; 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) {

View File

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

View File

@ -1,4 +1,3 @@
from collections.abc import Iterable
import functools import functools
import hashlib import hashlib
import logging import logging
@ -8,7 +7,6 @@ import re
import freetype import freetype
import glyphsets import glyphsets
from packaging import version
import requests import requests
from esphome import core, external_files from esphome import core, external_files
@ -88,7 +86,7 @@ def flatten(lists) -> list:
return list(chain.from_iterable(lists)) 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 Check that the given font file actually contains the requested glyphs
:param file: A Truetype font file :param file: A Truetype font file
@ -177,24 +175,6 @@ def validate_glyphs(config):
return 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") FONT_EXTENSIONS = (".ttf", ".woff", ".otf")
@ -393,7 +373,9 @@ def font_file_schema(value):
# Default if no glyphs or glyphsets are provided # Default if no glyphs or glyphsets are provided
DEFAULT_GLYPHSET = "GF_Latin_Kernel" DEFAULT_GLYPHSET = "GF_Latin_Kernel"
# default for bitmap fonts # default for bitmap fonts
DEFAULT_GLYPHS = ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz<C2><B0>' DEFAULT_GLYPHS = (
' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
)
CONF_RAW_GLYPH_ID = "raw_glyph_id" 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 # 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) { if (state != this->settings_.beeper_state) {
this->settings_.beeper_state = state; this->settings_.beeper_state = state;
#ifdef USE_SWITCH #ifdef USE_SWITCH
this->beeper_switch_->publish_state(state); if (this->beeper_switch_ != nullptr) {
this->beeper_switch_->publish_state(state);
}
#endif #endif
this->hon_rtc_.save(&this->settings_); 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) { void HonClimate::set_quiet_mode_state(bool state) {
if (state != this->get_quiet_mode_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; this->settings_.quiet_mode_state = state;
#ifdef USE_SWITCH #ifdef USE_SWITCH
this->quiet_mode_switch_->publish_state(state); if (this->quiet_mode_switch_ != nullptr) {
this->quiet_mode_switch_->publish_state(state);
}
#endif #endif
this->hon_rtc_.save(&this->settings_); this->hon_rtc_.save(&this->settings_);
} }
@ -509,7 +518,7 @@ void HonClimate::initialization() {
} }
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing; this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
this->current_horizontal_swing_ = this->settings_.last_horizontal_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() { 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) { if (this->mode == CLIMATE_MODE_OFF) {
// AC just turned on from remote need to turn off display // AC just turned on from remote need to turn off display
this->force_send_control_ = true; 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; 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()) { if (new_quiet_mode != this->get_quiet_mode_state()) {
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF; this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
this->settings_.quiet_mode_state = new_quiet_mode; 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_); 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 // 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); this->sck_pin_->digital_write(true);
delayMicroseconds(1); delayMicroseconds(1);
this->sck_pin_->digital_write(false); this->sck_pin_->digital_write(false);

View File

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

View File

@ -33,14 +33,15 @@ enum SpeakerEventGroupBits : uint32_t {
STATE_RUNNING = (1 << 11), STATE_RUNNING = (1 << 11),
STATE_STOPPING = (1 << 12), STATE_STOPPING = (1 << 12),
STATE_STOPPED = (1 << 13), STATE_STOPPED = (1 << 13),
ERR_INVALID_FORMAT = (1 << 14), ERR_TASK_FAILED_TO_START = (1 << 14),
ERR_TASK_FAILED_TO_START = (1 << 15), ERR_ESP_INVALID_STATE = (1 << 15),
ERR_ESP_INVALID_STATE = (1 << 16), ERR_ESP_NOT_SUPPORTED = (1 << 16),
ERR_ESP_INVALID_ARG = (1 << 17), ERR_ESP_INVALID_ARG = (1 << 17),
ERR_ESP_INVALID_SIZE = (1 << 18), ERR_ESP_INVALID_SIZE = (1 << 18),
ERR_ESP_NO_MEM = (1 << 19), ERR_ESP_NO_MEM = (1 << 19),
ERR_ESP_FAIL = (1 << 20), 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 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; return ESP_ERR_INVALID_SIZE;
case SpeakerEventGroupBits::ERR_ESP_NO_MEM: case SpeakerEventGroupBits::ERR_ESP_NO_MEM:
return ESP_ERR_NO_MEM; return ESP_ERR_NO_MEM;
case SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED:
return ESP_ERR_NOT_SUPPORTED;
default: default:
return ESP_FAIL; return ESP_FAIL;
} }
@ -135,19 +138,19 @@ void I2SAudioSpeaker::loop() {
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); 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"); this->status_set_error("Failed to adjust I2S bus to match the incoming audio");
ESP_LOGE(TAG, ESP_LOGE(TAG,
"Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8, "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_.sample_rate, this->audio_stream_info_.channels,
this->audio_stream_info_.bits_per_sample); 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) { void I2SAudioSpeaker::set_volume(float volume) {
@ -236,13 +239,15 @@ void I2SAudioSpeaker::speaker_task(void *params) {
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING); xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING);
audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_; 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 * const uint32_t bytes_per_ms =
bytes_per_sample * number_of_channels; 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 = 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))) { if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) {
// Failed to allocate buffers // Failed to allocate buffers
@ -250,14 +255,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
this_speaker->delete_task_(dma_buffers_size); this_speaker->delete_task_(dma_buffers_size);
} }
if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) { if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) {
// 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
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING); xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING);
bool stop_gracefully = false; bool stop_gracefully = false;
@ -275,6 +273,12 @@ void I2SAudioSpeaker::speaker_task(void *params) {
stop_gracefully = true; 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; i2s_event_t i2s_event;
while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) { while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) {
if (i2s_event.type == I2S_EVENT_TX_Q_OVF) { 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::STATE_STOPPING);
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_INVALID_FORMAT);
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); 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: case ESP_ERR_NO_MEM:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM); xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
return true; return true;
case ESP_ERR_NOT_SUPPORTED:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED);
return true;
default: default:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL); xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL);
return true; return true;
@ -411,18 +415,40 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin
return ESP_OK; 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()) { if (!this->parent_->try_lock()) {
return ESP_ERR_INVALID_STATE; 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; int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000;
i2s_driver_config_t config = { i2s_driver_config_t config = {
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX), .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_, .bits_per_sample = this->bits_per_sample_,
.channel_format = this->channel_, .channel_format = channel,
.communication_format = this->i2s_comm_fmt_, .communication_format = this->i2s_comm_fmt_,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = DMA_BUFFERS_COUNT, .dma_buf_count = DMA_BUFFERS_COUNT,
@ -477,30 +503,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() {
return err; 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) { void I2SAudioSpeaker::delete_task_(size_t buffer_size) {
this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr 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); esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size);
/// @brief Starts the ESP32 I2S driver. /// @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 /// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out
/// the I2S port and uninstall the driver, if necessary. /// 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. /// @param audio_stream_info Stream information for the I2S driver.
/// ESP_ERR_INVALID_ARG if installing the driver or setting the data out pin fails due to a parameter error. /// @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_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_FAIL if setting the data out pin fails due to an IO error ESP_OK if successful
/// ESP_OK if successful esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info);
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);
/// @brief Deletes the speaker's task. /// @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 /// 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 from esphome import core, pins
import esphome.codegen as cg 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 from esphome.components.display import validate_rotation
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@ -147,7 +147,6 @@ def _validate(config):
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
font.validate_pillow_installed,
display.FULL_DISPLAY_SCHEMA.extend( display.FULL_DISPLAY_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(ILI9XXXDisplay), cv.GenerateID(): cv.declare_id(ILI9XXXDisplay),

View File

@ -10,7 +10,6 @@ import puremagic
from esphome import core, external_files from esphome import core, external_files
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import font
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DITHER, 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]): def load_svg_image(file: bytes, resize: tuple[int, int]):

View File

@ -38,7 +38,7 @@ def literal(arg):
def call_lambda(lamb: LambdaExpression): def call_lambda(lamb: LambdaExpression):
expr = lamb.content.strip() expr = lamb.content.strip()
if expr.startswith("return") and expr.endswith(";"): if expr.startswith("return") and expr.endswith(";"):
return expr[7:][:-1] return expr[6:][:-1].strip()
return f"{lamb}()" 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) { inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) {
lv_img_set_src(obj, image->get_lv_img_dsc()); 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 #endif // USE_LVGL_IMAGE
#ifdef USE_LVGL_ANIMIMG #ifdef USE_LVGL_ANIMIMG
inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) { 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), 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): class LineType(WidgetType):
def __init__(self): def __init__(self):
@ -43,6 +48,7 @@ class LineType(WidgetType):
LvType("lv_line_t"), LvType("lv_line_t"),
(CONF_MAIN,), (CONF_MAIN,),
LINE_SCHEMA, LINE_SCHEMA,
modify_schema=LINE_MODIFY_SCHEMA,
) )
async def to_code(self, w: Widget, config): 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 ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema
from ..types import LV_EVENT, char_ptr, lv_obj_t 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 .button import button_spec
from .buttonmatrix import ( from .buttonmatrix import (
BUTTONMATRIX_BUTTON_SCHEMA, BUTTONMATRIX_BUTTON_SCHEMA,
@ -119,6 +119,7 @@ async def msgbox_to_code(top_layer, conf):
button_style = {CONF_ITEMS: button_style} button_style = {CONF_ITEMS: button_style}
await set_obj_properties(buttonmatrix_widget, button_style) await set_obj_properties(buttonmatrix_widget, button_style)
await set_obj_properties(msgbox_widget, conf) await set_obj_properties(msgbox_widget, conf)
await add_widgets(msgbox_widget, conf)
async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action: async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action:
outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN") outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN")
if close_button: 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 { SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { auto reg_it = std::find_if(
return (r.start_address == start_address && r.register_type == register_type); 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); ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
} else { } else {
return reg_it->sensors; return reg_it->sensors;
@ -240,18 +240,18 @@ void ModbusController::update() {
// walk through the sensors and determine the register ranges to read // walk through the sensors and determine the register ranges to read
size_t ModbusController::create_register_ranges_() { size_t ModbusController::create_register_ranges_() {
register_ranges_.clear(); this->register_ranges_.clear();
if (this->parent_->role == modbus::ModbusRole::CLIENT && sensorset_.empty()) { if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) {
ESP_LOGW(TAG, "No sensors registered"); ESP_LOGW(TAG, "No sensors registered");
return 0; return 0;
} }
// iterator is sorted see SensorItemsComparator for details // iterator is sorted see SensorItemsComparator for details
auto ix = sensorset_.begin(); auto ix = this->sensorset_.begin();
RegisterRange r = {}; RegisterRange r = {};
uint8_t buffer_offset = 0; uint8_t buffer_offset = 0;
SensorItem *prev = nullptr; SensorItem *prev = nullptr;
while (ix != sensorset_.end()) { while (ix != this->sensorset_.end()) {
SensorItem *curr = *ix; SensorItem *curr = *ix;
ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count, 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 // this register can re-use the data from the previous register
// remove this sensore because start_address is changed (sort-order) // 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->start_address = r.start_address;
curr->offset += prev->offset; curr->offset += prev->offset;
sensorset_.insert(curr); this->sensorset_.insert(curr);
// move iterator backwards because it will be incremented later // move iterator backwards because it will be incremented later
ix--; ix--;
@ -293,14 +293,14 @@ size_t ModbusController::create_register_ranges_() {
// this register can extend the current range // this register can extend the current range
// remove this sensore because start_address is changed (sort-order) // 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->start_address = r.start_address;
curr->offset += buffer_offset; curr->offset += buffer_offset;
buffer_offset += curr->get_register_size(); buffer_offset += curr->get_register_size();
r.register_count += curr->register_count; r.register_count += curr->register_count;
sensorset_.insert(curr); this->sensorset_.insert(curr);
// move iterator backwards because it will be incremented later // move iterator backwards because it will be incremented later
ix--; ix--;
@ -327,7 +327,7 @@ size_t ModbusController::create_register_ranges_() {
ix++; ix++;
} else { } else {
ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); 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 = {}; r = {};
buffer_offset = 0; buffer_offset = 0;
// do not increment the iterator here because the current sensor has to be re-evaluated // 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) { if (r.register_count > 0) {
// Add the last range // Add the last range
ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); 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() { void ModbusController::dump_config() {
@ -352,18 +352,18 @@ void ModbusController::dump_config() {
ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_); ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGCONFIG(TAG, "sensormap"); 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", 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, static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count,
it->get_register_size()); it->get_register_size());
} }
ESP_LOGCONFIG(TAG, "ranges"); 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), 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); it.start_address, it.register_count, it.skip_updates);
} }
ESP_LOGCONFIG(TAG, "server registers"); 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, ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address,
static_cast<uint8_t>(r->value_type), r->register_count); static_cast<uint8_t>(r->value_type), r->register_count);
} }
@ -372,15 +372,15 @@ void ModbusController::dump_config() {
void ModbusController::loop() { void ModbusController::loop() {
// Incoming data to process? // Incoming data to process?
if (!incoming_queue_.empty()) { if (!this->incoming_queue_.empty()) {
auto &message = incoming_queue_.front(); auto &message = this->incoming_queue_.front();
if (message != nullptr) if (message != nullptr)
process_modbus_data_(message.get()); this->process_modbus_data_(message.get());
incoming_queue_.pop(); this->incoming_queue_.pop();
} else { } else {
// all messages processed send pending commands // 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_() { void ModbusController::dump_sensors_() {
ESP_LOGV(TAG, "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, 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); 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 // 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; } void set_register_size(uint8_t register_size) { response_bytes = register_size; }
ModbusRegisterType register_type; ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
SensorValueType sensor_value_type; SensorValueType sensor_value_type{SensorValueType::RAW};
uint16_t start_address; uint16_t start_address{0};
uint32_t bitmask; uint32_t bitmask{0};
uint8_t offset; uint8_t offset{0};
uint8_t register_count; uint8_t register_count{0};
uint8_t response_bytes{0}; uint8_t response_bytes{0};
uint16_t skip_updates; uint16_t skip_updates{0};
std::vector<uint8_t> custom_data{}; std::vector<uint8_t> custom_data{};
bool force_new_range{false}; bool force_new_range{false};
}; };
@ -261,9 +261,9 @@ class ServerRegister {
this->register_count = register_count; this->register_count = register_count;
this->read_lambda = std::move(read_lambda); this->read_lambda = std::move(read_lambda);
} }
uint16_t address; uint16_t address{0};
SensorValueType value_type; SensorValueType value_type{SensorValueType::RAW};
uint8_t register_count; uint8_t register_count{0};
std::function<float()> read_lambda; std::function<float()> read_lambda;
}; };
@ -312,11 +312,11 @@ struct RegisterRange {
class ModbusCommandItem { class ModbusCommandItem {
public: public:
static const size_t MAX_PAYLOAD_BYTES = 240; static const size_t MAX_PAYLOAD_BYTES = 240;
ModbusController *modbusdevice; ModbusController *modbusdevice{nullptr};
uint16_t register_address; uint16_t register_address{0};
uint16_t register_count; uint16_t register_count{0};
ModbusFunctionCode function_code; ModbusFunctionCode function_code{ModbusFunctionCode::CUSTOM};
ModbusRegisterType register_type; ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
on_data_func; on_data_func;
std::vector<uint8_t> payload = {}; std::vector<uint8_t> payload = {};
@ -493,23 +493,23 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
/// Collection of all sensors for this component /// Collection of all sensors for this component
SensorSet sensorset_; SensorSet sensorset_;
/// Collection of all server registers for this component /// Collection of all server registers for this component
std::vector<ServerRegister *> server_registers_; std::vector<ServerRegister *> server_registers_{};
/// Continuous range of modbus registers /// Continuous range of modbus registers
std::vector<RegisterRange> register_ranges_; std::vector<RegisterRange> register_ranges_{};
/// Hold the pending requests to be sent /// Hold the pending requests to be sent
std::list<std::unique_ptr<ModbusCommandItem>> command_queue_; std::list<std::unique_ptr<ModbusCommandItem>> command_queue_;
/// modbus response data waiting to get processed /// modbus response data waiting to get processed
std::queue<std::unique_ptr<ModbusCommandItem>> incoming_queue_; std::queue<std::unique_ptr<ModbusCommandItem>> incoming_queue_;
/// if duplicate commands can be sent /// if duplicate commands can be sent
bool allow_duplicate_commands_; bool allow_duplicate_commands_{false};
/// when was the last send operation /// 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 /// 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 /// if module didn't respond the last command
bool module_offline_; bool module_offline_{false};
/// how many updates to skip if module is offline /// 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 /// How many times we will retry a command if we get no response
uint8_t max_cmd_retries_{4}; uint8_t max_cmd_retries_{4};
/// Command sent callback /// Command sent callback

View File

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

View File

@ -27,7 +27,7 @@ void ModbusFloatOutput::write_state(float value) {
return; return;
} }
} else { } else {
value = multiply_by_ * value; value = this->multiply_by_ * value;
} }
// lambda didn't set payload // lambda didn't set payload
if (data.empty()) { if (data.empty()) {
@ -40,12 +40,13 @@ void ModbusFloatOutput::write_state(float value) {
// Create and send the write command // Create and send the write command
ModbusCommandItem write_cmd; ModbusCommandItem write_cmd;
if (this->register_count == 1 && !this->use_write_multiple_) { 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 { } 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); this->register_count, data);
} }
parent_->queue_command(write_cmd); this->parent_->queue_command(write_cmd);
} }
void ModbusFloatOutput::dump_config() { 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 // offset for coil and discrete inputs is the coil/register number not bytes
if (this->use_write_multiple_) { if (this->use_write_multiple_) {
std::vector<bool> states{state}; 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 { } 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); 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 dump_config() override;
void set_parent(ModbusController *parent) { this->parent_ = parent; } 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 // Do nothing
void parse_and_publish(const std::vector<uint8_t> &data) override{}; 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; void write_state(float value) override;
optional<write_transform_func_t> write_transform_func_{nullopt}; optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_; ModbusController *parent_{nullptr};
float multiply_by_{1.0}; float multiply_by_{1.0};
bool use_write_multiple_; bool use_write_multiple_{false};
}; };
class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem { 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; void write_state(bool state) override;
optional<write_transform_func_t> write_transform_func_{nullopt}; optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_; ModbusController *parent_{nullptr};
bool use_write_multiple_; bool use_write_multiple_{false};
}; };
} // namespace modbus_controller } // 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; const uint16_t write_address = this->start_address + this->offset / 2;
ModbusCommandItem write_cmd; ModbusCommandItem write_cmd;
if ((this->register_count == 1) && (!this->use_write_multiple_)) { 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 { } 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_) if (this->optimistic_)
this->publish_state(value); 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; void control(const std::string &value) override;
protected: protected:
std::vector<int64_t> mapping_; std::vector<int64_t> mapping_{};
ModbusController *parent_; ModbusController *parent_{nullptr};
bool use_write_multiple_{false}; bool use_write_multiple_{false};
bool optimistic_{false}; bool optimistic_{false};
optional<transform_func_t> transform_func_; optional<transform_func_t> transform_func_{nullopt};
optional<write_transform_func_t> write_transform_func_; optional<write_transform_func_t> write_transform_func_{nullopt};
}; };
} // namespace modbus_controller } // 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 // offset for coil and discrete inputs is the coil/register number not bytes
if (this->use_write_multiple_) { if (this->use_write_multiple_) {
std::vector<bool> states{state}; 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 { } 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 { } else {
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2 // since offset is in bytes and a register is 16 bits we get the start by adding offset/2
if (this->use_write_multiple_) { if (this->use_write_multiple_) {
std::vector<uint16_t> bool_states(1, state ? (0xFFFF & this->bitmask) : 0); 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); bool_states);
} else { } 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); state ? 0xFFFF & this->bitmask : 0u);
} }
} }
} }
this->parent_->queue_command(cmd); this->parent_->queue_command(cmd);
publish_state(state); this->publish_state(state);
} }
// ModbusSwitch end // ModbusSwitch end
} // namespace modbus_controller } // 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; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected: protected:
ModbusController *parent_; ModbusController *parent_{nullptr};
bool use_write_multiple_; bool use_write_multiple_{false};
optional<transform_func_t> publish_transform_func_{nullopt}; optional<transform_func_t> publish_transform_func_{nullopt};
optional<write_transform_func_t> write_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_USE_ABBREVIATIONS,
CONF_USERNAME, CONF_USERNAME,
CONF_WILL_MESSAGE, CONF_WILL_MESSAGE,
CONF_PUBLISH_NAN_AS_NONE,
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
@ -296,6 +297,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, cv.Optional(CONF_QOS, default=0): cv.mqtt_qos,
} }
), ),
cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean,
} }
), ),
validate_config, validate_config,
@ -449,6 +451,8 @@ async def to_code(config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) 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( 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_; } 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; } 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_; } 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() { void MQTTClientComponent::disable_birth_message() {
this->birth_message_.topic = ""; this->birth_message_.topic = "";
this->recalculate_availability_(); 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_connect(mqtt_on_connect_callback_t &&callback);
void set_on_disconnect(mqtt_on_disconnect_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: protected:
void send_device_info_(); void send_device_info_();
@ -328,6 +332,8 @@ class MQTTClientComponent : public Component {
uint32_t connect_begin_; uint32_t connect_begin_;
uint32_t last_connected_{0}; uint32_t last_connected_{0};
optional<MQTTClientDisconnectReason> disconnect_reason_{}; optional<MQTTClientDisconnectReason> disconnect_reason_{};
bool publish_nan_as_none_{false};
}; };
extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) 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) { 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(); int8_t accuracy = this->sensor_->get_accuracy_decimals();
return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy));
} }

View File

@ -9,11 +9,6 @@
#include "lwip/apps/sntp.h" #include "lwip/apps/sntp.h"
#endif #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 esphome {
namespace sntp { namespace sntp {
@ -26,30 +21,29 @@ void SNTPComponent::setup() {
esp_sntp_stop(); esp_sntp_stop();
} }
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL); 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 #else
sntp_stop(); sntp_stop();
sntp_setoperatingmode(SNTP_OPMODE_POLL); sntp_setoperatingmode(SNTP_OPMODE_POLL);
#endif
sntp_setservername(0, strdup(this->server_1_.c_str())); size_t i = 0;
if (!this->server_2_.empty()) { for (auto &server : this->servers_) {
sntp_setservername(1, strdup(this->server_2_.c_str())); 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(); sntp_init();
#endif
} }
void SNTPComponent::dump_config() { void SNTPComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SNTP Time:"); ESP_LOGCONFIG(TAG, "SNTP Time:");
ESP_LOGCONFIG(TAG, " Server 1: '%s'", this->server_1_.c_str()); size_t i = 0;
ESP_LOGCONFIG(TAG, " Server 2: '%s'", this->server_2_.c_str()); for (auto &server : this->servers_) {
ESP_LOGCONFIG(TAG, " Server 3: '%s'", this->server_3_.c_str()); ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server.c_str());
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); }
} }
void SNTPComponent::update() { void SNTPComponent::update() {
#if !defined(USE_ESP_IDF) #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 /// \see https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
class SNTPComponent : public time::RealTimeClock { class SNTPComponent : public time::RealTimeClock {
public: 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 setup() override;
void dump_config() 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; } float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
void update() override; void update() override;
void loop() override; void loop() override;
protected: protected:
std::string server_1_; std::vector<std::string> servers_;
std::string server_2_;
std::string server_3_;
bool has_time_{false}; bool has_time_{false};
}; };

View File

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

View File

@ -692,6 +692,7 @@ CONF_PRIORITY = "priority"
CONF_PROJECT = "project" CONF_PROJECT = "project"
CONF_PROTOCOL = "protocol" CONF_PROTOCOL = "protocol"
CONF_PUBLISH_INITIAL_STATE = "publish_initial_state" CONF_PUBLISH_INITIAL_STATE = "publish_initial_state"
CONF_PUBLISH_NAN_AS_NONE = "publish_nan_as_none"
CONF_PULL_MODE = "pull_mode" CONF_PULL_MODE = "pull_mode"
CONF_PULLDOWN = "pulldown" CONF_PULLDOWN = "pulldown"
CONF_PULLUP = "pullup" 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) { bool str_equals_case_insensitive(const std::string &a, const std::string &b) {
return strcasecmp(a.c_str(), b.c_str()) == 0; 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_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) { bool str_endswith(const std::string &str, const std::string &end) {
return str.rfind(end) == (str.size() - end.size()); return str.rfind(end) == (str.size() - end.size());
} }
#endif
std::string str_truncate(const std::string &str, size_t length) { std::string str_truncate(const std::string &str, size_t length) {
return str.length() > length ? str.substr(0, length) : str; 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. ; This are common settings for the ESP32 (all variants) using IDF.
[common:esp32-idf] [common:esp32-idf]
extends = common: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 = 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 framework = espidf
lib_deps = lib_deps =

View File

@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "esphome" name = "esphome"
license = {text = "MIT"} 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" readme = "README.md"
authors = [ authors = [
{name = "The ESPHome Authors", email = "esphome@nabucasa.com"} {name = "The ESPHome Authors", email = "esphome@nabucasa.com"}

View File

@ -1,4 +1,4 @@
# Useful stuff when working in a development environment # 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-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 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)=", "-Ddeprecated(x)=",
# allow to condition code on the presence of clang-tidy # allow to condition code on the presence of clang-tidy
"-DCLANG_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 # (esp-idf) Fix __once_callable in some libstdc++ headers
"-D_GLIBCXX_HAVE_TLS", "-D_GLIBCXX_HAVE_TLS",
] ]
@ -238,7 +236,7 @@ def main():
failed_files = [] failed_files = []
try: try:
executable = get_binary("clang-tidy", 14) executable = get_binary("clang-tidy", 18)
task_queue = queue.Queue(args.jobs) task_queue = queue.Queue(args.jobs)
lock = threading.Lock() lock = threading.Lock()
for _ in range(args.jobs): for _ in range(args.jobs):
@ -283,12 +281,12 @@ def main():
print("Applying fixes ...") print("Applying fixes ...")
try: try:
try: try:
subprocess.call(["clang-apply-replacements-14", tmpdir]) subprocess.call(["clang-apply-replacements-18", tmpdir])
except FileNotFoundError: except FileNotFoundError:
subprocess.call(["clang-apply-replacements", tmpdir]) subprocess.call(["clang-apply-replacements", tmpdir])
except FileNotFoundError: except FileNotFoundError:
print( 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, file=sys.stderr,
) )
except: 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: substitutions:
- platform: ledc pwm_platform: ledc
pin: 14 output1_pin: "14"
id: gpio_output1 output2_pin: "15"
- platform: ledc output3_pin: "12"
pin: 15 output4_pin: "13"
id: gpio_output2 hbridge_on_pin: "4"
- platform: ledc hbridge_off_pin: "5"
pin: 12
id: gpio_output3
- platform: ledc
pin: 13
id: gpio_output4
light: packages:
- platform: hbridge common: !include common.yaml
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
fan: switch:
- platform: hbridge - id: !extend switch_hbridge
id: fan_hbridge pulse_length: 60ms
speed_count: 4 wait_time: 10ms
name: H-bridge Fan with Presets optimistic: false
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!

View File

@ -1,33 +1,16 @@
output: substitutions:
- platform: ledc pwm_platform: "ledc"
pin: 4 output1_pin: "4"
id: gpio_output1 output2_pin: "5"
- platform: ledc output3_pin: "6"
pin: 5 output4_pin: "7"
id: gpio_output2 hbridge_on_pin: "2"
- platform: ledc hbridge_off_pin: "3"
pin: 6
id: gpio_output3
- platform: ledc
pin: 7
id: gpio_output4
light: packages:
- platform: hbridge common: !include common.yaml
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
fan: switch:
- platform: hbridge - id: !extend switch_hbridge
id: fan_hbridge wait_time: 10ms
speed_count: 4 optimistic: true
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!

View File

@ -1,33 +1,15 @@
output: substitutions:
- platform: ledc pwm_platform: "ledc"
pin: 4 output1_pin: "4"
id: gpio_output1 output2_pin: "5"
- platform: ledc output3_pin: "6"
pin: 5 output4_pin: "7"
id: gpio_output2 hbridge_on_pin: "2"
- platform: ledc hbridge_off_pin: "3"
pin: 6
id: gpio_output3
- platform: ledc
pin: 7
id: gpio_output4
light: packages:
- platform: hbridge common: !include common.yaml
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
fan: switch:
- platform: hbridge - id: !extend switch_hbridge
id: fan_hbridge pulse_length: 60ms
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!

View File

@ -1,33 +1,16 @@
output: substitutions:
- platform: ledc pwm_platform: "ledc"
pin: 14 output1_pin: "14"
id: gpio_output1 output2_pin: "15"
- platform: ledc output3_pin: "12"
pin: 15 output4_pin: "13"
id: gpio_output2 hbridge_on_pin: "4"
- platform: ledc hbridge_off_pin: "5"
pin: 12
id: gpio_output3
- platform: ledc
pin: 13
id: gpio_output4
light: packages:
- platform: hbridge common: !include common.yaml
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
fan: switch:
- platform: hbridge - id: !extend switch_hbridge
id: fan_hbridge pulse_length: 60ms
speed_count: 4 wait_time: 10ms
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!

View File

@ -1,33 +1,16 @@
output: substitutions:
- platform: esp8266_pwm pwm_platform: "esp8266_pwm"
pin: 4 output1_pin: "4"
id: gpio_output1 output2_pin: "5"
- platform: esp8266_pwm output3_pin: "12"
pin: 5 output4_pin: "13"
id: gpio_output2 hbridge_on_pin: "14"
- platform: esp8266_pwm hbridge_off_pin: "15"
pin: 12
id: gpio_output3
- platform: esp8266_pwm
pin: 13
id: gpio_output4
light: packages:
- platform: hbridge common: !include common.yaml
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
fan: switch:
- platform: hbridge - id: !extend switch_hbridge
id: fan_hbridge pulse_length: 60ms
speed_count: 4 wait_time: 10ms
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!

View File

@ -1,33 +1,16 @@
output: substitutions:
- platform: rp2040_pwm pwm_platform: "rp2040_pwm"
pin: 4 output1_pin: "4"
id: gpio_output1 output2_pin: "5"
- platform: rp2040_pwm output3_pin: "6"
pin: 5 output4_pin: "7"
id: gpio_output2 hbridge_on_pin: "2"
- platform: rp2040_pwm hbridge_off_pin: "3"
pin: 6
id: gpio_output3
- platform: rp2040_pwm
pin: 7
id: gpio_output4
light: packages:
- platform: hbridge common: !include common.yaml
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
fan: switch:
- platform: hbridge - id: !extend switch_hbridge
id: fan_hbridge wait_time: 10ms
speed_count: 4 optimistic: true
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!

View File

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

View File

@ -60,6 +60,7 @@ mqtt:
- mqtt.publish: - mqtt.publish:
topic: some/topic topic: some/topic
payload: Good-bye payload: Good-bye
publish_nan_as_none: false
binary_sensor: binary_sensor:
- platform: template - 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