mirror of
https://github.com/esphome/esphome.git
synced 2026-09-08 22:08:49 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36250682b0 | ||
|
|
4f75647f63 | ||
|
|
9f5121e271 | ||
|
|
a1e3ec7118 | ||
|
|
d3bae21d13 | ||
|
|
9d138e73c9 | ||
|
|
e23a6bf59f |
@@ -398,23 +398,13 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
│ ├── i2c/ # I2C bus
|
│ ├── i2c/ # I2C bus
|
||||||
│ └── spi/ # SPI bus
|
│ └── spi/ # SPI bus
|
||||||
└── components/[component]/
|
└── components/[component]/
|
||||||
├── common.yaml # Component-only config (no bus definitions)
|
├── common.yaml # Component-only config (no bus definitions)
|
||||||
├── test.esp32-idf.yaml # config + compile
|
├── test.esp32-idf.yaml
|
||||||
├── test.esp8266-ard.yaml # config + compile
|
├── test.esp8266-ard.yaml
|
||||||
├── test-variant.esp32-idf.yaml # variant test, config + compile
|
└── test.rp2040-ard.yaml
|
||||||
├── validate.esp32-idf.yaml # config-only (never compiled)
|
|
||||||
└── validate-legacy.esp32-idf.yaml # config-only variant
|
|
||||||
```
|
```
|
||||||
Run them using `script/test_build_components`. Use `-c <component>` to test specific components and `-t <target>` for specific platforms.
|
Run them using `script/test_build_components`. Use `-c <component>` to test specific components and `-t <target>` for specific platforms.
|
||||||
|
|
||||||
* **Config-only test files (`validate.*.yaml`):** Use this prefix when a YAML file only needs to exercise schema/validation paths and does not need to be compiled. CI runs `validate.*.yaml` files with `esphome config` only and skips them during compile. The grammar mirrors `test.*.yaml`:
|
|
||||||
- `validate.<platform>.yaml` — base config-only test
|
|
||||||
- `validate-<variant>.<platform>.yaml` — config-only variant
|
|
||||||
|
|
||||||
Use this for things like deprecated-syntax migration tests, schema edge cases, or platform-specific validation branches where building firmware adds no signal. A component may have any mix of `test.*.yaml` and `validate.*.yaml` files. Validate files never participate in bus-grouping; each one runs as its own `esphome config` invocation.
|
|
||||||
|
|
||||||
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/`:
|
||||||
```yaml
|
```yaml
|
||||||
# test.esp32-idf.yaml — use packages for buses
|
# test.esp32-idf.yaml — use packages for buses
|
||||||
@@ -462,7 +452,7 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
6. **Pull Request:** Submit a PR against the `dev` branch. The Pull Request title should have a prefix of the component being worked on (e.g., `[display] Fix bug`, `[abc123] Add new component`). Update documentation, examples, and add `CODEOWNERS` entries as needed. Pull requests should always be made using the `.github/PULL_REQUEST_TEMPLATE.md` template - fill out all sections completely without removing any parts of the template.
|
6. **Pull Request:** Submit a PR against the `dev` branch. The Pull Request title should have a prefix of the component being worked on (e.g., `[display] Fix bug`, `[abc123] Add new component`). Update documentation, examples, and add `CODEOWNERS` entries as needed. Pull requests should always be made using the `.github/PULL_REQUEST_TEMPLATE.md` template - fill out all sections completely without removing any parts of the template.
|
||||||
|
|
||||||
* **Documentation Contributions:**
|
* **Documentation Contributions:**
|
||||||
* Documentation is hosted in the separate `esphome/esphome.io` repository.
|
* Documentation is hosted in the separate `esphome/esphome-docs` repository.
|
||||||
* The contribution workflow is the same as for the codebase.
|
* The contribution workflow is the same as for the codebase.
|
||||||
* When editing a component's documentation page, also update the corresponding component index page to ensure both pages remain in sync.
|
* When editing a component's documentation page, also update the corresponding component index page to ensure both pages remain in sync.
|
||||||
|
|
||||||
@@ -681,7 +671,7 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
- [ ] Explored non-breaking alternatives
|
- [ ] Explored non-breaking alternatives
|
||||||
- [ ] Added deprecation warnings if possible (use `ESPDEPRECATED` macro for C++)
|
- [ ] Added deprecation warnings if possible (use `ESPDEPRECATED` macro for C++)
|
||||||
- [ ] Documented migration path in PR description with before/after examples
|
- [ ] Documented migration path in PR description with before/after examples
|
||||||
- [ ] Updated all internal usage and esphome.io
|
- [ ] Updated all internal usage and esphome-docs
|
||||||
- [ ] Tested backward compatibility during deprecation period
|
- [ ] Tested backward compatibility during deprecation period
|
||||||
|
|
||||||
* **Deprecation Pattern (C++):**
|
* **Deprecation Pattern (C++):**
|
||||||
+7
-29
@@ -5,30 +5,24 @@ Checks: >-
|
|||||||
-altera-*,
|
-altera-*,
|
||||||
-android-*,
|
-android-*,
|
||||||
-boost-*,
|
-boost-*,
|
||||||
-bugprone-derived-method-shadowing-base-method,
|
|
||||||
-bugprone-easily-swappable-parameters,
|
-bugprone-easily-swappable-parameters,
|
||||||
-bugprone-implicit-widening-of-multiplication-result,
|
-bugprone-implicit-widening-of-multiplication-result,
|
||||||
-bugprone-invalid-enum-default-initialization,
|
|
||||||
-bugprone-multi-level-implicit-pointer-conversion,
|
-bugprone-multi-level-implicit-pointer-conversion,
|
||||||
-bugprone-narrowing-conversions,
|
-bugprone-narrowing-conversions,
|
||||||
-bugprone-tagged-union-member-count,
|
|
||||||
-bugprone-signed-char-misuse,
|
-bugprone-signed-char-misuse,
|
||||||
-bugprone-switch-missing-default-case,
|
-bugprone-switch-missing-default-case,
|
||||||
-cert-dcl50-cpp,
|
-cert-dcl50-cpp,
|
||||||
-cert-err33-c,
|
-cert-err33-c,
|
||||||
-cert-err58-cpp,
|
-cert-err58-cpp,
|
||||||
-cert-int09-c,
|
|
||||||
-cert-oop57-cpp,
|
-cert-oop57-cpp,
|
||||||
-cert-str34-c,
|
-cert-str34-c,
|
||||||
-clang-analyzer-optin.core.EnumCastOutOfRange,
|
-clang-analyzer-optin.core.EnumCastOutOfRange,
|
||||||
-clang-analyzer-optin.cplusplus.UninitializedObject,
|
-clang-analyzer-optin.cplusplus.UninitializedObject,
|
||||||
-clang-analyzer-osx.*,
|
-clang-analyzer-osx.*,
|
||||||
-clang-analyzer-security.ArrayBound,
|
|
||||||
-clang-diagnostic-delete-abstract-non-virtual-dtor,
|
-clang-diagnostic-delete-abstract-non-virtual-dtor,
|
||||||
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
|
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
|
||||||
-clang-diagnostic-deprecated-declarations,
|
-clang-diagnostic-deprecated-declarations,
|
||||||
-clang-diagnostic-ignored-optimization-argument,
|
-clang-diagnostic-ignored-optimization-argument,
|
||||||
-clang-diagnostic-missing-designated-field-initializers,
|
|
||||||
-clang-diagnostic-missing-field-initializers,
|
-clang-diagnostic-missing-field-initializers,
|
||||||
-clang-diagnostic-shadow-field,
|
-clang-diagnostic-shadow-field,
|
||||||
-clang-diagnostic-unused-const-variable,
|
-clang-diagnostic-unused-const-variable,
|
||||||
@@ -48,7 +42,6 @@ Checks: >-
|
|||||||
-cppcoreguidelines-owning-memory,
|
-cppcoreguidelines-owning-memory,
|
||||||
-cppcoreguidelines-prefer-member-initializer,
|
-cppcoreguidelines-prefer-member-initializer,
|
||||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||||
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
|
|
||||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||||
-cppcoreguidelines-pro-type-const-cast,
|
-cppcoreguidelines-pro-type-const-cast,
|
||||||
@@ -61,13 +54,12 @@ Checks: >-
|
|||||||
-cppcoreguidelines-rvalue-reference-param-not-moved,
|
-cppcoreguidelines-rvalue-reference-param-not-moved,
|
||||||
-cppcoreguidelines-special-member-functions,
|
-cppcoreguidelines-special-member-functions,
|
||||||
-cppcoreguidelines-use-default-member-init,
|
-cppcoreguidelines-use-default-member-init,
|
||||||
-cppcoreguidelines-use-enum-class,
|
|
||||||
-cppcoreguidelines-virtual-class-destructor,
|
-cppcoreguidelines-virtual-class-destructor,
|
||||||
-fuchsia-default-arguments-calls,
|
|
||||||
-fuchsia-default-arguments-declarations,
|
|
||||||
-fuchsia-multiple-inheritance,
|
-fuchsia-multiple-inheritance,
|
||||||
-fuchsia-overloaded-operator,
|
-fuchsia-overloaded-operator,
|
||||||
-fuchsia-statically-constructed-objects,
|
-fuchsia-statically-constructed-objects,
|
||||||
|
-fuchsia-default-arguments-declarations,
|
||||||
|
-fuchsia-default-arguments-calls,
|
||||||
-google-build-using-namespace,
|
-google-build-using-namespace,
|
||||||
-google-explicit-constructor,
|
-google-explicit-constructor,
|
||||||
-google-readability-braces-around-statements,
|
-google-readability-braces-around-statements,
|
||||||
@@ -79,63 +71,49 @@ Checks: >-
|
|||||||
-llvm-else-after-return,
|
-llvm-else-after-return,
|
||||||
-llvm-header-guard,
|
-llvm-header-guard,
|
||||||
-llvm-include-order,
|
-llvm-include-order,
|
||||||
-llvm-prefer-static-over-anonymous-namespace,
|
|
||||||
-llvm-qualified-auto,
|
-llvm-qualified-auto,
|
||||||
-llvm-use-ranges,
|
|
||||||
-llvmlibc-*,
|
-llvmlibc-*,
|
||||||
-misc-const-correctness,
|
-misc-const-correctness,
|
||||||
-misc-include-cleaner,
|
-misc-include-cleaner,
|
||||||
-misc-multiple-inheritance,
|
|
||||||
-misc-no-recursion,
|
-misc-no-recursion,
|
||||||
-misc-non-private-member-variables-in-classes,
|
-misc-non-private-member-variables-in-classes,
|
||||||
-misc-override-with-different-visibility,
|
|
||||||
-misc-unused-parameters,
|
-misc-unused-parameters,
|
||||||
-misc-use-anonymous-namespace,
|
-misc-use-anonymous-namespace,
|
||||||
-misc-use-internal-linkage,
|
|
||||||
-modernize-avoid-bind,
|
-modernize-avoid-bind,
|
||||||
-modernize-avoid-variadic-functions,
|
|
||||||
-modernize-avoid-c-arrays,
|
-modernize-avoid-c-arrays,
|
||||||
-modernize-avoid-c-style-cast,
|
-modernize-concat-nested-namespaces,
|
||||||
-modernize-macro-to-enum,
|
-modernize-macro-to-enum,
|
||||||
-modernize-return-braced-init-list,
|
-modernize-return-braced-init-list,
|
||||||
-modernize-type-traits,
|
-modernize-type-traits,
|
||||||
-modernize-use-auto,
|
-modernize-use-auto,
|
||||||
-modernize-use-constraints,
|
-modernize-use-constraints,
|
||||||
-modernize-use-default-member-init,
|
-modernize-use-default-member-init,
|
||||||
-modernize-use-designated-initializers,
|
|
||||||
-modernize-use-equals-default,
|
-modernize-use-equals-default,
|
||||||
-modernize-use-integer-sign-comparison,
|
|
||||||
-modernize-use-nodiscard,
|
-modernize-use-nodiscard,
|
||||||
-modernize-use-nullptr,
|
-modernize-use-nullptr,
|
||||||
-modernize-use-ranges,
|
-modernize-use-nodiscard,
|
||||||
|
-modernize-use-nullptr,
|
||||||
-modernize-use-trailing-return-type,
|
-modernize-use-trailing-return-type,
|
||||||
-mpi-*,
|
-mpi-*,
|
||||||
-objc-*,
|
-objc-*,
|
||||||
-performance-enum-size,
|
-performance-enum-size,
|
||||||
-portability-avoid-pragma-once,
|
|
||||||
-portability-template-virtual-member-function,
|
|
||||||
-readability-ambiguous-smartptr-reset-call,
|
|
||||||
-readability-avoid-nested-conditional-operator,
|
-readability-avoid-nested-conditional-operator,
|
||||||
|
-readability-container-contains,
|
||||||
-readability-container-data-pointer,
|
-readability-container-data-pointer,
|
||||||
-readability-convert-member-functions-to-static,
|
-readability-convert-member-functions-to-static,
|
||||||
-readability-else-after-return,
|
-readability-else-after-return,
|
||||||
-readability-enum-initial-value,
|
|
||||||
-readability-function-cognitive-complexity,
|
-readability-function-cognitive-complexity,
|
||||||
-readability-implicit-bool-conversion,
|
-readability-implicit-bool-conversion,
|
||||||
-readability-isolate-declaration,
|
-readability-isolate-declaration,
|
||||||
-readability-magic-numbers,
|
-readability-magic-numbers,
|
||||||
-readability-make-member-function-const,
|
-readability-make-member-function-const,
|
||||||
-readability-math-missing-parentheses,
|
|
||||||
-readability-named-parameter,
|
-readability-named-parameter,
|
||||||
-readability-redundant-casting,
|
-readability-redundant-casting,
|
||||||
-readability-redundant-inline-specifier,
|
-readability-redundant-inline-specifier,
|
||||||
-readability-redundant-member-init,
|
-readability-redundant-member-init,
|
||||||
-readability-redundant-parentheses,
|
-readability-redundant-string-init,
|
||||||
-readability-redundant-typename,
|
|
||||||
-readability-uppercase-literal-suffix,
|
-readability-uppercase-literal-suffix,
|
||||||
-readability-use-anyofallof,
|
-readability-use-anyofallof,
|
||||||
-readability-use-std-min-max,
|
|
||||||
-readability-use-concise-preprocessor-directives,
|
|
||||||
WarningsAsErrors: '*'
|
WarningsAsErrors: '*'
|
||||||
FormatStyle: google
|
FormatStyle: google
|
||||||
CheckOptions:
|
CheckOptions:
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
27aaab4e0ebfc10491720345aa746fc2dffa6a3985f73ec111b12dd99078d46f
|
1b1ce6324c50c4595703c7df0a8a479b4fe84b71ff1a8793cce1a16f17a33324
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Required fields:
|
|||||||
- **What does this implement/fix?**: Brief description of changes
|
- **What does this implement/fix?**: Brief description of changes
|
||||||
- **Types of changes**: Check ONE appropriate box (Bugfix, New feature, Breaking change, etc.)
|
- **Types of changes**: Check ONE appropriate box (Bugfix, New feature, Breaking change, etc.)
|
||||||
- **Related issue**: Use `fixes <link>` syntax if applicable
|
- **Related issue**: Use `fixes <link>` syntax if applicable
|
||||||
- **Pull request in esphome.io**: Link if docs are needed
|
- **Pull request in esphome-docs**: Link if docs are needed
|
||||||
- **Test Environment**: Check platforms you tested on
|
- **Test Environment**: Check platforms you tested on
|
||||||
- **Example config.yaml**: Include working example YAML
|
- **Example config.yaml**: Include working example YAML
|
||||||
- **Checklist**: Verify code is tested and tests added
|
- **Checklist**: Verify code is tested and tests added
|
||||||
@@ -54,9 +54,9 @@ Required fields:
|
|||||||
|
|
||||||
- fixes https://github.com/esphome/esphome/issues/XXX
|
- fixes https://github.com/esphome/esphome/issues/XXX
|
||||||
|
|
||||||
**Pull request in [esphome.io](https://github.com/esphome/esphome.io) with documentation (if applicable):**
|
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):**
|
||||||
|
|
||||||
- esphome/esphome.io#XXX
|
- esphome/esphome-docs#XXX
|
||||||
|
|
||||||
## Test Environment
|
## Test Environment
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ component_name:
|
|||||||
- [x] Tests have been added to verify that the new code works (under `tests/` folder).
|
- [x] Tests have been added to verify that the new code works (under `tests/` folder).
|
||||||
|
|
||||||
If user exposed functionality or configuration variables are added/changed:
|
If user exposed functionality or configuration variables are added/changed:
|
||||||
- [ ] Documentation added/updated in [esphome.io](https://github.com/esphome/esphome.io).
|
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. Push and Create PR
|
## 5. Push and Create PR
|
||||||
|
|||||||
+1
-1
@@ -115,4 +115,4 @@ examples/
|
|||||||
Dockerfile
|
Dockerfile
|
||||||
.git/
|
.git/
|
||||||
tests/
|
tests/
|
||||||
.?*
|
.*
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Report an issue with the ESPHome documentation
|
- name: Report an issue with the ESPHome documentation
|
||||||
url: https://github.com/esphome/esphome.io/issues/new/choose
|
url: https://github.com/esphome/esphome-docs/issues/new/choose
|
||||||
about: Report an issue with the ESPHome documentation.
|
about: Report an issue with the ESPHome documentation.
|
||||||
- name: Report an issue with the ESPHome web server
|
- name: Report an issue with the ESPHome web server
|
||||||
url: https://github.com/esphome/esphome-webserver/issues/new/choose
|
url: https://github.com/esphome/esphome-webserver/issues/new/choose
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
- fixes <link to issue>
|
- fixes <link to issue>
|
||||||
|
|
||||||
**Pull request in [esphome.io](https://github.com/esphome/esphome.io) with documentation (if applicable):**
|
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):**
|
||||||
|
|
||||||
- esphome/esphome.io#<esphome.io PR number goes here>
|
- esphome/esphome-docs#<esphome-docs PR number goes here>
|
||||||
|
|
||||||
## Test Environment
|
## Test Environment
|
||||||
|
|
||||||
@@ -43,4 +43,4 @@
|
|||||||
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).
|
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).
|
||||||
|
|
||||||
If user exposed functionality or configuration variables are added/changed:
|
If user exposed functionality or configuration variables are added/changed:
|
||||||
- [ ] Documentation added/updated in [esphome.io](https://github.com/esphome/esphome.io).
|
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ runs:
|
|||||||
|
|
||||||
- name: Build and push to ghcr by digest
|
- name: Build and push to ghcr by digest
|
||||||
id: build-ghcr
|
id: build-ghcr
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILD_SUMMARY: false
|
DOCKER_BUILD_SUMMARY: false
|
||||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||||
@@ -73,7 +73,7 @@ runs:
|
|||||||
|
|
||||||
- name: Build and push to dockerhub by digest
|
- name: Build and push to dockerhub by digest
|
||||||
id: build-dockerhub
|
id: build-dockerhub
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILD_SUMMARY: false
|
DOCKER_BUILD_SUMMARY: false
|
||||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||||
|
|||||||
@@ -27,18 +27,6 @@ runs:
|
|||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ inputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ inputs.cache-key }}
|
||||||
- name: Set up uv
|
|
||||||
# Only needed on cache miss to populate the venv. ``uv pip install``
|
|
||||||
# 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@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
# Pin uv version so the action does not have to fetch the
|
|
||||||
# manifest from raw.githubusercontent.com on every cache
|
|
||||||
# miss; that fetch flakes on Windows runners.
|
|
||||||
version: "0.11.15"
|
|
||||||
- name: Create Python virtual environment
|
- name: Create Python virtual environment
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os != 'Windows'
|
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os != 'Windows'
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -46,8 +34,8 @@ runs:
|
|||||||
python -m venv venv
|
python -m venv venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
uv pip install -r requirements.txt -r requirements_test.txt
|
pip install -r requirements.txt -r requirements_test.txt
|
||||||
uv pip install -e .
|
pip install -e .
|
||||||
- name: Create Python virtual environment
|
- name: Create Python virtual environment
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
|
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -55,5 +43,5 @@ runs:
|
|||||||
python -m venv venv
|
python -m venv venv
|
||||||
source ./venv/Scripts/activate
|
source ./venv/Scripts/activate
|
||||||
python --version
|
python --version
|
||||||
uv pip install -r requirements.txt -r requirements_test.txt
|
pip install -r requirements.txt -r requirements_test.txt
|
||||||
uv pip install -e .
|
pip install -e .
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
../AGENTS.md
|
../.ai/instructions.md
|
||||||
@@ -5,7 +5,6 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
open-pull-requests-limit: 10
|
|
||||||
ignore:
|
ignore:
|
||||||
# Hypotehsis is only used for testing and is updated quite often
|
# Hypotehsis is only used for testing and is updated quite often
|
||||||
- dependency-name: hypothesis
|
- dependency-name: hypothesis
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
DOCS_PR_PATTERNS: [
|
DOCS_PR_PATTERNS: [
|
||||||
/https:\/\/github\.com\/esphome\/esphome\.io\/pull\/\d+/,
|
|
||||||
/esphome\/esphome\.io#\d+/,
|
|
||||||
// Keep matching the old esphome-docs name during the transition period
|
|
||||||
/https:\/\/github\.com\/esphome\/esphome-docs\/pull\/\d+/,
|
/https:\/\/github\.com\/esphome\/esphome-docs\/pull\/\d+/,
|
||||||
/esphome\/esphome-docs#\d+/
|
/esphome\/esphome-docs#\d+/
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
const fs = require('fs');
|
||||||
const { DOCS_PR_PATTERNS } = require('./constants');
|
const { DOCS_PR_PATTERNS } = require('./constants');
|
||||||
const {
|
const {
|
||||||
COMPONENT_REGEX,
|
COMPONENT_REGEX,
|
||||||
@@ -8,31 +9,6 @@ const {
|
|||||||
} = require('../detect-tags');
|
} = require('../detect-tags');
|
||||||
const { loadCodeowners, getEffectiveOwners } = require('../codeowners');
|
const { loadCodeowners, getEffectiveOwners } = require('../codeowners');
|
||||||
|
|
||||||
// Top-level `CONFIG_SCHEMA = ...` (assignment) or `CONFIG_SCHEMA: ConfigType = ...` (annotation).
|
|
||||||
// Ruff/Black enforce exactly one space around `=` and no space before `:`,
|
|
||||||
// so we can match strictly: `CONFIG_SCHEMA ` or `CONFIG_SCHEMA:`.
|
|
||||||
const CONFIG_SCHEMA_REGEX = /^CONFIG_SCHEMA[ :]/m;
|
|
||||||
|
|
||||||
// Fetch a file's contents from the PR head SHA via the GitHub API.
|
|
||||||
// The auto-label workflow runs on `pull_request_target`, which checks out the
|
|
||||||
// base branch — files added by the PR don't exist in the workspace, so we have
|
|
||||||
// to fetch them from the head SHA. Returns null if the file can't be fetched.
|
|
||||||
async function fetchPrFileContent(github, context, path) {
|
|
||||||
try {
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
const { data } = await github.rest.repos.getContent({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
path,
|
|
||||||
ref: context.payload.pull_request.head.sha,
|
|
||||||
});
|
|
||||||
return Buffer.from(data.content, 'base64').toString('utf8');
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Failed to fetch ${path} from PR head:`, error.message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Merge branch detection
|
// Strategy: Merge branch detection
|
||||||
async function detectMergeBranch(context) {
|
async function detectMergeBranch(context) {
|
||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
@@ -69,72 +45,52 @@ async function detectComponentPlatforms(changedFiles, apiData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Strategy: New component detection
|
// Strategy: New component detection
|
||||||
async function detectNewComponents(github, context, prFiles) {
|
async function detectNewComponents(prFiles) {
|
||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
let hasYamlLoadable = false;
|
|
||||||
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
||||||
|
|
||||||
for (const file of addedFiles) {
|
for (const file of addedFiles) {
|
||||||
const componentMatch = file.match(/^esphome\/components\/([^\/]+)\/__init__\.py$/);
|
const componentMatch = file.match(/^esphome\/components\/([^\/]+)\/__init__\.py$/);
|
||||||
if (!componentMatch) continue;
|
if (componentMatch) {
|
||||||
|
try {
|
||||||
labels.add('new-component');
|
const content = fs.readFileSync(file, 'utf8');
|
||||||
const content = await fetchPrFileContent(github, context, file);
|
if (content.includes('IS_TARGET_PLATFORM = True')) {
|
||||||
if (content === null) {
|
labels.add('new-target-platform');
|
||||||
// Safe default: assume YAML-loadable so needs-docs behaviour is unchanged on fetch failure
|
}
|
||||||
hasYamlLoadable = true;
|
} catch (error) {
|
||||||
continue;
|
console.log(`Failed to read content of ${file}:`, error.message);
|
||||||
}
|
}
|
||||||
if (content.includes('IS_TARGET_PLATFORM = True')) {
|
labels.add('new-component');
|
||||||
labels.add('new-target-platform');
|
|
||||||
}
|
|
||||||
if (CONFIG_SCHEMA_REGEX.test(content)) {
|
|
||||||
hasYamlLoadable = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { labels, hasYamlLoadable };
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy: New platform detection
|
// Strategy: New platform detection
|
||||||
async function detectNewPlatforms(github, context, prFiles, apiData) {
|
async function detectNewPlatforms(prFiles, apiData) {
|
||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
let hasYamlLoadable = false;
|
|
||||||
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
||||||
|
|
||||||
const platformPathPatterns = [
|
|
||||||
/^esphome\/components\/([^\/]+)\/([^\/]+)\.py$/,
|
|
||||||
/^esphome\/components\/([^\/]+)\/([^\/]+)\/__init__\.py$/,
|
|
||||||
];
|
|
||||||
|
|
||||||
const removedFiles = new Set(prFiles.filter(file => file.status === 'removed').map(file => file.filename));
|
|
||||||
|
|
||||||
for (const file of addedFiles) {
|
for (const file of addedFiles) {
|
||||||
for (const re of platformPathPatterns) {
|
const platformFileMatch = file.match(/^esphome\/components\/([^\/]+)\/([^\/]+)\.py$/);
|
||||||
const match = file.match(re);
|
if (platformFileMatch) {
|
||||||
if (!match) continue;
|
const [, component, platform] = platformFileMatch;
|
||||||
const platform = match[2];
|
if (apiData.platformComponents.includes(platform)) {
|
||||||
if (!apiData.platformComponents.includes(platform)) break;
|
labels.add('new-platform');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Skip if this is a restructure between flat and subdirectory forms (either direction):
|
const platformDirMatch = file.match(/^esphome\/components\/([^\/]+)\/([^\/]+)\/__init__\.py$/);
|
||||||
// <component>/<platform>.py <-> <component>/<platform>/__init__.py
|
if (platformDirMatch) {
|
||||||
const flatEquivalent = `esphome/components/${match[1]}/${platform}.py`;
|
const [, component, platform] = platformDirMatch;
|
||||||
const subdirEquivalent = `esphome/components/${match[1]}/${platform}/__init__.py`;
|
if (apiData.platformComponents.includes(platform)) {
|
||||||
if (removedFiles.has(flatEquivalent) || removedFiles.has(subdirEquivalent)) break;
|
labels.add('new-platform');
|
||||||
|
|
||||||
labels.add('new-platform');
|
|
||||||
const content = await fetchPrFileContent(github, context, file);
|
|
||||||
if (content === null) {
|
|
||||||
// Safe default: assume YAML-loadable so needs-docs behaviour is unchanged on fetch failure
|
|
||||||
hasYamlLoadable = true;
|
|
||||||
} else if (CONFIG_SCHEMA_REGEX.test(content)) {
|
|
||||||
hasYamlLoadable = true;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { labels, hasYamlLoadable };
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy: Core files detection
|
// Strategy: Core files detection
|
||||||
@@ -344,7 +300,7 @@ function detectMaintainerAccess(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Strategy: Requirements detection
|
// Strategy: Requirements detection
|
||||||
async function detectRequirements(allLabels, prFiles, context, hasYamlLoadable) {
|
async function detectRequirements(allLabels, prFiles, context) {
|
||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
|
|
||||||
// Check for missing tests
|
// Check for missing tests
|
||||||
@@ -352,15 +308,8 @@ async function detectRequirements(allLabels, prFiles, context, hasYamlLoadable)
|
|||||||
labels.add('needs-tests');
|
labels.add('needs-tests');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for missing docs.
|
// Check for missing docs
|
||||||
// `new-feature` (PR-body checkbox) always counts. `new-component` / `new-platform`
|
if (allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) {
|
||||||
// only count when at least one newly added file defines a top-level CONFIG_SCHEMA,
|
|
||||||
// i.e. the new component/platform is actually loadable from YAML.
|
|
||||||
const docsEligible =
|
|
||||||
allLabels.has('new-feature') ||
|
|
||||||
((allLabels.has('new-component') || allLabels.has('new-platform')) && hasYamlLoadable);
|
|
||||||
|
|
||||||
if (docsEligible) {
|
|
||||||
const prBody = context.payload.pull_request.body || '';
|
const prBody = context.payload.pull_request.body || '';
|
||||||
const hasDocsLink = DOCS_PR_PATTERNS.some(pattern => pattern.test(prBody));
|
const hasDocsLink = DOCS_PR_PATTERNS.some(pattern => pattern.test(prBody));
|
||||||
|
|
||||||
|
|||||||
@@ -106,8 +106,8 @@ module.exports = async ({ github, context }) => {
|
|||||||
const [
|
const [
|
||||||
branchLabels,
|
branchLabels,
|
||||||
componentLabels,
|
componentLabels,
|
||||||
newComponentResult,
|
newComponentLabels,
|
||||||
newPlatformResult,
|
newPlatformLabels,
|
||||||
coreLabels,
|
coreLabels,
|
||||||
sizeLabels,
|
sizeLabels,
|
||||||
dashboardLabels,
|
dashboardLabels,
|
||||||
@@ -120,8 +120,8 @@ module.exports = async ({ github, context }) => {
|
|||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
detectMergeBranch(context),
|
detectMergeBranch(context),
|
||||||
detectComponentPlatforms(changedFiles, apiData),
|
detectComponentPlatforms(changedFiles, apiData),
|
||||||
detectNewComponents(github, context, prFiles),
|
detectNewComponents(prFiles),
|
||||||
detectNewPlatforms(github, context, prFiles, apiData),
|
detectNewPlatforms(prFiles, apiData),
|
||||||
detectCoreChanges(changedFiles),
|
detectCoreChanges(changedFiles),
|
||||||
detectPRSize(prFiles, totalAdditions, totalDeletions, totalChanges, isMegaPR, SMALL_PR_THRESHOLD, MEDIUM_PR_THRESHOLD, TOO_BIG_THRESHOLD),
|
detectPRSize(prFiles, totalAdditions, totalDeletions, totalChanges, isMegaPR, SMALL_PR_THRESHOLD, MEDIUM_PR_THRESHOLD, TOO_BIG_THRESHOLD),
|
||||||
detectDashboardChanges(changedFiles),
|
detectDashboardChanges(changedFiles),
|
||||||
@@ -133,13 +133,6 @@ module.exports = async ({ github, context }) => {
|
|||||||
detectMaintainerAccess(context)
|
detectMaintainerAccess(context)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Extract new-component / new-platform results
|
|
||||||
const newComponentLabels = newComponentResult.labels;
|
|
||||||
const newPlatformLabels = newPlatformResult.labels;
|
|
||||||
// Eligible for needs-docs only if any newly added component or platform file
|
|
||||||
// defines a top-level CONFIG_SCHEMA (i.e. is actually loadable from YAML).
|
|
||||||
const hasYamlLoadable = newComponentResult.hasYamlLoadable || newPlatformResult.hasYamlLoadable;
|
|
||||||
|
|
||||||
// Extract deprecated component info
|
// Extract deprecated component info
|
||||||
const deprecatedLabels = deprecatedResult.labels;
|
const deprecatedLabels = deprecatedResult.labels;
|
||||||
const deprecatedInfo = deprecatedResult.deprecatedInfo;
|
const deprecatedInfo = deprecatedResult.deprecatedInfo;
|
||||||
@@ -161,7 +154,7 @@ module.exports = async ({ github, context }) => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Detect requirements based on all other labels
|
// Detect requirements based on all other labels
|
||||||
const requirementLabels = await detectRequirements(allLabels, prFiles, context, hasYamlLoadable);
|
const requirementLabels = await detectRequirements(allLabels, prFiles, context);
|
||||||
for (const label of requirementLabels) {
|
for (const label of requirementLabels) {
|
||||||
allLabels.add(label);
|
allLabels.add(label);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "auto-label-pr",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"test": "node --test tests/*.test.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
const { describe, it } = require('node:test');
|
|
||||||
const assert = require('node:assert/strict');
|
|
||||||
const { detectNewPlatforms, detectNewComponents } = require('../detectors');
|
|
||||||
|
|
||||||
// Minimal GitHub API mock — only repos.getContent is called by detectNewPlatforms/detectNewComponents
|
|
||||||
// to check for CONFIG_SCHEMA in newly added files.
|
|
||||||
function makeGithub(content = '') {
|
|
||||||
return {
|
|
||||||
rest: {
|
|
||||||
repos: {
|
|
||||||
getContent: async () => ({
|
|
||||||
data: { content: Buffer.from(content).toString('base64') }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONTEXT = {
|
|
||||||
repo: { owner: 'esphome', repo: 'esphome' },
|
|
||||||
payload: { pull_request: { head: { sha: 'abc123' }, base: { ref: 'dev' } } }
|
|
||||||
};
|
|
||||||
|
|
||||||
const API_DATA = {
|
|
||||||
targetPlatforms: ['esp32', 'esp8266', 'rp2040'],
|
|
||||||
platformComponents: ['cover', 'sensor', 'binary_sensor', 'switch', 'light', 'fan', 'climate', 'valve']
|
|
||||||
};
|
|
||||||
|
|
||||||
const WITH_SCHEMA = 'CONFIG_SCHEMA = cv.Schema({})';
|
|
||||||
const WITHOUT_SCHEMA = 'CODEOWNERS = ["@esphome/core"]';
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// detectNewPlatforms
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
describe('detectNewPlatforms', () => {
|
|
||||||
describe('restructure detection (no false positives)', () => {
|
|
||||||
it('flat .py -> subdir __init__.py is not a new platform', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/endstop/cover.py', status: 'removed' },
|
|
||||||
{ filename: 'esphome/components/endstop/cover/__init__.py', status: 'added' },
|
|
||||||
];
|
|
||||||
const result = await detectNewPlatforms(makeGithub(WITH_SCHEMA), CONTEXT, prFiles, API_DATA);
|
|
||||||
assert.equal(result.labels.size, 0);
|
|
||||||
assert.equal(result.hasYamlLoadable, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('subdir __init__.py -> flat .py is not a new platform', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/endstop/cover/__init__.py', status: 'removed' },
|
|
||||||
{ filename: 'esphome/components/endstop/cover.py', status: 'added' },
|
|
||||||
];
|
|
||||||
const result = await detectNewPlatforms(makeGithub(WITH_SCHEMA), CONTEXT, prFiles, API_DATA);
|
|
||||||
assert.equal(result.labels.size, 0);
|
|
||||||
assert.equal(result.hasYamlLoadable, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('genuine new platforms', () => {
|
|
||||||
it('new subdir platform with CONFIG_SCHEMA sets new-platform and hasYamlLoadable', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/my_sensor/cover/__init__.py', status: 'added' },
|
|
||||||
];
|
|
||||||
const result = await detectNewPlatforms(makeGithub(WITH_SCHEMA), CONTEXT, prFiles, API_DATA);
|
|
||||||
assert.ok(result.labels.has('new-platform'));
|
|
||||||
assert.equal(result.hasYamlLoadable, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('new flat platform with CONFIG_SCHEMA sets new-platform and hasYamlLoadable', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/my_sensor/cover.py', status: 'added' },
|
|
||||||
];
|
|
||||||
const result = await detectNewPlatforms(makeGithub(WITH_SCHEMA), CONTEXT, prFiles, API_DATA);
|
|
||||||
assert.ok(result.labels.has('new-platform'));
|
|
||||||
assert.equal(result.hasYamlLoadable, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('new platform without CONFIG_SCHEMA sets new-platform but not hasYamlLoadable', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/my_sensor/cover.py', status: 'added' },
|
|
||||||
];
|
|
||||||
const result = await detectNewPlatforms(makeGithub(WITHOUT_SCHEMA), CONTEXT, prFiles, API_DATA);
|
|
||||||
assert.ok(result.labels.has('new-platform'));
|
|
||||||
assert.equal(result.hasYamlLoadable, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('non-platform file addition produces no labels', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/my_sensor/sensor.py', status: 'added' },
|
|
||||||
];
|
|
||||||
// Override platformComponents so 'sensor' is not a recognized platform -> no label expected.
|
|
||||||
const nonPlatformApiData = { ...API_DATA, platformComponents: ['cover'] };
|
|
||||||
const result = await detectNewPlatforms(makeGithub(WITH_SCHEMA), CONTEXT, prFiles, nonPlatformApiData);
|
|
||||||
assert.equal(result.labels.size, 0);
|
|
||||||
assert.equal(result.hasYamlLoadable, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// detectNewComponents
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
describe('detectNewComponents', () => {
|
|
||||||
it('new top-level __init__.py sets new-component', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/actuator/__init__.py', status: 'added', },
|
|
||||||
];
|
|
||||||
const result = await detectNewComponents(makeGithub(WITHOUT_SCHEMA), CONTEXT, prFiles);
|
|
||||||
assert.ok(result.labels.has('new-component'));
|
|
||||||
assert.equal(result.hasYamlLoadable, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('new top-level __init__.py with CONFIG_SCHEMA sets hasYamlLoadable', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/my_component/__init__.py', status: 'added' },
|
|
||||||
];
|
|
||||||
const result = await detectNewComponents(makeGithub(WITH_SCHEMA), CONTEXT, prFiles);
|
|
||||||
assert.ok(result.labels.has('new-component'));
|
|
||||||
assert.equal(result.hasYamlLoadable, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('new top-level __init__.py with IS_TARGET_PLATFORM sets new-target-platform', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/my_platform/__init__.py', status: 'added' },
|
|
||||||
];
|
|
||||||
const result = await detectNewComponents(makeGithub('IS_TARGET_PLATFORM = True'), CONTEXT, prFiles);
|
|
||||||
assert.ok(result.labels.has('new-component'));
|
|
||||||
assert.ok(result.labels.has('new-target-platform'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('modified __init__.py does not set new-component', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/existing/__init__.py', status: 'modified' },
|
|
||||||
];
|
|
||||||
const result = await detectNewComponents(makeGithub(WITH_SCHEMA), CONTEXT, prFiles);
|
|
||||||
assert.equal(result.labels.size, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('nested __init__.py does not set new-component', async () => {
|
|
||||||
const prFiles = [
|
|
||||||
{ filename: 'esphome/components/endstop/cover/__init__.py', status: 'added' },
|
|
||||||
];
|
|
||||||
const result = await detectNewComponents(makeGithub(WITH_SCHEMA), CONTEXT, prFiles);
|
|
||||||
assert.equal(result.labels.size, 0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -6,10 +6,9 @@ on:
|
|||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [labeled, opened, reopened, synchronize, edited]
|
types: [labeled, opened, reopened, synchronize, edited]
|
||||||
|
|
||||||
# All PR/label/review writes are performed with the App token minted below,
|
|
||||||
# so the workflow's GITHUB_TOKEN only needs read access for checkout.
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout reads the workflow source
|
pull-requests: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
SMALL_PR_THRESHOLD: 30
|
SMALL_PR_THRESHOLD: 30
|
||||||
@@ -28,14 +27,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v2
|
||||||
with:
|
with:
|
||||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||||
# Scope the minted App token to the minimum needed by auto-label-pr/*.js.
|
|
||||||
permission-contents: read # repos.getContent for CODEOWNERS and file lookups in detectors.js
|
|
||||||
permission-issues: write # listLabelsOnIssue, addLabels, removeLabel, list/createComment
|
|
||||||
permission-pull-requests: write # pulls.listFiles, list/create/update/dismissReview
|
|
||||||
|
|
||||||
- name: Auto Label PR
|
- name: Auto Label PR
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ on:
|
|||||||
- ".github/workflows/ci-api-proto.yml"
|
- ".github/workflows/ci-api-proto.yml"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout for the PR head
|
contents: read
|
||||||
pull-requests: write # pulls.createReview / listReviews / dismissReview when generated proto files are stale
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
@@ -26,16 +26,6 @@ jobs:
|
|||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
- 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@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
# Pin uv version so the action does not have to fetch the
|
|
||||||
# manifest from raw.githubusercontent.com on every cache
|
|
||||||
# miss; that fetch flakes on Windows runners.
|
|
||||||
version: "0.11.15"
|
|
||||||
|
|
||||||
- name: Install apt dependencies
|
- name: Install apt dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -44,7 +34,7 @@ jobs:
|
|||||||
sudo apt install -y protobuf-compiler
|
sudo apt install -y protobuf-compiler
|
||||||
protoc --version
|
protoc --version
|
||||||
- name: Install python dependencies
|
- name: Install python dependencies
|
||||||
run: uv pip install --system aioesphomeapi -c requirements.txt -r requirements_dev.txt
|
run: pip install aioesphomeapi -c requirements.txt -r requirements_dev.txt
|
||||||
- name: Generate files
|
- name: Generate files
|
||||||
run: script/api_protobuf/api_protobuf.py
|
run: script/api_protobuf/api_protobuf.py
|
||||||
- name: Check for changes
|
- name: Check for changes
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ on:
|
|||||||
- ".github/workflows/ci-clang-tidy-hash.yml"
|
- ".github/workflows/ci-clang-tidy-hash.yml"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout for the PR head
|
contents: read
|
||||||
pull-requests: write # pulls.createReview / listReviews / dismissReview when the clang-tidy hash is out of date
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verify-hash:
|
verify-hash:
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ on:
|
|||||||
- "script/platformio_install_deps.py"
|
- "script/platformio_install_deps.py"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout only; the build does not push images
|
contents: read
|
||||||
|
packages: read
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
@@ -48,7 +49,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
|
|
||||||
- name: Set TAG
|
- name: Set TAG
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
name: CI - GitHub Scripts
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [dev, beta, release]
|
|
||||||
paths:
|
|
||||||
- ".github/scripts/**"
|
|
||||||
- ".github/workflows/ci-github-scripts.yml"
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- ".github/scripts/**"
|
|
||||||
- ".github/workflows/ci-github-scripts.yml"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-auto-label-pr:
|
|
||||||
name: Test auto-label-pr scripts
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code from GitHub
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
working-directory: .github/scripts/auto-label-pr
|
|
||||||
run: npm test
|
|
||||||
@@ -7,9 +7,9 @@ on:
|
|||||||
types: [completed]
|
types: [completed]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout of the base repo at the PR's target branch
|
contents: read
|
||||||
pull-requests: write # gh api to look up the PR by head SHA and post/update the memory-impact comment
|
pull-requests: write
|
||||||
actions: read # gh run download for the memory-analysis artifacts produced by the CI workflow run
|
actions: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
memory-impact-comment:
|
memory-impact-comment:
|
||||||
|
|||||||
+46
-274
@@ -6,10 +6,18 @@ on:
|
|||||||
branches: [dev, beta, release]
|
branches: [dev, beta, release]
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "**"
|
||||||
|
- "!.github/workflows/*.yml"
|
||||||
|
- "!.github/actions/build-image/*"
|
||||||
|
- ".github/workflows/ci.yml"
|
||||||
|
- "!.yamllint"
|
||||||
|
- "!.github/dependabot.yml"
|
||||||
|
- "!docker/**"
|
||||||
merge_group:
|
merge_group:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout for all jobs; individual jobs add their own scopes when they need to write
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DEFAULT_PYTHON: "3.11"
|
DEFAULT_PYTHON: "3.11"
|
||||||
@@ -44,26 +52,14 @@ jobs:
|
|||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ steps.cache-key.outputs.key }}
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ steps.cache-key.outputs.key }}
|
||||||
- name: Set up uv
|
|
||||||
# Only needed on cache miss to populate the venv. ``uv pip install``
|
|
||||||
# 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@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
# Pin uv version so the action does not have to fetch the
|
|
||||||
# manifest from raw.githubusercontent.com on every cache
|
|
||||||
# miss; that fetch flakes on Windows runners.
|
|
||||||
version: "0.11.15"
|
|
||||||
- name: Create Python virtual environment
|
- name: Create Python virtual environment
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
uv pip install -r requirements.txt -r requirements_dev.txt -r requirements_test.txt pre-commit
|
pip install -r requirements.txt -r requirements_dev.txt -r requirements_test.txt pre-commit
|
||||||
uv pip install -e .
|
pip install -e .
|
||||||
|
|
||||||
pylint:
|
pylint:
|
||||||
name: Check pylint
|
name: Check pylint
|
||||||
@@ -93,8 +89,6 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
- determine-jobs
|
|
||||||
if: needs.determine-jobs.outputs.core-ci == 'true'
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -142,57 +136,6 @@ jobs:
|
|||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
|
|
||||||
device-builder:
|
|
||||||
name: Test downstream esphome/device-builder
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- common
|
|
||||||
- determine-jobs
|
|
||||||
if: needs.determine-jobs.outputs.device-builder == 'true'
|
|
||||||
steps:
|
|
||||||
- name: Check out esphome (this PR)
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
path: esphome
|
|
||||||
- name: Check out esphome/device-builder
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
repository: esphome/device-builder
|
|
||||||
ref: main
|
|
||||||
path: device-builder
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
||||||
with:
|
|
||||||
python-version: "3.13"
|
|
||||||
- name: Set up uv
|
|
||||||
# Mirrors the install shape device-builder's own CI uses
|
|
||||||
# (esphome/device-builder#192): uv replaces pip for the
|
|
||||||
# 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@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
# Pin uv version so the action does not have to fetch the
|
|
||||||
# manifest from raw.githubusercontent.com on every cache
|
|
||||||
# miss; that fetch flakes on Windows runners.
|
|
||||||
version: "0.11.15"
|
|
||||||
- name: Install device-builder + esphome from PR
|
|
||||||
# Install device-builder with its esphome + test extras
|
|
||||||
# first so its pinned versions of pytest/etc. land, then
|
|
||||||
# overlay the PR's esphome so the downstream tests run
|
|
||||||
# against this PR's Python code. ``--system`` installs into
|
|
||||||
# the runner's Python instead of a venv.
|
|
||||||
run: |
|
|
||||||
uv pip install --system -e './device-builder[esphome,test]'
|
|
||||||
uv pip install --system -e ./esphome
|
|
||||||
- name: Run device-builder pytest
|
|
||||||
# ``-n auto`` runs under pytest-xdist (matches device-builder's
|
|
||||||
# own CI). No ``--cov`` here -- this is purely a downstream
|
|
||||||
# smoke check against this PR's esphome code.
|
|
||||||
working-directory: device-builder
|
|
||||||
run: pytest -q -n auto --maxfail=5 --durations=30 --no-cov --ignore=tests/benchmarks
|
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
name: Run pytest
|
name: Run pytest
|
||||||
strategy:
|
strategy:
|
||||||
@@ -217,8 +160,6 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
- determine-jobs
|
|
||||||
if: needs.determine-jobs.outputs.core-ci == 'true'
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -234,14 +175,14 @@ jobs:
|
|||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
. ./venv/Scripts/activate.ps1
|
. ./venv/Scripts/activate.ps1
|
||||||
pytest -vv --cov-report=xml --tb=native --durations=30 -n auto tests --ignore=tests/integration/
|
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||||
- name: Run pytest
|
- name: Run pytest
|
||||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
|
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pytest -vv --cov-report=xml --tb=native --durations=30 -n auto tests --ignore=tests/integration/
|
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Save Python virtual environment cache
|
- name: Save Python virtual environment cache
|
||||||
@@ -257,17 +198,13 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
outputs:
|
outputs:
|
||||||
core-ci: ${{ steps.determine.outputs.core-ci }}
|
|
||||||
integration-tests: ${{ steps.determine.outputs.integration-tests }}
|
integration-tests: ${{ steps.determine.outputs.integration-tests }}
|
||||||
integration-test-buckets: ${{ steps.determine.outputs.integration-test-buckets }}
|
integration-tests-run-all: ${{ steps.determine.outputs.integration-tests-run-all }}
|
||||||
|
integration-test-files: ${{ steps.determine.outputs.integration-test-files }}
|
||||||
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
||||||
clang-tidy-mode: ${{ steps.determine.outputs.clang-tidy-mode }}
|
clang-tidy-mode: ${{ steps.determine.outputs.clang-tidy-mode }}
|
||||||
clang-tidy-full-scan: ${{ steps.determine.outputs.clang-tidy-full-scan }}
|
|
||||||
python-linters: ${{ steps.determine.outputs.python-linters }}
|
python-linters: ${{ steps.determine.outputs.python-linters }}
|
||||||
import-time: ${{ steps.determine.outputs.import-time }}
|
import-time: ${{ steps.determine.outputs.import-time }}
|
||||||
device-builder: ${{ steps.determine.outputs.device-builder }}
|
|
||||||
native-idf: ${{ steps.determine.outputs.native-idf }}
|
|
||||||
native-idf-components: ${{ steps.determine.outputs.native-idf-components }}
|
|
||||||
changed-components: ${{ steps.determine.outputs.changed-components }}
|
changed-components: ${{ steps.determine.outputs.changed-components }}
|
||||||
changed-components-with-tests: ${{ steps.determine.outputs.changed-components-with-tests }}
|
changed-components-with-tests: ${{ steps.determine.outputs.changed-components-with-tests }}
|
||||||
directly-changed-components-with-tests: ${{ steps.determine.outputs.directly-changed-components-with-tests }}
|
directly-changed-components-with-tests: ${{ steps.determine.outputs.directly-changed-components-with-tests }}
|
||||||
@@ -277,7 +214,6 @@ jobs:
|
|||||||
cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }}
|
cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }}
|
||||||
cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }}
|
cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }}
|
||||||
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
|
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
|
||||||
validate-only-components: ${{ steps.determine.outputs.validate-only-components }}
|
|
||||||
benchmarks: ${{ steps.determine.outputs.benchmarks }}
|
benchmarks: ${{ steps.determine.outputs.benchmarks }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
@@ -301,27 +237,18 @@ jobs:
|
|||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
EXTRA_ARGS=""
|
output=$(python script/determine-jobs.py)
|
||||||
if [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-run-all') }}" == "true" ]]; then
|
|
||||||
EXTRA_ARGS="--force-all"
|
|
||||||
echo "::notice::ci-run-all label detected -- forcing every CI job to run"
|
|
||||||
fi
|
|
||||||
output=$(python script/determine-jobs.py $EXTRA_ARGS)
|
|
||||||
echo "Test determination output:"
|
echo "Test determination output:"
|
||||||
echo "$output" | jq
|
echo "$output" | jq
|
||||||
|
|
||||||
# Extract individual fields
|
# Extract individual fields
|
||||||
echo "core-ci=$(echo "$output" | jq -r '.core_ci')" >> $GITHUB_OUTPUT
|
|
||||||
echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT
|
echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT
|
||||||
echo "integration-test-buckets=$(echo "$output" | jq -c '.integration_test_buckets')" >> $GITHUB_OUTPUT
|
echo "integration-tests-run-all=$(echo "$output" | jq -r '.integration_tests_run_all')" >> $GITHUB_OUTPUT
|
||||||
|
echo "integration-test-files=$(echo "$output" | jq -c '.integration_test_files')" >> $GITHUB_OUTPUT
|
||||||
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
||||||
echo "clang-tidy-mode=$(echo "$output" | jq -r '.clang_tidy_mode')" >> $GITHUB_OUTPUT
|
echo "clang-tidy-mode=$(echo "$output" | jq -r '.clang_tidy_mode')" >> $GITHUB_OUTPUT
|
||||||
echo "clang-tidy-full-scan=$(echo "$output" | jq -r '.clang_tidy_full_scan')" >> $GITHUB_OUTPUT
|
|
||||||
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
||||||
echo "import-time=$(echo "$output" | jq -r '.import_time')" >> $GITHUB_OUTPUT
|
echo "import-time=$(echo "$output" | jq -r '.import_time')" >> $GITHUB_OUTPUT
|
||||||
echo "device-builder=$(echo "$output" | jq -r '.device_builder')" >> $GITHUB_OUTPUT
|
|
||||||
echo "native-idf=$(echo "$output" | jq -r '.native_idf')" >> $GITHUB_OUTPUT
|
|
||||||
echo "native-idf-components=$(echo "$output" | jq -r '.native_idf_components')" >> $GITHUB_OUTPUT
|
|
||||||
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
||||||
echo "changed-components-with-tests=$(echo "$output" | jq -c '.changed_components_with_tests')" >> $GITHUB_OUTPUT
|
echo "changed-components-with-tests=$(echo "$output" | jq -c '.changed_components_with_tests')" >> $GITHUB_OUTPUT
|
||||||
echo "directly-changed-components-with-tests=$(echo "$output" | jq -c '.directly_changed_components_with_tests')" >> $GITHUB_OUTPUT
|
echo "directly-changed-components-with-tests=$(echo "$output" | jq -c '.directly_changed_components_with_tests')" >> $GITHUB_OUTPUT
|
||||||
@@ -331,7 +258,6 @@ jobs:
|
|||||||
echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT
|
echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT
|
||||||
echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT
|
echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT
|
||||||
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
|
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
|
||||||
echo "validate-only-components=$(echo "$output" | jq -c '.validate_only_components')" >> $GITHUB_OUTPUT
|
|
||||||
echo "benchmarks=$(echo "$output" | jq -r '.benchmarks')" >> $GITHUB_OUTPUT
|
echo "benchmarks=$(echo "$output" | jq -r '.benchmarks')" >> $GITHUB_OUTPUT
|
||||||
- name: Save components graph cache
|
- name: Save components graph cache
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
@@ -341,16 +267,12 @@ jobs:
|
|||||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||||
|
|
||||||
integration-tests:
|
integration-tests:
|
||||||
name: Run integration tests (${{ matrix.bucket.name }})
|
name: Run integration tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
- determine-jobs
|
- determine-jobs
|
||||||
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
bucket: ${{ fromJson(needs.determine-jobs.outputs.integration-test-buckets) }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -365,36 +287,31 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
- 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@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
# Pin uv version so the action does not have to fetch the
|
|
||||||
# manifest from raw.githubusercontent.com on every cache
|
|
||||||
# miss; that fetch flakes on Windows runners.
|
|
||||||
version: "0.11.15"
|
|
||||||
- name: Create Python virtual environment
|
- name: Create Python virtual environment
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
uv pip install -r requirements.txt -r requirements_test.txt
|
pip install -r requirements.txt -r requirements_test.txt
|
||||||
uv pip install -e .
|
pip install -e .
|
||||||
- name: Register matcher
|
- name: Register matcher
|
||||||
run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
|
run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
|
||||||
- name: Run integration tests
|
- name: Run integration tests
|
||||||
env:
|
env:
|
||||||
# JSON array of test paths; parsed into a bash array below to avoid
|
INTEGRATION_TEST_FILES: ${{ needs.determine-jobs.outputs.integration-test-files }}
|
||||||
# shell word-splitting / glob hazards.
|
INTEGRATION_TESTS_RUN_ALL: ${{ needs.determine-jobs.outputs.integration-tests-run-all }}
|
||||||
BUCKET_TESTS: ${{ toJson(matrix.bucket.tests) }}
|
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
mapfile -t test_files < <(echo "$BUCKET_TESTS" | jq -r '.[]')
|
if [[ "$INTEGRATION_TESTS_RUN_ALL" == "true" ]]; then
|
||||||
echo "Bucket ${{ matrix.bucket.name }}: running ${#test_files[@]} integration tests"
|
echo "Running all integration tests"
|
||||||
pytest -vv --no-cov --tb=native --durations=30 -n auto "${test_files[@]}"
|
pytest -vv --no-cov --tb=native -n auto tests/integration/
|
||||||
|
else
|
||||||
|
# Parse JSON array into bash array to avoid shell expansion issues
|
||||||
|
mapfile -t test_files < <(echo "$INTEGRATION_TEST_FILES" | jq -r '.[]')
|
||||||
|
echo "Running ${#test_files[@]} specific integration tests"
|
||||||
|
pytest -vv --no-cov --tb=native -n auto "${test_files[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
cpp-unit-tests:
|
cpp-unit-tests:
|
||||||
name: Run C++ unit tests
|
name: Run C++ unit tests
|
||||||
@@ -452,12 +369,9 @@ jobs:
|
|||||||
echo "binary=$BINARY" >> $GITHUB_OUTPUT
|
echo "binary=$BINARY" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Run CodSpeed benchmarks
|
- name: Run CodSpeed benchmarks
|
||||||
uses: CodSpeedHQ/action@9d332c4d90b43981c3e55ae8e38e68709996240f # v4.17.0
|
uses: CodSpeedHQ/action@c381be0bfd20e844fb45594f6aa182ffcd94545c # v4.15.0
|
||||||
with:
|
with:
|
||||||
run: |
|
run: ${{ steps.build.outputs.binary }}
|
||||||
. venv/bin/activate
|
|
||||||
${{ steps.build.outputs.binary }}
|
|
||||||
pytest tests/benchmarks/python/ --codspeed --no-cov
|
|
||||||
mode: simulation
|
mode: simulation
|
||||||
|
|
||||||
clang-tidy-single:
|
clang-tidy-single:
|
||||||
@@ -531,13 +445,7 @@ jobs:
|
|||||||
id: check_full_scan
|
id: check_full_scan
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
# determine-jobs.clang-tidy-full-scan is true when core C++ changed
|
if python script/clang_tidy_hash.py --check; then
|
||||||
# OR the ci-run-all label forced --force-all. Independent of the
|
|
||||||
# hash check, both must produce a full scan in the job itself.
|
|
||||||
if [ "${{ needs.determine-jobs.outputs.clang-tidy-full-scan }}" = "true" ]; then
|
|
||||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "reason=determine_jobs" >> $GITHUB_OUTPUT
|
|
||||||
elif python script/clang_tidy_hash.py --check; then
|
|
||||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||||
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
@@ -549,7 +457,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
||||||
echo "Running FULL clang-tidy scan (reason: ${{ steps.check_full_scan.outputs.reason }})"
|
echo "Running FULL clang-tidy scan (hash changed)"
|
||||||
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
||||||
else
|
else
|
||||||
echo "Running clang-tidy on changed files only"
|
echo "Running clang-tidy on changed files only"
|
||||||
@@ -609,13 +517,7 @@ jobs:
|
|||||||
id: check_full_scan
|
id: check_full_scan
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
# determine-jobs.clang-tidy-full-scan is true when core C++ changed
|
if python script/clang_tidy_hash.py --check; then
|
||||||
# OR the ci-run-all label forced --force-all. Independent of the
|
|
||||||
# hash check, both must produce a full scan in the job itself.
|
|
||||||
if [ "${{ needs.determine-jobs.outputs.clang-tidy-full-scan }}" = "true" ]; then
|
|
||||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "reason=determine_jobs" >> $GITHUB_OUTPUT
|
|
||||||
elif python script/clang_tidy_hash.py --check; then
|
|
||||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||||
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
@@ -627,7 +529,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
||||||
echo "Running FULL clang-tidy scan (reason: ${{ steps.check_full_scan.outputs.reason }})"
|
echo "Running FULL clang-tidy scan (hash changed)"
|
||||||
script/clang-tidy --all-headers --fix --environment esp32-arduino-tidy
|
script/clang-tidy --all-headers --fix --environment esp32-arduino-tidy
|
||||||
else
|
else
|
||||||
echo "Running clang-tidy on changed files only"
|
echo "Running clang-tidy on changed files only"
|
||||||
@@ -704,13 +606,7 @@ jobs:
|
|||||||
id: check_full_scan
|
id: check_full_scan
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
# determine-jobs.clang-tidy-full-scan is true when core C++ changed
|
if python script/clang_tidy_hash.py --check; then
|
||||||
# OR the ci-run-all label forced --force-all. Independent of the
|
|
||||||
# hash check, both must produce a full scan in the job itself.
|
|
||||||
if [ "${{ needs.determine-jobs.outputs.clang-tidy-full-scan }}" = "true" ]; then
|
|
||||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "reason=determine_jobs" >> $GITHUB_OUTPUT
|
|
||||||
elif python script/clang_tidy_hash.py --check; then
|
|
||||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||||
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
@@ -722,7 +618,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
||||||
echo "Running FULL clang-tidy scan (reason: ${{ steps.check_full_scan.outputs.reason }})"
|
echo "Running FULL clang-tidy scan (hash changed)"
|
||||||
script/clang-tidy --all-headers --fix ${{ matrix.options }}
|
script/clang-tidy --all-headers --fix ${{ matrix.options }}
|
||||||
else
|
else
|
||||||
echo "Running clang-tidy on changed files only"
|
echo "Running clang-tidy on changed files only"
|
||||||
@@ -833,142 +729,20 @@ jobs:
|
|||||||
echo "Config validation passed! Starting compilation..."
|
echo "Config validation passed! Starting compilation..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Compute the compile-stage component list. Components whose only
|
|
||||||
# changes are validate.*.yaml files are config-only -- their source
|
|
||||||
# and test fixtures didn't move, so rebuilding firmware adds no
|
|
||||||
# signal. Subtract them from this batch before invoking compile.
|
|
||||||
validate_only_json='${{ needs.determine-jobs.outputs.validate-only-components }}'
|
|
||||||
if [ -z "$validate_only_json" ]; then
|
|
||||||
validate_only_json='[]'
|
|
||||||
fi
|
|
||||||
if ! validate_only_csv=$(echo "$validate_only_json" | jq -r 'join(",")'); then
|
|
||||||
echo "::error::Failed to render validate-only-components as CSV from: $validate_only_json"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z "$validate_only_csv" ]; then
|
|
||||||
compile_csv="$components_csv"
|
|
||||||
else
|
|
||||||
components_sorted=$(echo "$components_csv" | tr ',' '\n' | sort -u)
|
|
||||||
validate_sorted=$(echo "$validate_only_csv" | tr ',' '\n' | sort -u)
|
|
||||||
if ! diff_out=$(comm -23 <(echo "$components_sorted") <(echo "$validate_sorted")); then
|
|
||||||
echo "::error::Failed to compute compile component subset."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
compile_csv=$(echo "$diff_out" | paste -sd ',' -)
|
|
||||||
skipped=$(comm -12 <(echo "$components_sorted") <(echo "$validate_sorted") | paste -sd ',' -)
|
|
||||||
if [ -n "$skipped" ]; then
|
|
||||||
echo "Validate-only components in this batch (skipping compile): $skipped"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Show disk space before compilation
|
# Show disk space before compilation
|
||||||
echo "Disk space before compilation:"
|
echo "Disk space before compilation:"
|
||||||
df -h
|
df -h
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [ -n "$compile_csv" ]; then
|
# Run compilation with grouping and isolation
|
||||||
# Run compilation with grouping and isolation
|
python3 script/test_build_components.py -e compile -c "$components_csv" -f --isolate "$directly_changed_csv"
|
||||||
python3 script/test_build_components.py -e compile -c "$compile_csv" -f --isolate "$directly_changed_csv"
|
|
||||||
else
|
|
||||||
echo "All components in this batch are validate-only -- skipping compile stage."
|
|
||||||
fi
|
|
||||||
|
|
||||||
test-native-idf:
|
|
||||||
name: Test components with native ESP-IDF
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- common
|
|
||||||
- determine-jobs
|
|
||||||
if: github.event_name == 'pull_request' && needs.determine-jobs.outputs.native-idf == 'true'
|
|
||||||
env:
|
|
||||||
ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf
|
|
||||||
# Comma-joined subset of the native-IDF representative component list,
|
|
||||||
# computed by script/determine-jobs.py (native_idf_components_to_test).
|
|
||||||
# Single source of truth -- the full list lives in
|
|
||||||
# script/determine-jobs.py::NATIVE_IDF_TEST_COMPONENTS.
|
|
||||||
TEST_COMPONENTS: ${{ needs.determine-jobs.outputs.native-idf-components }}
|
|
||||||
steps:
|
|
||||||
- name: Check out code from GitHub
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
|
|
||||||
- name: Restore Python
|
|
||||||
uses: ./.github/actions/restore-python
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
|
||||||
|
|
||||||
- name: Cache ESPHome
|
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ~/.esphome-idf
|
|
||||||
key: ${{ runner.os }}-esphome-${{ needs.common.outputs.cache-key }}
|
|
||||||
|
|
||||||
- name: Run native ESP-IDF compile test
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
|
|
||||||
# Check if /mnt has more free space than / before bind mounting
|
|
||||||
# Extract available space in KB for comparison
|
|
||||||
root_avail=$(df -k / | awk 'NR==2 {print $4}')
|
|
||||||
mnt_avail=$(df -k /mnt 2>/dev/null | awk 'NR==2 {print $4}')
|
|
||||||
|
|
||||||
echo "Available space: / has ${root_avail}KB, /mnt has ${mnt_avail}KB"
|
|
||||||
|
|
||||||
# Only use /mnt if it has more space than /
|
|
||||||
if [ -n "$mnt_avail" ] && [ "$mnt_avail" -gt "$root_avail" ]; then
|
|
||||||
echo "Using /mnt for build files (more space available)"
|
|
||||||
# Bind mount PlatformIO directory to /mnt (tools, packages, build cache all go there)
|
|
||||||
sudo mkdir -p /mnt/esphome-idf
|
|
||||||
sudo chown $USER:$USER /mnt/esphome-idf
|
|
||||||
mkdir -p ~/.esphome-idf
|
|
||||||
sudo mount --bind /mnt/esphome-idf ~/.esphome-idf
|
|
||||||
|
|
||||||
# Bind mount test build directory to /mnt
|
|
||||||
sudo mkdir -p /mnt/test_build_components_build
|
|
||||||
sudo chown $USER:$USER /mnt/test_build_components_build
|
|
||||||
mkdir -p tests/test_build_components/build
|
|
||||||
sudo mount --bind /mnt/test_build_components_build tests/test_build_components/build
|
|
||||||
else
|
|
||||||
echo "Using / for build files (more space available than /mnt or /mnt unavailable)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Testing components: $TEST_COMPONENTS"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Show disk space before validation (after bind mounts setup)
|
|
||||||
echo "Disk space before config validation:"
|
|
||||||
df -h
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Run config validation (auto-grouped by test_build_components.py)
|
|
||||||
python3 script/test_build_components.py -e config -t esp32-idf -c "$TEST_COMPONENTS" -f --toolchain esp-idf
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Config validation passed! Starting compilation..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Show disk space before compilation
|
|
||||||
echo "Disk space before compilation:"
|
|
||||||
df -h
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Run compilation (auto-grouped by test_build_components.py)
|
|
||||||
python3 script/test_build_components.py -e compile -t esp32-idf -c "$TEST_COMPONENTS" -f --toolchain esp-idf
|
|
||||||
|
|
||||||
- name: Save ESPHome cache
|
|
||||||
if: github.ref == 'refs/heads/dev'
|
|
||||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ~/.esphome-idf
|
|
||||||
key: ${{ runner.os }}-esphome-${{ needs.common.outputs.cache-key }}
|
|
||||||
|
|
||||||
pre-commit-ci-lite:
|
pre-commit-ci-lite:
|
||||||
name: pre-commit.ci lite
|
name: pre-commit.ci lite
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
- determine-jobs
|
if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release')
|
||||||
if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release') && needs.determine-jobs.outputs.core-ci == 'true'
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -1242,8 +1016,8 @@ jobs:
|
|||||||
- memory-impact-pr-branch
|
- memory-impact-pr-branch
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && fromJSON(needs.determine-jobs.outputs.memory_impact).should_run == 'true' && needs.memory-impact-target-branch.outputs.skip != 'true'
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && fromJSON(needs.determine-jobs.outputs.memory_impact).should_run == 'true' && needs.memory-impact-target-branch.outputs.skip != 'true'
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout to load the comment-posting script
|
contents: read
|
||||||
pull-requests: write # ci_memory_impact_comment.py posts/updates the memory-impact comment on the PR
|
pull-requests: write
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
steps:
|
steps:
|
||||||
@@ -1292,9 +1066,7 @@ jobs:
|
|||||||
- clang-tidy-nosplit
|
- clang-tidy-nosplit
|
||||||
- clang-tidy-split
|
- clang-tidy-split
|
||||||
- determine-jobs
|
- determine-jobs
|
||||||
- device-builder
|
|
||||||
- test-build-components-split
|
- test-build-components-split
|
||||||
- test-native-idf
|
|
||||||
- pre-commit-ci-lite
|
- pre-commit-ci-lite
|
||||||
- memory-impact-target-branch
|
- memory-impact-target-branch
|
||||||
- memory-impact-pr-branch
|
- memory-impact-pr-branch
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ on:
|
|||||||
types: [opened, reopened]
|
types: [opened, reopened]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write # pulls.update to close the PR opened from a fork's default branch
|
pull-requests: write
|
||||||
issues: write # issues.createComment to explain to the contributor why the PR was closed
|
issues: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
close:
|
close:
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ on:
|
|||||||
- beta
|
- beta
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
issues: write # issues.addLabels / removeLabel to manage the 'code-owner-approved' label on the PR
|
issues: write
|
||||||
pull-requests: read # listReviews to determine whether a codeowner has approved
|
pull-requests: read
|
||||||
contents: read # actions/checkout to read CODEOWNERS and the shared codeowners.js helper
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
codeowner-approved:
|
codeowner-approved:
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ on:
|
|||||||
- release
|
- release
|
||||||
- beta
|
- beta
|
||||||
|
|
||||||
# PR/review writes (requestReviewers, issues.createComment) are performed with the App token minted below,
|
|
||||||
# so the workflow's GITHUB_TOKEN only needs read access for checkout.
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout to read CODEOWNERS and the shared codeowners.js helper
|
pull-requests: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
request-codeowner-reviews:
|
request-codeowner-reviews:
|
||||||
@@ -33,20 +32,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.base.sha }}
|
ref: ${{ github.event.pull_request.base.sha }}
|
||||||
|
|
||||||
- name: Generate a token
|
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
||||||
with:
|
|
||||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
|
||||||
# Scope the minted App token to the minimum needed by the github-script step below.
|
|
||||||
permission-pull-requests: write # pulls.listFiles, pulls.get, pulls.listReviews, pulls.requestReviewers
|
|
||||||
permission-issues: write # issues.listComments and issues.createComment (PR comments use the issues API)
|
|
||||||
|
|
||||||
- name: Request reviews from component codeowners
|
- name: Request reviews from component codeowners
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
|
||||||
script: |
|
script: |
|
||||||
const { loadCodeowners, getEffectiveOwners } = require('./.github/scripts/codeowners.js');
|
const { loadCodeowners, getEffectiveOwners } = require('./.github/scripts/codeowners.js');
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: "30 18 * * 4"
|
- cron: "30 18 * * 4"
|
||||||
|
|
||||||
# Deny by default; the analyze job opts in to exactly what it needs.
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
name: Analyze (${{ matrix.language }})
|
name: Analyze (${{ matrix.language }})
|
||||||
@@ -29,10 +26,15 @@ jobs:
|
|||||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||||
permissions:
|
permissions:
|
||||||
security-events: write # upload CodeQL SARIF results to the Code Scanning API
|
# required for all workflows
|
||||||
packages: read # fetch internal or private CodeQL query packs
|
security-events: write
|
||||||
actions: read # required by codeql-action when run from a private repo
|
|
||||||
contents: read # actions/checkout to scan the repository
|
# required to fetch internal or private CodeQL packs
|
||||||
|
packages: read
|
||||||
|
|
||||||
|
# only required for workflows in private repositories
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -56,7 +58,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
build-mode: ${{ matrix.build-mode }}
|
build-mode: ${{ matrix.build-mode }}
|
||||||
@@ -84,6 +86,6 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
name: Add Dashboard Deprecation Comment
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, synchronize]
|
|
||||||
|
|
||||||
# All API calls (pulls.listFiles + issues.{list,create,update}Comment) are performed with
|
|
||||||
# the App token minted below, so the workflow's GITHUB_TOKEN does not need any scopes.
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
dashboard-deprecation-comment:
|
|
||||||
name: Dashboard deprecation comment
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Release-bump PRs (bump-X.Y.Z -> beta, beta -> release) inevitably
|
|
||||||
# roll up everything merged into dev since the last cut, which can
|
|
||||||
# include dashboard changes that have already been reviewed once.
|
|
||||||
# The bot's purpose is to warn new contributors before they invest
|
|
||||||
# time -- that only applies to PRs entering dev.
|
|
||||||
if: github.event.pull_request.base.ref == 'dev'
|
|
||||||
steps:
|
|
||||||
- name: Generate a token
|
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
||||||
with:
|
|
||||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
|
||||||
# pulls.listFiles + issues.{list,create,update}Comment on PRs. For PR resources
|
|
||||||
# the issues.*Comment APIs require the pull-requests scope, not issues.
|
|
||||||
permission-pull-requests: write
|
|
||||||
|
|
||||||
- name: Add dashboard deprecation comment
|
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
||||||
with:
|
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
|
||||||
script: |
|
|
||||||
const commentMarker = "<!-- This comment was generated automatically by the dashboard-deprecation-comment workflow. -->";
|
|
||||||
|
|
||||||
const commentBody = `Thanks for opening this PR!
|
|
||||||
|
|
||||||
Heads up: the legacy ESPHome dashboard (\`esphome/dashboard/\` and \`tests/dashboard/\`) is **deprecated** and is being replaced by [ESPHome Device Builder](https://github.com/esphome/device-builder). We are not adding new features to the legacy dashboard and it will eventually be removed from this repository.
|
|
||||||
|
|
||||||
What this means for your PR:
|
|
||||||
|
|
||||||
- **New features / enhancements**: please port the change to [esphome/device-builder](https://github.com/esphome/device-builder) instead. We are unlikely to review or merge new dashboard features here.
|
|
||||||
- **Bug fixes**: small fixes may still be considered, but please check first whether the same issue exists in Device Builder, where the fix will have a longer life.
|
|
||||||
- **Security issues**: please do not file a public PR. Report privately via [GitHub security advisories](https://github.com/esphome/esphome/security/advisories/new) so we can coordinate a fix.
|
|
||||||
|
|
||||||
We appreciate the contribution and apologize for the friction; flagging this early so your time isn't spent on a change that may not land.
|
|
||||||
|
|
||||||
---
|
|
||||||
(Added by the PR bot)
|
|
||||||
|
|
||||||
${commentMarker}`;
|
|
||||||
|
|
||||||
async function getDashboardChanges(github, owner, repo, prNumber) {
|
|
||||||
const changedFiles = await github.paginate(
|
|
||||||
github.rest.pulls.listFiles,
|
|
||||||
{
|
|
||||||
owner: owner,
|
|
||||||
repo: repo,
|
|
||||||
pull_number: prNumber,
|
|
||||||
per_page: 100,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return changedFiles.filter(file =>
|
|
||||||
file.filename.startsWith('esphome/dashboard/') ||
|
|
||||||
file.filename.startsWith('tests/dashboard/')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findBotComment(github, owner, repo, prNumber) {
|
|
||||||
const comments = await github.paginate(
|
|
||||||
github.rest.issues.listComments,
|
|
||||||
{
|
|
||||||
owner: owner,
|
|
||||||
repo: repo,
|
|
||||||
issue_number: prNumber,
|
|
||||||
per_page: 100,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return comments.find(comment =>
|
|
||||||
comment.body.includes(commentMarker) && comment.user.type === "Bot"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const prNumber = context.payload.pull_request.number;
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
|
|
||||||
const dashboardChanges = await getDashboardChanges(github, owner, repo, prNumber);
|
|
||||||
const existingComment = await findBotComment(github, owner, repo, prNumber);
|
|
||||||
|
|
||||||
if (dashboardChanges.length === 0) {
|
|
||||||
// PR doesn't (or no longer) touches the legacy dashboard. If we previously
|
|
||||||
// commented (e.g. files were removed in a later push), leave the comment in
|
|
||||||
// place for history rather than thrash on edit/delete.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingComment) {
|
|
||||||
if (existingComment.body === commentBody) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await github.rest.issues.updateComment({
|
|
||||||
owner: owner,
|
|
||||||
repo: repo,
|
|
||||||
comment_id: existingComment.id,
|
|
||||||
body: commentBody,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: owner,
|
|
||||||
repo: repo,
|
|
||||||
issue_number: prNumber,
|
|
||||||
body: commentBody,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -4,29 +4,20 @@ on:
|
|||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
|
|
||||||
# All API calls (pulls.listFiles + issues.{list,create,update}Comment) are performed with
|
permissions:
|
||||||
# the App token minted below, so the workflow's GITHUB_TOKEN does not need any scopes.
|
contents: read # Needed to fetch PR details
|
||||||
permissions: {}
|
issues: write # Needed to create and update comments (PR comments are managed via the issues REST API)
|
||||||
|
pull-requests: write # also needed?
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
external-comment:
|
external-comment:
|
||||||
name: External component comment
|
name: External component comment
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
||||||
with:
|
|
||||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
|
||||||
# pulls.listFiles + issues.{list,create,update}Comment on PRs. For PR resources
|
|
||||||
# the issues.*Comment APIs require the pull-requests scope, not issues.
|
|
||||||
permission-pull-requests: write
|
|
||||||
|
|
||||||
- name: Add external component comment
|
- name: Add external component comment
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
// Generate external component usage instructions
|
// Generate external component usage instructions
|
||||||
function generateExternalComponentInstructions(prNumber, componentNames, owner, repo) {
|
function generateExternalComponentInstructions(prNumber, componentNames, owner, repo) {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ on:
|
|||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
issues: write # issues.createComment to mention component codeowners on the newly labelled issue
|
issues: write
|
||||||
contents: read # repos.getContent to fetch CODEOWNERS from the default branch
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
notify-codeowners:
|
notify-codeowners:
|
||||||
|
|||||||
@@ -6,12 +6,6 @@ on:
|
|||||||
- cron: "30 0 * * *" # Run daily at 00:30 UTC
|
- cron: "30 0 * * *" # Run daily at 00:30 UTC
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# Deny by default; the lock job opts in to exactly what the reusable workflow needs.
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lock:
|
lock:
|
||||||
permissions:
|
uses: esphome/workflows/.github/workflows/lock.yml@3c4e8446aa1029f1c346a482034b3ee1489077ca # 2026.4.0
|
||||||
issues: write # issues.lock on closed issues
|
|
||||||
pull-requests: write # issues.lock on closed pull requests
|
|
||||||
uses: esphome/workflows/.github/workflows/lock.yml@025a1e6255610c498ed590403b7e510b69e474df # 2026.4.1
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ on:
|
|||||||
- beta
|
- beta
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout to load detect-tags.js
|
contents: read
|
||||||
pull-requests: read # pulls.listFiles to map changed files to component/core/dashboard/ci tags
|
pull-requests: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
@@ -29,11 +29,10 @@ jobs:
|
|||||||
} = require('./.github/scripts/detect-tags.js');
|
} = require('./.github/scripts/detect-tags.js');
|
||||||
|
|
||||||
const title = context.payload.pull_request.title;
|
const title = context.payload.pull_request.title;
|
||||||
const user = context.payload.pull_request.user;
|
const author = context.payload.pull_request.user.login;
|
||||||
|
|
||||||
// Skip bot PRs (e.g. dependabot, esphome[bot] device-class sync) -
|
// Skip bot PRs (e.g. dependabot) - they have their own title format
|
||||||
// they have their own title formats.
|
if (author === 'dependabot[bot]') {
|
||||||
if (user.type === 'Bot') {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,15 +68,14 @@ jobs:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for MDX syntax characters not wrapped in backticks.
|
// Check for angle brackets not wrapped in backticks.
|
||||||
// Astro docs MDX treats bare `<` as JSX component opening tags and
|
// Astro docs MDX treats bare < as JSX component opening tags.
|
||||||
// bare `{` as JS expressions, so both must be escaped in changelog entries.
|
|
||||||
const stripped = title.replace(/`[^`]*`/g, '');
|
const stripped = title.replace(/`[^`]*`/g, '');
|
||||||
if (/[<>{}]/.test(stripped)) {
|
if (/[<>]/.test(stripped)) {
|
||||||
core.setFailed(
|
core.setFailed(
|
||||||
'PR title contains `<`, `>`, `{`, or `}` not wrapped in backticks.\n' +
|
'PR title contains `<` or `>` not wrapped in backticks.\n' +
|
||||||
'Astro docs MDX interprets bare `<` as JSX components and bare `{` as JS expressions.\n' +
|
'Astro docs MDX interprets bare `<` as JSX components.\n' +
|
||||||
'Please wrap these characters with backticks, e.g.: [component] Add `<feature>` support'
|
'Please wrap angle brackets with backticks, e.g.: [component] Add `<feature>` support'
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ on:
|
|||||||
- cron: "0 2 * * *"
|
- cron: "0 2 * * *"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout for all jobs; deploy jobs add their own scopes when they need to write
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
init:
|
init:
|
||||||
@@ -57,8 +57,8 @@ jobs:
|
|||||||
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
|
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout to build the sdist/wheel
|
contents: read
|
||||||
id-token: write # OIDC token for PyPI Trusted Publishing (pypa/gh-action-pypi-publish)
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
@@ -78,8 +78,8 @@ jobs:
|
|||||||
name: Build ESPHome ${{ matrix.platform.arch }}
|
name: Build ESPHome ${{ matrix.platform.arch }}
|
||||||
if: github.repository == 'esphome/esphome'
|
if: github.repository == 'esphome/esphome'
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout to load Dockerfile and build context
|
contents: read
|
||||||
packages: write # docker/login-action + build-push-action push image digests to ghcr.io
|
packages: write
|
||||||
runs-on: ${{ matrix.platform.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
needs: [init]
|
needs: [init]
|
||||||
strategy:
|
strategy:
|
||||||
@@ -99,15 +99,15 @@ jobs:
|
|||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -152,8 +152,8 @@ jobs:
|
|||||||
- deploy-docker
|
- deploy-docker
|
||||||
if: github.repository == 'esphome/esphome'
|
if: github.repository == 'esphome/esphome'
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # actions/checkout to load Dockerfile and build context
|
contents: read
|
||||||
packages: write # docker/login-action + build-push-action push image digests to ghcr.io
|
packages: write
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -178,17 +178,17 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
if: matrix.registry == 'dockerhub'
|
if: matrix.registry == 'dockerhub'
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
if: matrix.registry == 'ghcr'
|
if: matrix.registry == 'ghcr'
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -212,6 +212,72 @@ jobs:
|
|||||||
docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \
|
docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \
|
||||||
$(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *)
|
$(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *)
|
||||||
|
|
||||||
|
deploy-ha-addon-repo:
|
||||||
|
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- init
|
||||||
|
- deploy-manifest
|
||||||
|
steps:
|
||||||
|
- name: Generate a token
|
||||||
|
id: generate-token
|
||||||
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||||
|
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||||
|
owner: esphome
|
||||||
|
repositories: home-assistant-addon
|
||||||
|
|
||||||
|
- name: Trigger Workflow
|
||||||
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
script: |
|
||||||
|
let description = "ESPHome";
|
||||||
|
if (context.eventName == "release") {
|
||||||
|
description = ${{ toJSON(github.event.release.body) }};
|
||||||
|
}
|
||||||
|
github.rest.actions.createWorkflowDispatch({
|
||||||
|
owner: "esphome",
|
||||||
|
repo: "home-assistant-addon",
|
||||||
|
workflow_id: "bump-version.yml",
|
||||||
|
ref: "main",
|
||||||
|
inputs: {
|
||||||
|
version: "${{ needs.init.outputs.tag }}",
|
||||||
|
content: description
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
deploy-esphome-schema:
|
||||||
|
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [init]
|
||||||
|
environment: ${{ needs.init.outputs.deploy_env }}
|
||||||
|
steps:
|
||||||
|
- name: Generate a token
|
||||||
|
id: generate-token
|
||||||
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||||
|
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||||
|
owner: esphome
|
||||||
|
repositories: esphome-schema
|
||||||
|
|
||||||
|
- name: Trigger Workflow
|
||||||
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
script: |
|
||||||
|
github.rest.actions.createWorkflowDispatch({
|
||||||
|
owner: "esphome",
|
||||||
|
repo: "esphome-schema",
|
||||||
|
workflow_id: "generate-schemas.yml",
|
||||||
|
ref: "main",
|
||||||
|
inputs: {
|
||||||
|
version: "${{ needs.init.outputs.tag }}",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
version-notifier:
|
version-notifier:
|
||||||
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
|
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -221,20 +287,19 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||||
owner: esphome
|
owner: esphome
|
||||||
repositories: version-notifier
|
repositories: version-notifier
|
||||||
permission-actions: write # actions.createWorkflowDispatch on the target repo (only API call made with this token)
|
|
||||||
|
|
||||||
- name: Trigger Workflow
|
- name: Trigger Workflow
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
await github.rest.actions.createWorkflowDispatch({
|
github.rest.actions.createWorkflowDispatch({
|
||||||
owner: "esphome",
|
owner: "esphome",
|
||||||
repo: "version-notifier",
|
repo: "version-notifier",
|
||||||
workflow_id: "notify.yml",
|
workflow_id: "notify.yml",
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
issues: write # actions/stale labels, comments on, and closes stale issues
|
issues: write
|
||||||
pull-requests: write # actions/stale labels, comments on, and closes stale pull requests
|
pull-requests: write
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: lock
|
group: lock
|
||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Stale
|
- name: Stale
|
||||||
uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||||
with:
|
with:
|
||||||
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
|
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
|
||||||
remove-stale-when-updated: true
|
remove-stale-when-updated: true
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened, labeled, unlabeled, synchronize]
|
types: [opened, reopened, labeled, unlabeled, synchronize]
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: read # issues.listLabelsOnIssue to detect blocking labels (needs-docs, merge-after-release, chained-pr)
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|||||||
@@ -6,27 +6,12 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: "45 6 * * *"
|
- cron: "45 6 * * *"
|
||||||
|
|
||||||
# Repo writes (branch push, PR open) happen via the App token minted below,
|
|
||||||
# so the workflow's GITHUB_TOKEN does not need any write scopes.
|
|
||||||
permissions:
|
|
||||||
contents: read # actions/checkout for this repo and home-assistant/core
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
sync:
|
||||||
name: Sync Device Classes
|
name: Sync Device Classes
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'esphome/esphome'
|
if: github.repository == 'esphome/esphome'
|
||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
||||||
with:
|
|
||||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
|
||||||
# Scope the minted App token to the minimum needed by peter-evans/create-pull-request.
|
|
||||||
permission-contents: write # git.createCommit + refs.create/update to push the sync/device-classes branch
|
|
||||||
permission-pull-requests: write # pulls.create / pulls.update to open or refresh the sync PR
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
@@ -41,56 +26,19 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.14"
|
python-version: "3.14"
|
||||||
|
|
||||||
- name: Set up uv
|
|
||||||
# An order of magnitude faster than pip on cold boots, with its
|
|
||||||
# own wheel cache. ``--system`` (below) installs into the
|
|
||||||
# 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@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
||||||
with:
|
|
||||||
enable-cache: true
|
|
||||||
# Pin uv version so the action does not have to fetch the
|
|
||||||
# manifest from raw.githubusercontent.com on every cache
|
|
||||||
# miss; that fetch flakes on Windows runners.
|
|
||||||
version: "0.11.15"
|
|
||||||
|
|
||||||
- name: Install Home Assistant
|
- name: Install Home Assistant
|
||||||
run: |
|
run: |
|
||||||
uv pip install --system -e lib/home-assistant
|
python -m pip install --upgrade pip
|
||||||
uv pip install --system -r requirements.txt -r requirements_test.txt pre-commit
|
pip install -e lib/home-assistant
|
||||||
|
pip install -r requirements_test.txt pre-commit
|
||||||
|
|
||||||
- name: Sync
|
- name: Sync
|
||||||
run: |
|
run: |
|
||||||
python ./script/sync-device_class.py
|
python ./script/sync-device_class.py
|
||||||
|
|
||||||
- name: Apply pre-commit auto-fixes
|
- name: Run pre-commit hooks
|
||||||
# First pass: let formatters (ruff, end-of-file-fixer, etc.) modify
|
run: |
|
||||||
# files. pre-commit exits non-zero whenever a hook touches anything,
|
python script/run-in-env.py pre-commit run --all-files
|
||||||
# which would otherwise abort the workflow before the auto-fixes
|
|
||||||
# can flow into the sync PR.
|
|
||||||
#
|
|
||||||
# SKIP:
|
|
||||||
# - no-commit-to-branch is a local guard against committing on
|
|
||||||
# dev/release/beta; CI runs on dev by definition, and
|
|
||||||
# peter-evans/create-pull-request creates the branch itself.
|
|
||||||
# - pylint surfaces import-error / relative-beyond-top-level
|
|
||||||
# noise here because this workflow installs only a subset of
|
|
||||||
# the runtime deps (HA + requirements*.txt); main CI already
|
|
||||||
# gates pylint on real PRs.
|
|
||||||
env:
|
|
||||||
SKIP: pylint,no-commit-to-branch
|
|
||||||
run: python script/run-in-env.py pre-commit run --all-files || true
|
|
||||||
|
|
||||||
- name: Verify pre-commit clean
|
|
||||||
# Second pass: re-run all hooks against the now-fixed tree.
|
|
||||||
# Auto-fixers exit 0 (nothing to change); any remaining failure
|
|
||||||
# from a check-only hook (flake8 / yamllint / ci-custom) is a
|
|
||||||
# real issue and fails the workflow loudly. Same SKIP list as
|
|
||||||
# above for the same reasons.
|
|
||||||
env:
|
|
||||||
SKIP: pylint,no-commit-to-branch
|
|
||||||
run: python script/run-in-env.py pre-commit run --all-files
|
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||||
@@ -102,4 +50,4 @@ jobs:
|
|||||||
delete-branch: true
|
delete-branch: true
|
||||||
title: "Synchronise Device Classes from Home Assistant"
|
title: "Synchronise Device Classes from Home Assistant"
|
||||||
body-path: .github/PULL_REQUEST_TEMPLATE.md
|
body-path: .github/PULL_REQUEST_TEMPLATE.md
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ ci:
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.15.15
|
rev: v0.15.12
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
@@ -55,7 +55,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: pylint
|
- id: pylint
|
||||||
name: pylint
|
name: pylint
|
||||||
entry: python script/run-in-env.py pylint
|
entry: python3 script/run-in-env.py pylint
|
||||||
language: system
|
language: system
|
||||||
types: [python]
|
types: [python]
|
||||||
files: ^esphome/.+\.py$
|
files: ^esphome/.+\.py$
|
||||||
@@ -68,5 +68,5 @@ repos:
|
|||||||
additional_dependencies: []
|
additional_dependencies: []
|
||||||
- id: ci-custom
|
- id: ci-custom
|
||||||
name: ci-custom
|
name: ci-custom
|
||||||
entry: python script/run-in-env.py script/ci-custom.py
|
entry: python3 script/run-in-env.py script/ci-custom.py
|
||||||
language: system
|
language: system
|
||||||
|
|||||||
@@ -416,8 +416,6 @@ esphome/components/resampler/speaker/* @kahrendt
|
|||||||
esphome/components/restart/* @esphome/core
|
esphome/components/restart/* @esphome/core
|
||||||
esphome/components/rf_bridge/* @jesserockz
|
esphome/components/rf_bridge/* @jesserockz
|
||||||
esphome/components/rgbct/* @jesserockz
|
esphome/components/rgbct/* @jesserockz
|
||||||
esphome/components/ring_buffer/* @kahrendt
|
|
||||||
esphome/components/router/speaker/* @kahrendt
|
|
||||||
esphome/components/rp2040/* @jesserockz
|
esphome/components/rp2040/* @jesserockz
|
||||||
esphome/components/rp2040_ble/* @bdraco
|
esphome/components/rp2040_ble/* @bdraco
|
||||||
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
|
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2026.6.0-dev
|
PROJECT_NUMBER = 2026.5.0-dev
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
+3
-7
@@ -13,16 +13,12 @@ RUN git config --system --add safe.directory "*" \
|
|||||||
&& git config --system advice.detachedHead false
|
&& git config --system advice.detachedHead false
|
||||||
|
|
||||||
# Install build tools for Python packages that require compilation
|
# Install build tools for Python packages that require compilation
|
||||||
# (e.g., ruamel.yaml.clib used by ESP-IDF's idf-component-manager).
|
# (e.g., ruamel.yaml.clibz used by ESP-IDF's idf-component-manager)
|
||||||
# Also install libusb-1.0 at runtime so the ESP-IDF tools installer can
|
|
||||||
# validate openocd-esp32 (it dynamically links libusb-1.0.so.0); without
|
|
||||||
# it idf_tools.py rejects the openocd install with exit 127 and aborts
|
|
||||||
# the whole framework setup.
|
|
||||||
RUN if command -v apk > /dev/null; then \
|
RUN if command -v apk > /dev/null; then \
|
||||||
apk add --no-cache build-base libusb; \
|
apk add --no-cache build-base; \
|
||||||
else \
|
else \
|
||||||
apt-get update \
|
apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends build-essential libusb-1.0-0 \
|
&& apt-get install -y --no-install-recommends build-essential \
|
||||||
&& rm -rf /var/lib/apt/lists/*; \
|
&& rm -rf /var/lib/apt/lists/*; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/with-contenv bashio
|
|
||||||
# ==============================================================================
|
|
||||||
# Installs the latest prerelease of esphome-device-builder when the
|
|
||||||
# `use_new_device_builder` config option is enabled.
|
|
||||||
# This is a temporary install-on-boot step until esphome-device-builder
|
|
||||||
# becomes a direct dependency of esphome.
|
|
||||||
# ==============================================================================
|
|
||||||
|
|
||||||
if ! bashio::config.true 'use_new_device_builder'; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
bashio::log.info "Installing latest prerelease of esphome-device-builder..."
|
|
||||||
if command -v uv > /dev/null; then
|
|
||||||
uv pip install --system --no-cache-dir --prerelease=allow --upgrade \
|
|
||||||
esphome-device-builder ||
|
|
||||||
bashio::exit.nok "Failed installing esphome-device-builder."
|
|
||||||
else
|
|
||||||
pip install --no-cache-dir --pre --upgrade esphome-device-builder ||
|
|
||||||
bashio::exit.nok "Failed installing esphome-device-builder."
|
|
||||||
fi
|
|
||||||
bashio::log.info "Installed esphome-device-builder."
|
|
||||||
@@ -49,12 +49,5 @@ if bashio::fs.directory_exists '/config/esphome/.esphome'; then
|
|||||||
rm -rf /config/esphome/.esphome
|
rm -rf /config/esphome/.esphome
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if bashio::config.true 'use_new_device_builder'; then
|
|
||||||
bashio::log.info "Starting ESPHome Device Builder..."
|
|
||||||
exec esphome-device-builder /config/esphome \
|
|
||||||
--ha-addon \
|
|
||||||
--ingress-port "$(bashio::addon.ingress_port)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
bashio::log.info "Starting ESPHome dashboard..."
|
bashio::log.info "Starting ESPHome dashboard..."
|
||||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
|
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
|
||||||
|
|||||||
@@ -4,14 +4,6 @@
|
|||||||
# Community Hass.io Add-ons: ESPHome
|
# Community Hass.io Add-ons: ESPHome
|
||||||
# Configures NGINX for use with ESPHome
|
# Configures NGINX for use with ESPHome
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
# When the new device builder is enabled it serves HA ingress directly,
|
|
||||||
# so nginx is not used at all -- skip configuration.
|
|
||||||
if bashio::config.true 'use_new_device_builder'; then
|
|
||||||
bashio::log.info "Skipping NGINX setup: new device builder serves ingress directly."
|
|
||||||
bashio::exit.ok
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p /var/log/nginx
|
mkdir -p /var/log/nginx
|
||||||
|
|
||||||
# Generate Ingress configuration
|
# Generate Ingress configuration
|
||||||
|
|||||||
@@ -5,14 +5,6 @@
|
|||||||
# Runs the NGINX proxy
|
# Runs the NGINX proxy
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
# The new device builder handles HA ingress itself, so nginx is bypassed.
|
|
||||||
# Block the longrun forever so s6 keeps the dependency satisfied and does
|
|
||||||
# not respawn it.
|
|
||||||
if bashio::config.true 'use_new_device_builder'; then
|
|
||||||
bashio::log.info "NGINX bypassed: new device builder serves ingress directly."
|
|
||||||
exec sleep infinity
|
|
||||||
fi
|
|
||||||
|
|
||||||
bashio::log.info "Waiting for ESPHome dashboard to come up..."
|
bashio::log.info "Waiting for ESPHome dashboard to come up..."
|
||||||
|
|
||||||
while [[ ! -S /var/run/esphome.sock ]]; do
|
while [[ ! -S /var/run/esphome.sock ]]; do
|
||||||
|
|||||||
+144
-617
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ from .helpers import (
|
|||||||
from .toolchain import find_tool, resolve_tool_path, run_tool
|
from .toolchain import find_tool, resolve_tool_path, run_tool
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from esphome.platformio.toolchain import IDEData
|
from esphome.platformio_api import IDEData
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -793,11 +793,8 @@ class MemoryAnalyzer:
|
|||||||
"""Scan ESPHome source object files to map extern "C" symbols to components.
|
"""Scan ESPHome source object files to map extern "C" symbols to components.
|
||||||
|
|
||||||
When no linker map file is available, this uses ``nm`` to scan ``.o`` files
|
When no linker map file is available, this uses ``nm`` to scan ``.o`` files
|
||||||
under ``src/`` (including ``src/main.cpp.o`` and everything beneath
|
under ``src/esphome/`` and build a symbol-to-component mapping. This catches
|
||||||
``src/esphome/``) and build a symbol-to-component mapping. This catches
|
``extern "C"`` functions and other symbols that lack C++ namespace prefixes.
|
||||||
``extern "C"`` functions, the ESPHome-generated ``setup()``/``loop()``
|
|
||||||
entry points in ``main.cpp``, and other symbols that lack C++ namespace
|
|
||||||
prefixes.
|
|
||||||
|
|
||||||
Skips scanning if ``_source_symbol_map`` was already populated by
|
Skips scanning if ``_source_symbol_map`` was already populated by
|
||||||
``_parse_map_file()``.
|
``_parse_map_file()``.
|
||||||
@@ -809,12 +806,12 @@ class MemoryAnalyzer:
|
|||||||
if obj_dir is None:
|
if obj_dir is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Scan all ESPHome-owned source object files: src/main.cpp.o and src/esphome/...
|
# Find ESPHome source object files
|
||||||
src_dir = obj_dir / "src"
|
esphome_src_dir = obj_dir / "src" / "esphome"
|
||||||
if not src_dir.is_dir():
|
if not esphome_src_dir.is_dir():
|
||||||
return
|
return
|
||||||
|
|
||||||
obj_files = sorted(src_dir.rglob("*.o"))
|
obj_files = sorted(esphome_src_dir.rglob("*.o"))
|
||||||
if not obj_files:
|
if not obj_files:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1067,10 +1064,6 @@ class MemoryAnalyzer:
|
|||||||
if component_name in self.external_components:
|
if component_name in self.external_components:
|
||||||
return f"{_COMPONENT_PREFIX_EXTERNAL}{component_name}"
|
return f"{_COMPONENT_PREFIX_EXTERNAL}{component_name}"
|
||||||
|
|
||||||
# ESPHome-generated entry point: src/main.cpp.o (contains setup()/loop())
|
|
||||||
if len(parts) >= 2 and parts[-2:] == ("src", "main.cpp.o"):
|
|
||||||
return _COMPONENT_CORE
|
|
||||||
|
|
||||||
# ESPHome core: src/esphome/core/... or src/esphome/...
|
# ESPHome core: src/esphome/core/... or src/esphome/...
|
||||||
if "core" in parts and "esphome" in parts:
|
if "core" in parts and "esphome" in parts:
|
||||||
return _COMPONENT_CORE
|
return _COMPONENT_CORE
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from collections import defaultdict
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
import heapq
|
import heapq
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
@@ -510,7 +509,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
|||||||
lines.append(
|
lines.append(
|
||||||
f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):"
|
f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):"
|
||||||
)
|
)
|
||||||
for i, (_symbol, demangled, size) in enumerate(large_core_symbols):
|
for i, (symbol, demangled, size) in enumerate(large_core_symbols):
|
||||||
# Core symbols only track (symbol, demangled, size) without section info,
|
# Core symbols only track (symbol, demangled, size) without section info,
|
||||||
# so we don't show section labels here
|
# so we don't show section labels here
|
||||||
lines.append(
|
lines.append(
|
||||||
@@ -602,7 +601,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
|||||||
lines.append(
|
lines.append(
|
||||||
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B & storage ({len(large_symbols)} symbols):"
|
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B & storage ({len(large_symbols)} symbols):"
|
||||||
)
|
)
|
||||||
for i, (_symbol, demangled, size, section) in enumerate(large_symbols):
|
for i, (symbol, demangled, size, section) in enumerate(large_symbols):
|
||||||
lines.append(
|
lines.append(
|
||||||
f"{i + 1}. {self._format_symbol_with_section(demangled, size, section)}"
|
f"{i + 1}. {self._format_symbol_with_section(demangled, size, section)}"
|
||||||
)
|
)
|
||||||
@@ -641,7 +640,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
|||||||
lines.append(
|
lines.append(
|
||||||
f" Symbols > {self.RAM_SYMBOL_SIZE_THRESHOLD} B ({len(large_ram_syms)}):"
|
f" Symbols > {self.RAM_SYMBOL_SIZE_THRESHOLD} B ({len(large_ram_syms)}):"
|
||||||
)
|
)
|
||||||
for _symbol, demangled, size, section in large_ram_syms[:10]:
|
for symbol, demangled, size, section in large_ram_syms[:10]:
|
||||||
# Format section label consistently by stripping leading dot
|
# Format section label consistently by stripping leading dot
|
||||||
section_label = section.lstrip(".") if section else ""
|
section_label = section.lstrip(".") if section else ""
|
||||||
display_name = _format_pstorage_name(demangled)
|
display_name = _format_pstorage_name(demangled)
|
||||||
@@ -700,7 +699,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
|||||||
content = "\n".join(lines)
|
content = "\n".join(lines)
|
||||||
|
|
||||||
if output_file:
|
if output_file:
|
||||||
with Path(output_file).open("w", encoding="utf-8") as f:
|
with open(output_file, "w", encoding="utf-8") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
else:
|
else:
|
||||||
print(content)
|
print(content)
|
||||||
@@ -738,8 +737,9 @@ def main():
|
|||||||
|
|
||||||
# Load build directory
|
# Load build directory
|
||||||
import json
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from esphome.platformio.toolchain import IDEData
|
from esphome.platformio_api import IDEData
|
||||||
|
|
||||||
build_path = Path(build_dir)
|
build_path = Path(build_dir)
|
||||||
|
|
||||||
@@ -785,7 +785,7 @@ def main():
|
|||||||
if not idedata_path.exists():
|
if not idedata_path.exists():
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
with idedata_path.open(encoding="utf-8") as f:
|
with open(idedata_path, encoding="utf-8") as f:
|
||||||
raw_data = json.load(f)
|
raw_data = json.load(f)
|
||||||
idedata = IDEData(raw_data)
|
idedata = IDEData(raw_data)
|
||||||
print(f"Loaded idedata from: {idedata_path}", file=sys.stderr)
|
print(f"Loaded idedata from: {idedata_path}", file=sys.stderr)
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ def batch_demangle(
|
|||||||
failed_count = 0
|
failed_count = 0
|
||||||
|
|
||||||
for original, stripped, prefix, demangled in zip(
|
for original, stripped, prefix, demangled in zip(
|
||||||
symbols, symbols_stripped, symbols_prefixes, demangled_lines, strict=True
|
symbols, symbols_stripped, symbols_prefixes, demangled_lines
|
||||||
):
|
):
|
||||||
# Add back any prefix that was removed
|
# Add back any prefix that was removed
|
||||||
demangled = _restore_symbol_prefix(prefix, stripped, demangled)
|
demangled = _restore_symbol_prefix(prefix, stripped, demangled)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
@@ -36,7 +37,7 @@ def _find_in_platformio_packages(tool_name: str) -> str | None:
|
|||||||
Full path to the tool or None if not found
|
Full path to the tool or None if not found
|
||||||
"""
|
"""
|
||||||
# Get PlatformIO packages directory
|
# Get PlatformIO packages directory
|
||||||
platformio_home = Path("~/.platformio/packages").expanduser()
|
platformio_home = Path(os.path.expanduser("~/.platformio/packages"))
|
||||||
if not platformio_home.exists():
|
if not platformio_home.exists():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class AsyncThreadRunner(threading.Thread, Generic[_T]):
|
|||||||
async def _runner(self) -> None:
|
async def _runner(self) -> None:
|
||||||
try:
|
try:
|
||||||
self.result = await self._coro_factory()
|
self.result = await self._coro_factory()
|
||||||
except Exception as exc: # noqa: BLE001 # pylint: disable=broad-except
|
except Exception as exc: # pylint: disable=broad-except
|
||||||
# Capture all exceptions so ``event`` is always set — otherwise a
|
# Capture all exceptions so ``event`` is always set — otherwise a
|
||||||
# crash would hang the waiter forever.
|
# crash would hang the waiter forever.
|
||||||
self.exception = exc
|
self.exception = exc
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ def validate_potentially_or_condition(value):
|
|||||||
return validate_condition(value)
|
return validate_condition(value)
|
||||||
|
|
||||||
|
|
||||||
DelayAction = cg.esphome_ns.class_("DelayAction", Action)
|
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
||||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
||||||
StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action)
|
StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action)
|
||||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||||
@@ -396,6 +396,7 @@ async def delay_action_to_code(
|
|||||||
args: TemplateArgsType,
|
args: TemplateArgsType,
|
||||||
) -> MockObj:
|
) -> MockObj:
|
||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
|
await cg.register_component(var, {})
|
||||||
template_ = await cg.templatable(config, args, cg.uint32)
|
template_ = await cg.templatable(config, args, cg.uint32)
|
||||||
cg.add(var.set_delay(template_))
|
cg.add(var.set_delay(template_))
|
||||||
return var
|
return var
|
||||||
|
|||||||
+40
-134
@@ -3,28 +3,23 @@
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from esphome.components.esp32 import get_esp32_variant, idf_version
|
from esphome.components.esp32 import get_esp32_variant
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
from esphome.helpers import mkdir_p, write_file_if_changed
|
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||||
from esphome.writer import update_storage_json
|
|
||||||
|
|
||||||
|
|
||||||
def get_available_components() -> list[str] | None:
|
def get_available_components() -> list[str] | None:
|
||||||
"""Get list of built-in ESP-IDF components from project_description.json.
|
"""Get list of available ESP-IDF components from project_description.json.
|
||||||
|
|
||||||
Excludes ``src``, IDF-managed components (``managed_components/``), and
|
Returns only internal ESP-IDF components, excluding external/managed
|
||||||
converted PIO libs (``pio_components/``). Returns ``None`` if the build
|
components (from idf_component.yml).
|
||||||
dir or ``project_description.json`` isn't ready yet.
|
|
||||||
"""
|
"""
|
||||||
if CORE.build_path is None:
|
|
||||||
return None
|
|
||||||
project_desc = Path(CORE.build_path) / "build" / "project_description.json"
|
project_desc = Path(CORE.build_path) / "build" / "project_description.json"
|
||||||
if not project_desc.exists():
|
if not project_desc.exists():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with project_desc.open(encoding="utf-8") as f:
|
with open(project_desc, encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
||||||
component_info = data.get("build_component_info", {})
|
component_info = data.get("build_component_info", {})
|
||||||
@@ -35,9 +30,9 @@ def get_available_components() -> list[str] | None:
|
|||||||
if name == "src":
|
if name == "src":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Exclude IDF-managed and converted-PIO components (external).
|
# Exclude managed/external components
|
||||||
comp_dir = info.get("dir", "")
|
comp_dir = info.get("dir", "")
|
||||||
if "managed_components" in comp_dir or "pio_components" in comp_dir:
|
if "managed_components" in comp_dir:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result.append(name)
|
result.append(name)
|
||||||
@@ -52,90 +47,23 @@ def has_discovered_components() -> bool:
|
|||||||
return get_available_components() is not None
|
return get_available_components() is not None
|
||||||
|
|
||||||
|
|
||||||
def get_project_cmakelists(minimal: bool = False) -> str:
|
def get_project_cmakelists() -> str:
|
||||||
"""Generate the top-level CMakeLists.txt for ESP-IDF project.
|
"""Generate the top-level CMakeLists.txt for ESP-IDF project."""
|
||||||
|
|
||||||
When ``minimal`` is true, omit ``ESPHOME_PROJECT_BUILTIN_COMPONENTS``
|
|
||||||
since ``project_description.json`` may be stale on the first write.
|
|
||||||
"""
|
|
||||||
# Get IDF target from ESP32 variant (e.g., ESP32S3 -> esp32s3)
|
# Get IDF target from ESP32 variant (e.g., ESP32S3 -> esp32s3)
|
||||||
variant = get_esp32_variant()
|
variant = get_esp32_variant()
|
||||||
idf_target = variant.lower().replace("-", "")
|
idf_target = variant.lower().replace("-", "")
|
||||||
|
|
||||||
# esp_idf_size 2.x (bundled with IDF >=6.0) made NG the default and
|
# Extract compile definitions from build flags (-DXXX -> XXX)
|
||||||
# removed the --ng flag; on 1.x (IDF 5.5) --ng is required to get
|
compile_defs = [flag for flag in CORE.build_flags if flag.startswith("-D")]
|
||||||
# --format=raw because the legacy mode doesn't support it.
|
|
||||||
size_ng_flag = "--ng" if idf_version() < cv.Version(6, 0, 0) else ""
|
|
||||||
|
|
||||||
# Project-wide compile options: -D defines and -W warning flags (skip
|
|
||||||
# -Wl, linker flags — those go on the src component via
|
|
||||||
# target_link_options below). Emitted via idf_build_set_property so the
|
|
||||||
# flags propagate to every IDF component (including managed ones like
|
|
||||||
# esphome__micro-mp3) rather than just src/. Required so suppressions
|
|
||||||
# like ``-Wno-error=maybe-uninitialized`` actually silence warnings in
|
|
||||||
# third-party components we don't author.
|
|
||||||
project_compile_opts = [
|
|
||||||
flag
|
|
||||||
for flag in sorted(CORE.build_flags)
|
|
||||||
if flag.startswith("-D")
|
|
||||||
or (flag.startswith("-W") and not flag.startswith("-Wl,"))
|
|
||||||
]
|
|
||||||
extra_compile_options = "\n".join(
|
extra_compile_options = "\n".join(
|
||||||
f'idf_build_set_property(COMPILE_OPTIONS "{flag}" APPEND)'
|
f'idf_build_set_property(COMPILE_OPTIONS "{compile_def}" APPEND)'
|
||||||
for flag in project_compile_opts
|
for compile_def in compile_defs
|
||||||
)
|
|
||||||
|
|
||||||
# Per-project list exposed as a CMake variable so converted PIO libs
|
|
||||||
# can reference ${ESPHOME_PROJECT_MANAGED_COMPONENTS} without baking
|
|
||||||
# project-specific names into their cached CMakeLists.
|
|
||||||
#
|
|
||||||
# Emit via idf_build_set_property (not plain set()) so the value is
|
|
||||||
# serialised into build_properties.temp.cmake and visible to IDF's
|
|
||||||
# early requirements-expansion pass (component_get_requirements.cmake
|
|
||||||
# runs as a separate CMake script invocation that doesn't load the
|
|
||||||
# project's top-level CMakeLists; without this, ${ESPHOME_PROJECT_
|
|
||||||
# MANAGED_COMPONENTS} in a converted-lib REQUIRES expands to empty).
|
|
||||||
from esphome.components.esp32 import get_managed_component_require_names
|
|
||||||
|
|
||||||
managed_components_property = "\n".join(
|
|
||||||
f"idf_build_set_property(ESPHOME_PROJECT_MANAGED_COMPONENTS {name} APPEND)"
|
|
||||||
for name in get_managed_component_require_names()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Built-in IDF components exposed via our own property (not IDF's
|
|
||||||
# __COMPONENT_REQUIRES_COMMON, which would append them to every
|
|
||||||
# component's REQUIRES including real IDF components). Referenced by
|
|
||||||
# src/CMakeLists and by each converted PIO lib's CMakeLists. Skipped
|
|
||||||
# on minimal writes because project_description.json may be stale.
|
|
||||||
builtin_components_property = (
|
|
||||||
""
|
|
||||||
if minimal
|
|
||||||
else "\n".join(
|
|
||||||
f"idf_build_set_property(ESPHOME_PROJECT_BUILTIN_COMPONENTS {name} APPEND)"
|
|
||||||
for name in sorted(get_available_components() or [])
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return f"""\
|
return f"""\
|
||||||
# Auto-generated by ESPHome
|
# Auto-generated by ESPHome
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
# On Windows, Ninja can fail with:
|
|
||||||
# "CreateProcess: The parameter is incorrect (is the command line too long?)"
|
|
||||||
# when compiler/linker command lines exceed the OS length limit.
|
|
||||||
#
|
|
||||||
# The following settings force CMake/Ninja to use *response files* (@file.rsp)
|
|
||||||
# to pass long lists of includes, objects, and other arguments indirectly,
|
|
||||||
# avoiding command-line length limits and fixing the build failure.
|
|
||||||
#
|
|
||||||
# This is especially useful for large ESP-IDF / ESPHome projects with many
|
|
||||||
# source files or include directories.
|
|
||||||
set(CMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES 1)
|
|
||||||
set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES 1)
|
|
||||||
set(CMAKE_C_USE_RESPONSE_FILE_FOR_OBJECTS 1)
|
|
||||||
set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS 1)
|
|
||||||
set(CMAKE_NINJA_FORCE_RESPONSE_FILE 1)
|
|
||||||
|
|
||||||
set(IDF_TARGET {idf_target})
|
set(IDF_TARGET {idf_target})
|
||||||
set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src)
|
set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src)
|
||||||
|
|
||||||
@@ -143,67 +71,50 @@ include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
|
|||||||
|
|
||||||
{extra_compile_options}
|
{extra_compile_options}
|
||||||
|
|
||||||
{managed_components_property}
|
|
||||||
|
|
||||||
{builtin_components_property}
|
|
||||||
|
|
||||||
project({CORE.name})
|
project({CORE.name})
|
||||||
|
|
||||||
# Emit raw JSON size data for ESPHome to read post-build.
|
|
||||||
add_custom_command(
|
|
||||||
TARGET ${{CMAKE_PROJECT_NAME}}.elf POST_BUILD
|
|
||||||
COMMAND ${{PYTHON}} -m esp_idf_size {size_ng_flag} --format=raw
|
|
||||||
-o ${{CMAKE_BINARY_DIR}}/esp_idf_size.json
|
|
||||||
${{CMAKE_PROJECT_NAME}}.map
|
|
||||||
WORKING_DIRECTORY ${{CMAKE_BINARY_DIR}}
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_component_cmakelists() -> str:
|
def get_component_cmakelists(minimal: bool = False) -> str:
|
||||||
"""Generate the main component CMakeLists.txt.
|
"""Generate the main component CMakeLists.txt."""
|
||||||
|
idf_requires = [] if minimal else (get_available_components() or [])
|
||||||
|
requires_str = " ".join(idf_requires)
|
||||||
|
|
||||||
REQUIRES pulls in the discovered built-in IDF components via the
|
# Extract compile options (-W flags, excluding linker flags)
|
||||||
project-level variables set in the top-level CMakeLists.
|
compile_opts = [
|
||||||
"""
|
flag
|
||||||
# Extract linker options (-Wl, flags). Compile flags (-D, -W) are
|
for flag in CORE.build_flags
|
||||||
# emitted project-wide via idf_build_set_property in
|
if flag.startswith("-W") and not flag.startswith("-Wl,")
|
||||||
# get_project_cmakelists so they reach every component, not just src/.
|
]
|
||||||
|
compile_opts_str = "\n ".join(sorted(compile_opts)) if compile_opts else ""
|
||||||
|
|
||||||
|
# Extract linker options (-Wl, flags)
|
||||||
link_opts = [flag for flag in CORE.build_flags if flag.startswith("-Wl,")]
|
link_opts = [flag for flag in CORE.build_flags if flag.startswith("-Wl,")]
|
||||||
link_opts_str = "\n ".join(sorted(link_opts)) if link_opts else ""
|
link_opts_str = "\n ".join(sorted(link_opts)) if link_opts else ""
|
||||||
|
|
||||||
return f"""\
|
return f"""\
|
||||||
# Auto-generated by ESPHome
|
# Auto-generated by ESPHome
|
||||||
# CONFIGURE_DEPENDS asks CMake to re-check the glob each build so test
|
file(GLOB_RECURSE app_sources
|
||||||
# runs that reuse the build dir don't compile stale source paths. It's
|
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cpp"
|
||||||
# invalid in script mode (cmake -P), which is how IDF's
|
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c"
|
||||||
# component_get_requirements.cmake includes us, so skip it there.
|
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cpp"
|
||||||
if(CMAKE_SCRIPT_MODE_FILE)
|
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c"
|
||||||
file(GLOB_RECURSE app_sources
|
)
|
||||||
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cpp"
|
|
||||||
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c"
|
|
||||||
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cpp"
|
|
||||||
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c"
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
file(GLOB_RECURSE app_sources CONFIGURE_DEPENDS
|
|
||||||
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cpp"
|
|
||||||
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c"
|
|
||||||
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cpp"
|
|
||||||
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS ${{app_sources}}
|
SRCS ${{app_sources}}
|
||||||
INCLUDE_DIRS "." "esphome"
|
INCLUDE_DIRS "." "esphome"
|
||||||
REQUIRES ${{ESPHOME_PROJECT_BUILTIN_COMPONENTS}}
|
REQUIRES {requires_str}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply C++ standard
|
# Apply C++ standard
|
||||||
target_compile_features(${{COMPONENT_LIB}} PUBLIC cxx_std_20)
|
target_compile_features(${{COMPONENT_LIB}} PUBLIC cxx_std_20)
|
||||||
|
|
||||||
|
# ESPHome compile options
|
||||||
|
target_compile_options(${{COMPONENT_LIB}} PUBLIC
|
||||||
|
{compile_opts_str}
|
||||||
|
)
|
||||||
|
|
||||||
# ESPHome linker options
|
# ESPHome linker options
|
||||||
target_link_options(${{COMPONENT_LIB}} PUBLIC
|
target_link_options(${{COMPONENT_LIB}} PUBLIC
|
||||||
{link_opts_str}
|
{link_opts_str}
|
||||||
@@ -213,22 +124,17 @@ target_link_options(${{COMPONENT_LIB}} PUBLIC
|
|||||||
|
|
||||||
def write_project(minimal: bool = False) -> None:
|
def write_project(minimal: bool = False) -> None:
|
||||||
"""Write ESP-IDF project files."""
|
"""Write ESP-IDF project files."""
|
||||||
# Refresh <data_dir>/storage/<name>.yaml.json so the dashboard's
|
|
||||||
# /info and /downloads endpoints can locate the build (they 404
|
|
||||||
# otherwise). This mirrors the PlatformIO build-gen path's call
|
|
||||||
# in build_gen/platformio.py:write_ini().
|
|
||||||
update_storage_json()
|
|
||||||
mkdir_p(CORE.build_path)
|
mkdir_p(CORE.build_path)
|
||||||
mkdir_p(CORE.relative_src_path())
|
mkdir_p(CORE.relative_src_path())
|
||||||
|
|
||||||
# Write top-level CMakeLists.txt
|
# Write top-level CMakeLists.txt
|
||||||
write_file_if_changed(
|
write_file_if_changed(
|
||||||
CORE.relative_build_path("CMakeLists.txt"),
|
CORE.relative_build_path("CMakeLists.txt"),
|
||||||
get_project_cmakelists(minimal=minimal),
|
get_project_cmakelists(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Write component CMakeLists.txt in src/
|
# Write component CMakeLists.txt in src/
|
||||||
write_file_if_changed(
|
write_file_if_changed(
|
||||||
CORE.relative_src_path("CMakeLists.txt"),
|
CORE.relative_src_path("CMakeLists.txt"),
|
||||||
get_component_cmakelists(),
|
get_component_cmakelists(minimal=minimal),
|
||||||
)
|
)
|
||||||
|
|||||||
+91
-20
@@ -98,13 +98,11 @@ _KNOWN_FILE_EXTENSIONS = frozenset(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Matches !secret references in YAML text. An optional surrounding
|
# Matches !secret references in YAML text. This is intentionally a simple
|
||||||
# quote pair around the key is allowed and ignored: YAML treats
|
# regex scan rather than a YAML parse — it may match inside comments or
|
||||||
# ``!secret 'foo'`` and ``!secret foo`` as the same key. This is
|
# multi-line strings, which is the conservative direction (include more
|
||||||
# intentionally a simple regex scan rather than a YAML parse — it may
|
# secrets rather than fewer).
|
||||||
# match inside comments or multi-line strings, which is the conservative
|
_SECRET_RE = re.compile(r"!secret\s+(\S+)")
|
||||||
# direction (include more secrets rather than fewer).
|
|
||||||
_SECRET_RE = re.compile(r"""!secret\s+['"]?([^\s'"]+)""")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_used_secret_keys(yaml_files: list[Path]) -> set[str]:
|
def _find_used_secret_keys(yaml_files: list[Path]) -> set[str]:
|
||||||
@@ -260,20 +258,42 @@ class ConfigBundleCreator:
|
|||||||
def _discover_yaml_includes(self) -> None:
|
def _discover_yaml_includes(self) -> None:
|
||||||
"""Discover YAML files loaded during config parsing.
|
"""Discover YAML files loaded during config parsing.
|
||||||
|
|
||||||
Delegates to :func:`yaml_util.discover_user_yaml_files`, which does a
|
Deliberately uses a fresh re-parse and force-loads every deferred
|
||||||
fresh re-parse and force-loads every deferred ``IncludeFile`` so that
|
``IncludeFile`` to include *all* potentially-reachable includes,
|
||||||
*all* potentially-reachable includes are captured (even branches not
|
even branches not selected by the local substitutions. Bundles are
|
||||||
selected by local substitutions). Bundles are meant to be compiled on
|
meant to be compiled on another system where command-line
|
||||||
another system where command-line substitution overrides may choose a
|
substitution overrides may choose a different branch — e.g.
|
||||||
different branch — e.g. ``!include network/${eth_model}/config.yaml``
|
``!include network/${eth_model}/config.yaml`` must ship every
|
||||||
must ship every candidate so the remote build can pick any one.
|
candidate so the remote build can pick any one.
|
||||||
|
|
||||||
|
Entries with unresolved substitution variables in the filename
|
||||||
|
path are skipped with a warning (they cannot be resolved without
|
||||||
|
the substitution pass).
|
||||||
|
|
||||||
|
Secrets files are tracked separately so we can filter them to
|
||||||
|
only include the keys this config actually references.
|
||||||
"""
|
"""
|
||||||
discovered = yaml_util.discover_user_yaml_files(self._config_path)
|
# Must be a fresh parse: IncludeFile.load() caches its result in
|
||||||
self._secrets_paths.update(discovered.secrets)
|
# _content, and we discover files by listening for loader calls. On
|
||||||
config_resolved = self._config_path.resolve()
|
# an already-parsed tree the cache is populated, .load() returns
|
||||||
for fpath in discovered.files:
|
# without calling the loader, the listener never fires, and the
|
||||||
if fpath == config_resolved:
|
# referenced files would be silently dropped from the bundle.
|
||||||
|
with yaml_util.track_yaml_loads() as loaded_files:
|
||||||
|
try:
|
||||||
|
data = yaml_util.load_yaml(self._config_path)
|
||||||
|
except EsphomeError:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Bundle: re-loading YAML for include discovery failed, "
|
||||||
|
"proceeding with partial file list"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_force_load_include_files(data)
|
||||||
|
|
||||||
|
for fpath in loaded_files:
|
||||||
|
if fpath == self._config_path.resolve():
|
||||||
continue # Already added as config
|
continue # Already added as config
|
||||||
|
if fpath.name in const.SECRETS_FILES:
|
||||||
|
self._secrets_paths.add(fpath)
|
||||||
self._add_file(fpath)
|
self._add_file(fpath)
|
||||||
|
|
||||||
def _discover_component_files(self) -> None:
|
def _discover_component_files(self) -> None:
|
||||||
@@ -412,7 +432,7 @@ class ConfigBundleCreator:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _add_to_tar(tar: tarfile.TarFile, bf: BundleFile) -> None:
|
def _add_to_tar(tar: tarfile.TarFile, bf: BundleFile) -> None:
|
||||||
"""Add a BundleFile to the tar archive with deterministic metadata."""
|
"""Add a BundleFile to the tar archive with deterministic metadata."""
|
||||||
with bf.source.open("rb") as f:
|
with open(bf.source, "rb") as f:
|
||||||
_add_bytes_to_tar(tar, bf.path, f.read())
|
_add_bytes_to_tar(tar, bf.path, f.read())
|
||||||
|
|
||||||
|
|
||||||
@@ -603,6 +623,57 @@ def _add_bytes_to_tar(tar: tarfile.TarFile, name: str, data: bytes) -> None:
|
|||||||
tar.addfile(info, io.BytesIO(data))
|
tar.addfile(info, io.BytesIO(data))
|
||||||
|
|
||||||
|
|
||||||
|
def _force_load_include_files(obj: Any, _seen: set[int] | None = None) -> None:
|
||||||
|
"""Recursively resolve any ``IncludeFile`` instances in a YAML tree.
|
||||||
|
|
||||||
|
Nested ``!include`` returns a deferred ``IncludeFile`` that is only
|
||||||
|
resolved during the substitution pass. During bundle discovery we need
|
||||||
|
the referenced files to actually load so the ``track_yaml_loads``
|
||||||
|
listener fires for them.
|
||||||
|
|
||||||
|
``IncludeFile`` instances with unresolved substitution variables in the
|
||||||
|
filename cannot be loaded — we skip and warn about those.
|
||||||
|
"""
|
||||||
|
if _seen is None:
|
||||||
|
_seen = set()
|
||||||
|
|
||||||
|
if isinstance(obj, yaml_util.IncludeFile):
|
||||||
|
if id(obj) in _seen:
|
||||||
|
return
|
||||||
|
_seen.add(id(obj))
|
||||||
|
if obj.has_unresolved_expressions():
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Bundle: cannot resolve !include %s (referenced from %s) "
|
||||||
|
"with substitutions in path",
|
||||||
|
obj.file,
|
||||||
|
obj.parent_file,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
loaded = obj.load()
|
||||||
|
except EsphomeError as err:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Bundle: failed to load !include %s (referenced from %s): %s",
|
||||||
|
obj.file,
|
||||||
|
obj.parent_file,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
_force_load_include_files(loaded, _seen)
|
||||||
|
elif isinstance(obj, dict):
|
||||||
|
if id(obj) in _seen:
|
||||||
|
return
|
||||||
|
_seen.add(id(obj))
|
||||||
|
for value in obj.values():
|
||||||
|
_force_load_include_files(value, _seen)
|
||||||
|
elif isinstance(obj, (list, tuple)):
|
||||||
|
if id(obj) in _seen:
|
||||||
|
return
|
||||||
|
_seen.add(id(obj))
|
||||||
|
for item in obj:
|
||||||
|
_force_load_include_files(item, _seen)
|
||||||
|
|
||||||
|
|
||||||
def _resolve_include_path(include_path: Any) -> Path | None:
|
def _resolve_include_path(include_path: Any) -> Path | None:
|
||||||
"""Resolve an include path to absolute, skipping system includes."""
|
"""Resolve an include path to absolute, skipping system includes."""
|
||||||
if isinstance(include_path, str) and include_path.startswith("<"):
|
if isinstance(include_path, str) and include_path.startswith("<"):
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
"""Validated-config cache for the upload/logs fast path.
|
|
||||||
|
|
||||||
compile dumps the validated config to <data_dir>/storage/<file>.validated.yaml;
|
|
||||||
the next upload/logs for that YAML reuses it instead of running the full
|
|
||||||
read_config pipeline. YAML round-trip (yaml_util.dump/load_yaml) keeps
|
|
||||||
!lambda/!include/IDs/paths intact; mtime gates staleness.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from esphome.core import CORE
|
|
||||||
from esphome.helpers import write_file
|
|
||||||
from esphome.storage_json import StorageJSON, ext_storage_path
|
|
||||||
from esphome.types import ConfigType
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def compiled_config_path(config_filename: str) -> Path:
|
|
||||||
"""Path to the cached validated config alongside the storage sidecar."""
|
|
||||||
return CORE.data_dir / "storage" / f"{config_filename}.validated.yaml"
|
|
||||||
|
|
||||||
|
|
||||||
def _cache_is_fresh(cache_path: Path, source_path: Path) -> bool:
|
|
||||||
"""True iff the cache file exists and isn't older than the source."""
|
|
||||||
try:
|
|
||||||
return cache_path.stat().st_mtime >= source_path.stat().st_mtime
|
|
||||||
except OSError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def save_compiled_config(config: ConfigType) -> None:
|
|
||||||
"""Write the validated-config cache. Always-write so mtime stays fresh.
|
|
||||||
|
|
||||||
Mode 0600 because show_secrets=True resolves !secret inline.
|
|
||||||
Failures are non-fatal: the fast path falls back to read_config.
|
|
||||||
"""
|
|
||||||
from esphome import yaml_util
|
|
||||||
|
|
||||||
try:
|
|
||||||
rendered = yaml_util.dump(config, show_secrets=True)
|
|
||||||
write_file(compiled_config_path(CORE.config_filename), rendered, private=True)
|
|
||||||
except Exception as err: # noqa: BLE001 # pylint: disable=broad-except
|
|
||||||
_LOGGER.debug("Skipping compiled config cache write: %s", err)
|
|
||||||
|
|
||||||
|
|
||||||
def load_compiled_config(conf_path: Path) -> ConfigType | None:
|
|
||||||
"""Load the cached validated config and apply storage metadata to CORE.
|
|
||||||
|
|
||||||
Returns None (caller falls back to read_config) when the cache is
|
|
||||||
missing, older than the source YAML, unparseable, or the sidecar
|
|
||||||
is incomplete.
|
|
||||||
"""
|
|
||||||
cache_path = compiled_config_path(conf_path.name)
|
|
||||||
if not _cache_is_fresh(cache_path, conf_path):
|
|
||||||
return None
|
|
||||||
|
|
||||||
from esphome import yaml_util
|
|
||||||
|
|
||||||
try:
|
|
||||||
config = yaml_util.load_yaml(cache_path, clear_secrets=False)
|
|
||||||
except Exception: # noqa: BLE001 # pylint: disable=broad-except
|
|
||||||
return None
|
|
||||||
|
|
||||||
storage = StorageJSON.load(ext_storage_path(conf_path.name))
|
|
||||||
if storage is None:
|
|
||||||
return None
|
|
||||||
# apply_to_core assumes a real compile wrote the sidecar; wizard-only
|
|
||||||
# sidecars leave both of these unset and can't drive upload/logs.
|
|
||||||
if not storage.core_platform and not storage.target_platform:
|
|
||||||
return None
|
|
||||||
storage.apply_to_core()
|
|
||||||
return config
|
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::a01nyub {
|
namespace esphome {
|
||||||
|
namespace a01nyub {
|
||||||
|
|
||||||
static const char *const TAG = "a01nyub.sensor";
|
static const char *const TAG = "a01nyub.sensor";
|
||||||
|
|
||||||
@@ -41,4 +42,5 @@ void A01nyubComponent::check_buffer_() {
|
|||||||
|
|
||||||
void A01nyubComponent::dump_config() { LOG_SENSOR("", "A01nyub Sensor", this); }
|
void A01nyubComponent::dump_config() { LOG_SENSOR("", "A01nyub Sensor", this); }
|
||||||
|
|
||||||
} // namespace esphome::a01nyub
|
} // namespace a01nyub
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
|
|
||||||
namespace esphome::a01nyub {
|
namespace esphome {
|
||||||
|
namespace a01nyub {
|
||||||
|
|
||||||
class A01nyubComponent : public sensor::Sensor, public Component, public uart::UARTDevice {
|
class A01nyubComponent : public sensor::Sensor, public Component, public uart::UARTDevice {
|
||||||
public:
|
public:
|
||||||
@@ -22,4 +23,5 @@ class A01nyubComponent : public sensor::Sensor, public Component, public uart::U
|
|||||||
std::vector<uint8_t> buffer_;
|
std::vector<uint8_t> buffer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::a01nyub
|
} // namespace a01nyub
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::a02yyuw {
|
namespace esphome {
|
||||||
|
namespace a02yyuw {
|
||||||
|
|
||||||
static const char *const TAG = "a02yyuw.sensor";
|
static const char *const TAG = "a02yyuw.sensor";
|
||||||
|
|
||||||
@@ -40,4 +41,5 @@ void A02yyuwComponent::check_buffer_() {
|
|||||||
|
|
||||||
void A02yyuwComponent::dump_config() { LOG_SENSOR("", "A02yyuw Sensor", this); }
|
void A02yyuwComponent::dump_config() { LOG_SENSOR("", "A02yyuw Sensor", this); }
|
||||||
|
|
||||||
} // namespace esphome::a02yyuw
|
} // namespace a02yyuw
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
|
|
||||||
namespace esphome::a02yyuw {
|
namespace esphome {
|
||||||
|
namespace a02yyuw {
|
||||||
|
|
||||||
class A02yyuwComponent : public sensor::Sensor, public Component, public uart::UARTDevice {
|
class A02yyuwComponent : public sensor::Sensor, public Component, public uart::UARTDevice {
|
||||||
public:
|
public:
|
||||||
@@ -22,4 +23,5 @@ class A02yyuwComponent : public sensor::Sensor, public Component, public uart::U
|
|||||||
std::vector<uint8_t> buffer_;
|
std::vector<uint8_t> buffer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::a02yyuw
|
} // namespace a02yyuw
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#include "a4988.h"
|
#include "a4988.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::a4988 {
|
namespace esphome {
|
||||||
|
namespace a4988 {
|
||||||
|
|
||||||
static const char *const TAG = "a4988.stepper";
|
static const char *const TAG = "a4988.stepper";
|
||||||
|
|
||||||
@@ -50,4 +51,5 @@ void A4988::loop() {
|
|||||||
this->step_pin_->digital_write(false);
|
this->step_pin_->digital_write(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::a4988
|
} // namespace a4988
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/components/stepper/stepper.h"
|
#include "esphome/components/stepper/stepper.h"
|
||||||
|
|
||||||
namespace esphome::a4988 {
|
namespace esphome {
|
||||||
|
namespace a4988 {
|
||||||
|
|
||||||
class A4988 : public stepper::Stepper, public Component {
|
class A4988 : public stepper::Stepper, public Component {
|
||||||
public:
|
public:
|
||||||
@@ -24,4 +25,5 @@ class A4988 : public stepper::Stepper, public Component {
|
|||||||
HighFrequencyLoopRequester high_freq_;
|
HighFrequencyLoopRequester high_freq_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::a4988
|
} // namespace a4988
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#include "adalight_light_effect.h"
|
#include "adalight_light_effect.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::adalight {
|
namespace esphome {
|
||||||
|
namespace adalight {
|
||||||
|
|
||||||
static const char *const TAG = "adalight_light_effect";
|
static const char *const TAG = "adalight_light_effect";
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL
|
|||||||
uint8_t *led_data = &frame_[6];
|
uint8_t *led_data = &frame_[6];
|
||||||
|
|
||||||
for (int led = 0; led < accepted_led_count; led++, led_data += 3) {
|
for (int led = 0; led < accepted_led_count; led++, led_data += 3) {
|
||||||
auto white = std::min({led_data[0], led_data[1], led_data[2]});
|
auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]);
|
||||||
|
|
||||||
it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
|
it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
|
||||||
}
|
}
|
||||||
@@ -137,4 +138,5 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL
|
|||||||
return CONSUMED;
|
return CONSUMED;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::adalight
|
} // namespace adalight
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::adalight {
|
namespace esphome {
|
||||||
|
namespace adalight {
|
||||||
|
|
||||||
class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
|
class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
|
||||||
public:
|
public:
|
||||||
@@ -34,4 +35,5 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
|
|||||||
std::vector<uint8_t> frame_;
|
std::vector<uint8_t> frame_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::adalight
|
} // namespace adalight
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
#include <zephyr/drivers/adc.h>
|
#include <zephyr/drivers/adc.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace esphome::adc {
|
namespace esphome {
|
||||||
|
namespace adc {
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@@ -161,4 +162,5 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::adc
|
} // namespace adc
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#include "adc_sensor.h"
|
#include "adc_sensor.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::adc {
|
namespace esphome {
|
||||||
|
namespace adc {
|
||||||
|
|
||||||
static const char *const TAG = "adc.common";
|
static const char *const TAG = "adc.common";
|
||||||
|
|
||||||
@@ -78,4 +79,5 @@ void ADCSensor::set_sample_count(uint8_t sample_count) {
|
|||||||
|
|
||||||
void ADCSensor::set_sampling_mode(SamplingMode sampling_mode) { this->sampling_mode_ = sampling_mode; }
|
void ADCSensor::set_sampling_mode(SamplingMode sampling_mode) { this->sampling_mode_ = sampling_mode; }
|
||||||
|
|
||||||
} // namespace esphome::adc
|
} // namespace adc
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome::adc {
|
namespace esphome {
|
||||||
|
namespace adc {
|
||||||
|
|
||||||
static const char *const TAG = "adc.esp32";
|
static const char *const TAG = "adc.esp32";
|
||||||
|
|
||||||
@@ -363,6 +364,7 @@ float ADCSensor::sample_autorange_() {
|
|||||||
return final_result;
|
return final_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::adc
|
} // namespace adc
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ ADC_MODE(ADC_VCC)
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#endif // USE_ADC_SENSOR_VCC
|
#endif // USE_ADC_SENSOR_VCC
|
||||||
|
|
||||||
namespace esphome::adc {
|
namespace esphome {
|
||||||
|
namespace adc {
|
||||||
|
|
||||||
static const char *const TAG = "adc.esp8266";
|
static const char *const TAG = "adc.esp8266";
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ float ADCSensor::sample() {
|
|||||||
return aggr.aggregate() / 1024.0f;
|
return aggr.aggregate() / 1024.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::adc
|
} // namespace adc
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP8266
|
#endif // USE_ESP8266
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
#include "adc_sensor.h"
|
#include "adc_sensor.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::adc {
|
namespace esphome {
|
||||||
|
namespace adc {
|
||||||
|
|
||||||
static const char *const TAG = "adc.libretiny";
|
static const char *const TAG = "adc.libretiny";
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ float ADCSensor::sample() {
|
|||||||
return aggr.aggregate() / 1000.0f;
|
return aggr.aggregate() / 1000.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::adc
|
} // namespace adc
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_LIBRETINY
|
#endif // USE_LIBRETINY
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
#define PICO_VSYS_PIN 29 // NOLINT(cppcoreguidelines-macro-usage)
|
#define PICO_VSYS_PIN 29 // NOLINT(cppcoreguidelines-macro-usage)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace esphome::adc {
|
namespace esphome {
|
||||||
|
namespace adc {
|
||||||
|
|
||||||
static const char *const TAG = "adc.rp2040";
|
static const char *const TAG = "adc.rp2040";
|
||||||
|
|
||||||
@@ -97,6 +98,7 @@ float ADCSensor::sample() {
|
|||||||
return aggr.aggregate() * 3.3f / 4096.0f * coeff;
|
return aggr.aggregate() * 3.3f / 4096.0f * coeff;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::adc
|
} // namespace adc
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_RP2040
|
#endif // USE_RP2040
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
#include "hal/nrf_saadc.h"
|
#include "hal/nrf_saadc.h"
|
||||||
|
|
||||||
namespace esphome::adc {
|
namespace esphome {
|
||||||
|
namespace adc {
|
||||||
|
|
||||||
static const char *const TAG = "adc.zephyr";
|
static const char *const TAG = "adc.zephyr";
|
||||||
|
|
||||||
@@ -201,5 +202,6 @@ float ADCSensor::sample() {
|
|||||||
return val_mv / 1000.0f;
|
return val_mv / 1000.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::adc
|
} // namespace adc
|
||||||
|
} // namespace esphome
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#include "adc128s102.h"
|
#include "adc128s102.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::adc128s102 {
|
namespace esphome {
|
||||||
|
namespace adc128s102 {
|
||||||
|
|
||||||
static const char *const TAG = "adc128s102";
|
static const char *const TAG = "adc128s102";
|
||||||
|
|
||||||
@@ -27,4 +28,5 @@ uint16_t ADC128S102::read_data(uint8_t channel) {
|
|||||||
return digital_value;
|
return digital_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::adc128s102
|
} // namespace adc128s102
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/components/spi/spi.h"
|
#include "esphome/components/spi/spi.h"
|
||||||
|
|
||||||
namespace esphome::adc128s102 {
|
namespace esphome {
|
||||||
|
namespace adc128s102 {
|
||||||
|
|
||||||
class ADC128S102 : public Component,
|
class ADC128S102 : public Component,
|
||||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||||
@@ -18,4 +19,5 @@ class ADC128S102 : public Component,
|
|||||||
uint16_t read_data(uint8_t channel);
|
uint16_t read_data(uint8_t channel);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::adc128s102
|
} // namespace adc128s102
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::adc128s102 {
|
namespace esphome {
|
||||||
|
namespace adc128s102 {
|
||||||
|
|
||||||
static const char *const TAG = "adc128s102.sensor";
|
static const char *const TAG = "adc128s102.sensor";
|
||||||
|
|
||||||
@@ -17,4 +18,5 @@ void ADC128S102Sensor::dump_config() {
|
|||||||
float ADC128S102Sensor::sample() { return this->parent_->read_data(this->channel_); }
|
float ADC128S102Sensor::sample() { return this->parent_->read_data(this->channel_); }
|
||||||
void ADC128S102Sensor::update() { this->publish_state(this->sample()); }
|
void ADC128S102Sensor::update() { this->publish_state(this->sample()); }
|
||||||
|
|
||||||
} // namespace esphome::adc128s102
|
} // namespace adc128s102
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
#include "../adc128s102.h"
|
#include "../adc128s102.h"
|
||||||
|
|
||||||
namespace esphome::adc128s102 {
|
namespace esphome {
|
||||||
|
namespace adc128s102 {
|
||||||
|
|
||||||
class ADC128S102Sensor : public PollingComponent,
|
class ADC128S102Sensor : public PollingComponent,
|
||||||
public Parented<ADC128S102>,
|
public Parented<ADC128S102>,
|
||||||
@@ -23,4 +24,5 @@ class ADC128S102Sensor : public PollingComponent,
|
|||||||
protected:
|
protected:
|
||||||
uint8_t channel_;
|
uint8_t channel_;
|
||||||
};
|
};
|
||||||
} // namespace esphome::adc128s102
|
} // namespace adc128s102
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#include "addressable_light_display.h"
|
#include "addressable_light_display.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::addressable_light {
|
namespace esphome {
|
||||||
|
namespace addressable_light {
|
||||||
|
|
||||||
static const char *const TAG = "addressable_light.display";
|
static const char *const TAG = "addressable_light.display";
|
||||||
|
|
||||||
@@ -65,4 +66,5 @@ void HOT AddressableLightDisplay::draw_absolute_pixel_internal(int x, int y, Col
|
|||||||
this->addressable_light_buffer_[y * this->get_width_internal() + x] = color;
|
this->addressable_light_buffer_[y * this->get_width_internal() + x] = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace esphome::addressable_light
|
} // namespace addressable_light
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::addressable_light {
|
namespace esphome {
|
||||||
|
namespace addressable_light {
|
||||||
|
|
||||||
class AddressableLightDisplay : public display::DisplayBuffer {
|
class AddressableLightDisplay : public display::DisplayBuffer {
|
||||||
public:
|
public:
|
||||||
@@ -60,4 +61,5 @@ class AddressableLightDisplay : public display::DisplayBuffer {
|
|||||||
optional<uint32_t> last_effect_index_;
|
optional<uint32_t> last_effect_index_;
|
||||||
optional<std::function<int(int, int)>> pixel_mapper_f_;
|
optional<std::function<int(int, int)>> pixel_mapper_f_;
|
||||||
};
|
};
|
||||||
} // namespace esphome::addressable_light
|
} // namespace addressable_light
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome::ade7880 {
|
namespace esphome {
|
||||||
|
namespace ade7880 {
|
||||||
|
|
||||||
static const char *const TAG = "ade7880";
|
static const char *const TAG = "ade7880";
|
||||||
|
|
||||||
@@ -312,4 +313,5 @@ void ADE7880::reset_device_() {
|
|||||||
this->store_.reset_pending = true;
|
this->store_.reset_pending = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::ade7880
|
} // namespace ade7880
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
|
|
||||||
#include "ade7880_registers.h"
|
#include "ade7880_registers.h"
|
||||||
|
|
||||||
namespace esphome::ade7880 {
|
namespace esphome {
|
||||||
|
namespace ade7880 {
|
||||||
|
|
||||||
struct NeutralChannel {
|
struct NeutralChannel {
|
||||||
void set_current(sensor::Sensor *sens) { this->current = sens; }
|
void set_current(sensor::Sensor *sens) { this->current = sens; }
|
||||||
@@ -124,4 +125,5 @@ class ADE7880 : public i2c::I2CDevice, public PollingComponent {
|
|||||||
void write_u32_register16_(uint16_t a_register, uint32_t value);
|
void write_u32_register16_(uint16_t a_register, uint32_t value);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::ade7880
|
} // namespace ade7880
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
|
|
||||||
#include "ade7880.h"
|
#include "ade7880.h"
|
||||||
|
|
||||||
namespace esphome::ade7880 {
|
namespace esphome {
|
||||||
|
namespace ade7880 {
|
||||||
|
|
||||||
// adapted from https://stackoverflow.com/a/55912127/1886371
|
// adapted from https://stackoverflow.com/a/55912127/1886371
|
||||||
template<size_t Bits, typename T> inline T sign_extend(const T &v) noexcept {
|
template<size_t Bits, typename T> inline T sign_extend(const T &v) noexcept {
|
||||||
@@ -96,4 +97,5 @@ void ADE7880::write_u32_register16_(uint16_t a_register, uint32_t value) {
|
|||||||
this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out));
|
this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::ade7880
|
} // namespace ade7880
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
// Source: https://www.analog.com/media/en/technical-documentation/application-notes/AN-1127.pdf
|
// Source: https://www.analog.com/media/en/technical-documentation/application-notes/AN-1127.pdf
|
||||||
|
|
||||||
namespace esphome::ade7880 {
|
namespace esphome {
|
||||||
|
namespace ade7880 {
|
||||||
|
|
||||||
// DSP Data Memory RAM registers
|
// DSP Data Memory RAM registers
|
||||||
constexpr uint16_t AIGAIN = 0x4380;
|
constexpr uint16_t AIGAIN = 0x4380;
|
||||||
@@ -241,4 +242,5 @@ constexpr uint8_t DSPWP_SET_RO = (1 << 7);
|
|||||||
// DSPWP_SEL Register Bits
|
// DSPWP_SEL Register Bits
|
||||||
constexpr uint8_t DSPWP_SEL_SET = 0xAD;
|
constexpr uint8_t DSPWP_SEL_SET = 0xAD;
|
||||||
|
|
||||||
} // namespace esphome::ade7880
|
} // namespace ade7880
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome::ade7953_base {
|
namespace esphome {
|
||||||
|
namespace ade7953_base {
|
||||||
|
|
||||||
static const char *const TAG = "ade7953";
|
static const char *const TAG = "ade7953";
|
||||||
|
|
||||||
@@ -159,4 +160,5 @@ void ADE7953::update() {
|
|||||||
ADE_PUBLISH(frequency, 223750.0f, 1 + val_16);
|
ADE_PUBLISH(frequency, 223750.0f, 1 + val_16);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::ade7953_base
|
} // namespace ade7953_base
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::ade7953_base {
|
namespace esphome {
|
||||||
|
namespace ade7953_base {
|
||||||
|
|
||||||
static constexpr uint8_t PGA_V_8 =
|
static constexpr uint8_t PGA_V_8 =
|
||||||
0x007; // PGA_V, (R/W) Default: 0x00, Unsigned, Voltage channel gain configuration (Bits[2:0])
|
0x007; // PGA_V, (R/W) Default: 0x00, Unsigned, Voltage channel gain configuration (Bits[2:0])
|
||||||
@@ -130,4 +131,5 @@ class ADE7953 : public PollingComponent, public sensor::Sensor {
|
|||||||
virtual bool ade_read_32(uint16_t reg, uint32_t *value) = 0;
|
virtual bool ade_read_32(uint16_t reg, uint32_t *value) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::ade7953_base
|
} // namespace ade7953_base
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::ade7953_i2c {
|
namespace esphome {
|
||||||
|
namespace ade7953_i2c {
|
||||||
|
|
||||||
static const char *const TAG = "ade7953";
|
static const char *const TAG = "ade7953";
|
||||||
|
|
||||||
@@ -75,4 +76,5 @@ bool AdE7953I2c::ade_read_32(uint16_t reg, uint32_t *value) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::ade7953_i2c
|
} // namespace ade7953_i2c
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::ade7953_i2c {
|
namespace esphome {
|
||||||
|
namespace ade7953_i2c {
|
||||||
|
|
||||||
class AdE7953I2c : public ade7953_base::ADE7953, public i2c::I2CDevice {
|
class AdE7953I2c : public ade7953_base::ADE7953, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
@@ -23,4 +24,5 @@ class AdE7953I2c : public ade7953_base::ADE7953, public i2c::I2CDevice {
|
|||||||
bool ade_read_32(uint16_t reg, uint32_t *value) override;
|
bool ade_read_32(uint16_t reg, uint32_t *value) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::ade7953_i2c
|
} // namespace ade7953_i2c
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::ade7953_spi {
|
namespace esphome {
|
||||||
|
namespace ade7953_spi {
|
||||||
|
|
||||||
static const char *const TAG = "ade7953";
|
static const char *const TAG = "ade7953";
|
||||||
|
|
||||||
@@ -82,4 +83,5 @@ bool AdE7953Spi::ade_read_32(uint16_t reg, uint32_t *value) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::ade7953_spi
|
} // namespace ade7953_spi
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::ade7953_spi {
|
namespace esphome {
|
||||||
|
namespace ade7953_spi {
|
||||||
|
|
||||||
class AdE7953Spi : public ade7953_base::ADE7953,
|
class AdE7953Spi : public ade7953_base::ADE7953,
|
||||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
|
||||||
@@ -27,4 +28,5 @@ class AdE7953Spi : public ade7953_base::ADE7953,
|
|||||||
bool ade_read_32(uint16_t reg, uint32_t *value) override;
|
bool ade_read_32(uint16_t reg, uint32_t *value) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::ade7953_spi
|
} // namespace ade7953_spi
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::ads1115 {
|
namespace esphome {
|
||||||
|
namespace ads1115 {
|
||||||
|
|
||||||
static const char *const TAG = "ads1115";
|
static const char *const TAG = "ads1115";
|
||||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
|
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
|
||||||
@@ -207,4 +208,5 @@ float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1
|
|||||||
return millivolts / 1e3f;
|
return millivolts / 1e3f;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::ads1115
|
} // namespace ads1115
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::ads1115 {
|
namespace esphome {
|
||||||
|
namespace ads1115 {
|
||||||
|
|
||||||
enum ADS1115Multiplexer {
|
enum ADS1115Multiplexer {
|
||||||
ADS1115_MULTIPLEXER_P0_N1 = 0b000,
|
ADS1115_MULTIPLEXER_P0_N1 = 0b000,
|
||||||
@@ -59,4 +60,5 @@ class ADS1115Component : public Component, public i2c::I2CDevice {
|
|||||||
bool continuous_mode_;
|
bool continuous_mode_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::ads1115
|
} // namespace ads1115
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::ads1115 {
|
namespace esphome {
|
||||||
|
namespace ads1115 {
|
||||||
|
|
||||||
static const char *const TAG = "ads1115.sensor";
|
static const char *const TAG = "ads1115.sensor";
|
||||||
|
|
||||||
@@ -28,4 +29,5 @@ void ADS1115Sensor::dump_config() {
|
|||||||
this->multiplexer_, this->gain_, this->resolution_, this->samplerate_);
|
this->multiplexer_, this->gain_, this->resolution_, this->samplerate_);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::ads1115
|
} // namespace ads1115
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
#include "../ads1115.h"
|
#include "../ads1115.h"
|
||||||
|
|
||||||
namespace esphome::ads1115 {
|
namespace esphome {
|
||||||
|
namespace ads1115 {
|
||||||
|
|
||||||
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
||||||
class ADS1115Sensor : public sensor::Sensor,
|
class ADS1115Sensor : public sensor::Sensor,
|
||||||
@@ -32,4 +33,5 @@ class ADS1115Sensor : public sensor::Sensor,
|
|||||||
ADS1115Samplerate samplerate_;
|
ADS1115Samplerate samplerate_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::ads1115
|
} // namespace ads1115
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::ads1118 {
|
namespace esphome {
|
||||||
|
namespace ads1118 {
|
||||||
|
|
||||||
static const char *const TAG = "ads1118";
|
static const char *const TAG = "ads1118";
|
||||||
static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111;
|
static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111;
|
||||||
@@ -121,4 +122,5 @@ float ADS1118::request_measurement(ADS1118Multiplexer multiplexer, ADS1118Gain g
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::ads1118
|
} // namespace ads1118
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
namespace esphome::ads1118 {
|
namespace esphome {
|
||||||
|
namespace ads1118 {
|
||||||
|
|
||||||
enum ADS1118Multiplexer {
|
enum ADS1118Multiplexer {
|
||||||
ADS1118_MULTIPLEXER_P0_N1 = 0b000,
|
ADS1118_MULTIPLEXER_P0_N1 = 0b000,
|
||||||
@@ -40,4 +41,5 @@ class ADS1118 : public Component,
|
|||||||
uint16_t config_{0};
|
uint16_t config_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::ads1118
|
} // namespace ads1118
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::ads1118 {
|
namespace esphome {
|
||||||
|
namespace ads1118 {
|
||||||
|
|
||||||
static const char *const TAG = "ads1118.sensor";
|
static const char *const TAG = "ads1118.sensor";
|
||||||
|
|
||||||
@@ -26,4 +27,5 @@ void ADS1118Sensor::update() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::ads1118
|
} // namespace ads1118
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
#include "../ads1118.h"
|
#include "../ads1118.h"
|
||||||
|
|
||||||
namespace esphome::ads1118 {
|
namespace esphome {
|
||||||
|
namespace ads1118 {
|
||||||
|
|
||||||
class ADS1118Sensor : public PollingComponent,
|
class ADS1118Sensor : public PollingComponent,
|
||||||
public sensor::Sensor,
|
public sensor::Sensor,
|
||||||
@@ -31,4 +32,5 @@ class ADS1118Sensor : public PollingComponent,
|
|||||||
bool temperature_mode_;
|
bool temperature_mode_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::ads1118
|
} // namespace ads1118
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome::ags10 {
|
namespace esphome {
|
||||||
|
namespace ags10 {
|
||||||
static const char *const TAG = "ags10";
|
static const char *const TAG = "ags10";
|
||||||
|
|
||||||
// Data acquisition.
|
// Data acquisition.
|
||||||
@@ -191,4 +192,5 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
} // namespace esphome::ags10
|
} // namespace ags10
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
namespace esphome::ags10 {
|
namespace esphome {
|
||||||
|
namespace ags10 {
|
||||||
|
|
||||||
class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
@@ -135,4 +136,5 @@ template<typename... Ts> class AGS10SetZeroPointAction : public Action<Ts...>, p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace esphome::ags10
|
} // namespace ags10
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::aht10 {
|
namespace esphome {
|
||||||
|
namespace aht10 {
|
||||||
|
|
||||||
static const char *const TAG = "aht10";
|
static const char *const TAG = "aht10";
|
||||||
static const uint8_t AHT10_INITIALIZE_CMD[] = {0xE1, 0x08, 0x00};
|
static const uint8_t AHT10_INITIALIZE_CMD[] = {0xE1, 0x08, 0x00};
|
||||||
@@ -159,4 +160,5 @@ void AHT10Component::dump_config() {
|
|||||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::aht10
|
} // namespace aht10
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
namespace esphome::aht10 {
|
namespace esphome {
|
||||||
|
namespace aht10 {
|
||||||
|
|
||||||
enum AHT10Variant { AHT10, AHT20 };
|
enum AHT10Variant { AHT10, AHT20 };
|
||||||
|
|
||||||
@@ -30,4 +31,5 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice {
|
|||||||
uint32_t start_time_{};
|
uint32_t start_time_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::aht10
|
} // namespace aht10
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::aic3204 {
|
namespace esphome {
|
||||||
|
namespace aic3204 {
|
||||||
|
|
||||||
static const char *const TAG = "aic3204";
|
static const char *const TAG = "aic3204";
|
||||||
|
|
||||||
@@ -166,4 +167,5 @@ bool AIC3204::write_volume_() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::aic3204
|
} // namespace aic3204
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user