mirror of
https://github.com/esphome/esphome.git
synced 2026-11-09 14:45:57 +00:00
Compare commits
165
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55b020eaf8 | ||
|
|
2499ecaa51 | ||
|
|
82824861ae | ||
|
|
ce35de6468 | ||
|
|
fc106564ed | ||
|
|
7b380674ee | ||
|
|
e4011bd96a | ||
|
|
1fa3fe26bb | ||
|
|
3de1503ec0 | ||
|
|
c248a7d3c4 | ||
|
|
59d507197d | ||
|
|
469a459b9a | ||
|
|
f06cba700f | ||
|
|
5b077a0a9a | ||
|
|
51b88cee9b | ||
|
|
da5aba76c7 | ||
|
|
765ca4a085 | ||
|
|
9b24d1afda | ||
|
|
549b56197f | ||
|
|
c390a46f1c | ||
|
|
cdc0d69e4c | ||
|
|
92723c905c | ||
|
|
ca8be3e953 | ||
|
|
3f330fcdb2 | ||
|
|
95278ea4d8 | ||
|
|
a6b2d20038 | ||
|
|
a37e3bba26 | ||
|
|
e83e892514 | ||
|
|
e1c781871b | ||
|
|
f926fabbd8 | ||
|
|
124969267d | ||
|
|
de24754b4d | ||
|
|
9b138d17de | ||
|
|
c64d5f149f | ||
|
|
d29268cc00 | ||
|
|
9969cb3de1 | ||
|
|
c9ca5ba910 | ||
|
|
6d3239bf43 | ||
|
|
295de90303 | ||
|
|
2473ddcf19 | ||
|
|
26303448d7 | ||
|
|
3755a7c970 | ||
|
|
76a0c75d27 | ||
|
|
55cb8ea03d | ||
|
|
58419f2207 | ||
|
|
1854bd12bb | ||
|
|
d927597771 | ||
|
|
20bd5f5316 | ||
|
|
3b862f03f1 | ||
|
|
c94cb79f65 | ||
|
|
bffe06211a | ||
|
|
e07be6bef5 | ||
|
|
c4100d5b35 | ||
|
|
781f4330ba | ||
|
|
d20e3b3bb5 | ||
|
|
b764b18733 | ||
|
|
657416e14b | ||
|
|
2d74ef7b29 | ||
|
|
562614d290 | ||
|
|
8a5c5a51f4 | ||
|
|
b656f2f9cb | ||
|
|
af3d283825 | ||
|
|
eeefe3eb14 | ||
|
|
1085d1bf3a | ||
|
|
af34c1d009 | ||
|
|
6536d88837 | ||
|
|
d7c9cc6b63 | ||
|
|
6dd111b1de | ||
|
|
4969f643cd | ||
|
|
ae31989bd8 | ||
|
|
49f3133c4f | ||
|
|
d07311b8f1 | ||
|
|
39966b6182 | ||
|
|
75d2cd10bf | ||
|
|
5f7fd7692d | ||
|
|
f960d99d66 | ||
|
|
ab4e3b42f5 | ||
|
|
5c6a7a89c8 | ||
|
|
be25249209 | ||
|
|
ffab9a81e7 | ||
|
|
bea7994c9f | ||
|
|
6c8c105449 | ||
|
|
ca7401750d | ||
|
|
cfb98dc580 | ||
|
|
1065312345 | ||
|
|
ca291a9724 | ||
|
|
38ed6919f8 | ||
|
|
9a5a8f9822 | ||
|
|
9dea935499 | ||
|
|
2d73971fcc | ||
|
|
5d511a1bd1 | ||
|
|
14b190ab8f | ||
|
|
c50a5a0879 | ||
|
|
75b9f8de1c | ||
|
|
7637cdc162 | ||
|
|
891ebdaaaa | ||
|
|
a01b213ab4 | ||
|
|
a21268f641 | ||
|
|
7813ea1c7f | ||
|
|
82a375907d | ||
|
|
9591e34722 | ||
|
|
408bd8820a | ||
|
|
07a175692a | ||
|
|
94d0113f66 | ||
|
|
09d3b645a3 | ||
|
|
940f87056d | ||
|
|
6f284d171a | ||
|
|
a7a4473b32 | ||
|
|
8092a23553 | ||
|
|
1a260e4c57 | ||
|
|
8a30af4ad1 | ||
|
|
cad297866f | ||
|
|
ff254ae9d8 | ||
|
|
f5ab5febb4 | ||
|
|
3d95a0ba33 | ||
|
|
d8bfae306f | ||
|
|
78d2fdbfc0 | ||
|
|
4af8e50dca | ||
|
|
93d00b5920 | ||
|
|
3d4e20f574 | ||
|
|
be3113e7d5 | ||
|
|
e236c741db | ||
|
|
ed4db4b20e | ||
|
|
51db039f59 | ||
|
|
a24b994a5e | ||
|
|
912e9dedae | ||
|
|
47a526d35c | ||
|
|
389e782e78 | ||
|
|
9a78022ded | ||
|
|
c11c788d25 | ||
|
|
d516fd5c92 | ||
|
|
8e87cfc17e | ||
|
|
f0c4dc67e7 | ||
|
|
863cb54982 | ||
|
|
64b621f8f1 | ||
|
|
7d29bdf177 | ||
|
|
86a0cdfb7a | ||
|
|
cf5519615a | ||
|
|
741310ef30 | ||
|
|
f96c0c7d0e | ||
|
|
b18b7fe7a1 | ||
|
|
6ea4ed0868 | ||
|
|
a78a80c0d1 | ||
|
|
4206de5ed0 | ||
|
|
ae54e8b696 | ||
|
|
b252c90a5b | ||
|
|
d68674597e | ||
|
|
57a5e54118 | ||
|
|
69d96a4707 | ||
|
|
52fedcbe97 | ||
|
|
d2b635cd92 | ||
|
|
6d5305f1fe | ||
|
|
354660cafc | ||
|
|
e5f6dbe6bf | ||
|
|
ab95d504fa | ||
|
|
605e7dc475 | ||
|
|
e8dc7471a0 | ||
|
|
e2c7d1e29c | ||
|
|
d4e1c12d76 | ||
|
|
502c93df84 | ||
|
|
37cff473d0 | ||
|
|
443c2caa0c | ||
|
|
79d6752c97 | ||
|
|
c3146d8f6a | ||
|
|
45d565db01 |
No files matched your search
@@ -116,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -188,9 +188,37 @@ Restore an owned or standalone switch in `setup()` with `sw->restore()`, or `res
|
||||
drive a state when the restore mode is disabled. A hub that applies its switches' restored states itself
|
||||
reads `sw->get_initial_write_state()`, which already applies `inverted`.
|
||||
|
||||
**Entities that each watch a register field:** one class taking register, shift, mask and match from a
|
||||
Python tuple table, in typed `StaticVector`s sized by `cg.slot_counter`; not a template per bit, not a
|
||||
virtual listener base (a second vptr per entity).
|
||||
**A hub that forwards data to its children:** when what each child does with the data is known at build time
|
||||
(decode a register field, match a key or tag, publish a value), declare a `cg.HubFunction` once in the hub's
|
||||
`__init__.py` and have each platform add one item per entity, built from codegen expressions. Code generation writes
|
||||
one function per hub. Pass `define=` and put the hub's `HUB_PUBLISHER(<name>, args...)` (in a public section; it
|
||||
ends on `public:`) and its call under that `#ifdef`; call the pointer only when it is not null. The children stay
|
||||
plain entities, with no listener class or list. A child that lost a `Component` base keeps its dump lines through
|
||||
a second hub function (`"dump"`, a `const char *` tag parameter, no define of its own) that the hub's
|
||||
`dump_config()` calls inside the value function's `#ifdef`; every child adds to both.
|
||||
|
||||
**Entities that each watch a register field:** when every field (register, shift, mask, match) is known at
|
||||
build time, use the rule above (see `bq25186`). A binary sensor tests its field value against an on-values
|
||||
bitmap (`match`); a one-bit field reads the bit (simplified from bq25186's `_state_expression`):
|
||||
```python
|
||||
# hub __init__.py
|
||||
STATUS_PUBLISHER = cg.HubFunction(
|
||||
"status", # esphome__<hub>__status_fn, passed to HUB_PUBLISHER(status, ...)'s setter
|
||||
cg.void,
|
||||
[(BQ25186StatusRegisters.const_ref(), "status")],
|
||||
define="USE_BQ25186_STATUS_PUBLISHER",
|
||||
)
|
||||
STATUS = STATUS_PUBLISHER.param("status")
|
||||
|
||||
# platform to_code, per configured entity
|
||||
value = (STATUS[reg] >> shift) & mask
|
||||
if mask != 0x01 or match != 0b10: # 0b10: a one-bit field that is on when set
|
||||
value = (cg.MockObj(f"{match:#06b}") >> value) & 1
|
||||
STATUS_PUBLISHER.add(hub, parent_ref(var).publish_state(value))
|
||||
```
|
||||
Store a field description per entity (a struct in a `StaticVector` sized by `cg.slot_counter`) only when the
|
||||
field is chosen at run time, as in `sen6x`, where the status bit depends on the variant read in `setup()`.
|
||||
Never a template per bit or a virtual listener base (a second vptr per entity).
|
||||
|
||||
## Other helpers
|
||||
|
||||
|
||||
@@ -58,8 +58,11 @@ APIs.
|
||||
Use `cg.add_define("MAX_SERVICES", count)` to set the size from Python configuration.
|
||||
|
||||
**Listener and child-entity registration lists are the most common case, and the most commonly
|
||||
missed.** A `register_*()` method called once per child at code generation time has a count that is
|
||||
known at compile time, so it should never be a `std::vector`. Use `cg.slot_counter()`: it returns a
|
||||
missed.** When the hub only forwards data to its children and each child's handling is known at build
|
||||
time, keep no list at all: use `cg.HubFunction` (see the component-codegen skill). When the hub must
|
||||
store the children (it iterates them for other reasons, or picks fields at run time), a `register_*()`
|
||||
method called once per child at code generation time has a count that is known at compile time, so it
|
||||
should never be a `std::vector`. Use `cg.slot_counter()`: it returns a
|
||||
function that each consumer calls once per slot it will occupy, and after every `to_code` has run it
|
||||
emits the define with the final count. When nothing registers, no define is emitted and the storage plus
|
||||
its registration method compile out entirely.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG BUILD_BASE_VERSION=2026.06.1
|
||||
ARG BUILD_BASE_VERSION=2026.11.0
|
||||
|
||||
|
||||
FROM ghcr.io/esphome/docker-base:debian-${BUILD_BASE_VERSION} AS base
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -154,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
|
||||
@@ -632,6 +633,7 @@ esphome/components/st7789v/* @kbx81
|
||||
esphome/components/st7920/* @marsjan155
|
||||
esphome/components/statsd/* @Links2004
|
||||
esphome/components/stcc4/* @j9brown
|
||||
esphome/components/store_yaml/* @bdraco
|
||||
esphome/components/stts22h/* @B48D81EFCC
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
|
||||
@@ -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
|
||||
|
||||
+9
-8
@@ -94,23 +94,24 @@ without a password leaves OTA open. The API is documented and is meant to be
|
||||
called by other devices, scripts, and pages.
|
||||
|
||||
As defense-in-depth, the web server checks the `Origin` header on browser requests
|
||||
to its entity control and state endpoints: a request whose `Origin` does not match
|
||||
the address the device is served on is rejected, and the `allowed_origins` option
|
||||
to every endpoint served through it, including the web OTA `/update` endpoint
|
||||
(the captive portal pages are open by design): a request whose `Origin` does not match
|
||||
the `Host` header of the same request is rejected, and the `allowed_origins` option
|
||||
widens that list. This blocks the common "confused deputy" (CSRF) case where a page
|
||||
the operator visits drives the device through their browser. It is **not** an
|
||||
authentication boundary: it only constrains browsers. Any client that omits the
|
||||
`Origin` header — `curl`, scripts, or other non-browser callers on the same
|
||||
network — reaches every endpoint exactly as before. The check also does not cover
|
||||
the web OTA `/update` endpoint. The device performs no CSRF-token or `Referer`
|
||||
network — reaches every endpoint exactly as before. The device performs no CSRF-token or `Referer`
|
||||
validation. The following are therefore **not** vulnerabilities in this repository:
|
||||
|
||||
- Requests without an `Origin` header (for example `curl`) reaching the control
|
||||
endpoints, whether or not `web_server` `auth:` is set.
|
||||
- Requests from an origin the operator added to `allowed_origins`.
|
||||
- Cross-origin or CSRF firmware upload through the web OTA endpoint (`/update`) when
|
||||
web OTA is enabled without `web_server` `auth:`. The `/update` endpoint is not
|
||||
covered by the `Origin` check; this is the same exposure as running OTA without a
|
||||
password.
|
||||
- DNS rebinding: a page whose host name resolves to the device sends a matching
|
||||
`Origin` and `Host`, so the check lets it through.
|
||||
- Firmware upload through the web OTA endpoint (`/update`) by a client that sends no
|
||||
`Origin` header when web OTA is enabled without `web_server` `auth:`; this is the
|
||||
same exposure as running OTA without a password.
|
||||
|
||||
The supported defenses are `web_server` `auth:`, protecting OTA (a web password or
|
||||
a native OTA password), and keeping devices on a trusted, segmented network. See
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
ARG BUILD_VERSION=dev
|
||||
ARG BUILD_BASE_VERSION=2026.06.1
|
||||
ARG BUILD_BASE_VERSION=2026.11.0
|
||||
ARG BUILD_TYPE=docker
|
||||
|
||||
FROM ghcr.io/esphome/docker-base:debian-${BUILD_BASE_VERSION} AS base-source-docker
|
||||
|
||||
+135
-74
@@ -3,6 +3,7 @@ from contextvars import ContextVar
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
import string
|
||||
from typing import Any
|
||||
|
||||
@@ -44,6 +45,9 @@ CONF_CHOOSE = "choose"
|
||||
CONF_CHOICE_ID = "choice_id"
|
||||
|
||||
|
||||
_BYTE_VECTOR = cg.std_vector.template(cg.uint8)
|
||||
|
||||
|
||||
def progmem_bytes(name: str, data: bytes | list[int]) -> MockObj:
|
||||
"""Shared PROGMEM table for constant bytes; equal payloads share one, empty is nullptr."""
|
||||
if not data:
|
||||
@@ -59,10 +63,11 @@ async def templatable_bytes(
|
||||
set_template: MockObj,
|
||||
set_static: MockObj,
|
||||
table_name: str,
|
||||
return_type: MockObj = _BYTE_VECTOR,
|
||||
) -> None:
|
||||
"""Set a TemplatableBytes: a lambda via set_template, constant bytes via set_static."""
|
||||
if cg.is_template(value):
|
||||
fn = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
|
||||
fn = await cg.templatable(value, args, return_type)
|
||||
cg.add(set_template(fn))
|
||||
else:
|
||||
cg.add(set_static(*_static_bytes(table_name, value)))
|
||||
@@ -623,34 +628,34 @@ async def _apply_parent(config: ConfigType, id_key: str = CONF_ID) -> str:
|
||||
return str(parent_ref(await cg.get_variable(config[id_key])))
|
||||
|
||||
|
||||
def _apply_lambda_args(args: TemplateArgsType) -> TemplateArgsType:
|
||||
# The generated function's parameters: a reference keeps its spelling, so a mutable
|
||||
# one stays mutable; a value is taken by const reference, so a std::string is never copied.
|
||||
return [
|
||||
(
|
||||
(
|
||||
t
|
||||
if str(cg.safe_exp(t)).rstrip().endswith("&")
|
||||
else cg.RawExpression(f"const std::remove_cvref_t<{cg.safe_exp(t)}> &")
|
||||
),
|
||||
arg,
|
||||
)
|
||||
for t, arg in args
|
||||
]
|
||||
def _is_reference(type_: SafeExpType) -> bool:
|
||||
return str(cg.safe_exp(type_)).rstrip().endswith("&")
|
||||
|
||||
|
||||
def _apply_param_type(type_: SafeExpType) -> SafeExpType:
|
||||
# A reference keeps its spelling; a value is taken by const reference, so it is never copied.
|
||||
if _is_reference(type_):
|
||||
return type_
|
||||
return cg.RawExpression(f"const std::remove_cvref_t<{cg.safe_exp(type_)}> &")
|
||||
|
||||
|
||||
def _apply_params(args: TemplateArgsType) -> TemplateArgsType:
|
||||
"""The generated function's parameters."""
|
||||
return [(_apply_param_type(t), arg) for t, arg in args]
|
||||
|
||||
|
||||
def _apply_function(
|
||||
id_: ID,
|
||||
return_type: SafeExpType,
|
||||
template_arg: cg.TemplateArguments,
|
||||
lambda_args: TemplateArgsType,
|
||||
args: TemplateArgsType,
|
||||
statements: list[str],
|
||||
) -> MockObj:
|
||||
"""Emit the generated function and declare ``id_`` as the ``ApplyAction`` or
|
||||
``ApplyCondition`` templated on it, so ``play()`` calls it directly."""
|
||||
|
||||
def emit() -> MockObj:
|
||||
fn = apply_static_function(id_, return_type, lambda_args, statements)
|
||||
fn = apply_static_function(id_, return_type, _apply_params(args), statements)
|
||||
return cg.new_Pvariable(id_, cg.TemplateArguments(fn, *template_arg))
|
||||
|
||||
return _COLLECTED_ACTION if _collect_action(id_, statements, emit) else emit()
|
||||
@@ -667,9 +672,7 @@ def apply_statements_action(
|
||||
Use it when the statements do not fit ``register_apply_action``. Generated names should
|
||||
start with ``esphome__`` so a trigger argument cannot shadow them.
|
||||
"""
|
||||
return _apply_function(
|
||||
action_id, cg.void, template_arg, _apply_lambda_args(args), statements
|
||||
)
|
||||
return _apply_function(action_id, cg.void, template_arg, args, statements)
|
||||
|
||||
|
||||
def apply_static_function(
|
||||
@@ -682,19 +685,78 @@ def apply_static_function(
|
||||
return cg.static_function(f"esphome__{id_.id}__fn", return_type, params, statements)
|
||||
|
||||
|
||||
async def _render_values(
|
||||
# Comments, raw strings, strings and char literals; a ' after a digit is a digit separator.
|
||||
_CPP_NOISE = re.compile(
|
||||
r"//[^\n]*|/\*.*?\*/"
|
||||
r'|\b(?:u8|u|U|L)?R"([^()\\\s]{0,16})\(.*?\)\1"'
|
||||
r'|"(?:\\.|[^"\\\n])*"'
|
||||
r"|(?<!\w)'(?:\\.[^'\\\n]{0,8}|[^'\\\n])'",
|
||||
re.DOTALL,
|
||||
)
|
||||
|
||||
|
||||
def _cpp_code_only(text: str) -> str:
|
||||
return _CPP_NOISE.sub(" ", text)
|
||||
|
||||
|
||||
def _names_arg(code: str, name: str) -> bool:
|
||||
"""Whether ``code`` (with noise removed) names ``name`` other than as a member."""
|
||||
return re.search(rf"(?<!\.)(?<!->)(?<!::)\b{re.escape(name)}\b", code) is not None
|
||||
|
||||
|
||||
def _has_top_level_comma(code: str) -> bool:
|
||||
depth = 0
|
||||
for ch in code:
|
||||
if ch in "([{":
|
||||
depth += 1
|
||||
elif ch in ")]}":
|
||||
depth -= 1
|
||||
elif ch == "," and depth == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def _call_user_lambda(
|
||||
value: Lambda, args: TemplateArgsType, return_type: SafeExpType
|
||||
) -> Expression:
|
||||
"""Call a config lambda in place; a value arg its body names is a copy it may modify.
|
||||
|
||||
The other args are the generated function's const references, so a body that names
|
||||
none of them is reduced to its expression, as ``call_lambda`` does. When unsure the
|
||||
name check counts a use: a needless copy is only a cost, a missed one fails to compile.
|
||||
"""
|
||||
code = _cpp_code_only(value.value)
|
||||
by_value = {
|
||||
name for t, name in args if not _is_reference(t) and _names_arg(code, name)
|
||||
}
|
||||
params = [
|
||||
(t if name in by_value else _apply_param_type(t), name) for t, name in args
|
||||
]
|
||||
inner = await cg.process_lambda(value, params, return_type=return_type)
|
||||
if by_value:
|
||||
return immediate_lambda_call(inner)
|
||||
expr = call_lambda(inner)
|
||||
# An uncast `a, b` would split into two arguments or end an initializer early
|
||||
if isinstance(expr, cg.RawExpression) and _has_top_level_comma(
|
||||
_cpp_code_only(str(expr))
|
||||
):
|
||||
return cg.RawExpression(f"({expr})")
|
||||
return expr
|
||||
|
||||
|
||||
async def _render_call(
|
||||
name: str,
|
||||
target: str,
|
||||
members: list[tuple[Any, Any, Any]],
|
||||
values: list[Any],
|
||||
config: ConfigType,
|
||||
parent: str | None,
|
||||
lambda_args: TemplateArgsType,
|
||||
args: TemplateArgsType,
|
||||
compare: bool = False,
|
||||
) -> list[str]:
|
||||
"""Render the argument text of one statement; every key must be present.
|
||||
) -> str:
|
||||
"""Render ``target`` with its config values filled in; every key must be present.
|
||||
|
||||
``compare``: an inlined lambda expression is parenthesized so it binds as a whole
|
||||
``compare``: a reduced lambda expression is parenthesized so it binds as a whole
|
||||
beside an operator.
|
||||
"""
|
||||
if any(value is None for value in values):
|
||||
@@ -705,8 +767,7 @@ async def _render_values(
|
||||
if isinstance(value, Lambda):
|
||||
if isinstance(type_, str):
|
||||
type_ = cg.RawExpression(type_.format(parent=parent))
|
||||
inner = await cg.process_lambda(value, lambda_args, return_type=type_)
|
||||
expr = call_lambda(inner)
|
||||
expr = await _call_user_lambda(value, args, type_)
|
||||
bare = compare and isinstance(expr, cg.RawExpression)
|
||||
exprs.append(f"({expr})" if bare else str(expr))
|
||||
elif isinstance(value, ID):
|
||||
@@ -716,7 +777,7 @@ async def _render_values(
|
||||
exprs.append(const_fn(config, value))
|
||||
else:
|
||||
exprs.append(str(cg.safe_exp(value)))
|
||||
return exprs
|
||||
return target.format(*exprs, parent=parent)
|
||||
|
||||
|
||||
def _apply_values(config: ConfigType, members: list[tuple[Any, Any, Any]]) -> list[Any]:
|
||||
@@ -780,7 +841,6 @@ def apply_action_builder(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
parent = await _apply_parent(config, id_key)
|
||||
lambda_args = _apply_lambda_args(args)
|
||||
receiver = "apply_call." if call else f"{parent}->"
|
||||
statements: list[str] = []
|
||||
for spec in statements_spec:
|
||||
@@ -795,19 +855,17 @@ def apply_action_builder(
|
||||
values = _apply_values(config, members)
|
||||
if not _apply_call_active(members, values):
|
||||
continue
|
||||
exprs = await _render_values(
|
||||
name, target, members, values, config, parent, lambda_args
|
||||
rendered = await _render_call(
|
||||
name, target, members, values, config, parent, args
|
||||
)
|
||||
statements.append(f"{receiver}{target.format(*exprs, parent=parent)};")
|
||||
statements.append(f"{receiver}{rendered};")
|
||||
if call:
|
||||
statements = [
|
||||
f"auto apply_call = {parent}->{call}();",
|
||||
*statements,
|
||||
"apply_call.perform();",
|
||||
]
|
||||
return _apply_function(
|
||||
action_id, cg.void, template_arg, lambda_args, statements
|
||||
)
|
||||
return _apply_function(action_id, cg.void, template_arg, args, statements)
|
||||
|
||||
return builder
|
||||
|
||||
@@ -820,11 +878,7 @@ async def _render_bytes(
|
||||
args: TemplateArgsType,
|
||||
table_name: str,
|
||||
) -> list[str]:
|
||||
"""The statements for one ``ApplyBytes``: what ``TemplatableBytes::visit`` does, inlined.
|
||||
|
||||
The payload lambda keeps the automation's own argument types, as ``TemplatableBytes``
|
||||
gave it, and is always called, so a body that modifies an argument still compiles.
|
||||
"""
|
||||
"""The statements for one ``ApplyBytes``: what ``TemplatableBytes::visit`` does, inlined."""
|
||||
value = _config_lookup(config, spec.conf_key)
|
||||
if value is None:
|
||||
return []
|
||||
@@ -834,11 +888,7 @@ async def _render_bytes(
|
||||
return f"{receiver}{target.format(data, parent=parent)};"
|
||||
|
||||
if isinstance(value, Lambda):
|
||||
inner = await cg.process_lambda(
|
||||
value, args, return_type=cg.std_vector.template(cg.uint8)
|
||||
)
|
||||
# Never reduced into the outer function, whose own parameters are const
|
||||
call = CallExpression(inner, *[cg.MockObj(name) for _, name in args])
|
||||
call = await _call_user_lambda(value, args, _BYTE_VECTOR)
|
||||
return [
|
||||
"{",
|
||||
f" const std::vector<uint8_t> esphome__bytes = {call};",
|
||||
@@ -873,22 +923,6 @@ def _apply_call_active(members: list[tuple[Any, Any, Any]], values: list[Any]) -
|
||||
return not members or any(value is not None for value in values)
|
||||
|
||||
|
||||
async def _render_check(
|
||||
name: str,
|
||||
target: str,
|
||||
members: list[tuple[Any, Any, Any]],
|
||||
values: list[Any],
|
||||
config: ConfigType,
|
||||
parent: str | None,
|
||||
lambda_args: TemplateArgsType,
|
||||
) -> str:
|
||||
"""Render a boolean check with ``values`` compared against config."""
|
||||
exprs = await _render_values(
|
||||
name, target, members, values, config, parent, lambda_args, compare=True
|
||||
)
|
||||
return target.format(*exprs, parent=parent)
|
||||
|
||||
|
||||
def register_apply_condition(
|
||||
name: str,
|
||||
schema: cv.Schema,
|
||||
@@ -921,16 +955,15 @@ def register_apply_condition(
|
||||
if defaults:
|
||||
config = {**defaults, **config}
|
||||
parent = await _apply_parent(config, id_key)
|
||||
lambda_args = _apply_lambda_args(args)
|
||||
values = _apply_values(config, members)
|
||||
check = await _render_check(
|
||||
name, call.target, members, values, config, parent, lambda_args
|
||||
check = await _render_call(
|
||||
name, call.target, members, values, config, parent, args, compare=True
|
||||
)
|
||||
return _apply_function(
|
||||
condition_id,
|
||||
cg.bool_,
|
||||
template_arg,
|
||||
lambda_args,
|
||||
args,
|
||||
[f"return {parent}->{check};"],
|
||||
)
|
||||
|
||||
@@ -1718,7 +1751,7 @@ def _emit_inline_run(
|
||||
cg.void,
|
||||
[
|
||||
(cg.RawExpression("const int &"), _RUNNING_PARAM),
|
||||
*_apply_lambda_args(args),
|
||||
*_apply_params(args),
|
||||
],
|
||||
statements,
|
||||
)
|
||||
@@ -1873,10 +1906,13 @@ def validate_synchronous_automation(
|
||||
)
|
||||
|
||||
|
||||
async def _new_automation(
|
||||
async def new_automation(
|
||||
args: TemplateArgsType, config: ConfigType, *ctor_args: MockObj
|
||||
) -> MockObj:
|
||||
"""Create the Automation for ``config`` with its actions."""
|
||||
"""Create the ``Automation<args>`` for an ``on_*`` config with its actions.
|
||||
|
||||
For a parent that stores and fires the automation itself; ``build_automation`` attaches it to a trigger.
|
||||
"""
|
||||
plan = await _build_automation_plan(args, config)
|
||||
return _automation_from_plan(plan, args, config, *ctor_args)
|
||||
|
||||
@@ -1923,7 +1959,7 @@ def _direct_function(
|
||||
return None
|
||||
statements = [line for line in _run_statements(plan[0]) if line != _STOP_CHECK]
|
||||
return apply_static_function(
|
||||
plan[0][0].id_, cg.void, _apply_lambda_args(args), statements
|
||||
plan[0][0].id_, cg.void, _apply_params(args), statements
|
||||
)
|
||||
|
||||
|
||||
@@ -1941,7 +1977,7 @@ def _when_call(
|
||||
async def build_automation(
|
||||
trigger: MockObj, args: TemplateArgsType, config: ConfigType
|
||||
) -> MockObj:
|
||||
return await _new_automation(args, config, trigger)
|
||||
return await new_automation(args, config, trigger)
|
||||
|
||||
|
||||
async def build_trigger_callback(
|
||||
@@ -1979,27 +2015,27 @@ async def _trigger_callback(
|
||||
) -> LambdaExpression:
|
||||
if (target := _direct_function(plan, args, config)) is None:
|
||||
target = f"{parent_ref(_automation_from_plan(plan, args, config))}->trigger"
|
||||
lambda_args = _apply_lambda_args(params)
|
||||
statements: list[str] = []
|
||||
if call is not None:
|
||||
members = call.members
|
||||
values = _apply_values(config, members)
|
||||
if _apply_call_active(members, values):
|
||||
check = await _render_check(
|
||||
check = await _render_call(
|
||||
"trigger filter",
|
||||
call.target,
|
||||
members,
|
||||
values,
|
||||
config,
|
||||
None if parent is None else str(parent_ref(parent)),
|
||||
lambda_args,
|
||||
params,
|
||||
compare=True,
|
||||
)
|
||||
statements.append(f"if (!({check}))\n return;")
|
||||
if forward is None:
|
||||
forward = [name for _, name in params]
|
||||
statements.append(f"{target}({', '.join(map(str, forward))});")
|
||||
return LambdaExpression(
|
||||
["\n".join(statements)], lambda_args, capture="", return_type=cg.void
|
||||
["\n".join(statements)], _apply_params(params), capture="", return_type=cg.void
|
||||
)
|
||||
|
||||
|
||||
@@ -2102,6 +2138,31 @@ async def build_trigger_automations(
|
||||
await build_automation(trigger, args, conf)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AutomationList:
|
||||
"""Automations a parent keeps in an ``AutomationList<count_define, args...>`` and triggers itself.
|
||||
|
||||
The parent adds each one with ``add_<conf_key>_automation()``. ``count_define`` is the most
|
||||
automations any one parent gets; it is absent when none do.
|
||||
"""
|
||||
|
||||
conf_key: str
|
||||
count_define: str
|
||||
args: TemplateArgsType
|
||||
|
||||
|
||||
async def build_automation_lists(
|
||||
parent: MockObj, config: ConfigType, entries: tuple[AutomationList, ...]
|
||||
) -> None:
|
||||
"""Build each entry's automations and add them to the parent's slot-counted list."""
|
||||
for entry in entries:
|
||||
request_slot = cg.slot_counter(entry.count_define)
|
||||
for conf in config.get(entry.conf_key, []):
|
||||
request_slot(str(parent))
|
||||
obj = await new_automation(entry.args, conf)
|
||||
cg.add(getattr(parent, f"add_{entry.conf_key}_automation")(obj))
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CallbackAutomation:
|
||||
"""A single callback automation entry for build_callback_automations."""
|
||||
|
||||
@@ -54,6 +54,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
||||
with_local_variable,
|
||||
)
|
||||
from esphome.cpp_helpers import ( # noqa: F401
|
||||
HubFunction,
|
||||
add_optional,
|
||||
add_pin,
|
||||
add_variable,
|
||||
|
||||
@@ -13,33 +13,25 @@ void AdE7953I2c::dump_config() {
|
||||
}
|
||||
bool AdE7953I2c::ade_write_8(uint16_t reg, uint8_t value) {
|
||||
uint8_t data[3];
|
||||
data[0] = reg >> 8;
|
||||
data[1] = reg >> 0;
|
||||
put_be16(data, reg);
|
||||
data[2] = value;
|
||||
return this->write(data, 3) != i2c::ERROR_OK;
|
||||
}
|
||||
bool AdE7953I2c::ade_write_16(uint16_t reg, uint16_t value) {
|
||||
uint8_t data[4];
|
||||
data[0] = reg >> 8;
|
||||
data[1] = reg >> 0;
|
||||
data[2] = value >> 8;
|
||||
data[3] = value >> 0;
|
||||
put_be16(data, reg);
|
||||
put_be16(data + 2, value);
|
||||
return this->write(data, 4) != i2c::ERROR_OK;
|
||||
}
|
||||
bool AdE7953I2c::ade_write_32(uint16_t reg, uint32_t value) {
|
||||
uint8_t data[6];
|
||||
data[0] = reg >> 8;
|
||||
data[1] = reg >> 0;
|
||||
data[2] = value >> 24;
|
||||
data[3] = value >> 16;
|
||||
data[4] = value >> 8;
|
||||
data[5] = value >> 0;
|
||||
put_be16(data, reg);
|
||||
put_be32(data + 2, value);
|
||||
return this->write(data, 6) != i2c::ERROR_OK;
|
||||
}
|
||||
bool AdE7953I2c::ade_read_8(uint16_t reg, uint8_t *value) {
|
||||
uint8_t reg_data[2];
|
||||
reg_data[0] = reg >> 8;
|
||||
reg_data[1] = reg >> 0;
|
||||
put_be16(reg_data, reg);
|
||||
i2c::ErrorCode err = this->write(reg_data, 2);
|
||||
if (err != i2c::ERROR_OK)
|
||||
return true;
|
||||
@@ -48,8 +40,7 @@ bool AdE7953I2c::ade_read_8(uint16_t reg, uint8_t *value) {
|
||||
}
|
||||
bool AdE7953I2c::ade_read_16(uint16_t reg, uint16_t *value) {
|
||||
uint8_t reg_data[2];
|
||||
reg_data[0] = reg >> 8;
|
||||
reg_data[1] = reg >> 0;
|
||||
put_be16(reg_data, reg);
|
||||
i2c::ErrorCode err = this->write(reg_data, 2);
|
||||
if (err != i2c::ERROR_OK)
|
||||
return true;
|
||||
@@ -62,8 +53,7 @@ bool AdE7953I2c::ade_read_16(uint16_t reg, uint16_t *value) {
|
||||
}
|
||||
bool AdE7953I2c::ade_read_32(uint16_t reg, uint32_t *value) {
|
||||
uint8_t reg_data[2];
|
||||
reg_data[0] = reg >> 8;
|
||||
reg_data[1] = reg >> 0;
|
||||
put_be16(reg_data, reg);
|
||||
i2c::ErrorCode err = this->write(reg_data, 2);
|
||||
if (err != i2c::ERROR_OK)
|
||||
return true;
|
||||
|
||||
@@ -10,10 +10,8 @@ static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
|
||||
|
||||
void ADS1115Component::setup() {
|
||||
uint16_t value;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)))
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t config = 0;
|
||||
// Clear single-shot bit
|
||||
@@ -57,10 +55,8 @@ void ADS1115Component::setup() {
|
||||
// 0bxxxxxxxxxxxxxx11
|
||||
config |= 0b0000000000000011;
|
||||
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_byte_16(ADS1115_REGISTER_CONFIG, config)))
|
||||
return;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
}
|
||||
void ADS1115Component::dump_config() {
|
||||
|
||||
@@ -51,28 +51,23 @@ void AHT10Component::setup() {
|
||||
error_code = this->write(AHT10_INITIALIZE_CMD, sizeof(AHT10_INITIALIZE_CMD));
|
||||
break;
|
||||
}
|
||||
if (error_code != i2c::ERROR_OK) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(error_code == i2c::ERROR_OK))
|
||||
return;
|
||||
}
|
||||
uint8_t cal_attempts = 0;
|
||||
uint8_t data = AHT10_STATUS_BUSY;
|
||||
while (data & AHT10_STATUS_BUSY) {
|
||||
delay(AHT10_DEFAULT_DELAY);
|
||||
if (this->read(&data, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->read(&data, 1) == i2c::ERROR_OK))
|
||||
return;
|
||||
}
|
||||
++cal_attempts;
|
||||
if (cal_attempts > AHT10_INIT_ATTEMPTS) {
|
||||
this->mark_failed(LOG_STR("Initialization timed out"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED
|
||||
this->mark_failed(LOG_STR("Initialization failed"));
|
||||
// Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED
|
||||
if (!this->setup_check((data & 0x68) == 0x08, LOG_STR("Initialization failed")))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void AHT10Component::restart_read_() {
|
||||
|
||||
@@ -373,9 +373,7 @@ void AirtonSerialClimate::send_ab_() {
|
||||
}
|
||||
|
||||
void AirtonSerialClimate::write_frame_(uint8_t *frame, size_t len) {
|
||||
const uint16_t crc = crc16(frame, len - 2);
|
||||
frame[len - 2] = static_cast<uint8_t>(crc >> 8);
|
||||
frame[len - 1] = static_cast<uint8_t>(crc);
|
||||
put_be16(frame + len - 2, crc16(frame, len - 2));
|
||||
this->write_array(frame, len);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,10 +72,8 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
|
||||
void AM2315C::setup() {
|
||||
// get status
|
||||
uint8_t status = 0;
|
||||
if (this->read(&status, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->read(&status, 1) == i2c::ERROR_OK))
|
||||
return;
|
||||
}
|
||||
|
||||
// reset registers if required, according to the datasheet
|
||||
// this can be required after power on, although this was
|
||||
|
||||
@@ -34,10 +34,8 @@ void AM2320Component::setup() {
|
||||
uint8_t data[8];
|
||||
data[0] = 0;
|
||||
data[1] = 4;
|
||||
if (!this->read_data_(data)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->read_data_(data)))
|
||||
return;
|
||||
}
|
||||
}
|
||||
void AM2320Component::dump_config() {
|
||||
ESP_LOGD(TAG, "AM2320:");
|
||||
|
||||
@@ -26,8 +26,7 @@ Am43Packet Am43Encoder::get_position_request() {
|
||||
|
||||
Am43Packet Am43Encoder::get_send_pin_request(uint16_t pin) {
|
||||
uint8_t data[2];
|
||||
data[0] = (pin & 0xFF00) >> 8;
|
||||
data[1] = pin & 0xFF;
|
||||
put_be16(data, pin);
|
||||
return encode(CMD_SEND_PIN, data, 2);
|
||||
}
|
||||
|
||||
|
||||
@@ -304,9 +304,7 @@ void APC1Component::send_command_(APC1Command cmd, uint16_t data) {
|
||||
0,
|
||||
0,
|
||||
};
|
||||
uint16_t checksum = sum16_bytes(buf.data(), 5);
|
||||
buf[5] = static_cast<uint8_t>((checksum >> 8) & 0xFF);
|
||||
buf[6] = static_cast<uint8_t>(checksum & 0xFF);
|
||||
put_be16(buf.data() + 5, sum16_bytes(buf.data(), 5));
|
||||
this->write_array(buf);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,10 +60,8 @@ static constexpr i2c::I2CRegValue APDS9960_GESTURE_OFFSETS[] PROGMEM = {
|
||||
|
||||
void APDS9960::setup() {
|
||||
uint8_t id;
|
||||
if (!this->read_byte(0x92, &id)) { // ID register
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->read_byte(0x92, &id))) // ID register
|
||||
return;
|
||||
}
|
||||
|
||||
if (id != 0xAB && id != 0x9C && id != 0xA8 && id != 0x9E) { // APDS9960 all should have one of these IDs
|
||||
this->mark_failed(LOG_STR("APDS9960 has invalid id"));
|
||||
|
||||
@@ -22,6 +22,7 @@ service APIConnection {
|
||||
rpc device_capabilities (DeviceCapabilitiesRequest) returns (DeviceCapabilitiesResponse) {}
|
||||
rpc device_wizard (DeviceWizardRequest) returns (DeviceWizardResponse) {}
|
||||
rpc wizard_input_set (WizardInputSetRequest) returns (void) {}
|
||||
rpc crash_report (CrashReportRequest) returns (CrashReportResponse) {}
|
||||
rpc list_entities (ListEntitiesRequest) returns (void) {}
|
||||
rpc subscribe_states (SubscribeStatesRequest) returns (void) {}
|
||||
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
|
||||
@@ -83,6 +84,7 @@ service APIConnection {
|
||||
rpc serial_proxy_set_mode(SerialProxySetModeRequest) returns (void) {}
|
||||
|
||||
rpc sendspin_pairing_token(SendspinPairingTokenRequest) returns (SendspinPairingTokenResponse) {}
|
||||
rpc get_yaml(GetYamlRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -401,6 +403,11 @@ message ZWaveProxyCapabilities {
|
||||
uint32 home_id = 2;
|
||||
}
|
||||
|
||||
message StoreYamlCapabilities {
|
||||
// Firmware embeds its YAML, so clients can request it with get_yaml
|
||||
bool supported = 1;
|
||||
}
|
||||
|
||||
message WizardCapabilities {
|
||||
// True when the device has a wizard configured, so DeviceWizardRequest will be answered
|
||||
bool configured = 1;
|
||||
@@ -414,6 +421,12 @@ message SendspinCapabilities {
|
||||
string client_id = 2 [(max_data_length) = 43];
|
||||
}
|
||||
|
||||
message CrashReportCapabilities {
|
||||
// Bitmask of CrashReportFlag. ESP32 checks BUILD_MATCHES against the build time stored with the record;
|
||||
// ESP8266, RP2 and LibreTiny store none and assume it.
|
||||
uint32 flags = 1;
|
||||
}
|
||||
|
||||
message DeviceCapabilitiesResponse {
|
||||
option (id) = 150;
|
||||
option (source) = SOURCE_SERVER;
|
||||
@@ -425,6 +438,8 @@ message DeviceCapabilitiesResponse {
|
||||
[(field_ifdef) = "USE_SERIAL_PROXY", (fixed_array_size_define) = "SERIAL_PROXY_COUNT"];
|
||||
WizardCapabilities wizard = 5 [(field_ifdef) = "USE_API_WIZARD"];
|
||||
SendspinCapabilities sendspin = 6 [(field_ifdef) = "USE_SENDSPIN"];
|
||||
StoreYamlCapabilities store_yaml = 7 [(field_ifdef) = "USE_STORE_YAML"];
|
||||
CrashReportCapabilities crash_report = 8 [(field_ifdef) = "USE_CRASH_REPORT"];
|
||||
}
|
||||
|
||||
// ==================== DEVICE WIZARD ====================
|
||||
@@ -538,6 +553,36 @@ message SubscribeStatesRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ==================== CRASH REPORT ====================
|
||||
|
||||
// Send only when crash_report.flags has PRESENT. The report is dropped once sent.
|
||||
message CrashReportRequest {
|
||||
option (id) = 163;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_CRASH_REPORT";
|
||||
// Empty
|
||||
}
|
||||
|
||||
message CrashReportCore {
|
||||
uint32 core = 1;
|
||||
repeated uint32 backtrace = 2 [(fixed_array_with_length_define) = "CRASH_REPORT_MAX_BACKTRACE"];
|
||||
}
|
||||
|
||||
message CrashReportResponse {
|
||||
option (id) = 164;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CRASH_REPORT";
|
||||
|
||||
uint32 flags = 1; // Bitmask of CrashReportFlag
|
||||
uint32 reason = 2; // CrashReason
|
||||
uint32 pc = 3;
|
||||
uint32 cause = 4;
|
||||
uint32 fault_address = 5;
|
||||
uint32 build_time = 6;
|
||||
uint32 crashed_core = 7;
|
||||
repeated CrashReportCore cores = 8 [(fixed_array_with_length_define) = "CRASH_REPORT_MAX_CORES"];
|
||||
}
|
||||
|
||||
// ==================== COMMON =====================
|
||||
|
||||
enum EntityCategory {
|
||||
@@ -2192,6 +2237,8 @@ message BluetoothScannerSetModeRequest {
|
||||
enum VoiceAssistantSubscribeFlag {
|
||||
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;
|
||||
VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1;
|
||||
// Not 2 or 4: aioesphomeapi sends API audio as 1 << 2
|
||||
VOICE_ASSISTANT_SUBSCRIBE_WAKE_WORD_AUDIO = 8;
|
||||
}
|
||||
|
||||
message SubscribeVoiceAssistantRequest {
|
||||
@@ -2225,6 +2272,11 @@ message VoiceAssistantRequest {
|
||||
uint32 flags = 3;
|
||||
VoiceAssistantAudioSettings audio_settings = 4;
|
||||
string wake_word_phrase = 5;
|
||||
string wake_word_id = 6;
|
||||
uint32 wake_word_audio_bytes = 7;
|
||||
float wake_word_probability = 8;
|
||||
float wake_word_probability_cutoff = 9;
|
||||
uint32 wake_word_audio_bytes2 = 10;
|
||||
}
|
||||
|
||||
message VoiceAssistantResponse {
|
||||
@@ -2250,6 +2302,7 @@ enum VoiceAssistantEvent {
|
||||
VOICE_ASSISTANT_WAKE_WORD_END = 10;
|
||||
VOICE_ASSISTANT_STT_VAD_START = 11;
|
||||
VOICE_ASSISTANT_STT_VAD_END = 12;
|
||||
VOICE_ASSISTANT_WAKE_WORD_REJECTED = 13;
|
||||
VOICE_ASSISTANT_TTS_STREAM_START = 98;
|
||||
VOICE_ASSISTANT_TTS_STREAM_END = 99;
|
||||
VOICE_ASSISTANT_INTENT_PROGRESS = 100;
|
||||
@@ -2843,7 +2896,7 @@ message InfraredRFReceiveEvent {
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2 [(force) = true]; // Key identifying the receiver instance
|
||||
repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector<int32_t>"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods
|
||||
repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::span<const int32_t>"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods
|
||||
}
|
||||
|
||||
// Sent only to the client that issued an InfraredRFTransmitRawTimingsRequest, once the
|
||||
@@ -3071,6 +3124,7 @@ message UsbDeviceDescriptor {
|
||||
uint32 interface_number = 4; // bInterfaceNumber the port is bound through
|
||||
uint32 interface_class = 5; // bInterfaceClass of that interface
|
||||
uint32 port_number = 6;
|
||||
string interface_description = 7; // iInterface string of that interface, empty when it has none
|
||||
}
|
||||
|
||||
// The identity of the device behind a port. Identifies one physical device among others of
|
||||
@@ -3143,3 +3197,25 @@ message SendspinPairingTokenResponse {
|
||||
// The pairing token (Sendspin pairing.md "Pairing Token"). Set only when status is OK.
|
||||
string token = 2 [(max_data_length) = 127];
|
||||
}
|
||||
|
||||
// ==================== STORE YAML ====================
|
||||
// Streams the compressed user YAML embedded in firmware; the client decompresses.
|
||||
// A GetYamlRequest during an active transfer is ignored; reconnect to restart.
|
||||
message GetYamlRequest {
|
||||
option (id) = 161;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_STORE_YAML";
|
||||
option (no_delay) = true;
|
||||
}
|
||||
|
||||
message GetYamlResponse {
|
||||
option (id) = 162;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_STORE_YAML";
|
||||
|
||||
// A zstd frame of the EHY1 file envelope, sent from flash
|
||||
bytes data = 1 [(force) = true, (pointer_to_buffer) = true, (progmem) = true];
|
||||
bool done = 2 [(force) = true];
|
||||
// Set on the first chunk only
|
||||
uint32 total_size = 3;
|
||||
}
|
||||
@@ -366,6 +366,13 @@ void APIConnection::loop() {
|
||||
// (missing a frame is fine, missing a state update is not)
|
||||
this->try_send_camera_image_();
|
||||
#endif
|
||||
|
||||
#ifdef USE_STORE_YAML
|
||||
// Guard inline so the idle hot path pays a compare, not a function call.
|
||||
if (this->store_yaml_pos_ != STORE_YAML_IDLE) {
|
||||
this->try_send_store_yaml_();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIConnection::check_keepalive_(uint32_t now) {
|
||||
@@ -1222,6 +1229,36 @@ void APIConnection::on_camera_image_request(const CameraImageRequest &msg) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_STORE_YAML
|
||||
void APIConnection::on_get_yaml_request() {
|
||||
// A re-request while a transfer is in flight is ignored (see api.proto)
|
||||
if (this->store_yaml_pos_ != STORE_YAML_IDLE)
|
||||
return;
|
||||
this->store_yaml_pos_ = 0;
|
||||
this->try_send_store_yaml_();
|
||||
}
|
||||
|
||||
void APIConnection::try_send_store_yaml_() {
|
||||
// Advance only after a successful send, so a full TX buffer retries the same chunk from loop()
|
||||
while (this->helper_->can_write_without_blocking()) {
|
||||
const size_t to_send = std::min(STORE_YAML_DATA_SIZE - this->store_yaml_pos_, MAX_BATCH_PACKET_SIZE);
|
||||
GetYamlResponse resp;
|
||||
resp.data = STORE_YAML_DATA + this->store_yaml_pos_;
|
||||
resp.data_len = to_send;
|
||||
if (this->store_yaml_pos_ == 0)
|
||||
resp.total_size = STORE_YAML_DATA_SIZE;
|
||||
resp.done = this->store_yaml_pos_ + to_send == STORE_YAML_DATA_SIZE;
|
||||
if (!this->send_message(resp))
|
||||
return;
|
||||
if (resp.done) {
|
||||
this->store_yaml_pos_ = STORE_YAML_IDLE;
|
||||
return;
|
||||
}
|
||||
this->store_yaml_pos_ += to_send;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
@@ -1316,7 +1353,7 @@ bool APIConnection::check_voice_assistant_api_connection_() const {
|
||||
|
||||
void APIConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
|
||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
||||
voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe);
|
||||
voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe, msg.flags);
|
||||
}
|
||||
}
|
||||
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
|
||||
@@ -1932,7 +1969,7 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 19;
|
||||
resp.api_version_minor = 20;
|
||||
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
|
||||
resp.server_info = esphome_version_ref();
|
||||
resp.name = StringRef(App.get_name());
|
||||
@@ -2123,6 +2160,14 @@ bool APIConnection::send_device_capabilities_response_() {
|
||||
#if defined(USE_SENDSPIN) && defined(USE_ESP_IDF)
|
||||
resp.sendspin.feature_flags = sendspin_::global_sendspin_hub->get_feature_flags();
|
||||
resp.sendspin.client_id = sendspin_::global_sendspin_hub->get_client_id();
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
resp.store_yaml.supported = true;
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
CrashReport report;
|
||||
if (crash_report_get(report))
|
||||
resp.crash_report.flags = report.flags;
|
||||
#endif
|
||||
return this->send_message(resp);
|
||||
}
|
||||
@@ -2152,6 +2197,34 @@ void APIConnection::on_device_capabilities_request() {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
#ifdef USE_CRASH_REPORT
|
||||
void APIConnection::on_crash_report_request() {
|
||||
CrashReport report;
|
||||
CrashReportResponse resp;
|
||||
if (crash_report_get(report)) {
|
||||
resp.flags = report.flags;
|
||||
resp.reason = static_cast<uint32_t>(report.reason);
|
||||
resp.pc = report.pc;
|
||||
resp.cause = report.cause;
|
||||
resp.fault_address = report.fault_address;
|
||||
resp.build_time = report.build_time;
|
||||
resp.crashed_core = report.crashed_core;
|
||||
for (uint8_t i = 0; i < report.core_count; i++) {
|
||||
auto &core = resp.cores[i];
|
||||
core.core = report.cores[i].core;
|
||||
core.backtrace = report.cores[i].addresses;
|
||||
core.backtrace_len = report.cores[i].count;
|
||||
}
|
||||
resp.cores_len = report.core_count;
|
||||
}
|
||||
if (!this->send_message(resp)) {
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
if (resp.flags & CRASH_REPORT_FLAG_PRESENT)
|
||||
crash_report_clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||
@@ -2365,10 +2438,14 @@ bool APIConnection::try_to_clear_buffer_slow_(bool log_out_of_space) {
|
||||
bool APIConnection::send_message_(uint32_t payload_size, uint16_t message_type, MessageEncodeFn encode_fn,
|
||||
const void *msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
// Skip dump for log messages (recursive logging risk) and camera frames (high-frequency noise)
|
||||
// Skip dump for log messages (recursive logging risk), camera frames (high-frequency noise)
|
||||
// and YAML chunks (may contain secrets)
|
||||
if (message_type != SubscribeLogsResponse::MESSAGE_TYPE
|
||||
#ifdef USE_CAMERA
|
||||
&& message_type != CameraImageResponse::MESSAGE_TYPE
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
&& message_type != GetYamlResponse::MESSAGE_TYPE
|
||||
#endif
|
||||
) {
|
||||
auto *proto_msg = static_cast<const ProtoMessage *>(msg);
|
||||
|
||||
@@ -32,6 +32,11 @@ namespace esphome::api {
|
||||
// Forward-declared to break the api_server.h cycle; full-type inlines are in api_connection_buffer.h.
|
||||
class APIServer;
|
||||
|
||||
#ifdef USE_STORE_YAML
|
||||
/// The zstd YAML envelope generated by store_yaml, STORE_YAML_DATA_SIZE bytes in flash.
|
||||
extern const uint8_t STORE_YAML_DATA[] PROGMEM;
|
||||
#endif
|
||||
|
||||
// One shared flash string for every refused-frame warning: send_message()
|
||||
// fails as soon as the TCP buffer is full, and each caller only pays for its
|
||||
// short name. The guard drops the helper and its arguments below WARN.
|
||||
@@ -123,6 +128,9 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
void set_camera_state(std::shared_ptr<camera::CameraImage> image);
|
||||
void on_camera_image_request(const CameraImageRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
void on_get_yaml_request();
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state(climate::Climate *climate);
|
||||
void on_climate_command_request(const ClimateCommandRequest &msg);
|
||||
@@ -277,6 +285,9 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
#ifdef USE_API_WIZARD
|
||||
void on_device_wizard_request();
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
void on_crash_report_request();
|
||||
#endif
|
||||
#ifdef USE_API_WIZARD_INPUTS
|
||||
void on_wizard_input_set_request(const WizardInputSetRequest &msg);
|
||||
#endif
|
||||
@@ -421,6 +432,13 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
void try_send_camera_image_();
|
||||
#endif
|
||||
|
||||
#ifdef USE_STORE_YAML
|
||||
void try_send_store_yaml_();
|
||||
static constexpr size_t STORE_YAML_IDLE = std::numeric_limits<size_t>::max();
|
||||
// Streaming offset into STORE_YAML_DATA, or STORE_YAML_IDLE
|
||||
size_t store_yaml_pos_{STORE_YAML_IDLE};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void process_state_subscriptions_();
|
||||
#endif
|
||||
|
||||
@@ -299,6 +299,19 @@ uint32_t ZWaveProxyCapabilities::calc_size_msg(const void *self) {
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
uint8_t *StoreYamlCapabilities::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const StoreYamlCapabilities *>(self);
|
||||
pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.supported);
|
||||
return pos;
|
||||
}
|
||||
uint32_t StoreYamlCapabilities::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const StoreYamlCapabilities *>(self);
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_bool(1, msg.supported);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_WIZARD
|
||||
uint8_t *WizardCapabilities::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const WizardCapabilities *>(self);
|
||||
@@ -327,6 +340,19 @@ uint32_t SendspinCapabilities::calc_size_msg(const void *self) {
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
uint8_t *CrashReportCapabilities::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const CrashReportCapabilities *>(self);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.flags);
|
||||
return pos;
|
||||
}
|
||||
uint32_t CrashReportCapabilities::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const CrashReportCapabilities *>(self);
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_uint32(1, msg.flags);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
uint8_t *DeviceCapabilitiesResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const DeviceCapabilitiesResponse *>(self);
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
@@ -348,6 +374,12 @@ uint8_t *DeviceCapabilitiesResponse::encode_msg(const void *self, uint8_t *__res
|
||||
#endif
|
||||
#ifdef USE_SENDSPIN
|
||||
pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 6, msg.sendspin);
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 7, msg.store_yaml);
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 8, msg.crash_report);
|
||||
#endif
|
||||
return pos;
|
||||
}
|
||||
@@ -373,6 +405,12 @@ uint32_t DeviceCapabilitiesResponse::calc_size_msg(const void *self) {
|
||||
#endif
|
||||
#ifdef USE_SENDSPIN
|
||||
size += ProtoSize::calc_message(1, msg.sendspin.calculate_size());
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
size += ProtoSize::calc_message(1, msg.store_yaml.calculate_size());
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
size += ProtoSize::calc_message(1, msg.crash_report.calculate_size());
|
||||
#endif
|
||||
return size;
|
||||
}
|
||||
@@ -405,6 +443,54 @@ void WizardInputSetRequest::decode_field(void *self, uint32_t tag, const uint8_t
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
uint8_t *CrashReportCore::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const CrashReportCore *>(self);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.core);
|
||||
for (uint16_t i = 0; i < msg.backtrace_len; i++) {
|
||||
pos = ProtoEncode::encode_uint32_force(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.backtrace[i]);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
uint32_t CrashReportCore::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const CrashReportCore *>(self);
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_uint32(1, msg.core);
|
||||
for (uint16_t i = 0; i < msg.backtrace_len; i++) {
|
||||
size += ProtoSize::calc_uint32_force(1, msg.backtrace[i]);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
uint8_t *CrashReportResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const CrashReportResponse *>(self);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.flags);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.reason);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.pc);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, msg.cause);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, msg.fault_address);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 6, msg.build_time);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, msg.crashed_core);
|
||||
for (uint16_t i = 0; i < msg.cores_len; i++) {
|
||||
pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 8, msg.cores[i]);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
uint32_t CrashReportResponse::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const CrashReportResponse *>(self);
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_uint32(1, msg.flags);
|
||||
size += ProtoSize::calc_uint32(1, msg.reason);
|
||||
size += ProtoSize::calc_uint32(1, msg.pc);
|
||||
size += ProtoSize::calc_uint32(1, msg.cause);
|
||||
size += ProtoSize::calc_uint32(1, msg.fault_address);
|
||||
size += ProtoSize::calc_uint32(1, msg.build_time);
|
||||
size += ProtoSize::calc_uint32(1, msg.crashed_core);
|
||||
for (uint16_t i = 0; i < msg.cores_len; i++) {
|
||||
size += ProtoSize::calc_message_force(1, msg.cores[i].calculate_size());
|
||||
}
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
uint8_t *ListEntitiesBinarySensorResponse::encode_msg(const void *self,
|
||||
uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
@@ -3028,6 +3114,15 @@ uint8_t *VoiceAssistantRequest::encode_msg(const void *self, uint8_t *__restrict
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.flags);
|
||||
pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 4, msg.audio_settings);
|
||||
pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, msg.wake_word_phrase);
|
||||
pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 6, msg.wake_word_id);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, msg.wake_word_audio_bytes);
|
||||
if (uint32_t raw = float_to_raw(msg.wake_word_probability); raw != 0) [[likely]] {
|
||||
pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 69, raw);
|
||||
}
|
||||
if (uint32_t raw = float_to_raw(msg.wake_word_probability_cutoff); raw != 0) [[likely]] {
|
||||
pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 77, raw);
|
||||
}
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.wake_word_audio_bytes2);
|
||||
return pos;
|
||||
}
|
||||
uint32_t VoiceAssistantRequest::calc_size_msg(const void *self) {
|
||||
@@ -3038,6 +3133,11 @@ uint32_t VoiceAssistantRequest::calc_size_msg(const void *self) {
|
||||
size += ProtoSize::calc_uint32(1, msg.flags);
|
||||
size += ProtoSize::calc_message(1, msg.audio_settings.calculate_size());
|
||||
size += ProtoSize::calc_length(1, msg.wake_word_phrase.size());
|
||||
size += ProtoSize::calc_length(1, msg.wake_word_id.size());
|
||||
size += ProtoSize::calc_uint32(1, msg.wake_word_audio_bytes);
|
||||
size += ProtoSize::calc_float(1, msg.wake_word_probability);
|
||||
size += ProtoSize::calc_float(1, msg.wake_word_probability_cutoff);
|
||||
size += ProtoSize::calc_uint32(1, msg.wake_word_audio_bytes2);
|
||||
return size;
|
||||
}
|
||||
void VoiceAssistantResponse::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) {
|
||||
@@ -4314,6 +4414,7 @@ uint8_t *UsbDeviceDescriptor::encode_msg(const void *self, uint8_t *__restrict__
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, msg.interface_number);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, msg.interface_class);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 6, msg.port_number);
|
||||
pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 7, msg.interface_description);
|
||||
return pos;
|
||||
}
|
||||
uint32_t UsbDeviceDescriptor::calc_size_msg(const void *self) {
|
||||
@@ -4325,6 +4426,7 @@ uint32_t UsbDeviceDescriptor::calc_size_msg(const void *self) {
|
||||
size += ProtoSize::calc_uint32(1, msg.interface_number);
|
||||
size += ProtoSize::calc_uint32(1, msg.interface_class);
|
||||
size += ProtoSize::calc_uint32(1, msg.port_number);
|
||||
size += ProtoSize::calc_length(1, msg.interface_description.size());
|
||||
return size;
|
||||
}
|
||||
uint8_t *SerialProxyIdentity::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
@@ -4407,6 +4509,24 @@ uint32_t SendspinPairingTokenResponse::calc_size_msg(const void *self) {
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
uint8_t *GetYamlResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const GetYamlResponse *>(self);
|
||||
pos = ProtoEncode::encode_progmem_bytes_force(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.data, msg.data_len);
|
||||
pos = ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 16);
|
||||
pos = ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, msg.done ? 0x01 : 0x00);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.total_size);
|
||||
return pos;
|
||||
}
|
||||
uint32_t GetYamlResponse::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const GetYamlResponse *>(self);
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length_force(1, msg.data_len);
|
||||
size += ProtoSize::calc_bool_force(1);
|
||||
size += ProtoSize::calc_uint32(1, msg.total_size);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#ifndef HAS_PROTO_MESSAGE_DUMP
|
||||
static_assert(!std::is_polymorphic_v<HelloRequest>, "decodable messages carry no vtable");
|
||||
static_assert(!std::is_polymorphic_v<DisconnectRequest>, "decodable messages carry no vtable");
|
||||
|
||||
@@ -262,6 +262,7 @@ enum BluetoothScannerMode : uint32_t {
|
||||
enum VoiceAssistantSubscribeFlag : uint32_t {
|
||||
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0,
|
||||
VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1,
|
||||
VOICE_ASSISTANT_SUBSCRIBE_WAKE_WORD_AUDIO = 8,
|
||||
};
|
||||
enum VoiceAssistantRequestFlag : uint32_t {
|
||||
VOICE_ASSISTANT_REQUEST_NONE = 0,
|
||||
@@ -283,6 +284,7 @@ enum VoiceAssistantEvent : uint32_t {
|
||||
VOICE_ASSISTANT_WAKE_WORD_END = 10,
|
||||
VOICE_ASSISTANT_STT_VAD_START = 11,
|
||||
VOICE_ASSISTANT_STT_VAD_END = 12,
|
||||
VOICE_ASSISTANT_WAKE_WORD_REJECTED = 13,
|
||||
VOICE_ASSISTANT_TTS_STREAM_START = 98,
|
||||
VOICE_ASSISTANT_TTS_STREAM_END = 99,
|
||||
VOICE_ASSISTANT_INTENT_PROGRESS = 100,
|
||||
@@ -734,6 +736,23 @@ class ZWaveProxyCapabilities final : public ProtoMessage {
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
class StoreYamlCapabilities final : public ProtoMessage {
|
||||
public:
|
||||
bool supported{false};
|
||||
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);
|
||||
}
|
||||
static uint32_t calc_size_msg(const void *self);
|
||||
uint32_t calculate_size() const { return calc_size_msg(this); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_API_WIZARD
|
||||
class WizardCapabilities final : public ProtoMessage {
|
||||
public:
|
||||
@@ -769,10 +788,27 @@ class SendspinCapabilities final : public ProtoMessage {
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
class CrashReportCapabilities final : public ProtoMessage {
|
||||
public:
|
||||
uint32_t flags{0};
|
||||
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);
|
||||
}
|
||||
static uint32_t calc_size_msg(const void *self);
|
||||
uint32_t calculate_size() const { return calc_size_msg(this); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
class DeviceCapabilitiesResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 150;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 136;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 170;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("device_capabilities_response"); }
|
||||
#endif
|
||||
@@ -793,6 +829,12 @@ class DeviceCapabilitiesResponse final : public ProtoMessage {
|
||||
#endif
|
||||
#ifdef USE_SENDSPIN
|
||||
SendspinCapabilities sendspin{};
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
StoreYamlCapabilities store_yaml{};
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
CrashReportCapabilities crash_report{};
|
||||
#endif
|
||||
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
@@ -863,6 +905,53 @@ class ListEntitiesDoneResponse final : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
};
|
||||
#ifdef USE_CRASH_REPORT
|
||||
class CrashReportCore final : public ProtoMessage {
|
||||
public:
|
||||
uint32_t core{0};
|
||||
std::array<uint32_t, CRASH_REPORT_MAX_BACKTRACE> backtrace{};
|
||||
uint16_t backtrace_len{0};
|
||||
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);
|
||||
}
|
||||
static uint32_t calc_size_msg(const void *self);
|
||||
uint32_t calculate_size() const { return calc_size_msg(this); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class CrashReportResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 164;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 164;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("crash_report_response"); }
|
||||
#endif
|
||||
uint32_t flags{0};
|
||||
uint32_t reason{0};
|
||||
uint32_t pc{0};
|
||||
uint32_t cause{0};
|
||||
uint32_t fault_address{0};
|
||||
uint32_t build_time{0};
|
||||
uint32_t crashed_core{0};
|
||||
std::array<CrashReportCore, CRASH_REPORT_MAX_CORES> cores{};
|
||||
uint16_t cores_len{0};
|
||||
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);
|
||||
}
|
||||
static uint32_t calc_size_msg(const void *self);
|
||||
uint32_t calculate_size() const { return calc_size_msg(this); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
@@ -2953,7 +3042,7 @@ class VoiceAssistantAudioSettings final : public ProtoMessage {
|
||||
class VoiceAssistantRequest final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 90;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 41;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 68;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("voice_assistant_request"); }
|
||||
#endif
|
||||
@@ -2962,6 +3051,11 @@ class VoiceAssistantRequest final : public ProtoMessage {
|
||||
uint32_t flags{0};
|
||||
VoiceAssistantAudioSettings audio_settings{};
|
||||
StringRef wake_word_phrase{nullptr, 0}; // null until set, encode only
|
||||
StringRef wake_word_id{nullptr, 0}; // null until set, encode only
|
||||
uint32_t wake_word_audio_bytes{0};
|
||||
float wake_word_probability{0.0f};
|
||||
float wake_word_probability_cutoff{0.0f};
|
||||
uint32_t wake_word_audio_bytes2{0};
|
||||
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);
|
||||
@@ -3841,7 +3935,7 @@ class InfraredRFReceiveEvent final : public ProtoMessage {
|
||||
uint32_t device_id{0};
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
const std::vector<int32_t> *timings{};
|
||||
const std::span<const int32_t> *timings{};
|
||||
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);
|
||||
@@ -4102,6 +4196,7 @@ class UsbDeviceDescriptor final : public ProtoMessage {
|
||||
uint32_t interface_number{0};
|
||||
uint32_t interface_class{0};
|
||||
uint32_t port_number{0};
|
||||
StringRef interface_description{nullptr, 0}; // null until set, encode only
|
||||
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);
|
||||
@@ -4209,5 +4304,30 @@ class SendspinPairingTokenResponse final : public ProtoMessage {
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
class GetYamlResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 162;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 25;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("get_yaml_response"); }
|
||||
#endif
|
||||
const uint8_t *data{nullptr};
|
||||
uint16_t data_len{0};
|
||||
bool done{false};
|
||||
uint32_t total_size{0};
|
||||
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);
|
||||
}
|
||||
static uint32_t calc_size_msg(const void *self);
|
||||
uint32_t calculate_size() const { return calc_size_msg(this); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace esphome::api
|
||||
@@ -675,6 +675,8 @@ const char *proto_enum_to_string<enums::VoiceAssistantSubscribeFlag>(enums::Voic
|
||||
return ESPHOME_PSTR("VOICE_ASSISTANT_SUBSCRIBE_NONE");
|
||||
case enums::VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO:
|
||||
return ESPHOME_PSTR("VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO");
|
||||
case enums::VOICE_ASSISTANT_SUBSCRIBE_WAKE_WORD_AUDIO:
|
||||
return ESPHOME_PSTR("VOICE_ASSISTANT_SUBSCRIBE_WAKE_WORD_AUDIO");
|
||||
default:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
@@ -720,6 +722,8 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V
|
||||
return ESPHOME_PSTR("VOICE_ASSISTANT_STT_VAD_START");
|
||||
case enums::VOICE_ASSISTANT_STT_VAD_END:
|
||||
return ESPHOME_PSTR("VOICE_ASSISTANT_STT_VAD_END");
|
||||
case enums::VOICE_ASSISTANT_WAKE_WORD_REJECTED:
|
||||
return ESPHOME_PSTR("VOICE_ASSISTANT_WAKE_WORD_REJECTED");
|
||||
case enums::VOICE_ASSISTANT_TTS_STREAM_START:
|
||||
return ESPHOME_PSTR("VOICE_ASSISTANT_TTS_STREAM_START");
|
||||
case enums::VOICE_ASSISTANT_TTS_STREAM_END:
|
||||
@@ -1127,6 +1131,13 @@ const char *ZWaveProxyCapabilities::dump_to(DumpBuffer &out) const {
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
const char *StoreYamlCapabilities::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("StoreYamlCapabilities"));
|
||||
dump_field(out, ESPHOME_PSTR("supported"), this->supported);
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_WIZARD
|
||||
const char *WizardCapabilities::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("WizardCapabilities"));
|
||||
@@ -1142,6 +1153,13 @@ const char *SendspinCapabilities::dump_to(DumpBuffer &out) const {
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
const char *CrashReportCapabilities::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("CrashReportCapabilities"));
|
||||
dump_field(out, ESPHOME_PSTR("flags"), this->flags);
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
const char *DeviceCapabilitiesResponse::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("DeviceCapabilitiesResponse"));
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
@@ -1175,6 +1193,16 @@ const char *DeviceCapabilitiesResponse::dump_to(DumpBuffer &out) const {
|
||||
out.append(2, ' ').append_p(ESPHOME_PSTR("sendspin")).append(": ");
|
||||
this->sendspin.dump_to(out);
|
||||
out.append("\n");
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
out.append(2, ' ').append_p(ESPHOME_PSTR("store_yaml")).append(": ");
|
||||
this->store_yaml.dump_to(out);
|
||||
out.append("\n");
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
out.append(2, ' ').append_p(ESPHOME_PSTR("crash_report")).append(": ");
|
||||
this->crash_report.dump_to(out);
|
||||
out.append("\n");
|
||||
#endif
|
||||
return out.c_str();
|
||||
}
|
||||
@@ -1197,6 +1225,32 @@ const char *ListEntitiesDoneResponse::dump_to(DumpBuffer &out) const {
|
||||
out.append_p(ESPHOME_PSTR("ListEntitiesDoneResponse {}"));
|
||||
return out.c_str();
|
||||
}
|
||||
#ifdef USE_CRASH_REPORT
|
||||
const char *CrashReportCore::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("CrashReportCore"));
|
||||
dump_field(out, ESPHOME_PSTR("core"), this->core);
|
||||
for (uint16_t i = 0; i < this->backtrace_len; i++) {
|
||||
dump_field(out, ESPHOME_PSTR("backtrace"), this->backtrace[i], 4);
|
||||
}
|
||||
return out.c_str();
|
||||
}
|
||||
const char *CrashReportResponse::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("CrashReportResponse"));
|
||||
dump_field(out, ESPHOME_PSTR("flags"), this->flags);
|
||||
dump_field(out, ESPHOME_PSTR("reason"), this->reason);
|
||||
dump_field(out, ESPHOME_PSTR("pc"), this->pc);
|
||||
dump_field(out, ESPHOME_PSTR("cause"), this->cause);
|
||||
dump_field(out, ESPHOME_PSTR("fault_address"), this->fault_address);
|
||||
dump_field(out, ESPHOME_PSTR("build_time"), this->build_time);
|
||||
dump_field(out, ESPHOME_PSTR("crashed_core"), this->crashed_core);
|
||||
for (uint16_t i = 0; i < this->cores_len; i++) {
|
||||
out.append(4, ' ').append_p(ESPHOME_PSTR("cores")).append(": ");
|
||||
this->cores[i].dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
const char *ListEntitiesBinarySensorResponse::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesBinarySensorResponse"));
|
||||
@@ -2391,6 +2445,11 @@ const char *VoiceAssistantRequest::dump_to(DumpBuffer &out) const {
|
||||
this->audio_settings.dump_to(out);
|
||||
out.append("\n");
|
||||
dump_field(out, ESPHOME_PSTR("wake_word_phrase"), this->wake_word_phrase);
|
||||
dump_field(out, ESPHOME_PSTR("wake_word_id"), this->wake_word_id);
|
||||
dump_field(out, ESPHOME_PSTR("wake_word_audio_bytes"), this->wake_word_audio_bytes);
|
||||
dump_field(out, ESPHOME_PSTR("wake_word_probability"), this->wake_word_probability);
|
||||
dump_field(out, ESPHOME_PSTR("wake_word_probability_cutoff"), this->wake_word_probability_cutoff);
|
||||
dump_field(out, ESPHOME_PSTR("wake_word_audio_bytes2"), this->wake_word_audio_bytes2);
|
||||
return out.c_str();
|
||||
}
|
||||
const char *VoiceAssistantResponse::dump_to(DumpBuffer &out) const {
|
||||
@@ -2975,6 +3034,7 @@ const char *UsbDeviceDescriptor::dump_to(DumpBuffer &out) const {
|
||||
dump_field(out, ESPHOME_PSTR("interface_number"), this->interface_number);
|
||||
dump_field(out, ESPHOME_PSTR("interface_class"), this->interface_class);
|
||||
dump_field(out, ESPHOME_PSTR("port_number"), this->port_number);
|
||||
dump_field(out, ESPHOME_PSTR("interface_description"), this->interface_description);
|
||||
return out.c_str();
|
||||
}
|
||||
const char *SerialProxyIdentity::dump_to(DumpBuffer &out) const {
|
||||
@@ -3016,6 +3076,15 @@ const char *SendspinPairingTokenResponse::dump_to(DumpBuffer &out) const {
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
const char *GetYamlResponse::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("GetYamlResponse"));
|
||||
dump_progmem_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len);
|
||||
dump_field(out, ESPHOME_PSTR("done"), this->done);
|
||||
dump_field(out, ESPHOME_PSTR("total_size"), this->total_size);
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
#include "esphome/components/select/select_traits.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_CRASH_REPORT
|
||||
#include "esphome/core/crash_report.h"
|
||||
#endif
|
||||
|
||||
// Standard library includes that might be needed
|
||||
#include <set>
|
||||
#include <span>
|
||||
|
||||
@@ -760,6 +760,24 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
|
||||
this->on_sendspin_pairing_token_request();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_STORE_YAML
|
||||
case 161 /* GetYamlRequest is empty */: {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_receive_message_(LOG_STR("on_get_yaml_request"));
|
||||
#endif
|
||||
this->on_get_yaml_request();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CRASH_REPORT
|
||||
case 163 /* CrashReportRequest is empty */: {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_receive_message_(LOG_STR("on_crash_report_request"));
|
||||
#endif
|
||||
this->on_crash_report_request();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -39,6 +39,9 @@ class APIServerConnectionBase {
|
||||
void on_list_entities_request() {};
|
||||
|
||||
void on_subscribe_states_request() {};
|
||||
#ifdef USE_CRASH_REPORT
|
||||
void on_crash_report_request() {};
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
void on_cover_command_request(const CoverCommandRequest &value) {};
|
||||
@@ -256,6 +259,10 @@ class APIServerConnectionBase {
|
||||
#ifdef USE_SENDSPIN
|
||||
void on_sendspin_pairing_token_request() {};
|
||||
#endif
|
||||
|
||||
#ifdef USE_STORE_YAML
|
||||
void on_get_yaml_request() {};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace esphome::api
|
||||
@@ -520,13 +520,13 @@ void APIServer::send_serial_proxy_identity(const SerialProxyIdentity &msg) {
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_id, uint32_t key,
|
||||
const std::vector<int32_t> *timings) {
|
||||
std::span<const int32_t> timings) {
|
||||
InfraredRFReceiveEvent resp{};
|
||||
#ifdef USE_DEVICES
|
||||
resp.device_id = device_id;
|
||||
#endif
|
||||
resp.key = key;
|
||||
resp.timings = timings;
|
||||
resp.timings = &timings;
|
||||
|
||||
for (auto &c : this->active_clients())
|
||||
c->send_infrared_rf_receive_event(resp);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::api {
|
||||
@@ -208,7 +209,7 @@ class APIServer final : public Component
|
||||
void send_serial_proxy_identity(const SerialProxyIdentity &msg);
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector<int32_t> *timings);
|
||||
void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, std::span<const int32_t> timings);
|
||||
#endif
|
||||
|
||||
bool is_connected() const { return this->api_connection_count_ != 0; }
|
||||
|
||||
@@ -22,10 +22,8 @@ static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R
|
||||
static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
|
||||
|
||||
void AS5600Component::setup() {
|
||||
if (!this->read_byte(REGISTER_STATUS).has_value()) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->read_byte(REGISTER_STATUS).has_value()))
|
||||
return;
|
||||
}
|
||||
|
||||
// configuration direction pin, if given
|
||||
// the dir pin on the chip should be low for clockwise
|
||||
@@ -53,10 +51,8 @@ void AS5600Component::setup() {
|
||||
// clang-format on
|
||||
|
||||
// write config to config register
|
||||
if (!this->write_byte_16(REGISTER_CONF, config)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_byte_16(REGISTER_CONF, config)))
|
||||
return;
|
||||
}
|
||||
|
||||
// configure the start position
|
||||
this->write_byte_16(REGISTER_ZPOS, this->start_position_);
|
||||
|
||||
@@ -315,10 +315,8 @@ void AS734XComponent::setup() {
|
||||
this->write_byte(AS7343_CHAIN_CMD, 0x46 + (i / len) * 0x10);
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(ok))
|
||||
return;
|
||||
}
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -160,17 +160,14 @@ void spi_dma_tx_finish_callback(unsigned int param) {
|
||||
|
||||
void BekenSPILEDStripLightOutput::setup() {
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
if (!this->internal_accessor_.allocate(allocator, this->num_leds_)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_ALLOC_FAIL));
|
||||
if (!this->setup_check(this->internal_accessor_.allocate(allocator, this->num_leds_),
|
||||
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) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_ALLOC_FAIL));
|
||||
if (!this->setup_check(this->dma_buf_ != nullptr, LOG_STR(ESP_LOG_MSG_ALLOC_FAIL)))
|
||||
return;
|
||||
}
|
||||
|
||||
memset(this->dma_buf_, 0, dma_buffer_size);
|
||||
|
||||
@@ -180,22 +177,16 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
value = PWD_SPI_CLK_BIT;
|
||||
sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_UP, &value);
|
||||
|
||||
if (spi_data != nullptr) {
|
||||
this->mark_failed(LOG_STR("SPI already initialized"));
|
||||
if (!this->setup_check(spi_data == nullptr, LOG_STR("SPI already initialized")))
|
||||
return;
|
||||
}
|
||||
|
||||
spi_data = (SpiData *) calloc(1, sizeof(SpiData)); // NOLINT(cppcoreguidelines-no-malloc)
|
||||
if (spi_data == nullptr) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_ALLOC_FAIL));
|
||||
if (!this->setup_check(spi_data != nullptr, LOG_STR(ESP_LOG_MSG_ALLOC_FAIL)))
|
||||
return;
|
||||
}
|
||||
|
||||
spi_data->dma_tx_semaphore = xSemaphoreCreateBinary();
|
||||
if (spi_data->dma_tx_semaphore == nullptr) {
|
||||
this->mark_failed(LOG_STR("TX semaphore init failed"));
|
||||
if (!this->setup_check(spi_data->dma_tx_semaphore != nullptr, LOG_STR("TX semaphore init failed")))
|
||||
return;
|
||||
}
|
||||
|
||||
spi_data->first_run = true;
|
||||
|
||||
|
||||
@@ -71,10 +71,8 @@ void BH1745Component::setup() {
|
||||
this->mark_failed(LOG_STR("Wrong part ID"));
|
||||
return;
|
||||
}
|
||||
if (!this->write_byte(REG_SYSTEM_CONTROL, SYSTEM_CONTROL_SW_RESET)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_byte(REG_SYSTEM_CONTROL, SYSTEM_CONTROL_SW_RESET)))
|
||||
return;
|
||||
}
|
||||
|
||||
// Light lost behind glass is scaled back up, as the other light sensors do
|
||||
this->lux_scale_ = 160.0f * this->glass_attenuation_factor_ /
|
||||
@@ -85,10 +83,8 @@ void BH1745Component::setup() {
|
||||
// High threshold 0 and low threshold 0xFFFF (low byte first) make the interrupt condition always
|
||||
// true, so the INT pin simply follows the enable bit, which drives the breakout's LEDs.
|
||||
static constexpr uint8_t THRESHOLDS[4] = {0x00, 0x00, 0xFF, 0xFF};
|
||||
if (!this->write_bytes(REG_TH_LSB, THRESHOLDS, sizeof(THRESHOLDS))) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_bytes(REG_TH_LSB, THRESHOLDS, sizeof(THRESHOLDS))))
|
||||
return;
|
||||
}
|
||||
}
|
||||
// RGBC_EN makes the chip measure continuously; update() reads the latest result.
|
||||
if (!this->write_byte(REG_MODE_CONTROL1, static_cast<uint8_t>(this->measurement_time_)) ||
|
||||
|
||||
@@ -64,10 +64,8 @@ MTreg:
|
||||
|
||||
void BH1750Sensor::setup() {
|
||||
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
||||
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write(&turn_on, 1) == i2c::ERROR_OK))
|
||||
return;
|
||||
}
|
||||
this->state_ = IDLE;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,8 @@ void BH1900NUXSensor::setup() {
|
||||
// Initialize I2C device
|
||||
i2c::ErrorCode result_code =
|
||||
this->write_register(SOFT_RESET_REG, &SOFT_RESET_PAYLOAD, 1); // Software Reset to check communication
|
||||
if (result_code != i2c::ERROR_OK) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(result_code == i2c::ERROR_OK))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BH1900NUXSensor::update() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "bl0942.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
|
||||
@@ -89,26 +90,19 @@ void BL0942::write_reg_(uint8_t reg, uint32_t val) {
|
||||
pkt[2] = (val & 0xff);
|
||||
pkt[3] = (val >> 8) & 0xff;
|
||||
pkt[4] = (val >> 16) & 0xff;
|
||||
pkt[5] = (pkt[0] + pkt[1] + pkt[2] + pkt[3] + pkt[4]) ^ 0xff;
|
||||
pkt[5] = sum_bytes(pkt, 5) ^ 0xFF;
|
||||
this->write_array(pkt, 6);
|
||||
delay(1);
|
||||
}
|
||||
|
||||
int BL0942::read_reg_(uint8_t reg) {
|
||||
union {
|
||||
uint8_t b[4];
|
||||
uint32_le_t le32;
|
||||
} resp;
|
||||
uint8_t resp[4];
|
||||
|
||||
this->write_byte(BL0942_READ_COMMAND | this->address_);
|
||||
this->write_byte(reg);
|
||||
this->flush();
|
||||
if (this->read_array(resp.b, 4) &&
|
||||
resp.b[3] ==
|
||||
(uint8_t) ((BL0942_READ_COMMAND + this->address_ + reg + resp.b[0] + resp.b[1] + resp.b[2]) ^ 0xff)) {
|
||||
resp.b[3] = 0;
|
||||
return resp.le32;
|
||||
}
|
||||
if (this->read_array(resp, 4) && resp[3] == (sum_bytes(resp, 3, BL0942_READ_COMMAND + this->address_ + reg) ^ 0xFF))
|
||||
return encode_uint24(resp[2], resp[1], resp[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,8 @@ static constexpr uint8_t CLOCK_1_60_HZ = 0x83;
|
||||
static constexpr uint32_t MAX_TIMER_DURATION_S = 255 * 60;
|
||||
|
||||
void BM8563::setup() {
|
||||
if (!this->write_byte_16(CONTROL_STATUS_1_REG, 0)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_byte_16(CONTROL_STATUS_1_REG, 0)))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BM8563::update() { this->read_time(); }
|
||||
|
||||
@@ -75,29 +75,21 @@ void BME280Component::setup() {
|
||||
this->reset_to_construction_state();
|
||||
}
|
||||
|
||||
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->read_byte(BME280_REGISTER_CHIPID, &chip_id)))
|
||||
return;
|
||||
}
|
||||
if (chip_id != 0x60) {
|
||||
this->mark_failed(LOG_STR("Wrong chip ID or no response"));
|
||||
if (!this->setup_check(chip_id == 0x60, LOG_STR("Wrong chip ID or no response")))
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a soft reset.
|
||||
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
|
||||
this->mark_failed(LOG_STR("Reset failed"));
|
||||
if (!this->setup_check(this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET), LOG_STR("Reset failed")))
|
||||
return;
|
||||
}
|
||||
// Wait until the NVM data has finished loading.
|
||||
uint8_t status;
|
||||
uint8_t retry = 5;
|
||||
do { // NOLINT
|
||||
delay(2);
|
||||
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
|
||||
this->mark_failed(LOG_STR("Error reading status register"));
|
||||
if (!this->setup_check(this->read_byte(BME280_REGISTER_STATUS, &status), LOG_STR("Error reading status register")))
|
||||
return;
|
||||
}
|
||||
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
|
||||
if (status & BME280_STATUS_IM_UPDATE) {
|
||||
this->mark_failed(LOG_STR("Timeout loading NVM"));
|
||||
@@ -129,29 +121,23 @@ void BME280Component::setup() {
|
||||
this->calibration_.h6 = read_u8_(BME280_REGISTER_DIG_H6);
|
||||
|
||||
uint8_t humid_control_val = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
|
||||
this->mark_failed(LOG_STR("Read humidity control"));
|
||||
if (!this->setup_check(this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val),
|
||||
LOG_STR("Read humidity control")))
|
||||
return;
|
||||
}
|
||||
humid_control_val &= ~0b00000111;
|
||||
humid_control_val |= this->humidity_oversampling_ & 0b111;
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
|
||||
this->mark_failed(LOG_STR("Write humidity control"));
|
||||
if (!this->setup_check(this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val),
|
||||
LOG_STR("Write humidity control")))
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t config_register = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed(LOG_STR("Read config"));
|
||||
if (!this->setup_check(this->read_byte(BME280_REGISTER_CONFIG, &config_register), LOG_STR("Read config")))
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b11111100;
|
||||
config_register |= 0b101 << 5; // 1000 ms standby time
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed(LOG_STR("Write config"));
|
||||
if (!this->setup_check(this->write_byte(BME280_REGISTER_CONFIG, config_register), LOG_STR("Write config")))
|
||||
return;
|
||||
}
|
||||
}
|
||||
void BME280Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BME280:");
|
||||
@@ -171,6 +157,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 +200,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 +219,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 +255,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;
|
||||
|
||||
@@ -193,12 +193,54 @@ void BME680BSECComponent::loop() {
|
||||
}
|
||||
this->status_comm_result(this->bsec_status_ <= BSEC_OK && this->bme680_status_ <= BME680_OK, nullptr);
|
||||
|
||||
// Process a single action from the queue. These are primarily sensor state publishes
|
||||
// that in totality take too long to send in a single call.
|
||||
if (this->queue_.size()) {
|
||||
auto action = std::move(this->queue_.front());
|
||||
this->queue_.pop();
|
||||
action();
|
||||
// Publish a single waiting state. All of them in one call take too long.
|
||||
if (this->pending_.empty())
|
||||
return;
|
||||
const PublishSlot slot = this->pending_.front();
|
||||
this->pending_.pop();
|
||||
this->pending_mask_.erase(slot);
|
||||
switch (slot) {
|
||||
case PUBLISH_SLOT_IAQ:
|
||||
this->publish_sensor_(this->iaq_sensor_, this->pending_values_[slot]);
|
||||
break;
|
||||
case PUBLISH_SLOT_IAQ_ACCURACY_TEXT:
|
||||
this->publish_sensor_(
|
||||
this->iaq_accuracy_text_sensor_,
|
||||
IaqAccuracyStrings::get_progmem_str(this->pending_accuracy_, IaqAccuracyStrings::LAST_INDEX));
|
||||
break;
|
||||
case PUBLISH_SLOT_IAQ_ACCURACY:
|
||||
this->publish_sensor_(this->iaq_accuracy_sensor_, this->pending_accuracy_, true);
|
||||
break;
|
||||
case PUBLISH_SLOT_SAVE_STATE:
|
||||
this->save_state_(this->pending_accuracy_);
|
||||
break;
|
||||
case PUBLISH_SLOT_CO2_EQUIVALENT:
|
||||
this->publish_sensor_(this->co2_equivalent_sensor_, this->pending_values_[slot]);
|
||||
break;
|
||||
case PUBLISH_SLOT_BREATH_VOC_EQUIVALENT:
|
||||
this->publish_sensor_(this->breath_voc_equivalent_sensor_, this->pending_values_[slot]);
|
||||
break;
|
||||
case PUBLISH_SLOT_PRESSURE:
|
||||
this->publish_sensor_(this->pressure_sensor_, this->pending_values_[slot]);
|
||||
break;
|
||||
case PUBLISH_SLOT_GAS_RESISTANCE:
|
||||
this->publish_sensor_(this->gas_resistance_sensor_, this->pending_values_[slot]);
|
||||
break;
|
||||
case PUBLISH_SLOT_TEMPERATURE:
|
||||
this->publish_sensor_(this->temperature_sensor_, this->pending_values_[slot]);
|
||||
break;
|
||||
case PUBLISH_SLOT_HUMIDITY:
|
||||
this->publish_sensor_(this->humidity_sensor_, this->pending_values_[slot]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BME680BSECComponent::queue_publish_(PublishSlot slot) {
|
||||
if (!this->pending_mask_.count(slot)) {
|
||||
this->pending_mask_.insert(slot);
|
||||
this->pending_.push(slot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,35 +427,31 @@ void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_out
|
||||
float signal = outputs[i].signal;
|
||||
switch (outputs[i].sensor_id) {
|
||||
case BSEC_OUTPUT_IAQ:
|
||||
case BSEC_OUTPUT_STATIC_IAQ: {
|
||||
uint8_t accuracy = std::min<uint8_t>(outputs[i].accuracy, IaqAccuracyStrings::LAST_INDEX);
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); });
|
||||
this->queue_push_([this, accuracy]() {
|
||||
this->publish_sensor_(this->iaq_accuracy_text_sensor_,
|
||||
IaqAccuracyStrings::get_progmem_str(accuracy, IaqAccuracyStrings::LAST_INDEX));
|
||||
});
|
||||
this->queue_push_([this, accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, accuracy, true); });
|
||||
|
||||
case BSEC_OUTPUT_STATIC_IAQ:
|
||||
this->pending_accuracy_ = std::min<uint8_t>(outputs[i].accuracy, IaqAccuracyStrings::LAST_INDEX);
|
||||
this->queue_publish_<PUBLISH_SLOT_IAQ>(signal);
|
||||
this->queue_publish_(PUBLISH_SLOT_IAQ_ACCURACY_TEXT);
|
||||
this->queue_publish_(PUBLISH_SLOT_IAQ_ACCURACY);
|
||||
// Queue up an opportunity to save state
|
||||
this->queue_push_([this, accuracy]() { this->save_state_(accuracy); });
|
||||
} break;
|
||||
this->queue_publish_(PUBLISH_SLOT_SAVE_STATE);
|
||||
break;
|
||||
case BSEC_OUTPUT_CO2_EQUIVALENT:
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->co2_equivalent_sensor_, signal); });
|
||||
this->queue_publish_<PUBLISH_SLOT_CO2_EQUIVALENT>(signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->breath_voc_equivalent_sensor_, signal); });
|
||||
this->queue_publish_<PUBLISH_SLOT_BREATH_VOC_EQUIVALENT>(signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_RAW_PRESSURE:
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->pressure_sensor_, signal / 100.0f); });
|
||||
this->queue_publish_<PUBLISH_SLOT_PRESSURE>(signal / 100.0f);
|
||||
break;
|
||||
case BSEC_OUTPUT_RAW_GAS:
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->gas_resistance_sensor_, signal); });
|
||||
this->queue_publish_<PUBLISH_SLOT_GAS_RESISTANCE>(signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->temperature_sensor_, signal); });
|
||||
this->queue_publish_<PUBLISH_SLOT_TEMPERATURE>(signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->humidity_sensor_, signal); });
|
||||
this->queue_publish_<PUBLISH_SLOT_HUMIDITY>(signal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/finite_set_mask.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <map>
|
||||
#include <queue>
|
||||
|
||||
#ifdef USE_BSEC
|
||||
#include <bsec.h>
|
||||
@@ -67,6 +68,22 @@ class BME680BSECComponent final : public Component, public i2c::I2CDevice {
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
// States waiting for loop() to publish them, one per pass
|
||||
enum PublishSlot : uint8_t {
|
||||
PUBLISH_SLOT_IAQ,
|
||||
PUBLISH_SLOT_CO2_EQUIVALENT,
|
||||
PUBLISH_SLOT_BREATH_VOC_EQUIVALENT,
|
||||
PUBLISH_SLOT_PRESSURE,
|
||||
PUBLISH_SLOT_GAS_RESISTANCE,
|
||||
PUBLISH_SLOT_TEMPERATURE,
|
||||
PUBLISH_SLOT_HUMIDITY,
|
||||
// These take pending_accuracy_ instead of a value
|
||||
PUBLISH_SLOT_IAQ_ACCURACY_TEXT,
|
||||
PUBLISH_SLOT_IAQ_ACCURACY,
|
||||
PUBLISH_SLOT_SAVE_STATE,
|
||||
PUBLISH_SLOT_COUNT,
|
||||
};
|
||||
|
||||
void set_config_();
|
||||
float calc_sensor_sample_rate_(SampleRate sample_rate);
|
||||
void update_subscription_();
|
||||
@@ -91,7 +108,12 @@ class BME680BSECComponent final : public Component, public i2c::I2CDevice {
|
||||
void save_state_(
|
||||
uint8_t accuracy); // Save the bsec_state_data_ member (volatile memory) to the ESP preferences (storage)
|
||||
|
||||
void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); }
|
||||
void queue_publish_(PublishSlot slot);
|
||||
template<PublishSlot S> void queue_publish_(float value) {
|
||||
static_assert(S < PUBLISH_SLOT_IAQ_ACCURACY_TEXT, "slot has no value");
|
||||
this->pending_values_[S] = value;
|
||||
this->queue_publish_(S);
|
||||
}
|
||||
|
||||
static uint8_t work_buffer_[BSEC_MAX_WORKBUFFER_SIZE];
|
||||
struct bme680_dev bme680_;
|
||||
@@ -102,7 +124,11 @@ class BME680BSECComponent final : public Component, public i2c::I2CDevice {
|
||||
uint32_t millis_overflow_counter_{0};
|
||||
int64_t next_call_ns_{0};
|
||||
|
||||
std::queue<std::function<void()>> queue_;
|
||||
// Arrival order; a slot already waiting keeps its place and takes the newer value
|
||||
StaticRingBuffer<PublishSlot, PUBLISH_SLOT_COUNT> pending_;
|
||||
FiniteSetMask<PublishSlot, DefaultBitPolicy<PublishSlot, PUBLISH_SLOT_COUNT>> pending_mask_;
|
||||
float pending_values_[PUBLISH_SLOT_IAQ_ACCURACY_TEXT];
|
||||
uint8_t pending_accuracy_{0};
|
||||
|
||||
bool bsec_state_data_valid_;
|
||||
uint8_t bsec_state_data_[BSEC_MAX_STATE_BLOB_SIZE]; // This is the current snapshot of the BSEC library state
|
||||
|
||||
@@ -130,14 +130,62 @@ void BME68xBSEC2Component::check_status_() {
|
||||
}
|
||||
|
||||
void BME68xBSEC2Component::loop() {
|
||||
// Process a single action from the queue. These are primarily sensor state publishes
|
||||
// that in totality take too long to send in a single call.
|
||||
if (this->queue_.size()) {
|
||||
auto action = std::move(this->queue_.front());
|
||||
this->queue_.pop();
|
||||
action();
|
||||
} else {
|
||||
// Publish a single waiting state. All of them in one call take too long.
|
||||
if (this->pending_.empty()) {
|
||||
this->disable_loop();
|
||||
return;
|
||||
}
|
||||
const PendingPublish pending = this->pending_.front();
|
||||
this->pending_.pop();
|
||||
const float value = pending.value;
|
||||
switch (pending.slot) {
|
||||
#ifdef USE_SENSOR
|
||||
case PUBLISH_SLOT_IAQ:
|
||||
this->publish_sensor_(this->iaq_sensor_, value);
|
||||
break;
|
||||
case PUBLISH_SLOT_IAQ_STATIC:
|
||||
this->publish_sensor_(this->iaq_static_sensor_, value);
|
||||
break;
|
||||
case PUBLISH_SLOT_CO2_EQUIVALENT:
|
||||
this->publish_sensor_(this->co2_equivalent_sensor_, value);
|
||||
break;
|
||||
case PUBLISH_SLOT_BREATH_VOC_EQUIVALENT:
|
||||
this->publish_sensor_(this->breath_voc_equivalent_sensor_, value);
|
||||
break;
|
||||
case PUBLISH_SLOT_PRESSURE:
|
||||
this->publish_sensor_(this->pressure_sensor_, value);
|
||||
break;
|
||||
case PUBLISH_SLOT_GAS_RESISTANCE:
|
||||
this->publish_sensor_(this->gas_resistance_sensor_, value);
|
||||
break;
|
||||
case PUBLISH_SLOT_TEMPERATURE:
|
||||
this->publish_sensor_(this->temperature_sensor_, value);
|
||||
break;
|
||||
case PUBLISH_SLOT_HUMIDITY:
|
||||
this->publish_sensor_(this->humidity_sensor_, value);
|
||||
break;
|
||||
case PUBLISH_SLOT_IAQ_ACCURACY:
|
||||
this->publish_sensor_(this->iaq_accuracy_sensor_, value, true);
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
case PUBLISH_SLOT_IAQ_ACCURACY_TEXT:
|
||||
this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[static_cast<uint8_t>(value)]);
|
||||
break;
|
||||
#endif
|
||||
case PUBLISH_SLOT_SAVE_STATE:
|
||||
this->save_state_(static_cast<uint8_t>(value));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BME68xBSEC2Component::queue_publish_(PublishSlot slot, float value) {
|
||||
// Only full when the loop stalled for seconds; drop the oldest so the newest reading still goes out
|
||||
if (!this->pending_.push({value, slot})) {
|
||||
this->pending_.pop();
|
||||
this->pending_.push({value, slot});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,44 +462,44 @@ void BME68xBSEC2Component::publish_(const bsec_output_t *outputs, uint8_t num_ou
|
||||
max_accuracy = std::max(outputs[i].accuracy, max_accuracy);
|
||||
update_accuracy = true;
|
||||
#ifdef USE_SENSOR
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); });
|
||||
this->queue_publish_(PUBLISH_SLOT_IAQ, signal);
|
||||
#endif
|
||||
break;
|
||||
case BSEC_OUTPUT_STATIC_IAQ:
|
||||
max_accuracy = std::max(outputs[i].accuracy, max_accuracy);
|
||||
update_accuracy = true;
|
||||
#ifdef USE_SENSOR
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_static_sensor_, signal); });
|
||||
this->queue_publish_(PUBLISH_SLOT_IAQ_STATIC, signal);
|
||||
#endif
|
||||
break;
|
||||
case BSEC_OUTPUT_CO2_EQUIVALENT:
|
||||
#ifdef USE_SENSOR
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->co2_equivalent_sensor_, signal); });
|
||||
this->queue_publish_(PUBLISH_SLOT_CO2_EQUIVALENT, signal);
|
||||
#endif
|
||||
break;
|
||||
case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
|
||||
#ifdef USE_SENSOR
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->breath_voc_equivalent_sensor_, signal); });
|
||||
this->queue_publish_(PUBLISH_SLOT_BREATH_VOC_EQUIVALENT, signal);
|
||||
#endif
|
||||
break;
|
||||
case BSEC_OUTPUT_RAW_PRESSURE:
|
||||
#ifdef USE_SENSOR
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->pressure_sensor_, signal / 100.0f); });
|
||||
this->queue_publish_(PUBLISH_SLOT_PRESSURE, signal / 100.0f);
|
||||
#endif
|
||||
break;
|
||||
case BSEC_OUTPUT_RAW_GAS:
|
||||
#ifdef USE_SENSOR
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->gas_resistance_sensor_, signal); });
|
||||
this->queue_publish_(PUBLISH_SLOT_GAS_RESISTANCE, signal);
|
||||
#endif
|
||||
break;
|
||||
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
|
||||
#ifdef USE_SENSOR
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->temperature_sensor_, signal); });
|
||||
this->queue_publish_(PUBLISH_SLOT_TEMPERATURE, signal);
|
||||
#endif
|
||||
break;
|
||||
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
|
||||
#ifdef USE_SENSOR
|
||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->humidity_sensor_, signal); });
|
||||
this->queue_publish_(PUBLISH_SLOT_HUMIDITY, signal);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -459,16 +507,13 @@ void BME68xBSEC2Component::publish_(const bsec_output_t *outputs, uint8_t num_ou
|
||||
if (update_accuracy) {
|
||||
max_accuracy = std::min<uint8_t>(max_accuracy, std::size(IAQ_ACCURACY_STATES) - 1);
|
||||
#ifdef USE_SENSOR
|
||||
this->queue_push_(
|
||||
[this, max_accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, max_accuracy, true); });
|
||||
this->queue_publish_(PUBLISH_SLOT_IAQ_ACCURACY, max_accuracy);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
this->queue_push_([this, max_accuracy]() {
|
||||
this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[max_accuracy]);
|
||||
});
|
||||
this->queue_publish_(PUBLISH_SLOT_IAQ_ACCURACY_TEXT, max_accuracy);
|
||||
#endif
|
||||
// Queue up an opportunity to save state
|
||||
this->queue_push_([this, max_accuracy]() { this->save_state_(max_accuracy); });
|
||||
this->queue_publish_(PUBLISH_SLOT_SAVE_STATE, max_accuracy);
|
||||
}
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
#ifdef USE_BSEC2
|
||||
@@ -15,7 +16,6 @@
|
||||
#endif
|
||||
|
||||
#include <cinttypes>
|
||||
#include <queue>
|
||||
|
||||
#include <bsec2.h>
|
||||
|
||||
@@ -83,6 +83,28 @@ class BME68xBSEC2Component : public Component {
|
||||
virtual uint32_t get_hash() = 0;
|
||||
|
||||
protected:
|
||||
// States waiting for loop() to publish them, one per pass
|
||||
enum PublishSlot : uint8_t {
|
||||
PUBLISH_SLOT_IAQ,
|
||||
PUBLISH_SLOT_IAQ_STATIC,
|
||||
PUBLISH_SLOT_CO2_EQUIVALENT,
|
||||
PUBLISH_SLOT_BREATH_VOC_EQUIVALENT,
|
||||
PUBLISH_SLOT_PRESSURE,
|
||||
PUBLISH_SLOT_GAS_RESISTANCE,
|
||||
PUBLISH_SLOT_TEMPERATURE,
|
||||
PUBLISH_SLOT_HUMIDITY,
|
||||
PUBLISH_SLOT_IAQ_ACCURACY,
|
||||
PUBLISH_SLOT_IAQ_ACCURACY_TEXT,
|
||||
PUBLISH_SLOT_SAVE_STATE,
|
||||
PUBLISH_SLOT_COUNT,
|
||||
};
|
||||
struct __attribute__((packed)) PendingPublish {
|
||||
float value; // the accuracy for the accuracy and save slots
|
||||
PublishSlot slot;
|
||||
};
|
||||
// A read returns up to 3 fields in parallel mode, each published in full
|
||||
static constexpr uint8_t PUBLISH_QUEUE_SIZE = 3 * PUBLISH_SLOT_COUNT;
|
||||
|
||||
void set_config_(const uint8_t *config, u_int32_t len);
|
||||
float calc_sensor_sample_rate_(SampleRate sample_rate);
|
||||
void update_subscription_();
|
||||
@@ -103,14 +125,14 @@ class BME68xBSEC2Component : public Component {
|
||||
void load_state_();
|
||||
void save_state_(uint8_t accuracy);
|
||||
|
||||
void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); }
|
||||
void queue_publish_(PublishSlot slot, float value);
|
||||
|
||||
struct bme68x_dev bme68x_;
|
||||
bsec_bme_settings_t bsec_settings_;
|
||||
bsec_version_t version_;
|
||||
struct bme68x_heatr_conf bme68x_heatr_conf_;
|
||||
|
||||
std::queue<std::function<void()>> queue_;
|
||||
StaticRingBuffer<PendingPublish, PUBLISH_QUEUE_SIZE> pending_;
|
||||
ESPPreferenceObject bsec_state_;
|
||||
|
||||
uint8_t const *bsec2_configuration_{nullptr};
|
||||
|
||||
@@ -82,59 +82,44 @@ void BMI270Component::setup() {
|
||||
return;
|
||||
|
||||
// 2. Soft-reset via CMD register (0x7E = 0xB6)
|
||||
if (!this->write_byte(BMI270_REG_CMD, BMI270_CMD_SOFT_RESET)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_byte(BMI270_REG_CMD, BMI270_CMD_SOFT_RESET)))
|
||||
return;
|
||||
}
|
||||
delay(20);
|
||||
|
||||
// 4. Upload the configuration blob
|
||||
if (!load_config_file_()) {
|
||||
this->mark_failed(LOG_STR("Config file upload failed"));
|
||||
if (!this->setup_check(load_config_file_(), LOG_STR("Config file upload failed")))
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Config blob uploaded ✓");
|
||||
|
||||
// 5. Configure accelerometer
|
||||
// ACC_CONF: ODR | BWP(0x2 = normal avg4) | perf_mode(1)
|
||||
uint8_t acc_conf =
|
||||
(uint8_t) (accel_odr_) | (BMI270_CONF_BWP_NORMAL << BMI270_CONF_BWP_SHIFT) | BMI270_ACC_CONF_PERF_MODE;
|
||||
if (!this->write_byte(BMI270_REG_ACC_CONF, acc_conf)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_byte(BMI270_REG_ACC_CONF, acc_conf)))
|
||||
return;
|
||||
}
|
||||
if (!this->write_byte(BMI270_REG_ACC_RANGE, (uint8_t) accel_range_)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_byte(BMI270_REG_ACC_RANGE, (uint8_t) accel_range_)))
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Configure gyroscope
|
||||
// GYR_CONF: ODR | BWP(0x2 = normal) | noise_perf(1) | filter_perf(1)
|
||||
uint8_t gyr_conf = (uint8_t) (gyro_odr_) | (BMI270_CONF_BWP_NORMAL << BMI270_CONF_BWP_SHIFT) |
|
||||
BMI270_GYR_CONF_NOISE_PERF | BMI270_GYR_CONF_FILTER_PERF;
|
||||
if (!this->write_byte(BMI270_REG_GYR_CONF, gyr_conf)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_byte(BMI270_REG_GYR_CONF, gyr_conf)))
|
||||
return;
|
||||
}
|
||||
if (!this->write_byte(BMI270_REG_GYR_RANGE, (uint8_t) gyro_range_)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_byte(BMI270_REG_GYR_RANGE, (uint8_t) gyro_range_)))
|
||||
return;
|
||||
}
|
||||
|
||||
// 7. Enable accelerometer, gyroscope, and temperature sensor
|
||||
// PWR_CTRL bits: temp_en[3] | gyr_en[2] | acc_en[1]
|
||||
if (!this->write_byte(BMI270_REG_PWR_CTRL, BMI270_PWR_CTRL_IMU)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->write_byte(BMI270_REG_PWR_CTRL, BMI270_PWR_CTRL_IMU)))
|
||||
return;
|
||||
}
|
||||
delay(5);
|
||||
|
||||
// 8. Re-enable advanced power save (optional; keeps current low between reads)
|
||||
// Disabled here for simplicity – leave in performance mode
|
||||
if (!this->write_byte(BMI270_REG_PWR_CONF, BMI270_PWR_CONF_FIFO_SELF_WAKEUP)) { // bit1 = fifo_self_wakeup
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
// bit1 = fifo_self_wakeup
|
||||
if (!this->setup_check(this->write_byte(BMI270_REG_PWR_CONF, BMI270_PWR_CONF_FIFO_SELF_WAKEUP)))
|
||||
return;
|
||||
}
|
||||
|
||||
// 9. Optionally bring up whatever is wired to the aux interface.
|
||||
// This is entirely additive: it is off by default, and failure only disables
|
||||
|
||||
@@ -24,10 +24,8 @@ void BMP085Component::update() {
|
||||
}
|
||||
void BMP085Component::setup() {
|
||||
uint8_t data[22];
|
||||
if (!this->read_bytes(BMP085_REGISTER_AC1_H, data, 22)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->read_bytes(BMP085_REGISTER_AC1_H, data, 22)))
|
||||
return;
|
||||
}
|
||||
|
||||
// Load calibration
|
||||
this->calibration_.ac1 = encode_uint16(data[0], data[1]);
|
||||
|
||||
@@ -40,33 +40,24 @@ void BMP280Component::setup() {
|
||||
|
||||
// Read the chip id twice, to work around a bug where the first read is 0.
|
||||
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
|
||||
if (!this->bmp_read_byte(0xD0, &chip_id)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->bmp_read_byte(0xD0, &chip_id)))
|
||||
return;
|
||||
}
|
||||
if (!this->bmp_read_byte(0xD0, &chip_id)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(this->bmp_read_byte(0xD0, &chip_id)))
|
||||
return;
|
||||
}
|
||||
if (chip_id != 0x58) {
|
||||
this->mark_failed(LOG_STR("Wrong chip ID or no response"));
|
||||
if (!this->setup_check(chip_id == 0x58, LOG_STR("Wrong chip ID or no response")))
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a soft reset.
|
||||
if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
||||
this->mark_failed(LOG_STR("Reset failed"));
|
||||
if (!this->setup_check(this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET), LOG_STR("Reset failed")))
|
||||
return;
|
||||
}
|
||||
// Wait until the NVM data has finished loading.
|
||||
uint8_t status;
|
||||
uint8_t retry = 5;
|
||||
do {
|
||||
delay(2);
|
||||
if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
|
||||
this->mark_failed(LOG_STR("Error reading status register"));
|
||||
if (!this->setup_check(this->bmp_read_byte(BMP280_REGISTER_STATUS, &status),
|
||||
LOG_STR("Error reading status register")))
|
||||
return;
|
||||
}
|
||||
} while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
|
||||
if (status & BMP280_STATUS_IM_UPDATE) {
|
||||
this->mark_failed(LOG_STR("Timeout loading NVM"));
|
||||
@@ -89,17 +80,13 @@ void BMP280Component::setup() {
|
||||
this->calibration_.p9 = this->read_s16_le_(0x9E);
|
||||
|
||||
uint8_t config_register = 0;
|
||||
if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed(LOG_STR("Read config"));
|
||||
if (!this->setup_check(this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register), LOG_STR("Read config")))
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b11111100;
|
||||
config_register |= 0b000 << 5; // 0.5 ms standby time
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed(LOG_STR("Write config"));
|
||||
if (!this->setup_check(this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register), LOG_STR("Write config")))
|
||||
return;
|
||||
}
|
||||
}
|
||||
void BMP280Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BMP280:");
|
||||
@@ -148,16 +135,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 +158,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;
|
||||
|
||||
@@ -45,15 +45,11 @@ static const LogString *iir_filter_to_str(IIRFilter filter) {
|
||||
|
||||
void BMP3XXComponent::setup() {
|
||||
// Call the Device base class "initialise" function
|
||||
if (!reset()) {
|
||||
this->mark_failed(LOG_STR("Failed to reset"));
|
||||
if (!this->setup_check(this->reset(), LOG_STR("Failed to reset")))
|
||||
return;
|
||||
}
|
||||
|
||||
if (!read_byte(BMP388_CHIP_ID, &this->chip_id_.reg)) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(read_byte(BMP388_CHIP_ID, &this->chip_id_.reg)))
|
||||
return;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "Chip %s Id 0x%X", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg);
|
||||
|
||||
if (chip_id_.reg != BMP388_ID && chip_id_.reg != BMP390_ID) {
|
||||
@@ -63,10 +59,10 @@ void BMP3XXComponent::setup() {
|
||||
// set sensor in sleep mode
|
||||
stop_conversion();
|
||||
// Read the calibration parameters into the params structure
|
||||
if (!read_bytes(BMP388_TRIM_PARAMS, (uint8_t *) &compensation_params_, sizeof(compensation_params_))) {
|
||||
this->mark_failed(LOG_STR("Can't read calibration data"));
|
||||
if (!this->setup_check(
|
||||
read_bytes(BMP388_TRIM_PARAMS, (uint8_t *) &compensation_params_, sizeof(compensation_params_)),
|
||||
LOG_STR("Can't read calibration data")))
|
||||
return;
|
||||
}
|
||||
compensation_float_params_.param_T1 =
|
||||
(float) compensation_params_.param_T1 / powf(2.0f, -8.0f); // Calculate the floating point trim parameters
|
||||
compensation_float_params_.param_T2 = (float) compensation_params_.param_T2 / powf(2.0f, 30.0f);
|
||||
@@ -84,10 +80,8 @@ void BMP3XXComponent::setup() {
|
||||
compensation_float_params_.param_P11 = (float) compensation_params_.param_P11 / powf(2.0f, 65.0f);
|
||||
|
||||
// Initialise the BMP388 IIR filter register
|
||||
if (!set_iir_filter(this->iir_filter_)) {
|
||||
this->mark_failed(LOG_STR("Failed to set IIR filter"));
|
||||
if (!this->setup_check(this->set_iir_filter(this->iir_filter_), LOG_STR("Failed to set IIR filter")))
|
||||
return;
|
||||
}
|
||||
|
||||
// Set power control registers
|
||||
pwr_ctrl_.bit.press_en = 1;
|
||||
@@ -103,10 +97,8 @@ void BMP3XXComponent::setup() {
|
||||
this->temperature_oversampling_ = OVERSAMPLING_NONE;
|
||||
}
|
||||
// Initialise the BMP388 oversampling register
|
||||
if (!set_oversampling_register(this->pressure_oversampling_, this->temperature_oversampling_)) {
|
||||
this->mark_failed(LOG_STR("Failed to set oversampling register"));
|
||||
return;
|
||||
}
|
||||
this->setup_check(this->set_oversampling_register(this->pressure_oversampling_, this->temperature_oversampling_),
|
||||
LOG_STR("Failed to set oversampling register"));
|
||||
}
|
||||
|
||||
void BMP3XXComponent::dump_config() {
|
||||
|
||||
@@ -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"
|
||||
@@ -66,19 +65,14 @@ BQ25186Component = bq25186_ns.class_(
|
||||
BQ25186_CHILD_SCHEMA = cv.Schema(
|
||||
{cv.GenerateID(CONF_BQ25186_ID): cv.use_id(BQ25186Component)}
|
||||
)
|
||||
_request_binary_sensor_slot = cg.slot_counter("BQ25186_BINARY_SENSOR_COUNT")
|
||||
_request_text_sensor_slot = cg.slot_counter("BQ25186_TEXT_SENSOR_COUNT")
|
||||
|
||||
|
||||
def register_binary_sensor(hub: cg.MockObj, var: cg.MockObj) -> cg.Expression:
|
||||
_request_binary_sensor_slot(str(hub))
|
||||
return hub.register_binary_sensor(var)
|
||||
|
||||
|
||||
def register_text_sensor(hub: cg.MockObj, var: cg.MockObj) -> cg.Expression:
|
||||
_request_text_sensor_slot(str(hub))
|
||||
return hub.register_text_sensor(var)
|
||||
|
||||
BQ25186StatusRegisters = bq25186_ns.class_("BQ25186StatusRegisters")
|
||||
STATUS_PUBLISHER = cg.HubFunction(
|
||||
"status",
|
||||
cg.void,
|
||||
[(BQ25186StatusRegisters.const_ref(), "status")],
|
||||
define="USE_BQ25186_STATUS_PUBLISHER",
|
||||
)
|
||||
STATUS = STATUS_PUBLISHER.param("status")
|
||||
|
||||
REG_VBAT_CTRL = 0x03
|
||||
REG_ICHG_CTRL = 0x04
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from functools import partial
|
||||
|
||||
from esphome.automation import parent_ref
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
@@ -9,16 +8,13 @@ from esphome.const import (
|
||||
DEVICE_CLASS_PLUG,
|
||||
DEVICE_CLASS_RUNNING,
|
||||
)
|
||||
from esphome.cpp_generator import HexIntLiteral
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import BQ25186_CHILD_SCHEMA, CONF_BQ25186_ID, bq25186_ns, register_binary_sensor
|
||||
from . import BQ25186_CHILD_SCHEMA, CONF_BQ25186_ID, STATUS, STATUS_PUBLISHER
|
||||
|
||||
DEPENDENCIES = ["bq25186"]
|
||||
|
||||
BQ25186BinarySensor = bq25186_ns.class_(
|
||||
"BQ25186BinarySensor", binary_sensor.BinarySensor
|
||||
)
|
||||
|
||||
REG_STAT0 = 0
|
||||
REG_STAT1 = 1
|
||||
REG_FLAG0 = 2
|
||||
@@ -51,11 +47,19 @@ _FIELDS = (
|
||||
)
|
||||
|
||||
|
||||
def _state_expression(reg: int, shift: int, mask: int, match: int) -> cg.MockObj:
|
||||
"""Whether the field (status[reg] >> shift) & mask reads as on, as a bit of match."""
|
||||
value = (STATUS[reg] >> shift) & HexIntLiteral(mask)
|
||||
if mask == 0x01 and match == _BIT_SET:
|
||||
return value
|
||||
return (cg.MockObj(f"{match:#06b}") >> value) & 1
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
BQ25186_CHILD_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(key): binary_sensor.binary_sensor_schema(
|
||||
BQ25186BinarySensor, device_class=device_class
|
||||
device_class=device_class
|
||||
)
|
||||
for key, *_, device_class in _FIELDS
|
||||
}
|
||||
@@ -68,6 +72,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_BQ25186_ID])
|
||||
sensors = binary_sensor.sub_binary_sensors(config)
|
||||
for key, reg, shift, mask, match, _ in _FIELDS:
|
||||
await sensors(
|
||||
key, partial(register_binary_sensor, hub), reg, shift, mask, match
|
||||
)
|
||||
if (var := await sensors(key, None)) is not None:
|
||||
state = _state_expression(reg, shift, mask, match)
|
||||
STATUS_PUBLISHER.add(hub, parent_ref(var).publish_state(state))
|
||||
@@ -1,10 +1,4 @@
|
||||
#include "bq25186.h"
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "bq25186_binary_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "bq25186_text_sensor.h"
|
||||
#endif
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -144,13 +138,9 @@ void BQ25186Component::update() {
|
||||
#endif
|
||||
if (!pending)
|
||||
this->status_clear_warning();
|
||||
#ifdef BQ25186_BINARY_SENSOR_COUNT
|
||||
for (auto *sensor : this->binary_sensors_)
|
||||
sensor->on_status(status);
|
||||
#endif
|
||||
#ifdef BQ25186_TEXT_SENSOR_COUNT
|
||||
for (auto *sensor : this->text_sensors_)
|
||||
sensor->on_status(status);
|
||||
#ifdef USE_BQ25186_STATUS_PUBLISHER
|
||||
if (this->status_publisher_ != nullptr)
|
||||
this->status_publisher_(status);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
#ifdef USE_BQ25186_PG_GPO
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
|
||||
@@ -38,8 +42,21 @@ static constexpr uint8_t BQ25186_SYS_REG_PG_GPO_LEVEL = 0x10;
|
||||
using BQ25186ConfigRegisters = std::array<uint8_t, BQ25186_CONFIG_REGISTER_COUNT>;
|
||||
using BQ25186StatusRegisters = std::array<uint8_t, BQ25186_STATUS_REGISTER_COUNT>;
|
||||
|
||||
class BQ25186BinarySensor;
|
||||
class BQ25186TextSensor;
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
// STAT0[6:5] charge status, then STAT1[4:3] TS status
|
||||
PROGMEM_STRING_TABLE(BQ25186StatusStrings, "Not Charging", "Constant Current", "Constant Voltage",
|
||||
"Charge Done/Disabled", "Normal", "Hot/Cold (Charging Suspended)", "Cool (Current Reduced)",
|
||||
"Warm (Voltage Reduced)");
|
||||
|
||||
/// Publish the two-bit field in the low bits of `value` as string `first + field`, only when it differs from `last`
|
||||
inline void publish_status_text(text_sensor::TextSensor *sensor, uint8_t &last, uint8_t value, uint8_t first) {
|
||||
value &= 0x03;
|
||||
if (value == last)
|
||||
return;
|
||||
last = value;
|
||||
sensor->publish_state(BQ25186StatusStrings::get_progmem_str(first + value, BQ25186StatusStrings::LAST_INDEX));
|
||||
}
|
||||
#endif
|
||||
|
||||
class BQ25186Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
@@ -52,11 +69,8 @@ class BQ25186Component : public PollingComponent, public i2c::I2CDevice {
|
||||
this->configure_on_boot_ = true;
|
||||
}
|
||||
bool apply_config(const BQ25186ConfigRegisters ®s);
|
||||
#ifdef BQ25186_BINARY_SENSOR_COUNT
|
||||
void register_binary_sensor(BQ25186BinarySensor *sensor) { this->binary_sensors_.push_back(sensor); }
|
||||
#endif
|
||||
#ifdef BQ25186_TEXT_SENSOR_COUNT
|
||||
void register_text_sensor(BQ25186TextSensor *sensor) { this->text_sensors_.push_back(sensor); }
|
||||
#ifdef USE_BQ25186_STATUS_PUBLISHER
|
||||
HUB_PUBLISHER(status, const BQ25186StatusRegisters &)
|
||||
#endif
|
||||
#ifdef USE_BQ25186_PG_GPO
|
||||
/// The /PG/GPO pin is put in general purpose output mode when a switch drives it
|
||||
@@ -78,12 +92,6 @@ class BQ25186Component : public PollingComponent, public i2c::I2CDevice {
|
||||
void check_pg_gpo_(const BQ25186StatusRegisters &status, bool &pending);
|
||||
#endif
|
||||
|
||||
#ifdef BQ25186_BINARY_SENSOR_COUNT
|
||||
StaticVector<BQ25186BinarySensor *, BQ25186_BINARY_SENSOR_COUNT> binary_sensors_;
|
||||
#endif
|
||||
#ifdef BQ25186_TEXT_SENSOR_COUNT
|
||||
StaticVector<BQ25186TextSensor *, BQ25186_TEXT_SENSOR_COUNT> text_sensors_;
|
||||
#endif
|
||||
BQ25186ConfigRegisters regs_{}; // the configuration last applied, written again after a chip reset
|
||||
bool configure_on_boot_{false};
|
||||
bool configured_{false}; // regs_ went to the chip at least once
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
|
||||
#include "bq25186.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome::bq25186 {
|
||||
|
||||
/// One field of a status register; `match` has a bit set for each field value that reads as on
|
||||
class BQ25186BinarySensor final : public binary_sensor::BinarySensor {
|
||||
public:
|
||||
BQ25186BinarySensor(uint8_t reg, uint8_t shift, uint8_t mask, uint8_t match)
|
||||
: reg_(reg), shift_(shift), mask_(mask), match_(match) {}
|
||||
|
||||
void on_status(const BQ25186StatusRegisters &status) {
|
||||
const uint8_t value = (status[this->reg_] >> this->shift_) & this->mask_;
|
||||
this->publish_state((this->match_ >> value) & 1);
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint8_t reg_;
|
||||
const uint8_t shift_;
|
||||
const uint8_t mask_;
|
||||
const uint8_t match_;
|
||||
};
|
||||
|
||||
} // namespace esphome::bq25186
|
||||
|
||||
#endif // USE_BINARY_SENSOR
|
||||
@@ -1,22 +0,0 @@
|
||||
#include "bq25186_text_sensor.h"
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::bq25186 {
|
||||
|
||||
// STAT0[6:5] charge status, then STAT1[4:3] TS status
|
||||
PROGMEM_STRING_TABLE(StatusStrings, "Not Charging", "Constant Current", "Constant Voltage", "Charge Done/Disabled",
|
||||
"Normal", "Hot/Cold (Charging Suspended)", "Cool (Current Reduced)", "Warm (Voltage Reduced)");
|
||||
|
||||
void BQ25186TextSensor::on_status(const BQ25186StatusRegisters &status) {
|
||||
const uint8_t value = (status[this->reg_] >> this->shift_) & 0x03;
|
||||
if (value == this->last_)
|
||||
return;
|
||||
this->last_ = value;
|
||||
this->publish_state(StatusStrings::get_progmem_str(this->first_ + value, StatusStrings::LAST_INDEX));
|
||||
}
|
||||
|
||||
} // namespace esphome::bq25186
|
||||
|
||||
#endif // USE_TEXT_SENSOR
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
|
||||
#include "bq25186.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome::bq25186 {
|
||||
|
||||
/// A two-bit status field; `first` is the string table index of the field's value 0
|
||||
class BQ25186TextSensor final : public text_sensor::TextSensor {
|
||||
public:
|
||||
BQ25186TextSensor(uint8_t reg, uint8_t shift, uint8_t first) : reg_(reg), shift_(shift), first_(first) {}
|
||||
void on_status(const BQ25186StatusRegisters &status);
|
||||
|
||||
protected:
|
||||
const uint8_t reg_;
|
||||
const uint8_t shift_;
|
||||
const uint8_t first_;
|
||||
uint8_t last_{0xFF}; // the field value last published
|
||||
};
|
||||
|
||||
} // namespace esphome::bq25186
|
||||
|
||||
#endif // USE_TEXT_SENSOR
|
||||
@@ -1,16 +1,24 @@
|
||||
from functools import partial
|
||||
|
||||
from esphome.automation import parent_ref
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.cpp_generator import (
|
||||
AssignmentExpression,
|
||||
HexIntLiteral,
|
||||
VariableDeclarationExpression,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import BQ25186_CHILD_SCHEMA, CONF_BQ25186_ID, bq25186_ns, register_text_sensor
|
||||
from . import (
|
||||
BQ25186_CHILD_SCHEMA,
|
||||
CONF_BQ25186_ID,
|
||||
STATUS,
|
||||
STATUS_PUBLISHER,
|
||||
bq25186_ns,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["bq25186"]
|
||||
|
||||
BQ25186TextSensor = bq25186_ns.class_("BQ25186TextSensor", text_sensor.TextSensor)
|
||||
|
||||
REG_STAT0 = 0
|
||||
REG_STAT1 = 1
|
||||
|
||||
@@ -20,12 +28,28 @@ _FIELDS = (
|
||||
("ts_status", REG_STAT1, 3, 4),
|
||||
)
|
||||
|
||||
|
||||
def _add_text_status(
|
||||
hub: cg.MockObj, var: cg.MockObj, reg: int, shift: int, first: int
|
||||
) -> None:
|
||||
"""Publish status string first + the two-bit field at status[reg] >> shift, on change."""
|
||||
last = cg.MockObj(f"esphome__{var}__last")
|
||||
# 0xFF so the first poll publishes
|
||||
declaration = VariableDeclarationExpression(cg.uint8, "", last, static=True)
|
||||
STATUS_PUBLISHER.add(
|
||||
hub, AssignmentExpression(None, None, declaration, HexIntLiteral(0xFF))
|
||||
)
|
||||
STATUS_PUBLISHER.add(
|
||||
hub,
|
||||
bq25186_ns.publish_status_text(
|
||||
parent_ref(var), last, STATUS[reg] >> shift, first
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
BQ25186_CHILD_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(key): text_sensor.text_sensor_schema(BQ25186TextSensor)
|
||||
for key, *_ in _FIELDS
|
||||
}
|
||||
{cv.Optional(key): text_sensor.text_sensor_schema() for key, *_ in _FIELDS}
|
||||
),
|
||||
cv.has_at_least_one_key(*(key for key, *_ in _FIELDS)),
|
||||
)
|
||||
@@ -35,4 +59,5 @@ async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_BQ25186_ID])
|
||||
sensors = text_sensor.sub_text_sensors(config)
|
||||
for key, reg, shift, first in _FIELDS:
|
||||
await sensors(key, partial(register_text_sensor, hub), reg, shift, first)
|
||||
if (var := await sensors(key, None)) is not None:
|
||||
_add_text_status(hub, var, reg, shift, first)
|
||||
@@ -27,16 +27,14 @@ static constexpr uint16_t TIME_TO_EMPTY_NOT_APPLICABLE = 0xFFFF;
|
||||
|
||||
void BQ27220Component::setup() {
|
||||
const uint8_t device_number_cmd[2] = {0x01, 0x00}; // DEVICE_NUMBER subcommand, little-endian
|
||||
if (this->write_register(REG_CONTROL, device_number_cmd, 2) != i2c::ERROR_OK) {
|
||||
this->mark_failed(LOG_STR("Failed to communicate with BQ27220"));
|
||||
if (!this->setup_check(this->write_register(REG_CONTROL, device_number_cmd, 2) == i2c::ERROR_OK,
|
||||
LOG_STR("Failed to communicate with BQ27220")))
|
||||
return;
|
||||
}
|
||||
delay(2); // SLUUBD4A §5.3: the subcommand result needs a moment before it can be read
|
||||
uint16_t device_number = 0;
|
||||
if (!this->read_byte_16_le(REG_MAC_DATA, &device_number)) {
|
||||
this->mark_failed(LOG_STR("Failed to read device number from BQ27220"));
|
||||
if (!this->setup_check(this->read_byte_16_le(REG_MAC_DATA, &device_number),
|
||||
LOG_STR("Failed to read device number from BQ27220")))
|
||||
return;
|
||||
}
|
||||
if (device_number != DEVICE_NUMBER) {
|
||||
ESP_LOGE(TAG, "Unexpected device number 0x%04X (expected 0x%04X)", device_number, DEVICE_NUMBER);
|
||||
this->mark_failed(LOG_STR("Unexpected device number"));
|
||||
|
||||
@@ -10,6 +10,8 @@ CODEOWNERS = ["@nagyrobi"]
|
||||
AUTO_LOAD = ["ble_device_base"]
|
||||
DOMAIN = "bthome_mithermometer"
|
||||
|
||||
CONF_REPLAY_PROTECTION = "replay_protection"
|
||||
|
||||
|
||||
bthome_mithermometer_ns = cg.esphome_ns.namespace("bthome_mithermometer")
|
||||
BTHomeMiThermometer = bthome_mithermometer_ns.class_(
|
||||
@@ -29,14 +31,25 @@ def bthome_mithermometer_base_schema(
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(BTHomeMiThermometer),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_BINDKEY): cv.bind_key,
|
||||
cv.Optional(CONF_REPLAY_PROTECTION, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(extra_schema)
|
||||
.extend(ble_device_base.BLE_DEVICE_SCHEMA),
|
||||
_validate_replay_protection,
|
||||
)
|
||||
|
||||
|
||||
def _validate_replay_protection(config: ConfigType) -> ConfigType:
|
||||
if config[CONF_REPLAY_PROTECTION] and CONF_BINDKEY not in config:
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_REPLAY_PROTECTION}' requires '{CONF_BINDKEY}'",
|
||||
path=[CONF_REPLAY_PROTECTION],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
async def setup_bthome_mithermometer(var: MockObj, config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await ble_device_base.register_ble_device(var, config)
|
||||
@@ -47,3 +60,6 @@ async def setup_bthome_mithermometer(var: MockObj, config: ConfigType) -> None:
|
||||
for index in range(0, len(bindkey), 2)
|
||||
]
|
||||
cg.add(var.set_bindkey(cg.ArrayInitializer(*bindkey_bytes)))
|
||||
if config[CONF_REPLAY_PROTECTION]:
|
||||
cg.add_define("USE_BTHOME_MITHERMOMETER_REPLAY_PROTECTION")
|
||||
cg.add(var.set_replay_protection(True))
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "bthome_ble.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
|
||||
@@ -31,6 +33,9 @@ static constexpr size_t BTHOME_BINDKEY_SIZE = 16;
|
||||
static constexpr size_t BTHOME_NONCE_SIZE = 13;
|
||||
static constexpr size_t BTHOME_MIC_SIZE = 4;
|
||||
static constexpr size_t BTHOME_COUNTER_SIZE = 4;
|
||||
// If no frame with a higher counter has been accepted for this long, a lower
|
||||
// counter is accepted again (the sensor rebooted, e.g. after a battery swap).
|
||||
static constexpr uint32_t BTHOME_REPLAY_RESYNC_MS = 10 * 60 * 1000;
|
||||
|
||||
// Both callers are log macros (LOGCONFIG / LOGVV); below CONFIG level they
|
||||
// compile away and an ungated helper trips -Wunused-function.
|
||||
@@ -156,6 +161,9 @@ void BTHomeMiThermometer::dump_config() {
|
||||
if (this->has_bindkey_) {
|
||||
char bindkey_hex[format_hex_pretty_size(BTHOME_BINDKEY_SIZE)];
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty_to(bindkey_hex, this->bindkey_, BTHOME_BINDKEY_SIZE, '.'));
|
||||
#ifdef USE_BTHOME_MITHERMOMETER_REPLAY_PROTECTION
|
||||
ESP_LOGCONFIG(TAG, " Replay Protection: %s", YESNO(this->replay_protection_));
|
||||
#endif
|
||||
}
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
@@ -253,6 +261,40 @@ bool BTHomeMiThermometer::decrypt_bthome_payload_(const std::vector<uint8_t> &da
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef USE_BTHOME_MITHERMOMETER_REPLAY_PROTECTION
|
||||
// PVVX sends new data under an unchanged counter, so only an exact repeat (counter and MIC) is a duplicate and
|
||||
// earlier frames under the current counter can be replayed; the lower-counter rule is the defence. It holds only
|
||||
// while the sensor keeps being heard: after the resync window one old frame becomes the baseline and recorded
|
||||
// frames above it can follow in order, the price of recovering from a counter restart. RAM only.
|
||||
bool BTHomeMiThermometer::check_replay_counter_(uint32_t counter, uint32_t mic) {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (counter == this->last_counter_ && mic == this->last_mic_) {
|
||||
ESP_LOGVV(TAG, "Duplicate BTHome frame (counter %" PRIu32 ")", counter);
|
||||
return false;
|
||||
}
|
||||
if (counter < this->last_counter_) {
|
||||
if (now - this->last_accepted_ms_ < BTHOME_REPLAY_RESYNC_MS) {
|
||||
// One warning per run of rejections
|
||||
if (this->replay_rejected_) {
|
||||
ESP_LOGVV(TAG, "BTHome frame rejected: counter %" PRIu32 " lower than last %" PRIu32, counter,
|
||||
this->last_counter_);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "BTHome frame rejected: counter %" PRIu32 " lower than last %" PRIu32 " (replay?)", counter,
|
||||
this->last_counter_);
|
||||
this->replay_rejected_ = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ESP_LOGW(TAG, "BTHome counter restarted (%" PRIu32 " -> %" PRIu32 "), resync", this->last_counter_, counter);
|
||||
}
|
||||
this->last_counter_ = counter;
|
||||
this->last_mic_ = mic;
|
||||
this->last_accepted_ms_ = now;
|
||||
this->replay_rejected_ = false;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool BTHomeMiThermometer::handle_service_data_(const ble_device_base::ServiceData &service_data,
|
||||
const ble_device_base::ESPBTDevice &device) {
|
||||
if (!service_data.uuid.contains(0xD2, 0xFC)) {
|
||||
@@ -339,6 +381,17 @@ bool BTHomeMiThermometer::handle_service_data_(const ble_device_base::ServiceDat
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_BTHOME_MITHERMOMETER_REPLAY_PROTECTION
|
||||
// After the MIC and address checks, so other devices never touch the state
|
||||
if (is_encrypted && this->replay_protection_) {
|
||||
const uint8_t *tail = data.data() + data.size() - BTHOME_COUNTER_SIZE - BTHOME_MIC_SIZE;
|
||||
if (!this->check_replay_counter_(encode_value_le<uint32_t>(tail),
|
||||
encode_value_le<uint32_t>(tail + BTHOME_COUNTER_SIZE))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (payload_size == 0) {
|
||||
ESP_LOGVV(TAG, "BTHome payload empty after header");
|
||||
return false;
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
// decryption availability is selected per platform in the .cpp.
|
||||
namespace esphome::bthome_mithermometer {
|
||||
|
||||
namespace testing {
|
||||
class ReplayClockAccess;
|
||||
} // namespace testing
|
||||
|
||||
class BTHomeMiThermometer final : public ble_device_base::ESPBTDeviceListener, public Component {
|
||||
SUB_SENSOR(temperature)
|
||||
SUB_SENSOR(humidity)
|
||||
@@ -23,18 +27,36 @@ class BTHomeMiThermometer final : public ble_device_base::ESPBTDeviceListener, p
|
||||
public:
|
||||
void set_address(uint64_t address) { this->address_ = address; }
|
||||
void set_bindkey(std::initializer_list<uint8_t> bindkey);
|
||||
#ifdef USE_BTHOME_MITHERMOMETER_REPLAY_PROTECTION
|
||||
void set_replay_protection(bool replay_protection) { this->replay_protection_ = replay_protection; }
|
||||
#endif
|
||||
|
||||
void dump_config() override;
|
||||
bool parse_device(const ble_device_base::ESPBTDevice &device) override;
|
||||
|
||||
protected:
|
||||
#ifdef USE_BTHOME_MITHERMOMETER_REPLAY_PROTECTION
|
||||
friend class testing::ReplayClockAccess;
|
||||
#endif
|
||||
bool handle_service_data_(const ble_device_base::ServiceData &service_data,
|
||||
const ble_device_base::ESPBTDevice &device);
|
||||
bool decrypt_bthome_payload_(const std::vector<uint8_t> &data, uint64_t source_address,
|
||||
std::vector<uint8_t> &payload) const;
|
||||
#ifdef USE_BTHOME_MITHERMOMETER_REPLAY_PROTECTION
|
||||
bool check_replay_counter_(uint32_t counter, uint32_t mic);
|
||||
#endif
|
||||
|
||||
uint64_t address_{0};
|
||||
#ifdef USE_BTHOME_MITHERMOMETER_REPLAY_PROTECTION
|
||||
uint32_t last_counter_{0};
|
||||
uint32_t last_mic_{0};
|
||||
uint32_t last_accepted_ms_{0};
|
||||
#endif
|
||||
optional<uint8_t> last_packet_id_{};
|
||||
#ifdef USE_BTHOME_MITHERMOMETER_REPLAY_PROTECTION
|
||||
bool replay_protection_{false};
|
||||
bool replay_rejected_{false};
|
||||
#endif
|
||||
bool has_bindkey_{false};
|
||||
uint8_t bindkey_[16];
|
||||
};
|
||||
|
||||
@@ -4,10 +4,12 @@ from typing import Any
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.config import iter_ids
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DATA, CONF_ID, CONF_PLATFORM, CONF_TRIGGER_ID
|
||||
from esphome.const import CONF_DATA, CONF_ID, CONF_PLATFORM
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -24,8 +26,25 @@ CONF_CANBUS_ID = "canbus_id"
|
||||
CONF_BIT_RATE = "bit_rate"
|
||||
CONF_ON_FRAME = "on_frame"
|
||||
CONF_MAX_FRAMES_PER_LOOP = "max_frames_per_loop"
|
||||
CONF_FD = "fd"
|
||||
CONF_BIT_RATE_SWITCH = "bit_rate_switch"
|
||||
CONF_PAD_DATA = "pad_data"
|
||||
CONF_DEFAULT_FD = "default_fd"
|
||||
CONF_DEFAULT_BIT_RATE_SWITCH = "default_bit_rate_switch"
|
||||
CONF_DEFAULT_PAD_DATA = "default_pad_data"
|
||||
DEFAULT_MAX_FRAMES_PER_LOOP = 50 # Keep in sync with max_frames_per_loop_ in canbus.h
|
||||
DEFAULT_BIT_RATE = "125KBPS" # Keep in sync with bit_rate_ in canbus.h
|
||||
# All 29 id bits; received ids never use more, so on_frame skips the & for this mask
|
||||
CAN_ID_MASK_ALL = 0x1FFFFFFF
|
||||
CAN_MAX_DATA_LENGTH = 8
|
||||
# Payload length of each CAN FD DLC, keep in sync with CAN_FD_DLC_LENGTHS in canbus.h
|
||||
CAN_FD_DATA_LENGTHS = (0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64)
|
||||
# canbus.send options that need a platform built on CANBUS_FD_SCHEMA
|
||||
_FD_SEND_KEYS = (CONF_FD, CONF_BIT_RATE_SWITCH, CONF_PAD_DATA)
|
||||
|
||||
|
||||
def _flag(name: str, value: bool) -> str:
|
||||
return name if value else f"!{name}"
|
||||
|
||||
|
||||
def validate_id(config: ConfigType) -> ConfigType:
|
||||
@@ -49,11 +68,8 @@ def validate_raw_data(value: Any) -> bytes | list:
|
||||
|
||||
canbus_ns = cg.esphome_ns.namespace("canbus")
|
||||
CanbusComponent = canbus_ns.class_("CanbusComponent", cg.Component)
|
||||
CanbusTrigger = canbus_ns.class_(
|
||||
"CanbusTrigger",
|
||||
automation.Trigger.template(cg.std_vector.template(cg.uint8), cg.uint32),
|
||||
cg.Component,
|
||||
)
|
||||
CanFrame = canbus_ns.struct("CanFrame")
|
||||
CanPayload = canbus_ns.class_("CanPayload")
|
||||
CanSpeed = canbus_ns.enum("CAN_SPEED")
|
||||
|
||||
CAN_SPEEDS = CanSpeed.options(
|
||||
@@ -81,6 +97,21 @@ CAN_SPEEDS = CanSpeed.options(
|
||||
prefix="CAN_",
|
||||
)
|
||||
|
||||
# Data phase rates for a CAN FD platform's own schema
|
||||
CAN_DATA_SPEEDS = {
|
||||
**CAN_SPEEDS,
|
||||
**CanSpeed.options(
|
||||
"2000KBPS",
|
||||
"3000KBPS",
|
||||
"4000KBPS",
|
||||
"5000KBPS",
|
||||
"6000KBPS",
|
||||
"7000KBPS",
|
||||
"8000KBPS",
|
||||
prefix="CAN_",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def get_rate(value: str) -> int:
|
||||
match = re.match(r"(\d+)(?:K(\d+)?)?BPS", value, re.IGNORECASE)
|
||||
@@ -93,7 +124,7 @@ def get_rate(value: str) -> int:
|
||||
CANBUS_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CanbusComponent),
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=CAN_ID_MASK_ALL),
|
||||
cv.Optional(CONF_BIT_RATE, default=DEFAULT_BIT_RATE): cv.enum(
|
||||
CAN_SPEEDS, upper=True
|
||||
),
|
||||
@@ -103,10 +134,9 @@ CANBUS_SCHEMA = cv.Schema(
|
||||
): cv.positive_not_null_int,
|
||||
cv.Optional(CONF_ON_FRAME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_CAN_ID_MASK, default=0x1FFFFFFF): cv.int_range(
|
||||
min=0, max=0x1FFFFFFF
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=CAN_ID_MASK_ALL),
|
||||
cv.Optional(CONF_CAN_ID_MASK, default=CAN_ID_MASK_ALL): cv.int_range(
|
||||
min=0, max=CAN_ID_MASK_ALL
|
||||
),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST): cv.boolean,
|
||||
@@ -118,39 +148,67 @@ CANBUS_SCHEMA = cv.Schema(
|
||||
|
||||
CANBUS_SCHEMA.add_extra(validate_id)
|
||||
|
||||
# A platform whose controller supports CAN FD extends this instead of CANBUS_SCHEMA. Its buses then accept the
|
||||
# CAN FD options of canbus.send, and register_canbus() turns on USE_CANBUS_FD, which makes every CanFrame
|
||||
# 64 bytes long.
|
||||
CANBUS_FD_SCHEMA = CANBUS_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_DEFAULT_FD, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DEFAULT_BIT_RATE_SWITCH, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DEFAULT_PAD_DATA, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _is_fd_bus(config: ConfigType) -> bool:
|
||||
return CONF_DEFAULT_FD in config
|
||||
|
||||
|
||||
async def setup_canbus_core_(var: MockObj, config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_can_id(config[CONF_CAN_ID]))
|
||||
if config[CONF_USE_EXTENDED_ID]:
|
||||
cg.add(var.set_use_extended_id(True))
|
||||
if _is_fd_bus(config):
|
||||
cg.add_define("USE_CANBUS_FD")
|
||||
cg.add(var.set_supports_fd(True))
|
||||
for key, setter in (
|
||||
(CONF_DEFAULT_FD, var.set_default_fd),
|
||||
(CONF_DEFAULT_BIT_RATE_SWITCH, var.set_default_bit_rate_switch),
|
||||
(CONF_DEFAULT_PAD_DATA, var.set_default_pad_data),
|
||||
):
|
||||
if config.get(key):
|
||||
cg.add(setter(True))
|
||||
if (bit_rate := config[CONF_BIT_RATE]) != DEFAULT_BIT_RATE:
|
||||
cg.add(var.set_bitrate(CAN_SPEEDS[bit_rate]))
|
||||
if (max_frames := config[CONF_MAX_FRAMES_PER_LOOP]) != DEFAULT_MAX_FRAMES_PER_LOOP:
|
||||
cg.add(var.set_max_frames_per_loop(max_frames))
|
||||
|
||||
for conf in config.get(CONF_ON_FRAME, []):
|
||||
can_id = conf[CONF_CAN_ID]
|
||||
can_id_mask = conf[CONF_CAN_ID_MASK]
|
||||
ext_id = conf[CONF_USE_EXTENDED_ID]
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], var, can_id, can_id_mask, ext_id
|
||||
)
|
||||
if CONF_REMOTE_TRANSMISSION_REQUEST in conf:
|
||||
cg.add(
|
||||
trigger.set_remote_transmission_request(
|
||||
conf[CONF_REMOTE_TRANSMISSION_REQUEST]
|
||||
)
|
||||
)
|
||||
await cg.register_component(trigger, conf)
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
can_id = f"0x{conf[CONF_CAN_ID]:X}"
|
||||
if (mask := conf[CONF_CAN_ID_MASK]) != CAN_ID_MASK_ALL:
|
||||
checks = [f"(frame.can_id & 0x{mask:X}) == {can_id}"]
|
||||
else:
|
||||
checks = [f"frame.can_id == {can_id}"]
|
||||
checks.append(_flag("frame.use_extended_id", conf[CONF_USE_EXTENDED_ID]))
|
||||
if (rtr := conf.get(CONF_REMOTE_TRANSMISSION_REQUEST)) is not None:
|
||||
checks.append(_flag("frame.remote_transmission_request", rtr))
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_frame_callback",
|
||||
[
|
||||
(cg.std_vector.template(cg.uint8), "x"),
|
||||
(CanPayload, "x"),
|
||||
(cg.uint32, "can_id"),
|
||||
(cg.bool_, "remote_transmission_request"),
|
||||
],
|
||||
conf,
|
||||
params=[(CanFrame.operator("const").operator("ref"), "frame")],
|
||||
forward=[
|
||||
"frame.payload()",
|
||||
"frame.can_id",
|
||||
"frame.remote_transmission_request",
|
||||
],
|
||||
when=" && ".join(checks),
|
||||
)
|
||||
|
||||
|
||||
@@ -170,16 +228,66 @@ async def register_canbus(
|
||||
await setup_canbus_core_(var, config)
|
||||
|
||||
|
||||
def validate_send(action: ConfigType, bus: ConfigType) -> None:
|
||||
"""Reject a canbus.send that its bus cannot send, as far as the values are known.
|
||||
|
||||
Lambda values are checked when the action runs.
|
||||
"""
|
||||
data = action[CONF_DATA]
|
||||
length = None if cg.is_template(data) else len(data)
|
||||
if not _is_fd_bus(bus):
|
||||
for key in _FD_SEND_KEYS:
|
||||
if key in action:
|
||||
raise cv.Invalid(
|
||||
f"{key} needs a canbus platform that supports CAN FD", path=[key]
|
||||
)
|
||||
fd = False
|
||||
else:
|
||||
fd = action.get(CONF_FD, bus[CONF_DEFAULT_FD])
|
||||
if fd is False and action.get(CONF_BIT_RATE_SWITCH) is True:
|
||||
raise cv.Invalid("bit_rate_switch requires fd: true")
|
||||
if fd is True and action[CONF_REMOTE_TRANSMISSION_REQUEST]:
|
||||
raise cv.Invalid("remote_transmission_request cannot be used with fd: true")
|
||||
if length is None:
|
||||
return
|
||||
if fd is False and length > CAN_MAX_DATA_LENGTH:
|
||||
raise cv.Invalid("Classic CAN data must be at most 8 bytes", path=[CONF_DATA])
|
||||
if (
|
||||
fd is True
|
||||
and length not in CAN_FD_DATA_LENGTHS
|
||||
and action.get(CONF_PAD_DATA, bus[CONF_DEFAULT_PAD_DATA]) is False
|
||||
):
|
||||
raise cv.Invalid("CAN FD data length requires pad_data: true", path=[CONF_DATA])
|
||||
|
||||
|
||||
def final_validate(config: list[ConfigType]) -> None:
|
||||
full_config = fv.full_config.get()
|
||||
for bus_id, path in iter_ids(full_config):
|
||||
if len(path) < 2 or path[-2:] != ["canbus.send", CONF_CANBUS_ID]:
|
||||
continue
|
||||
action_path = path[:-1]
|
||||
action = full_config.get_config_for_path(action_path)
|
||||
bus = fv.get_declaring_config(bus_id, full_config)
|
||||
with cv.prepend_path([cv.ROOT_CONFIG_PATH, *action_path]):
|
||||
validate_send(action, bus)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validate
|
||||
|
||||
|
||||
# Actions
|
||||
CANBUS_SEND_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
|
||||
cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=CAN_ID_MASK_ALL),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST, default=False): cv.boolean,
|
||||
# One classic CAN frame
|
||||
cv.Optional(CONF_FD): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_BIT_RATE_SWITCH): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_PAD_DATA): cv.templatable(cv.boolean),
|
||||
# One CAN FD frame; final_validate holds a classic bus to 8 bytes
|
||||
cv.Required(CONF_DATA): cv.templatable(
|
||||
cv.All(validate_raw_data, cv.Length(max=8))
|
||||
cv.All(validate_raw_data, cv.Length(max=CAN_FD_DATA_LENGTHS[-1]))
|
||||
),
|
||||
},
|
||||
validate_id,
|
||||
@@ -209,6 +317,14 @@ async def canbus_action_to_code(
|
||||
cg.add(
|
||||
var.set_remote_transmission_request(config[CONF_REMOTE_TRANSMISSION_REQUEST])
|
||||
)
|
||||
# Only set on a CAN FD bus, which final_validate checks
|
||||
for key, setter in (
|
||||
(CONF_FD, var.set_fd),
|
||||
(CONF_BIT_RATE_SWITCH, var.set_bit_rate_switch),
|
||||
(CONF_PAD_DATA, var.set_pad_data),
|
||||
):
|
||||
if (value := config.get(key)) is not None:
|
||||
cg.add(setter(await cg.templatable(value, args, cg.bool_)))
|
||||
|
||||
await automation.templatable_bytes(
|
||||
config[CONF_DATA],
|
||||
@@ -216,5 +332,6 @@ async def canbus_action_to_code(
|
||||
var.set_data_template,
|
||||
var.set_data_static,
|
||||
"canbus_data",
|
||||
return_type=CanPayload,
|
||||
)
|
||||
return var
|
||||
@@ -22,49 +22,112 @@ void Canbus::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
canbus::Error Canbus::send_data(const CanFrame &frame) {
|
||||
if (frame.can_id > (frame.use_extended_id ? CAN_EXTENDED_ID_MAX : CAN_STANDARD_ID_MAX)) {
|
||||
ESP_LOGE(TAG, "CAN ID 0x%08" PRIx32 " exceeds frame identifier width", frame.can_id);
|
||||
return ERROR_FAILTX;
|
||||
}
|
||||
#ifdef USE_CANBUS_FD
|
||||
if (frame.fd && !this->supports_fd_) {
|
||||
ESP_LOGE(TAG, "CAN FD frame on a bus without CAN FD");
|
||||
return ERROR_FAILTX;
|
||||
}
|
||||
if (frame.fd ? (frame.remote_transmission_request || can_dlc_to_len(frame.can_data_length_code) == CAN_INVALID_DLC)
|
||||
: frame.bit_rate_switch) {
|
||||
ESP_LOGE(TAG, "Invalid CAN frame: fd=%d rtr=%d brs=%d dlc=%" PRIu8, frame.fd, frame.remote_transmission_request,
|
||||
frame.bit_rate_switch, frame.can_data_length_code);
|
||||
return ERROR_FAILTX;
|
||||
}
|
||||
#endif
|
||||
return this->send_frame_(frame);
|
||||
}
|
||||
|
||||
#ifdef USE_CANBUS_FD
|
||||
canbus::Error Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
||||
const uint8_t *data, size_t len, bool fd, bool bit_rate_switch, bool pad_data) {
|
||||
#else
|
||||
canbus::Error Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
||||
const uint8_t *data, size_t len) {
|
||||
struct CanFrame can_message;
|
||||
|
||||
#endif
|
||||
CanFrame frame;
|
||||
frame.use_extended_id = use_extended_id;
|
||||
frame.remote_transmission_request = remote_transmission_request;
|
||||
frame.can_id = can_id;
|
||||
#ifdef USE_CANBUS_FD
|
||||
frame.fd = fd;
|
||||
frame.bit_rate_switch = bit_rate_switch;
|
||||
frame.error_state_indicator = false;
|
||||
if (fd) {
|
||||
frame.can_data_length_code = can_len_to_dlc(len, pad_data);
|
||||
if (frame.can_data_length_code == CAN_INVALID_DLC) {
|
||||
ESP_LOGE(TAG, "CAN FD cannot carry %zu bytes without pad_data", len);
|
||||
return ERROR_FAILTX;
|
||||
}
|
||||
if (!remote_transmission_request && len != 0) {
|
||||
std::memcpy(frame.data, data, len);
|
||||
std::memset(frame.data + len, 0, can_dlc_to_len(frame.can_data_length_code) - len);
|
||||
}
|
||||
return this->send_data(frame);
|
||||
}
|
||||
#endif
|
||||
if (len > CAN_MAX_DATA_LENGTH) {
|
||||
ESP_LOGW(TAG, "Classic CAN carries 8 bytes, %zu given, sent 8", len);
|
||||
}
|
||||
// Clamp before narrowing, so a lambda payload of 256 bytes or more cannot wrap to a short length
|
||||
const uint8_t size = static_cast<uint8_t>(std::min<size_t>(len, CAN_MAX_DATA_LENGTH));
|
||||
if (use_extended_id) {
|
||||
ESP_LOGD(TAG, "send extended id=0x%08" PRIx32 " rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request),
|
||||
size);
|
||||
frame.can_data_length_code = static_cast<uint8_t>(std::min<size_t>(len, CAN_MAX_DATA_LENGTH));
|
||||
// A remote request carries no data, so its bytes are zeroed rather than left from the stack. An empty vector may
|
||||
// hand over a null pointer, which memcpy must not get even for 0 bytes.
|
||||
if (remote_transmission_request) {
|
||||
std::memset(frame.data, 0, frame.can_data_length_code);
|
||||
} else if (frame.can_data_length_code != 0) {
|
||||
std::memcpy(frame.data, data, frame.can_data_length_code);
|
||||
}
|
||||
#ifdef USE_CANBUS_FD
|
||||
// The caller's flags can still contradict each other
|
||||
return this->send_data(frame);
|
||||
#else
|
||||
return this->send_frame_(frame);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Canbus::lambda_payload_fits_(const CanPayload &payload, [[maybe_unused]] bool fd) {
|
||||
if (!payload.truncated())
|
||||
return true;
|
||||
#ifdef USE_CANBUS_FD
|
||||
if (fd) {
|
||||
ESP_LOGE(TAG, "CAN FD carries at most %u bytes, frame not sent", CAN_FRAME_DATA_LENGTH);
|
||||
return false;
|
||||
}
|
||||
// The payload still holds 64 bytes, so send_data() warns as it cuts a classic frame to 8
|
||||
#else
|
||||
ESP_LOGW(TAG, "Classic CAN carries 8 bytes, the rest is cut");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
canbus::Error Canbus::send_frame_(const CanFrame &frame) {
|
||||
const uint8_t size = frame.payload_length();
|
||||
if (frame.use_extended_id) {
|
||||
ESP_LOGD(TAG, "send extended id=0x%08" PRIx32 " rtr=%s size=%d", frame.can_id,
|
||||
TRUEFALSE(frame.remote_transmission_request), size);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "send standard id=0x%03" PRIx32 " rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request),
|
||||
size);
|
||||
ESP_LOGD(TAG, "send standard id=0x%03" PRIx32 " rtr=%s size=%d", frame.can_id,
|
||||
TRUEFALSE(frame.remote_transmission_request), size);
|
||||
}
|
||||
can_message.can_data_length_code = size;
|
||||
can_message.can_id = can_id;
|
||||
can_message.use_extended_id = use_extended_id;
|
||||
can_message.remote_transmission_request = remote_transmission_request;
|
||||
ESP_LOGV_HEX(TAG, frame.data, size, CAN_FRAME_DATA_LENGTH, " data=%s");
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
can_message.data[i] = data[i];
|
||||
ESP_LOGVV(TAG, " data[%d]=%02x", i, can_message.data[i]);
|
||||
}
|
||||
|
||||
canbus::Error error = this->send_message(&can_message);
|
||||
// Drivers take a mutable pointer but do not change the frame
|
||||
canbus::Error error = this->send_message(const_cast<CanFrame *>(&frame));
|
||||
if (error != canbus::ERROR_OK) {
|
||||
if (use_extended_id) {
|
||||
ESP_LOGW(TAG, "send to extended id=0x%08" PRIx32 " failed with error %d!", can_id, error);
|
||||
if (frame.use_extended_id) {
|
||||
ESP_LOGW(TAG, "send to extended id=0x%08" PRIx32 " failed with error %d!", frame.can_id, error);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "send to standard id=0x%03" PRIx32 " failed with error %d!", can_id, error);
|
||||
ESP_LOGW(TAG, "send to standard id=0x%03" PRIx32 " failed with error %d!", frame.can_id, error);
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void Canbus::add_trigger(CanbusTrigger *trigger) {
|
||||
if (trigger->use_extended_id_) {
|
||||
ESP_LOGVV(TAG, "add trigger for extended canid=0x%08" PRIx32, trigger->can_id_);
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "add trigger for std canid=0x%03" PRIx32, trigger->can_id_);
|
||||
}
|
||||
this->triggers_.push_back(trigger);
|
||||
};
|
||||
|
||||
void Canbus::loop() {
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
if ((now - this->last_event_check_time_) >= EVENT_CHECK_INTERVAL_MS) {
|
||||
@@ -75,38 +138,27 @@ void Canbus::loop() {
|
||||
struct CanFrame can_message;
|
||||
uint32_t message_counter = 0;
|
||||
for (; message_counter < this->max_frames_per_loop_; message_counter++) {
|
||||
#ifdef USE_CANBUS_FD
|
||||
// Classic drivers leave the CAN FD flags alone
|
||||
can_message.fd = false;
|
||||
can_message.bit_rate_switch = false;
|
||||
can_message.error_state_indicator = false;
|
||||
#endif
|
||||
if (this->read_message(&can_message) != canbus::ERROR_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t size = can_message.payload_length();
|
||||
if (can_message.use_extended_id) {
|
||||
ESP_LOGD(TAG, "received can message (#%" PRIu32 ") extended can_id=0x%" PRIx32 " size=%d", message_counter + 1,
|
||||
can_message.can_id, can_message.can_data_length_code);
|
||||
can_message.can_id, size);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "received can message (#%" PRIu32 ") std can_id=0x%" PRIx32 " size=%d", message_counter + 1,
|
||||
can_message.can_id, can_message.can_data_length_code);
|
||||
can_message.can_id, size);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
// show data received
|
||||
for (int i = 0; i < std::min(can_message.can_data_length_code, CAN_MAX_DATA_LENGTH); i++) {
|
||||
ESP_LOGV(TAG, " can_message.data[%d]=%02x", i, can_message.data[i]);
|
||||
data.push_back(can_message.data[i]);
|
||||
}
|
||||
|
||||
this->callback_manager_(can_message.can_id, can_message.use_extended_id, can_message.remote_transmission_request,
|
||||
data);
|
||||
|
||||
// fire all triggers
|
||||
for (auto *trigger : this->triggers_) {
|
||||
if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) &&
|
||||
(trigger->use_extended_id_ == can_message.use_extended_id) &&
|
||||
(!trigger->remote_transmission_request_.has_value() ||
|
||||
trigger->remote_transmission_request_.value() == can_message.remote_transmission_request)) {
|
||||
trigger->trigger(data, can_message.can_id, can_message.remote_transmission_request);
|
||||
}
|
||||
}
|
||||
ESP_LOGV_HEX(TAG, can_message.data, size, CAN_FRAME_DATA_LENGTH, " data=%s");
|
||||
this->frame_callbacks_.call(can_message);
|
||||
}
|
||||
|
||||
if (message_counter == this->max_frames_per_loop_) {
|
||||
|
||||
@@ -2,9 +2,16 @@
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::canbus {
|
||||
@@ -39,28 +46,128 @@ enum CanSpeed : uint8_t {
|
||||
CAN_250KBPS,
|
||||
CAN_500KBPS,
|
||||
CAN_800KBPS,
|
||||
CAN_1000KBPS
|
||||
CAN_1000KBPS,
|
||||
#ifdef USE_CANBUS_FD
|
||||
// Data phase rates of a CAN FD controller
|
||||
CAN_2000KBPS,
|
||||
CAN_3000KBPS,
|
||||
CAN_4000KBPS,
|
||||
CAN_5000KBPS,
|
||||
CAN_6000KBPS,
|
||||
CAN_7000KBPS,
|
||||
CAN_8000KBPS,
|
||||
#endif
|
||||
};
|
||||
|
||||
class CanbusTrigger;
|
||||
template<typename... Ts> class CanbusSendAction;
|
||||
|
||||
/* CAN payload length definitions according to ISO 11898-1 */
|
||||
static const uint8_t CAN_MAX_DATA_LENGTH = 8;
|
||||
static constexpr uint8_t CAN_MAX_DATA_LENGTH = 8;
|
||||
#ifdef USE_CANBUS_FD
|
||||
static constexpr uint8_t CAN_FD_MAX_DATA_LENGTH = 64;
|
||||
/// Payload bytes a CanFrame holds: one CAN FD frame when a platform that supports it is configured
|
||||
static constexpr uint8_t CAN_FRAME_DATA_LENGTH = CAN_FD_MAX_DATA_LENGTH;
|
||||
#else
|
||||
/// Payload bytes a CanFrame holds: one classic frame
|
||||
static constexpr uint8_t CAN_FRAME_DATA_LENGTH = CAN_MAX_DATA_LENGTH;
|
||||
#endif
|
||||
|
||||
static const uint8_t CAN_ERROR_PASSIVE_THRESHOLD = 128;
|
||||
static constexpr uint32_t CAN_STANDARD_ID_MAX = 0x7FF;
|
||||
static constexpr uint32_t CAN_EXTENDED_ID_MAX = 0x1FFFFFFF;
|
||||
|
||||
/*
|
||||
Can Frame describes a normative CAN Frame
|
||||
The RTR = Remote Transmission Request is implemented in every CAN controller but rarely used
|
||||
So currently the flag is passed to and from the hardware but currently ignored to the user application.
|
||||
*/
|
||||
static constexpr uint8_t CAN_ERROR_PASSIVE_THRESHOLD = 128;
|
||||
|
||||
#ifdef USE_CANBUS_FD
|
||||
/// Returned by the length conversions for a value that cannot be represented
|
||||
static constexpr uint8_t CAN_INVALID_DLC = UINT8_MAX;
|
||||
|
||||
/// Payload length of each CAN FD DLC; a classic frame carries min(DLC, 8) bytes
|
||||
static constexpr uint8_t CAN_FD_DLC_COUNT = 16;
|
||||
static constexpr uint8_t CAN_FD_DLC_LENGTHS[CAN_FD_DLC_COUNT] PROGMEM = {0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 12, 16, 20, 24, 32, 48, 64};
|
||||
|
||||
/// CAN_FD_DLC_LENGTHS[dlc] for dlc below CAN_FD_DLC_COUNT; read from flash at run time
|
||||
constexpr uint8_t can_fd_dlc_length(uint8_t dlc) {
|
||||
return std::is_constant_evaluated() ? CAN_FD_DLC_LENGTHS[dlc] : progmem_read_byte(&CAN_FD_DLC_LENGTHS[dlc]);
|
||||
}
|
||||
|
||||
/// Payload length of a CAN FD DLC, or CAN_INVALID_DLC above 15
|
||||
constexpr uint8_t can_dlc_to_len(uint8_t dlc) {
|
||||
return dlc < CAN_FD_DLC_COUNT ? can_fd_dlc_length(dlc) : CAN_INVALID_DLC;
|
||||
}
|
||||
|
||||
/// CAN FD DLC for a payload length. With pad, the next longer length is used and the caller zero fills
|
||||
/// the extra bytes; otherwise a length CAN FD cannot carry returns CAN_INVALID_DLC.
|
||||
constexpr uint8_t can_len_to_dlc(size_t len, bool pad) {
|
||||
for (uint8_t dlc = 0; dlc < CAN_FD_DLC_COUNT; dlc++) {
|
||||
const uint8_t length = can_fd_dlc_length(dlc);
|
||||
if (length >= len)
|
||||
return (length == len || pad) ? dlc : CAN_INVALID_DLC;
|
||||
}
|
||||
return CAN_INVALID_DLC;
|
||||
}
|
||||
#else
|
||||
/// Payload length of a classic DLC; 9 to 15 still carry 8 bytes
|
||||
constexpr uint8_t can_dlc_to_len(uint8_t dlc) { return std::min(dlc, CAN_MAX_DATA_LENGTH); }
|
||||
#endif
|
||||
|
||||
/// Frame payload without heap; converts to and from std::vector<uint8_t> for older lambdas.
|
||||
class CanPayload : public StaticVector<uint8_t, CAN_FRAME_DATA_LENGTH> {
|
||||
public:
|
||||
using StaticVector<uint8_t, CAN_FRAME_DATA_LENGTH>::StaticVector;
|
||||
// Not = default, so value-init does not zero the buffer
|
||||
CanPayload() {}
|
||||
// Every constructor that can be given more than one frame records that it cut the rest
|
||||
template<typename InputIt> CanPayload(InputIt first, InputIt last) {
|
||||
for (; first != last; ++first) {
|
||||
if (this->size() == CAN_FRAME_DATA_LENGTH) {
|
||||
this->truncated_ = true;
|
||||
break;
|
||||
}
|
||||
this->push_back(*first);
|
||||
}
|
||||
}
|
||||
CanPayload(std::initializer_list<uint8_t> data) : CanPayload(data.begin(), data.end()) {}
|
||||
CanPayload(const std::vector<uint8_t> &data) // NOLINT(google-explicit-constructor)
|
||||
: CanPayload(data.begin(), data.end()) {}
|
||||
operator std::vector<uint8_t>() const { return {this->begin(), this->end()}; } // NOLINT(google-explicit-constructor)
|
||||
/// Whether it was built from more bytes than one frame can carry
|
||||
bool truncated() const { return this->truncated_; }
|
||||
|
||||
protected:
|
||||
bool truncated_{false};
|
||||
};
|
||||
|
||||
/// One CAN frame as passed between the bus and its driver. The CAN FD flags come after data, so code that
|
||||
/// brace-initializes a classic frame keeps working.
|
||||
struct CanFrame {
|
||||
bool use_extended_id = false;
|
||||
bool remote_transmission_request = false;
|
||||
uint32_t can_id; /* 29 or 11 bit CAN_ID */
|
||||
uint8_t can_data_length_code; /* frame payload length in byte (0 .. CAN_MAX_DATA_LENGTH) */
|
||||
uint8_t data[CAN_MAX_DATA_LENGTH] __attribute__((aligned(8)));
|
||||
uint32_t can_id; /* 29 or 11 bit CAN_ID; drivers clear bits 29-31 (on_frame relies on it) */
|
||||
uint8_t can_data_length_code; /* DLC as on the wire, not a byte count (see payload_length()) */
|
||||
uint8_t data[CAN_FRAME_DATA_LENGTH] __attribute__((aligned(8)));
|
||||
#ifdef USE_CANBUS_FD
|
||||
bool fd = false; /* CAN FD frame; it cannot be a remote transmission request */
|
||||
bool bit_rate_switch = false; /* CAN FD only: data phase sent at the data bit rate */
|
||||
bool error_state_indicator = false; /* CAN FD only: received transmitter was error passive */
|
||||
#endif
|
||||
|
||||
/// Payload bytes for the DLC: at most 8 for a classic frame, 0 for an invalid CAN FD DLC
|
||||
uint8_t payload_length() const {
|
||||
#ifdef USE_CANBUS_FD
|
||||
if (this->fd) {
|
||||
const uint8_t len = can_dlc_to_len(this->can_data_length_code);
|
||||
return len == CAN_INVALID_DLC ? 0 : len;
|
||||
}
|
||||
#endif
|
||||
return std::min(this->can_data_length_code, CAN_MAX_DATA_LENGTH);
|
||||
}
|
||||
CanPayload payload() const {
|
||||
CanPayload out;
|
||||
std::memcpy(out.data(), this->data, CAN_FRAME_DATA_LENGTH);
|
||||
out.resize(this->payload_length());
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
enum CanEventFlags : uint8_t {
|
||||
@@ -91,8 +198,18 @@ class Canbus : public Component {
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
void loop() override;
|
||||
|
||||
/// Validates the frame and passes it to the driver.
|
||||
canbus::Error send_data(const CanFrame &frame);
|
||||
#ifdef USE_CANBUS_FD
|
||||
/// Sends up to 8 bytes as a classic frame, longer data is cut. An fd frame carries up to 64 bytes; pad_data
|
||||
/// zero fills a length CAN FD cannot carry up to the next one.
|
||||
canbus::Error send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, const uint8_t *data,
|
||||
size_t len, bool fd = false, bool bit_rate_switch = false, bool pad_data = false);
|
||||
#else
|
||||
/// Sends up to 8 bytes as a classic frame, longer data is cut.
|
||||
canbus::Error send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, const uint8_t *data,
|
||||
size_t len);
|
||||
#endif
|
||||
canbus::Error send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
||||
const std::vector<uint8_t> &data) {
|
||||
return this->send_data(can_id, use_extended_id, remote_transmission_request, data.data(), data.size());
|
||||
@@ -105,9 +222,28 @@ class Canbus : public Component {
|
||||
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
|
||||
void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
|
||||
void set_max_frames_per_loop(uint32_t max_frames_per_loop) { this->max_frames_per_loop_ = max_frames_per_loop; }
|
||||
#ifdef USE_CANBUS_FD
|
||||
// Defaults for canbus.send actions only, not for send_data()
|
||||
void set_default_fd(bool default_fd) { this->default_fd_ = default_fd; }
|
||||
void set_default_bit_rate_switch(bool default_bit_rate_switch) {
|
||||
this->default_bit_rate_switch_ = default_bit_rate_switch;
|
||||
}
|
||||
void set_default_pad_data(bool default_pad_data) { this->default_pad_data_ = default_pad_data; }
|
||||
void set_supports_fd(bool supports_fd) { this->supports_fd_ = supports_fd; }
|
||||
#endif
|
||||
/// Whether this bus sends and receives CAN FD frames; a classic bus can share a build with a CAN FD one
|
||||
bool supports_fd() const {
|
||||
#ifdef USE_CANBUS_FD
|
||||
return this->supports_fd_;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
virtual CanStatus get_status() { return {}; }
|
||||
|
||||
void add_trigger(CanbusTrigger *trigger);
|
||||
/// The frame is only valid during the call.
|
||||
template<typename F> void add_frame_callback(F &&callback) { this->frame_callbacks_.add(std::forward<F>(callback)); }
|
||||
|
||||
/**
|
||||
* Add a callback to be called when a CAN message is received. All received messages
|
||||
* are passed to the callback without filtering.
|
||||
@@ -118,19 +254,30 @@ class Canbus : public Component {
|
||||
* - rtr If this is a remote transmission request
|
||||
* - data The message data
|
||||
*/
|
||||
template<typename F> void add_callback(F &&callback) { this->callback_manager_.add(std::forward<F>(callback)); }
|
||||
template<typename F>
|
||||
ESPDEPRECATED("Use add_frame_callback(), which does not copy the payload. Removed in 2027.5.0.", "2026.11.0")
|
||||
void add_callback(F &&callback) {
|
||||
this->frame_callbacks_.add([cb = std::forward<F>(callback)](const CanFrame &frame) mutable {
|
||||
cb(frame.can_id, frame.use_extended_id, frame.remote_transmission_request,
|
||||
std::vector<uint8_t>(frame.data, frame.data + frame.payload_length()));
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
template<typename... Ts> friend class CanbusSendAction;
|
||||
std::vector<CanbusTrigger *> triggers_{};
|
||||
uint32_t can_id_;
|
||||
uint32_t max_frames_per_loop_{50}; // Keep in sync with DEFAULT_MAX_FRAMES_PER_LOOP in __init__.py
|
||||
CanSpeed bit_rate_{CAN_125KBPS}; // Keep in sync with DEFAULT_BIT_RATE in __init__.py
|
||||
bool use_extended_id_{false};
|
||||
#ifdef USE_CANBUS_FD
|
||||
bool default_fd_{false};
|
||||
bool default_bit_rate_switch_{false};
|
||||
bool default_pad_data_{false};
|
||||
bool supports_fd_{false};
|
||||
#endif
|
||||
uint8_t events_to_log_{0};
|
||||
bool bus_off_{false};
|
||||
CallbackManager<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)>
|
||||
callback_manager_{};
|
||||
CallbackManager<void(const CanFrame &frame)> frame_callbacks_{};
|
||||
|
||||
// interval in which to check for new can bus events (bus-off, passive, active)
|
||||
static constexpr uint32_t EVENT_CHECK_INTERVAL_MS = 100;
|
||||
@@ -150,6 +297,10 @@ class Canbus : public Component {
|
||||
#endif
|
||||
|
||||
void log_events_(CanEventFlags events, uint32_t now);
|
||||
/// Logs and sends a frame that is already valid.
|
||||
canbus::Error send_frame_(const CanFrame &frame);
|
||||
/// For a canbus.send lambda: a CAN FD frame that did not fit is refused, a classic one is cut to 8 bytes.
|
||||
bool lambda_payload_fits_(const CanPayload &payload, [[maybe_unused]] bool fd);
|
||||
virtual bool setup_internal() = 0;
|
||||
virtual Error send_message(struct CanFrame *frame) = 0;
|
||||
virtual Error read_message(struct CanFrame *frame) = 0;
|
||||
@@ -157,7 +308,12 @@ class Canbus : public Component {
|
||||
};
|
||||
|
||||
template<typename... Ts> class CanbusSendAction final : public Action<Ts...>, public Parented<Canbus> {
|
||||
TEMPLATABLE_BYTES(data)
|
||||
TEMPLATABLE_BUFFER(data, CanPayload)
|
||||
#ifdef USE_CANBUS_FD
|
||||
TEMPLATABLE_VALUE(bool, fd)
|
||||
TEMPLATABLE_VALUE(bool, bit_rate_switch)
|
||||
TEMPLATABLE_VALUE(bool, pad_data)
|
||||
#endif
|
||||
|
||||
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
||||
|
||||
@@ -171,11 +327,30 @@ template<typename... Ts> class CanbusSendAction final : public Action<Ts...>, pu
|
||||
auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
|
||||
auto use_extended_id =
|
||||
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
|
||||
this->data_.template visit<CAN_MAX_DATA_LENGTH>(
|
||||
[&](const uint8_t *data, size_t len) {
|
||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data, len);
|
||||
},
|
||||
x...);
|
||||
#ifdef USE_CANBUS_FD
|
||||
const bool fd = this->fd_.value_or(x..., this->parent_->default_fd_);
|
||||
const bool bit_rate_switch = this->bit_rate_switch_.value_or(x..., fd && this->parent_->default_bit_rate_switch_);
|
||||
const bool pad_data = this->pad_data_.value_or(x..., this->parent_->default_pad_data_);
|
||||
#endif
|
||||
#ifndef USE_CANBUS_FD
|
||||
const bool fd = false;
|
||||
#endif
|
||||
auto send = [&](const uint8_t *data, size_t len) {
|
||||
#ifdef USE_CANBUS_FD
|
||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data, len, fd,
|
||||
bit_rate_switch, pad_data);
|
||||
#else
|
||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data, len);
|
||||
#endif
|
||||
};
|
||||
if (this->data_.is_static()) {
|
||||
this->data_.template visit<CAN_FRAME_DATA_LENGTH>(send, x...);
|
||||
return;
|
||||
}
|
||||
// The conversion from a lambda's vector cuts it to one frame, so the length it had is checked here
|
||||
const CanPayload payload = this->data_.value(x...);
|
||||
if (this->parent_->lambda_payload_fits_(payload, fd))
|
||||
send(payload.data(), payload.size());
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -184,26 +359,4 @@ template<typename... Ts> class CanbusSendAction final : public Action<Ts...>, pu
|
||||
bool remote_transmission_request_{false};
|
||||
};
|
||||
|
||||
class CanbusTrigger final : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {
|
||||
friend class Canbus;
|
||||
|
||||
public:
|
||||
explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const std::uint32_t can_id_mask,
|
||||
const bool use_extended_id)
|
||||
: parent_(parent), can_id_(can_id), can_id_mask_(can_id_mask), use_extended_id_(use_extended_id) {};
|
||||
|
||||
void set_remote_transmission_request(bool remote_transmission_request) {
|
||||
this->remote_transmission_request_ = remote_transmission_request;
|
||||
}
|
||||
|
||||
void setup() override { this->parent_->add_trigger(this); }
|
||||
|
||||
protected:
|
||||
Canbus *parent_;
|
||||
uint32_t can_id_;
|
||||
uint32_t can_id_mask_;
|
||||
bool use_extended_id_;
|
||||
optional<bool> remote_transmission_request_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::canbus
|
||||
@@ -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);
|
||||
|
||||
@@ -24,10 +24,8 @@ void CH423Component::setup() {
|
||||
if (success && !(this->sys_params_ & CH423_SYS_IO_OE)) {
|
||||
success = this->read_inputs_();
|
||||
}
|
||||
if (!success) {
|
||||
this->mark_failed(LOG_STR("CH423 not detected"));
|
||||
if (!this->setup_check(success, LOG_STR("CH423 not detected")))
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
|
||||
this->status_has_error());
|
||||
|
||||
@@ -23,10 +23,9 @@ uint8_t cm1106_checksum(const uint8_t *response, size_t len) {
|
||||
|
||||
void CM1106Component::setup() {
|
||||
uint8_t response[8] = {0};
|
||||
if (!this->cm1106_write_command_(C_M1106_CMD_GET_CO2, sizeof(C_M1106_CMD_GET_CO2), response, sizeof(response))) {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
if (!this->setup_check(
|
||||
this->cm1106_write_command_(C_M1106_CMD_GET_CO2, sizeof(C_M1106_CMD_GET_CO2), response, sizeof(response))))
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->abc_boot_logic_ != CM1106_ABC_NONE) {
|
||||
this->abc_set_(this->abc_boot_logic_ == CM1106_ABC_ENABLED);
|
||||
@@ -42,7 +41,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;
|
||||
}
|
||||
@@ -73,8 +75,7 @@ void CM1106Component::update() {
|
||||
void CM1106Component::calibrate_zero(uint16_t ppm) {
|
||||
uint8_t cmd[6];
|
||||
memcpy(cmd, C_M1106_CMD_SET_CO2_CALIB, sizeof(cmd));
|
||||
cmd[3] = ppm >> 8;
|
||||
cmd[4] = ppm & 0xFF;
|
||||
put_be16(cmd + 3, ppm);
|
||||
if (this->send_command_expect_ack_(cmd, sizeof(cmd), C_M1106_CMD_SET_CO2_CALIB_RESPONSE)) {
|
||||
ESP_LOGD(TAG, "Successfully calibrated sensor to %uppm", ppm);
|
||||
}
|
||||
@@ -86,8 +87,7 @@ void CM1106Component::abc_set_(bool enabled) {
|
||||
memcpy(cmd, C_M1106_CMD_SET_ABC_STATUS, sizeof(cmd));
|
||||
cmd[4] = enabled ? CM1106_ABC_FLAG_ENABLE : CM1106_ABC_FLAG_DISABLE;
|
||||
cmd[5] = this->abc_cycle_;
|
||||
cmd[6] = this->abc_baseline_ >> 8;
|
||||
cmd[7] = this->abc_baseline_ & 0xFF;
|
||||
put_be16(cmd + 6, this->abc_baseline_);
|
||||
if (this->send_command_expect_ack_(cmd, sizeof(cmd), C_M1106_CMD_SET_ABC_STATUS_RESPONSE)) {
|
||||
ESP_LOGD(TAG, "Successfully set ABC status");
|
||||
}
|
||||
@@ -101,7 +101,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;
|
||||
|
||||
@@ -24,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"
|
||||
@@ -50,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"
|
||||
@@ -74,6 +77,7 @@ 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"
|
||||
@@ -104,7 +108,9 @@ CONF_VOC_INDEX = "voc_index"
|
||||
CONF_VOLUME_INCREMENT = "volume_increment"
|
||||
CONF_VOLUME_INITIAL = "volume_initial"
|
||||
CONF_VOLUME_MAX = "volume_max"
|
||||
CONF_VOLUME_MAX_DB = "volume_max_db"
|
||||
CONF_VOLUME_MIN = "volume_min"
|
||||
CONF_VOLUME_MIN_DB = "volume_min_db"
|
||||
CONF_WINDOW = "window"
|
||||
|
||||
ICON_CURRENT_DC = "mdi:current-dc"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Loaded 100 of 664 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user