mirror of
https://github.com/esphome/esphome.git
synced 2026-11-09 14:45:57 +00:00
Merge remote-tracking branch 'origin/dev' into sx1509-max6956-apply-outputs
# Conflicts: # esphome/components/sx1509/sx1509.h
This commit is contained in:
842 files changed
+30368
-5413
No files matched your search
@@ -70,6 +70,21 @@ await automation.build_callback_automation(
|
||||
`build_parent_callback_automation`, `build_trigger_callback`, `build_callback_automations` and
|
||||
`build_trigger_automations` cover the other shapes; see their docstrings in `esphome/automation.py`.
|
||||
|
||||
When every action in a callback automation is inline (see Actions below), no `Automation` or action objects
|
||||
are generated: the callback calls one generated function, at the same RAM cost as a hand-written lambda. A
|
||||
custom forwarder blocks this unless its filter is also given as `params`, `forward` and `when`, so always pass
|
||||
them together:
|
||||
```python
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_IDLE,
|
||||
"add_on_state_callback",
|
||||
forwarder=StateEnterForwarder.template(State.IDLE),
|
||||
params=[(State, "state")],
|
||||
forward=[],
|
||||
when=f"state == {State.IDLE}",
|
||||
)
|
||||
```
|
||||
|
||||
**C++: no trigger class needed.** The callback registration method must be templatized to accept both
|
||||
`std::function` and lightweight forwarder structs (which avoid heap allocation):
|
||||
```cpp
|
||||
@@ -101,6 +116,8 @@ void add_on_state_callback(std::function<void(bool)> &&callback) {
|
||||
|------|----------------------|-------------|
|
||||
| `CallbackManager<void(Ts...)>` | 12 bytes (empty `std::vector`) | Callbacks are always or almost always registered |
|
||||
| `LazyCallbackManager<void(Ts...)>` | 4 bytes (`nullptr`) | Callbacks are often not registered (common case) |
|
||||
| `AutomationList<N, Ts...>` + `build_automation_lists` | 4 bytes per automation + count, none when unused | A hub fires its own `on_*` automations with no filter (4 B per automation vs 8 B per `Callback`) |
|
||||
| `automation.new_automation(args, conf)` + the parent's own fixed table | the table entry (pointer plus its own data) | Like `AutomationList`, but each automation needs a stored value of its own, e.g. a threshold the parent compares against |
|
||||
|
||||
Both live in `esphome/core/helpers.h` and accept `std::function`, lambdas and forwarder structs through
|
||||
their templatized `add()` method. `LazyCallbackManager` is a drop-in replacement for `CallbackManager` that defers allocation until the first
|
||||
@@ -187,6 +204,15 @@ payload passes `automation.ApplyBytes(CONF_DATA, "send({})")` in the same call;
|
||||
flash and a lambda's vector is forwarded as data and size. `TEMPLATABLE_VALUE` with `cg.templatable` stays
|
||||
for actions whose `play()` has real logic beyond forwarding values.
|
||||
|
||||
Adjacent `register_apply_action` and `lambda:` actions in a list merge into one generated function and one
|
||||
object, which saves 12 to 16 bytes of RAM per merged action. An action with its own C++ class ends the merged
|
||||
run, so prefer `register_apply_action` wherever the action allows it. Because of this, `build_action_list` can
|
||||
return fewer objects than the config has entries. A builder that wraps `apply_action_builder` may do other work
|
||||
first, but must return its result unchanged and not use it. A builder that must generate its own statements, and
|
||||
is registered with `automation.ApplyAction`, can call `apply_statements_action` and still merge; `script.execute`
|
||||
is an example. `if`, `choose`, `while` and `repeat` whose bodies are all inline become C++ control flow in the
|
||||
same function, and `register_apply_condition` and `lambda:` conditions become plain expressions there.
|
||||
|
||||
## Conditions
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -41,6 +41,12 @@ performance nit, because it fragments a small shared heap. Flag:
|
||||
literal already lives in flash).
|
||||
- `std::string` / `std::to_string` / string-returning helpers on hot paths where
|
||||
a buffer or view API exists.
|
||||
- A `set_timeout` / `set_interval` / `defer` lambda whose captures exceed 8 bytes
|
||||
on 32-bit or are not trivially copyable (`this` plus two 4-byte values, `this`
|
||||
plus a member function pointer, a `std::string`, a `std::function`):
|
||||
`std::function` then heap-allocates each time it is scheduled. Flag it on
|
||||
retries, re-armed timeouts and hot-path `defer`; a `set_interval` set up once
|
||||
in `setup()` is a single allocation. `[this]` plus one 4-byte value fits.
|
||||
|
||||
## C++ conventions
|
||||
|
||||
@@ -69,6 +75,27 @@ performance nit, because it fragments a small shared heap. Flag:
|
||||
typed as `std::function`, so lightweight forwarders avoid a heap allocation.
|
||||
- Two-space indent, `using` over `typedef`, wrap at 120 columns.
|
||||
|
||||
## Use the existing helper
|
||||
|
||||
New components often hand-write code a helper already covers. Ask for the
|
||||
helper; `AGENTS.md` section 4 and the `component-codegen` skill have the detail.
|
||||
|
||||
| Seen in the PR | Ask for |
|
||||
| --- | --- |
|
||||
| `if (this->x_sensor_ != nullptr) this->x_sensor_->publish_state(v);` | `PUBLISH_STATE(this->x_sensor_, v);` (`esphome/core/entity_base.h`) |
|
||||
| set warning / `return` / clear warning around a bus result | `if (!this->status_comm_result(ok)) return;` |
|
||||
| hand-written `set_x_sensor()` plus `x_sensor_` member | `SUB_SENSOR(x)`, `SUB_BINARY_SENSOR`, `SUB_TEXT_SENSOR`, `SUB_SWITCH`, ... |
|
||||
| `if CONF_X in config: s = await sensor.new_sensor(config[CONF_X])` plus setter | `sensors = sensor.sub_sensors(config)` then `await sensors(CONF_X, hub.set_x_sensor)` |
|
||||
| hand-written `{"a": MyEnum.MY_ENUM_A, ...}` dict | `MyEnum.options("a", "b", prefix="MY_ENUM_")` |
|
||||
| loops on `available()` / `read_byte()` | UART `read_available(on_byte)`, `discard_available()`, `LineReader<N>`, `FrameReader<N, HEADER...>` |
|
||||
| hand-rolled CRC, checksum or bit loop | `crc8`, `crc16`, `xor_bytes`, `sum16_bytes`, `encode_uint16`, ... (`esphome/core/helpers.h`) |
|
||||
| integer split into buffer bytes by hand (`buf[0] = v >> 8; buf[1] = v;`) | `put_be16`/`24`/`32`, `put_le16`, `put_le32` (`esphome/core/helpers.h`) |
|
||||
| bare `mark_failed()`, or `ESP_LOGE` then `mark_failed()` | `this->mark_failed(LOG_STR("reason"))` or `LOG_STR(ESP_LOG_MSG_COMM_FAIL)` |
|
||||
| button/switch/number/select C++ class that only calls the hub | `ApplyButton`, `ApplySwitch`, `ApplyNumber`, `ApplySelect` with `apply=` |
|
||||
| action or condition class that only forwards to the parent | `register_apply_action` / `register_apply_condition` |
|
||||
| enum-to-string `switch` returning literals | `PROGMEM_STRING_TABLE` with `get_log_str` / `get_progmem_str` |
|
||||
| `if (x := config.get(CONF_X)) is not None: cg.add(var.set_x(x))` | `cg.add_optional(var.set_x, config.get(CONF_X))`, when the setter takes the value unchanged |
|
||||
|
||||
## Python conventions
|
||||
|
||||
- Type-annotate every new function signature (params and return), new dataclass
|
||||
|
||||
@@ -45,7 +45,7 @@ runs:
|
||||
id: apt
|
||||
# Pinned on purpose and not watched by Dependabot: the step above relies on how this
|
||||
# release calls apt-fast and where it writes install.log.
|
||||
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.3
|
||||
with:
|
||||
packages: libsdl2-dev libmbedtls-dev ccache
|
||||
# 1.3 drops the empty caches saved before the step above existed
|
||||
|
||||
@@ -42,7 +42,7 @@ runs:
|
||||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
||||
uses: docker/build-push-action@v7.4.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
@@ -67,7 +67,7 @@ runs:
|
||||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
||||
uses: docker/build-push-action@v7.4.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
||||
@@ -27,13 +27,13 @@ runs:
|
||||
echo "key=$key" >> "$GITHUB_OUTPUT"
|
||||
- name: Cache the native toolchain (write on dev)
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache@v6.1.0
|
||||
with:
|
||||
path: ${{ env.ESPHOME_ARDUINO8266_PREFIX }}
|
||||
key: ${{ runner.os }}-esp8266-native-${{ steps.version.outputs.key }}
|
||||
- name: Restore the native toolchain (off dev)
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: ${{ env.ESPHOME_ARDUINO8266_PREFIX }}
|
||||
key: ${{ runner.os }}-esp8266-native-${{ steps.version.outputs.key }}
|
||||
@@ -38,13 +38,13 @@ runs:
|
||||
# Extend the extension list if a component ships extensionless headers.
|
||||
- name: Cache clang-tidy idedata (write on dev)
|
||||
if: (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write')) && steps.key.outputs.skip != 'true'
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache@v6.1.0
|
||||
with:
|
||||
path: ${{ steps.key.outputs.path }}
|
||||
key: ${{ steps.key.outputs.key }}
|
||||
- name: Cache clang-tidy idedata (restore-only off dev)
|
||||
if: github.ref != 'refs/heads/dev' && !contains(github.event.pull_request.labels.*.name, 'ci-cache-write') && steps.key.outputs.skip != 'true'
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: ${{ steps.key.outputs.path }}
|
||||
key: ${{ steps.key.outputs.key }}
|
||||
@@ -49,13 +49,13 @@ runs:
|
||||
# -slim: bump when prune-esp-idf changes what it removes; a key is never overwritten.
|
||||
- name: Cache ESP-IDF install (write on dev)
|
||||
if: (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write')) && inputs.restore-only != 'true'
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@v5.0.5
|
||||
with:
|
||||
path: ~/.esphome-idf
|
||||
key: ${{ runner.os }}-esphome-idf-${{ steps.version.outputs.version }}-py${{ steps.version.outputs.python-version }}-slim
|
||||
- name: Cache ESP-IDF install (restore-only off dev)
|
||||
if: github.ref != 'refs/heads/dev' && !contains(github.event.pull_request.labels.*.name, 'ci-cache-write') || inputs.restore-only == 'true'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@v5.0.5
|
||||
with:
|
||||
path: ~/.esphome-idf
|
||||
key: ${{ runner.os }}-esphome-idf-${{ steps.version.outputs.version }}-py${{ steps.version.outputs.python-version }}-slim
|
||||
@@ -67,4 +67,4 @@ runs:
|
||||
. venv/bin/activate
|
||||
python -c 'from esphome.espidf.framework import check_esp_idf_install; check_esp_idf_install("${{ steps.version.outputs.version }}")'
|
||||
- name: Prune ESP-IDF install
|
||||
uses: ./.github/actions/prune-esp-idf
|
||||
uses: $/.github/actions/prune-esp-idf
|
||||
@@ -35,14 +35,14 @@ runs:
|
||||
# restore-only and never push multi-GB artifacts into their own scope.
|
||||
- name: Cache nRF Connect SDK install (write on dev)
|
||||
if: github.ref == 'refs/heads/dev' && inputs.restore-only != 'true'
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache@v6.1.0
|
||||
with:
|
||||
path: ~/.esphome-sdk-nrf
|
||||
# yamllint disable-line rule:line-length
|
||||
key: ${{ runner.os }}-esphome-sdk-nrf-${{ steps.version.outputs.version }}-${{ hashFiles('esphome/components/nrf52/requirements.txt') }}
|
||||
- name: Cache nRF Connect SDK install (restore-only off dev)
|
||||
if: github.ref != 'refs/heads/dev' || inputs.restore-only == 'true'
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: ~/.esphome-sdk-nrf
|
||||
# yamllint disable-line rule:line-length
|
||||
|
||||
@@ -17,12 +17,12 @@ runs:
|
||||
steps:
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
@@ -32,7 +32,7 @@ runs:
|
||||
# detects the activated venv via ``VIRTUAL_ENV`` so the venv layout
|
||||
# downstream jobs rely on is preserved.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
||||
uses: astral-sh/setup-uv@v10.3.3
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
# This file is machine-generated by `gh actions-lock`.
|
||||
# Do not edit by hand; run `gh actions-lock` to update.
|
||||
# Docs: https://gh.io/actions-lockfile
|
||||
version: 'v0.0.3'
|
||||
workflows:
|
||||
'.github/workflows/auto-label-pr.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/create-github-app-token@v3.3.0'
|
||||
- 'actions/github-script@v9.0.0'
|
||||
'.github/workflows/ci-api-proto.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/github-script@v9.0.0'
|
||||
- 'actions/setup-python@v7.1.0'
|
||||
- 'actions/upload-artifact@v7.0.2'
|
||||
- 'astral-sh/setup-uv@v10.3.3'
|
||||
'.github/workflows/ci-docker.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/download-artifact@v8.0.2'
|
||||
- 'actions/setup-python@v7.1.0'
|
||||
- 'actions/upload-artifact@v7.0.2'
|
||||
- 'docker/login-action@v4.7.0'
|
||||
- 'docker/setup-buildx-action@v4.4.1'
|
||||
'.github/workflows/ci-github-scripts.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
'.github/workflows/ci-memory-impact-comment.yml':
|
||||
- 'actions/cache@v6.1.0'
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/setup-python@v7.1.0'
|
||||
- 'astral-sh/setup-uv@v10.3.3'
|
||||
'.github/workflows/ci.yml':
|
||||
- 'actions/cache@v5.0.5'
|
||||
- 'actions/cache@v6.1.0'
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/download-artifact@v8.0.2'
|
||||
- 'actions/setup-python@v7.1.0'
|
||||
- 'actions/upload-artifact@v7.0.2'
|
||||
- 'astral-sh/setup-uv@v10.3.3'
|
||||
- 'awalsh128/cache-apt-pkgs-action@v1.6.3'
|
||||
- 'codecov/codecov-action@v7.1.1'
|
||||
- 'codspeedhq/action@v5.6.1'
|
||||
- 'j178/prek-action@v3.1.0'
|
||||
- 'pre-commit-ci/lite-action@v1.2.0'
|
||||
'.github/workflows/close-pr-from-fork-default-branch.yml':
|
||||
- 'actions/github-script@v9.0.0'
|
||||
'.github/workflows/codeowner-approved-label-update.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/github-script@v9.0.0'
|
||||
'.github/workflows/codeowner-review-request.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/create-github-app-token@v3.3.0'
|
||||
- 'actions/github-script@v9.0.0'
|
||||
'.github/workflows/codeql.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'github/codeql-action@v4.39.1'
|
||||
'.github/workflows/external-component-bot.yml':
|
||||
- 'actions/create-github-app-token@v3.3.0'
|
||||
- 'actions/github-script@v9.0.0'
|
||||
'.github/workflows/issue-codeowner-notify.yml':
|
||||
- 'actions/github-script@v9.0.0'
|
||||
'.github/workflows/lock.yml': []
|
||||
'.github/workflows/pr-title-check.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/github-script@v9.0.0'
|
||||
'.github/workflows/release-nightly.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
'.github/workflows/release.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/create-github-app-token@v3.3.0'
|
||||
- 'actions/download-artifact@v8.0.2'
|
||||
- 'actions/github-script@v9.0.0'
|
||||
- 'actions/setup-python@v7.1.0'
|
||||
- 'actions/upload-artifact@v7.0.2'
|
||||
- 'docker/build-push-action@v7.4.0'
|
||||
- 'docker/login-action@v4.7.0'
|
||||
- 'docker/setup-buildx-action@v4.4.1'
|
||||
- 'pypa/gh-action-pypi-publish@v1.14.3'
|
||||
'.github/workflows/stale.yml': []
|
||||
'.github/workflows/status-check-labels.yml':
|
||||
- 'actions/github-script@v9.0.0'
|
||||
'.github/workflows/sync-dependency-versions.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/create-github-app-token@v3.3.0'
|
||||
- 'actions/setup-python@v7.1.0'
|
||||
'.github/workflows/sync-device-classes.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/create-github-app-token@v3.3.0'
|
||||
- 'actions/setup-python@v7.1.0'
|
||||
- 'astral-sh/setup-uv@v10.3.3'
|
||||
- 'peter-evans/create-pull-request@v8.1.1'
|
||||
'.github/workflows/sync-integration-durations.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
- 'actions/create-github-app-token@v3.3.0'
|
||||
- 'actions/setup-python@v7.1.0'
|
||||
- 'peter-evans/create-pull-request@v8.1.1'
|
||||
'.github/workflows/verify-lockfile.yml':
|
||||
- 'actions/checkout@v7.0.1'
|
||||
dependencies:
|
||||
'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9':
|
||||
ref: 'v6.1.0'
|
||||
commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9'
|
||||
owner_id: 44036562
|
||||
repo_id: 215566462
|
||||
'actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7':
|
||||
ref: 'v5.0.4'
|
||||
commit: 'sha1-668228422ae6a00e4ad889ee87cd7109ec5666a7'
|
||||
owner_id: 44036562
|
||||
repo_id: 215566462
|
||||
'actions/cache@v5.0.5':
|
||||
ref: 'v5.0.5'
|
||||
commit: 'sha1-27d5ce7f107fe9357f9df03efb73ab90386fccae'
|
||||
owner_id: 44036562
|
||||
repo_id: 215566462
|
||||
'actions/cache@v6.1.0':
|
||||
ref: 'v6.1.0'
|
||||
commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9'
|
||||
owner_id: 44036562
|
||||
repo_id: 215566462
|
||||
'actions/checkout@v7.0.1':
|
||||
ref: 'v7.0.1'
|
||||
commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1'
|
||||
owner_id: 44036562
|
||||
repo_id: 197814629
|
||||
'actions/create-github-app-token@v3.3.0':
|
||||
ref: 'v3.3.0'
|
||||
commit: 'sha1-64ab729a6294000f5aa4a4bc0b73d1f32bc45510'
|
||||
owner_id: 44036562
|
||||
repo_id: 642580244
|
||||
'actions/download-artifact@v8.0.2':
|
||||
ref: 'v8.0.2'
|
||||
commit: 'sha1-9000827ccba6bdab643e8b6fd33ac0654aef8333'
|
||||
owner_id: 44036562
|
||||
repo_id: 192626254
|
||||
'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd':
|
||||
ref: 'v8.0.0'
|
||||
commit: 'sha1-ed597411d8f924073f98dfc5c65a23a2325f34cd'
|
||||
owner_id: 44036562
|
||||
repo_id: 205262760
|
||||
'actions/github-script@v9.0.0':
|
||||
ref: 'v9.0.0'
|
||||
commit: 'sha1-3a2844b7e9c422d3c10d287c895573f7108da1b3'
|
||||
owner_id: 44036562
|
||||
repo_id: 205262760
|
||||
'actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405':
|
||||
ref: 'v6.2.0'
|
||||
commit: 'sha1-a309ff8b426b58ec0e2a45f0f869d46889d02405'
|
||||
owner_id: 44036562
|
||||
repo_id: 192625525
|
||||
'actions/setup-python@v7.1.0':
|
||||
ref: 'v7.1.0'
|
||||
commit: 'sha1-6d4fd22b20465ccc0f7354476588e8df7c9ca0e0'
|
||||
owner_id: 44036562
|
||||
repo_id: 192625525
|
||||
'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f':
|
||||
ref: 'v7.0.0'
|
||||
commit: 'sha1-bbbca2ddaa5d8feaa63e36b76fdaad77386f024f'
|
||||
owner_id: 44036562
|
||||
repo_id: 192625955
|
||||
'actions/upload-artifact@v7.0.2':
|
||||
ref: 'v7.0.2'
|
||||
commit: 'sha1-cf430e030ddbb5b0abf93d22962f4752f3646cd9'
|
||||
owner_id: 44036562
|
||||
repo_id: 192625955
|
||||
'astral-sh/setup-uv@v10.3.3':
|
||||
ref: 'v10.3.3'
|
||||
commit: 'sha1-68559e99cc807c1eb7e9238fd43a15bba460eec2'
|
||||
owner_id: 115962839
|
||||
repo_id: 846733598
|
||||
'awalsh128/cache-apt-pkgs-action@v1.6.3':
|
||||
ref: 'v1.6.3'
|
||||
commit: 'sha1-553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9'
|
||||
owner_id: 2087466
|
||||
repo_id: 408212054
|
||||
uses:
|
||||
- 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9'
|
||||
- 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f'
|
||||
'codecov/codecov-action@v7.1.1':
|
||||
ref: 'v7.1.1'
|
||||
commit: 'sha1-303a32d7a59b442fa8d48b6a1cc6825c09c847a5'
|
||||
owner_id: 8226205
|
||||
repo_id: 200299178
|
||||
uses:
|
||||
- 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd'
|
||||
'codspeedhq/action@v5.6.1':
|
||||
ref: 'v5.6.1'
|
||||
commit: 'sha1-61efe07e57e55bedeab750c434504de272a56e8c'
|
||||
owner_id: 116658140
|
||||
repo_id: 561699778
|
||||
uses:
|
||||
- 'actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7'
|
||||
- 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd'
|
||||
'docker/build-push-action@v7.4.0':
|
||||
ref: 'v7.4.0'
|
||||
commit: 'sha1-c3c9e263c25d99ce0380d002d59b67737d91b0dc'
|
||||
owner_id: 5429470
|
||||
repo_id: 241092383
|
||||
'docker/login-action@v4.7.0':
|
||||
ref: 'v4.7.0'
|
||||
commit: 'sha1-81c6f93d4bc991e510b342a00b419b3bb4efc5c6'
|
||||
owner_id: 5429470
|
||||
repo_id: 287743349
|
||||
'docker/setup-buildx-action@v4.4.1':
|
||||
ref: 'v4.4.1'
|
||||
commit: 'sha1-f87e5991a6d7451dcb8d9637bfbc97413f497069'
|
||||
owner_id: 5429470
|
||||
repo_id: 288485773
|
||||
'github/codeql-action@v4.39.1':
|
||||
ref: 'v4.39.1'
|
||||
commit: 'sha1-9b3254085494b55a0f7c4c2b3d2d52cc712ce370'
|
||||
owner_id: 9919
|
||||
repo_id: 259445878
|
||||
'j178/prek-action@v3.1.0':
|
||||
ref: 'v3.1.0'
|
||||
commit: 'sha1-44924fe9ef4a6f88253c4b6b1bf11c689d3ce53d'
|
||||
owner_id: 10510431
|
||||
repo_id: 1052387486
|
||||
'peter-evans/create-pull-request@v8.1.1':
|
||||
ref: 'v8.1.1'
|
||||
commit: 'sha1-5f6978faf089d4d20b00c7766989d076bb2fc7f1'
|
||||
owner_id: 18365890
|
||||
repo_id: 197176365
|
||||
'pre-commit-ci/lite-action@v1.2.0':
|
||||
ref: 'v1.2.0'
|
||||
commit: 'sha1-062bca0919bc9d6e66755cc05074b70c77e111fc'
|
||||
owner_id: 64617429
|
||||
repo_id: 560608915
|
||||
'pypa/gh-action-pypi-publish@v1.14.3':
|
||||
ref: 'v1.14.3'
|
||||
commit: 'sha1-7245906fb61fa33a13554f9d5fb856fe61758086'
|
||||
owner_id: 647025
|
||||
repo_id: 178055147
|
||||
uses:
|
||||
- 'actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405'
|
||||
@@ -24,11 +24,11 @@ jobs:
|
||||
if: github.event.pull_request.state == 'open' && (github.event.action != 'labeled' || github.event.sender.type != 'Bot')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
uses: actions/create-github-app-token@v3.3.0
|
||||
with:
|
||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
permission-pull-requests: write # pulls.get, pulls.listFiles, list/create/update/dismissReview
|
||||
|
||||
- name: Auto Label PR
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@v9.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
|
||||
@@ -21,15 +21,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- 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@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
||||
uses: astral-sh/setup-uv@v10.3.3
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull-request-only workflow: a save could never be shared and
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
fi
|
||||
- if: failure()
|
||||
name: Review PR
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@v9.0.0
|
||||
with:
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
run: git diff
|
||||
- if: failure()
|
||||
name: Archive artifacts
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@v7.0.2
|
||||
with:
|
||||
name: generated-proto-files
|
||||
path: |
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
esphome/components/api/api_pb2_service.*
|
||||
- if: success()
|
||||
name: Dismiss review
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@v9.0.0
|
||||
with:
|
||||
script: |
|
||||
let reviews = await github.rest.pulls.listReviews({
|
||||
|
||||
@@ -60,13 +60,13 @@ jobs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
push: ${{ steps.tag.outputs.push }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
||||
uses: docker/setup-buildx-action@v4.4.1
|
||||
|
||||
- name: Determine tag and whether to push
|
||||
id: tag
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
|
||||
- name: Log in to the GitHub container registry
|
||||
if: steps.tag.outputs.push == 'true'
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
|
||||
uses: docker/login-action@v4.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
|
||||
- name: Upload compile-test image artifact
|
||||
if: matrix.os == 'ubuntu-24.04' && matrix.build_type == 'docker'
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@v7.0.2
|
||||
with:
|
||||
# The tar is already compressed, so upload it as-is. archive: false
|
||||
# skips the redundant zip and makes the file name the artifact name
|
||||
@@ -152,16 +152,16 @@ jobs:
|
||||
- "ha-addon"
|
||||
- "docker"
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
||||
uses: docker/setup-buildx-action@v4.4.1
|
||||
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
|
||||
uses: docker/login-action@v4.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -208,9 +208,9 @@ jobs:
|
||||
- nrf52
|
||||
- host
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Download image artifact
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
uses: actions/download-artifact@v8.0.2
|
||||
with:
|
||||
name: compile-test-image.tar.zst
|
||||
- name: Load image
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Run tests
|
||||
working-directory: .github/scripts/auto-label-pr
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Check out code from base repository
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
# Always check out from the base repository (esphome/esphome), never from forks
|
||||
# Use the PR's target branch to ensure we run trusted code from the main repo
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Restore Python
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache-key: ${{ hashFiles('.cache-key') }}
|
||||
|
||||
+104
-104
@@ -28,18 +28,18 @@ jobs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_dev.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache@v6.1.0
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
# detects the activated venv via ``VIRTUAL_ENV`` so downstream jobs
|
||||
# that ``. venv/bin/activate`` see an identical layout.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
||||
uses: astral-sh/setup-uv@v10.3.3
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
@@ -79,11 +79,11 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- name: Install apt packages (cached)
|
||||
uses: ./.github/actions/apt-packages
|
||||
uses: $/.github/actions/apt-packages
|
||||
|
||||
determine-jobs:
|
||||
name: Determine which jobs to run
|
||||
@@ -125,17 +125,17 @@ jobs:
|
||||
release-pr: ${{ github.base_ref == 'beta' || github.base_ref == 'release' }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
# Fetch enough history to find the merge base
|
||||
fetch-depth: 2
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Restore components graph cache
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: .temp/components_graph.json
|
||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||
@@ -185,7 +185,7 @@ jobs:
|
||||
echo "benchmarks=$(echo "$output" | jq -r '.benchmarks')" >> $GITHUB_OUTPUT
|
||||
- name: Save components graph cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/save@v6.1.0
|
||||
with:
|
||||
path: .temp/components_graph.json
|
||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||
@@ -204,14 +204,14 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache the native toolchain
|
||||
uses: ./.github/actions/cache-arduino8266
|
||||
uses: $/.github/actions/cache-arduino8266
|
||||
- name: Install the native toolchain
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -226,9 +226,9 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.core-ci == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
@@ -255,9 +255,9 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
@@ -277,7 +277,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && needs.determine-jobs.outputs.release-pr == 'false' && needs.determine-jobs.outputs.core-ci == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Read prek version from requirements_test.txt
|
||||
id: prek
|
||||
# requirements_test.txt is the only place the version is pinned, so a
|
||||
@@ -289,7 +289,7 @@ jobs:
|
||||
fi
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
- name: Run prek
|
||||
uses: j178/prek-action@44924fe9ef4a6f88253c4b6b1bf11c689d3ce53d # v3.1.0
|
||||
uses: j178/prek-action@v3.1.0
|
||||
with:
|
||||
prek-version: ${{ steps.prek.outputs.version }}
|
||||
# This job only runs on pull requests, so nothing ever populates
|
||||
@@ -302,7 +302,7 @@ jobs:
|
||||
# Pushes any fixes the hooks made back to the pull request. This step
|
||||
# must keep its default name: the GitHub App that performs the push
|
||||
# locates the workflow run by that name.
|
||||
- uses: pre-commit-ci/lite-action@062bca0919bc9d6e66755cc05074b70c77e111fc # v1.2.0
|
||||
- uses: pre-commit-ci/lite-action@v1.2.0
|
||||
if: always()
|
||||
with:
|
||||
msg: apply automatic formatting fixes
|
||||
@@ -336,10 +336,10 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.core-ci == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Restore Python
|
||||
id: restore-python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
@@ -356,10 +356,10 @@ jobs:
|
||||
. venv/bin/activate
|
||||
pytest -vv --cov-report=xml --tb=native --durations=30 -n auto tests --ignore=tests/integration/
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
||||
uses: codecov/codecov-action@v7.1.1
|
||||
- name: Save Python virtual environment cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/save@v6.1.0
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
@@ -383,9 +383,9 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.core-ci == 'false'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Report empty upload to Codecov
|
||||
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
||||
uses: codecov/codecov-action@v7.1.1
|
||||
with:
|
||||
run_command: empty-upload
|
||||
force: true
|
||||
@@ -416,23 +416,23 @@ jobs:
|
||||
INTEGRATION_CCACHE_SAVE: ${{ needs.determine-jobs.outputs.integration-run-all == 'true' && (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write')) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Install apt packages (cached)
|
||||
# ccache speeds up the host compiles. A cache hit never touches apt
|
||||
# (mirror outages cannot hang the job); the timeout bounds the cold
|
||||
# path. libsdl2-dev is needed by the headless display tests, which
|
||||
# capture screenshots.
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/apt-packages
|
||||
uses: $/.github/actions/apt-packages
|
||||
- name: Set up Python 3.13
|
||||
id: python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Restore integration library cache
|
||||
# A miss or a changed pin self-heals with a download, so no restore-keys
|
||||
id: library-cache
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: ${{ env.INTEGRATION_LIBRARY_CACHE_PATH }}
|
||||
key: integration-libraries-v1-${{ runner.os }}-${{ hashFiles('esphome/components/json/__init__.py', 'esphome/components/noise/__init__.py', 'esphome/components/lvgl/__init__.py', 'esphome/components/improv_base/__init__.py') }}
|
||||
@@ -441,35 +441,35 @@ jobs:
|
||||
# per slice of INTEGRATION_TESTS_SPLIT_BUCKETS in script/determine-jobs.py.
|
||||
- name: Restore integration ccache (slice 1/5)
|
||||
if: needs.determine-jobs.outputs.integration-run-all != 'true' || strategy.job-index == 0
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: ${{ env.INTEGRATION_CCACHE_PATH }}
|
||||
key: integration-ccache-v1-0-${{ github.run_id }}
|
||||
restore-keys: integration-ccache-v1-0-
|
||||
- name: Restore integration ccache (slice 2/5)
|
||||
if: needs.determine-jobs.outputs.integration-run-all != 'true' || strategy.job-index == 1
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: ${{ env.INTEGRATION_CCACHE_PATH }}
|
||||
key: integration-ccache-v1-1-${{ github.run_id }}
|
||||
restore-keys: integration-ccache-v1-1-
|
||||
- name: Restore integration ccache (slice 3/5)
|
||||
if: needs.determine-jobs.outputs.integration-run-all != 'true' || strategy.job-index == 2
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: ${{ env.INTEGRATION_CCACHE_PATH }}
|
||||
key: integration-ccache-v1-2-${{ github.run_id }}
|
||||
restore-keys: integration-ccache-v1-2-
|
||||
- name: Restore integration ccache (slice 4/5)
|
||||
if: needs.determine-jobs.outputs.integration-run-all != 'true' || strategy.job-index == 3
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: ${{ env.INTEGRATION_CCACHE_PATH }}
|
||||
key: integration-ccache-v1-3-${{ github.run_id }}
|
||||
restore-keys: integration-ccache-v1-3-
|
||||
- name: Restore integration ccache (slice 5/5)
|
||||
if: needs.determine-jobs.outputs.integration-run-all != 'true' || strategy.job-index == 4
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: ${{ env.INTEGRATION_CCACHE_PATH }}
|
||||
key: integration-ccache-v1-4-${{ github.run_id }}
|
||||
@@ -486,14 +486,14 @@ jobs:
|
||||
CCACHE_DIR="$dir" ccache -z
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache@v6.1.0
|
||||
with:
|
||||
path: venv
|
||||
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@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
||||
uses: astral-sh/setup-uv@v10.3.3
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
@@ -560,7 +560,7 @@ jobs:
|
||||
# Tests that compare rendered output write the image they actually got here, so a
|
||||
# failure can be looked at without reproducing the whole build locally.
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@v7.0.2
|
||||
with:
|
||||
name: integration-test-artifacts-${{ matrix.bucket.name }}
|
||||
path: test_artifacts/
|
||||
@@ -571,7 +571,7 @@ jobs:
|
||||
# script/update_integration_test_durations.py; only full matrix dev
|
||||
# runs produce usable data.
|
||||
if: github.ref == 'refs/heads/dev' && needs.determine-jobs.outputs.integration-run-all == 'true'
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@v7.0.2
|
||||
with:
|
||||
name: junit-integration-${{ strategy.job-index }}
|
||||
path: junit-integration.xml
|
||||
@@ -587,14 +587,14 @@ jobs:
|
||||
run: ccache -s
|
||||
- name: Save integration ccache slice
|
||||
if: success() && env.INTEGRATION_CCACHE_SAVE == 'true'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/save@v6.1.0
|
||||
with:
|
||||
path: ${{ env.INTEGRATION_CCACHE_PATH }}
|
||||
key: integration-ccache-v1-${{ strategy.job-index }}-${{ github.run_id }}
|
||||
- name: Save integration library cache
|
||||
# Bucket 0 only; the others would race the same immutable key.
|
||||
if: success() && (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write')) && strategy.job-index == 0 && steps.library-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/save@v6.1.0
|
||||
with:
|
||||
path: ${{ env.INTEGRATION_LIBRARY_CACHE_PATH }}
|
||||
key: ${{ steps.library-cache.outputs.cache-primary-key }}
|
||||
@@ -643,9 +643,9 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.import-time == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
@@ -655,7 +655,7 @@ jobs:
|
||||
script/check_import_time.py --check --har importtime.har
|
||||
- name: Upload waterfall HAR
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@v7.0.2
|
||||
with:
|
||||
name: import-time-waterfall
|
||||
path: importtime.har
|
||||
@@ -689,10 +689,10 @@ jobs:
|
||||
# have already been benchmarked on their original dev pull requests.
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
@@ -753,7 +753,7 @@ jobs:
|
||||
apt-get install -y libc6-dbg
|
||||
|
||||
- name: Run CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@78102a468f96d8e0ccb92d0ec49425753fc42062 # v5.6.0
|
||||
uses: CodSpeedHQ/action@v5.6.1
|
||||
with:
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -770,10 +770,10 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
@@ -834,7 +834,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -843,11 +843,11 @@ jobs:
|
||||
# libsdl2-dev is needed by the sdl component on the host, libmbedtls-dev
|
||||
# by dsmr and dlms_meter.
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/apt-packages
|
||||
uses: $/.github/actions/apt-packages
|
||||
|
||||
- name: Restore Python
|
||||
id: restore-python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
@@ -858,33 +858,33 @@ jobs:
|
||||
# a new image ships a newer patch release and drops the old interpreter.
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev' && matrix.pio_cache_key
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache@v6.1.0
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ steps.restore-python.outputs.python-version }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev' && matrix.pio_cache_key
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ steps.restore-python.outputs.python-version }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache ESP-IDF install
|
||||
if: matrix.cache_idf
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
uses: $/.github/actions/cache-esp-idf
|
||||
with:
|
||||
framework: arduino
|
||||
|
||||
- name: Cache clang-tidy idedata
|
||||
if: matrix.cache_idf
|
||||
uses: ./.github/actions/cache-clang-tidy-idedata
|
||||
uses: $/.github/actions/cache-clang-tidy-idedata
|
||||
with:
|
||||
environment: esp32-arduino-tidy
|
||||
|
||||
- name: Cache nRF Connect SDK install
|
||||
if: matrix.cache_sdk_nrf
|
||||
uses: ./.github/actions/cache-sdk-nrf
|
||||
uses: $/.github/actions/cache-sdk-nrf
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
@@ -933,7 +933,7 @@ jobs:
|
||||
|
||||
- name: Prune ESP-IDF install before cache save
|
||||
if: matrix.cache_idf && (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write'))
|
||||
uses: ./.github/actions/prune-esp-idf
|
||||
uses: $/.github/actions/prune-esp-idf
|
||||
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes ${{ matrix.ignore_errors && '|| true' || '' }}
|
||||
@@ -953,22 +953,22 @@ jobs:
|
||||
ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache ESP-IDF install
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
uses: $/.github/actions/cache-esp-idf
|
||||
|
||||
- name: Cache clang-tidy idedata
|
||||
uses: ./.github/actions/cache-clang-tidy-idedata
|
||||
uses: $/.github/actions/cache-clang-tidy-idedata
|
||||
with:
|
||||
environment: esp32-idf-tidy
|
||||
|
||||
@@ -1008,7 +1008,7 @@ jobs:
|
||||
|
||||
- name: Prune ESP-IDF install before cache save
|
||||
if: (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write'))
|
||||
uses: ./.github/actions/prune-esp-idf
|
||||
uses: $/.github/actions/prune-esp-idf
|
||||
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
@@ -1047,22 +1047,22 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache ESP-IDF install
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
uses: $/.github/actions/cache-esp-idf
|
||||
|
||||
- name: Cache clang-tidy idedata
|
||||
uses: ./.github/actions/cache-clang-tidy-idedata
|
||||
uses: $/.github/actions/cache-clang-tidy-idedata
|
||||
with:
|
||||
environment: esp32-idf-tidy
|
||||
|
||||
@@ -1102,7 +1102,7 @@ jobs:
|
||||
|
||||
- name: Prune ESP-IDF install before cache save
|
||||
if: (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write'))
|
||||
uses: ./.github/actions/prune-esp-idf
|
||||
uses: $/.github/actions/prune-esp-idf
|
||||
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
@@ -1143,22 +1143,22 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache ESP-IDF install
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
uses: $/.github/actions/cache-esp-idf
|
||||
|
||||
- name: Cache clang-tidy idedata
|
||||
uses: ./.github/actions/cache-clang-tidy-idedata
|
||||
uses: $/.github/actions/cache-clang-tidy-idedata
|
||||
with:
|
||||
environment: ${{ matrix.tidy_environment }}
|
||||
|
||||
@@ -1197,7 +1197,7 @@ jobs:
|
||||
|
||||
- name: Prune ESP-IDF install before cache save
|
||||
if: (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write'))
|
||||
uses: ./.github/actions/prune-esp-idf
|
||||
uses: $/.github/actions/prune-esp-idf
|
||||
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
@@ -1231,7 +1231,7 @@ jobs:
|
||||
run: echo ${{ matrix.batch.components }}
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Install apt packages (cached)
|
||||
# A cache hit (seeded on dev by seed-apt-cache) never touches apt,
|
||||
@@ -1239,9 +1239,9 @@ jobs:
|
||||
# the cold path. The action has no --no-install-recommends; same
|
||||
# package set this job used before #17463.
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/apt-packages
|
||||
uses: $/.github/actions/apt-packages
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
@@ -1250,7 +1250,7 @@ jobs:
|
||||
# ESP-IDF install; never save -- just reuse the shared install the
|
||||
# dev tidy jobs already cached when present.
|
||||
if: matrix.batch.needs_idf
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
uses: $/.github/actions/cache-esp-idf
|
||||
with:
|
||||
restore-only: true
|
||||
- name: Cache nRF Connect SDK install (restore-only)
|
||||
@@ -1258,14 +1258,14 @@ jobs:
|
||||
# sdk-nrf install; never save -- just reuse the shared install the
|
||||
# dev nrf52 tidy job cached when present.
|
||||
if: matrix.batch.needs_nrf
|
||||
uses: ./.github/actions/cache-sdk-nrf
|
||||
uses: $/.github/actions/cache-sdk-nrf
|
||||
with:
|
||||
restore-only: true
|
||||
- name: Cache the native ESP8266 toolchain
|
||||
# Only batches whose test platforms include esp8266; never saves
|
||||
# here, it reuses the install the dev seed job cached.
|
||||
if: matrix.batch.needs_arduino8266
|
||||
uses: ./.github/actions/cache-arduino8266
|
||||
uses: $/.github/actions/cache-arduino8266
|
||||
- name: Validate and compile components with intelligent grouping
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -1408,10 +1408,10 @@ jobs:
|
||||
TEST_COMPONENTS: ${{ needs.determine-jobs.outputs.esp32-platformio-components }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
@@ -1446,16 +1446,16 @@ jobs:
|
||||
TEST_COMPONENTS: ${{ needs.determine-jobs.outputs.esp8266-native-components }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache the native toolchain
|
||||
uses: ./.github/actions/cache-arduino8266
|
||||
uses: $/.github/actions/cache-arduino8266
|
||||
|
||||
- name: Run native toolchain compile test
|
||||
run: |
|
||||
@@ -1495,10 +1495,10 @@ jobs:
|
||||
CCACHE_SHA256: 2568347a697e103ca1b073981c704ad76fb2507d066c38dba038dd73399d968f
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
@@ -1622,17 +1622,17 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.device-builder == 'true'
|
||||
steps:
|
||||
- name: Check out esphome (this PR)
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
path: esphome
|
||||
- name: Check out esphome/device-builder
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
repository: esphome/device-builder
|
||||
ref: main
|
||||
path: device-builder
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Set up uv
|
||||
@@ -1641,7 +1641,7 @@ jobs:
|
||||
# install step (order-of-magnitude faster on cold boots,
|
||||
# with its own wheel cache). actions/setup-python still
|
||||
# provides the interpreter.
|
||||
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
||||
uses: astral-sh/setup-uv@v10.3.3
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
@@ -1684,7 +1684,7 @@ jobs:
|
||||
skip: ${{ steps.check-script.outputs.skip || steps.check-tests.outputs.skip }}
|
||||
steps:
|
||||
- name: Check out target branch
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
@@ -1756,7 +1756,7 @@ jobs:
|
||||
- name: Restore cached memory analysis
|
||||
id: cache-memory-analysis
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/restore@v6.1.0
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
@@ -1773,14 +1773,14 @@ jobs:
|
||||
|
||||
- name: Restore Python
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache the native ESP8266 toolchain
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && fromJSON(needs.determine-jobs.outputs.memory_impact).needs_arduino8266
|
||||
uses: ./.github/actions/cache-arduino8266
|
||||
uses: $/.github/actions/cache-arduino8266
|
||||
|
||||
- name: Build, compile, and analyze memory
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
@@ -1819,7 +1819,7 @@ jobs:
|
||||
|
||||
- name: Save memory analysis to cache
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
uses: actions/cache/save@v6.1.0
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
@@ -1840,7 +1840,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload memory analysis JSON
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@v7.0.2
|
||||
with:
|
||||
name: memory-analysis-target
|
||||
path: memory-analysis-target.json
|
||||
@@ -1859,15 +1859,15 @@ jobs:
|
||||
flash_usage: ${{ steps.extract.outputs.flash_usage }}
|
||||
steps:
|
||||
- name: Check out PR branch
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache the native ESP8266 toolchain
|
||||
if: fromJSON(needs.determine-jobs.outputs.memory_impact).needs_arduino8266
|
||||
uses: ./.github/actions/cache-arduino8266
|
||||
uses: $/.github/actions/cache-arduino8266
|
||||
- name: Build, compile, and analyze memory
|
||||
id: extract
|
||||
run: |
|
||||
@@ -1903,7 +1903,7 @@ jobs:
|
||||
--platform "$platform"
|
||||
|
||||
- name: Upload memory analysis JSON
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@v7.0.2
|
||||
with:
|
||||
name: memory-analysis-pr
|
||||
path: memory-analysis-pr.json
|
||||
@@ -1926,20 +1926,20 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
uses: $/.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Download target analysis JSON
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
uses: actions/download-artifact@v8.0.2
|
||||
with:
|
||||
name: memory-analysis-target
|
||||
path: ./memory-analysis
|
||||
continue-on-error: true
|
||||
- name: Download PR analysis JSON
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
uses: actions/download-artifact@v8.0.2
|
||||
with:
|
||||
name: memory-analysis-pr
|
||||
path: ./memory-analysis
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
&& github.event.pull_request.head.ref == github.event.repository.default_branch
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
- uses: actions/github-script@v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout base branch
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
sparse-checkout: |
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
CODEOWNERS
|
||||
|
||||
- name: Check codeowner approval and update label
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@v9.0.0
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
with:
|
||||
|
||||
@@ -29,13 +29,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout base branch
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
uses: actions/create-github-app-token@v3.3.0
|
||||
with:
|
||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
permission-issues: write # issues.listComments and issues.createComment (PR comments use the issues API)
|
||||
|
||||
- name: Request reviews from component codeowners
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@v9.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
|
||||
@@ -52,11 +52,11 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/init@v4.39.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -84,6 +84,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/analyze@v4.39.1
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
uses: actions/create-github-app-token@v3.3.0
|
||||
with:
|
||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Add external component comment
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@v9.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify codeowners for component issues
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
|
||||
@@ -14,4 +14,4 @@ jobs:
|
||||
permissions:
|
||||
issues: write # issues.lock on closed issues
|
||||
pull-requests: write # issues.lock on closed pull requests
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@31d160e5f0ffa0ee65d5b27cb55c153d415f2abe # 2026.10.2
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@e1432c7b31fec7d24a13dc192953ebac836ed98a # 2026.10.3
|
||||
@@ -16,9 +16,9 @@ jobs:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7.0.1
|
||||
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
- uses: actions/github-script@v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const {
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
actions: write # gh workflow run starts release.yml
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Start the release workflow
|
||||
env:
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Get tag
|
||||
id: tag
|
||||
env:
|
||||
@@ -84,9 +84,9 @@ jobs:
|
||||
contents: read # actions/checkout to build the sdist/wheel
|
||||
id-token: write # OIDC token for PyPI Trusted Publishing (pypa/gh-action-pypi-publish)
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Build
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
pip3 install build
|
||||
python3 -m build
|
||||
- name: Publish
|
||||
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
|
||||
uses: pypa/gh-action-pypi-publish@v1.14.3
|
||||
with:
|
||||
skip-existing: true
|
||||
|
||||
@@ -116,29 +116,29 @@ jobs:
|
||||
os: "ubuntu-24.04-arm"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
||||
uses: docker/setup-buildx-action@v4.4.1
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
|
||||
uses: docker/login-action@v4.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
|
||||
uses: docker/login-action@v4.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build docker
|
||||
uses: ./.github/actions/build-image
|
||||
uses: $/.github/actions/build-image
|
||||
with:
|
||||
target: final
|
||||
build_type: docker
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
version: ${{ needs.init.outputs.tag }}
|
||||
|
||||
- name: Build ha-addon
|
||||
uses: ./.github/actions/build-image
|
||||
uses: $/.github/actions/build-image
|
||||
with:
|
||||
target: final
|
||||
build_type: ha-addon
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
# version: ${{ needs.init.outputs.tag }}
|
||||
|
||||
- name: Upload digests
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@v7.0.2
|
||||
with:
|
||||
name: digests-${{ matrix.platform.arch }}
|
||||
path: /tmp/digests
|
||||
@@ -192,27 +192,27 @@ jobs:
|
||||
- ghcr
|
||||
- dockerhub
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
uses: actions/download-artifact@v8.0.2
|
||||
with:
|
||||
pattern: digests-*
|
||||
path: /tmp/digests
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
||||
uses: docker/setup-buildx-action@v4.4.1
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
|
||||
uses: docker/login-action@v4.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
if: matrix.registry == 'ghcr'
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
|
||||
uses: docker/login-action@v4.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -245,7 +245,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
uses: actions/create-github-app-token@v3.3.0
|
||||
with:
|
||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -254,7 +254,7 @@ jobs:
|
||||
permission-actions: write # actions.createWorkflowDispatch on the target repo (only API call made with this token)
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@v9.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
# No GITHUB_TOKEN permissions: the reusable workflow mints an ESPHome
|
||||
# GitHub App token so the labels, comments and closures come from
|
||||
# esphome[bot] instead of github-actions[bot].
|
||||
uses: esphome/workflows/.github/workflows/stale.yml@31d160e5f0ffa0ee65d5b27cb55c153d415f2abe # main
|
||||
uses: esphome/workflows/.github/workflows/stale.yml@e1432c7b31fec7d24a13dc192953ebac836ed98a # main
|
||||
secrets:
|
||||
ESPHOME_GITHUB_APP_PRIVATE_KEY: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
with:
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for blocking labels
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const blockingLabels = ['needs-docs', 'needs-developer-docs', 'merge-after-release', 'chained-pr'];
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
uses: actions/create-github-app-token@v3.3.0
|
||||
with:
|
||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
- name: Check out base branch
|
||||
# Provides the script that runs below. Deliberately the base branch
|
||||
# so the pull request cannot change what executes here.
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
persist-credentials: false
|
||||
@@ -64,14 +64,14 @@ jobs:
|
||||
# refuses heads that live in a different repository, and the job
|
||||
# condition above already limits runs to same-repository branches.
|
||||
# Leaving it off keeps that refusal as a backstop for fork heads.
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
path: pull-request
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
uses: actions/create-github-app-token@v3.3.0
|
||||
with:
|
||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -28,16 +28,16 @@ jobs:
|
||||
permission-pull-requests: write # pulls.create / pulls.update to open or refresh the sync PR
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Checkout Home Assistant
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
repository: home-assistant/core
|
||||
path: lib/home-assistant
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
# setup-python interpreter so subsequent ``prek`` /
|
||||
# ``script/run-in-env.py`` steps find the deps without a
|
||||
# ``uv run`` prefix.
|
||||
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
||||
uses: astral-sh/setup-uv@v10.3.3
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pin uv version so the action does not have to fetch the
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
run: python script/run-in-env.py prek run --all-files
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
uses: peter-evans/create-pull-request@v8.1.1
|
||||
with:
|
||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||
committer: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
uses: actions/create-github-app-token@v3.3.0
|
||||
with:
|
||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -28,10 +28,10 @@ jobs:
|
||||
permission-pull-requests: write # open or refresh the sync PR
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
||||
uses: actions/setup-python@v7.1.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
uses: peter-evans/create-pull-request@v8.1.1
|
||||
with:
|
||||
commit-message: "[ci] Refresh integration test durations"
|
||||
committer: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
name: verify-lockfile
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify actions lockfile
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install gh-actions-lock
|
||||
run: gh extension install github/gh-actions-lock --pin v0.4.0
|
||||
- name: Verify lockfile
|
||||
run: gh actions-lock --verify
|
||||
@@ -205,6 +205,18 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
static constexpr uint32_t READ_TIMEOUT_ID = 0;
|
||||
this->set_timeout(READ_TIMEOUT_ID, 50, [this]() { this->read_(); });
|
||||
```
|
||||
* **Scheduler lambdas capture at most 8 bytes:** `set_timeout`, `set_interval` and `defer` store the lambda
|
||||
in a `std::function`, which keeps it inline only when it is at most 8 bytes on 32-bit targets and trivially
|
||||
copyable; anything else is a heap allocation each time it is scheduled, which matters for retries, re-armed
|
||||
timeouts and `defer` on a hot path. `[this]` plus one 4-byte value fits. `[this]` plus two 4-byte values,
|
||||
`[this]` plus a member function pointer (8 bytes), a `std::string` or a `std::function` do not: keep that
|
||||
state in members and capture `[this]`, or capture a small index instead.
|
||||
```cpp
|
||||
// Bad - 12 bytes on 32-bit, allocates on every retry
|
||||
this->set_timeout(RETRY_ID, 10, [this, configure = config.configure]() { (this->*configure)(); });
|
||||
// Good - 8 bytes, the pointer is looked up when the timeout runs
|
||||
this->set_timeout(RETRY_ID, 10, [this, feature]() { this->run_configure_(feature); });
|
||||
```
|
||||
* **Don't override a default with the same value:** if a base class method already returns what you
|
||||
want, do not override it. `Component::get_setup_priority()` returns `setup_priority::DATA`, so a
|
||||
component that wants `DATA` should simply leave it alone.
|
||||
@@ -227,16 +239,20 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
A `dump_config` helper that picks a name returns `const LogString *` from `LOG_STR(...)`.
|
||||
* **CRCs and bit tricks** come from `esphome/core/helpers.h` (`crc8`, `crc16`, `xor_bytes`, `sum16_bytes`,
|
||||
`reverse_bits`, `encode_uint16`, `encode_value_le`, `sign_extend<BITS>`, `bcd_to_uint8`, `assign_bits`); pick
|
||||
the helper's arguments instead of writing a loop. 7-byte BCD RTC calendars use `time::decode_bcd_calendar<LAYOUT>`
|
||||
and `encode_bcd_calendar<LAYOUT>` with `EPSON_BCD_CALENDAR`, `DS_BCD_CALENDAR` or `PCF_BCD_CALENDAR`.
|
||||
the helper's arguments instead of writing a loop. `put_be16`/`24`/`32`, `put_le16` and `put_le32` write an
|
||||
integer into a byte buffer and return the pointer past it. 7-byte BCD RTC calendars use
|
||||
`time::decode_bcd_calendar<LAYOUT>` and `encode_bcd_calendar<LAYOUT>` with `EPSON_BCD_CALENDAR`,
|
||||
`DS_BCD_CALENDAR` or `PCF_BCD_CALENDAR`.
|
||||
* **Bus helpers:** UART drains with `read_available(on_byte)` and `discard_available()`, text lines with
|
||||
`LineReader<N>`, fixed-length binary frames with `FrameReader<N, HEADER...>`; I2C has `update_register`,
|
||||
`write_registers` (PROGMEM `I2CRegValue` table), `check_chip_id` and `read_bytes_16_le`.
|
||||
`if (!this->status_comm_result(ok)) return;` sets or clears the warning.
|
||||
`if (!this->status_comm_result(ok)) return;` sets or clears the warning; in `setup()`,
|
||||
`if (!this->setup_check(ok)) return;` marks the component failed on error.
|
||||
* **A hub's pointer to a child entity** comes from `SUB_SENSOR(name)`, `SUB_BINARY_SENSOR`, `SUB_TEXT_SENSOR`,
|
||||
`SUB_SWITCH`, `SUB_NUMBER`, `SUB_SELECT` or `SUB_BUTTON`, which declare the `set_<name>_<type>()` setter and
|
||||
the `<name>_<type>_` member (`SUB_SWITCH(turbo)` gives `set_turbo_switch()`); do not write the pair by hand.
|
||||
A child sensor that skips repeated values is `SUB_SENSOR_WITH_DEDUP(name, type)`.
|
||||
Publish to an optional child with `PUBLISH_STATE(this->x_sensor_, value);`, not a hand-written null check.
|
||||
* **An unset float is `NAN`**, not `optional<float>`; the optional costs 4 to 8 bytes of padding per member.
|
||||
* **`PollingComponent` first runs `update()`** after a random share of the interval; call `this->update()` at the
|
||||
end of `setup()` when the entities need a state at boot.
|
||||
|
||||
+4
-9
@@ -33,8 +33,6 @@ esphome/components/airthings_wave_base/* @jeromelaban @ncareau
|
||||
esphome/components/airthings_wave_mini/* @ncareau
|
||||
esphome/components/airthings_wave_plus/* @jeromelaban @precurse
|
||||
esphome/components/airton/* @Procsiab
|
||||
esphome/components/airton/select/* @Procsiab
|
||||
esphome/components/airton/switch/* @Procsiab
|
||||
esphome/components/airton_serial/* @guiguid
|
||||
esphome/components/alarm_control_panel/* @grahambrown11 @hwstar
|
||||
esphome/components/alpha3/* @jan-hofmeier
|
||||
@@ -156,6 +154,7 @@ esphome/components/d01/* @ch604
|
||||
esphome/components/dac7678/* @NickB1
|
||||
esphome/components/daikin_arc/* @MagicBear
|
||||
esphome/components/daikin_brc/* @hagak
|
||||
esphome/components/daikin_madoka/* @Petapton
|
||||
esphome/components/dallas_temp/* @ssieb
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
esphome/components/dashboard_import/* @esphome/core
|
||||
@@ -262,14 +261,7 @@ esphome/components/growatt_solar/* @leeuwte
|
||||
esphome/components/gsl3670/* @clydebarrow
|
||||
esphome/components/gt911/* @clydebarrow @jesserockz
|
||||
esphome/components/haier/* @paveldn
|
||||
esphome/components/haier/binary_sensor/* @paveldn
|
||||
esphome/components/haier/button/* @paveldn
|
||||
esphome/components/haier/select/* @paveldn
|
||||
esphome/components/haier/sensor/* @paveldn
|
||||
esphome/components/haier/switch/* @paveldn
|
||||
esphome/components/haier/text_sensor/* @paveldn
|
||||
esphome/components/haier_tundra/* @fauxpark
|
||||
esphome/components/haier_tundra/button/* @fauxpark
|
||||
esphome/components/haier_tundra/switch/* @fauxpark
|
||||
esphome/components/haier_water_heater/* @TheLexus
|
||||
esphome/components/havells_solar/* @sourabhjaiswal
|
||||
@@ -303,6 +295,7 @@ esphome/components/honeywellabp2_i2c/* @jpfaff
|
||||
esphome/components/host/* @clydebarrow @esphome/core
|
||||
esphome/components/host/time/* @clydebarrow
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/ht16k33_char/* @ilikecake
|
||||
esphome/components/hte501/* @Stock-M
|
||||
esphome/components/http_request/ota/* @oarcher
|
||||
esphome/components/http_request/update/* @jesserockz
|
||||
@@ -384,6 +377,7 @@ esphome/components/ltr_als_ps/* @latonita
|
||||
esphome/components/lvgl/* @clydebarrow
|
||||
esphome/components/m5stack_4relay/* @KoenBreeman
|
||||
esphome/components/m5stack_8angle/* @rnauber
|
||||
esphome/components/m5stack_unit_bldc/* @lboue
|
||||
esphome/components/mapping/* @clydebarrow
|
||||
esphome/components/matrix_keypad/* @ssieb
|
||||
esphome/components/max17043/* @blacknell
|
||||
@@ -481,6 +475,7 @@ esphome/components/output/* @esphome/core
|
||||
esphome/components/packet_interface/* @clydebarrow
|
||||
esphome/components/packet_interface/packet_transport/* @clydebarrow
|
||||
esphome/components/packet_transport/* @clydebarrow
|
||||
esphome/components/panasonic_ir/* @duckle29
|
||||
esphome/components/pca6416a/* @Mat931
|
||||
esphome/components/pca9505/* @ajfriesen @ssieb
|
||||
esphome/components/pca9554/* @bdraco @clydebarrow @hwstar
|
||||
|
||||
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2026.11.0-dev
|
||||
PROJECT_NUMBER = 2026.12.0-dev
|
||||
|
||||
# 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
|
||||
|
||||
+706
-118
File diff suppressed because it is too large.
Load diff
@@ -14,9 +14,10 @@ enum SaturationVaporPressureEquation {
|
||||
|
||||
/// This class implements calculation of absolute humidity from temperature and relative humidity.
|
||||
class AbsoluteHumidityComponent final : public sensor::Sensor, public Component {
|
||||
SUB_SENSOR(temperature)
|
||||
SUB_SENSOR(humidity)
|
||||
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
void set_equation(SaturationVaporPressureEquation equation) { this->equation_ = equation; }
|
||||
|
||||
void setup() override;
|
||||
@@ -48,9 +49,6 @@ class AbsoluteHumidityComponent final : public sensor::Sensor, public Component
|
||||
*/
|
||||
static float vapor_density(float es, float hr, float ta);
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
|
||||
float temperature_{NAN};
|
||||
float humidity_{NAN};
|
||||
SaturationVaporPressureEquation equation_;
|
||||
|
||||
@@ -22,7 +22,6 @@ void AddressableLightDisplay::update() {
|
||||
|
||||
void AddressableLightDisplay::display() {
|
||||
bool dirty = false;
|
||||
uint8_t old_r, old_g, old_b, old_w;
|
||||
Color *c;
|
||||
|
||||
for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) {
|
||||
@@ -32,16 +31,11 @@ void AddressableLightDisplay::display() {
|
||||
|
||||
// Track the original values for the pixel view. If it has changed updating, then
|
||||
// we trigger a redraw. Avoiding redraws == avoiding flicker!
|
||||
old_r = pixel.get_red();
|
||||
old_g = pixel.get_green();
|
||||
old_b = pixel.get_blue();
|
||||
old_w = pixel.get_white();
|
||||
|
||||
pixel.set_rgbw(c->r, c->g, c->b, c->w);
|
||||
const Color old_raw_color = pixel.get_raw();
|
||||
pixel.set(Color{c->r, c->g, c->b, c->w});
|
||||
|
||||
// If the actual value of the pixel changed, then schedule a redraw.
|
||||
if (pixel.get_red() != old_r || pixel.get_green() != old_g || pixel.get_blue() != old_b ||
|
||||
pixel.get_white() != old_w) {
|
||||
if (pixel.get_raw() != old_raw_color) {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +53,6 @@ CONF_POWER_GAIN = "power_gain"
|
||||
|
||||
CONF_NEUTRAL = "neutral"
|
||||
|
||||
# Tuple of power channel phases
|
||||
POWER_PHASES = (CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C)
|
||||
|
||||
# Tuple of sensor types that can be configured for power channels
|
||||
POWER_SENSOR_TYPES = (
|
||||
CONF_CURRENT,
|
||||
@@ -66,6 +63,20 @@ POWER_SENSOR_TYPES = (
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
)
|
||||
POWER_CALIBRATION_TYPES = (
|
||||
CONF_CURRENT_GAIN,
|
||||
CONF_VOLTAGE_GAIN,
|
||||
CONF_POWER_GAIN,
|
||||
CONF_PHASE_ANGLE,
|
||||
)
|
||||
|
||||
# Channel config key, ADE7880 setter, sensor types, calibration types
|
||||
CHANNELS = (
|
||||
(CONF_PHASE_A, "set_channel_a", POWER_SENSOR_TYPES, POWER_CALIBRATION_TYPES),
|
||||
(CONF_PHASE_B, "set_channel_b", POWER_SENSOR_TYPES, POWER_CALIBRATION_TYPES),
|
||||
(CONF_PHASE_C, "set_channel_c", POWER_SENSOR_TYPES, POWER_CALIBRATION_TYPES),
|
||||
(CONF_NEUTRAL, "set_channel_n", (CONF_CURRENT,), (CONF_CURRENT_GAIN,)),
|
||||
)
|
||||
|
||||
NEUTRAL_CHANNEL_SCHEMA = cv.Schema(
|
||||
{
|
||||
@@ -191,7 +202,7 @@ def prefix_sensor_name(
|
||||
|
||||
|
||||
def process_channel_sensors(
|
||||
config: ConfigType, channel_key: str, sensor_types: tuple
|
||||
config: ConfigType, channel_key: str, sensor_types: tuple[str, ...]
|
||||
) -> None:
|
||||
"""Process sensors for a channel and prefix their names.
|
||||
|
||||
@@ -212,12 +223,8 @@ def process_channel_sensors(
|
||||
|
||||
def preprocess_channels(config: ConfigType) -> ConfigType:
|
||||
"""Preprocess channel configurations to add channel name prefix to sensor names."""
|
||||
# Process power channels
|
||||
for channel in POWER_PHASES:
|
||||
process_channel_sensors(config, channel, POWER_SENSOR_TYPES)
|
||||
|
||||
# Process neutral channel
|
||||
process_channel_sensors(config, CONF_NEUTRAL, (CONF_CURRENT,))
|
||||
for channel_key, _, sensor_types, _ in CHANNELS:
|
||||
process_channel_sensors(config, channel_key, sensor_types)
|
||||
|
||||
return config
|
||||
|
||||
@@ -244,39 +251,20 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
async def neutral_channel(config: ConfigType) -> MockObj:
|
||||
async def new_channel(
|
||||
config: ConfigType,
|
||||
sensor_types: tuple[str, ...],
|
||||
calibration_types: tuple[str, ...],
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
current = config[CONF_CURRENT]
|
||||
sens = await sensor.new_sensor(current)
|
||||
cg.add(var.set_current(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
for sensor_type in sensor_types:
|
||||
await sensors(sensor_type, getattr(var, f"set_{sensor_type}"))
|
||||
|
||||
cg.add(
|
||||
var.set_current_gain_calibration(config[CONF_CALIBRATION][CONF_CURRENT_GAIN])
|
||||
)
|
||||
|
||||
return var
|
||||
|
||||
|
||||
async def power_channel(config: ConfigType) -> MockObj:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
for sensor_type in POWER_SENSOR_TYPES:
|
||||
if conf := config.get(sensor_type):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{sensor_type}")(sens))
|
||||
|
||||
for calib_type in [
|
||||
CONF_CURRENT_GAIN,
|
||||
CONF_VOLTAGE_GAIN,
|
||||
CONF_POWER_GAIN,
|
||||
CONF_PHASE_ANGLE,
|
||||
]:
|
||||
cg.add(
|
||||
getattr(var, f"set_{calib_type}_calibration")(
|
||||
config[CONF_CALIBRATION][calib_type]
|
||||
)
|
||||
)
|
||||
calibration = config[CONF_CALIBRATION]
|
||||
for calib_type in calibration_types:
|
||||
cg.add(getattr(var, f"set_{calib_type}_calibration")(calibration[calib_type]))
|
||||
|
||||
return var
|
||||
|
||||
@@ -288,21 +276,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.add_pin(var.set_irq1_pin, config[CONF_IRQ1_PIN])
|
||||
await cg.add_pin(var.set_reset_pin, config.get(CONF_RESET_PIN))
|
||||
|
||||
if frequency := config.get(CONF_FREQUENCY):
|
||||
cg.add(var.set_frequency(frequency))
|
||||
cg.add(var.set_frequency(config[CONF_FREQUENCY]))
|
||||
|
||||
if channel := config.get(CONF_PHASE_A):
|
||||
chan = await power_channel(channel)
|
||||
cg.add(var.set_channel_a(chan))
|
||||
|
||||
if channel := config.get(CONF_PHASE_B):
|
||||
chan = await power_channel(channel)
|
||||
cg.add(var.set_channel_b(chan))
|
||||
|
||||
if channel := config.get(CONF_PHASE_C):
|
||||
chan = await power_channel(channel)
|
||||
cg.add(var.set_channel_c(chan))
|
||||
|
||||
if channel := config.get(CONF_NEUTRAL):
|
||||
chan = await neutral_channel(channel)
|
||||
cg.add(var.set_channel_n(chan))
|
||||
for channel_key, setter, sensor_types, calibration_types in CHANNELS:
|
||||
if channel := config.get(channel_key):
|
||||
chan = await new_channel(channel, sensor_types, calibration_types)
|
||||
cg.add(getattr(var, setter)(chan))
|
||||
@@ -120,10 +120,7 @@ void AHT10Component::read_data_() {
|
||||
uint32_t raw_temperature = encode_uint24(data[3] & 0xF, data[4], data[5]);
|
||||
uint32_t raw_humidity = encode_uint24(data[1], data[2], data[3]) >> 4;
|
||||
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
float temperature = ((200.0f * static_cast<float>(raw_temperature)) / AHT10_DIVISOR) - 50.0f;
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
PUBLISH_STATE(this->temperature_sensor_, ((200.0f * static_cast<float>(raw_temperature)) / AHT10_DIVISOR) - 50.0f);
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
float humidity = raw_humidity == 0 ? NAN : static_cast<float>(raw_humidity) * 100.0f / AHT10_DIVISOR;
|
||||
if (std::isnan(humidity)) {
|
||||
|
||||
@@ -175,11 +175,7 @@ void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) {
|
||||
if ((value_len >= (sizeof(AccessControlPointResponse) + 2)) && (raw_value[0] == ACCESS_CONTROL_POINT_COMMAND)) {
|
||||
ESP_LOGD(TAG, "Battery received: %u mV", (unsigned int) value->battery);
|
||||
|
||||
if (this->battery_voltage_ != nullptr) {
|
||||
float voltage = value->battery / 1000.0f;
|
||||
|
||||
this->battery_voltage_->publish_state(voltage);
|
||||
}
|
||||
PUBLISH_STATE(this->battery_voltage_, value->battery / 1000.0f);
|
||||
|
||||
// read the battery again at the configured update interval
|
||||
if (this->battery_update_interval_ != this->update_interval_) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import ENTITY_CATEGORY_CONFIG
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from ..climate import (
|
||||
from .climate import (
|
||||
CONF_AIRTON_ID,
|
||||
CONF_VERTICAL_DIRECTION,
|
||||
VERTICAL_DIRECTIONS,
|
||||
@@ -4,7 +4,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DISPLAY, ENTITY_CATEGORY_CONFIG
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from ..climate import CONF_AIRTON_ID, CONF_SLEEP_MODE, AirtonClimate, airton_ns
|
||||
from .climate import CONF_AIRTON_ID, CONF_SLEEP_MODE, AirtonClimate, airton_ns
|
||||
|
||||
CODEOWNERS = ["@Procsiab"]
|
||||
|
||||
@@ -256,16 +256,11 @@ void AirtonSerialClimate::handle_ac_(const uint8_t *data) {
|
||||
|
||||
void AirtonSerialClimate::publish_settings_() {
|
||||
#ifdef USE_SWITCH
|
||||
if (this->display_switch_ != nullptr)
|
||||
this->display_switch_->publish_state((this->flags_ & FLAG_DISPLAY) != 0);
|
||||
if (this->ionizer_switch_ != nullptr)
|
||||
this->ionizer_switch_->publish_state((this->flags_ & FLAG_IONIZER) != 0);
|
||||
if (this->sleep_switch_ != nullptr)
|
||||
this->sleep_switch_->publish_state((this->flags_ & FLAG_SLEEP) != 0);
|
||||
if (this->eco_switch_ != nullptr)
|
||||
this->eco_switch_->publish_state((this->flags_ & FLAG_ECO) != 0);
|
||||
if (this->health_switch_ != nullptr)
|
||||
this->health_switch_->publish_state((this->flags_ & FLAG_HEALTH) != 0);
|
||||
PUBLISH_STATE(this->display_switch_, (this->flags_ & FLAG_DISPLAY) != 0);
|
||||
PUBLISH_STATE(this->ionizer_switch_, (this->flags_ & FLAG_IONIZER) != 0);
|
||||
PUBLISH_STATE(this->sleep_switch_, (this->flags_ & FLAG_SLEEP) != 0);
|
||||
PUBLISH_STATE(this->eco_switch_, (this->flags_ & FLAG_ECO) != 0);
|
||||
PUBLISH_STATE(this->health_switch_, (this->flags_ & FLAG_HEALTH) != 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -105,57 +105,32 @@ ALARM_CONTROL_PANEL_CODE_ACTION_SCHEMA = maybe_simple_id(
|
||||
ALARM_CONTROL_PANEL_CONDITION_SCHEMA = ALARM_CONTROL_PANEL_ACTION_SCHEMA
|
||||
|
||||
|
||||
def _on_state_enter(conf_key: str, state: cg.MockObj) -> automation.CallbackAutomation:
|
||||
return automation.CallbackAutomation(
|
||||
conf_key,
|
||||
"add_on_state_callback",
|
||||
forwarder=StateEnterForwarder.template(state),
|
||||
params=[(AlarmControlPanelState, "state")],
|
||||
forward=[],
|
||||
when=f"state == {state}",
|
||||
)
|
||||
|
||||
|
||||
_CALLBACK_AUTOMATIONS = (
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_STATE, "add_on_state_callback", forwarder=StateAnyForwarder
|
||||
),
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_TRIGGERED,
|
||||
CONF_ON_STATE,
|
||||
"add_on_state_callback",
|
||||
forwarder=StateEnterForwarder.template(
|
||||
AlarmControlPanelState.ACP_STATE_TRIGGERED
|
||||
),
|
||||
),
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_ARMING,
|
||||
"add_on_state_callback",
|
||||
forwarder=StateEnterForwarder.template(AlarmControlPanelState.ACP_STATE_ARMING),
|
||||
),
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_PENDING,
|
||||
"add_on_state_callback",
|
||||
forwarder=StateEnterForwarder.template(
|
||||
AlarmControlPanelState.ACP_STATE_PENDING
|
||||
),
|
||||
),
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_ARMED_HOME,
|
||||
"add_on_state_callback",
|
||||
forwarder=StateEnterForwarder.template(
|
||||
AlarmControlPanelState.ACP_STATE_ARMED_HOME
|
||||
),
|
||||
),
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_ARMED_NIGHT,
|
||||
"add_on_state_callback",
|
||||
forwarder=StateEnterForwarder.template(
|
||||
AlarmControlPanelState.ACP_STATE_ARMED_NIGHT
|
||||
),
|
||||
),
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_ARMED_AWAY,
|
||||
"add_on_state_callback",
|
||||
forwarder=StateEnterForwarder.template(
|
||||
AlarmControlPanelState.ACP_STATE_ARMED_AWAY
|
||||
),
|
||||
),
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_DISARMED,
|
||||
"add_on_state_callback",
|
||||
forwarder=StateEnterForwarder.template(
|
||||
AlarmControlPanelState.ACP_STATE_DISARMED
|
||||
),
|
||||
forwarder=StateAnyForwarder,
|
||||
params=[(AlarmControlPanelState, "state")],
|
||||
forward=[],
|
||||
),
|
||||
_on_state_enter(CONF_ON_TRIGGERED, AlarmControlPanelState.ACP_STATE_TRIGGERED),
|
||||
_on_state_enter(CONF_ON_ARMING, AlarmControlPanelState.ACP_STATE_ARMING),
|
||||
_on_state_enter(CONF_ON_PENDING, AlarmControlPanelState.ACP_STATE_PENDING),
|
||||
_on_state_enter(CONF_ON_ARMED_HOME, AlarmControlPanelState.ACP_STATE_ARMED_HOME),
|
||||
_on_state_enter(CONF_ON_ARMED_NIGHT, AlarmControlPanelState.ACP_STATE_ARMED_NIGHT),
|
||||
_on_state_enter(CONF_ON_ARMED_AWAY, AlarmControlPanelState.ACP_STATE_ARMED_AWAY),
|
||||
_on_state_enter(CONF_ON_DISARMED, AlarmControlPanelState.ACP_STATE_DISARMED),
|
||||
automation.CallbackAutomation(CONF_ON_CLEARED, "add_on_cleared_callback"),
|
||||
automation.CallbackAutomation(CONF_ON_CHIME, "add_on_chime_callback"),
|
||||
automation.CallbackAutomation(CONF_ON_READY, "add_on_ready_callback"),
|
||||
|
||||
@@ -7,20 +7,19 @@
|
||||
namespace esphome::aqi {
|
||||
|
||||
class AQISensor final : public sensor::Sensor, public Component {
|
||||
SUB_SENSOR(pm_2_5)
|
||||
SUB_SENSOR(pm_10_0)
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
|
||||
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }
|
||||
void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; }
|
||||
void set_extended_range(bool extended_range) { this->extended_range_ = extended_range; }
|
||||
|
||||
protected:
|
||||
void calculate_aqi_();
|
||||
|
||||
sensor::Sensor *pm_2_5_sensor_{nullptr};
|
||||
sensor::Sensor *pm_10_0_sensor_{nullptr};
|
||||
AQICalculatorType aqi_calc_type_{AQI_TYPE};
|
||||
bool extended_range_{false};
|
||||
AQICalculatorFactory aqi_calculator_factory_;
|
||||
|
||||
@@ -4,7 +4,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import DEVICE_CLASS_SWITCH, ICON_WIFI
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_AT581X_ID, AT581XComponent
|
||||
from . import CONF_AT581X_ID, AT581XComponent
|
||||
|
||||
DEPENDENCIES = ["at581x"]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "atm90e32.h"
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <numbers>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::atm90e32 {
|
||||
@@ -107,7 +109,6 @@ void ATM90E32Component::update() {
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
this->check_phase_status();
|
||||
this->check_over_current();
|
||||
this->check_freq_status();
|
||||
#endif
|
||||
}
|
||||
@@ -120,23 +121,19 @@ const char *ATM90E32Component::get_calibration_id_() { return this->instance_id_
|
||||
|
||||
void ATM90E32Component::setup() {
|
||||
this->spi_setup();
|
||||
this->configure_chip_();
|
||||
}
|
||||
|
||||
void ATM90E32Component::configure_chip_() {
|
||||
const char *cs = this->get_calibration_id_();
|
||||
char legacy_cs[GPIO_SUMMARY_MAX_LEN];
|
||||
this->get_cs_summary_(legacy_cs);
|
||||
const bool has_distinct_legacy_namespace = strcmp(cs, legacy_cs) != 0;
|
||||
|
||||
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
||||
uint16_t high_thresh = 0;
|
||||
uint16_t low_thresh = 0;
|
||||
|
||||
if (line_freq_ == 60) {
|
||||
mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz
|
||||
// for freq threshold registers
|
||||
high_thresh = 6300; // 63.00 Hz
|
||||
low_thresh = 5700; // 57.00 Hz
|
||||
} else {
|
||||
high_thresh = 5300; // 53.00 Hz
|
||||
low_thresh = 4700; // 47.00 Hz
|
||||
}
|
||||
|
||||
if (current_phases_ == 2) {
|
||||
@@ -160,8 +157,6 @@ void ATM90E32Component::setup() {
|
||||
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // Zero crossing (ZX2, ZX1, ZX0) pin config
|
||||
this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program)
|
||||
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
|
||||
this->write16_(ATM90E32_REGISTER_FREQHITH, high_thresh); // Frequency high threshold
|
||||
this->write16_(ATM90E32_REGISTER_FREQLOTH, low_thresh); // Frequency low threshold
|
||||
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
|
||||
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
|
||||
this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
|
||||
@@ -249,30 +244,19 @@ void ATM90E32Component::setup() {
|
||||
}
|
||||
|
||||
this->restore_gain_calibrations_();
|
||||
|
||||
if (!this->using_saved_calibrations_) {
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
||||
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
||||
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
||||
this->gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;
|
||||
this->gain_phase_[phase].current_gain = this->phase_[phase].ct_gain_;
|
||||
}
|
||||
this->write_gains_to_registers_();
|
||||
}
|
||||
|
||||
// Sag threshold (78%)
|
||||
uint16_t sagth = calculate_voltage_threshold(line_freq_, this->phase_[0].voltage_gain_, 0.78f);
|
||||
// Overvoltage threshold (122%)
|
||||
uint16_t ovth = calculate_voltage_threshold(line_freq_, this->phase_[0].voltage_gain_, 1.22f);
|
||||
|
||||
// Write to registers
|
||||
this->write16_(ATM90E32_REGISTER_SAGTH, sagth);
|
||||
this->write16_(ATM90E32_REGISTER_OVTH, ovth);
|
||||
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
|
||||
this->write16_(ATM90E32_REGISTER_FREQHITH, this->frequency_high_threshold_);
|
||||
this->write16_(ATM90E32_REGISTER_FREQLOTH, this->frequency_low_threshold_);
|
||||
// SAGTH, OVTH and OITH were written with the gains, by write_gains_to_registers_().
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
||||
}
|
||||
|
||||
@@ -658,6 +642,8 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
|
||||
GainCalibration previous_gains[3] = {this->gain_phase_[0], this->gain_phase_[1], this->gain_phase_[2]};
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
float measured_voltage = this->get_phase_voltage_avg_(phase);
|
||||
float measured_current = this->get_phase_current_avg_(phase);
|
||||
@@ -728,81 +714,66 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
|
||||
this->save_gain_calibration_to_memory_();
|
||||
this->write_gains_to_registers_();
|
||||
this->verify_gain_writes_();
|
||||
this->finish_gain_calibration_(previous_gains);
|
||||
}
|
||||
|
||||
void ATM90E32Component::save_gain_calibration_to_memory_() {
|
||||
template<typename T, typename F>
|
||||
void ATM90E32Component::finish_calibration_(const LogString *name, const LogString *noun,
|
||||
ESPPreferenceObject &preference, const T (&values)[3], bool writes_verified,
|
||||
bool &has_stored, bool &restored, bool (&mismatches)[3], F &&rollback) {
|
||||
static_assert(sizeof(values) == CALIBRATION_PAYLOAD_SIZE, "Calibration payload size mismatch");
|
||||
const char *cs = this->get_calibration_id_();
|
||||
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
||||
global_preferences->sync();
|
||||
if (success) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration saved to memory.", cs);
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save gain calibration to memory!", cs);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::finish_offset_calibration_(const OffsetCalibration (&previous)[3], bool previous_restored,
|
||||
bool previous_using_saved, OffsetCalibrationType type) {
|
||||
const bool power_offsets = type == OffsetCalibrationType::OFFSET_CALIBRATION_TYPE_POWER;
|
||||
const char *cs = this->get_calibration_id_();
|
||||
const LogString *name = offset_calibration_name(power_offsets);
|
||||
OffsetCalibration(*offsets)[3] = power_offsets ? &this->power_offset_phase_ : &this->offset_phase_;
|
||||
ESPPreferenceObject *preference = power_offsets ? &this->power_offset_pref_ : &this->offset_pref_;
|
||||
bool *has_stored =
|
||||
power_offsets ? &this->has_stored_power_offset_calibration_ : &this->has_stored_offset_calibration_;
|
||||
bool *restored = power_offsets ? &this->restored_power_offset_calibration_ : &this->restored_offset_calibration_;
|
||||
bool *mismatches = power_offsets ? this->power_offset_calibration_mismatch_ : this->offset_calibration_mismatch_;
|
||||
|
||||
const bool writes_verified = this->verify_offset_writes_(type);
|
||||
bool saved = false;
|
||||
bool synced = false;
|
||||
if (writes_verified) {
|
||||
saved = preference->save(offsets);
|
||||
synced = global_preferences->sync();
|
||||
const bool previous_has_stored = has_stored;
|
||||
const bool previous_restored = restored;
|
||||
// Raw stored bytes, re-saved on rollback; all zero (the cleared sentinel) when nothing was stored.
|
||||
uint8_t previous_stored[CALIBRATION_PAYLOAD_SIZE]{};
|
||||
bool attempt = writes_verified;
|
||||
if (writes_verified && previous_has_stored && !preference.load(&previous_stored)) {
|
||||
attempt = false;
|
||||
ESP_LOGE(TAG,
|
||||
"[CALIBRATION][%s] Failed to load stored %s calibration "
|
||||
"values; refusing to overwrite them. "
|
||||
"Clear stored %s calibrations and retry.",
|
||||
cs, LOG_STR_ARG(noun), LOG_STR_ARG(noun));
|
||||
}
|
||||
|
||||
if (writes_verified && saved && synced) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
*has_stored = true;
|
||||
*restored = true;
|
||||
for (uint8_t phase = 0; phase < 3; phase++)
|
||||
mismatches[phase] = false;
|
||||
bool persisted = false;
|
||||
if (attempt) {
|
||||
const bool saved = preference.save(&values);
|
||||
const bool synced = global_preferences->sync();
|
||||
persisted = saved && synced;
|
||||
}
|
||||
|
||||
if (persisted) {
|
||||
has_stored = true;
|
||||
restored = true;
|
||||
for (bool &mismatch : mismatches)
|
||||
mismatch = false;
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] %s calibration saved to memory. %s calibration completed and verified.", cs,
|
||||
LOG_STR_ARG(name), LOG_STR_ARG(name));
|
||||
return;
|
||||
}
|
||||
|
||||
if (writes_verified) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save %s calibration to memory!", cs, LOG_STR_ARG(name));
|
||||
if (attempt) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save %s calibration to memory!", cs, LOG_STR_ARG(noun));
|
||||
}
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
this->write_offsets_to_registers_(phase, previous[phase].first_offset, previous[phase].second_offset, type);
|
||||
}
|
||||
const bool rollback_verified = this->verify_offset_writes_(type);
|
||||
const bool rollback_verified = rollback();
|
||||
|
||||
bool rollback_persisted = false;
|
||||
if (writes_verified) {
|
||||
OffsetCalibration rollback[3]{};
|
||||
prepare_offset_rollback(previous, previous_restored, rollback);
|
||||
const bool rollback_saved = preference->save(&rollback);
|
||||
if (attempt) {
|
||||
const bool rollback_saved = preference.save(&previous_stored);
|
||||
const bool rollback_synced = global_preferences->sync();
|
||||
rollback_persisted = rollback_saved && rollback_synced;
|
||||
if (!rollback_saved || !rollback_synced) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to persist restored %s calibration values!", cs, LOG_STR_ARG(name));
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to persist restored %s calibration values!", cs, LOG_STR_ARG(noun));
|
||||
}
|
||||
}
|
||||
|
||||
*restored = previous_restored;
|
||||
if (rollback_persisted)
|
||||
*has_stored = previous_restored;
|
||||
this->using_saved_calibrations_ = previous_using_saved;
|
||||
restored = previous_restored && rollback_verified;
|
||||
has_stored = previous_has_stored;
|
||||
if (!rollback_verified) {
|
||||
for (bool &mismatch : mismatches)
|
||||
mismatch = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] %s calibration failed; rollback readback verification failed.", cs,
|
||||
LOG_STR_ARG(name));
|
||||
return;
|
||||
@@ -810,6 +781,33 @@ void ATM90E32Component::finish_offset_calibration_(const OffsetCalibration (&pre
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] %s calibration failed; previous values restored.", cs, LOG_STR_ARG(name));
|
||||
}
|
||||
|
||||
void ATM90E32Component::finish_gain_calibration_(const GainCalibration (&previous)[3]) {
|
||||
this->finish_calibration_(LOG_STR("Gain"), LOG_STR("gain"), this->gain_calibration_pref_, this->gain_phase_,
|
||||
this->verify_gain_writes_(), this->has_stored_gain_calibration_,
|
||||
this->restored_gain_calibration_, this->gain_calibration_mismatch_, [this, &previous]() {
|
||||
for (uint8_t phase = 0; phase < 3; phase++)
|
||||
this->gain_phase_[phase] = previous[phase];
|
||||
this->write_gains_to_registers_();
|
||||
return this->verify_gain_writes_();
|
||||
});
|
||||
}
|
||||
|
||||
void ATM90E32Component::finish_offset_calibration_(const OffsetCalibration (&previous)[3], OffsetCalibrationType type) {
|
||||
const bool power_offsets = type == OffsetCalibrationType::OFFSET_CALIBRATION_TYPE_POWER;
|
||||
this->finish_calibration_(
|
||||
offset_calibration_name(power_offsets), offset_calibration_name(power_offsets),
|
||||
power_offsets ? this->power_offset_pref_ : this->offset_pref_,
|
||||
power_offsets ? this->power_offset_phase_ : this->offset_phase_, this->verify_offset_writes_(type),
|
||||
power_offsets ? this->has_stored_power_offset_calibration_ : this->has_stored_offset_calibration_,
|
||||
power_offsets ? this->restored_power_offset_calibration_ : this->restored_offset_calibration_,
|
||||
power_offsets ? this->power_offset_calibration_mismatch_ : this->offset_calibration_mismatch_,
|
||||
[this, &previous, type]() {
|
||||
for (uint8_t phase = 0; phase < 3; phase++)
|
||||
this->write_offsets_to_registers_(phase, previous[phase].first_offset, previous[phase].second_offset, type);
|
||||
return this->verify_offset_writes_(type);
|
||||
});
|
||||
}
|
||||
|
||||
void ATM90E32Component::run_offset_calibrations() {
|
||||
const char *cs = this->get_calibration_id_();
|
||||
if (!this->enable_offset_calibration_) {
|
||||
@@ -826,8 +824,6 @@ void ATM90E32Component::run_offset_calibrations() {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
|
||||
|
||||
OffsetCalibration previous_offsets[3] = {this->offset_phase_[0], this->offset_phase_[1], this->offset_phase_[2]};
|
||||
const bool previous_restored = this->restored_offset_calibration_;
|
||||
const bool previous_using_saved = this->using_saved_calibrations_;
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
int16_t voltage_offset = calibrate_offset(phase, true);
|
||||
@@ -842,8 +838,7 @@ void ATM90E32Component::run_offset_calibrations() {
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==================================================================\n", cs);
|
||||
|
||||
this->finish_offset_calibration_(previous_offsets, previous_restored, previous_using_saved,
|
||||
OffsetCalibrationType::OFFSET_CALIBRATION_TYPE_VOLTAGE_CURRENT);
|
||||
this->finish_offset_calibration_(previous_offsets, OffsetCalibrationType::OFFSET_CALIBRATION_TYPE_VOLTAGE_CURRENT);
|
||||
}
|
||||
|
||||
void ATM90E32Component::run_power_offset_calibrations() {
|
||||
@@ -864,8 +859,6 @@ void ATM90E32Component::run_power_offset_calibrations() {
|
||||
|
||||
OffsetCalibration previous_offsets[3] = {this->power_offset_phase_[0], this->power_offset_phase_[1],
|
||||
this->power_offset_phase_[2]};
|
||||
const bool previous_restored = this->restored_power_offset_calibration_;
|
||||
const bool previous_using_saved = this->using_saved_calibrations_;
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
int16_t active_offset = calibrate_power_offset(phase, false);
|
||||
@@ -879,8 +872,7 @@ void ATM90E32Component::run_power_offset_calibrations() {
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
|
||||
this->finish_offset_calibration_(previous_offsets, previous_restored, previous_using_saved,
|
||||
OffsetCalibrationType::OFFSET_CALIBRATION_TYPE_POWER);
|
||||
this->finish_offset_calibration_(previous_offsets, OffsetCalibrationType::OFFSET_CALIBRATION_TYPE_POWER);
|
||||
}
|
||||
|
||||
void ATM90E32Component::write_gains_to_registers_() {
|
||||
@@ -891,9 +883,56 @@ void ATM90E32Component::write_gains_to_registers_() {
|
||||
this->write16_(current_gain_registers[phase], this->gain_phase_[phase].current_gain);
|
||||
}
|
||||
|
||||
this->write_thresholds_();
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
|
||||
}
|
||||
|
||||
void ATM90E32Component::write_thresholds_() {
|
||||
static constexpr uint16_t LEGACY_OITH = 0xC000;
|
||||
struct GainRange {
|
||||
uint16_t min{UINT16_MAX};
|
||||
uint16_t max{0};
|
||||
};
|
||||
// Reads back the gain registers of the configured phases; {0, 0} when none are configured.
|
||||
auto read_gain_range = [this](const bool (&configured)[3], const uint16_t (®isters)[3]) {
|
||||
GainRange range;
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (configured[phase]) {
|
||||
const uint16_t gain = this->read16_(registers[phase]);
|
||||
range.min = std::min(range.min, gain);
|
||||
range.max = std::max(range.max, gain);
|
||||
}
|
||||
}
|
||||
if (range.max == 0)
|
||||
range.min = 0;
|
||||
return range;
|
||||
};
|
||||
const GainRange voltage = read_gain_range(this->has_config_voltage_gain_, this->voltage_gain_registers);
|
||||
const GainRange current = read_gain_range(this->has_config_current_gain_, this->current_gain_registers);
|
||||
const bool current_configured = !std::isnan(this->threshold_current_peak_a_);
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
this->use_voltage_threshold_flags_ = voltage.min == voltage.max;
|
||||
if (!this->use_voltage_threshold_flags_) {
|
||||
ESP_LOGW(TAG, "Phase voltage gains differ; using per-phase RMS voltage alarms");
|
||||
}
|
||||
// The shared OITH is encoded at the highest gain, so a lower-gain phase would flag below current_peak.
|
||||
this->use_current_threshold_flags_ = current.min == current.max;
|
||||
if (!this->use_current_threshold_flags_ && current_configured) {
|
||||
ESP_LOGW(TAG, "Phase current gains differ; using per-phase RMS over-current alarms");
|
||||
}
|
||||
#endif
|
||||
|
||||
const uint16_t sagth = calculate_voltage_threshold_from_volts(this->threshold_voltage_sag_v_, voltage.min);
|
||||
const uint16_t ovth = calculate_voltage_threshold_from_volts(this->threshold_voltage_peak_v_, voltage.max);
|
||||
const uint16_t oith = current_configured
|
||||
? calculate_current_threshold_from_amps(this->threshold_current_peak_a_, current.max)
|
||||
: LEGACY_OITH;
|
||||
ESP_LOGD(TAG, "Thresholds: SAGTH=0x%04" PRIX16 ", OVTH=0x%04" PRIX16 ", OITH=0x%04" PRIX16, sagth, ovth, oith);
|
||||
this->write16_(ATM90E32_REGISTER_SAGTH, sagth);
|
||||
this->write16_(ATM90E32_REGISTER_OVTH, ovth);
|
||||
this->write16_(ATM90E32_REGISTER_OITH, oith);
|
||||
}
|
||||
|
||||
void ATM90E32Component::write_offsets_to_registers_(uint8_t phase, int16_t first_offset, int16_t second_offset,
|
||||
OffsetCalibrationType type) {
|
||||
const bool power_offsets = type == OffsetCalibrationType::OFFSET_CALIBRATION_TYPE_POWER;
|
||||
@@ -925,52 +964,58 @@ void ATM90E32Component::restore_gain_calibrations_() {
|
||||
this->gain_phase_[i] = this->config_gain_phase_[i];
|
||||
}
|
||||
|
||||
bool have_data = this->gain_calibration_pref_.load(&this->gain_phase_);
|
||||
|
||||
const bool have_data = this->gain_calibration_pref_.load(&this->gain_phase_);
|
||||
bool all_zero = true;
|
||||
bool same_as_config = true;
|
||||
if (have_data) {
|
||||
bool all_zero = true;
|
||||
bool same_as_config = true;
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
const auto &cfg = this->config_gain_phase_[phase];
|
||||
const auto &config = this->config_gain_phase_[phase];
|
||||
const auto &saved = this->gain_phase_[phase];
|
||||
if (saved.voltage_gain != 0 || saved.current_gain != 0)
|
||||
all_zero = false;
|
||||
if (saved.voltage_gain != cfg.voltage_gain || saved.current_gain != cfg.current_gain)
|
||||
if (saved.voltage_gain != config.voltage_gain || saved.current_gain != config.current_gain)
|
||||
same_as_config = false;
|
||||
}
|
||||
|
||||
if (!all_zero && !same_as_config) {
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
bool mismatch = false;
|
||||
if (this->has_config_voltage_gain_[phase] &&
|
||||
this->gain_phase_[phase].voltage_gain != this->config_gain_phase_[phase].voltage_gain)
|
||||
mismatch = true;
|
||||
if (this->has_config_current_gain_[phase] &&
|
||||
this->gain_phase_[phase].current_gain != this->config_gain_phase_[phase].current_gain)
|
||||
mismatch = true;
|
||||
if (mismatch)
|
||||
this->gain_calibration_mismatch_[phase] = true;
|
||||
}
|
||||
|
||||
this->write_gains_to_registers_();
|
||||
|
||||
if (this->verify_gain_writes_()) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
this->restored_gain_calibration_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Gain verification failed! Calibration may not be applied correctly.", cs);
|
||||
}
|
||||
}
|
||||
|
||||
this->using_saved_calibrations_ = false;
|
||||
for (uint8_t i = 0; i < 3; ++i)
|
||||
this->gain_phase_[i] = this->config_gain_phase_[i];
|
||||
this->write_gains_to_registers_();
|
||||
// A stored table equal to the config still counts as stored, so it is backed up and can be cleared;
|
||||
// it is only not reported as restored from flash.
|
||||
this->has_stored_gain_calibration_ = have_data && !all_zero;
|
||||
if (!this->has_stored_gain_calibration_) {
|
||||
for (uint8_t phase = 0; phase < 3; ++phase)
|
||||
this->gain_phase_[phase] = this->config_gain_phase_[phase];
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored gain calibrations found. Using config file values.", cs);
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored gain calibrations found. Using config file values.", cs);
|
||||
this->write_gains_to_registers_();
|
||||
const bool initial_values_verified = this->verify_gain_writes_();
|
||||
if (initial_values_verified) {
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
this->gain_calibration_mismatch_[phase] =
|
||||
this->has_stored_gain_calibration_ &&
|
||||
((this->has_config_voltage_gain_[phase] &&
|
||||
this->gain_phase_[phase].voltage_gain != this->config_gain_phase_[phase].voltage_gain) ||
|
||||
(this->has_config_current_gain_[phase] &&
|
||||
this->gain_phase_[phase].current_gain != this->config_gain_phase_[phase].current_gain));
|
||||
}
|
||||
this->restored_gain_calibration_ = this->has_stored_gain_calibration_ && !same_as_config;
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
this->gain_calibration_mismatch_[phase] = false;
|
||||
this->gain_phase_[phase] = this->config_gain_phase_[phase];
|
||||
}
|
||||
this->write_gains_to_registers_();
|
||||
const bool fallback_verified = this->verify_gain_writes_();
|
||||
this->restored_gain_calibration_ = false;
|
||||
if (fallback_verified) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] %s calibration restore failed verification; config values verified.", cs,
|
||||
LOG_STR_LITERAL("gain"));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] %s calibration restore and config fallback both failed verification.", cs,
|
||||
LOG_STR_LITERAL("gain"));
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::restore_offset_calibrations_(OffsetCalibrationType type) {
|
||||
@@ -1003,15 +1048,6 @@ void ATM90E32Component::restore_offset_calibrations_(OffsetCalibrationType type)
|
||||
}
|
||||
|
||||
*has_stored = have_data && !all_zero;
|
||||
*restored = false;
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
mismatches[phase] = false;
|
||||
if (*has_stored) {
|
||||
mismatches[phase] =
|
||||
(has_first[phase] && (*offsets)[phase].first_offset != (*config_offsets)[phase].first_offset) ||
|
||||
(has_second[phase] && (*offsets)[phase].second_offset != (*config_offsets)[phase].second_offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (!*has_stored) {
|
||||
for (uint8_t phase = 0; phase < 3; phase++)
|
||||
@@ -1024,22 +1060,26 @@ void ATM90E32Component::restore_offset_calibrations_(OffsetCalibrationType type)
|
||||
}
|
||||
const bool initial_values_verified = this->verify_offset_writes_(type);
|
||||
if (initial_values_verified) {
|
||||
const auto state = resolve_offset_restore_state(*has_stored, true, false);
|
||||
*restored = state.restored;
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
mismatches[phase] =
|
||||
*has_stored &&
|
||||
((has_first[phase] && (*offsets)[phase].first_offset != (*config_offsets)[phase].first_offset) ||
|
||||
(has_second[phase] && (*offsets)[phase].second_offset != (*config_offsets)[phase].second_offset));
|
||||
}
|
||||
*restored = *has_stored;
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] %s calibration values verified.", cs, LOG_STR_ARG(name));
|
||||
return;
|
||||
}
|
||||
|
||||
this->using_saved_calibrations_ = false;
|
||||
for (uint8_t phase = 0; phase < 3; phase++)
|
||||
mismatches[phase] = false;
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
(*offsets)[phase] = (*config_offsets)[phase];
|
||||
this->write_offsets_to_registers_(phase, (*offsets)[phase].first_offset, (*offsets)[phase].second_offset, type);
|
||||
}
|
||||
const auto state = resolve_offset_restore_state(*has_stored, false, this->verify_offset_writes_(type));
|
||||
*restored = state.restored;
|
||||
if (state.values_verified) {
|
||||
const bool fallback_verified = this->verify_offset_writes_(type);
|
||||
*restored = false;
|
||||
if (fallback_verified) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] %s calibration restore failed verification; config values verified.", cs,
|
||||
LOG_STR_ARG(name));
|
||||
} else {
|
||||
@@ -1048,9 +1088,22 @@ void ATM90E32Component::restore_offset_calibrations_(OffsetCalibrationType type)
|
||||
}
|
||||
}
|
||||
|
||||
bool ATM90E32Component::clear_calibration_pref_(ESPPreferenceObject &preference, bool &has_stored, bool &restored,
|
||||
bool (&mismatches)[3]) {
|
||||
const uint8_t cleared[CALIBRATION_PAYLOAD_SIZE]{};
|
||||
const bool saved = preference.save(&cleared);
|
||||
const bool synced = global_preferences->sync();
|
||||
if (saved && synced)
|
||||
has_stored = false;
|
||||
restored = false;
|
||||
for (bool &mismatch : mismatches)
|
||||
mismatch = false;
|
||||
return saved && synced;
|
||||
}
|
||||
|
||||
void ATM90E32Component::clear_gain_calibrations() {
|
||||
const char *cs = this->get_calibration_id_();
|
||||
if (!this->using_saved_calibrations_) {
|
||||
if (!this->has_stored_gain_calibration_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored gain calibrations to clear. Current values:", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||
@@ -1081,16 +1134,8 @@ void ATM90E32Component::clear_gain_calibrations() {
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
|
||||
|
||||
GainCalibration zero_gains[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||
bool success = this->gain_calibration_pref_.save(&zero_gains);
|
||||
global_preferences->sync();
|
||||
|
||||
this->using_saved_calibrations_ = false;
|
||||
this->restored_gain_calibration_ = false;
|
||||
for (bool &phase : this->gain_calibration_mismatch_)
|
||||
phase = false;
|
||||
|
||||
if (!success) {
|
||||
if (!this->clear_calibration_pref_(this->gain_calibration_pref_, this->has_stored_gain_calibration_,
|
||||
this->restored_gain_calibration_, this->gain_calibration_mismatch_)) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to clear gain calibrations!", cs);
|
||||
}
|
||||
|
||||
@@ -1129,16 +1174,12 @@ void ATM90E32Component::clear_offset_calibrations() {
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
|
||||
|
||||
OffsetCalibration zero_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||
this->offset_pref_.save(&zero_offsets); // Clear stored values in flash
|
||||
global_preferences->sync();
|
||||
|
||||
this->has_stored_offset_calibration_ = false;
|
||||
this->restored_offset_calibration_ = false;
|
||||
for (bool &phase : this->offset_calibration_mismatch_)
|
||||
phase = false;
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Offsets cleared.", cs);
|
||||
if (!this->clear_calibration_pref_(this->offset_pref_, this->has_stored_offset_calibration_,
|
||||
this->restored_offset_calibration_, this->offset_calibration_mismatch_)) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to clear offset calibrations!", cs);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Offsets cleared.", cs);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::clear_power_offset_calibrations() {
|
||||
@@ -1173,16 +1214,13 @@ void ATM90E32Component::clear_power_offset_calibrations() {
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
|
||||
OffsetCalibration zero_power_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||
this->power_offset_pref_.save(&zero_power_offsets);
|
||||
global_preferences->sync();
|
||||
|
||||
this->has_stored_power_offset_calibration_ = false;
|
||||
this->restored_power_offset_calibration_ = false;
|
||||
for (bool &phase : this->power_offset_calibration_mismatch_)
|
||||
phase = false;
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offsets cleared.", cs);
|
||||
if (!this->clear_calibration_pref_(this->power_offset_pref_, this->has_stored_power_offset_calibration_,
|
||||
this->restored_power_offset_calibration_,
|
||||
this->power_offset_calibration_mismatch_)) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to clear power offsets!", cs);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offsets cleared.", cs);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
||||
@@ -1259,28 +1297,45 @@ bool ATM90E32Component::verify_offset_writes_(OffsetCalibrationType type) {
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void ATM90E32Component::check_phase_status() {
|
||||
static constexpr uint16_t OVER_CURRENT_FLAGS[3] = {ATM90E32_STATUS_S0_OIPHASEAST, ATM90E32_STATUS_S0_OIPHASEBST,
|
||||
ATM90E32_STATUS_S0_OIPHASECST};
|
||||
static constexpr float LEGACY_OVER_CURRENT_A = 65.53f;
|
||||
uint16_t state0 = this->read16_(ATM90E32_REGISTER_EMMSTATE0);
|
||||
uint16_t state1 = this->read16_(ATM90E32_REGISTER_EMMSTATE1);
|
||||
const bool current_configured = !std::isnan(this->threshold_current_peak_a_);
|
||||
const float current_limit = current_configured ? this->threshold_current_peak_a_ : LEGACY_OVER_CURRENT_A;
|
||||
|
||||
for (int phase = 0; phase < 3; phase++) {
|
||||
std::string status;
|
||||
|
||||
if (state0 & over_voltage_flags[phase])
|
||||
status += "Over Voltage; ";
|
||||
if (state1 & voltage_sag_flags[phase])
|
||||
status += "Voltage Sag; ";
|
||||
if (state1 & phase_loss_flags[phase])
|
||||
status += "Phase Loss; ";
|
||||
|
||||
auto *sensor = this->phase_status_text_sensor_[phase];
|
||||
if (sensor == nullptr)
|
||||
continue;
|
||||
|
||||
if (!status.empty()) {
|
||||
status.pop_back(); // remove space
|
||||
status.pop_back(); // remove semicolon
|
||||
ESP_LOGW(TAG, "%s: %s", LOG_STR_ARG(sensor->get_log_name()), status.c_str());
|
||||
sensor->publish_state(status);
|
||||
char status[sizeof("Over Current; Over Voltage; Voltage Sag; Phase Loss; ")];
|
||||
size_t length = 0;
|
||||
bool over_voltage = state0 & this->over_voltage_flags[phase];
|
||||
bool voltage_sag = state1 & this->voltage_sag_flags[phase];
|
||||
if (!this->use_voltage_threshold_flags_) {
|
||||
const float voltage = this->get_phase_voltage_(phase);
|
||||
over_voltage = voltage > this->threshold_voltage_peak_v_;
|
||||
voltage_sag = voltage < this->threshold_voltage_sag_v_;
|
||||
}
|
||||
|
||||
const float current = this->has_config_current_gain_[phase] ? this->get_phase_current_(phase) : 0.0f;
|
||||
if ((current_configured && this->use_current_threshold_flags_ && (state0 & OVER_CURRENT_FLAGS[phase])) ||
|
||||
current > current_limit)
|
||||
length = buf_append_str(status, sizeof(status), length, "Over Current; ");
|
||||
if (over_voltage)
|
||||
length = buf_append_str(status, sizeof(status), length, "Over Voltage; ");
|
||||
if (voltage_sag)
|
||||
length = buf_append_str(status, sizeof(status), length, "Voltage Sag; ");
|
||||
if (state1 & this->phase_loss_flags[phase])
|
||||
length = buf_append_str(status, sizeof(status), length, "Phase Loss; ");
|
||||
|
||||
if (length != 0) {
|
||||
length -= 2; // Remove the trailing separator.
|
||||
status[length] = '\0';
|
||||
ESP_LOGW(TAG, "%s: %s", LOG_STR_ARG(sensor->get_log_name()), status);
|
||||
sensor->publish_state(status, length);
|
||||
} else {
|
||||
sensor->publish_state("Okay");
|
||||
}
|
||||
@@ -1309,34 +1364,30 @@ void ATM90E32Component::check_freq_status() {
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::check_over_current() {
|
||||
constexpr float max_current_threshold = 65.53f;
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
float current_val =
|
||||
this->phase_[phase].current_sensor_ != nullptr ? this->phase_[phase].current_sensor_->state : 0.0f;
|
||||
|
||||
if (current_val > max_current_threshold) {
|
||||
ESP_LOGW(TAG, "Over current detected on Phase %c: %.2f A", 'A' + phase, current_val);
|
||||
ESP_LOGW(TAG, "You may need to half your gain_ct: value & multiply the current and power values by 2");
|
||||
PUBLISH_STATE(this->phase_status_text_sensor_[phase], "Over Current; ");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t ATM90E32Component::calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier) {
|
||||
// this assumes that 60Hz electrical systems use 120V mains,
|
||||
// which is usually, but not always the case
|
||||
float nominal_voltage = (line_freq == 60) ? 120.0f : 220.0f;
|
||||
float target_voltage = nominal_voltage * multiplier;
|
||||
static uint16_t encode_threshold(float value) {
|
||||
if (!std::isfinite(value) || value > UINT16_MAX) {
|
||||
ESP_LOGW(TAG, "Threshold exceeds the 16-bit register range; clamping to 65535");
|
||||
return UINT16_MAX;
|
||||
}
|
||||
return static_cast<uint16_t>(std::round(std::max(value, 0.0f)));
|
||||
}
|
||||
|
||||
float peak_01v = target_voltage * 100.0f * std::numbers::sqrt2_v<float>; // convert RMS → peak, scale to 0.01V
|
||||
float divider = (2.0f * ugain) / 32768.0f;
|
||||
// Datasheet equation (section "Power Quality Monitoring"): xxThRegValue =
|
||||
// RmsRegValue * sqrt(2) / (VIgain / 2^14). RmsRegValue is in 0.01V for voltage and 0.001A for current.
|
||||
static uint16_t calculate_threshold(float rms, float scale, uint16_t gain) {
|
||||
if (gain == 0)
|
||||
return 0;
|
||||
return encode_threshold(rms * scale * std::numbers::sqrt2_v<float> / (gain / 16384.0f));
|
||||
}
|
||||
|
||||
float threshold = peak_01v / divider;
|
||||
uint16_t ATM90E32Component::calculate_voltage_threshold_from_volts(float voltage_rms, uint16_t ugain) {
|
||||
return calculate_threshold(voltage_rms, 100.0f, ugain);
|
||||
}
|
||||
|
||||
return static_cast<uint16_t>(threshold);
|
||||
uint16_t ATM90E32Component::calculate_current_threshold_from_amps(float current_rms, uint16_t igain) {
|
||||
return calculate_threshold(current_rms, 1000.0f, igain);
|
||||
}
|
||||
|
||||
bool ATM90E32Component::validate_spi_read_(uint16_t expected, const char *context) {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include "atm90e32_reg.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include <cmath>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
@@ -22,31 +26,24 @@ struct OffsetCalibration {
|
||||
int16_t second_offset{0};
|
||||
};
|
||||
|
||||
static_assert(sizeof(OffsetCalibration[3]) == 12, "Offset calibration preference layout must remain compatible");
|
||||
struct GainCalibration {
|
||||
uint16_t voltage_gain{1};
|
||||
uint16_t current_gain{1};
|
||||
};
|
||||
|
||||
// Stored size of each calibration table; an all-zero payload marks a table as cleared.
|
||||
static constexpr size_t CALIBRATION_PAYLOAD_SIZE = 12;
|
||||
|
||||
static_assert(sizeof(OffsetCalibration[3]) == CALIBRATION_PAYLOAD_SIZE,
|
||||
"Offset calibration preference layout must remain compatible");
|
||||
static_assert(sizeof(GainCalibration[3]) == CALIBRATION_PAYLOAD_SIZE,
|
||||
"Gain calibration preference layout must remain compatible");
|
||||
|
||||
enum class OffsetCalibrationType : uint8_t {
|
||||
OFFSET_CALIBRATION_TYPE_VOLTAGE_CURRENT,
|
||||
OFFSET_CALIBRATION_TYPE_POWER,
|
||||
};
|
||||
|
||||
struct OffsetRestoreState {
|
||||
bool restored;
|
||||
bool values_verified;
|
||||
};
|
||||
|
||||
inline OffsetRestoreState resolve_offset_restore_state(bool has_stored_values, bool initial_values_verified,
|
||||
bool fallback_values_verified) {
|
||||
if (initial_values_verified)
|
||||
return {has_stored_values, true};
|
||||
return {false, fallback_values_verified};
|
||||
}
|
||||
|
||||
inline void prepare_offset_rollback(const OffsetCalibration (&previous)[3], bool had_stored_values,
|
||||
OffsetCalibration (&rollback)[3]) {
|
||||
for (uint8_t phase = 0; phase < 3; phase++)
|
||||
rollback[phase] = had_stored_values ? previous[phase] : OffsetCalibration{};
|
||||
}
|
||||
|
||||
class ATM90E32Component final : public PollingComponent,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_1MHZ> {
|
||||
@@ -121,6 +118,13 @@ class ATM90E32Component final : public PollingComponent,
|
||||
this->has_config_reactive_power_offset_[phase] = true;
|
||||
}
|
||||
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
|
||||
void set_threshold_voltage_sag_v(float voltage) { this->threshold_voltage_sag_v_ = voltage; }
|
||||
void set_threshold_voltage_peak_v(float voltage) { this->threshold_voltage_peak_v_ = voltage; }
|
||||
void set_threshold_current_peak_a(float current) { this->threshold_current_peak_a_ = current; }
|
||||
void set_frequency_thresholds(uint16_t low, uint16_t high) {
|
||||
this->frequency_low_threshold_ = low;
|
||||
this->frequency_high_threshold_ = high;
|
||||
}
|
||||
void set_line_freq(int freq) { line_freq_ = freq; }
|
||||
void set_current_phases(int phases) { current_phases_ = phases; }
|
||||
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
|
||||
@@ -156,18 +160,18 @@ class ATM90E32Component final : public PollingComponent,
|
||||
return 5.0f; // Default current
|
||||
#endif
|
||||
}
|
||||
bool using_saved_calibrations_ = false; // Track if stored calibrations are being used
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void check_phase_status();
|
||||
void check_freq_status();
|
||||
void check_over_current();
|
||||
void set_phase_status_text_sensor(uint8_t phase, text_sensor::TextSensor *sensor) {
|
||||
this->phase_status_text_sensor_[phase] = sensor;
|
||||
}
|
||||
#endif
|
||||
uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier);
|
||||
static uint16_t calculate_voltage_threshold_from_volts(float voltage_rms, uint16_t ugain);
|
||||
static uint16_t calculate_current_threshold_from_amps(float current_rms, uint16_t igain);
|
||||
|
||||
protected:
|
||||
friend class ATM90E32ThresholdRegisters;
|
||||
#ifdef USE_NUMBER
|
||||
number::Number *ref_voltages_[3]{nullptr, nullptr, nullptr};
|
||||
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
|
||||
@@ -205,12 +209,21 @@ class ATM90E32Component final : public PollingComponent,
|
||||
void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; };
|
||||
void restore_offset_calibrations_(OffsetCalibrationType type);
|
||||
void restore_gain_calibrations_();
|
||||
void save_gain_calibration_to_memory_();
|
||||
void finish_offset_calibration_(const OffsetCalibration (&previous)[3], bool previous_restored,
|
||||
bool previous_using_saved, OffsetCalibrationType type);
|
||||
void finish_gain_calibration_(const GainCalibration (&previous)[3]);
|
||||
void finish_offset_calibration_(const OffsetCalibration (&previous)[3], OffsetCalibrationType type);
|
||||
// Persists verified register values; otherwise rollback() rewrites the previous values and returns their readback.
|
||||
// name starts a sentence; noun is used mid-sentence, keeping the log text the calibration helper matches.
|
||||
template<typename T, typename F>
|
||||
void finish_calibration_(const LogString *name, const LogString *noun, ESPPreferenceObject &preference,
|
||||
const T (&values)[3], bool writes_verified, bool &has_stored, bool &restored,
|
||||
bool (&mismatches)[3], F &&rollback);
|
||||
bool clear_calibration_pref_(ESPPreferenceObject &preference, bool &has_stored, bool &restored,
|
||||
bool (&mismatches)[3]);
|
||||
void configure_chip_();
|
||||
void write_offsets_to_registers_(uint8_t phase, int16_t first_offset, int16_t second_offset,
|
||||
OffsetCalibrationType type);
|
||||
void write_gains_to_registers_();
|
||||
void write_thresholds_();
|
||||
bool verify_gain_writes_();
|
||||
bool verify_offset_writes_(OffsetCalibrationType type);
|
||||
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
|
||||
@@ -256,11 +269,7 @@ class ATM90E32Component final : public PollingComponent,
|
||||
OffsetCalibration power_offset_phase_[3];
|
||||
OffsetCalibration config_power_offset_phase_[3];
|
||||
|
||||
struct GainCalibration {
|
||||
uint16_t voltage_gain{1};
|
||||
uint16_t current_gain{1};
|
||||
} gain_phase_[3];
|
||||
|
||||
GainCalibration gain_phase_[3];
|
||||
GainCalibration config_gain_phase_[3];
|
||||
|
||||
bool has_config_voltage_offset_[3]{false, false, false};
|
||||
@@ -274,6 +283,8 @@ class ATM90E32Component final : public PollingComponent,
|
||||
ESPPreferenceObject power_offset_pref_;
|
||||
ESPPreferenceObject gain_calibration_pref_;
|
||||
|
||||
friend class ATM90E32CalibrationTestAccess;
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *phase_status_text_sensor_[3]{nullptr};
|
||||
#endif
|
||||
@@ -284,13 +295,23 @@ class ATM90E32Component final : public PollingComponent,
|
||||
bool peak_current_signed_{false};
|
||||
bool enable_offset_calibration_{false};
|
||||
bool enable_gain_calibration_{false};
|
||||
float threshold_voltage_sag_v_{0.0f};
|
||||
float threshold_voltage_peak_v_{0.0f};
|
||||
float threshold_current_peak_a_{NAN}; // NAN keeps the legacy OITH register and 65.53 A software limit
|
||||
uint16_t frequency_low_threshold_{0};
|
||||
uint16_t frequency_high_threshold_{0};
|
||||
const char *instance_id_{nullptr};
|
||||
bool has_stored_offset_calibration_{false};
|
||||
bool has_stored_power_offset_calibration_{false};
|
||||
bool has_stored_gain_calibration_{false};
|
||||
bool restored_offset_calibration_{false};
|
||||
bool restored_power_offset_calibration_{false};
|
||||
bool restored_gain_calibration_{false};
|
||||
bool calibration_message_printed_{false};
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool use_voltage_threshold_flags_{true};
|
||||
bool use_current_threshold_flags_{true};
|
||||
#endif
|
||||
bool offset_calibration_mismatch_[3]{false, false, false};
|
||||
bool power_offset_calibration_mismatch_[3]{false, false, false};
|
||||
bool gain_calibration_mismatch_[3]{false, false, false};
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_SCALE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from ..sensor import ATM90E32Component
|
||||
from .sensor import ATM90E32Component
|
||||
|
||||
CONF_RUN_GAIN_CALIBRATION = "run_gain_calibration"
|
||||
CONF_CLEAR_GAIN_CALIBRATION = "clear_gain_calibration"
|
||||
@@ -1,3 +1,6 @@
|
||||
import logging
|
||||
import math
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor, spi
|
||||
import esphome.config_validation as cv
|
||||
@@ -45,6 +48,8 @@ from esphome.types import ConfigType
|
||||
|
||||
from . import atm90e32_ns
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_CHIP_TEMPERATURE = "chip_temperature"
|
||||
CONF_GAIN_PGA = "gain_pga"
|
||||
CONF_CURRENT_PHASES = "current_phases"
|
||||
@@ -61,6 +66,16 @@ CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration"
|
||||
CONF_ENABLE_GAIN_CALIBRATION = "enable_gain_calibration"
|
||||
CONF_PHASE_STATUS = "phase_status"
|
||||
CONF_FREQUENCY_STATUS = "frequency_status"
|
||||
CONF_THRESHOLDS = "thresholds"
|
||||
CONF_ALLOW_UNSAFE_THRESHOLDS = "allow_unsafe_thresholds"
|
||||
CONF_VOLTAGE_NOMINAL = "voltage_nominal"
|
||||
CONF_VOLTAGE_SAG_PCT = "voltage_sag_pct"
|
||||
CONF_VOLTAGE_PEAK_PCT = "voltage_peak_pct"
|
||||
CONF_VOLTAGE_SAG_V = "voltage_sag_v"
|
||||
CONF_VOLTAGE_PEAK_V = "voltage_peak_v"
|
||||
CONF_CURRENT_PEAK = "current_peak"
|
||||
CONF_FREQUENCY_LOW_HZ = "frequency_low_hz"
|
||||
CONF_FREQUENCY_HIGH_HZ = "frequency_high_hz"
|
||||
UNIT_DEG = "degrees"
|
||||
LINE_FREQS = {
|
||||
"50HZ": 50,
|
||||
@@ -76,6 +91,13 @@ PGA_GAINS = {
|
||||
"4X": 0x2A,
|
||||
}
|
||||
|
||||
DEFAULT_VOLTAGE_SAG_PCT = 0.78
|
||||
DEFAULT_VOLTAGE_PEAK_PCT = 1.22
|
||||
# IRMS saturates at 65.535 A, so a higher limit could never be exceeded.
|
||||
MAX_CURRENT_PEAK_A = 65.53
|
||||
MAX_FREQUENCY_HZ = 655.35
|
||||
DEFAULT_FREQUENCY_THRESHOLD_BAND_HZ = 3.0
|
||||
|
||||
ATM90E32Component = atm90e32_ns.class_(
|
||||
"ATM90E32Component", cg.PollingComponent, spi.SPIDevice
|
||||
)
|
||||
@@ -156,7 +178,158 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
|
||||
POSITIVE_VOLTAGE = cv.All(cv.voltage, cv.positive_not_null_float)
|
||||
THRESHOLD_PERCENT = cv.All(
|
||||
cv.unbounded_percentage, cv.float_range(min=0, min_included=False, max=2.0)
|
||||
)
|
||||
REGISTER_FREQUENCY = cv.All(
|
||||
cv.frequency, cv.float_range(min=0, min_included=False, max=MAX_FREQUENCY_HZ)
|
||||
)
|
||||
|
||||
THRESHOLDS_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ALLOW_UNSAFE_THRESHOLDS, default=False): cv.boolean,
|
||||
cv.Optional(CONF_VOLTAGE_NOMINAL): POSITIVE_VOLTAGE,
|
||||
cv.Exclusive(CONF_VOLTAGE_SAG_PCT, "voltage_sag_threshold"): THRESHOLD_PERCENT,
|
||||
cv.Exclusive(CONF_VOLTAGE_SAG_V, "voltage_sag_threshold"): POSITIVE_VOLTAGE,
|
||||
cv.Exclusive(
|
||||
CONF_VOLTAGE_PEAK_PCT, "voltage_peak_threshold"
|
||||
): THRESHOLD_PERCENT,
|
||||
cv.Exclusive(CONF_VOLTAGE_PEAK_V, "voltage_peak_threshold"): POSITIVE_VOLTAGE,
|
||||
cv.Optional(CONF_CURRENT_PEAK): cv.All(
|
||||
cv.current,
|
||||
cv.float_range(min=0, min_included=False, max=MAX_CURRENT_PEAK_A),
|
||||
),
|
||||
cv.Optional(CONF_FREQUENCY_LOW_HZ): REGISTER_FREQUENCY,
|
||||
cv.Optional(CONF_FREQUENCY_HIGH_HZ): REGISTER_FREQUENCY,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _default_voltage_nominal(line_frequency: int) -> float:
|
||||
return 120.0 if line_frequency == 60 else 220.0
|
||||
|
||||
|
||||
def _resolve_thresholds(config: ConfigType) -> ConfigType:
|
||||
line_frequency = config[CONF_LINE_FREQUENCY].enum_value
|
||||
resolved = dict(config[CONF_THRESHOLDS])
|
||||
nominal_voltage = resolved.setdefault(
|
||||
CONF_VOLTAGE_NOMINAL, _default_voltage_nominal(line_frequency)
|
||||
)
|
||||
for volts_key, pct_key, default_pct in (
|
||||
(CONF_VOLTAGE_SAG_V, CONF_VOLTAGE_SAG_PCT, DEFAULT_VOLTAGE_SAG_PCT),
|
||||
(CONF_VOLTAGE_PEAK_V, CONF_VOLTAGE_PEAK_PCT, DEFAULT_VOLTAGE_PEAK_PCT),
|
||||
):
|
||||
resolved.setdefault(
|
||||
volts_key, nominal_voltage * resolved.get(pct_key, default_pct)
|
||||
)
|
||||
resolved.setdefault(
|
||||
CONF_FREQUENCY_LOW_HZ, line_frequency - DEFAULT_FREQUENCY_THRESHOLD_BAND_HZ
|
||||
)
|
||||
resolved.setdefault(
|
||||
CONF_FREQUENCY_HIGH_HZ, line_frequency + DEFAULT_FREQUENCY_THRESHOLD_BAND_HZ
|
||||
)
|
||||
return resolved
|
||||
|
||||
|
||||
def _validate_thresholds(config: ConfigType) -> ConfigType:
|
||||
configured = config[CONF_THRESHOLDS]
|
||||
thresholds = _resolve_thresholds(config)
|
||||
nominal_voltage = thresholds[CONF_VOLTAGE_NOMINAL]
|
||||
sag_voltage = thresholds[CONF_VOLTAGE_SAG_V]
|
||||
peak_voltage = thresholds[CONF_VOLTAGE_PEAK_V]
|
||||
|
||||
if not thresholds[CONF_ALLOW_UNSAFE_THRESHOLDS]:
|
||||
for key, ratio, low, high in (
|
||||
(CONF_VOLTAGE_SAG_V, sag_voltage / nominal_voltage, 0.50, 0.99),
|
||||
(CONF_VOLTAGE_PEAK_V, peak_voltage / nominal_voltage, 1.01, 1.50),
|
||||
):
|
||||
if not low <= ratio <= high:
|
||||
raise cv.Invalid(
|
||||
f"thresholds.{key} must be between {low:.0%} and {high:.0%} of voltage_nominal; "
|
||||
"set thresholds.allow_unsafe_thresholds: true to override."
|
||||
)
|
||||
|
||||
if not sag_voltage < nominal_voltage < peak_voltage:
|
||||
raise cv.Invalid(
|
||||
"Invalid voltage thresholds: expected sag < nominal < peak "
|
||||
f"(got {sag_voltage:.3f}V < {nominal_voltage:.3f}V < {peak_voltage:.3f}V)."
|
||||
)
|
||||
|
||||
frequency_low = thresholds[CONF_FREQUENCY_LOW_HZ]
|
||||
frequency_high = thresholds[CONF_FREQUENCY_HIGH_HZ]
|
||||
|
||||
if round(frequency_low * 100) >= round(frequency_high * 100):
|
||||
raise cv.Invalid(
|
||||
"thresholds.frequency_low_hz must be lower than thresholds.frequency_high_hz "
|
||||
"at the register resolution of 0.01Hz."
|
||||
)
|
||||
|
||||
# Each limit counts as set by the user when any key it is derived from was given.
|
||||
for gain_key, scale, registers in (
|
||||
(
|
||||
CONF_GAIN_VOLTAGE,
|
||||
100,
|
||||
(
|
||||
(
|
||||
CONF_VOLTAGE_SAG_V,
|
||||
min,
|
||||
(CONF_VOLTAGE_NOMINAL, CONF_VOLTAGE_SAG_PCT, CONF_VOLTAGE_SAG_V),
|
||||
),
|
||||
(
|
||||
CONF_VOLTAGE_PEAK_V,
|
||||
max,
|
||||
(CONF_VOLTAGE_NOMINAL, CONF_VOLTAGE_PEAK_PCT, CONF_VOLTAGE_PEAK_V),
|
||||
),
|
||||
),
|
||||
),
|
||||
(CONF_GAIN_CT, 1000, ((CONF_CURRENT_PEAK, max, (CONF_CURRENT_PEAK,)),)),
|
||||
):
|
||||
explicit = {
|
||||
key: any(source in configured for source in sources)
|
||||
for key, _, sources in registers
|
||||
}
|
||||
# A legacy config without current_peak keeps the fixed OITH register value.
|
||||
if gain_key == CONF_GAIN_CT and not explicit[CONF_CURRENT_PEAK]:
|
||||
continue
|
||||
gains = {
|
||||
config[phase][gain_key]
|
||||
for phase in (CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C)
|
||||
if phase in config
|
||||
}
|
||||
if not gains or 0 in gains:
|
||||
if not any(explicit.values()):
|
||||
continue
|
||||
raise cv.Invalid(
|
||||
f"Thresholds require at least one phase and a nonzero {gain_key} on every configured phase."
|
||||
)
|
||||
for key, select_gain, _ in registers:
|
||||
gain = select_gain(gains)
|
||||
limit = 65535 * gain / (scale * math.sqrt(2) * 16384)
|
||||
if thresholds[key] <= limit:
|
||||
continue
|
||||
message = (
|
||||
f"thresholds.{key} ({thresholds[key]:.1f}) cannot fit the 16-bit threshold register with "
|
||||
f"{gain_key}: {gain}; the highest value that fits is {limit:.1f}"
|
||||
)
|
||||
if explicit[key]:
|
||||
raise cv.Invalid(
|
||||
f"{message}. Increase {gain_key} or lower the threshold."
|
||||
)
|
||||
# Defaults are only warned about so existing configs keep building; the register is clamped.
|
||||
_LOGGER.warning(
|
||||
"%s. The default is clamped to the register maximum, so this alarm cannot trigger; "
|
||||
"set thresholds.%s to at most %.1f to use it.",
|
||||
message,
|
||||
key,
|
||||
limit,
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ATM90E32Component),
|
||||
@@ -185,10 +358,12 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_OFFSET_CALIBRATION, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_GAIN_CALIBRATION, default=False): cv.boolean,
|
||||
cv.Optional(CONF_THRESHOLDS, default={}): THRESHOLDS_SCHEMA,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(spi.spi_device_schema())
|
||||
.extend(spi.spi_device_schema()),
|
||||
_validate_thresholds,
|
||||
)
|
||||
|
||||
|
||||
@@ -250,3 +425,15 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))
|
||||
cg.add(var.set_enable_offset_calibration(config[CONF_ENABLE_OFFSET_CALIBRATION]))
|
||||
cg.add(var.set_enable_gain_calibration(config[CONF_ENABLE_GAIN_CALIBRATION]))
|
||||
|
||||
# The user's keys stay as written so `esphome config` output can be loaded again.
|
||||
thresholds = _resolve_thresholds(config)
|
||||
cg.add(var.set_threshold_voltage_sag_v(thresholds[CONF_VOLTAGE_SAG_V]))
|
||||
cg.add(var.set_threshold_voltage_peak_v(thresholds[CONF_VOLTAGE_PEAK_V]))
|
||||
cg.add_optional(var.set_threshold_current_peak_a, thresholds.get(CONF_CURRENT_PEAK))
|
||||
cg.add(
|
||||
var.set_frequency_thresholds(
|
||||
round(thresholds[CONF_FREQUENCY_LOW_HZ] * 100),
|
||||
round(thresholds[CONF_FREQUENCY_HIGH_HZ] * 100),
|
||||
)
|
||||
)
|
||||
+1
-1
@@ -4,7 +4,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from ..sensor import ATM90E32Component
|
||||
from .sensor import ATM90E32Component
|
||||
|
||||
CONF_PHASE_STATUS = "phase_status"
|
||||
CONF_FREQUENCY_STATUS = "frequency_status"
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -19,6 +21,9 @@ static const uint32_t CONNECTION_TIMEOUT_MS = 5000;
|
||||
static const uint8_t MAX_FETCHING_HEADER_ATTEMPTS = 6;
|
||||
|
||||
static const size_t HTTP_STREAM_BUFFER_SIZE = 2048;
|
||||
// esp_http_client_read() blocks until it fills the request; small requests deliver a phrase tail before a pause ends
|
||||
static constexpr size_t HTTP_READ_MAX_BYTES = 1024;
|
||||
static constexpr int HTTP_BODY_TIMEOUT_MS = 100;
|
||||
|
||||
static const uint8_t MAX_REDIRECTIONS = 5;
|
||||
|
||||
@@ -196,10 +201,18 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
|
||||
file_type = this->audio_file_type_;
|
||||
}
|
||||
|
||||
// Keep the longer timeout for connection, headers, and redirects.
|
||||
err = esp_http_client_set_timeout_ms(this->client_, HTTP_BODY_TIMEOUT_MS);
|
||||
if (err != ESP_OK) {
|
||||
this->cleanup_connection_();
|
||||
return err;
|
||||
}
|
||||
|
||||
this->last_data_read_ms_ = millis();
|
||||
|
||||
this->output_transfer_buffer_ = AudioSinkTransferBuffer::create(this->buffer_size_);
|
||||
if (this->output_transfer_buffer_ == nullptr) {
|
||||
this->cleanup_connection_();
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
@@ -254,8 +267,9 @@ AudioReaderState AudioReader::http_read_() {
|
||||
return AudioReaderState::FINISHED;
|
||||
}
|
||||
} else if (this->output_transfer_buffer_->free() > 0) {
|
||||
int received_len = esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(),
|
||||
this->output_transfer_buffer_->free());
|
||||
size_t read_size = std::min(this->output_transfer_buffer_->free(), HTTP_READ_MAX_BYTES);
|
||||
int received_len =
|
||||
esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), read_size);
|
||||
|
||||
if (received_len > 0) {
|
||||
this->output_transfer_buffer_->increase_buffer_length(received_len);
|
||||
@@ -278,6 +292,9 @@ AudioReaderState AudioReader::http_read_() {
|
||||
|
||||
delay(READ_WRITE_TIMEOUT_MS);
|
||||
}
|
||||
} else {
|
||||
// A full buffer means the consumer is behind, not that the server went quiet
|
||||
this->last_data_read_ms_ = millis();
|
||||
}
|
||||
|
||||
return AudioReaderState::READING;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ballu.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::ballu {
|
||||
@@ -90,9 +91,7 @@ void BalluClimate::transmit_state() {
|
||||
for (uint8_t i = 0; i < BALLU_STATE_LENGTH - 1; i++)
|
||||
remote_state[12] += remote_state[i];
|
||||
|
||||
ESP_LOGV(TAG, "Sending: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", remote_state[0],
|
||||
remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6],
|
||||
remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], remote_state[12]);
|
||||
ESP_LOGV_HEX(TAG, remote_state, BALLU_STATE_LENGTH, BALLU_STATE_LENGTH, "Sending: %s");
|
||||
|
||||
// Send code
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
@@ -136,9 +135,7 @@ bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Received: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", remote_state[0],
|
||||
remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6],
|
||||
remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], remote_state[12]);
|
||||
ESP_LOGV_HEX(TAG, remote_state, BALLU_STATE_LENGTH, BALLU_STATE_LENGTH, "Received: %s");
|
||||
|
||||
// verify header remote code
|
||||
if (remote_state[0] != 0xc3)
|
||||
|
||||
@@ -8,14 +8,14 @@ ESPHOME_LOG_TAG(TAG, "bang_bang.climate");
|
||||
BangBangClimate::BangBangClimate() = default;
|
||||
|
||||
void BangBangClimate::setup() {
|
||||
this->sensor_->add_on_state_callback([this](float state) {
|
||||
this->temperature_sensor_->add_on_state_callback([this](float state) {
|
||||
this->current_temperature = state;
|
||||
// control may have changed, recompute
|
||||
this->compute_state_();
|
||||
// current temperature changed, publish state
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_temperature = this->sensor_->state;
|
||||
this->current_temperature = this->temperature_sensor_->state;
|
||||
|
||||
// register for humidity values and get initial state
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
|
||||
@@ -17,13 +17,14 @@ struct BangBangClimateTargetTempConfig {
|
||||
};
|
||||
|
||||
class BangBangClimate final : public climate::Climate, public Component {
|
||||
SUB_SENSOR(temperature)
|
||||
SUB_SENSOR(humidity)
|
||||
|
||||
public:
|
||||
BangBangClimate();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
void set_normal_config(const BangBangClimateTargetTempConfig &normal_config);
|
||||
@@ -47,11 +48,6 @@ class BangBangClimate final : public climate::Climate, public Component {
|
||||
/// Switch the climate device to the given climate mode.
|
||||
void switch_to_action_(climate::ClimateAction action);
|
||||
|
||||
/// The sensor used for getting the current temperature
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
/// The sensor used for getting the current humidity
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
|
||||
/** The trigger to call when the controller should switch to idle mode.
|
||||
*
|
||||
* In idle mode, the controller is assumed to have both heating and cooling disabled.
|
||||
|
||||
@@ -46,7 +46,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = await climate.new_climate(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_sensor, config[CONF_SENSOR])
|
||||
await cg.add_variable(var.set_temperature_sensor, config[CONF_SENSOR])
|
||||
await cg.add_variable(var.set_humidity_sensor, config.get(CONF_HUMIDITY_SENSOR))
|
||||
|
||||
normal_config = BangBangClimateTargetTempConfig(
|
||||
|
||||
@@ -159,33 +159,19 @@ void spi_dma_tx_finish_callback(unsigned int param) {
|
||||
}
|
||||
|
||||
void BekenSPILEDStripLightOutput::setup() {
|
||||
size_t buffer_size = this->get_buffer_size_();
|
||||
size_t dma_buffer_size = (buffer_size * 8) + (2 * 64);
|
||||
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
this->buf_ = allocator.allocate(buffer_size);
|
||||
if (this->buf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Cannot allocate LED buffer!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->effect_data_ = allocator.allocate(this->num_leds_);
|
||||
if (this->effect_data_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Cannot allocate effect data!");
|
||||
this->mark_failed();
|
||||
if (!this->internal_accessor_.allocate(allocator, this->num_leds_)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_ALLOC_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t dma_buffer_size = (this->get_buffer_size_() * 8) + (2 * 64);
|
||||
this->dma_buf_ = allocator.allocate(dma_buffer_size);
|
||||
if (this->dma_buf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Cannot allocate DMA buffer!");
|
||||
this->mark_failed();
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_ALLOC_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
memset(this->buf_, 0, buffer_size);
|
||||
memset(this->effect_data_, 0, this->num_leds_);
|
||||
memset(this->dma_buf_, 0, dma_buffer_size);
|
||||
|
||||
uint32_t value = PCLK_POSI_SPI;
|
||||
@@ -195,22 +181,19 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_UP, &value);
|
||||
|
||||
if (spi_data != nullptr) {
|
||||
ESP_LOGE(TAG, "SPI device already initialized!");
|
||||
this->mark_failed();
|
||||
this->mark_failed(LOG_STR("SPI already initialized"));
|
||||
return;
|
||||
}
|
||||
|
||||
spi_data = (SpiData *) calloc(1, sizeof(SpiData)); // NOLINT(cppcoreguidelines-no-malloc)
|
||||
if (spi_data == nullptr) {
|
||||
ESP_LOGE(TAG, "Cannot allocate spi_data!");
|
||||
this->mark_failed();
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_ALLOC_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
spi_data->dma_tx_semaphore = xSemaphoreCreateBinary();
|
||||
if (spi_data->dma_tx_semaphore == nullptr) {
|
||||
ESP_LOGE(TAG, "TX Semaphore init faild!");
|
||||
this->mark_failed();
|
||||
this->mark_failed(LOG_STR("TX semaphore init failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -291,6 +274,8 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
// T-like variants: No transfer-length register. The SPI controller shifts out
|
||||
// everything the DMA writes into SPI_DAT.
|
||||
#endif
|
||||
|
||||
this->accessor_ = &this->internal_accessor_;
|
||||
}
|
||||
|
||||
void BekenSPILEDStripLightOutput::set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency) {
|
||||
@@ -351,7 +336,7 @@ void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
|
||||
|
||||
size_t buffer_size = this->get_buffer_size_();
|
||||
size_t size = 0;
|
||||
uint8_t *psrc = this->buf_;
|
||||
uint8_t *psrc = this->internal_accessor_.color_data;
|
||||
uint8_t *pdest = this->dma_buf_ + 64;
|
||||
// The 64 byte padding is a workaround for a SPI DMA bug where the
|
||||
// output doesn't exactly start at the beginning of dma_buf_
|
||||
@@ -381,7 +366,8 @@ void BekenSPILEDStripLightOutput::dump_config() {
|
||||
" Channel colors: %s\n"
|
||||
" Max refresh rate: %" PRIu32 "\n"
|
||||
" Number of LEDs: %u",
|
||||
this->channel_colors_.to_string(channel_colors), this->max_refresh_rate_, this->num_leds_);
|
||||
this->internal_accessor_.channel_colors.to_string(channel_colors), this->max_refresh_rate_,
|
||||
this->num_leds_);
|
||||
}
|
||||
|
||||
float BekenSPILEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
@@ -18,33 +18,27 @@ class BekenSPILEDStripLightOutput final : public light::AddressableLight {
|
||||
float get_setup_priority() const override;
|
||||
|
||||
int32_t size() const override { return this->num_leds_; }
|
||||
light::LightTraits get_traits() override { return this->channel_colors_.traits(); }
|
||||
light::LightTraits get_traits() override { return this->internal_accessor_.channel_colors.traits(); }
|
||||
|
||||
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
|
||||
void set_channel_colors(light::ChannelColors channel_colors) { this->channel_colors_ = channel_colors; }
|
||||
void set_channel_colors(light::ChannelColors channel_colors) {
|
||||
this->internal_accessor_.channel_colors = channel_colors;
|
||||
}
|
||||
|
||||
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
|
||||
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
|
||||
|
||||
void set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency);
|
||||
|
||||
void clear_effect_data() override {
|
||||
if (this->effect_data_ != nullptr)
|
||||
memset(this->effect_data_, 0, this->num_leds_);
|
||||
}
|
||||
void clear_effect_data() override { this->internal_accessor_.clear_effect_data(this->num_leds_); }
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
light::ESPColorView get_view_internal(int32_t index) const override {
|
||||
return this->channel_colors_.view(this->buf_, this->effect_data_, index, &this->correction_);
|
||||
}
|
||||
size_t get_buffer_size_() const { return this->num_leds_ * this->internal_accessor_.channel_colors.bytes_per_led(); }
|
||||
|
||||
size_t get_buffer_size_() const { return this->num_leds_ * this->channel_colors_.bytes_per_led(); }
|
||||
|
||||
uint8_t *buf_{nullptr};
|
||||
uint8_t *effect_data_{nullptr};
|
||||
light::CorrectedBufferColorAccessor internal_accessor_{nullptr, nullptr, {0, 1, 2}, &this->correction_};
|
||||
uint8_t *dma_buf_{nullptr};
|
||||
|
||||
uint8_t pin_;
|
||||
@@ -53,7 +47,6 @@ class BekenSPILEDStripLightOutput final : public light::AddressableLight {
|
||||
uint32_t spi_frequency_{6666666};
|
||||
uint8_t bit0_{0xE0};
|
||||
uint8_t bit1_{0xFC};
|
||||
light::ChannelColors channel_colors_{0, 1, 2, light::ChannelColors::NO_WHITE};
|
||||
|
||||
uint32_t last_refresh_{0};
|
||||
uint32_t max_refresh_rate_{0};
|
||||
|
||||
@@ -3,7 +3,7 @@ from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_BH1745_ID, BH1745Component
|
||||
from . import CONF_BH1745_ID, BH1745Component
|
||||
|
||||
DEPENDENCIES = ["bh1745"]
|
||||
|
||||
@@ -162,8 +162,8 @@ class SettleFilter : public Filter {
|
||||
protected:
|
||||
void reset() override;
|
||||
|
||||
bool steady_{true}; // Before delay_ so it fills the Filter base's tail padding
|
||||
TemplatableFn<uint32_t> delay_{};
|
||||
bool steady_{true};
|
||||
};
|
||||
|
||||
} // namespace esphome::binary_sensor
|
||||
|
||||
@@ -3,8 +3,8 @@ import esphome.config_validation as cv
|
||||
from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_BL0940_ID
|
||||
from ..sensor import BL0940
|
||||
from . import CONF_BL0940_ID
|
||||
from .sensor import BL0940
|
||||
|
||||
CONFIG_SCHEMA = button.button_schema(
|
||||
button.ApplyButton,
|
||||
@@ -5,7 +5,14 @@ from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32_ble, esp32_ble_client, esp32_ble_tracker
|
||||
from esphome.components.const import CONF_ACCEPT, CONF_ON_NUMERIC_COMPARISON_REQUEST
|
||||
from esphome.components.ble_device_base import validate_passkey
|
||||
from esphome.components.const import (
|
||||
CONF_ACCEPT,
|
||||
CONF_ON_NUMERIC_COMPARISON_REQUEST,
|
||||
CONF_ON_PASSKEY_NOTIFICATION,
|
||||
CONF_ON_PASSKEY_REQUEST,
|
||||
CONF_PASSKEY,
|
||||
)
|
||||
from esphome.components.esp32_ble import BTLoggers
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -101,9 +108,6 @@ BLERemoveBondAction = ble_client_ns.class_(
|
||||
"BLEClientRemoveBondAction", automation.Action
|
||||
)
|
||||
|
||||
CONF_PASSKEY = "passkey"
|
||||
CONF_ON_PASSKEY_REQUEST = "on_passkey_request"
|
||||
CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification"
|
||||
CONF_AUTO_CONNECT = "auto_connect"
|
||||
|
||||
MULTI_CONF = True
|
||||
@@ -205,7 +209,7 @@ BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema(
|
||||
BLE_PASSKEY_REPLY_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
|
||||
cv.Required(CONF_PASSKEY): cv.templatable(cv.int_range(min=0, max=999999)),
|
||||
cv.Required(CONF_PASSKEY): cv.templatable(validate_passkey),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -46,6 +46,9 @@ DOMAIN = "ble_device_base"
|
||||
|
||||
CONF_BLE_HUB_ID = "ble_hub_id"
|
||||
|
||||
# A six digit pairing passkey
|
||||
validate_passkey = cv.int_range(min=0, max=999999)
|
||||
|
||||
# Number of parsed-advertisement listeners registered in this build; read via
|
||||
# cg.get_slot_count() by esp32_ble_tracker's feature coupling.
|
||||
LISTENER_COUNT_DEFINE = "ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT"
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Actions shared by the BLE server platforms (esp32_ble_server, zephyr_ble_server).
|
||||
|
||||
An action name lives in one registry, so a platform cannot register it on its own without
|
||||
replacing another platform's entry. Each server instead adds its schema and builder here,
|
||||
and the action validates and builds with the one for the target platform.
|
||||
"""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import TemplateArgsType
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import ConfigType
|
||||
|
||||
ActionBuilder = Callable[
|
||||
[ConfigType, ID, cg.TemplateArguments, TemplateArgsType], Awaitable[cg.MockObj]
|
||||
]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _PlatformAction:
|
||||
is_target: Callable[[], bool]
|
||||
schema: cv.Schema | cv.All
|
||||
builder: ActionBuilder
|
||||
|
||||
|
||||
_ACTIONS: dict[str, list[_PlatformAction]] = {}
|
||||
|
||||
|
||||
def _for_target(name: str) -> _PlatformAction:
|
||||
for platform in _ACTIONS[name]:
|
||||
if platform.is_target():
|
||||
return platform
|
||||
raise cv.Invalid(f"{name} is not supported on this platform")
|
||||
|
||||
|
||||
def _keys_schema(schema: cv.Schema | cv.All) -> cv.Schema:
|
||||
"""The dict schema of ``schema``, or of a ``cv.All`` that adds validators after it."""
|
||||
if isinstance(schema, cv.All):
|
||||
return next(v for v in schema.validators if isinstance(v, cv.Schema))
|
||||
return schema
|
||||
|
||||
|
||||
def _merged_schema(platforms: list[_PlatformAction]) -> cv.Schema:
|
||||
"""All platforms' keys, with a key that not every platform takes shown as optional."""
|
||||
schemas = [_keys_schema(p.schema).schema for p in platforms]
|
||||
shared = set.intersection(*({str(key) for key in s} for s in schemas))
|
||||
merged: dict[Any, Any] = {}
|
||||
seen: set[str] = set()
|
||||
for schema in schemas:
|
||||
for key, validator in schema.items():
|
||||
if (text := str(key)) in seen:
|
||||
continue
|
||||
seen.add(text)
|
||||
merged[key if text in shared else cv.Optional(text)] = validator
|
||||
return cv.Schema(merged)
|
||||
|
||||
|
||||
def register_server_action(
|
||||
name: str,
|
||||
is_target: Callable[[], bool],
|
||||
schema: cv.Schema | cv.All,
|
||||
builder: ActionBuilder,
|
||||
) -> None:
|
||||
"""Add a server platform's version of the action ``name``.
|
||||
|
||||
``is_target`` tells whether the build is for this platform, e.g. ``lambda: CORE.is_esp32``.
|
||||
"""
|
||||
if (platforms := _ACTIONS.get(name)) is None:
|
||||
platforms = _ACTIONS[name] = []
|
||||
|
||||
# The schema depends on the target, so the language schema shows every platform's keys
|
||||
@schema_extractor("schema")
|
||||
def validate(value: Any) -> ConfigType:
|
||||
if value is SCHEMA_EXTRACT:
|
||||
return _merged_schema(platforms)
|
||||
return _for_target(name).schema(value)
|
||||
|
||||
async def dispatch(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> cg.MockObj:
|
||||
return await _for_target(name).builder(
|
||||
config, action_id, template_arg, args
|
||||
)
|
||||
|
||||
automation.register_action(
|
||||
name, automation.ApplyAction, validate, synchronous=True
|
||||
)(dispatch)
|
||||
platforms.append(_PlatformAction(is_target, schema, builder))
|
||||
|
||||
|
||||
def register_server_apply_action(
|
||||
name: str,
|
||||
is_target: Callable[[], bool],
|
||||
schema: cv.Schema | cv.All,
|
||||
*fields: automation.ApplyField | automation.ApplyCall,
|
||||
) -> None:
|
||||
"""Add a server platform's version of the apply action ``name``.
|
||||
|
||||
The other arguments are those of ``register_server_action`` and
|
||||
``automation.register_apply_action``.
|
||||
"""
|
||||
register_server_action(
|
||||
name,
|
||||
is_target,
|
||||
schema,
|
||||
automation.apply_action_builder(name, schema, *fields),
|
||||
)
|
||||
@@ -635,16 +635,8 @@ void BluetoothProxy::loop() {
|
||||
#endif
|
||||
|
||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
// The API subscriber is gone: tear down any connections it left behind
|
||||
// (disconnect() on an already-disconnecting slot is a no-op).
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// The API subscriber is gone: tear down any connections it left behind.
|
||||
this->disconnect_all_connections_();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -737,6 +729,17 @@ void BluetoothProxy::reset_owed_replies_() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void BluetoothProxy::disconnect_all_connections_() {
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags) {
|
||||
if (api_connection != this->api_connection_) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
@@ -754,8 +757,11 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
|
||||
// Stale retry latches belong to the previous subscriber's session; a
|
||||
// re-subscribe by the current one keeps what it is still owed.
|
||||
this->reset_owed_replies_();
|
||||
this->api_connection_ = api_connection;
|
||||
// Held links belong to a client that no longer gets their events; free them so idle-only
|
||||
// advertisers are seen again. After the swap, so a sync slot free reports to the new client.
|
||||
this->disconnect_all_connections_();
|
||||
}
|
||||
this->api_connection_ = api_connection;
|
||||
#ifdef USE_BLE_SCANNER_STATE_CALLBACK
|
||||
// get_scanner_state() is part of the push-hub surface (see BLEHubContract).
|
||||
this->send_scanner_state_(this->hub_->get_scanner_state());
|
||||
|
||||
@@ -328,6 +328,8 @@ class BluetoothProxy final : public Component {
|
||||
/// Drops state only, never sends: api_connection_ is the departing
|
||||
/// subscriber on subscribe and nullptr on unsubscribe.
|
||||
void reset_owed_replies_();
|
||||
/// Tear down every held link; repeat calls are no-ops.
|
||||
void disconnect_all_connections_();
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
/// Report a reply we deliberately do not latch, so no drop is silent.
|
||||
void log_reply_dropped_(const char *what, uint64_t address);
|
||||
|
||||
@@ -171,6 +171,10 @@ inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return (
|
||||
void BME280Component::update() {
|
||||
// Enable sensor
|
||||
ESP_LOGV(TAG, "Sending conversion request");
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, this->humidity_oversampling_ & 0b111)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
uint8_t meas_value = 0;
|
||||
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||
@@ -210,12 +214,11 @@ void BME280Component::update() {
|
||||
});
|
||||
}
|
||||
float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
|
||||
int32_t adc = encode_uint24(data[3], data[4], data[5]);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000) {
|
||||
// temperature was disabled
|
||||
if (this->temperature_oversampling_ == BME280_OVERSAMPLING_NONE) {
|
||||
return NAN;
|
||||
}
|
||||
int32_t adc = encode_uint24(data[3], data[4], data[5]);
|
||||
adc >>= 4;
|
||||
|
||||
const int32_t t1 = this->calibration_.t1;
|
||||
const int32_t t2 = this->calibration_.t2;
|
||||
@@ -230,12 +233,11 @@ float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
|
||||
}
|
||||
|
||||
float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
|
||||
int32_t adc = encode_uint24(data[0], data[1], data[2]);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000) {
|
||||
// pressure was disabled
|
||||
if (this->pressure_oversampling_ == BME280_OVERSAMPLING_NONE) {
|
||||
return NAN;
|
||||
}
|
||||
int32_t adc = encode_uint24(data[0], data[1], data[2]);
|
||||
adc >>= 4;
|
||||
const int64_t p1 = this->calibration_.p1;
|
||||
const int64_t p2 = this->calibration_.p2;
|
||||
const int64_t p3 = this->calibration_.p3;
|
||||
@@ -267,11 +269,10 @@ float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
|
||||
}
|
||||
|
||||
float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) {
|
||||
uint16_t const raw_adc = encode_uint16(data[6], data[7]);
|
||||
if (raw_adc == 0x8000)
|
||||
if (this->humidity_oversampling_ == BME280_OVERSAMPLING_NONE) {
|
||||
return NAN;
|
||||
|
||||
int32_t const adc = raw_adc;
|
||||
}
|
||||
int32_t const adc = encode_uint16(data[6], data[7]);
|
||||
|
||||
const int32_t h1 = this->calibration_.h1;
|
||||
const int32_t h2 = this->calibration_.h2;
|
||||
|
||||
@@ -148,16 +148,15 @@ void BMP280Component::update() {
|
||||
}
|
||||
|
||||
float BMP280Component::read_temperature_(int32_t *t_fine) {
|
||||
if (this->temperature_oversampling_ == BMP280_OVERSAMPLING_NONE) {
|
||||
return NAN;
|
||||
}
|
||||
uint8_t data[3]{};
|
||||
if (!this->bmp_read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
|
||||
return NAN;
|
||||
ESP_LOGV(TAG, "Read temperature data, raw: %02X %02X %02X", data[0], data[1], data[2]);
|
||||
int32_t adc = encode_uint24(data[0], data[1], data[2]);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000) {
|
||||
// temperature was disabled
|
||||
return NAN;
|
||||
}
|
||||
|
||||
const int32_t t1 = this->calibration_.t1;
|
||||
const int32_t t2 = this->calibration_.t2;
|
||||
@@ -172,15 +171,14 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
|
||||
}
|
||||
|
||||
float BMP280Component::read_pressure_(int32_t t_fine) {
|
||||
if (this->pressure_oversampling_ == BMP280_OVERSAMPLING_NONE) {
|
||||
return NAN;
|
||||
}
|
||||
uint8_t data[3];
|
||||
if (!this->bmp_read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
|
||||
return NAN;
|
||||
int32_t adc = encode_uint24(data[0], data[1], data[2]);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000) {
|
||||
// pressure was disabled
|
||||
return NAN;
|
||||
}
|
||||
const int64_t p1 = this->calibration_.p1;
|
||||
const int64_t p2 = this->calibration_.p2;
|
||||
const int64_t p3 = this->calibration_.p3;
|
||||
|
||||
@@ -249,13 +249,8 @@ void BMP581Component::update() {
|
||||
// 4) Publish measurements to sensor(s), if applicable //
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
if (this->temperature_sensor_) {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
|
||||
if (this->pressure_sensor_) {
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
}
|
||||
PUBLISH_STATE(this->temperature_sensor_, temperature);
|
||||
PUBLISH_STATE(this->pressure_sensor_, pressure);
|
||||
|
||||
this->status_clear_warning();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Any
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome.components.const import CONF_CHARGE_ENABLED, CONF_INPUT_CURRENT_LIMIT
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_UPDATE_INTERVAL, CONF_WATCHDOG
|
||||
from esphome.core import CORE, ID, EsphomeError, Lambda, TimePeriod
|
||||
@@ -19,7 +20,6 @@ CONF_BQ25186_ID = "bq25186_id"
|
||||
CONF_CONFIGURE_ON_BOOT = "configure_on_boot"
|
||||
CONF_BATTERY_REGULATION_VOLTAGE = "battery_regulation_voltage"
|
||||
CONF_FAST_CHARGE_CURRENT = "fast_charge_current"
|
||||
CONF_CHARGE_ENABLED = "charge_enabled"
|
||||
CONF_FLASH_CHARGING_MODE = "flash_charging_mode"
|
||||
CONF_PRECHARGE_IS_ITERM = "precharge_is_iterm"
|
||||
CONF_TERMINATION_PERCENT = "termination_percent"
|
||||
@@ -38,7 +38,6 @@ CONF_SAFETY_TIMER = "safety_timer"
|
||||
CONF_LONG_PRESS_DURATION = "long_press_duration"
|
||||
CONF_HW_RESET_REQUIRES_VIN = "hw_reset_requires_vin"
|
||||
CONF_AUTOWAKE = "autowake"
|
||||
CONF_INPUT_CURRENT_LIMIT = "input_current_limit"
|
||||
CONF_PUSH_BUTTON_LONG_PRESS_ACTION = "push_button_long_press_action"
|
||||
CONF_WAKE1_TIMER = "wake1_timer"
|
||||
CONF_WAKE2_TIMER = "wake2_timer"
|
||||
|
||||
@@ -72,15 +72,12 @@ void CAP1188Component::dump_config() {
|
||||
void CAP1188Component::loop() {
|
||||
uint8_t touched = 0;
|
||||
|
||||
this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1);
|
||||
// On a failed read the channels keep their state instead of reporting a release
|
||||
if (!this->status_comm_result(this->read_byte(CAP1188_SENSOR_INPUT_STATUS, &touched)))
|
||||
return;
|
||||
|
||||
if (touched) {
|
||||
uint8_t data = 0;
|
||||
this->read_register(CAP1188_MAIN, &data, 1);
|
||||
data = data & ~CAP1188_MAIN_INT;
|
||||
|
||||
this->write_register(CAP1188_MAIN, &data, 1);
|
||||
}
|
||||
if (touched)
|
||||
this->update_register(CAP1188_MAIN, CAP1188_MAIN_INT, 0);
|
||||
|
||||
for (auto *channel : this->channels_) {
|
||||
channel->process(touched);
|
||||
|
||||
@@ -21,6 +21,8 @@ class ClimateIR : public Component,
|
||||
public climate::Climate,
|
||||
public remote_base::RemoteReceiverListener,
|
||||
public remote_base::RemoteTransmittable {
|
||||
SUB_SENSOR(humidity)
|
||||
|
||||
public:
|
||||
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
|
||||
bool supports_dry = false, bool supports_fan_only = false,
|
||||
@@ -47,7 +49,6 @@ class ClimateIR : public Component,
|
||||
this->set_mode_supported_(climate::CLIMATE_MODE_HEAT_COOL, supports_heat_cool);
|
||||
}
|
||||
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
|
||||
|
||||
protected:
|
||||
float minimum_temperature_, maximum_temperature_, temperature_step_;
|
||||
@@ -84,7 +85,6 @@ class ClimateIR : public Component,
|
||||
climate::ClimatePresetMask presets_{};
|
||||
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace esphome::climate_ir
|
||||
@@ -42,7 +42,10 @@ void CM1106Component::update() {
|
||||
}
|
||||
|
||||
if (response[0] != 0x16 || response[1] != 0x05 || response[2] != 0x01) {
|
||||
ESP_LOGW(TAG, "Unexpected response: %02X %02X %02X %02X", response[0], response[1], response[2], response[3]);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
|
||||
char hex_buf[format_hex_pretty_size(4)];
|
||||
ESP_LOGW(TAG, "Unexpected response: %s", format_hex_pretty_to(hex_buf, response, 4, ' '));
|
||||
#endif
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
@@ -101,7 +104,10 @@ bool CM1106Component::send_command_expect_ack_(const uint8_t *command, size_t co
|
||||
return false;
|
||||
}
|
||||
if (!this->status_comm_result(memcmp(response, ack, sizeof(response)) == 0)) {
|
||||
ESP_LOGW(TAG, "Unexpected response: %02X %02X %02X %02X", response[0], response[1], response[2], response[3]);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
|
||||
char hex_buf[format_hex_pretty_size(4)];
|
||||
ESP_LOGW(TAG, "Unexpected response: %s", format_hex_pretty_to(hex_buf, response, 4, ' '));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -59,6 +59,9 @@ class CombinationOneParameterComponent : public CombinationComponent {
|
||||
};
|
||||
|
||||
class KalmanCombinationComponent final : public CombinationOneParameterComponent {
|
||||
// Optional sensor for publishing the current error
|
||||
SUB_SENSOR(std_dev)
|
||||
|
||||
public:
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
@@ -66,15 +69,11 @@ class KalmanCombinationComponent final : public CombinationOneParameterComponent
|
||||
void set_process_std_dev(float process_std_dev) {
|
||||
this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f;
|
||||
}
|
||||
void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; }
|
||||
|
||||
protected:
|
||||
void update_variance_();
|
||||
void correct_(float value, float stddev);
|
||||
|
||||
// Optional sensor for publishing the current error
|
||||
sensor::Sensor *std_dev_sensor_{nullptr};
|
||||
|
||||
// Tick of the last update
|
||||
uint32_t last_update_{0};
|
||||
// Change of the variance, per ms
|
||||
|
||||
@@ -9,6 +9,7 @@ BYTE_ORDER_BIG = "big_endian"
|
||||
CONF_ACCELEROMETER_ODR = "accelerometer_odr"
|
||||
CONF_ACCELEROMETER_RANGE = "accelerometer_range"
|
||||
CONF_ACCEPT = "accept"
|
||||
CONF_ACTIVE_HIGH = "active_high"
|
||||
CONF_ADC_AVERAGING = "adc_averaging"
|
||||
CONF_ADC_TIME = "adc_time"
|
||||
CONF_ALLOWED_IPS = "allowed_ips"
|
||||
@@ -23,6 +24,8 @@ CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
|
||||
CONF_BUTTONS = "buttons"
|
||||
CONF_BYTE_ORDER = "byte_order"
|
||||
CONF_CHANNEL_COLORS = "channel_colors"
|
||||
CONF_CHARGE_CURRENT = "charge_current"
|
||||
CONF_CHARGE_ENABLED = "charge_enabled"
|
||||
CONF_CLEAR_CHANNEL = "clear_channel"
|
||||
CONF_CLIMATE_ID = "climate_id"
|
||||
CONF_CO2_EQUIVALENT = "co2_equivalent"
|
||||
@@ -49,6 +52,7 @@ CONF_HOST = "host"
|
||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||
CONF_IAQ = "iaq"
|
||||
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
|
||||
CONF_INPUT_CURRENT_LIMIT = "input_current_limit"
|
||||
CONF_IS_WRGB = "is_wrgb"
|
||||
CONF_KEYS = "keys"
|
||||
CONF_LABEL = "label"
|
||||
@@ -59,15 +63,21 @@ CONF_MAX_PACKET_SIZE = "max_packet_size"
|
||||
CONF_MODEM = "modem"
|
||||
CONF_MODULATION = "modulation"
|
||||
CONF_NOX_INDEX = "nox_index"
|
||||
CONF_ON_DOUBLE_TAP = "on_double_tap"
|
||||
CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request"
|
||||
CONF_ON_PACKET = "on_packet"
|
||||
CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification"
|
||||
CONF_ON_PASSKEY_REQUEST = "on_passkey_request"
|
||||
CONF_ON_RECEIVE = "on_receive"
|
||||
CONF_ON_SCAN_END = "on_scan_end"
|
||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||
CONF_ON_STOP = "on_stop"
|
||||
CONF_ON_TAP = "on_tap"
|
||||
CONF_OUT_PIN = "out_pin"
|
||||
CONF_OUT_PIN_LEVEL = "out_pin_level"
|
||||
CONF_PARITY = "parity"
|
||||
CONF_PASSKEY = "passkey"
|
||||
CONF_PRECHARGE_CURRENT = "precharge_current"
|
||||
CONF_PROVIDER = "provider"
|
||||
CONF_PWM_FREQUENCY = "pwm_frequency"
|
||||
CONF_QUERY_PARAMS = "query_params"
|
||||
@@ -87,6 +97,7 @@ CONF_SLOT = "slot"
|
||||
CONF_SOURCES = "sources"
|
||||
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
|
||||
CONF_STOP_BITS = "stop_bits"
|
||||
CONF_TAP = "tap"
|
||||
CONF_TARGET_COUNT = "target_count"
|
||||
CONF_TEMPERATURE_SENSOR = "temperature_sensor"
|
||||
CONF_USE_PSRAM = "use_psram"
|
||||
|
||||
@@ -38,4 +38,4 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
|
||||
await cg.add_variable(var.set_source_binary_sensor, config[CONF_SOURCE_ID])
|
||||
@@ -6,9 +6,9 @@ namespace esphome::copy {
|
||||
ESPHOME_LOG_TAG(TAG, "copy.binary_sensor");
|
||||
|
||||
void CopyBinarySensor::setup() {
|
||||
source_->add_on_state_callback([this](bool value) { this->publish_state(value); });
|
||||
if (source_->has_state())
|
||||
this->publish_state(source_->state);
|
||||
this->source_binary_sensor_->add_on_state_callback([this](bool value) { this->publish_state(value); });
|
||||
if (this->source_binary_sensor_->has_state())
|
||||
this->publish_state(this->source_binary_sensor_->state);
|
||||
}
|
||||
|
||||
void CopyBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Copy Binary Sensor", this); }
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopyBinarySensor final : public binary_sensor::BinarySensor, public Component {
|
||||
SUB_BINARY_SENSOR(source)
|
||||
|
||||
public:
|
||||
void set_source(binary_sensor::BinarySensor *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
binary_sensor::BinarySensor *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::copy
|
||||
File renamed without changes.
@@ -38,4 +38,4 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = await number.new_number(config, min_value=0, max_value=0, step=0)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
|
||||
await cg.add_variable(var.set_source_number, config[CONF_SOURCE_ID])
|
||||
@@ -6,20 +6,20 @@ namespace esphome::copy {
|
||||
ESPHOME_LOG_TAG(TAG, "copy.number");
|
||||
|
||||
void CopyNumber::setup() {
|
||||
source_->add_on_state_callback([this](float value) { this->publish_state(value); });
|
||||
this->source_number_->add_on_state_callback([this](float value) { this->publish_state(value); });
|
||||
|
||||
traits.set_min_value(source_->traits.get_min_value());
|
||||
traits.set_max_value(source_->traits.get_max_value());
|
||||
traits.set_step(source_->traits.get_step());
|
||||
traits.set_min_value(this->source_number_->traits.get_min_value());
|
||||
traits.set_max_value(this->source_number_->traits.get_max_value());
|
||||
traits.set_step(this->source_number_->traits.get_step());
|
||||
|
||||
if (source_->has_state())
|
||||
this->publish_state(source_->state);
|
||||
if (this->source_number_->has_state())
|
||||
this->publish_state(this->source_number_->state);
|
||||
}
|
||||
|
||||
void CopyNumber::dump_config() { LOG_NUMBER("", "Copy Number", this); }
|
||||
|
||||
void CopyNumber::control(float value) {
|
||||
auto call2 = source_->make_call();
|
||||
auto call2 = this->source_number_->make_call();
|
||||
call2.set_value(value);
|
||||
call2.perform();
|
||||
}
|
||||
|
||||
@@ -6,15 +6,14 @@
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopyNumber final : public number::Number, public Component {
|
||||
SUB_NUMBER(source)
|
||||
|
||||
public:
|
||||
void set_source(number::Number *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
|
||||
number::Number *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::copy
|
||||
@@ -31,4 +31,4 @@ async def to_code(config: ConfigType) -> None:
|
||||
await select.register_select(var, config, options=[])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
|
||||
await cg.add_variable(var.set_source_select, config[CONF_SOURCE_ID])
|
||||
@@ -6,11 +6,11 @@ namespace esphome::copy {
|
||||
ESPHOME_LOG_TAG(TAG, "copy.select");
|
||||
|
||||
void CopySelect::setup() {
|
||||
source_->add_on_state_callback([this](size_t index) { this->publish_state(index); });
|
||||
this->source_select_->add_on_state_callback([this](size_t index) { this->publish_state(index); });
|
||||
|
||||
traits.set_options(source_->traits.get_options());
|
||||
traits.set_options(this->source_select_->traits.get_options());
|
||||
|
||||
auto idx = this->source_->active_index();
|
||||
auto idx = this->source_select_->active_index();
|
||||
if (idx.has_value())
|
||||
this->publish_state(*idx);
|
||||
}
|
||||
@@ -18,7 +18,7 @@ void CopySelect::setup() {
|
||||
void CopySelect::dump_config() { LOG_SELECT("", "Copy Select", this); }
|
||||
|
||||
void CopySelect::control(size_t index) {
|
||||
auto call = source_->make_call();
|
||||
auto call = this->source_select_->make_call();
|
||||
call.set_index(index);
|
||||
call.perform();
|
||||
}
|
||||
|
||||
@@ -6,15 +6,14 @@
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopySelect final : public select::Select, public Component {
|
||||
SUB_SELECT(source)
|
||||
|
||||
public:
|
||||
void set_source(select::Select *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void control(size_t index) override;
|
||||
|
||||
select::Select *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::copy
|
||||
File renamed without changes.
@@ -36,4 +36,4 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = await switch.new_switch(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
|
||||
await cg.add_variable(var.set_source_switch, config[CONF_SOURCE_ID])
|
||||
@@ -6,13 +6,13 @@ namespace esphome::copy {
|
||||
ESPHOME_LOG_TAG(TAG, "copy.switch");
|
||||
|
||||
void CopySwitch::setup() {
|
||||
source_->add_on_state_callback([this](float value) { this->publish_state(value); });
|
||||
this->source_switch_->add_on_state_callback([this](float value) { this->publish_state(value); });
|
||||
|
||||
this->publish_state(source_->state);
|
||||
this->publish_state(this->source_switch_->state);
|
||||
}
|
||||
|
||||
void CopySwitch::dump_config() { LOG_SWITCH("", "Copy Switch", this); }
|
||||
|
||||
void CopySwitch::write_state(bool state) { this->source_->control(state); }
|
||||
void CopySwitch::write_state(bool state) { this->source_switch_->control(state); }
|
||||
|
||||
} // namespace esphome::copy
|
||||
@@ -6,15 +6,14 @@
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopySwitch final : public switch_::Switch, public Component {
|
||||
SUB_SWITCH(source)
|
||||
|
||||
public:
|
||||
void set_source(switch_::Switch *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
|
||||
switch_::Switch *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::copy
|
||||
File renamed without changes.
@@ -189,7 +189,8 @@ void CSE7766Component::parse_data_() {
|
||||
if (have_power && this->reactive_power_sensor_ != nullptr) {
|
||||
const float q_squared = apparent_power * apparent_power - power * power;
|
||||
if (q_squared < 0.0f) {
|
||||
ESP_LOGD(TAG, "Impossible reactive power: S^2-P^2 is negative (%.4f)", q_squared);
|
||||
// From the same noise as a power factor above 1 (below)
|
||||
ESP_LOGV(TAG, "Impossible reactive power: S^2-P^2 is negative (%.4f)", q_squared);
|
||||
this->reactive_power_sensor_->publish_state(0.0f);
|
||||
} else {
|
||||
this->reactive_power_sensor_->publish_state(std::sqrt(q_squared));
|
||||
@@ -200,8 +201,9 @@ void CSE7766Component::parse_data_() {
|
||||
if (apparent_power > 0) {
|
||||
pf = power / apparent_power;
|
||||
if (pf > 1) {
|
||||
// Noise near the chip's resolution limit, seen during inrush, pushes the ratio past unity
|
||||
ESP_LOGD(TAG, "Power factor %.4f above 1, clamped", pf);
|
||||
// Noise near the chip's resolution limit pushes the ratio past unity: during inrush, and on
|
||||
// nearly every frame of a steady resistive load, so it is logged only at verbose level
|
||||
ESP_LOGV(TAG, "Power factor %.4f above 1, clamped", pf);
|
||||
pf = 1.0f;
|
||||
}
|
||||
} else if (apparent_power == 0 && power == 0) {
|
||||
|
||||
File renamed without changes.
@@ -13,8 +13,6 @@ CST816Touchscreen = cst816_ns.class_(
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
CST816ButtonListener = cst816_ns.class_("CST816ButtonListener")
|
||||
|
||||
CONF_SKIP_PROBE = "skip_probe"
|
||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
{
|
||||
|
||||
@@ -32,11 +32,6 @@ static const uint8_t CST816D_CHIP_ID = 0xB6;
|
||||
static const uint8_t CST816T_CHIP_ID = 0xB5;
|
||||
static const uint8_t CST716_CHIP_ID = 0x20;
|
||||
|
||||
class CST816ButtonListener {
|
||||
public:
|
||||
virtual void update_button(bool state) = 0;
|
||||
};
|
||||
|
||||
class CST816Touchscreen final : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
namespace esphome::current_based {
|
||||
|
||||
class CurrentBasedCover final : public cover::Cover, public Component {
|
||||
SUB_SENSOR(open)
|
||||
SUB_SENSOR(close)
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
@@ -17,7 +20,6 @@ class CurrentBasedCover final : public cover::Cover, public Component {
|
||||
Trigger<> *get_stop_trigger() { return &this->stop_trigger_; }
|
||||
|
||||
Trigger<> *get_open_trigger() { return &this->open_trigger_; }
|
||||
void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; }
|
||||
void set_open_moving_current_threshold(float open_moving_current_threshold) {
|
||||
this->open_moving_current_threshold_ = open_moving_current_threshold;
|
||||
}
|
||||
@@ -27,7 +29,6 @@ class CurrentBasedCover final : public cover::Cover, public Component {
|
||||
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
|
||||
|
||||
Trigger<> *get_close_trigger() { return &this->close_trigger_; }
|
||||
void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; }
|
||||
void set_close_moving_current_threshold(float close_moving_current_threshold) {
|
||||
this->close_moving_current_threshold_ = close_moving_current_threshold;
|
||||
}
|
||||
@@ -64,13 +65,11 @@ class CurrentBasedCover final : public cover::Cover, public Component {
|
||||
|
||||
Trigger<> stop_trigger_;
|
||||
|
||||
sensor::Sensor *open_sensor_{nullptr};
|
||||
Trigger<> open_trigger_;
|
||||
float open_moving_current_threshold_{0.0f};
|
||||
float open_obstacle_current_threshold_{FLT_MAX};
|
||||
uint32_t open_duration_{0};
|
||||
|
||||
sensor::Sensor *close_sensor_{nullptr};
|
||||
Trigger<> close_trigger_;
|
||||
float close_moving_current_threshold_{0.0f};
|
||||
float close_obstacle_current_threshold_{FLT_MAX};
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 842 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user