mirror of
https://github.com/esphome/esphome.git
synced 2026-07-06 23:19:36 +00:00
Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de6a4f793f | |||
| 0b311962b5 | |||
| b22def399f | |||
| cdd334284e | |||
| 9caf431740 | |||
| 468b32b986 | |||
| 39ad583b39 | |||
| 90403576c4 | |||
| 51fa25856d | |||
| 27d4b63a8a | |||
| 104c2f86f6 | |||
| e64a79f431 | |||
| b79db76099 | |||
| 66ab807596 | |||
| 2067da4ff5 | |||
| cfdd6d383f | |||
| bdd51bd476 | |||
| af7b6e3589 | |||
| fd86417bf5 | |||
| e095c457ff | |||
| 39c0f9cc84 | |||
| 3f94e6dcbb | |||
| b9588a8984 | |||
| 105d1362a2 | |||
| f6c260a2c5 | |||
| c720186170 | |||
| 8ab19c0242 | |||
| 2c24e82ba3 | |||
| fcfaa43e1e | |||
| 4c8e45a222 | |||
| 7878052533 | |||
| e94fcda8b7 | |||
| 5738c60206 | |||
| a035d84474 | |||
| 7ad43358c2 | |||
| 187cd51867 | |||
| fd16eec416 | |||
| ea14a93e67 | |||
| c456fc98ab | |||
| 711d8bb0ad | |||
| c3233739c5 | |||
| 5fe36a45ed | |||
| 0725157bf5 | |||
| 9f589ec4fc | |||
| 5417a16f9d | |||
| f447c88b4c | |||
| 0b48ca0027 | |||
| bef6773281 | |||
| 4f0968f1df | |||
| 648f5e1b06 | |||
| 65fc10d627 | |||
| 41cf842d5d | |||
| 792dfbcbbf | |||
| 06c7ac37d1 | |||
| 0666cb8635 | |||
| 5b8bf51022 | |||
| 4a7c58d5ae | |||
| 7522780c67 | |||
| e4a68c2da3 | |||
| 0427d20c5b | |||
| d25d160686 | |||
| 0e260e5cbb | |||
| 58780cd765 | |||
| 1fc73731a8 | |||
| e47feace11 | |||
| 4472d3b61b | |||
| 06c5bcbc66 | |||
| 6c44775bf5 | |||
| b127363fa0 | |||
| 782b58bbeb | |||
| 5de508ad8c | |||
| 054c8ba485 | |||
| 3b2be021b2 | |||
| 848defedd8 | |||
| 4c9ed129cf | |||
| c8b37fb1c8 | |||
| 1b556f5d0c | |||
| 8a3d0aeafb |
@@ -42,7 +42,7 @@ runs:
|
||||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
@@ -67,7 +67,7 @@ runs:
|
||||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Cache nRF Connect SDK
|
||||
description: >
|
||||
Resolve the pinned sdk-nrf version and cache the native sdk-nrf install
|
||||
(west workspace, Zephyr SDK toolchain, python env) at ~/.esphome-sdk-nrf.
|
||||
Every job that installs sdk-nrf natively (the nrf52 clang-tidy job and,
|
||||
once the component tests build natively, their batches) shares one cache.
|
||||
Callers must set env ESPHOME_SDK_NRF_PREFIX: ~/.esphome-sdk-nrf and have
|
||||
the Python venv already restored.
|
||||
inputs:
|
||||
restore-only:
|
||||
description: >
|
||||
When "true", only restore -- never save the cache, even on dev. Use from
|
||||
jobs that may not produce a complete install (e.g. a component batch
|
||||
that fails mid-install), so a partial install is never written.
|
||||
default: "false"
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Resolve sdk-nrf and toolchain versions for cache key
|
||||
# Both versions are pinned in code, not in any file that feeds the
|
||||
# other cache keys, so resolve them explicitly. Keying on them means
|
||||
# the cache invalidates when either is bumped (actions/cache never
|
||||
# overwrites a key).
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
version=$(python -c '
|
||||
from esphome.components.nrf52 import RECOMMENDED_SDK_NRF_VERSION
|
||||
from esphome.components.nrf52.framework import TOOLCHAIN_VERSION
|
||||
print(f"{RECOMMENDED_SDK_NRF_VERSION}-{TOOLCHAIN_VERSION}")')
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
# Mirror cache-esp-idf: only dev-branch runs write the shared cache (so it
|
||||
# lives in the default-branch scope readable by all PRs); PRs are
|
||||
# restore-only and never push multi-GB artifacts into their own scope.
|
||||
- name: Cache nRF Connect SDK install (write on dev)
|
||||
if: github.ref == 'refs/heads/dev' && inputs.restore-only != 'true'
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.esphome-sdk-nrf
|
||||
# yamllint disable-line rule:line-length
|
||||
key: ${{ runner.os }}-esphome-sdk-nrf-${{ steps.version.outputs.version }}-${{ hashFiles('esphome/components/nrf52/requirements.txt') }}
|
||||
- name: Cache nRF Connect SDK install (restore-only off dev)
|
||||
if: github.ref != 'refs/heads/dev' || inputs.restore-only == 'true'
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.esphome-sdk-nrf
|
||||
# yamllint disable-line rule:line-length
|
||||
key: ${{ runner.os }}-esphome-sdk-nrf-${{ steps.version.outputs.version }}-${{ hashFiles('esphome/components/nrf52/requirements.txt') }}
|
||||
@@ -32,7 +32,7 @@ runs:
|
||||
# detects the activated venv via ``VIRTUAL_ENV`` so the venv layout
|
||||
# downstream jobs rely on is preserved.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pin uv version so the action does not have to fetch the
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Set up uv
|
||||
# ``--system`` (below) installs into the setup-python interpreter;
|
||||
# no venv is created or restored by this workflow.
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pin uv version so the action does not have to fetch the
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Determine tag and whether to push
|
||||
id: tag
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
|
||||
- name: Log in to the GitHub container registry
|
||||
if: steps.tag.outputs.push == 'true'
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -151,10 +151,10 @@ jobs:
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
+31
-12
@@ -49,7 +49,7 @@ jobs:
|
||||
# detects the activated venv via ``VIRTUAL_ENV`` so downstream jobs
|
||||
# that ``. venv/bin/activate`` see an identical layout.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pin uv version so the action does not have to fetch the
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
script/build_codeowners.py --check
|
||||
script/build_language_schema.py --check
|
||||
script/generate-esp32-boards.py --check
|
||||
script/generate-rp2040-boards.py --check
|
||||
script/generate-rp2-boards.py --check
|
||||
script/ci_check_duplicate_test_ids.py
|
||||
|
||||
import-time:
|
||||
@@ -171,7 +171,7 @@ jobs:
|
||||
# install step (order-of-magnitude faster on cold boots,
|
||||
# with its own wheel cache). actions/setup-python still
|
||||
# provides the interpreter.
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pin uv version so the action does not have to fetch the
|
||||
@@ -372,7 +372,7 @@ jobs:
|
||||
- name: Set up uv
|
||||
# Only needed on cache miss to populate the venv.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pin uv version so the action does not have to fetch the
|
||||
@@ -475,6 +475,8 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
# esp32-arduino-tidy installs ESP-IDF natively; share the native IDF cache.
|
||||
ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf
|
||||
# nrf52-tidy installs sdk-nrf natively; pin it to a cacheable path.
|
||||
ESPHOME_SDK_NRF_PREFIX: ~/.esphome-sdk-nrf
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
@@ -491,7 +493,7 @@ jobs:
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ZEPHYR
|
||||
options: --environment nrf52-tidy --grep USE_ZEPHYR --grep USE_NRF52
|
||||
pio_cache_key: tidy-zephyr
|
||||
cache_sdk_nrf: true
|
||||
ignore_errors: false
|
||||
|
||||
steps:
|
||||
@@ -527,6 +529,10 @@ jobs:
|
||||
with:
|
||||
framework: arduino
|
||||
|
||||
- name: Cache nRF Connect SDK install
|
||||
if: matrix.cache_sdk_nrf
|
||||
uses: ./.github/actions/cache-sdk-nrf
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
@@ -795,7 +801,7 @@ jobs:
|
||||
if: always()
|
||||
|
||||
test-build-components-split:
|
||||
name: Test components batch (${{ matrix.components }})
|
||||
name: Test components batch (${{ matrix.batch.components }})
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
@@ -805,11 +811,14 @@ jobs:
|
||||
# esp32 component builds use the native ESP-IDF toolchain (default), so
|
||||
# share the tidy jobs' install location -- the restore below lands here.
|
||||
ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf
|
||||
# nrf52 component builds install sdk-nrf natively; pin it to the shared
|
||||
# cacheable path so the restore below lands where the build looks.
|
||||
ESPHOME_SDK_NRF_PREFIX: ~/.esphome-sdk-nrf
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }}
|
||||
matrix:
|
||||
components: ${{ fromJson(needs.determine-jobs.outputs.component-test-batches) }}
|
||||
batch: ${{ fromJson(needs.determine-jobs.outputs.component-test-batches) }}
|
||||
steps:
|
||||
- name: Show disk space
|
||||
run: |
|
||||
@@ -817,10 +826,10 @@ jobs:
|
||||
df -h
|
||||
|
||||
- name: List components
|
||||
run: echo ${{ matrix.components }}
|
||||
run: echo ${{ matrix.batch.components }}
|
||||
|
||||
- name: Cache apt packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
|
||||
with:
|
||||
packages: libsdl2-dev ccache
|
||||
version: 1.1
|
||||
@@ -833,11 +842,21 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache ESP-IDF install (restore-only)
|
||||
# A batch may contain no esp32 build, so never save -- just reuse the
|
||||
# shared install the dev tidy jobs already cached when present.
|
||||
# Only batches whose test platforms include esp32 need the native
|
||||
# ESP-IDF install; never save -- just reuse the shared install the
|
||||
# dev tidy jobs already cached when present.
|
||||
if: matrix.batch.needs_idf
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
with:
|
||||
restore-only: true
|
||||
- name: Cache nRF Connect SDK install (restore-only)
|
||||
# Only batches whose test platforms include nrf52 need the native
|
||||
# sdk-nrf install; never save -- just reuse the shared install the
|
||||
# dev nrf52 tidy job cached when present.
|
||||
if: matrix.batch.needs_nrf
|
||||
uses: ./.github/actions/cache-sdk-nrf
|
||||
with:
|
||||
restore-only: true
|
||||
- name: Validate and compile components with intelligent grouping
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -868,7 +887,7 @@ jobs:
|
||||
fi
|
||||
|
||||
# Convert space-separated components to comma-separated for Python script
|
||||
components_csv=$(echo "${{ matrix.components }}" | tr ' ' ',')
|
||||
components_csv=$(echo "${{ matrix.batch.components }}" | tr ' ' ',')
|
||||
|
||||
# Only isolate directly changed components when targeting dev branch
|
||||
# For beta/release branches, group everything for faster CI
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -84,6 +84,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -99,15 +99,15 @@ jobs:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -178,17 +178,17 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
if: matrix.registry == 'ghcr'
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
# setup-python interpreter so subsequent ``pre-commit`` /
|
||||
# ``script/run-in-env.py`` steps find the deps without a
|
||||
# ``uv run`` prefix.
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pin uv version so the action does not have to fetch the
|
||||
|
||||
@@ -427,13 +427,14 @@ This document provides essential context for AI models interacting with this pro
|
||||
|
||||
When a PR's only edits to a component are `validate.*.yaml` files (no source changes, no `test.*.yaml` changes, and the component isn't pulled in as a dependency of another changed component), CI skips the compile stage for that component entirely and only runs config validation. This is decided in `script/determine-jobs.py` via `_component_change_is_validate_only` and surfaced as the `validate_only_components` output that the `test-build-components-split` job consumes.
|
||||
|
||||
* **Test Grouping with Packages:** Components that use shared bus packages can be grouped together in CI to reduce build count. **Never define buses (uart, i2c, spi, modbus) directly in test YAML files** — always use packages from `test_build_components/common/`:
|
||||
* **Test Grouping with Packages:** Components that use shared bus packages can be grouped together in CI to reduce build count. **Never define buses (uart, i2c, spi, modbus) directly in test YAML files** — always use packages from `test_build_components/common/`.
|
||||
|
||||
All includes in test files must go through dict-style `packages:` so that batch grouping works correctly — the grouping scripts only understand dict-style packages. Never use list-style packages (`packages: [- !include ...]`) or top-level merge keys (`<<: !include common.yaml`). Bus packages are keyed by the bus name; the component's `common.yaml` is keyed by the component name (e.g. `cst328: !include common.yaml`):
|
||||
```yaml
|
||||
# test.esp32-idf.yaml — use packages for buses
|
||||
# test.esp32-idf.yaml — everything included via named packages
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart_115200/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
my_component: !include common.yaml
|
||||
```
|
||||
```yaml
|
||||
# common.yaml — component config only, NO bus definitions
|
||||
|
||||
+3
-1
@@ -122,6 +122,7 @@ esphome/components/cover/* @esphome/core
|
||||
esphome/components/cs5460a/* @balrog-kun
|
||||
esphome/components/cse7761/* @berfenger
|
||||
esphome/components/cst226/* @clydebarrow
|
||||
esphome/components/cst328/* @latonita
|
||||
esphome/components/cst816/* @clydebarrow
|
||||
esphome/components/cst9220/* @clydebarrow
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
@@ -425,7 +426,7 @@ esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rgbct/* @jesserockz
|
||||
esphome/components/ring_buffer/* @kahrendt
|
||||
esphome/components/router/speaker/* @kahrendt
|
||||
esphome/components/rp2040/* @jesserockz
|
||||
esphome/components/rp2/* @jesserockz
|
||||
esphome/components/rp2040_ble/* @bdraco
|
||||
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
|
||||
esphome/components/rp2040_pwm/* @jesserockz
|
||||
@@ -501,6 +502,7 @@ esphome/components/ssd1331_base/* @kbx81
|
||||
esphome/components/ssd1331_spi/* @kbx81
|
||||
esphome/components/ssd1351_base/* @kbx81
|
||||
esphome/components/ssd1351_spi/* @kbx81
|
||||
esphome/components/st7123/* @miniskipper
|
||||
esphome/components/st7567_base/* @latonita
|
||||
esphome/components/st7567_i2c/* @latonita
|
||||
esphome/components/st7567_spi/* @latonita
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.0.22
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.2.0
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
@@ -21,9 +21,10 @@ export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
# Keep the native ESP-IDF install on the persistent cache root, not the
|
||||
# Keep the native toolchain installs on the persistent cache root, not the
|
||||
# container's ephemeral user cache dir (re-downloaded on every restart).
|
||||
export ESPHOME_ESP_IDF_PREFIX="$(dirname "${pio_cache_base}")/idf"
|
||||
export ESPHOME_SDK_NRF_PREFIX="$(dirname "${pio_cache_base}")/sdk-nrf"
|
||||
|
||||
# If /build is mounted, use that as the build path
|
||||
# otherwise use path in /config (so that builds aren't lost on container restart)
|
||||
|
||||
@@ -15,9 +15,10 @@ export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
# Keep the native ESP-IDF install on the persistent /data volume, not the
|
||||
# Keep the native toolchain installs on the persistent /data volume, not the
|
||||
# container's ephemeral user cache dir (wiped on every add-on update/restart).
|
||||
export ESPHOME_ESP_IDF_PREFIX=/data/cache/idf
|
||||
export ESPHOME_SDK_NRF_PREFIX=/data/cache/sdk-nrf
|
||||
|
||||
if bashio::config.true 'leave_front_door_open'; then
|
||||
export DISABLE_HA_AUTHENTICATION=true
|
||||
|
||||
+18
-12
@@ -225,8 +225,9 @@ def _discover_mac_suffix_devices() -> list[str] | None:
|
||||
|
||||
Returns:
|
||||
- ``None`` when discovery isn't applicable (``name_add_mac_suffix`` off,
|
||||
mDNS disabled, or ``CORE.address`` is already an IP). Callers should
|
||||
then fall back to whatever default OTA address they normally use.
|
||||
mDNS disabled, or ``CORE.address`` isn't a ``.local`` mDNS address).
|
||||
Callers should then fall back to whatever default OTA address they
|
||||
normally use.
|
||||
- ``[]`` when discovery ran but found nothing. Callers should NOT fall
|
||||
back to the base name: with ``name_add_mac_suffix`` enabled, the base
|
||||
name by definition doesn't exist on the network.
|
||||
@@ -236,7 +237,7 @@ def _discover_mac_suffix_devices() -> list[str] | None:
|
||||
``aioesphomeapi`` via :func:`_resolve_network_devices`) reuses the IPs we
|
||||
already have without opening a second Zeroconf client.
|
||||
"""
|
||||
if not (has_name_add_mac_suffix() and has_mdns() and has_non_ip_address()):
|
||||
if not (has_name_add_mac_suffix() and has_mdns() and has_mdns_address()):
|
||||
return None
|
||||
from esphome.zeroconf import discover_mdns_devices
|
||||
|
||||
@@ -354,7 +355,7 @@ def choose_upload_log_host(
|
||||
bootsel_permission_error = False
|
||||
if (
|
||||
purpose == Purpose.UPLOADING
|
||||
and CORE.is_rp2040
|
||||
and CORE.is_rp2
|
||||
and (picotool := _find_picotool()) is not None
|
||||
):
|
||||
bootsel = detect_rp2040_bootsel(picotool)
|
||||
@@ -401,7 +402,7 @@ def choose_upload_log_host(
|
||||
# Show helpful BOOTSEL instructions for RP2040 when no BOOTSEL device is found
|
||||
if (
|
||||
purpose == Purpose.UPLOADING
|
||||
and CORE.is_rp2040
|
||||
and CORE.is_rp2
|
||||
and not any(get_port_type(opt[1]) == PortType.BOOTSEL for opt in options)
|
||||
):
|
||||
if bootsel_permission_error:
|
||||
@@ -503,17 +504,22 @@ def has_mdns() -> bool:
|
||||
|
||||
|
||||
def has_non_ip_address() -> bool:
|
||||
"""Check if CORE.address is set and is not an IP address."""
|
||||
"""Check if ``CORE.address`` is set and is not an IP address."""
|
||||
return CORE.address is not None and not is_ip_address(CORE.address)
|
||||
|
||||
|
||||
def has_mdns_address() -> bool:
|
||||
"""Check if ``CORE.address`` is a ``.local`` mDNS hostname."""
|
||||
return CORE.address is not None and CORE.address.endswith(".local")
|
||||
|
||||
|
||||
def has_ip_address() -> bool:
|
||||
"""Check if CORE.address is a valid IP address."""
|
||||
"""Check if ``CORE.address`` is a valid IP address."""
|
||||
return CORE.address is not None and is_ip_address(CORE.address)
|
||||
|
||||
|
||||
def has_resolvable_address() -> bool:
|
||||
"""Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address)."""
|
||||
"""Check if ``CORE.address`` is resolvable (via mDNS, DNS, or is an IP address)."""
|
||||
# Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable
|
||||
# The resolve_ip_address() function in helpers.py handles all types via AsyncResolver
|
||||
if CORE.address is None:
|
||||
@@ -532,7 +538,7 @@ def has_resolvable_address() -> bool:
|
||||
return True
|
||||
|
||||
# .local mDNS hostnames are only resolvable if mDNS is enabled
|
||||
return not CORE.address.endswith(".local")
|
||||
return not has_mdns_address()
|
||||
|
||||
|
||||
def has_name_add_mac_suffix() -> bool:
|
||||
@@ -979,7 +985,7 @@ def upload_using_platformio(config: ConfigType, port: str) -> int:
|
||||
# RP2040 platform-raspberrypi build recipe expects firmware.bin.signed for
|
||||
# the upload target, but 'nobuild' skips the build phase that creates it.
|
||||
# Create it here so the upload doesn't fail.
|
||||
if CORE.is_rp2040:
|
||||
if CORE.is_rp2:
|
||||
idedata = toolchain.get_idedata(config)
|
||||
build_dir = Path(idedata.firmware_elf_path).parent
|
||||
firmware_bin = build_dir / "firmware.bin"
|
||||
@@ -1167,7 +1173,7 @@ def upload_program(
|
||||
if CORE.is_esp32 or CORE.is_esp8266:
|
||||
file = getattr(args, "file", None)
|
||||
exit_code = upload_using_esptool(config, host, file, args.upload_speed)
|
||||
elif CORE.is_rp2040 or CORE.is_libretiny:
|
||||
elif CORE.is_rp2 or CORE.is_libretiny:
|
||||
exit_code = upload_using_platformio(config, host)
|
||||
# else: Unknown target platform, exit_code remains 1
|
||||
|
||||
@@ -1641,7 +1647,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
|
||||
# After BOOTSEL upload, wait for a new serial port to appear
|
||||
# so it shows up in the log chooser
|
||||
if successful_device is None and CORE.is_rp2040:
|
||||
if successful_device is None and CORE.is_rp2:
|
||||
_wait_for_serial_port(known_ports=pre_upload_ports)
|
||||
# If exactly one new serial port appeared, use it directly
|
||||
serial_ports = get_serial_ports()
|
||||
|
||||
@@ -6,7 +6,11 @@ from pathlib import Path
|
||||
from esphome.components.esp32 import get_esp32_variant, idf_version
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE
|
||||
from esphome.framework_helpers import get_project_compile_flags, get_project_link_flags
|
||||
from esphome.framework_helpers import (
|
||||
get_project_compile_flags,
|
||||
get_project_cxx_compile_flags,
|
||||
get_project_link_flags,
|
||||
)
|
||||
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||
|
||||
# Replaces the IDF default C++ standard (-std=gnu++2b appended to
|
||||
@@ -91,6 +95,14 @@ def get_project_cmakelists(minimal: bool = False) -> str:
|
||||
for flag in project_compile_opts
|
||||
)
|
||||
|
||||
# Flags registered via cg.add_cxx_build_flag() go on CXX_COMPILE_OPTIONS
|
||||
# (not COMPILE_OPTIONS) because GCC warns when a C++-only flag such as
|
||||
# -Wno-volatile is passed on a C compile.
|
||||
cxx_compile_options = "\n".join(
|
||||
f'idf_build_set_property(CXX_COMPILE_OPTIONS "{flag}" APPEND)'
|
||||
for flag in get_project_cxx_compile_flags()
|
||||
)
|
||||
|
||||
cpp_standard_options = (
|
||||
CPP_STANDARD_TEMPLATE.format(standard=CORE.cpp_standard)
|
||||
if CORE.cpp_standard
|
||||
@@ -155,6 +167,8 @@ include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
|
||||
|
||||
{cpp_standard_options}
|
||||
|
||||
{cxx_compile_options}
|
||||
|
||||
{extra_compile_options}
|
||||
|
||||
{managed_components_property}
|
||||
|
||||
@@ -108,7 +108,6 @@ Import("env")
|
||||
def write_cxx_flags_script() -> None:
|
||||
path = CORE.relative_build_path(CXX_FLAGS_FILE_NAME)
|
||||
contents = CXX_FLAGS_FILE_CONTENTS
|
||||
if not CORE.is_host:
|
||||
contents += 'env.Append(CXXFLAGS=["-Wno-volatile"])'
|
||||
contents += "\n"
|
||||
for flag in sorted(CORE.cxx_build_flags):
|
||||
contents += f'env.Append(CXXFLAGS=["{flag}"])\n'
|
||||
write_file_if_changed(path, contents)
|
||||
|
||||
@@ -25,6 +25,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
||||
add,
|
||||
add_build_flag,
|
||||
add_build_unflag,
|
||||
add_cxx_build_flag,
|
||||
add_define,
|
||||
add_global,
|
||||
add_library,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Importing `esphome.loader` here installs the component-alias
|
||||
# ``sys.meta_path`` finder before any submodule lookup runs. Without this,
|
||||
# `from esphome.components import <legacy_alias>` from a fresh interpreter
|
||||
# can race the finder install and raise ImportError, since the legacy
|
||||
# alias dir no longer exists on disk.
|
||||
from esphome import loader as _loader # noqa: F401
|
||||
|
||||
@@ -227,12 +227,12 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
|
||||
|
||||
def validate_adc_pin(value):
|
||||
if str(value).upper() == "VCC":
|
||||
if CORE.is_rp2040:
|
||||
if CORE.is_rp2:
|
||||
return pins.internal_gpio_input_pin_schema(29)
|
||||
return cv.only_on([PLATFORM_ESP8266])("VCC")
|
||||
|
||||
if str(value).upper() == "TEMPERATURE":
|
||||
return cv.only_on_rp2040("TEMPERATURE")
|
||||
return cv.only_on_rp2("TEMPERATURE")
|
||||
|
||||
if CORE.is_esp32:
|
||||
conf = pins.internal_gpio_input_pin_schema(value)
|
||||
@@ -261,11 +261,11 @@ def validate_adc_pin(value):
|
||||
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC")
|
||||
return conf
|
||||
|
||||
if CORE.is_rp2040:
|
||||
if CORE.is_rp2:
|
||||
conf = pins.internal_gpio_input_pin_schema(value)
|
||||
number = conf[CONF_NUMBER]
|
||||
if number not in (26, 27, 28, 29):
|
||||
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC")
|
||||
raise cv.Invalid("RP2: Only pins 26, 27, 28 and 29 support ADC")
|
||||
return conf
|
||||
|
||||
if CORE.is_libretiny:
|
||||
|
||||
@@ -123,9 +123,9 @@ class ADCSensor final : public sensor::Sensor, public PollingComponent, public v
|
||||
void set_autorange(bool autorange) { this->autorange_ = autorange; }
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
void set_is_temperature() { this->is_temperature_ = true; }
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_RP2
|
||||
|
||||
protected:
|
||||
uint8_t sample_count_{1};
|
||||
@@ -152,9 +152,9 @@ class ADCSensor final : public sensor::Sensor, public PollingComponent, public v
|
||||
static adc_oneshot_unit_handle_t shared_adc_handles[2];
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
bool is_temperature_{false};
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_RP2
|
||||
|
||||
#ifdef USE_ZEPHYR
|
||||
const struct adc_dt_spec *channel_ = nullptr;
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
|
||||
#include "adc_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
namespace esphome::adc {
|
||||
|
||||
static const char *const TAG = "adc.rp2040";
|
||||
static const char *const TAG = "adc.rp2";
|
||||
|
||||
void ADCSensor::setup() {
|
||||
static bool initialized = false;
|
||||
@@ -102,4 +102,4 @@ float ADCSensor::sample() {
|
||||
|
||||
} // namespace esphome::adc
|
||||
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_RP2
|
||||
@@ -201,7 +201,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||
"adc_sensor_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
|
||||
"adc_sensor_libretiny.cpp": {
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
|
||||
@@ -300,7 +300,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
CONF_LISTEN_BACKLOG,
|
||||
esp8266=1, # Limited RAM (~40KB free), LWIP raw sockets
|
||||
esp32=4, # More RAM (520KB), BSD sockets
|
||||
rp2040=1, # Limited RAM (264KB), LWIP raw sockets like ESP8266
|
||||
rp2=1, # Limited RAM (264KB), LWIP raw sockets like ESP8266
|
||||
bk72xx=4, # Moderate RAM, BSD-style sockets
|
||||
rtl87xx=4, # Moderate RAM, BSD-style sockets
|
||||
host=4, # Abundant resources
|
||||
@@ -311,7 +311,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
CONF_MAX_CONNECTIONS,
|
||||
esp8266=4, # ~40KB free RAM, each connection uses ~500-1000 bytes
|
||||
esp32=5, # 520KB RAM available
|
||||
rp2040=4, # 264KB RAM but LWIP constraints
|
||||
rp2=4, # 264KB RAM but LWIP constraints
|
||||
bk72xx=5, # Moderate RAM
|
||||
rtl87xx=5, # Moderate RAM
|
||||
host=8, # Abundant resources
|
||||
@@ -326,7 +326,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
CONF_MAX_SEND_QUEUE,
|
||||
esp8266=4, # Limited RAM, need to fail fast
|
||||
esp32=8, # More RAM, can buffer more
|
||||
rp2040=8, # Moderate RAM
|
||||
rp2=8, # Moderate RAM
|
||||
bk72xx=8, # Moderate RAM
|
||||
nrf52=8, # Moderate RAM
|
||||
rtl87xx=8, # Moderate RAM
|
||||
@@ -540,17 +540,20 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
# synchronous=False: when on_success/on_error is configured, play() stores the
|
||||
# trigger args until the HomeassistantActionResponse arrives, so non-owning args
|
||||
# (StringRef into the API receive buffer) must not be used.
|
||||
@automation.register_action(
|
||||
"homeassistant.action",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
synchronous=False,
|
||||
)
|
||||
@automation.register_action(
|
||||
"homeassistant.service",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
synchronous=False,
|
||||
)
|
||||
async def homeassistant_service_to_code(
|
||||
config: ConfigType,
|
||||
@@ -644,6 +647,8 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
# synchronous=True is safe here: the event schema has no on_success/on_error,
|
||||
# so play() never stores the trigger args.
|
||||
@automation.register_action(
|
||||
"homeassistant.event",
|
||||
HomeAssistantServiceCallAction,
|
||||
|
||||
@@ -1759,7 +1759,7 @@ bool APIConnection::send_device_info_response_() {
|
||||
// Manufacturer string - define once, handle ESP8266 PROGMEM separately
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
#define ESPHOME_MANUFACTURER "Espressif"
|
||||
#elif defined(USE_RP2040)
|
||||
#elif defined(USE_RP2)
|
||||
#define ESPHOME_MANUFACTURER "Raspberry Pi"
|
||||
#elif defined(USE_BK72XX)
|
||||
#define ESPHOME_MANUFACTURER "Beken"
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
#ifdef USE_ESP32_CRASH_HANDLER
|
||||
#include "esphome/components/esp32/crash_handler.h"
|
||||
#endif
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
#include "esphome/components/rp2040/crash_handler.h"
|
||||
#ifdef USE_RP2_CRASH_HANDLER
|
||||
#include "esphome/components/rp2/crash_handler.h"
|
||||
#endif
|
||||
#ifdef USE_ESP8266_CRASH_HANDLER
|
||||
#include "esphome/components/esp8266/crash_handler.h"
|
||||
@@ -279,8 +279,8 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
esp32::crash_handler_log();
|
||||
esp32::crash_handler_clear();
|
||||
#endif
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
rp2040::crash_handler_log();
|
||||
#ifdef USE_RP2_CRASH_HANDLER
|
||||
rp2::crash_handler_log();
|
||||
#endif
|
||||
#ifdef USE_ESP8266_CRASH_HANDLER
|
||||
esp8266::crash_handler_log();
|
||||
|
||||
@@ -13,7 +13,7 @@ def AUTO_LOAD() -> list[str]:
|
||||
if (
|
||||
not CORE.is_esp32
|
||||
and not CORE.is_esp8266
|
||||
and not CORE.is_rp2040
|
||||
and not CORE.is_rp2
|
||||
and not CORE.is_libretiny
|
||||
):
|
||||
return ["socket"]
|
||||
@@ -37,7 +37,7 @@ async def to_code(config):
|
||||
elif CORE.is_esp8266:
|
||||
# https://github.com/ESP32Async/ESPAsyncTCP
|
||||
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")
|
||||
elif CORE.is_rp2040:
|
||||
elif CORE.is_rp2:
|
||||
# https://github.com/ayushsharma82/RPAsyncTCP
|
||||
# RPAsyncTCP is a drop-in replacement for AsyncTCP_RP2040W with better
|
||||
# ESPAsyncWebServer compatibility
|
||||
@@ -47,6 +47,6 @@ async def to_code(config):
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
# Exclude socket implementation for platforms that use AsyncTCP libraries
|
||||
if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny:
|
||||
if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2 or CORE.is_libretiny:
|
||||
return ["async_tcp_socket.cpp"]
|
||||
return []
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#elif defined(USE_ESP8266)
|
||||
// Use ESPAsyncTCP library for ESP8266 (always Arduino)
|
||||
#include <ESPAsyncTCP.h>
|
||||
#elif defined(USE_RP2040)
|
||||
#elif defined(USE_RP2)
|
||||
// Use RPAsyncTCP library for RP2040
|
||||
#include <RPAsyncTCP.h>
|
||||
#else
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "async_tcp_socket.h"
|
||||
|
||||
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
|
||||
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2) && !defined(USE_LIBRETINY) && \
|
||||
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
|
||||
|
||||
#include "esphome/components/network/util.h"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
|
||||
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2) && !defined(USE_LIBRETINY) && \
|
||||
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
|
||||
|
||||
#include "esphome/components/socket/socket.h"
|
||||
|
||||
@@ -379,9 +379,17 @@ void BluetoothProxy::bluetooth_set_connection_params(const api::BluetoothSetConn
|
||||
}
|
||||
|
||||
void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
|
||||
return;
|
||||
if (this->api_connection_ != nullptr && this->api_connection_ != api_connection) {
|
||||
// A previous subscriber still holds the slot. This is almost always a stale
|
||||
// connection from a client that dropped without a clean disconnect and has
|
||||
// not yet hit the keepalive timeout; rejecting the new subscriber would
|
||||
// silently starve it of advertisements until it reconnects, so the newest
|
||||
// subscriber wins instead.
|
||||
char old_peername[socket::SOCKADDR_STR_LEN];
|
||||
char new_peername[socket::SOCKADDR_STR_LEN];
|
||||
ESP_LOGW(TAG, "Subscription from %s (%s) replaces %s (%s)", api_connection->get_name(),
|
||||
api_connection->get_peername_to(new_peername), this->api_connection_->get_name(),
|
||||
this->api_connection_->get_peername_to(old_peername));
|
||||
}
|
||||
this->api_connection_ = api_connection;
|
||||
this->parent_->recalculate_advertisement_parser_types();
|
||||
|
||||
@@ -13,7 +13,7 @@ from esphome.const import (
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RP2,
|
||||
PLATFORM_RTL87XX,
|
||||
PlatformFramework,
|
||||
)
|
||||
@@ -54,7 +54,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RP2,
|
||||
PLATFORM_RTL87XX,
|
||||
]
|
||||
),
|
||||
@@ -105,7 +105,7 @@ async def to_code(config):
|
||||
if config[CONF_COMPRESSION] == "gzip":
|
||||
cg.add_define("USE_CAPTIVE_PORTAL_GZIP")
|
||||
|
||||
if CORE.using_arduino and (CORE.is_esp8266 or CORE.is_libretiny or CORE.is_rp2040):
|
||||
if CORE.using_arduino and (CORE.is_esp8266 or CORE.is_libretiny or CORE.is_rp2):
|
||||
cg.add_library("DNSServer", None)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@latonita"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
cst328_ns = cg.esphome_ns.namespace("cst328")
|
||||
@@ -0,0 +1,28 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from .. import cst328_ns
|
||||
from ..touchscreen import CST328ButtonListener, CST328Touchscreen
|
||||
|
||||
CONF_CST328_ID = "cst328_id"
|
||||
|
||||
CST328Button = cst328_ns.class_(
|
||||
"CST328Button",
|
||||
binary_sensor.BinarySensor,
|
||||
cg.Component,
|
||||
CST328ButtonListener,
|
||||
cg.Parented.template(CST328Touchscreen),
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CST328Button).extend(
|
||||
{
|
||||
cv.GenerateID(CONF_CST328_ID): cv.use_id(CST328Touchscreen),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_CST328_ID])
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "cst328_button.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::cst328 {
|
||||
static const char *const TAG = "cst328.binary_sensor";
|
||||
|
||||
void CST328Button::setup() {
|
||||
this->parent_->register_button_listener(this);
|
||||
this->publish_initial_state(false);
|
||||
}
|
||||
|
||||
void CST328Button::dump_config() { LOG_BINARY_SENSOR("", "CST328 Button", this); }
|
||||
|
||||
void CST328Button::update_button(bool state) { this->publish_state(state); }
|
||||
|
||||
} // namespace esphome::cst328
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "../touchscreen/cst328_touchscreen.h"
|
||||
|
||||
namespace esphome::cst328 {
|
||||
|
||||
class CST328Button : public binary_sensor::BinarySensor,
|
||||
public Component,
|
||||
public CST328ButtonListener,
|
||||
public Parented<CST328Touchscreen> {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update_button(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::cst328
|
||||
@@ -0,0 +1,38 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, touchscreen
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
|
||||
|
||||
from .. import cst328_ns
|
||||
|
||||
CST328Touchscreen = cst328_ns.class_(
|
||||
"CST328Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
CST328ButtonListener = cst328_ns.class_("CST328ButtonListener")
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
touchscreen.touchscreen_schema("100ms")
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CST328Touchscreen),
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x1A))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
||||
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
|
||||
if reset_pin := config.get(CONF_RESET_PIN):
|
||||
cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin)))
|
||||
@@ -0,0 +1,168 @@
|
||||
#include "cst328_touchscreen.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::cst328 {
|
||||
|
||||
static const char *const TAG = "cst328.touchscreen";
|
||||
|
||||
static const uint32_t CST328_BEFORE_RESET_TIMEOUT = 50; // 50 ms from datasheet
|
||||
static const uint32_t CST328_TRANSITION_TIMEOUT = 300; // 200 ms from datasheet, but typically much less
|
||||
static const uint16_t CST328_FW_CRC = 0xCACA; // Expected firmware CRC value
|
||||
static const uint8_t CST328_SYNC_BYTE = 0xAB; // Sync byte used in communication
|
||||
|
||||
static const uint8_t ZERO_BYTE = 0;
|
||||
|
||||
#define I2C_WARN_ON_ERROR(x, log_tag, format, ...) \
|
||||
do { \
|
||||
i2c::ErrorCode err_rc_ = (x); \
|
||||
if (err_rc_ != i2c::ERROR_OK) { \
|
||||
ESP_LOGW(log_tag, "%s(%d): [error %d] " format, __FUNCTION__, __LINE__, err_rc_, ##__VA_ARGS__); \
|
||||
this->status_set_warning(format); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define I2C_FAIL_ON_ERROR(x, log_tag, format, ...) \
|
||||
do { \
|
||||
i2c::ErrorCode err_rc_ = (x); \
|
||||
if (err_rc_ != i2c::ERROR_OK) { \
|
||||
ESP_LOGE(log_tag, "%s(%d): [error %d] " format, __FUNCTION__, __LINE__, err_rc_, ##__VA_ARGS__); \
|
||||
this->mark_failed(); \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
void CST328Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up CST328 Touchscreen...");
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
this->set_timeout(CST328_BEFORE_RESET_TIMEOUT, [this] { this->reset_device_(); });
|
||||
} else {
|
||||
this->continue_setup_();
|
||||
}
|
||||
}
|
||||
|
||||
void CST328Touchscreen::reset_device_() {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
this->set_timeout(CST328_TRANSITION_TIMEOUT, [this] { this->continue_setup_(); });
|
||||
}
|
||||
|
||||
void CST328Touchscreen::continue_setup_() {
|
||||
ESP_LOGV(TAG, "Continuing CST328 setup...");
|
||||
|
||||
uint8_t data_byte{0};
|
||||
uint8_t buf[24]{};
|
||||
|
||||
I2C_FAIL_ON_ERROR(this->write_register16(CST_WM_DEBUG_INFO, buf, 0), TAG, "Failed to enter debug/info mode");
|
||||
I2C_FAIL_ON_ERROR(this->read_register16(CST_REG_FW_CRC_AND_BOOT_TIME, buf, 4), TAG,
|
||||
"Failed to read FW CRC and boot time");
|
||||
|
||||
uint16_t fw_crc = buf[2] + (buf[3] << 8);
|
||||
if (fw_crc != CST328_FW_CRC) {
|
||||
ESP_LOGE(TAG, "Error: Firmware CRC mismatch, expected 0x%04X but got 0x%04X", CST328_FW_CRC, fw_crc);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
I2C_FAIL_ON_ERROR(this->read_register16(CST_REG_CHIP_TYPE_AND_PROJECT_ID, buf, 4), TAG,
|
||||
"Failed to read chip and project ID");
|
||||
|
||||
this->chip_id_ = buf[2] + (buf[3] << 8);
|
||||
this->project_id_ = buf[0] + (buf[1] << 8);
|
||||
ESP_LOGD(TAG, "Chip ID %X, project ID %X", this->chip_id_, this->project_id_);
|
||||
I2C_FAIL_ON_ERROR(this->read_register16(CST_REG_FW_REVISION, buf, 4), TAG, "Failed to read FW version");
|
||||
|
||||
this->fw_ver_major_ = buf[3];
|
||||
this->fw_ver_minor_ = buf[2];
|
||||
this->fw_build_ = buf[0] + (buf[1] << 8);
|
||||
ESP_LOGV(TAG, "FW version %d.%d.%d", this->fw_ver_major_, this->fw_ver_minor_, this->fw_build_);
|
||||
|
||||
if (i2c::ERROR_OK == this->read_register16(CST_REG_X_Y_RESOLUTION, buf, 4)) {
|
||||
this->x_raw_max_ = buf[0] + (buf[1] << 8);
|
||||
this->y_raw_max_ = buf[2] + (buf[3] << 8);
|
||||
} else {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
|
||||
I2C_WARN_ON_ERROR(this->write_register16(CST_WM_NORMAL, buf, 0), TAG, "Failed to enter normal mode");
|
||||
I2C_WARN_ON_ERROR(this->read_register16(CST_REG_TOUCH_INFORMATION, &data_byte, 1), TAG, "Failed to read sync");
|
||||
I2C_WARN_ON_ERROR(this->write_register16(CST_REG_TOUCH_INFORMATION, &CST328_SYNC_BYTE, 1), TAG,
|
||||
"Failed to write sync");
|
||||
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
|
||||
this->setup_complete_ = true;
|
||||
ESP_LOGV(TAG, "CST328 setup complete");
|
||||
}
|
||||
|
||||
void CST328Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CST328 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Chip ID: 0x%04X, Project ID: 0x%04X", this->chip_id_, this->project_id_);
|
||||
ESP_LOGCONFIG(TAG, " FW version: %d.%d.%d", this->fw_ver_major_, this->fw_ver_minor_, this->fw_build_);
|
||||
ESP_LOGCONFIG(TAG, " X/Y resolution: %d/%d", this->x_raw_max_, this->y_raw_max_);
|
||||
}
|
||||
|
||||
void CST328Touchscreen::update_button_state_(bool state) {
|
||||
if (this->button_touched_ == state) {
|
||||
return;
|
||||
}
|
||||
this->button_touched_ = state;
|
||||
for (auto *listener : this->button_listeners_) {
|
||||
listener->update_button(state);
|
||||
}
|
||||
}
|
||||
|
||||
void CST328Touchscreen::update_touches() {
|
||||
if (!this->setup_complete_) {
|
||||
this->skip_update_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t touch_data[CST328_TOUCH_DATA_SIZE];
|
||||
|
||||
this->status_clear_warning();
|
||||
|
||||
if (i2c::ERROR_OK != this->read_register16(CST_REG_TOUCH_INFORMATION, touch_data, CST328_TOUCH_DATA_SIZE)) {
|
||||
ESP_LOGW(TAG, "Failed to read touch data");
|
||||
this->status_set_warning();
|
||||
this->skip_update_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t touch_cnt = touch_data[CST_REG_FINGER_COUNT_IDX] & 0x0F;
|
||||
if (touch_cnt == 0 || touch_cnt > CST328_TOUCH_MAX_POINTS) {
|
||||
this->update_button_state_(false);
|
||||
} else {
|
||||
this->update_button_state_(true);
|
||||
|
||||
uint8_t data_idx = 0;
|
||||
for (uint8_t i = 0; i < touch_cnt; i++) {
|
||||
uint8_t id = touch_data[data_idx] >> 4;
|
||||
int16_t x = (touch_data[data_idx + 1] << 4) | ((touch_data[data_idx + 3] >> 4) & 0x0F);
|
||||
int16_t y = (touch_data[data_idx + 2] << 4) | (touch_data[data_idx + 3] & 0x0F);
|
||||
int16_t z = touch_data[data_idx + 4];
|
||||
|
||||
this->add_raw_touch_position_(id, x, y, z);
|
||||
data_idx += (i == 0) ? 7 : 5;
|
||||
}
|
||||
}
|
||||
|
||||
bool cleanup_error = false;
|
||||
cleanup_error |= (i2c::ERROR_OK != this->write_register16(CST_REG_TOUCH_FINGER_NUMBER, &ZERO_BYTE, 1));
|
||||
cleanup_error |= (i2c::ERROR_OK != this->write_register16(CST_REG_TOUCH_INFORMATION, &CST328_SYNC_BYTE, 1));
|
||||
|
||||
if (cleanup_error) {
|
||||
ESP_LOGW(TAG, "Failed to clean up touch registers");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::cst328
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome::cst328 {
|
||||
|
||||
static const uint8_t CST328_TOUCH_MAX_POINTS = 5;
|
||||
static const uint8_t CST328_TOUCH_DATA_SIZE = CST328_TOUCH_MAX_POINTS * 5 + 2;
|
||||
|
||||
static const uint16_t CST_REG_TOUCH_INFORMATION = 0xD000;
|
||||
static const uint16_t CST_REG_TOUCH_FINGER_NUMBER = 0xD005;
|
||||
|
||||
static const uint16_t CST_REG_FINGER_COUNT_IDX = CST_REG_TOUCH_FINGER_NUMBER - CST_REG_TOUCH_INFORMATION;
|
||||
|
||||
static const uint16_t CST_REG_X_Y_RESOLUTION = 0xD1F8;
|
||||
static const uint16_t CST_REG_FW_CRC_AND_BOOT_TIME = 0xD1FC;
|
||||
static const uint16_t CST_REG_CHIP_TYPE_AND_PROJECT_ID = 0xD204;
|
||||
static const uint16_t CST_REG_FW_REVISION = 0xD208;
|
||||
|
||||
static const uint16_t CST_WM_DEBUG_INFO = 0xD101;
|
||||
static const uint16_t CST_WM_NORMAL = 0xD109;
|
||||
|
||||
class CST328ButtonListener {
|
||||
public:
|
||||
virtual void update_button(bool state) = 0;
|
||||
};
|
||||
|
||||
class CST328Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void register_button_listener(CST328ButtonListener *listener) { this->button_listeners_.push_back(listener); }
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
||||
|
||||
protected:
|
||||
void update_touches() override;
|
||||
void reset_device_();
|
||||
void continue_setup_();
|
||||
void update_button_state_(bool state);
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{};
|
||||
GPIOPin *reset_pin_{};
|
||||
|
||||
std::vector<CST328ButtonListener *> button_listeners_;
|
||||
bool button_touched_{};
|
||||
|
||||
uint16_t chip_id_{};
|
||||
uint16_t project_id_{};
|
||||
uint8_t fw_ver_major_{};
|
||||
uint8_t fw_ver_minor_{};
|
||||
uint16_t fw_build_{};
|
||||
|
||||
bool setup_complete_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::cst328
|
||||
@@ -70,7 +70,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
},
|
||||
"debug_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||
"debug_host.cpp": {PlatformFramework.HOST_NATIVE},
|
||||
"debug_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||
"debug_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
|
||||
"debug_libretiny.cpp": {
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "debug_component.h"
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <Arduino.h>
|
||||
@@ -9,8 +9,8 @@
|
||||
#else
|
||||
#include <hardware/structs/vreg_and_chip_reset.h>
|
||||
#endif
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
#include "esphome/components/rp2040/crash_handler.h"
|
||||
#ifdef USE_RP2_CRASH_HANDLER
|
||||
#include "esphome/components/rp2/crash_handler.h"
|
||||
#endif
|
||||
namespace esphome::debug {
|
||||
|
||||
@@ -41,8 +41,8 @@ const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFE
|
||||
|
||||
if (watchdog_caused_reboot()) {
|
||||
bool handled = false;
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
if (rp2040::crash_handler_has_data()) {
|
||||
#ifdef USE_RP2_CRASH_HANDLER
|
||||
if (rp2::crash_handler_has_data()) {
|
||||
pos = buf_append_str(buf, size, pos, "Crash (HardFault)|");
|
||||
handled = true;
|
||||
}
|
||||
@@ -112,6 +112,7 @@ def model_schema(config):
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=core.TimePeriod(milliseconds=500)),
|
||||
),
|
||||
**model.get_config_options(),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -198,6 +199,7 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
await display.register_display(var, config)
|
||||
config = await model.to_code(var, config)
|
||||
await spi.register_spi_device(var, config, write_only=True)
|
||||
|
||||
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
#include "epaper_spi_t133a01.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
static constexpr const char *const TAG = "epaper_spi.t133a01";
|
||||
|
||||
// Color indices used in the 4bpp buffer (sprite-side)
|
||||
// These MUST match the Arduino GFX TFT_eSPI.h color definitions and
|
||||
// the remap_color()/COLOR_GET mapping:
|
||||
// 0x0F=BLACK, 0x00=WHITE, 0x02=GREEN, 0x06=RED, 0x0B=YELLOW, 0x0D=BLUE
|
||||
static constexpr uint8_t T133A01_BLACK = 0x0F;
|
||||
static constexpr uint8_t T133A01_WHITE = 0x00;
|
||||
static constexpr uint8_t T133A01_GREEN = 0x02;
|
||||
static constexpr uint8_t T133A01_RED = 0x06;
|
||||
static constexpr uint8_t T133A01_YELLOW = 0x0B;
|
||||
static constexpr uint8_t T133A01_BLUE = 0x0D;
|
||||
|
||||
// T133A01 register addresses
|
||||
static constexpr uint8_t R00_PSR = 0x00;
|
||||
static constexpr uint8_t R01_PWR = 0x01;
|
||||
static constexpr uint8_t R02_POF = 0x02;
|
||||
static constexpr uint8_t R04_PON = 0x04;
|
||||
static constexpr uint8_t R05_BTST_N = 0x05;
|
||||
static constexpr uint8_t R06_BTST_P = 0x06;
|
||||
static constexpr uint8_t R10_DTM = 0x10;
|
||||
static constexpr uint8_t R12_DRF = 0x12;
|
||||
static constexpr uint8_t R50_CDI = 0x50;
|
||||
static constexpr uint8_t R61_TRES = 0x61;
|
||||
static constexpr uint8_t RA5_DCDC = 0xA5;
|
||||
static constexpr uint8_t RE0_CCSET = 0xE0;
|
||||
static constexpr uint8_t RE3_PWS = 0xE3;
|
||||
|
||||
/**
|
||||
* COLOR_GET remap table from T133A01_Defines.h.
|
||||
* Translates 4bpp sprite color index to the hardware pixel encoding.
|
||||
* Sprite: 0x0F=BLACK 0x00=WHITE 0x02=GREEN 0x06=RED 0x0B=YELLOW 0x0D=BLUE
|
||||
* HW: 0x00=BLACK 0x01=WHITE 0x06=GREEN 0x03=RED 0x02=YELLOW 0x05=BLUE
|
||||
*/
|
||||
uint8_t EPaperT133A01::remap_color(uint8_t index) {
|
||||
switch (index & 0x0F) {
|
||||
case 0x0F:
|
||||
return 0x00; // Black
|
||||
case 0x00:
|
||||
return 0x01; // White
|
||||
case 0x02:
|
||||
return 0x06; // Green
|
||||
case 0x06:
|
||||
return 0x03; // Red
|
||||
case 0x0B:
|
||||
return 0x02; // Yellow
|
||||
case 0x0D:
|
||||
return 0x05; // Blue
|
||||
default:
|
||||
return 0x01; // White fallback
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an ESPHome Color to a 4-bit sprite color index.
|
||||
* Index values match the Arduino GFX TFT_eSPI color definitions:
|
||||
* 0x00=WHITE, 0x02=GREEN, 0x06=RED, 0x0B=YELLOW, 0x0D=BLUE, 0x0F=BLACK
|
||||
*/
|
||||
uint8_t EPaperT133A01::color_to_index(Color color) {
|
||||
unsigned char max_rgb = std::max({color.r, color.g, color.b});
|
||||
unsigned char min_rgb = std::min({color.r, color.g, color.b});
|
||||
|
||||
// Check for grayscale
|
||||
if ((max_rgb - min_rgb) < 50) {
|
||||
if ((static_cast<int>(color.r) + color.g + color.b) > 382) {
|
||||
return T133A01_WHITE;
|
||||
}
|
||||
return T133A01_BLACK;
|
||||
}
|
||||
|
||||
bool r_on = (color.r > 128);
|
||||
bool g_on = (color.g > 128);
|
||||
bool b_on = (color.b > 128);
|
||||
|
||||
if (r_on && g_on && !b_on)
|
||||
return T133A01_YELLOW;
|
||||
if (r_on && !g_on && !b_on)
|
||||
return T133A01_RED;
|
||||
if (!r_on && g_on && !b_on)
|
||||
return T133A01_GREEN;
|
||||
if (!r_on && !g_on && b_on)
|
||||
return T133A01_BLUE;
|
||||
// Handle mixed colors: map to nearest primary
|
||||
if (!r_on && g_on && b_on)
|
||||
return T133A01_GREEN; // Cyan -> Green
|
||||
if (r_on && !g_on)
|
||||
return T133A01_RED; // Magenta -> Red
|
||||
if (r_on)
|
||||
return T133A01_WHITE;
|
||||
return T133A01_BLACK;
|
||||
}
|
||||
|
||||
void EPaperT133A01::setup() {
|
||||
// Base setup initialises the buffer, the standard pins and the SPI bus.
|
||||
EPaperBase::setup();
|
||||
|
||||
// Both chip-selects are driven directly by this driver (the dual-CS
|
||||
// protocol needs CS held HIGH while CS1 receives data, which the SPI
|
||||
// bus cannot do). Start both deselected (HIGH).
|
||||
this->cs_pin_->setup();
|
||||
this->cs_pin_->digital_write(true);
|
||||
this->cs1_pin_->setup();
|
||||
this->cs1_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
bool EPaperT133A01::reset() {
|
||||
for (auto *enable_pin : this->enable_pins_) {
|
||||
enable_pin->digital_write(true);
|
||||
}
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
if (this->state_ == EPaperState::RESET) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
return false;
|
||||
}
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the T133A01 display.
|
||||
*
|
||||
* The init sequence uses a mix of CS and CS1 commands as per the Arduino driver.
|
||||
* The base class init_sequence is NOT used for T133A01 because the dual-CS
|
||||
* protocol requires per-command routing.
|
||||
*/
|
||||
bool EPaperT133A01::initialise(bool partial) {
|
||||
// Init sequence mirrors the Arduino GFX library's EPD_INIT() macro
|
||||
// (T133A01_Defines.h). Commands routed to CS only leave CS1 deselected;
|
||||
// commands routed to both controllers assert CS and CS1 together.
|
||||
|
||||
// 0x74 - panel config (CS only)
|
||||
this->write_command_(0x74, {0x00, 0x0C, 0x0C, 0xD9, 0xDD, 0xDD, 0x15, 0x15, 0x55}, true, false);
|
||||
delay(10);
|
||||
|
||||
// 0xF0 - panel config (CS + CS1)
|
||||
this->write_command_(0xF0, {0x49, 0x55, 0x13, 0x5D, 0x05, 0x10}, true, true);
|
||||
delay(10);
|
||||
|
||||
// PSR - Panel Setting Register (CS + CS1)
|
||||
this->write_command_(0x00, {0xDF, 0x69}, true, true);
|
||||
delay(10);
|
||||
|
||||
// DCDC (CS only)
|
||||
this->write_command_(RA5_DCDC, {0x44, 0x54, 0x00}, true, false);
|
||||
delay(10);
|
||||
|
||||
// CDI (CS + CS1)
|
||||
this->write_command_(R50_CDI, {0x37}, true, true);
|
||||
delay(10);
|
||||
|
||||
// 0x60 (CS + CS1)
|
||||
this->write_command_(0x60, {0x03, 0x03}, true, true);
|
||||
delay(10);
|
||||
|
||||
// 0x86 (CS + CS1)
|
||||
this->write_command_(0x86, {0x10}, true, true);
|
||||
delay(10);
|
||||
|
||||
// PWS - Phase Width Setting (CS + CS1)
|
||||
this->write_command_(RE3_PWS, {0x22}, true, true);
|
||||
delay(10);
|
||||
|
||||
// TRES - Resolution Setting (CS + CS1).
|
||||
// With width=1200, height=1600: first word = width = 1200, second word = height/2 = 800.
|
||||
this->write_command_(R61_TRES,
|
||||
{(uint8_t) (this->width_ >> 8), (uint8_t) (this->width_ & 0xFF),
|
||||
(uint8_t) ((this->height_ / 2) >> 8), (uint8_t) ((this->height_ / 2) & 0xFF)},
|
||||
true, true);
|
||||
delay(10);
|
||||
|
||||
// PWR - Power Setting (CS only)
|
||||
this->write_command_(R01_PWR, {0x0F, 0x00, 0x28, 0x2C, 0x28, 0x38}, true, false);
|
||||
delay(10);
|
||||
|
||||
// 0xB6 (CS only)
|
||||
this->write_command_(0xB6, {0x07}, true, false);
|
||||
delay(10);
|
||||
|
||||
// BTST_P (CS only)
|
||||
this->write_command_(R06_BTST_P, {0xE0, 0x20}, true, false);
|
||||
delay(10);
|
||||
|
||||
// 0xB7 (CS only)
|
||||
this->write_command_(0xB7, {0x01}, true, false);
|
||||
delay(10);
|
||||
|
||||
// BTST_N (CS only)
|
||||
this->write_command_(R05_BTST_N, {0xE0, 0x20}, true, false);
|
||||
delay(10);
|
||||
|
||||
// 0xB0 (CS only)
|
||||
this->write_command_(0xB0, {0x01}, true, false);
|
||||
delay(10);
|
||||
|
||||
// 0xB1 (CS only)
|
||||
this->write_command_(0xB1, {0x02}, true, false);
|
||||
delay(10);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EPaperT133A01::write_command_(uint8_t command, const uint8_t *data, size_t length, bool use_cs, bool use_cs1) {
|
||||
ESP_LOGV(TAG, "Command: 0x%02X, Length: %u, CS: %d, CS1: %d", command, (unsigned) length, use_cs, use_cs1);
|
||||
// Chip-selects are active-low: assert the requested controllers.
|
||||
this->cs_pin_->digital_write(!use_cs);
|
||||
this->cs1_pin_->digital_write(!use_cs1);
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
this->write_byte(command);
|
||||
if (length > 0) {
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_array(data, length);
|
||||
}
|
||||
this->disable();
|
||||
this->cs_pin_->digital_write(true);
|
||||
this->cs1_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void EPaperT133A01::fill(Color color) {
|
||||
if (this->get_clipping().is_set()) {
|
||||
EPaperBase::fill(color);
|
||||
return;
|
||||
}
|
||||
auto pixel_color = color_to_index(color);
|
||||
this->buffer_.fill(pixel_color + (pixel_color << 4));
|
||||
}
|
||||
|
||||
void EPaperT133A01::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->rotate_coordinates_(x, y))
|
||||
return;
|
||||
auto pixel_bits = color_to_index(color);
|
||||
uint32_t pixel_position = x + y * this->get_width_internal();
|
||||
uint32_t byte_position = pixel_position / 2;
|
||||
auto original = this->buffer_[byte_position];
|
||||
if ((pixel_position & 1) != 0) {
|
||||
this->buffer_[byte_position] = (original & 0xF0) | pixel_bits;
|
||||
} else {
|
||||
this->buffer_[byte_position] = (original & 0x0F) | (pixel_bits << 4);
|
||||
}
|
||||
}
|
||||
|
||||
void EPaperT133A01::power_on() {
|
||||
ESP_LOGV(TAG, "Power on");
|
||||
this->write_command_(R04_PON, true, true);
|
||||
}
|
||||
|
||||
void EPaperT133A01::power_off() {
|
||||
ESP_LOGV(TAG, "Power off");
|
||||
this->write_command_(R02_POF, {0x00}, true, true);
|
||||
}
|
||||
|
||||
void EPaperT133A01::refresh_screen(bool partial) {
|
||||
ESP_LOGV(TAG, "Refresh screen");
|
||||
// Display Refresh
|
||||
this->write_command_(R12_DRF, {0x01}, true, true);
|
||||
}
|
||||
|
||||
void EPaperT133A01::deep_sleep() {
|
||||
ESP_LOGV(TAG, "Deep sleep");
|
||||
this->write_command_(0x07, {0xA5}, true, true);
|
||||
}
|
||||
|
||||
bool HOT EPaperT133A01::transfer_data() {
|
||||
const uint32_t start_time = millis();
|
||||
const uint16_t bytes_per_half_row = this->width_ / 4;
|
||||
const uint16_t total_rows = this->height_;
|
||||
const uint16_t bytes_per_row = this->width_ / 2;
|
||||
uint8_t line_data[400] = {};
|
||||
|
||||
size_t half = this->current_data_index_;
|
||||
|
||||
// --- CCSET: select color set before data transfer (CS + CS1) ---
|
||||
if (half == 0) {
|
||||
this->write_command_(RE0_CCSET, {0x01}, true, true);
|
||||
this->wait_for_idle_(true);
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// --- CS phase: left half of each row via CS ---
|
||||
// T133A01 requires CS to stay LOW for the ENTIRE DTM data stream.
|
||||
// Toggling CS between chunks resets the controller's data pointer,
|
||||
// causing only the last chunk to be retained. Keep CS asserted
|
||||
// across timeout boundaries by NOT deselecting on yield.
|
||||
if (half < total_rows) {
|
||||
if (half == 0) {
|
||||
this->cs_pin_->digital_write(false); // select CS
|
||||
this->cs1_pin_->digital_write(true); // deselect CS1
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
this->write_byte(R10_DTM);
|
||||
this->dc_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
while (half < total_rows) {
|
||||
size_t buf_offset = half * bytes_per_row;
|
||||
for (uint16_t col = 0; col < bytes_per_half_row; col++) {
|
||||
uint8_t b = this->buffer_[buf_offset + col];
|
||||
line_data[col] = (remap_color(b >> 4) << 4) | remap_color(b & 0x0F);
|
||||
}
|
||||
this->write_array(line_data, bytes_per_half_row);
|
||||
half++;
|
||||
this->current_data_index_ = half;
|
||||
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "CS phase done");
|
||||
this->disable();
|
||||
this->cs_pin_->digital_write(true); // deselect CS
|
||||
}
|
||||
|
||||
// --- CS1 phase: right half of each row via CS1 ---
|
||||
// Same continuous-transaction requirement as the CS phase.
|
||||
// CS is held HIGH so only CS1 receives the data.
|
||||
if (half >= total_rows && half < total_rows * 2) {
|
||||
size_t cs1_row = half - total_rows;
|
||||
|
||||
if (cs1_row == 0) {
|
||||
this->cs_pin_->digital_write(true); // deselect CS
|
||||
this->cs1_pin_->digital_write(false); // select CS1
|
||||
this->enable();
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(R10_DTM);
|
||||
this->dc_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
while (half < total_rows * 2) {
|
||||
size_t row = half - total_rows;
|
||||
size_t buf_offset = row * bytes_per_row + bytes_per_half_row;
|
||||
for (uint16_t col = 0; col < bytes_per_half_row; col++) {
|
||||
uint8_t b = this->buffer_[buf_offset + col];
|
||||
line_data[col] = (remap_color(b >> 4) << 4) | remap_color(b & 0x0F);
|
||||
}
|
||||
this->write_array(line_data, bytes_per_half_row);
|
||||
half++;
|
||||
this->current_data_index_ = half;
|
||||
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "CS1 phase done");
|
||||
this->disable();
|
||||
this->cs1_pin_->digital_write(true); // deselect CS1
|
||||
}
|
||||
|
||||
this->current_data_index_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EPaperT133A01::dump_config() {
|
||||
EPaperBase::dump_config();
|
||||
LOG_PIN(" CS Pin: ", this->cs_pin_);
|
||||
LOG_PIN(" CS1 Pin: ", this->cs1_pin_);
|
||||
}
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "epaper_spi.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
/**
|
||||
* T133A01-based 6-color e-paper display driver.
|
||||
*
|
||||
* The T133A01 controller uses a dual-CS SPI architecture:
|
||||
* - CS (primary): Controls the first half of pixel data transfer
|
||||
* - CS1 (secondary): Controls panel commands (init, power, refresh) and
|
||||
* the second half of pixel data transfer
|
||||
*
|
||||
* Color depth: 4 bits per pixel, supporting 6 colors:
|
||||
* White, Green, Red, Yellow, Blue, Black
|
||||
*
|
||||
* Buffer layout: 2 pixels per byte (4bpp packed), total buffer size
|
||||
* is width * height / 2 bytes.
|
||||
*/
|
||||
class EPaperT133A01 : public EPaperBase {
|
||||
public:
|
||||
EPaperT133A01(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length)
|
||||
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR) {
|
||||
this->buffer_length_ = (size_t) width * height / 2; // 2 pixels per byte at 4bpp
|
||||
}
|
||||
|
||||
void set_cs_pins(GPIOPin *cs, GPIOPin *cs1) {
|
||||
this->cs_pin_ = cs;
|
||||
this->cs1_pin_ = cs1;
|
||||
}
|
||||
|
||||
void fill(Color color) override;
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
protected:
|
||||
bool reset() override;
|
||||
bool initialise(bool partial) override;
|
||||
void refresh_screen(bool partial) override;
|
||||
void power_on() override;
|
||||
void power_off() override;
|
||||
void deep_sleep() override;
|
||||
|
||||
bool transfer_data() override;
|
||||
|
||||
/**
|
||||
* Send a command (and optional data) selecting one or both controllers.
|
||||
* Both chip-selects are active-low and managed directly by this driver.
|
||||
* @param command The command byte to send
|
||||
* @param data Optional pointer to data bytes to send after the command
|
||||
* @param length Number of data bytes to send after the command
|
||||
* @param use_cs assert CS (left controller) for this transaction
|
||||
* @param use_cs1 assert CS1 (right controller) for this transaction
|
||||
*/
|
||||
void write_command_(uint8_t command, const uint8_t *data, size_t length, bool use_cs, bool use_cs1);
|
||||
void write_command_(uint8_t command, std::initializer_list<uint8_t> data, bool use_cs, bool use_cs1) {
|
||||
this->write_command_(command, data.begin(), data.size(), use_cs, use_cs1);
|
||||
}
|
||||
void write_command_(uint8_t command, bool use_cs, bool use_cs1) {
|
||||
this->write_command_(command, nullptr, 0, use_cs, use_cs1);
|
||||
}
|
||||
|
||||
/// Convert Color to 4-bit T133A01 color index
|
||||
static uint8_t color_to_index(Color color);
|
||||
|
||||
/// Apply COLOR_GET remap table to translate sprite indices to hardware values
|
||||
static uint8_t remap_color(uint8_t index);
|
||||
|
||||
GPIOPin *cs_pin_{nullptr};
|
||||
GPIOPin *cs1_pin_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "epaper_waveshare_bwr.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
enum class BwrState : uint8_t {
|
||||
BWR_BLACK,
|
||||
BWR_WHITE,
|
||||
BWR_RED,
|
||||
};
|
||||
|
||||
static BwrState color_to_bwr(Color color) {
|
||||
if (color.r > color.g + color.b && color.r > 127) {
|
||||
return BwrState::BWR_RED;
|
||||
}
|
||||
if (color.r + color.g + color.b >= 382) {
|
||||
return BwrState::BWR_WHITE;
|
||||
}
|
||||
return BwrState::BWR_BLACK;
|
||||
}
|
||||
|
||||
// UC8179 3-color display buffer layout:
|
||||
// - 1 bit per pixel, 8 pixels per byte
|
||||
// - Buffer first half: Black/White plane (1=black, 0=white)
|
||||
// - Buffer second half: Red plane (1=red, 0=white)
|
||||
// - Total: row_width * height * 2 bytes
|
||||
|
||||
void EPaperWaveshareBWR::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->rotate_coordinates_(x, y))
|
||||
return;
|
||||
|
||||
const uint32_t pos = (x / 8) + (y * this->row_width_);
|
||||
const uint8_t bit = 0x80 >> (x & 0x07);
|
||||
const uint32_t red_offset = this->buffer_length_ / 2u;
|
||||
|
||||
const auto bwr = color_to_bwr(color);
|
||||
|
||||
if (bwr == BwrState::BWR_BLACK) {
|
||||
this->buffer_[pos] |= bit;
|
||||
} else {
|
||||
this->buffer_[pos] &= ~bit;
|
||||
}
|
||||
|
||||
if (bwr == BwrState::BWR_RED) {
|
||||
this->buffer_[red_offset + pos] |= bit;
|
||||
} else {
|
||||
this->buffer_[red_offset + pos] &= ~bit;
|
||||
}
|
||||
}
|
||||
|
||||
void EPaperWaveshareBWR::fill(Color color) {
|
||||
const size_t half_buffer = this->buffer_length_ / 2u;
|
||||
const auto bwr = color_to_bwr(color);
|
||||
|
||||
if (bwr == BwrState::BWR_BLACK) {
|
||||
// Black plane: 0xFF (black), Red plane: 0x00 (no red)
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[i] = 0xFF;
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[half_buffer + i] = 0x00;
|
||||
} else if (bwr == BwrState::BWR_RED) {
|
||||
// Black plane: 0x00 (no black), Red plane: 0xFF (red)
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[i] = 0x00;
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[half_buffer + i] = 0xFF;
|
||||
} else {
|
||||
// Black plane: 0x00 (no black), Red plane: 0x00 (no red)
|
||||
this->buffer_.fill(0x00);
|
||||
}
|
||||
}
|
||||
|
||||
bool HOT EPaperWaveshareBWR::transfer_data() {
|
||||
const uint32_t start_time = millis();
|
||||
const size_t buffer_length = this->buffer_length_;
|
||||
const size_t half_buffer = buffer_length / 2u;
|
||||
|
||||
uint8_t bytes_to_send[MAX_TRANSFER_SIZE];
|
||||
|
||||
// Phase 1: send Black/White plane (first half) via command 0x10 (DTM1)
|
||||
// UC8179 DTM1 (0x10): inverted to get 0=black, 1=white
|
||||
if (this->current_data_index_ < half_buffer) {
|
||||
if (this->current_data_index_ == 0) {
|
||||
this->command(0x10); // DATA START TRANSMISSION 1 (black channel)
|
||||
}
|
||||
this->start_data_();
|
||||
while (this->current_data_index_ < half_buffer) {
|
||||
const size_t bytes_to_copy = std::min(MAX_TRANSFER_SIZE, half_buffer - this->current_data_index_);
|
||||
for (size_t i = 0; i < bytes_to_copy; i++) {
|
||||
bytes_to_send[i] = ~this->buffer_[this->current_data_index_ + i];
|
||||
}
|
||||
this->write_array(bytes_to_send, bytes_to_copy);
|
||||
this->current_data_index_ += bytes_to_copy;
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
this->disable();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
// Phase 2: send Red plane (second half) via command 0x13 (DTM2)
|
||||
// UC8179 DTM2 (0x13): 1=red, 0=white
|
||||
if (this->current_data_index_ < buffer_length) {
|
||||
if (this->current_data_index_ == half_buffer) {
|
||||
this->command(0x13); // DATA START TRANSMISSION 2 (red channel)
|
||||
}
|
||||
this->start_data_();
|
||||
while (this->current_data_index_ < buffer_length) {
|
||||
const size_t bytes_to_copy = std::min(MAX_TRANSFER_SIZE, buffer_length - this->current_data_index_);
|
||||
for (size_t i = 0; i < bytes_to_copy; i++) {
|
||||
bytes_to_send[i] = this->buffer_[this->current_data_index_ + i];
|
||||
}
|
||||
this->write_array(bytes_to_send, bytes_to_copy);
|
||||
this->current_data_index_ += bytes_to_copy;
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
this->disable();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
this->current_data_index_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EPaperWaveshareBWR::power_on() {
|
||||
this->cmd_data(0x01, {0x07, 0x17, 0x3F, 0x3F}); // POWER SETTING
|
||||
this->command(0x04); // POWER ON
|
||||
}
|
||||
|
||||
void EPaperWaveshareBWR::refresh_screen(bool /*partial*/) {
|
||||
this->command(0x12); // DISPLAY REFRESH
|
||||
}
|
||||
|
||||
void EPaperWaveshareBWR::power_off() {
|
||||
this->command(0x02); // POWER OFF
|
||||
}
|
||||
|
||||
void EPaperWaveshareBWR::deep_sleep() {
|
||||
this->cmd_data(0x07, {0xA5}); // DEEP SLEEP with check code
|
||||
}
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "epaper_spi.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
/**
|
||||
* Waveshare 3-color e-paper displays (UC8179 controller).
|
||||
* Supports: 7.5" V2 BWR (EDP_7in5b_V2), 800x480 pixels.
|
||||
*
|
||||
* Color scheme: Black, White, Red (BWR)
|
||||
* Buffer layout: 1 bit per pixel, separate planes
|
||||
* - Buffer first half: Black/White plane (1=black, 0=white)
|
||||
* - Buffer second half: Red plane (1=red, 0=no red)
|
||||
* - Total buffer: width * height / 4 bytes (2 * width * height / 8)
|
||||
*
|
||||
* The init sequence (INITIALISE state) sends panel configuration only.
|
||||
* Power-on (0x01 + 0x04) is sent in the POWER_ON state after data transfer;
|
||||
* the state machine then busy-waits before triggering REFRESH_SCREEN (0x12).
|
||||
*/
|
||||
class EPaperWaveshareBWR : public EPaperBase {
|
||||
public:
|
||||
EPaperWaveshareBWR(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length)
|
||||
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) {
|
||||
this->buffer_length_ = this->row_width_ * height * 2;
|
||||
}
|
||||
|
||||
void fill(Color color) override;
|
||||
|
||||
protected:
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
bool transfer_data() override;
|
||||
void refresh_screen(bool partial) override;
|
||||
void power_on() override;
|
||||
void power_off() override;
|
||||
void deep_sleep() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -2,11 +2,15 @@ from typing import Any, Self
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DIMENSIONS, CONF_HEIGHT, CONF_WIDTH
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
|
||||
class EpaperModel:
|
||||
models: dict[str, Self] = {}
|
||||
|
||||
# Whether the driver manages chip-select itself instead of via the SPI bus.
|
||||
manages_cs: bool = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
@@ -35,6 +39,25 @@ class EpaperModel:
|
||||
def get_constructor_args(self, config) -> tuple:
|
||||
return ()
|
||||
|
||||
def get_config_options(self) -> dict:
|
||||
"""
|
||||
Return model-specific configuration schema options.
|
||||
The base implementation adds nothing; specific models override this to
|
||||
declare extra options without cluttering the shared schema.
|
||||
:return: A mapping suitable for cv.Schema.extend()
|
||||
"""
|
||||
return {}
|
||||
|
||||
async def to_code(self, var: MockObj, config: dict) -> dict:
|
||||
"""
|
||||
Generate model-specific code for the options added by add_options().
|
||||
The base implementation does nothing; specific models override this.
|
||||
The config can be updated in place to add or remove options.
|
||||
:param var: The component variable
|
||||
:param config: The validated configuration
|
||||
"""
|
||||
return config
|
||||
|
||||
def get_dimensions(self, config) -> tuple[int, int]:
|
||||
if CONF_DIMENSIONS in config:
|
||||
# Explicit dimensions, just use as is
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"""T133A01-based e-paper displays.
|
||||
|
||||
The T133A01 is a 6-color e-paper controller IC that drives large panels
|
||||
(1200x1600 portrait). It uses a dual-CS SPI architecture where CS
|
||||
controls one half of the pixel data and CS1 controls the other half,
|
||||
as well as panel-level commands (power on, refresh, power off).
|
||||
|
||||
Supported models:
|
||||
- Seeed-reTerminal-E1004: 1200x1600 pixels, 6-color (T133A01 panel)
|
||||
"""
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_CS_PIN
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from . import EpaperModel
|
||||
|
||||
CONF_CS1_PIN = "cs1_pin"
|
||||
|
||||
|
||||
class T133A01Model(EpaperModel):
|
||||
"""EpaperModel subclass for T133A01-based 6-color e-paper displays."""
|
||||
|
||||
# The driver drives CS and CS1 directly for the dual-CS protocol.
|
||||
manages_cs = True
|
||||
|
||||
def __init__(self, name, class_name="EPaperT133A01", **defaults):
|
||||
super().__init__(name, class_name, **defaults)
|
||||
|
||||
def get_config_options(self) -> dict:
|
||||
# CS1 is the second chip-select required by the dual-CS architecture.
|
||||
# fallback=None makes it required unless the model provides a default.
|
||||
return {
|
||||
self.option(CONF_CS1_PIN, fallback=None): pins.gpio_output_pin_schema,
|
||||
}
|
||||
|
||||
async def to_code(self, var: MockObj, config: dict) -> dict:
|
||||
cs = await cg.gpio_pin_expression(config[CONF_CS_PIN])
|
||||
cs1 = await cg.gpio_pin_expression(config[CONF_CS1_PIN])
|
||||
cg.add(var.set_cs_pins(cs, cs1))
|
||||
# Remove CS and CS1 from the config so that the base class doesn't try to handle them.
|
||||
return {k: v for k, v in config.items() if k not in (CONF_CS_PIN, CONF_CS1_PIN)}
|
||||
|
||||
|
||||
t133a01_base = T133A01Model(
|
||||
"t133a01",
|
||||
minimum_update_interval="30s",
|
||||
data_rate="10MHz",
|
||||
)
|
||||
|
||||
# Seeed reTerminal E1004 - 13.3" 6-color e-paper (1200x1600, T133A01)
|
||||
# Portrait orientation (1200 wide × 1600 tall), matching the Arduino
|
||||
# Setup523 defines TFT_WIDTH=1200, TFT_HEIGHT=1600.
|
||||
# CS and CS1 each receive half of each row's pixel data
|
||||
# (300 bytes = 600 pixels per controller, for all 1600 rows).
|
||||
Seeed_reTerminal_E1004 = t133a01_base.extend(
|
||||
"Seeed-reTerminal-E1004",
|
||||
width=1200,
|
||||
height=1600,
|
||||
cs_pin=10,
|
||||
cs1_pin=2,
|
||||
dc_pin=11,
|
||||
reset_pin=38,
|
||||
busy_pin={
|
||||
"number": 13,
|
||||
"inverted": True,
|
||||
"mode": {"input": True},
|
||||
},
|
||||
enable_pin=12,
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Waveshare Black/White/Red e-paper displays using UC8179 controller.
|
||||
|
||||
Supported models:
|
||||
- waveshare-7.5in-bv2-bwr: 800x480 pixels (7.5" BWR display, EDP_7in5b_V2)
|
||||
|
||||
These displays use the UC8179 controller. Panel configuration is sent during
|
||||
the INITIALISE state. Power-on is handled in the POWER_ON state, after data
|
||||
transfer, so the state machine's built-in busy wait covers the power-on delay.
|
||||
"""
|
||||
|
||||
from . import EpaperModel
|
||||
|
||||
|
||||
class WaveshareBWR(EpaperModel):
|
||||
"""EpaperModel class for Waveshare Black/White/Red displays using UC8179 controller."""
|
||||
|
||||
def __init__(self, name, **defaults):
|
||||
super().__init__(name, "EPaperWaveshareBWR", **defaults)
|
||||
|
||||
def get_init_sequence(self, config):
|
||||
"""Generate initialization sequence for UC8179 BWR displays.
|
||||
|
||||
Panel configuration only — power-on is handled separately in power_on()
|
||||
after data transfer, with the state machine busy-waiting before refresh.
|
||||
"""
|
||||
width, height = self.get_dimensions(config)
|
||||
return (
|
||||
# PANEL SETTING (KWR mode)
|
||||
(0x00, 0x0F),
|
||||
# RESOLUTION SETTING (width x height)
|
||||
(
|
||||
0x61,
|
||||
(width >> 8) & 0xFF,
|
||||
width & 0xFF,
|
||||
(height >> 8) & 0xFF,
|
||||
height & 0xFF,
|
||||
),
|
||||
# DUAL SPI MODE (disabled)
|
||||
(0x15, 0x00),
|
||||
# VCOM AND DATA INTERVAL SETTING
|
||||
(0x50, 0x11, 0x07),
|
||||
# TCON SETTING
|
||||
(0x60, 0x22),
|
||||
# RESOLUTION GATE SETTING
|
||||
(0x65, 0x00, 0x00, 0x00, 0x00),
|
||||
)
|
||||
|
||||
|
||||
# Model: Waveshare 7.5" V2 BWR (EDP_7in5b_V2) — 800x480, UC8179 controller
|
||||
WaveshareBWR(
|
||||
"waveshare-7.5in-bv2-bwr",
|
||||
width=800,
|
||||
height=480,
|
||||
data_rate="10MHz",
|
||||
minimum_update_interval="30s",
|
||||
)
|
||||
@@ -11,8 +11,11 @@ class ESP32PreferenceBackend final {
|
||||
bool save(const uint8_t *data, size_t len);
|
||||
bool load(uint8_t *data, size_t len);
|
||||
|
||||
uint32_t key;
|
||||
uint32_t nvs_handle;
|
||||
uint32_t key{0};
|
||||
uint32_t nvs_handle{0}; // NVS (flash) path
|
||||
uint16_t rtc_offset{0}; // RTC path: word offset into the RTC storage region
|
||||
uint8_t length_words{0}; // RTC path: data length in 32-bit words
|
||||
bool in_flash{true}; // true: store in NVS (flash); false: store in RTC memory
|
||||
};
|
||||
|
||||
class ESP32Preferences;
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
#include "preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/preferences_rtc.h"
|
||||
#include <esp_attr.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <soc/soc_caps.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
@@ -18,6 +21,48 @@ struct NVSData {
|
||||
|
||||
static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
// RTC memory backend for preferences requested with in_flash=false. Survives deep sleep and
|
||||
// software/CPU resets, but not power loss; integrity is guarded by a per-record checksum so
|
||||
// power-on garbage is detected on load. Keep this small: RTC memory is scarce and shared.
|
||||
//
|
||||
// Only compiled in when USE_ESP32_RTC_PREFERENCES_STORAGE is set (see preferences.h): the storage
|
||||
// buffer reserves RTC memory, so it exists only when some config option actually selected RTC
|
||||
// storage AND the variant has RTC memory (the ESP32-C2 and -C61 have none, so RTC_NOINIT_ATTR would
|
||||
// have no section to land in and fail to link). Otherwise in_flash=false transparently falls back
|
||||
// to NVS (see make_preference below).
|
||||
//
|
||||
// On variants with only RTC fast memory (C3/C6/H2/P4/C5/...) RTC_NOINIT_ATTR lands in RTC fast memory.
|
||||
// This is still safe: the linker reserves .rtc_noinit ahead of any RTC-fast-as-heap pool
|
||||
// (CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP), and IDF keeps the RTC fast power domain on in deep
|
||||
// sleep (forced on whether or not it is used as heap), so the data is retained across both resets and
|
||||
// deep sleep -- only power loss clears it.
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
static constexpr size_t RTC_PREF_SIZE_WORDS = 64; // 256 bytes
|
||||
static constexpr size_t RTC_PREF_MAX_WORDS = 255; // length_words field is a uint8_t
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static RTC_NOINIT_ATTR uint32_t s_rtc_storage[RTC_PREF_SIZE_WORDS];
|
||||
|
||||
static bool save_to_rtc(uint16_t offset, uint32_t key, uint8_t length_words, const uint8_t *data, size_t len) {
|
||||
if (rtc_pref_bytes_to_words(len) != length_words)
|
||||
return false;
|
||||
const size_t buffer_size = static_cast<size_t>(length_words) + 1;
|
||||
if (static_cast<size_t>(offset) + buffer_size > RTC_PREF_SIZE_WORDS)
|
||||
return false;
|
||||
rtc_pref_encode(&s_rtc_storage[offset], key, length_words, data, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool load_from_rtc(uint16_t offset, uint32_t key, uint8_t length_words, uint8_t *data, size_t len) {
|
||||
if (rtc_pref_bytes_to_words(len) != length_words)
|
||||
return false;
|
||||
const size_t buffer_size = static_cast<size_t>(length_words) + 1;
|
||||
if (static_cast<size_t>(offset) + buffer_size > RTC_PREF_SIZE_WORDS)
|
||||
return false;
|
||||
return rtc_pref_decode(&s_rtc_storage[offset], key, length_words, data, len);
|
||||
}
|
||||
#endif // USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
|
||||
// open() runs from app_main() before the logger is initialized, so any failure
|
||||
// must be deferred until after global_logger is set. This is emitted from the
|
||||
// first make_preference() call, which runs from the generated setup() after
|
||||
@@ -25,6 +70,10 @@ static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-n
|
||||
static esp_err_t s_open_err = ESP_OK; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
bool ESP32PreferenceBackend::save(const uint8_t *data, size_t len) {
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
if (!this->in_flash)
|
||||
return save_to_rtc(this->rtc_offset, this->key, this->length_words, data, len);
|
||||
#endif
|
||||
// try find in pending saves and update that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == this->key) {
|
||||
@@ -41,6 +90,10 @@ bool ESP32PreferenceBackend::save(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
bool ESP32PreferenceBackend::load(uint8_t *data, size_t len) {
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
if (!this->in_flash)
|
||||
return load_from_rtc(this->rtc_offset, this->key, this->length_words, data, len);
|
||||
#endif
|
||||
// try find in pending saves and load from that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == this->key) {
|
||||
@@ -94,6 +147,26 @@ void ESP32Preferences::open() {
|
||||
}
|
||||
}
|
||||
|
||||
ESPPreferenceObject ESP32Preferences::make_preference(size_t length, uint32_t type, bool in_flash) {
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
if (!in_flash)
|
||||
return this->make_rtc_preference_(length, type);
|
||||
#else
|
||||
if (!in_flash) {
|
||||
// RTC storage is not compiled in (no config option selected it), so this request
|
||||
// falls back to NVS -- the historic ESP32 behavior. Warn once so callers explicitly
|
||||
// asking for RTC storage can discover the fallback.
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
ESP_LOGW(TAG, "RTC preference storage not compiled in; using NVS (enable with 'preferences: rtc_storage: true')");
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// in_flash, or RTC storage not compiled in: fall back to NVS.
|
||||
return this->make_preference(length, type);
|
||||
}
|
||||
|
||||
ESPPreferenceObject ESP32Preferences::make_preference(size_t length, uint32_t type) {
|
||||
if (s_open_err != ESP_OK) {
|
||||
if (this->nvs_handle == 0) {
|
||||
@@ -106,10 +179,34 @@ ESPPreferenceObject ESP32Preferences::make_preference(size_t length, uint32_t ty
|
||||
auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
pref->nvs_handle = this->nvs_handle;
|
||||
pref->key = type;
|
||||
pref->in_flash = true;
|
||||
|
||||
return ESPPreferenceObject(pref);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
ESPPreferenceObject ESP32Preferences::make_rtc_preference_(size_t length, uint32_t type) {
|
||||
const uint32_t length_words = rtc_pref_bytes_to_words(length);
|
||||
if (length_words > RTC_PREF_MAX_WORDS) {
|
||||
ESP_LOGE(TAG, "RTC preference too large: %" PRIu32 " words", length_words);
|
||||
return {};
|
||||
}
|
||||
const uint32_t total_words = length_words + 1; // +1 for checksum
|
||||
if (static_cast<size_t>(this->current_rtc_offset_) + total_words > RTC_PREF_SIZE_WORDS) {
|
||||
ESP_LOGE(TAG, "RTC preference storage full, cannot allocate %" PRIu32 " words", total_words);
|
||||
return {};
|
||||
}
|
||||
auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
pref->key = type;
|
||||
pref->in_flash = false;
|
||||
pref->rtc_offset = this->current_rtc_offset_;
|
||||
pref->length_words = static_cast<uint8_t>(length_words);
|
||||
this->current_rtc_offset_ += static_cast<uint16_t>(total_words);
|
||||
|
||||
return ESPPreferenceObject(pref);
|
||||
}
|
||||
#endif // USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
|
||||
bool ESP32Preferences::sync() {
|
||||
if (s_pending_save.empty())
|
||||
return true;
|
||||
@@ -186,6 +283,12 @@ bool ESP32Preferences::is_changed_(uint32_t nvs_handle, const NVSData &to_save,
|
||||
bool ESP32Preferences::reset() {
|
||||
ESP_LOGD(TAG, "Erasing storage");
|
||||
s_pending_save.clear();
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
// Invalidate RTC-backed preferences too (checksum will no longer match). current_rtc_offset_ is
|
||||
// deliberately left alone: existing backends keep pointing at their allocated slots, and reset()
|
||||
// is always followed by a restart (same reason nvs_handle is zeroed below).
|
||||
memset(s_rtc_storage, 0, sizeof(s_rtc_storage));
|
||||
#endif
|
||||
|
||||
nvs_flash_deinit();
|
||||
nvs_flash_erase();
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/preference_backend.h"
|
||||
#include <soc/soc_caps.h>
|
||||
|
||||
// RTC-backed preference storage is compiled in only when a config option actually selects it
|
||||
// (USE_ESP32_RTC_PREFERENCES, emitted during code generation) and the variant has RTC memory
|
||||
// (SOC_RTC_MEM_SUPPORTED; the ESP32-C2 and -C61 have none). Otherwise in_flash=false falls
|
||||
// back to NVS and no RTC memory is reserved.
|
||||
#if defined(USE_ESP32_RTC_PREFERENCES) && SOC_RTC_MEM_SUPPORTED
|
||||
#define USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
#endif
|
||||
|
||||
namespace esphome::esp32 {
|
||||
|
||||
@@ -11,9 +20,8 @@ class ESP32Preferences final : public PreferencesMixin<ESP32Preferences> {
|
||||
public:
|
||||
using PreferencesMixin<ESP32Preferences>::make_preference;
|
||||
void open();
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) {
|
||||
return this->make_preference(length, type);
|
||||
}
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash);
|
||||
// Two-argument form defaults to NVS (flash) storage, preserving historic ESP32 behavior.
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type);
|
||||
bool sync();
|
||||
bool reset();
|
||||
@@ -22,6 +30,13 @@ class ESP32Preferences final : public PreferencesMixin<ESP32Preferences> {
|
||||
|
||||
protected:
|
||||
bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str);
|
||||
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
// RTC-backed storage (in_flash=false).
|
||||
ESPPreferenceObject make_rtc_preference_(size_t length, uint32_t type);
|
||||
// Next free word offset in the RTC storage region (bump allocated in make_preference order).
|
||||
uint16_t current_rtc_offset_{0};
|
||||
#endif
|
||||
};
|
||||
|
||||
void setup_preferences();
|
||||
|
||||
@@ -310,6 +310,14 @@ async def to_code(config):
|
||||
# For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;`
|
||||
cg.add_build_flag("-DNEW_OOM_ABORT")
|
||||
|
||||
# Force-include inline std::__throw_* overrides so GCC dead-strips the unused
|
||||
# libstdc++ error message strings (e.g. "basic_string::_M_create") from DRAM.
|
||||
# See throw_stubs.h for details. Must be prepended before <string>, so this
|
||||
# uses build_src_flags with -include.
|
||||
cg.add_platformio_option(
|
||||
"build_src_flags", "-include esphome/components/esp8266/throw_stubs.h"
|
||||
)
|
||||
|
||||
# In testing mode, fake larger memory to allow linking grouped component tests
|
||||
# Real ESP8266 hardware only has 32KB IRAM and ~80KB RAM, but for CI testing
|
||||
# we pretend it has much larger memory to test that components compile together
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace esphome {
|
||||
uint32_t random_uint32() { return os_random(); }
|
||||
bool random_bytes(uint8_t *data, size_t len) { return os_get_random(data, len) == 0; }
|
||||
|
||||
// ESP8266 Mutex is defined inline as a no-op in helpers.h when USE_ESP8266 (or USE_RP2040) is set,
|
||||
// ESP8266 Mutex is defined inline as a no-op in helpers.h when USE_ESP8266 (or USE_RP2) is set,
|
||||
// independent of the ESPHOME_THREAD_SINGLE thread model define.
|
||||
|
||||
IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); }
|
||||
|
||||
@@ -8,6 +8,7 @@ extern "C" {
|
||||
#include "preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/preferences_rtc.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -80,16 +81,6 @@ static uint32_t get_esp8266_flash_sector() {
|
||||
}
|
||||
static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
|
||||
|
||||
static inline size_t bytes_to_words(size_t bytes) { return (bytes + 3) / 4; }
|
||||
|
||||
template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
|
||||
uint32_t crc = type;
|
||||
while (first != last) {
|
||||
crc ^= (*first++ * 2654435769UL) >> 1;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) {
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
uint32_t j = offset + i;
|
||||
@@ -137,21 +128,19 @@ static constexpr size_t PREF_MAX_BUFFER_WORDS =
|
||||
ESP8266_FLASH_STORAGE_SIZE > RTC_NORMAL_REGION_WORDS ? ESP8266_FLASH_STORAGE_SIZE : RTC_NORMAL_REGION_WORDS;
|
||||
|
||||
bool ESP8266PreferenceBackend::save(const uint8_t *data, size_t len) {
|
||||
if (bytes_to_words(len) != this->length_words)
|
||||
if (rtc_pref_bytes_to_words(len) != this->length_words)
|
||||
return false;
|
||||
const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
|
||||
if (buffer_size > PREF_MAX_BUFFER_WORDS)
|
||||
return false;
|
||||
uint32_t buffer[PREF_MAX_BUFFER_WORDS];
|
||||
memset(buffer, 0, buffer_size * sizeof(uint32_t));
|
||||
memcpy(buffer, data, len);
|
||||
buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type);
|
||||
rtc_pref_encode(buffer, this->type, this->length_words, data, len);
|
||||
return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size)
|
||||
: save_to_rtc(this->offset, buffer, buffer_size);
|
||||
}
|
||||
|
||||
bool ESP8266PreferenceBackend::load(uint8_t *data, size_t len) {
|
||||
if (bytes_to_words(len) != this->length_words)
|
||||
if (rtc_pref_bytes_to_words(len) != this->length_words)
|
||||
return false;
|
||||
const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
|
||||
if (buffer_size > PREF_MAX_BUFFER_WORDS)
|
||||
@@ -161,10 +150,7 @@ bool ESP8266PreferenceBackend::load(uint8_t *data, size_t len) {
|
||||
: load_from_rtc(this->offset, buffer, buffer_size);
|
||||
if (!ret)
|
||||
return false;
|
||||
if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type))
|
||||
return false;
|
||||
memcpy(data, buffer, len);
|
||||
return true;
|
||||
return rtc_pref_decode(buffer, this->type, this->length_words, data, len);
|
||||
}
|
||||
|
||||
void ESP8266Preferences::setup() {
|
||||
@@ -177,13 +163,13 @@ void ESP8266Preferences::setup() {
|
||||
}
|
||||
|
||||
ESPPreferenceObject ESP8266Preferences::make_preference(size_t length, uint32_t type, bool in_flash) {
|
||||
const uint32_t length_words = bytes_to_words(length);
|
||||
const uint32_t length_words = rtc_pref_bytes_to_words(length);
|
||||
if (length_words > MAX_PREFERENCE_WORDS) {
|
||||
ESP_LOGE(TAG, "Preference too large: %u words", static_cast<unsigned int>(length_words));
|
||||
return {};
|
||||
}
|
||||
|
||||
const uint32_t total_words = length_words + 1; // +1 for CRC
|
||||
const uint32_t total_words = length_words + 1; // +1 for checksum
|
||||
uint16_t offset;
|
||||
|
||||
if (in_flash) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
/*
|
||||
* Inline overrides for std::__throw_* helpers (ESP8266).
|
||||
*
|
||||
* ESP8266 Arduino compiles with -fno-exceptions and ships a libstdc++ whose
|
||||
* std::__throw_* functions already just call abort() -- they never read their
|
||||
* const char* message argument. But the compiler still emits the message load
|
||||
* at every throw site (inside header-instantiated std::string / std::vector
|
||||
* code), so --gc-sections keeps those libstdc++ error strings alive. On
|
||||
* ESP8266 .rodata lives in DRAM, so each one wastes scarce RAM (e.g.
|
||||
* "basic_string::_M_construct null not valid", "basic_string::_M_create",
|
||||
* "cannot create std::vector larger than max_size()", "array::at: ...").
|
||||
*
|
||||
* Providing inline definitions here lets GCC see the message argument is
|
||||
* unused, dead-strip the load, and drop the string entirely -- no LTO needed.
|
||||
* Behavior is identical to today: a bare abort() (the message was never
|
||||
* printed). This header MUST be force-included before <string>, so it is
|
||||
* wired up via build_src_flags "-include ..." in this component's __init__.py.
|
||||
*
|
||||
* Note: this defines functions in namespace std (technically UB). It is safe
|
||||
* here because the definitions match the existing abort() behavior exactly.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
// Empty namespace so the CI namespace check is satisfied; the overrides below
|
||||
// must live in namespace std, so they cannot go in the component namespace.
|
||||
namespace esphome::esp8266 {} // namespace esphome::esp8266
|
||||
|
||||
// NOLINTBEGIN(bugprone-reserved-identifier,bugprone-std-namespace-modification,cert-dcl37-c,cert-dcl51-cpp,cert-dcl58-cpp,readability-identifier-naming)
|
||||
namespace std {
|
||||
|
||||
__attribute__((__noreturn__)) inline void __throw_logic_error(const char *) { __builtin_abort(); }
|
||||
__attribute__((__noreturn__)) inline void __throw_length_error(const char *) { __builtin_abort(); }
|
||||
__attribute__((__noreturn__)) inline void __throw_out_of_range(const char *) { __builtin_abort(); }
|
||||
__attribute__((__noreturn__)) inline void __throw_out_of_range_fmt(const char *, ...) { __builtin_abort(); }
|
||||
|
||||
} // namespace std
|
||||
// NOLINTEND(bugprone-reserved-identifier,bugprone-std-namespace-modification,cert-dcl37-c,cert-dcl51-cpp,cert-dcl58-cpp,readability-identifier-naming)
|
||||
|
||||
#endif // __cplusplus
|
||||
@@ -126,7 +126,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
CONF_PORT,
|
||||
esp8266=8266,
|
||||
esp32=3232,
|
||||
rp2040=2040,
|
||||
rp2=2040,
|
||||
bk72xx=8892,
|
||||
ln882x=8820,
|
||||
rtl87xx=8892,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_libretiny.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_rp2.h"
|
||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
@@ -41,7 +41,7 @@ DeletePeerAction = espnow_ns.class_("DeletePeerAction", automation.Action)
|
||||
ESPNowHandlerTrigger = automation.Trigger.template(
|
||||
ESPNowRecvInfoConstRef,
|
||||
cg.uint8.operator("const").operator("ptr"),
|
||||
cg.uint8,
|
||||
cg.uint16,
|
||||
)
|
||||
|
||||
OnUnknownPeerTrigger = espnow_ns.class_(
|
||||
@@ -56,6 +56,20 @@ OnBroadcastTrigger = espnow_ns.class_(
|
||||
|
||||
|
||||
CONF_AUTO_ADD_PEER = "auto_add_peer"
|
||||
CONF_MAX_PAYLOAD_SIZE = "max_payload_size"
|
||||
|
||||
# Payload limits of ESP-NOW v1 and v2 frames. The radio negotiates the
|
||||
# protocol version per peer on its own; the option only sizes this device's
|
||||
# packet buffers, whose static RAM cost is proportional to it (~8 KB at 250
|
||||
# bytes, ~44 KB at 1470).
|
||||
ESPNOW_PAYLOAD_V1 = 250
|
||||
ESPNOW_PAYLOAD_V2 = 1470
|
||||
|
||||
# Config-time cap for action payloads. The per-device limit is the
|
||||
# ``max_payload_size`` option, which the action schema cannot see; send()
|
||||
# enforces it at runtime.
|
||||
MAX_ESPNOW_PACKET_SIZE = ESPNOW_PAYLOAD_V2
|
||||
|
||||
CONF_PEERS = "peers"
|
||||
CONF_ON_SENT = "on_sent"
|
||||
CONF_ON_UNKNOWN_PEER = "on_unknown_peer"
|
||||
@@ -63,7 +77,15 @@ CONF_ON_BROADCAST = "on_broadcast"
|
||||
CONF_CONTINUE_ON_ERROR = "continue_on_error"
|
||||
CONF_WAIT_FOR_SENT = "wait_for_sent"
|
||||
|
||||
MAX_ESPNOW_PACKET_SIZE = 250 # Maximum size of the payload in bytes
|
||||
|
||||
def _validate_max_payload_size(value: int) -> int:
|
||||
if value > ESPNOW_PAYLOAD_V1:
|
||||
return cv.require_framework_version(
|
||||
esp_idf=cv.Version(5, 4, 0),
|
||||
esp32_arduino=cv.Version(3, 2, 0),
|
||||
extra_message="ESP-NOW v2 frames need an ESP-NOW v2 capable framework",
|
||||
)(value)
|
||||
return value
|
||||
|
||||
|
||||
def validate_channel(value):
|
||||
@@ -78,6 +100,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.GenerateID(): cv.declare_id(ESPNowComponent),
|
||||
cv.OnlyWithout(CONF_CHANNEL, CONF_WIFI): validate_channel,
|
||||
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_MAX_PAYLOAD_SIZE, default=ESPNOW_PAYLOAD_V1): cv.All(
|
||||
cv.int_range(min=1, max=ESPNOW_PAYLOAD_V2), _validate_max_payload_size
|
||||
),
|
||||
cv.Optional(CONF_AUTO_ADD_PEER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PEERS): cv.ensure_list(cv.mac_address),
|
||||
cv.Optional(CONF_ON_UNKNOWN_PEER): automation.validate_automation(
|
||||
@@ -113,7 +138,7 @@ async def _trigger_to_code(config):
|
||||
[
|
||||
(ESPNowRecvInfoConstRef, "info"),
|
||||
(cg.uint8.operator("const").operator("ptr"), "data"),
|
||||
(cg.uint8, "size"),
|
||||
(cg.uint16, "size"),
|
||||
],
|
||||
config,
|
||||
)
|
||||
@@ -125,6 +150,7 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add_define("USE_ESPNOW")
|
||||
cg.add_define("USE_ESPNOW_MAX_PAYLOAD_SIZE", config[CONF_MAX_PAYLOAD_SIZE])
|
||||
if wifi_channel := config.get(CONF_CHANNEL):
|
||||
cg.add(var.set_wifi_channel(wifi_channel))
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ template<typename... Ts> class SetChannelAction final : public Action<Ts...>, pu
|
||||
}
|
||||
};
|
||||
|
||||
class OnReceiveTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint8_t>,
|
||||
class OnReceiveTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint16_t>,
|
||||
public ESPNowReceivedPacketHandler {
|
||||
public:
|
||||
explicit OnReceiveTrigger(std::array<uint8_t, ESP_NOW_ETH_ALEN> address) : has_address_(true) {
|
||||
@@ -128,7 +128,7 @@ class OnReceiveTrigger final : public Trigger<const ESPNowRecvInfo &, const uint
|
||||
|
||||
explicit OnReceiveTrigger() {}
|
||||
|
||||
bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override {
|
||||
bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) override {
|
||||
bool match = !this->has_address_ || (memcmp(this->address_, info.src_addr, ESP_NOW_ETH_ALEN) == 0);
|
||||
if (!match)
|
||||
return false;
|
||||
@@ -141,15 +141,15 @@ class OnReceiveTrigger final : public Trigger<const ESPNowRecvInfo &, const uint
|
||||
bool has_address_{false};
|
||||
uint8_t address_[ESP_NOW_ETH_ALEN]{};
|
||||
};
|
||||
class OnUnknownPeerTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint8_t>,
|
||||
class OnUnknownPeerTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint16_t>,
|
||||
public ESPNowUnknownPeerHandler {
|
||||
public:
|
||||
bool on_unknown_peer(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override {
|
||||
bool on_unknown_peer(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) override {
|
||||
this->trigger(info, data, size);
|
||||
return false; // Return false to continue processing other internal handlers
|
||||
}
|
||||
};
|
||||
class OnBroadcastTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint8_t>,
|
||||
class OnBroadcastTrigger final : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint16_t>,
|
||||
public ESPNowBroadcastHandler {
|
||||
public:
|
||||
explicit OnBroadcastTrigger(std::array<uint8_t, ESP_NOW_ETH_ALEN> address) : has_address_(true) {
|
||||
@@ -157,7 +157,7 @@ class OnBroadcastTrigger final : public Trigger<const ESPNowRecvInfo &, const ui
|
||||
}
|
||||
explicit OnBroadcastTrigger() {}
|
||||
|
||||
bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override {
|
||||
bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) override {
|
||||
bool match = !this->has_address_ || (memcmp(this->address_, info.src_addr, ESP_NOW_ETH_ALEN) == 0);
|
||||
if (!match)
|
||||
return false;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "espnow_err.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
@@ -96,9 +97,9 @@ void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status)
|
||||
void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
|
||||
// Drop oversized frames before copying. ESP-NOW v2 peers (IDF >= 5.4 builds a
|
||||
// v2 stack with no opt-out) can send up to ESP_NOW_MAX_DATA_LEN_V2 (1470 B),
|
||||
// but our receive buffer is ESP_NOW_MAX_DATA_LEN (250 B); copying a larger
|
||||
// frame would overflow packet_.receive.data.
|
||||
if (size < 0 || size > ESP_NOW_MAX_DATA_LEN) {
|
||||
// but the receive buffer only fits v2 frames with ``max_payload_size``; copying a
|
||||
// larger frame would overflow packet_.receive.data.
|
||||
if (size < 0 || size > ESPNOW_MAX_DATA_LEN) {
|
||||
global_esp_now->receive_packet_queue_.increment_dropped_count();
|
||||
return;
|
||||
}
|
||||
@@ -285,11 +286,14 @@ void ESPNowComponent::loop() {
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char src_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
char dst_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
// Cap the hex dump at a v1 frame: a full v2 frame would need a
|
||||
// ~4.4 KB stack buffer.
|
||||
char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)];
|
||||
format_mac_addr_upper(info.src_addr, src_buf);
|
||||
format_mac_addr_upper(info.des_addr, dst_buf);
|
||||
ESP_LOGV(TAG, "<<< [%s -> %s] %s", src_buf, dst_buf,
|
||||
format_hex_pretty_to(hex_buf, packet->packet_.receive.data, packet->packet_.receive.size));
|
||||
format_hex_pretty_to(hex_buf, packet->packet_.receive.data,
|
||||
std::min<uint16_t>(packet->packet_.receive.size, ESP_NOW_MAX_DATA_LEN)));
|
||||
#endif
|
||||
if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
|
||||
for (auto *handler : this->broadcast_handlers_) {
|
||||
@@ -362,7 +366,7 @@ esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payl
|
||||
return ESP_ERR_ESPNOW_PEER_NOT_SET;
|
||||
} else if (memcmp(peer_address, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
|
||||
return ESP_ERR_ESPNOW_OWN_ADDRESS;
|
||||
} else if (size > ESP_NOW_MAX_DATA_LEN) {
|
||||
} else if (size > ESPNOW_MAX_DATA_LEN) {
|
||||
return ESP_ERR_ESPNOW_DATA_SIZE;
|
||||
} else if (!esp_now_is_peer_exist(peer_address)) {
|
||||
if (memcmp(peer_address, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0 || this->auto_add_peer_) {
|
||||
|
||||
@@ -62,7 +62,7 @@ class ESPNowUnknownPeerHandler {
|
||||
/// @param data Pointer to the received data payload
|
||||
/// @param size Size of the received data in bytes
|
||||
/// @return true if the packet was handled, false otherwise
|
||||
virtual bool on_unknown_peer(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) = 0;
|
||||
virtual bool on_unknown_peer(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) = 0;
|
||||
};
|
||||
|
||||
/// Handler interface for receiving ESPNow packets
|
||||
@@ -74,7 +74,7 @@ class ESPNowReceivedPacketHandler {
|
||||
/// @param data Pointer to the received data payload
|
||||
/// @param size Size of the received data in bytes
|
||||
/// @return true if the packet was handled, false otherwise
|
||||
virtual bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) = 0;
|
||||
virtual bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) = 0;
|
||||
};
|
||||
/// Handler interface for receiving ESPNow broadcast packets
|
||||
/// Components should inherit from this class to handle incoming ESPNow data
|
||||
@@ -85,7 +85,7 @@ class ESPNowBroadcastHandler {
|
||||
/// @param data Pointer to the received data payload
|
||||
/// @param size Size of the received data in bytes
|
||||
/// @return true if the packet was handled, false otherwise
|
||||
virtual bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) = 0;
|
||||
virtual bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) = 0;
|
||||
};
|
||||
|
||||
class ESPNowComponent final : public Component {
|
||||
|
||||
@@ -19,6 +19,23 @@ namespace esphome::espnow {
|
||||
static const uint8_t ESPNOW_BROADCAST_ADDR[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
static const uint8_t ESPNOW_MULTICAST_ADDR[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE};
|
||||
|
||||
// Maximum payload this component sends and receives, from the
|
||||
// ``max_payload_size`` option. The radio stack speaks ESP-NOW v2 regardless
|
||||
// (negotiated per peer); payloads beyond the v1 limit (250 bytes) are opt-in
|
||||
// because the packet pools are statically sized from this, so their RAM cost
|
||||
// is proportional (~8 KB at 250 bytes, ~44 KB at the v2 limit of 1470).
|
||||
#ifndef USE_ESPNOW_MAX_PAYLOAD_SIZE
|
||||
#define USE_ESPNOW_MAX_PAYLOAD_SIZE ESP_NOW_MAX_DATA_LEN
|
||||
#endif
|
||||
static constexpr uint16_t ESPNOW_MAX_DATA_LEN = USE_ESPNOW_MAX_PAYLOAD_SIZE;
|
||||
#ifdef ESP_NOW_MAX_DATA_LEN_V2
|
||||
static_assert(ESPNOW_MAX_DATA_LEN <= ESP_NOW_MAX_DATA_LEN_V2,
|
||||
"espnow max_payload_size cannot exceed the ESP-NOW v2 frame limit");
|
||||
#else
|
||||
static_assert(ESPNOW_MAX_DATA_LEN <= ESP_NOW_MAX_DATA_LEN,
|
||||
"espnow max_payload_size beyond 250 bytes requires an ESP-IDF with ESP-NOW v2 support (5.4+)");
|
||||
#endif
|
||||
|
||||
struct WifiPacketRxControl {
|
||||
int8_t rssi; // Received Signal Strength Indicator (RSSI) of packet, unit: dBm
|
||||
uint32_t timestamp; // Timestamp in microseconds when the packet was received, precise only if modem sleep or
|
||||
@@ -78,10 +95,10 @@ class ESPNowPacket {
|
||||
union {
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct received_data {
|
||||
ESPNowRecvInfo info; // Information about the received packet
|
||||
uint8_t data[ESP_NOW_MAX_DATA_LEN]; // Data received in the packet
|
||||
uint8_t size; // Size of the received data
|
||||
WifiPacketRxControl rx_ctrl; // Status of the received packet
|
||||
ESPNowRecvInfo info; // Information about the received packet
|
||||
uint8_t data[ESPNOW_MAX_DATA_LEN]; // Data received in the packet
|
||||
uint16_t size; // Size of the received data
|
||||
WifiPacketRxControl rx_ctrl; // Status of the received packet
|
||||
} receive;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
@@ -144,15 +161,15 @@ class ESPNowSendPacket {
|
||||
this->callback_ = nullptr; // Reset callback
|
||||
}
|
||||
|
||||
uint8_t address_[ESP_NOW_ETH_ALEN]{0}; // MAC address of the peer to send the packet to
|
||||
uint8_t data_[ESP_NOW_MAX_DATA_LEN]{0}; // Data to send
|
||||
uint8_t size_{0}; // Size of the data to send, must be <= ESP_NOW_MAX_DATA_LEN
|
||||
send_callback_t callback_{nullptr}; // Callback to call when the send operation is complete
|
||||
uint8_t address_[ESP_NOW_ETH_ALEN]{0}; // MAC address of the peer to send the packet to
|
||||
uint8_t data_[ESPNOW_MAX_DATA_LEN]{0}; // Data to send
|
||||
uint16_t size_{0}; // Size of the data to send, must be <= ESPNOW_MAX_DATA_LEN
|
||||
send_callback_t callback_{nullptr}; // Callback to call when the send operation is complete
|
||||
|
||||
private:
|
||||
void init_data_(const uint8_t *peer_address, const uint8_t *payload, size_t size) {
|
||||
memcpy(this->address_, peer_address, ESP_NOW_ETH_ALEN);
|
||||
if (size > ESP_NOW_MAX_DATA_LEN) {
|
||||
if (size > ESPNOW_MAX_DATA_LEN) {
|
||||
this->size_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ void ESPNowTransport::send_packet(const std::vector<uint8_t> &buf) const {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buf.size() > ESP_NOW_MAX_DATA_LEN) {
|
||||
ESP_LOGE(TAG, "Packet too large: %zu bytes (max %d)", buf.size(), ESP_NOW_MAX_DATA_LEN);
|
||||
if (buf.size() > ESPNOW_MAX_DATA_LEN) {
|
||||
ESP_LOGE(TAG, "Packet too large: %zu bytes (max %u)", buf.size(), (unsigned) ESPNOW_MAX_DATA_LEN);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ void ESPNowTransport::send_packet(const std::vector<uint8_t> &buf) const {
|
||||
});
|
||||
}
|
||||
|
||||
bool ESPNowTransport::on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) {
|
||||
ESP_LOGV(TAG, "Received packet of size %u from %02X:%02X:%02X:%02X:%02X:%02X", size, info.src_addr[0],
|
||||
bool ESPNowTransport::on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) {
|
||||
ESP_LOGV(TAG, "Received packet of size %u from %02X:%02X:%02X:%02X:%02X:%02X", (unsigned) size, info.src_addr[0],
|
||||
info.src_addr[1], info.src_addr[2], info.src_addr[3], info.src_addr[4], info.src_addr[5]);
|
||||
|
||||
if (data == nullptr || size == 0) {
|
||||
@@ -70,9 +70,9 @@ bool ESPNowTransport::on_receive(const ESPNowRecvInfo &info, const uint8_t *data
|
||||
return false; // Allow other handlers to run
|
||||
}
|
||||
|
||||
bool ESPNowTransport::on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) {
|
||||
ESP_LOGV(TAG, "Received broadcast packet of size %u from %02X:%02X:%02X:%02X:%02X:%02X", size, info.src_addr[0],
|
||||
info.src_addr[1], info.src_addr[2], info.src_addr[3], info.src_addr[4], info.src_addr[5]);
|
||||
bool ESPNowTransport::on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) {
|
||||
ESP_LOGV(TAG, "Received broadcast packet of size %u from %02X:%02X:%02X:%02X:%02X:%02X", (unsigned) size,
|
||||
info.src_addr[0], info.src_addr[1], info.src_addr[2], info.src_addr[3], info.src_addr[4], info.src_addr[5]);
|
||||
|
||||
if (data == nullptr || size == 0) {
|
||||
ESP_LOGW(TAG, "Received empty or null broadcast packet");
|
||||
|
||||
@@ -24,12 +24,12 @@ class ESPNowTransport final : public packet_transport::PacketTransport,
|
||||
}
|
||||
|
||||
// ESPNow handler interface
|
||||
bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override;
|
||||
bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override;
|
||||
bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) override;
|
||||
bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint16_t size) override;
|
||||
|
||||
protected:
|
||||
void send_packet(const std::vector<uint8_t> &buf) const override;
|
||||
size_t get_max_packet_size() override { return ESP_NOW_MAX_DATA_LEN; }
|
||||
size_t get_max_packet_size() override { return ESPNOW_MAX_DATA_LEN; }
|
||||
bool should_send() override;
|
||||
|
||||
peer_address_t peer_address_{{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
|
||||
|
||||
@@ -179,9 +179,11 @@ _ALWAYS_EXTERNAL_IDF_COMPONENTS = {"LAN8670", "ENC28J60"}
|
||||
|
||||
# ESP32-only SPI ethernet types (W5100 is RP2040-only, no ESP-IDF driver)
|
||||
SPI_ETHERNET_TYPES = {"W5500", "DM9051", "ENC28J60"}
|
||||
# RP2040-supported ethernet types (SPI and PIO QSPI)
|
||||
RP2040_ETHERNET_TYPES = {"W5100", "W5500", "W6100", "W6300", "ENC28J60"}
|
||||
_RP2040_SPI_LIBRARIES = {
|
||||
# RP2-supported ethernet types (SPI and PIO QSPI). Applies to the whole
|
||||
# RP2 family (RP2040 and RP2350); the chip-specific W5100 caveat in the
|
||||
# comment above is about ESP-IDF driver coverage, not the RP2 platform.
|
||||
RP2_ETHERNET_TYPES = {"W5100", "W5500", "W6100", "W6300", "ENC28J60"}
|
||||
_RP2_SPI_LIBRARIES = {
|
||||
"W5100": "lwIP_w5100",
|
||||
"W5500": "lwIP_w5500",
|
||||
"ENC28J60": "lwIP_enc28j60",
|
||||
@@ -361,10 +363,10 @@ def _validate(config):
|
||||
f"{config[CONF_TYPE]} PHY requires RMII interface and is only supported "
|
||||
f"on ESP32 classic and ESP32-P4, not {variant}"
|
||||
)
|
||||
elif CORE.is_rp2040 and config[CONF_TYPE] not in RP2040_ETHERNET_TYPES:
|
||||
elif CORE.is_rp2 and config[CONF_TYPE] not in RP2_ETHERNET_TYPES:
|
||||
raise cv.Invalid(
|
||||
f"Only {', '.join(sorted(RP2040_ETHERNET_TYPES))} are supported on RP2040, "
|
||||
f"not {config[CONF_TYPE]}"
|
||||
f"Only {', '.join(sorted(RP2_ETHERNET_TYPES))} are supported on the RP2 "
|
||||
f"platform, not {config[CONF_TYPE]}"
|
||||
)
|
||||
return config
|
||||
|
||||
@@ -459,7 +461,7 @@ SPI_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.only_on([Platform.ESP32, Platform.RP2040]),
|
||||
cv.only_on([Platform.ESP32, Platform.RP2]),
|
||||
_validate_spi_interface,
|
||||
)
|
||||
|
||||
@@ -473,13 +475,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
"JL1101": RMII_SCHEMA,
|
||||
"KSZ8081": RMII_SCHEMA,
|
||||
"KSZ8081RNA": RMII_SCHEMA,
|
||||
"W5100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])),
|
||||
"W5100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2])),
|
||||
"W5500": SPI_SCHEMA,
|
||||
"OPENETH": cv.All(BASE_SCHEMA, cv.only_on([Platform.ESP32])),
|
||||
"DM9051": SPI_SCHEMA,
|
||||
"ENC28J60": SPI_SCHEMA,
|
||||
"W6100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])),
|
||||
"W6300": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])),
|
||||
"W6100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2])),
|
||||
"W6300": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2])),
|
||||
"LAN8670": RMII_SCHEMA,
|
||||
"GENERIC": GENERIC_SCHEMA,
|
||||
"YT8531": GENERIC_SCHEMA,
|
||||
@@ -537,7 +539,7 @@ async def to_code(config):
|
||||
|
||||
if CORE.is_esp32:
|
||||
await _to_code_esp32(var, config)
|
||||
elif CORE.is_rp2040:
|
||||
elif CORE.is_rp2:
|
||||
await _to_code_rp2040(var, config)
|
||||
|
||||
cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
|
||||
@@ -670,7 +672,7 @@ async def _to_code_rp2040(var: cg.Pvariable, config: ConfigType) -> None:
|
||||
cg.add(var.set_reset_pin(config[CONF_RESET_PIN]))
|
||||
|
||||
cg.add_define("USE_ETHERNET_SPI")
|
||||
cg.add_library(_RP2040_SPI_LIBRARIES[config[CONF_TYPE]], None)
|
||||
cg.add_library(_RP2_SPI_LIBRARIES[config[CONF_TYPE]], None)
|
||||
|
||||
|
||||
def _final_validate_rmii_pins(config: ConfigType) -> None:
|
||||
@@ -752,7 +754,7 @@ _platform_filter = filter_source_files_from_platform(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
},
|
||||
"ethernet_component_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||
"ethernet_component_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
|
||||
"esp_eth_phy_jl1101.c": {
|
||||
PlatformFramework.ESP32_IDF,
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
|
||||
@@ -25,7 +25,7 @@ extern "C" eth_esp32_emac_config_t eth_esp32_emac_default_config(void);
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
#if defined(USE_ETHERNET_W5500)
|
||||
#include <W5500lwIP.h>
|
||||
#elif defined(USE_ETHERNET_W5100)
|
||||
@@ -182,14 +182,14 @@ class EthernetComponent final : public Component {
|
||||
#endif // USE_ETHERNET_SPI
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
void set_clk_pin(uint8_t clk_pin);
|
||||
void set_miso_pin(uint8_t miso_pin);
|
||||
void set_mosi_pin(uint8_t mosi_pin);
|
||||
void set_cs_pin(uint8_t cs_pin);
|
||||
void set_interrupt_pin(int8_t interrupt_pin);
|
||||
void set_reset_pin(int8_t reset_pin);
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_RP2
|
||||
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); }
|
||||
@@ -272,7 +272,7 @@ class EthernetComponent final : public Component {
|
||||
esp_eth_phy_t *phy_{nullptr};
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
static constexpr uint32_t LINK_CHECK_INTERVAL = 500; // ms between link/IP polls
|
||||
#if defined(USE_ETHERNET_W5100)
|
||||
static constexpr uint32_t RESET_DELAY_MS = 150; // W5100S PLL lock time
|
||||
@@ -301,7 +301,7 @@ class EthernetComponent final : public Component {
|
||||
uint8_t cs_pin_;
|
||||
int8_t interrupt_pin_{-1};
|
||||
int8_t reset_pin_{-1};
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_RP2
|
||||
|
||||
// Common members
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
|
||||
@@ -839,7 +839,7 @@ void EthernetComponent::dump_connect_params_() {
|
||||
case ETH_SPEED_100M:
|
||||
link_speed = 100;
|
||||
break;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 1, 0)
|
||||
case ETH_SPEED_1000M:
|
||||
link_speed = 1000;
|
||||
break;
|
||||
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
#include "ethernet_component.h"
|
||||
|
||||
#if defined(USE_ETHERNET) && defined(USE_RP2040)
|
||||
#if defined(USE_ETHERNET) && defined(USE_RP2)
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "esphome/components/rp2040/gpio.h"
|
||||
#include "esphome/components/rp2/gpio.h"
|
||||
|
||||
#include <SPI.h>
|
||||
#include <lwip/dns.h>
|
||||
@@ -29,7 +29,7 @@ void EthernetComponent::setup() {
|
||||
|
||||
// Toggle reset pin if configured
|
||||
if (this->reset_pin_ >= 0) {
|
||||
rp2040::RP2040GPIOPin reset_pin;
|
||||
rp2::RP2GPIOPin reset_pin;
|
||||
reset_pin.set_pin(this->reset_pin_);
|
||||
reset_pin.set_flags(gpio::FLAG_OUTPUT);
|
||||
reset_pin.setup();
|
||||
@@ -380,4 +380,4 @@ void EthernetComponent::disable() {
|
||||
|
||||
} // namespace esphome::ethernet
|
||||
|
||||
#endif // USE_ETHERNET && USE_RP2040
|
||||
#endif // USE_ETHERNET && USE_RP2
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#if !defined(USE_RP2040) && !defined(USE_HOST)
|
||||
#if !defined(USE_RP2) && !defined(USE_HOST)
|
||||
|
||||
namespace esphome::factory_reset {
|
||||
|
||||
@@ -73,4 +73,4 @@ void FactoryResetComponent::setup() {
|
||||
|
||||
} // namespace esphome::factory_reset
|
||||
|
||||
#endif // !defined(USE_RP2040) && !defined(USE_HOST)
|
||||
#endif // !defined(USE_RP2) && !defined(USE_HOST)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#if !defined(USE_RP2040) && !defined(USE_HOST)
|
||||
#if !defined(USE_RP2) && !defined(USE_HOST)
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_system.h>
|
||||
@@ -32,4 +32,4 @@ class FactoryResetComponent final : public Component {
|
||||
|
||||
} // namespace esphome::factory_reset
|
||||
|
||||
#endif // !defined(USE_RP2040) && !defined(USE_HOST)
|
||||
#endif // !defined(USE_RP2) && !defined(USE_HOST)
|
||||
|
||||
@@ -39,7 +39,6 @@ CONFIG_SCHEMA = (
|
||||
# due to hardware limitations or lack of reliable interrupt support. This ensures
|
||||
# stable operation on these platforms. Future maintainers should verify platform
|
||||
# capabilities before changing this default behavior.
|
||||
# nrf52 has no gpio interrupts implemented yet
|
||||
cv.SplitDefault(
|
||||
CONF_USE_INTERRUPT,
|
||||
bk72xx=False,
|
||||
@@ -47,8 +46,8 @@ CONFIG_SCHEMA = (
|
||||
esp8266=True,
|
||||
host=True,
|
||||
ln882x=False,
|
||||
nrf52=False,
|
||||
rp2040=True,
|
||||
nrf52=True,
|
||||
rp2=True,
|
||||
rtl87xx=False,
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_INTERRUPT_TYPE, default="ANY"): cv.enum(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <cstring>
|
||||
#include "hmac_sha256.h"
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::hmac_sha256 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ def validate_url(value):
|
||||
def validate_ssl_verification(config):
|
||||
error_message = ""
|
||||
|
||||
if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
|
||||
if CORE.is_rp2 and config[CONF_VERIFY_SSL]:
|
||||
error_message = "ESPHome does not support certificate verification on RP2040"
|
||||
|
||||
if (
|
||||
@@ -96,7 +96,7 @@ def _declare_request_class(value):
|
||||
return cv.declare_id(HttpRequestHost)(value)
|
||||
if CORE.is_esp32:
|
||||
return cv.declare_id(HttpRequestIDF)(value)
|
||||
if CORE.is_esp8266 or CORE.is_rp2040:
|
||||
if CORE.is_esp8266 or CORE.is_rp2:
|
||||
return cv.declare_id(HttpRequestArduino)(value)
|
||||
return NotImplementedError
|
||||
|
||||
@@ -118,7 +118,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
||||
cv.Optional(CONF_WATCHDOG_TIMEOUT): cv.All(
|
||||
cv.Any(cv.only_on_esp32, cv.only_on_rp2040),
|
||||
cv.Any(cv.only_on_esp32, cv.only_on_rp2),
|
||||
cv.positive_not_null_time_period,
|
||||
cv.positive_time_period_milliseconds,
|
||||
),
|
||||
@@ -144,7 +144,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
esp8266_arduino=cv.Version(2, 5, 1),
|
||||
esp32_arduino=cv.Version(0, 0, 0),
|
||||
esp_idf=cv.Version(0, 0, 0),
|
||||
rp2040_arduino=cv.Version(0, 0, 0),
|
||||
rp2_arduino=cv.Version(0, 0, 0),
|
||||
host=cv.Version(0, 0, 0),
|
||||
),
|
||||
validate_ssl_verification,
|
||||
@@ -204,7 +204,7 @@ async def to_code(config):
|
||||
)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("ESP8266HTTPClient", None)
|
||||
if CORE.is_rp2040 and CORE.using_arduino:
|
||||
if CORE.is_rp2 and CORE.using_arduino:
|
||||
cg.add_library("HTTPClient", None)
|
||||
if CORE.is_host:
|
||||
if IS_MACOS:
|
||||
@@ -368,7 +368,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
"http_request_host.cpp": {PlatformFramework.HOST_NATIVE},
|
||||
"http_request_arduino.cpp": {
|
||||
PlatformFramework.ESP8266_ARDUINO,
|
||||
PlatformFramework.RP2040_ARDUINO,
|
||||
PlatformFramework.RP2_ARDUINO,
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
|
||||
@@ -510,9 +510,9 @@ template<typename... Ts> class HttpRequestSendAction final : public Action<Ts...
|
||||
return;
|
||||
}
|
||||
|
||||
size_t max_length = this->max_response_buffer_size_;
|
||||
#ifdef USE_HTTP_REQUEST_RESPONSE
|
||||
if (this->capture_response_.value(x...)) {
|
||||
size_t max_length = this->max_response_buffer_size_;
|
||||
std::string response_body;
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
uint8_t *buf = allocator.allocate(max_length);
|
||||
|
||||
@@ -72,7 +72,7 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
||||
|
||||
bool status = container->client_.begin(*stream_ptr, url.c_str());
|
||||
|
||||
#elif defined(USE_RP2040)
|
||||
#elif defined(USE_RP2)
|
||||
if (secure) {
|
||||
container->client_.setInsecure();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
|
||||
#if defined(USE_RP2040)
|
||||
#if defined(USE_RP2)
|
||||
#include <HTTPClient.h>
|
||||
#include <WiFiClient.h>
|
||||
#endif
|
||||
|
||||
@@ -36,7 +36,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
esp8266_arduino=cv.Version(2, 5, 1),
|
||||
esp32_arduino=cv.Version(0, 0, 0),
|
||||
esp_idf=cv.Version(0, 0, 0),
|
||||
rp2040_arduino=cv.Version(0, 0, 0),
|
||||
rp2_arduino=cv.Version(0, 0, 0),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ from esphome.const import (
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_HOST,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RP2,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
@@ -130,7 +130,7 @@ def validate_config(config):
|
||||
return cv.require_framework_version(
|
||||
esp_idf=cv.Version(5, 4, 2), esp32_arduino=cv.Version(3, 2, 1)
|
||||
)(config)
|
||||
if CORE.is_rp2040:
|
||||
if CORE.is_rp2:
|
||||
sda_controller = _rp2040_i2c_controller(config[CONF_SDA])
|
||||
scl_controller = _rp2040_i2c_controller(config[CONF_SCL])
|
||||
if sda_controller != scl_controller:
|
||||
@@ -171,7 +171,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
CONF_SDA,
|
||||
esp32="SDA",
|
||||
esp8266="SDA",
|
||||
rp2040="SDA",
|
||||
rp2="SDA",
|
||||
nrf52="SDA",
|
||||
): pins.internal_gpio_pin_number,
|
||||
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32=True): cv.All(
|
||||
@@ -181,7 +181,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
CONF_SCL,
|
||||
esp32="SCL",
|
||||
esp8266="SCL",
|
||||
rp2040="SCL",
|
||||
rp2="SCL",
|
||||
nrf52="SCL",
|
||||
): pins.internal_gpio_pin_number,
|
||||
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32=True): cv.All(
|
||||
@@ -191,7 +191,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
CONF_FREQUENCY,
|
||||
esp32="50kHz",
|
||||
esp8266="50kHz",
|
||||
rp2040="50kHz",
|
||||
rp2="50kHz",
|
||||
nrf52="100kHz",
|
||||
host="50kHz",
|
||||
): cv.All(
|
||||
@@ -219,7 +219,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
[
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RP2,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_HOST,
|
||||
]
|
||||
@@ -233,7 +233,7 @@ def _final_validate(config):
|
||||
full_config = fv.full_config.get()[CONF_I2C]
|
||||
if CORE.using_zephyr and len(full_config) > 1:
|
||||
raise cv.Invalid("Second i2c is not implemented on Zephyr yet")
|
||||
if CORE.is_rp2040:
|
||||
if CORE.is_rp2:
|
||||
if len(full_config) > 2:
|
||||
raise cv.Invalid(
|
||||
"The maximum number of I2C interfaces for RP2040/RP2350 is 2"
|
||||
@@ -443,7 +443,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
{
|
||||
"i2c_bus_arduino.cpp": {
|
||||
PlatformFramework.ESP8266_ARDUINO,
|
||||
PlatformFramework.RP2040_ARDUINO,
|
||||
PlatformFramework.RP2_ARDUINO,
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
|
||||
@@ -19,7 +19,7 @@ void ArduinoI2CBus::setup() {
|
||||
|
||||
#if defined(USE_ESP8266)
|
||||
wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
#elif defined(USE_RP2040)
|
||||
#elif defined(USE_RP2)
|
||||
// Select Wire instance based on pin assignment, not definition order.
|
||||
// I2C controller = (gpio / 2) % 2: even pairs (0-1,4-5,...) → I2C0, odd pairs (2-3,6-7,...) → I2C1
|
||||
// RP2040 datasheet Table 2 (section 1.4.3): https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
|
||||
@@ -41,7 +41,7 @@ void ArduinoI2CBus::setup() {
|
||||
}
|
||||
|
||||
void ArduinoI2CBus::set_pins_and_clock_() {
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
wire_->setSDA(this->sda_pin_);
|
||||
wire_->setSCL(this->scl_pin_);
|
||||
wire_->begin();
|
||||
@@ -52,7 +52,7 @@ void ArduinoI2CBus::set_pins_and_clock_() {
|
||||
#if defined(USE_ESP8266)
|
||||
// https://github.com/esp8266/Arduino/blob/master/libraries/Wire/Wire.h
|
||||
wire_->setClockStretchLimit(timeout_); // unit: us
|
||||
#elif defined(USE_RP2040)
|
||||
#elif defined(USE_RP2)
|
||||
// https://github.com/earlephilhower/ArduinoCore-API/blob/e37df85425e0ac020bfad226d927f9b00d2e0fb7/api/Stream.h
|
||||
wire_->setTimeout(timeout_ / 1000); // unit: ms
|
||||
#endif
|
||||
@@ -70,7 +70,7 @@ void ArduinoI2CBus::dump_config() {
|
||||
if (timeout_ > 0) {
|
||||
#if defined(USE_ESP8266)
|
||||
ESP_LOGCONFIG(TAG, " Timeout: %u us", this->timeout_);
|
||||
#elif defined(USE_RP2040)
|
||||
#elif defined(USE_RP2)
|
||||
ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "internal_temperature.h"
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::internal_temperature {
|
||||
|
||||
static const char *const TAG = "internal_temperature.rp2040";
|
||||
static const char *const TAG = "internal_temperature.rp2";
|
||||
|
||||
void InternalTemperatureSensor::update() {
|
||||
float temperature = NAN;
|
||||
@@ -28,4 +28,4 @@ void InternalTemperatureSensor::update() {
|
||||
|
||||
} // namespace esphome::internal_temperature
|
||||
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_RP2
|
||||
@@ -10,7 +10,7 @@ from esphome.const import (
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RP2,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
PlatformFramework,
|
||||
@@ -34,7 +34,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.only_on(
|
||||
[
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RP2,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_LN882X,
|
||||
@@ -58,7 +58,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"internal_temperature_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||
"internal_temperature_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
|
||||
"internal_temperature_bk72xx.cpp": {
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::libretiny {
|
||||
|
||||
static const char *const TAG = "lt.gpio";
|
||||
static const char *const TAG = "libretiny.gpio";
|
||||
|
||||
static int IRAM_ATTR flags_to_mode(gpio::Flags flags) {
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::libretiny {
|
||||
|
||||
static const char *const TAG = "lt.component";
|
||||
static const char *const TAG = "libretiny";
|
||||
|
||||
void LTComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
|
||||
@@ -54,7 +54,7 @@ from esphome.const import (
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RP2,
|
||||
PLATFORM_RTL87XX,
|
||||
PlatformFramework,
|
||||
)
|
||||
@@ -154,7 +154,7 @@ HARDWARE_UART_TO_SERIAL = {
|
||||
UART2: cg.global_ns.Serial2,
|
||||
DEFAULT: cg.global_ns.Serial,
|
||||
},
|
||||
PLATFORM_RP2040: {
|
||||
PLATFORM_RP2: {
|
||||
UART0: cg.global_ns.Serial1,
|
||||
UART1: cg.global_ns.Serial2,
|
||||
USB_CDC: cg.global_ns.Serial,
|
||||
@@ -171,7 +171,7 @@ def uart_selection(value):
|
||||
return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
|
||||
if CORE.is_esp8266:
|
||||
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
|
||||
if CORE.is_rp2040:
|
||||
if CORE.is_rp2:
|
||||
return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value)
|
||||
if CORE.is_libretiny:
|
||||
family = get_libretiny_family()
|
||||
@@ -282,7 +282,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
esp32_s2=USB_CDC,
|
||||
esp32_s3=USB_SERIAL_JTAG,
|
||||
esp32_s31=USB_SERIAL_JTAG,
|
||||
rp2040=USB_CDC,
|
||||
rp2=USB_CDC,
|
||||
bk72xx=DEFAULT,
|
||||
ln882x=DEFAULT,
|
||||
rtl87xx=DEFAULT,
|
||||
@@ -292,7 +292,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
[
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RP2,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_RTL87XX,
|
||||
@@ -417,11 +417,7 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
cg.add_define("USE_ESP8266_LOGGER_SERIAL1")
|
||||
enable_serial1()
|
||||
|
||||
if (
|
||||
(CORE.is_esp8266 or CORE.is_rp2040)
|
||||
and has_serial_logging
|
||||
and is_at_least_verbose
|
||||
):
|
||||
if (CORE.is_esp8266 or CORE.is_rp2) and has_serial_logging and is_at_least_verbose:
|
||||
debug_serial_port = HARDWARE_UART_TO_SERIAL[CORE.target_platform][
|
||||
config.get(CONF_HARDWARE_UART)
|
||||
]
|
||||
@@ -605,7 +601,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
},
|
||||
"logger_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||
"logger_host.cpp": {PlatformFramework.HOST_NATIVE},
|
||||
"logger_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||
"logger_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
|
||||
"logger_libretiny.cpp": {
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
|
||||
@@ -206,7 +206,7 @@ void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
|
||||
void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
UARTSelection Logger::get_uart() const { return this->uart_; }
|
||||
#endif
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
#if defined(USE_ESP8266)
|
||||
#include <HardwareSerial.h>
|
||||
#endif // USE_ESP8266
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
#include <HardwareSerial.h>
|
||||
#include <SerialUSB.h>
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_RP2
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
#ifdef USE_ESP32
|
||||
@@ -96,7 +96,7 @@ struct CStrCompare {
|
||||
// macOS allows up to 64 bytes, Linux up to 16
|
||||
static constexpr size_t THREAD_NAME_BUF_SIZE = 64;
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
/** Enum for logging UART selection
|
||||
*
|
||||
* Advanced configuration (pin selection, etc) is not supported.
|
||||
@@ -122,7 +122,7 @@ enum UARTSelection : uint8_t {
|
||||
UART_SELECTION_UART0_SWAP,
|
||||
#endif // USE_ESP8266
|
||||
};
|
||||
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY || USE_ZEPHYR
|
||||
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2 || USE_LIBRETINY || USE_ZEPHYR
|
||||
|
||||
/**
|
||||
* @brief Logger component for all ESPHome logging.
|
||||
@@ -160,7 +160,7 @@ class Logger final : public Component {
|
||||
#ifdef USE_HOST
|
||||
void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
|
||||
/// Get the UART used by the logger.
|
||||
UARTSelection get_uart() const;
|
||||
@@ -351,7 +351,7 @@ class Logger final : public Component {
|
||||
#endif
|
||||
// Group smaller types together at the end
|
||||
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_ZEPHYR)
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_ZEPHYR)
|
||||
UARTSelection uart_{UART_SELECTION_UART0};
|
||||
#endif
|
||||
#ifdef USE_LIBRETINY
|
||||
@@ -505,8 +505,8 @@ class LoggerMessageTrigger final : public Trigger<uint8_t, const char *, const c
|
||||
#include "logger_esp32.h"
|
||||
#elif defined(USE_ESP8266)
|
||||
#include "logger_esp8266.h"
|
||||
#elif defined(USE_RP2040)
|
||||
#include "logger_rp2040.h"
|
||||
#elif defined(USE_RP2)
|
||||
#include "logger_rp2.h"
|
||||
#elif defined(USE_LIBRETINY)
|
||||
#include "logger_libretiny.h"
|
||||
#endif
|
||||
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
#include "logger.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
#include "esphome/components/rp2040/crash_handler.h"
|
||||
#ifdef USE_RP2_CRASH_HANDLER
|
||||
#include "esphome/components/rp2/crash_handler.h"
|
||||
#endif
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -29,8 +29,8 @@ void Logger::pre_setup() {
|
||||
}
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
rp2040::crash_handler_log();
|
||||
#ifdef USE_RP2_CRASH_HANDLER
|
||||
rp2::crash_handler_log();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -50,4 +50,4 @@ const LogString *Logger::get_uart_selection_() {
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_RP2
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
@@ -57,7 +57,7 @@ void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
static const struct device *uart_dev = nullptr;
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
case UART_SELECTION_UART0: // NOLINT(bugprone-branch-clone)
|
||||
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart0));
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
|
||||
@@ -929,7 +929,7 @@ void lv_mem_init() {}
|
||||
|
||||
void lv_mem_deinit() {}
|
||||
|
||||
#if defined(USE_HOST) || defined(USE_RP2040) || defined(USE_ESP8266)
|
||||
#if defined(USE_HOST) || defined(USE_RP2) || defined(USE_ESP8266)
|
||||
void *lv_malloc_core(size_t size) {
|
||||
auto *ptr = malloc(size); // NOLINT
|
||||
if (ptr == nullptr) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::md5 {
|
||||
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2040) && !defined(USE_ESP32)
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_ESP32)
|
||||
void MD5Digest::init() {
|
||||
memset(this->digest_, 0, 16);
|
||||
MD5Init(&this->ctx_);
|
||||
@@ -14,7 +14,7 @@ void MD5Digest::init() {
|
||||
void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); }
|
||||
|
||||
void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); }
|
||||
#endif // USE_ARDUINO && !USE_RP2040
|
||||
#endif // USE_ARDUINO && !USE_RP2
|
||||
|
||||
#ifdef USE_ESP32
|
||||
void MD5Digest::init() {
|
||||
@@ -27,7 +27,7 @@ void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this-
|
||||
void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); }
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
void MD5Digest::init() {
|
||||
memset(this->digest_, 0, 16);
|
||||
br_md5_init(&this->ctx_);
|
||||
@@ -36,7 +36,7 @@ void MD5Digest::init() {
|
||||
void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_, data, len); }
|
||||
|
||||
void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); }
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_RP2
|
||||
|
||||
#ifdef USE_HOST
|
||||
MD5Digest::~MD5Digest() {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#define MD5_CTX_TYPE md5_context_t
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#ifdef USE_RP2
|
||||
#include <MD5Builder.h>
|
||||
#define MD5_CTX_TYPE br_md5_context
|
||||
#endif
|
||||
|
||||
@@ -70,11 +70,11 @@ def _require_network_interface(config: ConfigType) -> ConfigType:
|
||||
window. Reject at config time rather than silently producing a component
|
||||
that never initializes.
|
||||
"""
|
||||
if config.get(CONF_DISABLED) or not (CORE.is_esp8266 or CORE.is_rp2040):
|
||||
if config.get(CONF_DISABLED) or not (CORE.is_esp8266 or CORE.is_rp2):
|
||||
return config
|
||||
full_config = fv.full_config.get()
|
||||
has_wifi = "wifi" in full_config
|
||||
has_ethernet = CORE.is_rp2040 and "ethernet" in full_config
|
||||
has_ethernet = CORE.is_rp2 and "ethernet" in full_config
|
||||
if not (has_wifi or has_ethernet):
|
||||
options = "'wifi'" if CORE.is_esp8266 else "'wifi' or 'ethernet'"
|
||||
raise cv.Invalid(
|
||||
@@ -192,18 +192,18 @@ async def to_code(config):
|
||||
if CORE.using_arduino:
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("ESP8266mDNS", None)
|
||||
elif CORE.is_rp2040:
|
||||
elif CORE.is_rp2:
|
||||
cg.add_library("LEAmDNS", None)
|
||||
|
||||
# Subscribe to the network IP state listener(s) so MDNS.update() is only
|
||||
# scheduled during the probe+announce phase. Same on_ip_state() override
|
||||
# serves both WiFi and Ethernet (signatures match).
|
||||
if CORE.is_esp8266 or CORE.is_rp2040:
|
||||
if CORE.is_esp8266 or CORE.is_rp2:
|
||||
if "wifi" in CORE.config:
|
||||
from esphome.components import wifi
|
||||
|
||||
wifi.request_wifi_ip_state_listener()
|
||||
if CORE.is_rp2040 and "ethernet" in CORE.config:
|
||||
if CORE.is_rp2 and "ethernet" in CORE.config:
|
||||
from esphome.components import ethernet
|
||||
|
||||
ethernet.request_ethernet_ip_state_listener()
|
||||
@@ -274,7 +274,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
},
|
||||
"mdns_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||
"mdns_host.cpp": {PlatformFramework.HOST_NATIVE},
|
||||
"mdns_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||
"mdns_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
|
||||
"mdns_libretiny.cpp": {
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
|
||||
@@ -100,7 +100,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
||||
if (!friendly_name_empty) {
|
||||
txt_count++; // friendly_name
|
||||
}
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_NRF52)
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_NRF52)
|
||||
txt_count++; // platform
|
||||
#endif
|
||||
#if defined(USE_WIFI) || defined(USE_ETHERNET) || defined(USE_OPENTHREAD)
|
||||
@@ -136,9 +136,9 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
||||
#elif defined(USE_ESP32)
|
||||
MDNS_STATIC_CONST_CHAR(PLATFORM_ESP32, "ESP32");
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_ESP32)});
|
||||
#elif defined(USE_RP2040)
|
||||
MDNS_STATIC_CONST_CHAR(PLATFORM_RP2040, "RP2040");
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_RP2040)});
|
||||
#elif defined(USE_RP2)
|
||||
MDNS_STATIC_CONST_CHAR(PLATFORM_RP2, "RP2");
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_RP2)});
|
||||
#elif defined(USE_LIBRETINY)
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(lt_cpu_get_model_name())});
|
||||
#elif defined(USE_NRF52)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
// On ESP8266 and RP2040 the scheduler-backed MDNS.update() polling window is armed by
|
||||
// IP state listener events on whichever network interface is configured.
|
||||
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && \
|
||||
#if (defined(USE_ESP8266) || defined(USE_RP2)) && \
|
||||
((defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS)) || \
|
||||
(defined(USE_ETHERNET) && defined(USE_ETHERNET_IP_STATE_LISTENERS)))
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
@@ -145,7 +145,7 @@ class MDNSComponent final : public Component
|
||||
#ifdef USE_MDNS_STORE_SERVICES
|
||||
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
|
||||
#endif
|
||||
#if defined(USE_RP2040) && defined(USE_MDNS_EVENT_DRIVEN_POLLING)
|
||||
#if defined(USE_RP2) && defined(USE_MDNS_EVENT_DRIVEN_POLLING)
|
||||
// RP2040 defers MDNS.begin() until the first IP-up event; this tracks that.
|
||||
bool initialized_{false};
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#if defined(USE_RP2040) && defined(USE_MDNS)
|
||||
#if defined(USE_RP2) && defined(USE_MDNS)
|
||||
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
namespace esphome::mdns {
|
||||
|
||||
static void register_rp2040(MDNSComponent *, StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
|
||||
static void register_rp2(MDNSComponent *, StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
|
||||
MDNS.begin(App.get_name().c_str());
|
||||
|
||||
for (const auto &service : services) {
|
||||
@@ -82,7 +82,7 @@ void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::
|
||||
return;
|
||||
}
|
||||
if (!this->initialized_) {
|
||||
this->setup_buffers_and_register_(register_rp2040);
|
||||
this->setup_buffers_and_register_(register_rp2);
|
||||
this->initialized_ = true;
|
||||
} else {
|
||||
MDNS.notifyAPChange();
|
||||
@@ -7,22 +7,22 @@
|
||||
|
||||
namespace esphome::micro_wake_word {
|
||||
|
||||
template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<MicroWakeWord> {
|
||||
template<typename... Ts> class StartAction final : public Action<Ts...>, public Parented<MicroWakeWord> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->start(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<MicroWakeWord> {
|
||||
template<typename... Ts> class StopAction final : public Action<Ts...>, public Parented<MicroWakeWord> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->stop(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsRunningCondition : public Condition<Ts...>, public Parented<MicroWakeWord> {
|
||||
template<typename... Ts> class IsRunningCondition final : public Condition<Ts...>, public Parented<MicroWakeWord> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_running(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EnableModelAction : public Action<Ts...> {
|
||||
template<typename... Ts> class EnableModelAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit EnableModelAction(WakeWordModel *wake_word_model) : wake_word_model_(wake_word_model) {}
|
||||
void play(const Ts &...x) override { this->wake_word_model_->enable(); }
|
||||
@@ -31,7 +31,7 @@ template<typename... Ts> class EnableModelAction : public Action<Ts...> {
|
||||
WakeWordModel *wake_word_model_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisableModelAction : public Action<Ts...> {
|
||||
template<typename... Ts> class DisableModelAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit DisableModelAction(WakeWordModel *wake_word_model) : wake_word_model_(wake_word_model) {}
|
||||
void play(const Ts &...x) override { this->wake_word_model_->disable(); }
|
||||
@@ -40,7 +40,7 @@ template<typename... Ts> class DisableModelAction : public Action<Ts...> {
|
||||
WakeWordModel *wake_word_model_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ModelIsEnabledCondition : public Condition<Ts...> {
|
||||
template<typename... Ts> class ModelIsEnabledCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
explicit ModelIsEnabledCondition(WakeWordModel *wake_word_model) : wake_word_model_(wake_word_model) {}
|
||||
bool check(const Ts &...x) override { return this->wake_word_model_->is_enabled(); }
|
||||
|
||||
@@ -31,10 +31,10 @@ enum State {
|
||||
STOPPED,
|
||||
};
|
||||
|
||||
class MicroWakeWord : public Component
|
||||
class MicroWakeWord final : public Component
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
,
|
||||
public ota::OTAGlobalStateListener
|
||||
public ota::OTAGlobalStateListener
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user