Compare commits

..

102 Commits

Author SHA1 Message Date
J. Nick Koston
c2968e64d1 Merge branch 'esp32_cam_init_order' into frame_helper_optimize_cleanup_api 2025-05-15 16:51:59 -05:00
J. Nick Koston
db9a070c42 Fix ESP32 Camera class inheritance
panic was introduced in bb1f24ab43

Fix ESP32 Camera crash by reordering parent class inheritance to match other entity components. The crash occurred because `EntityBase` methods were accessed before initialization when Component was the first parent. Changing ESP32Camera to inherit first from EntityBase ensures proper memory layout and initialization order, consistent with all other entities in the codebase.

```
[14:39:21][D][binary_sensor:032]: 'esp-cam status': Sending state ON
[14:39:21][D][api.connection:1527]: Home Assistant 2025.6.0.dev0 (192.168.209.64): Connected successfully
[14:39:21]Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.
[14:39:21]
[14:39:21]Core  1 register dump:
[14:39:21]PC      : 0x4008aa94  PS      : 0x00060d30  A0      : 0x800d40eb  A1      : 0x3ffc06a0
WARNING Decoded 0x4008aa94: strlen at /Users/brnomac003/.gitlab-runner/builds/qR2TxTby/0/idf/crosstool-NG/.build/xtensa-esp32-elf/src/newlib/newlib/libc/machine/xtensa/strlen.S:46
[14:39:21]A2      : 0x00000027  A3      : 0x00000023  A4      : 0x000000ff  A5      : 0x0000ff00
[14:39:21]A6      : 0x00ff0000  A7      : 0xff000000  A8      : 0x800d4982  A9      : 0x3ffc0670
[14:39:21]A10     : 0x3ffc0728  A11     : 0x3ffc07f0  A12     : 0x00000006  A13     : 0x00000000
[14:39:21]A14     : 0x3ffb5e64  A15     : 0x00000000  SAR     : 0x00000019  EXCCAUSE: 0x0000001c
[14:39:21]EXCVADDR: 0x00000027  LBEG    : 0x4008a350  LEND    : 0x4008a36c  LCOUNT  : 0xffffffff
[14:39:21]
[14:39:21]
[14:39:21]Backtrace: 0x4008aa91:0x3ffc06a0 0x400d40e8:0x3ffc06b0 0x400e64ea:0x3ffc06d0 0x400d4b2d:0x3ffc0710 0x400d5e0e:0x3ffc0760 0x400d47df:0x3ffc0830 0x400d484a:0x3ffc0870 0x400da2cd:0x3ffc08a0 0x400e6319:0x3ffc08c0 0x400d5134:0x3ffc08e0
0x400da244:0x3ffc0980 0x401669e5:0x3ffc0a60 0x40166a9d:0x3ffc0a80 0x400e4c1f:0x3ffc0aa0 0x400e7446:0x3ffc0ad0 0x400da78a:0x3ffc0af0
WARNING Found stack trace! Trying to decode it
WARNING Decoded 0x4008aa91: strlen at /Users/brnomac003/.gitlab-runner/builds/qR2TxTby/0/idf/crosstool-NG/.build/xtensa-esp32-elf/src/newlib/newlib/libc/machine/xtensa/strlen.S:43
WARNING Decoded 0x400d40e8: std::char_traits<char>::length(char const*) at /Users/bdraco/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/12.2.0/bits/char_traits.h:395
 (inlined by) std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&) at
/Users/bdraco/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/12.2.0/bits/basic_string.h:641
WARNING Decoded 0x400e64ea: esphome::EntityBase::get_object_id[abi:cxx11]() const at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/core/entity_base.cpp:53
WARNING Decoded 0x400d4b2d: esphome::api::get_default_unique_id(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, esphome::EntityBase*) at
/Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/components/api/api_connection.cpp:256
WARNING Decoded 0x400d5e0e: esphome::api::APIConnection::try_send_camera_info_(esphome::esp32_camera::ESP32Camera*) at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/components/api/api_connection.cpp:1139 (discriminator 1)
WARNING Decoded 0x400d47df: esphome::api::APIConnection::send_info_(esphome::EntityBase*, bool (esphome::api::APIConnection::*)(void*)) at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/components/api/api_connection.h:478
(discriminator 6)
WARNING Decoded 0x400d484a: esphome::api::APIConnection::send_camera_info(esphome::esp32_camera::ESP32Camera*) at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/components/api/api_connection.cpp:1134 (discriminator 4)
WARNING Decoded 0x400da2cd: esphome::api::ListEntitiesIterator::on_camera(esphome::esp32_camera::ESP32Camera*) at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/components/api/list_entities.cpp:81
WARNING Decoded 0x400e6319: esphome::ComponentIterator::advance() at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/core/component_iterator.cpp:170
WARNING Decoded 0x400d5134: esphome::api::APIConnection::loop() at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/components/api/api_connection.cpp:163
WARNING Decoded 0x400da244: esphome::api::APIServer::loop() at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/components/api/api_server.cpp:148
WARNING Decoded 0x401669e5: esphome::Component::call_loop() at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/core/component.cpp:80
WARNING Decoded 0x40166a9d: esphome::Component::call() at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/core/component.cpp:107
WARNING Decoded 0x400e4c1f: esphome::Application::loop() at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/core/application.cpp:75
WARNING Decoded 0x400e7446: loop() at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/cam-utilities-indoor.yaml:54
WARNING Decoded 0x400da78a: esphome::loop_task(void*) at /Users/bdraco/esphome/.esphome/build/cam-utilities-indoor/src/esphome/components/esp32/core.cpp:79 (discriminator 1)
```
2025-05-15 15:24:29 -05:00
J. Nick Koston
4bff8ef969 Revert "Bail quickly if there is no data to read"
This reverts commit e544f6711e.
2025-05-15 13:43:28 -05:00
J. Nick Koston
9ff93142fd Revert "FIONREAD"
This reverts commit 2201f67045.
2025-05-15 13:42:49 -05:00
J. Nick Koston
a4c5384b30 Revert "preen"
This reverts commit b5af2cb4ee.
2025-05-15 13:42:48 -05:00
J. Nick Koston
b5af2cb4ee preen 2025-05-15 13:32:12 -05:00
J. Nick Koston
29b3d7355c Merge branch 'bail_no_data' into frame_helper_optimize_cleanup_api 2025-05-15 13:27:19 -05:00
J. Nick Koston
2201f67045 FIONREAD 2025-05-15 13:26:59 -05:00
J. Nick Koston
7a364ff63a cleanup 2025-05-15 12:54:34 -05:00
J. Nick Koston
e544f6711e Bail quickly if there is no data to read 2025-05-15 12:54:34 -05:00
J. Nick Koston
31f5bbf623 cleanup 2025-05-15 12:52:25 -05:00
J. Nick Koston
488dc40f2e Bail quickly if there is no data to read 2025-05-15 12:48:33 -05:00
J. Nick Koston
e8e0e34702 Merge branch 'multi_task_logger_esp32' into frame_helper_optimize_cleanup_api 2025-05-15 11:02:49 -05:00
J. Nick Koston
eebdc9c38f Fix ESP32 console logging corruption and message loss in multi-task environments
These changes enhance ESPHome's logging system on ESP32 multi-task environments:

1. **Emergency Console Logging**:
   - Added fallback console logging when the task log buffer is full or disabled
   - Ensures critical messages are still visible even when the ring buffer fails

2. **Improved Console Output**:
   - Messages successfully sent to the ring buffer now also display on the console
   - Ensures consistent console output for all log messages regardless of source

3. **Optimized Resource Usage**:
   - Release ring buffer messages earlier after transferring to tx_buffer
   - Reduces contention for the shared log buffer in multi-task environments

1. **Stack Memory Efficiency**:
   - No longer need to allocate stack memory for console output when ring buffer is available
   - Only uses stack memory for emergency fallback cases, reducing stack usage in normal operation

2. **Console Output Integrity**:
   - Prevents console output corruption that could occur with concurrent writes from multiple tasks
   - Serializes all console output through the main loop when possible

3. **Message Ordering**:
   - Messages from different tasks may appear slightly out of order due to async delivery to main loop
   - This trade-off is preferable to corrupted console output from concurrent writes

These improvements provide more reliable logging behavior, particularly under memory constraints or high logging volume, while maintaining thread safety and minimizing resource contention.
2025-05-15 10:52:58 -05:00
J. Nick Koston
65b6d256bc Merge branch 'implement_buffer_queue' into frame_helper_optimize_cleanup_api 2025-05-15 03:52:34 -05:00
J. Nick Koston
428371d685 preen 2025-05-15 03:51:08 -05:00
J. Nick Koston
d108219947 dry 2025-05-15 03:49:51 -05:00
J. Nick Koston
cdf3ed07ba dry 2025-05-15 03:47:10 -05:00
J. Nick Koston
061bbabd09 Merge branch 'implement_buffer_queue' into frame_helper_optimize_cleanup_api 2025-05-15 03:25:02 -05:00
J. Nick Koston
2646ec166b save some bytes 2025-05-15 03:24:47 -05:00
J. Nick Koston
bafc57f02e fix refactoring error 2025-05-15 03:06:52 -05:00
J. Nick Koston
549ed6178b fix refactoring error 2025-05-15 03:06:36 -05:00
J. Nick Koston
0c67e06573 Merge branch 'implement_buffer_queue' into frame_helper_optimize_cleanup_api 2025-05-15 03:02:16 -05:00
J. Nick Koston
592a95c565 dry 2025-05-15 02:58:15 -05:00
J. Nick Koston
f41ef68b41 dry 2025-05-15 02:55:00 -05:00
J. Nick Koston
26af1cf650 preen 2025-05-15 02:32:19 -05:00
J. Nick Koston
66b995cffe cleanups 2025-05-15 02:13:20 -05:00
J. Nick Koston
5d0b74db3d Merge branch 'implement_buffer_queue' into frame_helper_optimize_cleanup_api 2025-05-15 01:54:52 -05:00
J. Nick Koston
31e3065600 fixes 2025-05-15 01:49:38 -05:00
J. Nick Koston
872a70d235 fixes 2025-05-15 01:48:43 -05:00
J. Nick Koston
7b84eb2903 fixes 2025-05-15 01:47:18 -05:00
J. Nick Koston
7b4e7108c0 cleanup 2025-05-15 01:45:24 -05:00
J. Nick Koston
d4b42ebf20 Eliminate outbound buffer expensive O(n) with O(1) queue operations
- Replaced inefficient vector-based buffer with a queue of discrete message buffers

- Moved common code to base class to reduce duplication

- Removed unnecessary data copying after partial sends

- Added small-buffer optimization to allow writing with backlog <256 bytes

- Moved common code to base class to reduce duplication
2025-05-15 01:31:43 -05:00
J. Nick Koston
a8e9c79975 Eliminate outbound buffer expensive O(n) with O(1) queue operations
- Replaced inefficient vector-based buffer with a queue of discrete message buffers

- Moved common code to base class to reduce duplication

- Removed unnecessary data copying after partial sends

- Added small-buffer optimization to allow writing with backlog <256 bytes

- Moved common code to base class to reduce duplication
2025-05-15 01:31:22 -05:00
J. Nick Koston
47b9c3db1d adjust 2025-05-14 23:09:18 -05:00
J. Nick Koston
b32ed848b0 Merge remote-tracking branch 'upstream/dev' into frame_helper_optimize_cleanup_api 2025-05-14 23:08:09 -05:00
J. Nick Koston
8fb68804e4 Merge branch 'cleanup_api' into frame_helper_optimize_cleanup_api 2025-05-14 17:28:29 -05:00
J. Nick Koston
e2453dd513 optimize 2025-05-14 17:27:05 -05:00
J. Nick Koston
ed563b0c83 Merge branch 'cleanup_api' into frame_helper_optimize_cleanup_api 2025-05-14 17:20:38 -05:00
J. Nick Koston
856d679ce2 Merge branch 'plaintext' into frame_helper_optimize 2025-05-14 16:58:59 -05:00
J. Nick Koston
f5ac77634b Use fixed buffer for plaintext protocol like noise protocol
This PR optimizes memory usage in the APIPlaintextFrameHelper by replacing the vector-based header buffer with a fixed-size buffer, similar to what's already done in the noise protocol implementation.

- Replaced `std::vector<uint8_t> rx_header_buf_` with a 5-byte fixed buffer `uint8_t rx_header_buf_[5]`
- Implemented a modified protocol parsing algorithm that validates the indicator byte without storing it
- Ensured compatibility with noise protocol by supporting message sizes up to at least 65535 (requires 3-byte varint)
- Improved readability with clearer conditional logic and better comments
- Added detailed notes about protocol limitations and variable length encoding

- Reduces memory fragmentation by eliminating dynamic allocations during header parsing
2025-05-14 16:48:12 -05:00
J. Nick Koston
b30d7fb0eb Use fixed buffer for plaintext protocol like noise protocol
This PR optimizes memory usage in the APIPlaintextFrameHelper by replacing the vector-based header buffer with a fixed-size buffer, similar to what's already done in the noise protocol implementation.

- Replaced `std::vector<uint8_t> rx_header_buf_` with a 5-byte fixed buffer `uint8_t rx_header_buf_[5]`
- Implemented a modified protocol parsing algorithm that validates the indicator byte without storing it
- Ensured compatibility with noise protocol by supporting message sizes up to at least 65535 (requires 3-byte varint)
- Improved readability with clearer conditional logic and better comments
- Added detailed notes about protocol limitations and variable length encoding

- Reduces memory fragmentation by eliminating dynamic allocations during header parsing
2025-05-14 16:47:26 -05:00
J. Nick Koston
fc609f02f3 reset 2025-05-14 15:19:07 -05:00
J. Nick Koston
3fbbec81af frame helper opt 2025-05-14 14:26:05 -05:00
J. Nick Koston
7934618c9c debug 2025-05-14 12:40:40 -05:00
J. Nick Koston
c4aee545c3 cleanup 2025-05-14 12:38:07 -05:00
J. Nick Koston
e244b71802 Merge remote-tracking branch 'upstream/cleanup_api' into cleanup_api 2025-05-13 23:49:05 -05:00
J. Nick Koston
2f078d4edf no need to loop twice 2025-05-13 23:48:48 -05:00
J. Nick Koston
4215cc5e6a Update esphome/components/api/api_connection.h 2025-05-13 23:20:12 -05:00
J. Nick Koston
b3911ef37c lint 2025-05-13 23:19:25 -05:00
J. Nick Koston
0d1dae175c Merge remote-tracking branch 'upstream/cleanup_api' into cleanup_api 2025-05-13 23:10:43 -05:00
J. Nick Koston
6e95ef06e0 preen 2025-05-13 23:10:29 -05:00
J. Nick Koston
d7311b048b Merge branch 'dev' into cleanup_api 2025-05-13 23:01:55 -05:00
J. Nick Koston
84a84e769b balance 2025-05-13 22:59:52 -05:00
J. Nick Koston
0db37ddf0a balance 2025-05-13 22:59:18 -05:00
J. Nick Koston
71577cf6d4 reduce 2025-05-13 22:37:00 -05:00
J. Nick Koston
8c0546b535 reduce 2025-05-13 22:32:40 -05:00
J. Nick Koston
9bf527b0b6 reduce 2025-05-13 22:28:51 -05:00
J. Nick Koston
c7501911bf reduce 2025-05-13 22:26:47 -05:00
J. Nick Koston
4b82ed5b81 revert 2025-05-13 22:12:12 -05:00
J. Nick Koston
1f8ae120d4 revert 2025-05-13 22:10:45 -05:00
J. Nick Koston
8769ddcfa9 revert 2025-05-13 22:04:23 -05:00
J. Nick Koston
3987b98044 remove 2025-05-13 22:03:17 -05:00
J. Nick Koston
0edfa4746a revert 2025-05-13 22:00:48 -05:00
J. Nick Koston
c8dcebfb3f cleanup 2025-05-13 21:22:30 -05:00
J. Nick Koston
edd755323c true 2025-05-13 21:11:19 -05:00
J. Nick Koston
4096c943cc cleanup 2025-05-13 21:08:59 -05:00
J. Nick Koston
e4caef77fc cleanup 2025-05-13 21:07:55 -05:00
J. Nick Koston
0d81306481 cleanup 2025-05-13 21:07:10 -05:00
J. Nick Koston
39abbe609a cleanup 2025-05-13 21:05:26 -05:00
J. Nick Koston
326df5752f cleanup 2025-05-13 21:04:46 -05:00
J. Nick Koston
7cf1db1382 cleanup 2025-05-13 21:04:07 -05:00
J. Nick Koston
dcdc2a30c5 fixes 2025-05-13 20:50:49 -05:00
J. Nick Koston
c0b9f6407c reduce diff 2025-05-13 20:45:09 -05:00
J. Nick Koston
85457eeed0 preen 2025-05-13 20:43:03 -05:00
J. Nick Koston
3fb10037a9 Refactor api_connection to avoid constructing protobuf messages if the tx_buffer is full
Also do not try to dequeue if the buffer is full
2025-05-13 20:36:57 -05:00
J. Nick Koston
45e08ed584 Merge branch 'improve_ble_batching' into loop_runtime_stats_ble_batching 2025-05-13 11:41:30 -05:00
J. Nick Koston
a7449dce92 Improve batching of BLE advertisements for better airtime efficiency 2025-05-13 11:31:48 -05:00
J. Nick Koston
8067caf16f preen 2025-05-13 11:30:26 -05:00
J. Nick Koston
5fbb066ee7 preen 2025-05-13 11:30:06 -05:00
J. Nick Koston
c9680a1ccb preen 2025-05-13 11:29:22 -05:00
J. Nick Koston
03399e6dd6 preen 2025-05-13 11:22:33 -05:00
J. Nick Koston
7f838ece00 preen 2025-05-13 11:22:05 -05:00
J. Nick Koston
3f87010c0e preen 2025-05-13 11:21:32 -05:00
J. Nick Koston
a960d9966d preen 2025-05-13 03:55:08 -05:00
J. Nick Koston
02c390c6c3 tweak 2025-05-13 03:51:22 -05:00
J. Nick Koston
eebefdf026 preen 2025-05-13 03:38:42 -05:00
J. Nick Koston
cb748bbb02 preen 2025-05-13 03:32:57 -05:00
J. Nick Koston
c35db19995 preen 2025-05-13 03:30:19 -05:00
J. Nick Koston
71b493bd8b its too much 2025-05-13 03:25:49 -05:00
J. Nick Koston
f67e02c653 its too much 2025-05-13 03:24:48 -05:00
J. Nick Koston
9db52b17f2 its too much 2025-05-13 03:24:36 -05:00
J. Nick Koston
d728382542 its too much 2025-05-13 03:24:07 -05:00
J. Nick Koston
d95bbfc6c4 its too much 2025-05-13 03:00:38 -05:00
J. Nick Koston
83db3eddd9 revert ota 2025-05-13 01:07:43 -05:00
J. Nick Koston
cc2c5a544e revert ota 2025-05-13 01:07:38 -05:00
J. Nick Koston
8fba8c2800 revert ota 2025-05-13 01:05:37 -05:00
J. Nick Koston
51d1da8460 revert ota 2025-05-13 01:04:09 -05:00
J. Nick Koston
2f1257056d revert 2025-05-13 01:02:00 -05:00
J. Nick Koston
2f8f6967bf fix ota 2025-05-13 00:55:19 -05:00
J. Nick Koston
246527e618 runtime stats 2025-05-13 00:54:05 -05:00
J. Nick Koston
3857cc9c83 runtime stats 2025-05-13 00:51:14 -05:00
646 changed files with 2546 additions and 10824 deletions

View File

@@ -1,4 +1,2 @@
[run]
omit =
esphome/components/*
tests/integration/*
omit = esphome/components/*

View File

@@ -1,37 +0,0 @@
ARG BUILD_BASE_VERSION=2025.04.0
FROM ghcr.io/esphome/docker-base:debian-${BUILD_BASE_VERSION} AS base
RUN git config --system --add safe.directory "*"
RUN apt update \
&& apt install -y \
protobuf-compiler
RUN pip install uv
RUN useradd esphome -m
USER esphome
ENV VIRTUAL_ENV=/home/esphome/.local/esphome-venv
RUN uv venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Override this set to true in the docker-base image
ENV UV_SYSTEM_PYTHON=false
WORKDIR /tmp
COPY requirements.txt ./
RUN uv pip install -r requirements.txt
COPY requirements_dev.txt requirements_test.txt ./
RUN uv pip install -r requirements_dev.txt -r requirements_test.txt
RUN \
platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000
COPY script/platformio_install_deps.py platformio.ini ./
RUN ./platformio_install_deps.py platformio.ini --libraries --platforms --tools
WORKDIR /workspaces

View File

@@ -1,17 +1,18 @@
{
"name": "ESPHome Dev",
"context": "..",
"dockerFile": "Dockerfile",
"image": "ghcr.io/esphome/esphome-lint:dev",
"postCreateCommand": [
"script/devcontainer-post-create"
],
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {}
"containerEnv": {
"DEVCONTAINER": "1",
"PIP_BREAK_SYSTEM_PACKAGES": "1",
"PIP_ROOT_USER_ACTION": "ignore"
},
"runArgs": [
"--privileged",
"-e",
"GIT_EDITOR=code --wait"
"ESPHOME_DASHBOARD_USE_PING=1"
// uncomment and edit the path in order to pass though local USB serial to the conatiner
// , "--device=/dev/ttyACM0"
],

View File

@@ -47,7 +47,7 @@ runs:
- name: Build and push to ghcr by digest
id: build-ghcr
uses: docker/build-push-action@v6.18.0
uses: docker/build-push-action@v6.16.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
@@ -73,7 +73,7 @@ runs:
- name: Build and push to dockerhub by digest
id: build-dockerhub
uses: docker/build-push-action@v6.18.0
uses: docker/build-push-action@v6.16.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:

View File

@@ -43,11 +43,11 @@ jobs:
- "docker"
# - "lint"
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:
python-version: "3.10"
python-version: "3.9"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.10.0

View File

@@ -20,8 +20,8 @@ permissions:
contents: read
env:
DEFAULT_PYTHON: "3.10"
PYUPGRADE_TARGET: "--py310-plus"
DEFAULT_PYTHON: "3.9"
PYUPGRADE_TARGET: "--py39-plus"
concurrency:
# yamllint disable-line rule:line-length
@@ -36,7 +36,7 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
@@ -68,7 +68,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -89,7 +89,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -110,7 +110,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -131,7 +131,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -152,7 +152,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -173,10 +173,10 @@ jobs:
fail-fast: false
matrix:
python-version:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
os:
- ubuntu-latest
- macOS-latest
@@ -185,24 +185,24 @@ jobs:
# Minimize CI resource usage
# by only running the Python version
# version used for docker images on Windows and macOS
- python-version: "3.13"
os: windows-latest
- python-version: "3.12"
os: windows-latest
- python-version: "3.10"
os: windows-latest
- python-version: "3.13"
os: macOS-latest
- python-version: "3.9"
os: windows-latest
- python-version: "3.12"
os: macOS-latest
- python-version: "3.10"
os: macOS-latest
- python-version: "3.9"
os: macOS-latest
runs-on: ${{ matrix.os }}
needs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -214,14 +214,14 @@ jobs:
if: matrix.os == 'windows-latest'
run: |
./venv/Scripts/activate
pytest -vv --cov-report=xml --tb=native -n auto tests
pytest -vv --cov-report=xml --tb=native tests
- name: Run pytest
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
run: |
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests
pytest -vv --cov-report=xml --tb=native tests
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.4.3
uses: codecov/codecov-action@v5.4.2
with:
token: ${{ secrets.CODECOV_TOKEN }}
@@ -232,7 +232,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -296,11 +296,11 @@ jobs:
name: Run script/clang-tidy for ZEPHYR
options: --environment nrf52-tidy --grep USE_ZEPHYR
pio_cache_key: tidy-zephyr
ignore_errors: false
ignore_errors: true
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -356,7 +356,7 @@ jobs:
count: ${{ steps.list-components.outputs.count }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
with:
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
fetch-depth: 500
@@ -406,7 +406,7 @@ jobs:
sudo apt-get install libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -432,7 +432,7 @@ jobs:
matrix: ${{ steps.split.outputs.components }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Split components into 20 groups
id: split
run: |
@@ -462,7 +462,7 @@ jobs:
sudo apt-get install libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:

View File

@@ -18,9 +18,8 @@ jobs:
outputs:
tag: ${{ steps.tag.outputs.tag }}
branch_build: ${{ steps.tag.outputs.branch_build }}
deploy_env: ${{ steps.tag.outputs.deploy_env }}
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v4.1.7
- name: Get tag
id: tag
# yamllint disable rule:line-length
@@ -28,11 +27,6 @@ jobs:
if [[ "${{ github.event_name }}" = "release" ]]; then
TAG="${{ github.event.release.tag_name}}"
BRANCH_BUILD="false"
if [[ "${{ github.event.release.prerelease }}" = "true" ]]; then
ENVIRONMENT="beta"
else
ENVIRONMENT="production"
fi
else
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
today="$(date --utc '+%Y%m%d')"
@@ -41,15 +35,12 @@ jobs:
if [[ "$BRANCH" != "dev" ]]; then
TAG="${TAG}-${BRANCH}"
BRANCH_BUILD="true"
ENVIRONMENT=""
else
BRANCH_BUILD="false"
ENVIRONMENT="dev"
fi
fi
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "branch_build=${BRANCH_BUILD}" >> $GITHUB_OUTPUT
echo "deploy_env=${ENVIRONMENT}" >> $GITHUB_OUTPUT
# yamllint enable rule:line-length
deploy-pypi:
@@ -60,7 +51,7 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:
@@ -92,11 +83,11 @@ jobs:
os: "ubuntu-24.04-arm"
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:
python-version: "3.10"
python-version: "3.9"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.10.0
@@ -168,7 +159,7 @@ jobs:
- ghcr
- dockerhub
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v4.1.7
- name: Download digests
uses: actions/download-artifact@v4.3.0
@@ -242,8 +233,9 @@ jobs:
deploy-esphome-schema:
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
runs-on: ubuntu-latest
needs: [init]
environment: ${{ needs.init.outputs.deploy_env }}
needs:
- init
- deploy-manifest
steps:
- name: Trigger Workflow
uses: actions/github-script@v7.0.1

View File

@@ -13,10 +13,10 @@ jobs:
if: github.repository == 'esphome/esphome'
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Checkout Home Assistant
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
with:
repository: home-assistant/core
path: lib/home-assistant
@@ -24,7 +24,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5.6.0
with:
python-version: 3.13
python-version: 3.12
- name: Install Home Assistant
run: |

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Run yamllint
uses: frenck/action-yamllint@v1.5.0
with:

1
.gitignore vendored
View File

@@ -143,4 +143,3 @@ sdkconfig.*
/components
/managed_components
api-docs/

View File

@@ -4,7 +4,7 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.11.10
rev: v0.11.9
hooks:
# Run the linter.
- id: ruff
@@ -28,10 +28,10 @@ repos:
- --branch=release
- --branch=beta
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
rev: v3.15.2
hooks:
- id: pyupgrade
args: [--py310-plus]
args: [--py39-plus]
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.37.1
hooks:

View File

@@ -96,7 +96,6 @@ esphome/components/ch422g/* @clydebarrow @jesterret
esphome/components/chsc6x/* @kkosik20
esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/cm1106/* @andrewjswan
esphome/components/color_temperature/* @jesserockz
esphome/components/combination/* @Cat-Ion @kahrendt
esphome/components/const/* @esphome/core
@@ -139,14 +138,12 @@ esphome/components/es7210/* @kahrendt
esphome/components/es7243e/* @kbx81
esphome/components/es8156/* @kbx81
esphome/components/es8311/* @kahrendt @kroimon
esphome/components/es8388/* @P4uLT
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @Rapsssito @jesserockz
esphome/components/esp32_ble_client/* @jesserockz
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_hosted/* @swoboda1337
esphome/components/esp32_improv/* @jesserockz
esphome/components/esp32_rmt/* @jesserockz
esphome/components/esp32_rmt_led_strip/* @jesserockz
@@ -236,7 +233,6 @@ esphome/components/kamstrup_kmp/* @cfeenstra1024
esphome/components/key_collector/* @ssieb
esphome/components/key_provider/* @ssieb
esphome/components/kuntze/* @ssieb
esphome/components/lc709203f/* @ilikecake
esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ld2420/* @descipher
@@ -482,8 +478,6 @@ esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter
esphome/components/update/* @jesserockz
esphome/components/uponor_smatrix/* @kroimon
esphome/components/usb_host/* @clydebarrow
esphome/components/usb_uart/* @clydebarrow
esphome/components/valve/* @esphome/core
esphome/components/vbus/* @ssieb
esphome/components/veml3235/* @kbx81

2877
Doxyfile

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,7 @@ FROM base-source-${BUILD_TYPE} AS base
RUN git config --system --add safe.directory "*"
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
RUN pip install --no-cache-dir -U pip uv==0.6.14
RUN pip install uv==0.6.14
COPY requirements.txt /

View File

@@ -593,20 +593,15 @@ def command_update_all(args):
middle_text = f" {middle_text} "
width = len(click.unstyle(middle_text))
half_line = "=" * ((twidth - width) // 2)
safe_print(f"{half_line}{middle_text}{half_line}")
click.echo(f"{half_line}{middle_text}{half_line}")
for f in files:
safe_print(f"Updating {color(AnsiFore.CYAN, f)}")
safe_print("-" * twidth)
safe_print()
if CORE.dashboard:
rc = run_external_process(
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
)
else:
rc = run_external_process(
"esphome", "run", f, "--no-logs", "--device", "OTA"
)
print(f"Updating {color(AnsiFore.CYAN, f)}")
print("-" * twidth)
print()
rc = run_external_process(
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
)
if rc == 0:
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
success[f] = True
@@ -614,17 +609,17 @@ def command_update_all(args):
print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
success[f] = False
safe_print()
safe_print()
safe_print()
print()
print()
print()
print_bar(f"[{color(AnsiFore.BOLD_WHITE, 'SUMMARY')}]")
failed = 0
for f in files:
if success[f]:
safe_print(f" - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
print(f" - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
else:
safe_print(f" - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
print(f" - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
failed += 1
return failed

View File

@@ -7,7 +7,7 @@ namespace a4988 {
static const char *const TAG = "a4988.stepper";
void A4988::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up A4988...");
if (this->sleep_pin_ != nullptr) {
this->sleep_pin_->setup();
this->sleep_pin_->digital_write(false);

View File

@@ -7,7 +7,7 @@ namespace absolute_humidity {
static const char *const TAG = "absolute_humidity.sensor";
void AbsoluteHumidityComponent::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, "Setting up absolute humidity '%s'...", this->get_name().c_str());
ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });

View File

@@ -22,7 +22,7 @@ static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
if (this->channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);

View File

@@ -17,7 +17,7 @@ namespace adc {
static const char *const TAG = "adc.esp8266";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif

View File

@@ -9,7 +9,7 @@ namespace adc {
static const char *const TAG = "adc.libretiny";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif // !USE_ADC_SENSOR_VCC

View File

@@ -14,7 +14,7 @@ namespace adc {
static const char *const TAG = "adc.rp2040";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
static bool initialized = false;
if (!initialized) {
adc_init();

View File

@@ -9,7 +9,7 @@ static const char *const TAG = "adc128s102";
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
void ADC128S102::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up adc128s102");
this->spi_setup();
}

View File

@@ -10,13 +10,15 @@ static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
void ADS1115Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
uint16_t value;
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
this->mark_failed();
return;
}
ESP_LOGCONFIG(TAG, "Configuring ADS1115...");
uint16_t config = 0;
// Clear single-shot bit
// 0b0xxxxxxxxxxxxxxx
@@ -66,10 +68,10 @@ void ADS1115Component::setup() {
this->prev_config_ = config;
}
void ADS1115Component::dump_config() {
ESP_LOGCONFIG(TAG, "ADS1115:");
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with ADS1115 failed!");
}
}
float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain,

View File

@@ -8,7 +8,7 @@ static const char *const TAG = "ads1118";
static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111;
void ADS1118::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up ads1118");
this->spi_setup();
this->config_ = 0;

View File

@@ -23,7 +23,7 @@ static const uint16_t ZP_CURRENT = 0x0000;
static const uint16_t ZP_DEFAULT = 0xFFFF;
void AGS10Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up ags10...");
auto version = this->read_version_();
if (version) {
@@ -65,7 +65,7 @@ void AGS10Component::dump_config() {
case NONE:
break;
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AGS10 failed!");
break;
case CRC_CHECK_FAILED:
ESP_LOGE(TAG, "The crc check failed");

View File

@@ -15,7 +15,6 @@
#include "aht10.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace aht10 {
@@ -35,59 +34,57 @@ static const uint8_t AHT10_INIT_ATTEMPTS = 10;
static const uint8_t AHT10_STATUS_BUSY = 0x80;
static const float AHT10_DIVISOR = 1048576.0f; // 2^20, used for temperature and humidity calculations
void AHT10Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Reset failed");
ESP_LOGE(TAG, "Reset AHT10 failed!");
}
delay(AHT10_SOFTRESET_DELAY);
i2c::ErrorCode error_code = i2c::ERROR_INVALID_ARGUMENT;
switch (this->variant_) {
case AHT10Variant::AHT20:
ESP_LOGCONFIG(TAG, "Setting up AHT20");
error_code = this->write(AHT20_INITIALIZE_CMD, sizeof(AHT20_INITIALIZE_CMD));
break;
case AHT10Variant::AHT10:
ESP_LOGCONFIG(TAG, "Setting up AHT10");
error_code = this->write(AHT10_INITIALIZE_CMD, sizeof(AHT10_INITIALIZE_CMD));
break;
}
if (error_code != i2c::ERROR_OK) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AHT10 failed!");
this->mark_failed();
return;
}
uint8_t cal_attempts = 0;
uint8_t data = AHT10_STATUS_BUSY;
int cal_attempts = 0;
while (data & AHT10_STATUS_BUSY) {
delay(AHT10_DEFAULT_DELAY);
if (this->read(&data, 1) != i2c::ERROR_OK) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AHT10 failed!");
this->mark_failed();
return;
}
++cal_attempts;
if (cal_attempts > AHT10_INIT_ATTEMPTS) {
ESP_LOGE(TAG, "Initialization timed out");
ESP_LOGE(TAG, "AHT10 initialization timed out!");
this->mark_failed();
return;
}
}
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED
ESP_LOGE(TAG, "Initialization failed");
ESP_LOGE(TAG, "AHT10 initialization failed!");
this->mark_failed();
return;
}
ESP_LOGV(TAG, "Initialization complete");
ESP_LOGV(TAG, "AHT10 initialization");
}
void AHT10Component::restart_read_() {
if (this->read_count_ == AHT10_ATTEMPTS) {
this->read_count_ = 0;
this->status_set_error("Reading timed out");
this->status_set_error("Measurements reading timed-out!");
return;
}
this->read_count_++;
@@ -100,24 +97,24 @@ void AHT10Component::read_data_() {
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
}
if (this->read(data, 6) != i2c::ERROR_OK) {
this->status_set_warning("Read failed, will retry");
this->status_set_warning("AHT10 read failed, retrying soon");
this->restart_read_();
return;
}
if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
ESP_LOGD(TAG, "Device busy, will retry");
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
this->restart_read_();
return;
}
if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
// Invalid humidity (0x0)
// Unrealistic humidity (0x0)
if (this->humidity_sensor_ == nullptr) {
ESP_LOGV(TAG, "Invalid humidity (reading not required)");
ESP_LOGV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required");
} else {
ESP_LOGD(TAG, "Invalid humidity, retrying");
ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying...");
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
this->status_set_warning("Communication with AHT10 failed!");
}
this->restart_read_();
return;
@@ -126,17 +123,22 @@ void AHT10Component::read_data_() {
if (this->read_count_ > 1) {
ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_));
}
uint32_t raw_temperature = encode_uint24(data[3] & 0xF, data[4], data[5]);
uint32_t raw_humidity = encode_uint24(data[1], data[2], data[3]) >> 4;
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
if (this->temperature_sensor_ != nullptr) {
float temperature = ((200.0f * static_cast<float>(raw_temperature)) / AHT10_DIVISOR) - 50.0f;
float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
this->temperature_sensor_->publish_state(temperature);
}
if (this->humidity_sensor_ != nullptr) {
float humidity = raw_humidity == 0 ? NAN : static_cast<float>(raw_humidity) * 100.0f / AHT10_DIVISOR;
float humidity;
if (raw_humidity == 0) { // unrealistic value
humidity = NAN;
} else {
humidity = (float) raw_humidity * 100.0f / 1048576.0f;
}
if (std::isnan(humidity)) {
ESP_LOGW(TAG, "Invalid humidity reading (0%%), ");
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
}
this->humidity_sensor_->publish_state(humidity);
}
@@ -148,7 +150,7 @@ void AHT10Component::update() {
return;
this->start_time_ = millis();
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
this->status_set_warning("Communication with AHT10 failed!");
return;
}
this->restart_read_();
@@ -160,7 +162,7 @@ void AHT10Component::dump_config() {
ESP_LOGCONFIG(TAG, "AHT10:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AHT10 failed!");
}
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);

View File

@@ -17,7 +17,7 @@ static const char *const TAG = "aic3204";
}
void AIC3204::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AIC3204...");
// Set register page to 0
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set page 0 failed");
@@ -113,7 +113,7 @@ void AIC3204::dump_config() {
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AIC3204 failed");
}
}

View File

@@ -90,7 +90,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
}
void AM2315C::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AM2315C...");
// get status
uint8_t status = 0;
@@ -188,7 +188,7 @@ void AM2315C::dump_config() {
ESP_LOGCONFIG(TAG, "AM2315C:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AM2315C failed!");
}
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);

View File

@@ -34,7 +34,7 @@ void AM2320Component::update() {
this->status_clear_warning();
}
void AM2320Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AM2320...");
uint8_t data[8];
data[0] = 0;
data[1] = 4;
@@ -47,7 +47,7 @@ void AM2320Component::dump_config() {
ESP_LOGD(TAG, "AM2320:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AM2320 failed!");
}
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);

View File

@@ -54,7 +54,7 @@ enum { // APDS9306 registers
}
void APDS9306::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up APDS9306...");
uint8_t id;
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
@@ -97,7 +97,7 @@ void APDS9306::dump_config() {
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with APDS9306 failed!");
break;
case WRONG_ID:
ESP_LOGE(TAG, "APDS9306 has invalid id!");

View File

@@ -15,7 +15,7 @@ static const char *const TAG = "apds9960";
#define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
void APDS9960::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up APDS9960...");
uint8_t id;
if (!this->read_byte(0x92, &id)) { // ID register
this->error_code_ = COMMUNICATION_FAILED;
@@ -141,7 +141,7 @@ void APDS9960::dump_config() {
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with APDS9960 failed!");
break;
case WRONG_ID:
ESP_LOGE(TAG, "APDS9960 has invalid id!");

View File

@@ -4,7 +4,6 @@
#include <cinttypes>
#include <utility>
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
@@ -27,6 +26,7 @@ namespace esphome {
namespace api {
static const char *const TAG = "api.connection";
static const char *const STATS_TAG = "api.stats";
static const int ESP32_CAMERA_STOP_STREAM = 5000;
// helper for allowing only unique entries in the queue
@@ -63,6 +63,11 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
: parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) {
this->proto_write_buffer_.reserve(64);
// Explicitly initialize stats
this->stats_enabled_ = true;
this->next_stats_log_ = 0;
ESP_LOGD(STATS_TAG, "API Connection created with stats_enabled_=true");
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
auto noise_ctx = parent->get_noise_ctx();
if (noise_ctx->has_psk()) {
@@ -79,11 +84,7 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
#endif
}
void APIConnection::start() {
this->last_traffic_ = App.get_loop_component_start_time();
// Set next_ping_retry_ to prevent immediate ping
// This ensures the first ping happens after the keepalive period
this->next_ping_retry_ = this->last_traffic_ + KEEPALIVE_TIMEOUT_MS;
this->last_traffic_ = millis();
APIError err = this->helper_->init();
if (err != APIError::OK) {
@@ -97,6 +98,64 @@ void APIConnection::start() {
this->helper_->set_log_info(this->client_info_);
}
void APIConnection::log_section_stats_() {
ESP_LOGI(STATS_TAG, "API Connection Section Runtime Statistics");
ESP_LOGI(STATS_TAG, "Period stats (last %" PRIu32 "ms):", this->stats_log_interval_);
if (this->section_stats_.empty()) {
ESP_LOGW(STATS_TAG, "No section stats collected yet");
return;
}
// First collect stats we want to display
std::vector<std::pair<std::string, const APISectionStats *>> stats_to_display;
for (const auto &it : this->section_stats_) {
const APISectionStats &stats = it.second;
if (stats.get_period_count() > 0) {
stats_to_display.push_back({it.first, &stats});
}
}
// Sort by period runtime (descending)
std::sort(stats_to_display.begin(), stats_to_display.end(), [](const auto &a, const auto &b) {
return a.second->get_period_time_ms() > b.second->get_period_time_ms();
});
// Log top components by period runtime
for (const auto &it : stats_to_display) {
const std::string &section = it.first;
const APISectionStats *stats = it.second;
ESP_LOGI(STATS_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", section.c_str(),
stats->get_period_count(), stats->get_period_avg_time_ms(), stats->get_period_max_time_ms(),
stats->get_period_time_ms());
}
// Log total stats since boot
ESP_LOGI(STATS_TAG, "Total stats (since boot):");
// Re-sort by total runtime for all-time stats
std::sort(stats_to_display.begin(), stats_to_display.end(),
[](const auto &a, const auto &b) { return a.second->get_total_time_ms() > b.second->get_total_time_ms(); });
for (const auto &it : stats_to_display) {
const std::string &section = it.first;
const APISectionStats *stats = it.second;
ESP_LOGI(STATS_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", section.c_str(),
stats->get_total_count(), stats->get_total_avg_time_ms(), stats->get_total_max_time_ms(),
stats->get_total_time_ms());
}
}
void APIConnection::reset_section_stats_() {
ESP_LOGD(STATS_TAG, "Resetting API section stats, sections count: %u", this->section_stats_.size());
for (auto &it : this->section_stats_) {
it.second.reset_period_stats();
}
}
APIConnection::~APIConnection() {
#ifdef USE_BLUETOOTH_PROXY
if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
@@ -111,6 +170,9 @@ APIConnection::~APIConnection() {
}
void APIConnection::loop() {
// Measure total time for entire loop function
const uint32_t loop_start_time = millis();
if (this->remove_)
return;
@@ -118,7 +180,7 @@ void APIConnection::loop() {
// when network is disconnected force disconnect immediately
// don't wait for timeout
this->on_fatal_error();
ESP_LOGW(TAG, "%s: Network unavailable; disconnecting", this->client_combined_info_.c_str());
ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", this->client_combined_info_.c_str());
return;
}
if (this->next_close_) {
@@ -128,7 +190,16 @@ void APIConnection::loop() {
return;
}
const uint32_t now = millis();
uint32_t start_time;
uint32_t duration;
// Section: Helper Loop
start_time = millis();
APIError err = this->helper_->loop();
duration = millis() - start_time;
this->section_stats_["helper_loop"].record_time(duration);
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(),
@@ -136,74 +207,93 @@ void APIConnection::loop() {
return;
}
// Check if socket has data ready before attempting to read
if (this->helper_->is_socket_ready()) {
ReadPacketBuffer buffer;
err = this->helper_->read_packet(&buffer);
if (err == APIError::WOULD_BLOCK) {
// pass
} else if (err != APIError::OK) {
on_fatal_error();
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str());
} else if (err == APIError::CONNECTION_CLOSED) {
ESP_LOGW(TAG, "%s: Connection closed", this->client_combined_info_.c_str());
} else {
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
errno);
}
return;
// Section: Read Packet
start_time = millis();
ReadPacketBuffer buffer;
err = this->helper_->read_packet(&buffer);
duration = millis() - start_time;
this->section_stats_["read_packet"].record_time(duration);
if (err == APIError::WOULD_BLOCK) {
// pass
} else if (err != APIError::OK) {
on_fatal_error();
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str());
} else if (err == APIError::CONNECTION_CLOSED) {
ESP_LOGW(TAG, "%s: Connection closed", this->client_combined_info_.c_str());
} else {
this->last_traffic_ = App.get_loop_component_start_time();
// read a packet
if (buffer.data_len > 0) {
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
} else {
this->read_message(0, buffer.type, nullptr);
}
if (this->remove_)
return;
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
errno);
}
return;
} else {
this->last_traffic_ = now;
// Section: Process Message
start_time = millis();
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
duration = millis() - start_time;
this->section_stats_["process_message"].record_time(duration);
if (this->remove_)
return;
}
// Section: Process Queue
start_time = millis();
if (!this->deferred_message_queue_.empty() && this->helper_->can_write_without_blocking()) {
this->deferred_message_queue_.process_queue();
}
duration = millis() - start_time;
this->section_stats_["process_queue"].record_time(duration);
// Section: Iterator Advance
start_time = millis();
if (!this->list_entities_iterator_.completed())
this->list_entities_iterator_.advance();
if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed())
this->initial_state_iterator_.advance();
duration = millis() - start_time;
this->section_stats_["iterator_advance"].record_time(duration);
// Section: Keepalive
start_time = millis();
static uint32_t keepalive = 60000;
static uint8_t max_ping_retries = 60;
static uint16_t ping_retry_interval = 1000;
const uint32_t now = App.get_loop_component_start_time();
if (this->sent_ping_) {
// Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) {
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
on_fatal_error();
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->client_combined_info_.c_str());
ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_combined_info_.c_str());
}
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) {
ESP_LOGVV(TAG, "Sending keepalive PING");
} else if (now - this->last_traffic_ > keepalive && now > this->next_ping_retry_) {
ESP_LOGVV(TAG, "Sending keepalive PING...");
this->sent_ping_ = this->send_ping_request(PingRequest());
if (!this->sent_ping_) {
char warn_str[38];
this->next_ping_retry_ = now + ping_retry_interval;
this->ping_retries_++;
sprintf(warn_str, "Sending keepalive failed %u time(s); ", this->ping_retries_);
if (this->ping_retries_ >= max_ping_retries) {
on_fatal_error();
ESP_LOGE(TAG, "%s: %sdisconnecting", this->client_combined_info_.c_str(), warn_str);
ESP_LOGE(TAG, "%s: Sending keepalive failed %d time(s). Disconnecting...", this->client_combined_info_.c_str(),
this->ping_retries_);
} else if (this->ping_retries_ >= 10) {
ESP_LOGW(TAG, "%s: %sretrying in %u ms", this->client_combined_info_.c_str(), warn_str, ping_retry_interval);
ESP_LOGW(TAG, "%s: Sending keepalive failed %d time(s), will retry in %d ms",
this->client_combined_info_.c_str(), this->ping_retries_, ping_retry_interval);
} else {
ESP_LOGD(TAG, "%s: %sretrying in %u ms", this->client_combined_info_.c_str(), warn_str, ping_retry_interval);
ESP_LOGD(TAG, "%s: Sending keepalive failed %d time(s), will retry in %d ms",
this->client_combined_info_.c_str(), this->ping_retries_, ping_retry_interval);
}
}
}
duration = millis() - start_time;
this->section_stats_["keepalive"].record_time(duration);
#ifdef USE_ESP32_CAMERA
// Section: Camera
start_time = millis();
if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) {
// Message will use 8 more bytes than the minimum size, and typical
// MTU is 1500. Sometimes users will see as low as 1460 MTU.
@@ -242,8 +332,12 @@ void APIConnection::loop() {
this->image_reader_.return_image();
}
}
duration = millis() - start_time;
this->section_stats_["camera"].record_time(duration);
#endif
// Section: State Subscriptions
start_time = millis();
if (state_subs_at_ != -1) {
const auto &subs = this->parent_->get_state_subs();
if (state_subs_at_ >= (int) subs.size()) {
@@ -259,6 +353,43 @@ void APIConnection::loop() {
}
}
}
duration = millis() - start_time;
this->section_stats_["state_subs"].record_time(duration);
// Log stats periodically
if (this->stats_enabled_) {
// If next_stats_log_ is 0, initialize it
if (this->next_stats_log_ == 0) {
this->next_stats_log_ = now + this->stats_log_interval_;
ESP_LOGI(STATS_TAG, "API section stats logging enabled, next log at %u", this->next_stats_log_);
} else if (now >= this->next_stats_log_) {
ESP_LOGI(STATS_TAG, "Logging API section stats now (current time: %u, scheduled time: %u)", now,
this->next_stats_log_);
// Force logging even if no stats are collected yet
ESP_LOGI(STATS_TAG, "Stats collection status: enabled=%d, sections=%u", this->stats_enabled_,
this->section_stats_.size());
// Explicitly log some stats we know should exist
ESP_LOGI(STATS_TAG, "Record count for key sections: helper_loop=%u, read_packet=%u, total_loop=%u",
this->section_stats_["helper_loop"].get_period_count(),
this->section_stats_["read_packet"].get_period_count(),
this->section_stats_["total_loop"].get_period_count());
this->log_section_stats_();
this->reset_section_stats_();
this->next_stats_log_ = now + this->stats_log_interval_;
ESP_LOGI(STATS_TAG, "Next API section stats log scheduled for %u", this->next_stats_log_);
}
}
// Record total loop execution time
const uint32_t total_loop_duration = millis() - loop_start_time;
this->section_stats_["total_loop"].record_time(total_loop_duration);
// Log a warning if the loop takes longer than 30ms
if (total_loop_duration > 30) {
ESP_LOGW(STATS_TAG, "API loop took %ums, which exceeds the recommended 30ms limit", total_loop_duration);
}
}
std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) {
@@ -269,7 +400,7 @@ DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
// remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response
// close will happen on next loop
ESP_LOGD(TAG, "%s disconnected", this->client_combined_info_.c_str());
ESP_LOGD(TAG, "%s requested disconnected", this->client_combined_info_.c_str());
this->next_close_ = true;
DisconnectResponse resp;
return resp;
@@ -1471,7 +1602,7 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
update->check();
break;
case enums::UPDATE_COMMAND_NONE:
ESP_LOGE(TAG, "UPDATE_COMMAND_NONE not handled; confirm command is correct");
ESP_LOGE(TAG, "UPDATE_COMMAND_NONE not handled. Check client is sending the correct command");
break;
default:
ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command);
@@ -1533,7 +1664,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
// bool invalid_password = 1;
resp.invalid_password = !correct;
if (correct) {
ESP_LOGD(TAG, "%s connected", this->client_combined_info_.c_str());
ESP_LOGD(TAG, "%s: Connected successfully", this->client_combined_info_.c_str());
this->connection_state_ = ConnectionState::AUTHENTICATED;
this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_);
#ifdef USE_HOMEASSISTANT_TIME
@@ -1604,7 +1735,7 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
}
}
if (!found) {
ESP_LOGV(TAG, "Could not find service");
ESP_LOGV(TAG, "Could not find matching service!");
}
}
#ifdef USE_API_NOISE
@@ -1651,11 +1782,24 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
return false;
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
if (!this->try_to_clear_buffer(message_type != 29)) { // SubscribeLogsResponse
// Track send_buffer time
const uint32_t start_time = millis();
if (this->remove_)
return false;
uint32_t check_block_start = millis();
if (!this->try_to_clear_buffer(true)) {
return false;
}
uint32_t check_block_duration = millis() - check_block_start;
this->section_stats_["try_to_clear_buffer"].record_time(check_block_duration);
uint32_t write_start = millis();
APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size());
uint32_t write_duration = millis() - write_start;
this->section_stats_["write_packet"].record_time(write_duration);
APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
if (err == APIError::WOULD_BLOCK)
return false;
if (err != APIError::OK) {
@@ -1668,16 +1812,27 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
}
return false;
}
// Measure total send_buffer function time
uint32_t total_duration = millis() - start_time;
this->section_stats_["send_buffer_total"].record_time(total_duration);
// Log a warning if send_buffer takes longer than 15ms
if (total_duration > 15) {
ESP_LOGW(STATS_TAG, "send_buffer took %ums (message_type=%u, size=%u)", total_duration, message_type,
buffer.get_buffer()->size());
}
// Do not set last_traffic_ on send
return true;
}
void APIConnection::on_unauthenticated_access() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s requested access without authentication", this->client_combined_info_.c_str());
ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_combined_info_.c_str());
}
void APIConnection::on_no_setup_connection() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s requested access without full connection", this->client_combined_info_.c_str());
ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_combined_info_.c_str());
}
void APIConnection::on_fatal_error() {
this->helper_->close();

View File

@@ -11,13 +11,14 @@
#include "esphome/core/entity_base.h"
#include <vector>
#include <map>
#include <string>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
// Keepalive timeout in milliseconds
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
using send_message_t = bool (APIConnection::*)(void *);
/*
@@ -64,6 +65,70 @@ class APIConnection : public APIServerConnection {
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
virtual ~APIConnection();
// Performance statistics class for API loop sections
class APISectionStats {
public:
APISectionStats()
: period_count_(0),
total_count_(0),
period_time_ms_(0),
total_time_ms_(0),
period_max_time_ms_(0),
total_max_time_ms_(0) {}
void record_time(uint32_t duration_ms) {
// Update period counters
this->period_count_++;
this->period_time_ms_ += duration_ms;
if (duration_ms > this->period_max_time_ms_)
this->period_max_time_ms_ = duration_ms;
// Update total counters
this->total_count_++;
this->total_time_ms_ += duration_ms;
if (duration_ms > this->total_max_time_ms_)
this->total_max_time_ms_ = duration_ms;
// Log if this is the first record in this period
if (this->period_count_ == 1) {
ESP_LOGV("api.stats", "First time recording stats for this section: %u ms", duration_ms);
}
}
void reset_period_stats() {
this->period_count_ = 0;
this->period_time_ms_ = 0;
this->period_max_time_ms_ = 0;
}
// Period stats (reset each logging interval)
uint32_t get_period_count() const { return this->period_count_; }
uint32_t get_period_time_ms() const { return this->period_time_ms_; }
uint32_t get_period_max_time_ms() const { return this->period_max_time_ms_; }
float get_period_avg_time_ms() const {
return this->period_count_ > 0 ? this->period_time_ms_ / static_cast<float>(this->period_count_) : 0.0f;
}
// Total stats (persistent until reboot)
uint32_t get_total_count() const { return this->total_count_; }
uint32_t get_total_time_ms() const { return this->total_time_ms_; }
uint32_t get_total_max_time_ms() const { return this->total_max_time_ms_; }
float get_total_avg_time_ms() const {
return this->total_count_ > 0 ? this->total_time_ms_ / static_cast<float>(this->total_count_) : 0.0f;
}
protected:
// Period stats (reset each logging interval)
uint32_t period_count_;
uint32_t period_time_ms_;
uint32_t period_max_time_ms_;
// Total stats (persistent until reboot)
uint32_t total_count_;
uint32_t total_time_ms_;
uint32_t total_max_time_ms_;
};
void start();
void loop();
@@ -408,14 +473,7 @@ class APIConnection : public APIServerConnection {
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
// FIXME: ensure no recursive writes can happen
this->proto_write_buffer_.clear();
// Get header padding size - used for both reserve and insert
uint8_t header_padding = this->helper_->frame_header_padding();
// Reserve space for header padding + message + footer
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
this->proto_write_buffer_.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
// Insert header padding bytes so message encoding starts at the correct position
this->proto_write_buffer_.insert(this->proto_write_buffer_.begin(), header_padding, 0);
this->proto_write_buffer_.reserve(reserve_size);
return {&this->proto_write_buffer_};
}
bool try_to_clear_buffer(bool log_out_of_space);
@@ -556,6 +614,17 @@ class APIConnection : public APIServerConnection {
InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_;
int state_subs_at_ = -1;
// API loop section performance statistics
std::map<std::string, APISectionStats> section_stats_;
uint32_t stats_log_interval_{60000}; // 60 seconds default
uint32_t next_stats_log_{0};
bool stats_enabled_{true};
void log_section_stats_();
void reset_section_stats_();
// Method to enable/disable section stats
void set_stats_enabled(bool enabled) { this->stats_enabled_ = enabled; }
};
} // namespace api

View File

@@ -7,7 +7,6 @@
#include "proto.h"
#include "api_pb2_size.h"
#include <cstring>
#include <cinttypes>
namespace esphome {
namespace api {
@@ -67,7 +66,7 @@ const char *api_error_to_str(APIError err) {
}
// Helper method to buffer data from IOVs
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, size_t total_write_len) {
SendBuffer buffer;
buffer.data.reserve(total_write_len);
for (int i = 0; i < iovcnt; i++) {
@@ -85,13 +84,13 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0)
return APIError::OK; // Nothing to do, success
uint16_t total_write_len = 0;
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += static_cast<uint16_t>(iov[i].iov_len);
total_write_len += iov[i].iov_len;
}
// Try to send any existing buffered data first if there is any
@@ -123,22 +122,22 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno);
this->state_ = State::FAILED;
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
} else if (static_cast<uint16_t>(sent) < total_write_len) {
} else if (static_cast<size_t>(sent) < total_write_len) {
// Partially sent, buffer the remaining data
SendBuffer buffer;
uint16_t to_consume = static_cast<uint16_t>(sent);
uint16_t remaining = total_write_len - static_cast<uint16_t>(sent);
size_t to_consume = sent;
size_t remaining = total_write_len - sent;
buffer.data.reserve(remaining);
for (int i = 0; i < iovcnt; i++) {
if (to_consume >= iov[i].iov_len) {
// This segment was fully sent
to_consume -= static_cast<uint16_t>(iov[i].iov_len);
to_consume -= iov[i].iov_len;
} else {
// This segment was partially sent or not sent at all
const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume;
uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_consume;
size_t len = iov[i].iov_len - to_consume;
buffer.data.insert(buffer.data.end(), data, data + len);
to_consume = 0;
}
@@ -191,28 +190,6 @@ APIError APIFrameHelper::try_send_tx_buf_() {
return APIError::OK; // All buffers sent successfully
}
APIError APIFrameHelper::init_common_() {
if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
ESP_LOGVV(TAG, "%s: Bad state for init %d", this->info_.c_str(), (int) state_);
return APIError::BAD_STATE;
}
int err = this->socket_->setblocking(false);
if (err != 0) {
state_ = State::FAILED;
ESP_LOGVV(TAG, "%s: Setting nonblocking failed with errno %d", this->info_.c_str(), errno);
return APIError::TCP_NONBLOCKING_FAILED;
}
int enable = 1;
err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
if (err != 0) {
state_ = State::FAILED;
ESP_LOGVV(TAG, "%s: Setting nodelay failed with errno %d", this->info_.c_str(), errno);
return APIError::TCP_NODELAY_FAILED;
}
return APIError::OK;
}
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__)
// uncomment to log raw packets
//#define HELPER_LOG_PACKETS
@@ -261,9 +238,23 @@ std::string noise_err_to_str(int err) {
/// Initialize the frame helper, returns OK if successful.
APIError APINoiseFrameHelper::init() {
APIError err = init_common_();
if (err != APIError::OK) {
return err;
if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
HELPER_LOG("Bad state for init %d", (int) state_);
return APIError::BAD_STATE;
}
int err = this->socket_->setblocking(false);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
return APIError::TCP_NONBLOCKING_FAILED;
}
int enable = 1;
err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nodelay failed with errno %d", errno);
return APIError::TCP_NODELAY_FAILED;
}
// init prologue
@@ -275,16 +266,16 @@ APIError APINoiseFrameHelper::init() {
/// Run through handshake messages (if in that phase)
APIError APINoiseFrameHelper::loop() {
APIError err = state_action_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
if (err == APIError::WOULD_BLOCK)
return APIError::OK;
if (err != APIError::OK)
return err;
}
if (!this->tx_buf_.empty()) {
err = try_send_tx_buf_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err;
}
}
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
if (this->tx_buf_.empty())
return APIError::OK;
err = try_send_tx_buf_();
if (err == APIError::WOULD_BLOCK)
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
return err;
}
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
@@ -310,7 +301,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// read header
if (rx_header_buf_len_ < 3) {
// no header information yet
uint8_t to_read = 3 - rx_header_buf_len_;
size_t to_read = 3 - rx_header_buf_len_;
ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
@@ -324,8 +315,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_header_buf_len_ += static_cast<uint8_t>(received);
if (static_cast<uint8_t>(received) != to_read) {
rx_header_buf_len_ += received;
if ((size_t) received != to_read) {
// not a full read
return APIError::WOULD_BLOCK;
}
@@ -357,7 +348,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
if (rx_buf_len_ < msg_size) {
// more data to read
uint16_t to_read = msg_size - rx_buf_len_;
size_t to_read = msg_size - rx_buf_len_;
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
@@ -371,8 +362,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_buf_len_ += static_cast<uint16_t>(received);
if (static_cast<uint16_t>(received) != to_read) {
rx_buf_len_ += received;
if ((size_t) received != to_read) {
// not all read
return APIError::WOULD_BLOCK;
}
@@ -421,8 +412,6 @@ APIError APINoiseFrameHelper::state_action_() {
if (aerr != APIError::OK)
return aerr;
// ignore contents, may be used in future for flags
// Reserve space for: existing prologue + 2 size bytes + frame data
prologue_.reserve(prologue_.size() + 2 + frame.msg.size());
prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
prologue_.push_back((uint8_t) frame.msg.size());
prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
@@ -431,20 +420,16 @@ APIError APINoiseFrameHelper::state_action_() {
}
if (state_ == State::SERVER_HELLO) {
// send server hello
const std::string &name = App.get_name();
const std::string &mac = get_mac_address();
std::vector<uint8_t> msg;
// Reserve space for: 1 byte proto + name + null + mac + null
msg.reserve(1 + name.size() + 1 + mac.size() + 1);
// chosen proto
msg.push_back(0x01);
// node name, terminated by null byte
const std::string &name = App.get_name();
const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
// node mac, terminated by null byte
const std::string &mac = get_mac_address();
const uint8_t *mac_ptr = reinterpret_cast<const uint8_t *>(mac.c_str());
msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1);
@@ -539,18 +524,16 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
std::vector<uint8_t> data;
data.resize(reason.length() + 1);
data[0] = 0x01; // failure
// Copy error message in bulk
if (!reason.empty()) {
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
for (size_t i = 0; i < reason.length(); i++) {
data[i + 1] = (uint8_t) reason[i];
}
// temporarily remove failed state
auto orig_state = state_;
state_ = State::EXPLICIT_REJECT;
write_frame_(data.data(), data.size());
state_ = orig_state;
}
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
int err;
APIError aerr;
@@ -578,7 +561,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return APIError::CIPHERSTATE_DECRYPT_FAILED;
}
uint16_t msg_size = mbuf.size;
size_t msg_size = mbuf.size;
uint8_t *msg_data = frame.msg.data();
if (msg_size < 4) {
state_ = State::FAILED;
@@ -604,7 +587,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = type;
return APIError::OK;
}
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
int err;
APIError aerr;
aerr = state_action_();
@@ -616,36 +599,31 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
return APIError::WOULD_BLOCK;
}
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
// Message data starts after padding
uint16_t payload_len = raw_buffer->size() - frame_header_padding_;
uint16_t padding = 0;
uint16_t msg_len = 4 + payload_len + padding;
size_t padding = 0;
size_t msg_len = 4 + payload_len + padding;
size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
auto tmpbuf = std::unique_ptr<uint8_t[]>{new (std::nothrow) uint8_t[frame_len]};
if (tmpbuf == nullptr) {
HELPER_LOG("Could not allocate for writing packet");
return APIError::OUT_OF_MEMORY;
}
// We need to resize to include MAC space, but we already reserved it in create_buffer
raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
// Write the noise header in the padded area
// Buffer layout:
// [0] - 0x01 indicator byte
// [1-2] - Size of encrypted payload (filled after encryption)
// [3-4] - Message type (encrypted)
// [5-6] - Payload length (encrypted)
// [7...] - Actual payload data (encrypted)
uint8_t *buf_start = raw_buffer->data();
buf_start[0] = 0x01; // indicator
// buf_start[1], buf_start[2] to be set later after encryption
tmpbuf[0] = 0x01; // indicator
// tmpbuf[1], tmpbuf[2] to be set later
const uint8_t msg_offset = 3;
buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
// payload data is already in the buffer starting at position 7
const uint8_t payload_offset = msg_offset + 4;
tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8); // type
tmpbuf[msg_offset + 1] = (uint8_t) type;
tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len
tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
// copy data
std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
// fill padding with zeros
std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
NoiseBuffer mbuf;
noise_buffer_init(mbuf);
// The capacity parameter should be msg_len + frame_footer_size_ (MAC length) to allow space for encryption
noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset);
err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
if (err != 0) {
state_ = State::FAILED;
@@ -653,20 +631,18 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
return APIError::CIPHERSTATE_ENCRYPT_FAILED;
}
uint16_t total_len = 3 + mbuf.size;
buf_start[1] = (uint8_t) (mbuf.size >> 8);
buf_start[2] = (uint8_t) mbuf.size;
size_t total_len = 3 + mbuf.size;
tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
tmpbuf[2] = (uint8_t) mbuf.size;
struct iovec iov;
// Point iov_base to the beginning of the buffer (no unused padding in Noise)
// We send the entire frame: indicator + size + encrypted(type + data_len + payload + MAC)
iov.iov_base = buf_start;
iov.iov_base = &tmpbuf[0];
iov.iov_len = total_len;
// write raw to not have two packets sent if NAGLE disabled
return this->write_raw_(&iov, 1);
return APIFrameHelper::write_raw_(&iov, 1);
}
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
uint8_t header[3];
header[0] = 0x01; // indicator
header[1] = (uint8_t) (len >> 8);
@@ -676,12 +652,12 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
iov[0].iov_base = header;
iov[0].iov_len = 3;
if (len == 0) {
return this->write_raw_(iov, 1);
return APIFrameHelper::write_raw_(iov, 1);
}
iov[1].iov_base = const_cast<uint8_t *>(data);
iov[1].iov_len = len;
return this->write_raw_(iov, 2);
return APIFrameHelper::write_raw_(iov, 2);
}
/** Initiate the data structures for the handshake.
@@ -752,8 +728,6 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
return APIError::HANDSHAKESTATE_SPLIT_FAILED;
}
frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
HELPER_LOG("Handshake complete!");
noise_handshakestate_free(handshake_);
handshake_ = nullptr;
@@ -780,7 +754,7 @@ extern "C" {
// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
void noise_rand_bytes(void *output, size_t len) {
if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
ESP_LOGE(TAG, "Acquiring random bytes failed; rebooting");
ESP_LOGE(TAG, "Failed to acquire random bytes, rebooting!");
arch_restart();
}
}
@@ -792,9 +766,22 @@ void noise_rand_bytes(void *output, size_t len) {
/// Initialize the frame helper, returns OK if successful.
APIError APIPlaintextFrameHelper::init() {
APIError err = init_common_();
if (err != APIError::OK) {
return err;
if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
HELPER_LOG("Bad state for init %d", (int) state_);
return APIError::BAD_STATE;
}
int err = this->socket_->setblocking(false);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
return APIError::TCP_NONBLOCKING_FAILED;
}
int enable = 1;
err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nodelay failed with errno %d", errno);
return APIError::TCP_NODELAY_FAILED;
}
state_ = State::DATA;
@@ -805,13 +792,12 @@ APIError APIPlaintextFrameHelper::loop() {
if (state_ != State::DATA) {
return APIError::BAD_STATE;
}
if (!this->tx_buf_.empty()) {
APIError err = try_send_tx_buf_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err;
}
}
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
if (this->tx_buf_.empty())
return APIError::OK;
APIError err = try_send_tx_buf_();
if (err == APIError::WOULD_BLOCK)
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
return err;
}
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
@@ -831,15 +817,12 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// read header
while (!rx_header_parsed_) {
// Now that we know when the socket is ready, we can read up to 3 bytes
// into the rx_header_buf_ before we have to switch back to reading
// one byte at a time to ensure we don't read past the message and
// into the next one.
// Read directly into rx_header_buf_ at the current position
// Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time
ssize_t received =
this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1);
uint8_t data;
// Reading one byte at a time is fastest in practice for ESP32 when
// there is no data on the wire (which is the common case).
// This results in faster failure detection compared to
// attempting to read multiple bytes at once.
ssize_t received = socket_->read(&data, 1);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
@@ -853,74 +836,64 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
return APIError::CONNECTION_CLOSED;
}
// If this was the first read, validate the indicator byte
if (rx_header_buf_pos_ == 0 && received > 0) {
if (rx_header_buf_[0] != 0x00) {
// Successfully read a byte
// Process byte according to current buffer position
if (rx_header_buf_pos_ == 0) { // Case 1: First byte (indicator byte)
if (data != 0x00) {
state_ = State::FAILED;
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
HELPER_LOG("Bad indicator byte %u", data);
return APIError::BAD_INDICATOR;
}
// We don't store the indicator byte, just increment position
rx_header_buf_pos_ = 1; // Set to 1 directly
continue; // Need more bytes before we can parse
}
rx_header_buf_pos_ += received;
// Check for buffer overflow
if (rx_header_buf_pos_ >= sizeof(rx_header_buf_)) {
// Check buffer overflow before storing
if (rx_header_buf_pos_ == 5) { // Case 2: Buffer would overflow (5 bytes is max allowed)
state_ = State::FAILED;
HELPER_LOG("Header buffer overflow");
return APIError::BAD_DATA_PACKET;
}
// Need at least 3 bytes total (indicator + 2 varint bytes) before trying to parse
if (rx_header_buf_pos_ < 3) {
continue;
// Store byte in buffer (adjust index to account for skipped indicator byte)
rx_header_buf_[rx_header_buf_pos_ - 1] = data;
// Increment position after storing
rx_header_buf_pos_++;
// Case 3: If we only have one varint byte, we need more
if (rx_header_buf_pos_ == 2) { // Have read indicator + 1 byte
continue; // Need more bytes before we can parse
}
// At this point, we have at least 3 bytes total:
// - Validated indicator byte (0x00) stored at position 0
// - Validated indicator byte (0x00) but not stored
// - At least 2 bytes in the buffer for the varints
// Buffer layout:
// [0]: indicator byte (0x00)
// [1-3]: Message size varint (variable length)
// - 2 bytes would only allow up to 16383, which is less than noise's UINT16_MAX (65535)
// First 1-3 bytes: Message size varint (variable length)
// - 2 bytes would only allow up to 16383, which is less than noise's 65535
// - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
// [2-5]: Message type varint (variable length)
// Remaining 1-2 bytes: Message type varint (variable length)
// We now attempt to parse both varints. If either is incomplete,
// we'll continue reading more bytes.
// Skip indicator byte at position 0
uint8_t varint_pos = 1;
uint32_t consumed = 0;
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[0], rx_header_buf_pos_ - 1, &consumed);
if (!msg_size_varint.has_value()) {
// not enough data there yet
continue;
}
if (msg_size_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
state_ = State::FAILED;
HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
std::numeric_limits<uint16_t>::max());
return APIError::BAD_DATA_PACKET;
}
rx_header_parsed_len_ = msg_size_varint->as_uint16();
rx_header_parsed_len_ = msg_size_varint->as_uint32();
// Move to next varint position
varint_pos += consumed;
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[consumed], rx_header_buf_pos_ - 1 - consumed, &consumed);
if (!msg_type_varint.has_value()) {
// not enough data there yet
continue;
}
if (msg_type_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
state_ = State::FAILED;
HELPER_LOG("Bad packet: message type %" PRIu32 " exceeds maximum %u", msg_type_varint->as_uint32(),
std::numeric_limits<uint16_t>::max());
return APIError::BAD_DATA_PACKET;
}
rx_header_parsed_type_ = msg_type_varint->as_uint16();
rx_header_parsed_type_ = msg_type_varint->as_uint32();
rx_header_parsed_ = true;
}
// header reading done
@@ -932,7 +905,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
if (rx_buf_len_ < rx_header_parsed_len_) {
// more data to read
uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
@@ -946,8 +919,8 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_buf_len_ += static_cast<uint16_t>(received);
if (static_cast<uint16_t>(received) != to_read) {
rx_buf_len_ += received;
if ((size_t) received != to_read) {
// not all read
return APIError::WOULD_BLOCK;
}
@@ -965,6 +938,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
rx_header_parsed_ = false;
return APIError::OK;
}
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
APIError aerr;
@@ -992,7 +966,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
"Bad indicator byte";
iov[0].iov_base = (void *) msg;
iov[0].iov_len = 19;
this->write_raw_(iov, 1);
APIFrameHelper::write_raw_(iov, 1);
}
return aerr;
}
@@ -1003,66 +977,28 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = rx_header_parsed_type_;
return APIError::OK;
}
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
if (state_ != State::DATA) {
return APIError::BAD_STATE;
}
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
// Message data starts after padding (frame_header_padding_ = 6)
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
std::vector<uint8_t> header;
header.reserve(1 + api::ProtoSize::varint(static_cast<uint32_t>(payload_len)) +
api::ProtoSize::varint(static_cast<uint32_t>(type)));
header.push_back(0x00);
ProtoVarInt(payload_len).encode(header);
ProtoVarInt(type).encode(header);
// Calculate varint sizes for header components
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
if (total_header_len > frame_header_padding_) {
// Header is too large to fit in the padding
return APIError::BAD_ARG;
struct iovec iov[2];
iov[0].iov_base = &header[0];
iov[0].iov_len = header.size();
if (payload_len == 0) {
return APIFrameHelper::write_raw_(iov, 1);
}
iov[1].iov_base = const_cast<uint8_t *>(payload);
iov[1].iov_len = payload_len;
// Calculate where to start writing the header
// The header starts at the latest possible position to minimize unused padding
//
// Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
// [0-2] - Unused padding
// [3] - 0x00 indicator byte
// [4] - Payload size varint (1 byte, for sizes 0-127)
// [5] - Message type varint (1 byte, for types 0-127)
// [6...] - Actual payload data
//
// Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
// [0-1] - Unused padding
// [2] - 0x00 indicator byte
// [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
// [5] - Message type varint (1 byte, for types 0-127)
// [6...] - Actual payload data
//
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
// [0] - 0x00 indicator byte
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
// [4-5] - Message type varint (2 bytes, for types 128-32767)
// [6...] - Actual payload data
uint8_t *buf_start = raw_buffer->data();
uint8_t header_offset = frame_header_padding_ - total_header_len;
// Write the plaintext header
buf_start[header_offset] = 0x00; // indicator
// Encode size varint directly into buffer
ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
// Encode type varint directly into buffer
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
struct iovec iov;
// Point iov_base to the beginning of our header (skip unused padding)
// This ensures we only send the actual header and payload, not the empty padding bytes
iov.iov_base = buf_start + header_offset;
iov.iov_len = total_header_len + payload_len;
return write_raw_(&iov, 1);
return APIFrameHelper::write_raw_(iov, 2);
}
#endif // USE_API_PLAINTEXT

View File

@@ -1,7 +1,6 @@
#pragma once
#include <cstdint>
#include <deque>
#include <limits>
#include <utility>
#include <vector>
@@ -13,18 +12,22 @@
#include "api_noise_context.h"
#include "esphome/components/socket/socket.h"
#include "esphome/core/application.h"
namespace esphome {
namespace api {
class ProtoWriteBuffer;
struct ReadPacketBuffer {
std::vector<uint8_t> container;
uint16_t type;
uint16_t data_offset;
uint16_t data_len;
size_t data_offset;
size_t data_len;
};
struct PacketBuffer {
const std::vector<uint8_t> container;
uint16_t type;
uint8_t data_offset;
uint8_t data_len;
};
enum class APIError : int {
@@ -65,7 +68,29 @@ class APIFrameHelper {
virtual APIError init() = 0;
virtual APIError loop() = 0;
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
bool can_write_without_blocking() {
// First check if we're in the DATA state
if (state_ != State::DATA) {
return false;
}
// Empty buffer can always accept more data
if (tx_buf_.empty()) {
return true;
}
// Optimization: Allow writing even with a small buffer backlog to reduce delays in message processing.
// This improves throughput for real-time data like sensor readings and prevents high-priority
// messages from being unnecessarily delayed by a small queue backlog.
// The 256-byte threshold is small enough to not impact memory usage significantly
// but large enough to improve overall system responsiveness.
if (tx_buf_.size() == 1 && tx_buf_.front().remaining() < 256) {
return true;
}
return false;
}
virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
std::string getpeername() { return socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
APIError close() {
@@ -86,13 +111,6 @@ class APIFrameHelper {
}
// Give this helper a name for logging
void set_log_info(std::string info) { info_ = std::move(info); }
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
// Get the frame header padding required by this protocol
virtual uint8_t frame_header_padding() = 0;
// Get the frame footer size required by this protocol
virtual uint8_t frame_footer_size() = 0;
// Check if socket has data ready to read
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
protected:
// Struct for holding parsed frame data
@@ -105,7 +123,7 @@ class APIFrameHelper {
std::vector<uint8_t> data;
uint16_t offset{0}; // Current offset within the buffer (uint16_t to reduce memory usage)
// Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
// Using uint16_t reduces memory usage since ESPHome API messages are limited to 64KB max
uint16_t remaining() const { return static_cast<uint16_t>(data.size()) - offset; }
const uint8_t *current_data() const { return data.data() + offset; }
};
@@ -149,56 +167,34 @@ class APIFrameHelper {
APIError try_send_tx_buf_();
// Helper method to buffer data from IOVs
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
template<typename StateEnum>
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
const std::string &info, StateEnum &state, StateEnum failed_state);
uint8_t frame_header_padding_{0};
uint8_t frame_footer_size_{0};
// Receive buffer for reading frame data
std::vector<uint8_t> rx_buf_;
uint16_t rx_buf_len_ = 0;
// Common initialization for both plaintext and noise protocols
APIError init_common_();
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, size_t total_write_len);
};
#ifdef USE_API_NOISE
class APINoiseFrameHelper : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
: APIFrameHelper(std::move(socket)), ctx_(std::move(ctx)) {
// Noise header structure:
// Pos 0: indicator (0x01)
// Pos 1-2: encrypted payload size (16-bit big-endian)
// Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
// Pos 7+: actual payload data
frame_header_padding_ = 7;
}
: APIFrameHelper(std::move(socket)), ctx_(std::move(ctx)) {}
~APINoiseFrameHelper() override;
APIError init() override;
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
// Get the frame header padding required by this protocol
uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; }
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
protected:
APIError state_action_();
APIError try_read_frame_(ParsedFrame *frame);
APIError write_frame_(const uint8_t *data, uint16_t len);
APIError write_frame_(const uint8_t *data, size_t len);
APIError init_handshake_();
APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const std::string &reason);
// Fixed-size header buffer for noise protocol:
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
// Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase
// Note: Maximum message size is 65535, with a limit of 128 bytes during handshake phase
uint8_t rx_header_buf_[3];
uint8_t rx_header_buf_len_ = 0;
size_t rx_header_buf_len_ = 0;
std::vector<uint8_t> rx_buf_;
size_t rx_buf_len_ = 0;
std::vector<uint8_t> prologue_;
@@ -213,38 +209,31 @@ class APINoiseFrameHelper : public APIFrameHelper {
#ifdef USE_API_PLAINTEXT
class APIPlaintextFrameHelper : public APIFrameHelper {
public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {
// Plaintext header structure (worst case):
// Pos 0: indicator (0x00)
// Pos 1-3: payload size varint (up to 3 bytes)
// Pos 4-5: message type varint (up to 2 bytes)
// Pos 6+: actual payload data
frame_header_padding_ = 6;
}
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {}
~APIPlaintextFrameHelper() override = default;
APIError init() override;
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; }
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
protected:
APIError try_read_frame_(ParsedFrame *frame);
// Fixed-size header buffer for plaintext protocol:
// We now store the indicator byte + the two varints.
// To match noise protocol's maximum message size (UINT16_MAX = 65535), we need:
// 1 byte for indicator + 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
// We only need space for the two varints since we validate the indicator byte separately.
// To match noise protocol's maximum message size (65535), we need:
// 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
//
// While varints could theoretically be up to 10 bytes each for 64-bit values,
// attempting to process messages with headers that large would likely crash the
// ESP32 due to memory constraints.
uint8_t rx_header_buf_[6]; // 1 byte indicator + 5 bytes for varints (3 for size + 2 for type)
uint8_t rx_header_buf_[5]; // 5 bytes for varints (3 for size + 2 for type)
uint8_t rx_header_buf_pos_ = 0;
bool rx_header_parsed_ = false;
uint16_t rx_header_parsed_type_ = 0;
uint16_t rx_header_parsed_len_ = 0;
uint32_t rx_header_parsed_type_ = 0;
uint32_t rx_header_parsed_len_ = 0;
std::vector<uint8_t> rx_buf_;
size_t rx_buf_len_ = 0;
};
#endif

View File

@@ -27,7 +27,7 @@ APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-c
APIServer::APIServer() { global_api_server = this; }
void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
this->setup_controller();
#ifdef USE_API_NOISE
@@ -43,7 +43,7 @@ void APIServer::setup() {
}
#endif
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
this->socket_ = socket::socket_ip(SOCK_STREAM, 0);
if (this->socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket");
this->mark_failed();
@@ -112,20 +112,18 @@ void APIServer::setup() {
}
void APIServer::loop() {
// Accept new clients only if the socket has incoming connections
if (this->socket_->ready()) {
while (true) {
struct sockaddr_storage source_addr;
socklen_t addr_len = sizeof(source_addr);
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
if (!sock)
break;
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
// Accept new clients
while (true) {
struct sockaddr_storage source_addr;
socklen_t addr_len = sizeof(source_addr);
auto sock = this->socket_->accept((struct sockaddr *) &source_addr, &addr_len);
if (!sock)
break;
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this);
this->clients_.emplace_back(conn);
conn->start();
}
auto *conn = new APIConnection(std::move(sock), this);
this->clients_.emplace_back(conn);
conn->start();
}
// Process clients and remove disconnected ones in a single pass
@@ -157,7 +155,7 @@ void APIServer::loop() {
const uint32_t now = millis();
if (!this->is_connected()) {
if (now - this->last_connected_ > this->reboot_timeout_) {
ESP_LOGE(TAG, "No client connected; rebooting");
ESP_LOGE(TAG, "No client connected to API. Rebooting...");
App.reboot();
}
this->status_set_warning();

View File

@@ -55,7 +55,6 @@ class ProtoVarInt {
return {}; // Incomplete or invalid varint
}
uint16_t as_uint16() const { return this->value_; }
uint32_t as_uint32() const { return this->value_; }
uint64_t as_uint64() const { return this->value_; }
bool as_bool() const { return this->value_; }
@@ -84,34 +83,6 @@ class ProtoVarInt {
return static_cast<int64_t>(this->value_ >> 1);
}
}
/**
* Encode the varint value to a pre-allocated buffer without bounds checking.
*
* @param buffer The pre-allocated buffer to write the encoded varint to
* @param len The size of the buffer in bytes
*
* @note The caller is responsible for ensuring the buffer is large enough
* to hold the encoded value. Use ProtoSize::varint() to calculate
* the exact size needed before calling this method.
* @note No bounds checking is performed for performance reasons.
*/
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
uint64_t val = this->value_;
if (val <= 0x7F) {
buffer[0] = val;
return;
}
size_t i = 0;
while (val && i < len) {
uint8_t temp = val & 0x7F;
val >>= 7;
if (val) {
buffer[i++] = temp | 0x80;
} else {
buffer[i++] = temp;
}
}
}
void encode(std::vector<uint8_t> &out) {
uint64_t val = this->value_;
if (val <= 0x7F) {

View File

@@ -7,7 +7,7 @@ namespace as3935 {
static const char *const TAG = "as3935";
void AS3935Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AS3935...");
this->irq_pin_->setup();
LOG_PIN(" IRQ Pin: ", this->irq_pin_);
@@ -282,7 +282,7 @@ void AS3935Component::display_oscillator(bool state, uint8_t osc) {
// based on the resonance frequency of the antenna and so it should be trimmed
// before the calibration is done.
bool AS3935Component::calibrate_oscillator() {
ESP_LOGI(TAG, "Starting oscillators calibration");
ESP_LOGI(TAG, "Starting oscillators calibration...");
this->write_register(CALIB_RCO, WIPE_ALL, DIRECT_COMMAND, 0); // Send command to calibrate the oscillators
this->display_oscillator(true, 2);
@@ -307,7 +307,7 @@ bool AS3935Component::calibrate_oscillator() {
}
void AS3935Component::tune_antenna() {
ESP_LOGI(TAG, "Starting antenna tuning");
ESP_LOGI(TAG, "Starting antenna tuning...");
uint8_t div_ratio = this->read_div_ratio();
uint8_t tune_val = this->read_capacitance();
ESP_LOGI(TAG, "Division Ratio is set to: %d", div_ratio);

View File

@@ -23,7 +23,7 @@ static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R
static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
void AS5600Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AS5600...");
if (!this->read_byte(REGISTER_STATUS).has_value()) {
this->mark_failed();
@@ -91,7 +91,7 @@ void AS5600Component::dump_config() {
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AS5600 failed!");
return;
}

View File

@@ -8,7 +8,7 @@ namespace as7341 {
static const char *const TAG = "as7341";
void AS7341Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AS7341...");
LOG_I2C_DEVICE(this);
// Verify device ID
@@ -38,7 +38,7 @@ void AS7341Component::dump_config() {
ESP_LOGCONFIG(TAG, "AS7341:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AS7341 failed!");
}
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Gain: %u", get_gain());

View File

@@ -71,11 +71,11 @@ bool AT581XComponent::i2c_read_reg(uint8_t addr, uint8_t &data) {
return this->read_register(addr, &data, 1) == esphome::i2c::NO_ERROR;
}
void AT581XComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); }
void AT581XComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up AT581X..."); }
void AT581XComponent::dump_config() { LOG_I2C_DEVICE(this); }
#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
bool AT581XComponent::i2c_write_config() {
ESP_LOGCONFIG(TAG, "Writing new config for AT581X");
ESP_LOGCONFIG(TAG, "Writing new config for AT581X...");
ESP_LOGCONFIG(TAG, "Frequency: %dMHz", this->freq_);
ESP_LOGCONFIG(TAG, "Sensing distance: %d", this->delta_);
ESP_LOGCONFIG(TAG, "Power: %dµA", this->power_);

View File

@@ -14,8 +14,11 @@ namespace esphome {
namespace at581x {
class AT581XComponent : public Component, public i2c::I2CDevice {
public:
#ifdef USE_SWITCH
protected:
switch_::Switch *rf_power_switch_{nullptr};
public:
void set_rf_power_switch(switch_::Switch *s) {
this->rf_power_switch_ = s;
s->turn_on();
@@ -45,9 +48,6 @@ class AT581XComponent : public Component, public i2c::I2CDevice {
bool i2c_read_reg(uint8_t addr, uint8_t &data);
protected:
#ifdef USE_SWITCH
switch_::Switch *rf_power_switch_{nullptr};
#endif
int freq_;
int self_check_time_ms_; /*!< Power-on self-test time, range: 0 ~ 65536 ms */
int protect_time_ms_; /*!< Protection time, recommended 1000 ms */

View File

@@ -41,7 +41,7 @@ void ATM90E26Component::update() {
}
void ATM90E26Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up ATM90E26 Component...");
this->spi_setup();
uint16_t mmode = 0x422; // default values for everything but L/N line current gains
@@ -135,7 +135,7 @@ void ATM90E26Component::dump_config() {
ESP_LOGCONFIG("", "ATM90E26:");
LOG_PIN(" CS Pin: ", this->cs_);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with ATM90E26 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage A", this->voltage_sensor_);

View File

@@ -108,7 +108,7 @@ void ATM90E32Component::update() {
}
void ATM90E32Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
this->spi_setup();
uint16_t mmode0 = 0x87; // 3P4W 50Hz
@@ -217,7 +217,7 @@ void ATM90E32Component::dump_config() {
ESP_LOGCONFIG("", "ATM90E32:");
LOG_PIN(" CS Pin: ", this->cs_);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with ATM90E32 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_);
@@ -686,7 +686,7 @@ void ATM90E32Component::restore_power_offset_calibrations_() {
}
void ATM90E32Component::clear_gain_calibrations() {
ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values");
ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values...");
for (int phase = 0; phase < 3; phase++) {
gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;

View File

@@ -17,7 +17,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
}
void AXS15231Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AXS15231 Touchscreen...");
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);

View File

@@ -7,7 +7,7 @@ CODEOWNERS = ["@bazuchan"]
ballu_ns = cg.esphome_ns.namespace("ballu")
BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(BalluClimate)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(BalluClimate)
async def to_code(config):

View File

@@ -3,7 +3,6 @@
#include "bedjet_hub.h"
#include "bedjet_child.h"
#include "bedjet_const.h"
#include "esphome/core/application.h"
#include <cinttypes>
namespace esphome {
@@ -527,7 +526,7 @@ void BedJetHub::dispatch_status_() {
}
if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) {
ESP_LOGW(TAG, "[%s] Timed out after %" PRId32 " sec. Retrying", this->get_name().c_str(), this->timeout_);
ESP_LOGW(TAG, "[%s] Timed out after %" PRId32 " sec. Retrying...", this->get_name().c_str(), this->timeout_);
// set_enabled(false) will only close the connection if state != IDLE.
this->parent()->set_state(espbt::ClientState::CONNECTING);
this->parent()->set_enabled(false);

View File

@@ -119,7 +119,7 @@ void spi_dma_tx_finish_callback(unsigned int param) {
}
void BekenSPILEDStripLightOutput::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up Beken SPI LED Strip...");
size_t buffer_size = this->get_buffer_size_();
size_t dma_buffer_size = (buffer_size * 8) + (2 * 64);
@@ -256,7 +256,7 @@ void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
this->last_refresh_ = now;
this->mark_shown_();
ESP_LOGVV(TAG, "Writing RGB values to bus");
ESP_LOGVV(TAG, "Writing RGB values to bus...");
if (spi_data == nullptr) {
ESP_LOGE(TAG, "SPI not initialized");

View File

@@ -38,7 +38,7 @@ MTreg:
*/
void BH1750Sensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->name_.c_str());
ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str());
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
this->mark_failed();
@@ -118,7 +118,7 @@ void BH1750Sensor::dump_config() {
LOG_SENSOR("", "BH1750", this);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL_FOR, this->get_name().c_str());
ESP_LOGE(TAG, "Communication with BH1750 failed!");
}
LOG_UPDATE_INTERVAL(this);

View File

@@ -68,7 +68,8 @@ void binary_sensor::MultiClickTrigger::on_state_(bool state) {
*this->at_index_ = *this->at_index_ + 1;
}
void binary_sensor::MultiClickTrigger::schedule_cooldown_() {
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms...",
this->invalid_cooldown_);
this->is_in_cooldown_ = true;
this->set_timeout("cooldown", this->invalid_cooldown_, [this]() {
ESP_LOGV(TAG, "Multi Click: Cooldown ended, matching is now enabled again.");

View File

@@ -15,21 +15,17 @@ void BinarySensor::publish_state(bool state) {
if (!this->publish_dedup_.next(state))
return;
if (this->filter_list_ == nullptr) {
this->send_state_internal(state, false);
this->send_state_internal(state);
} else {
this->filter_list_->input(state, false);
this->filter_list_->input(state);
}
}
void BinarySensor::publish_initial_state(bool state) {
if (!this->publish_dedup_.next(state))
return;
if (this->filter_list_ == nullptr) {
this->send_state_internal(state, true);
} else {
this->filter_list_->input(state, true);
}
this->has_state_ = false;
this->publish_state(state);
}
void BinarySensor::send_state_internal(bool state, bool is_initial) {
void BinarySensor::send_state_internal(bool state) {
bool is_initial = !this->has_state_;
if (is_initial) {
ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state));
} else {

View File

@@ -67,7 +67,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
void send_state_internal(bool state, bool is_initial);
void send_state_internal(bool state);
/// Return whether this binary sensor has outputted a state.
virtual bool has_state() const;

View File

@@ -9,37 +9,37 @@ namespace binary_sensor {
static const char *const TAG = "sensor.filter";
void Filter::output(bool value, bool is_initial) {
void Filter::output(bool value) {
if (!this->dedup_.next(value))
return;
if (this->next_ == nullptr) {
this->parent_->send_state_internal(value, is_initial);
this->parent_->send_state_internal(value);
} else {
this->next_->input(value, is_initial);
this->next_->input(value);
}
}
void Filter::input(bool value, bool is_initial) {
auto b = this->new_value(value, is_initial);
void Filter::input(bool value) {
auto b = this->new_value(value);
if (b.has_value()) {
this->output(*b, is_initial);
this->output(*b);
}
}
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
optional<bool> DelayedOnOffFilter::new_value(bool value) {
if (value) {
this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
} else {
this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); });
}
return {};
}
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
optional<bool> DelayedOnFilter::new_value(bool value) {
if (value) {
this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); });
return {};
} else {
this->cancel_timeout("ON");
@@ -49,9 +49,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
optional<bool> DelayedOffFilter::new_value(bool value) {
if (!value) {
this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); });
return {};
} else {
this->cancel_timeout("OFF");
@@ -61,11 +61,11 @@ optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
optional<bool> InvertFilter::new_value(bool value) { return !value; }
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) {
optional<bool> AutorepeatFilter::new_value(bool value) {
if (value) {
// Ignore if already running
if (this->active_timing_ != 0)
@@ -101,7 +101,7 @@ void AutorepeatFilter::next_timing_() {
void AutorepeatFilter::next_value_(bool val) {
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
this->output(val, false); // This is at least the second one so not initial
this->output(val);
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
}
@@ -109,18 +109,18 @@ float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARD
LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); }
optional<bool> SettleFilter::new_value(bool value, bool is_initial) {
optional<bool> SettleFilter::new_value(bool value) {
if (!this->steady_) {
this->set_timeout("SETTLE", this->delay_.value(), [this, value, is_initial]() {
this->set_timeout("SETTLE", this->delay_.value(), [this, value]() {
this->steady_ = true;
this->output(value, is_initial);
this->output(value);
});
return {};
} else {
this->steady_ = false;
this->output(value, is_initial);
this->output(value);
this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
return value;
}

View File

@@ -14,11 +14,11 @@ class BinarySensor;
class Filter {
public:
virtual optional<bool> new_value(bool value, bool is_initial) = 0;
virtual optional<bool> new_value(bool value) = 0;
void input(bool value, bool is_initial);
void input(bool value);
void output(bool value, bool is_initial);
void output(bool value);
protected:
friend BinarySensor;
@@ -30,7 +30,7 @@ class Filter {
class DelayedOnOffFilter : public Filter, public Component {
public:
optional<bool> new_value(bool value, bool is_initial) override;
optional<bool> new_value(bool value) override;
float get_setup_priority() const override;
@@ -44,7 +44,7 @@ class DelayedOnOffFilter : public Filter, public Component {
class DelayedOnFilter : public Filter, public Component {
public:
optional<bool> new_value(bool value, bool is_initial) override;
optional<bool> new_value(bool value) override;
float get_setup_priority() const override;
@@ -56,7 +56,7 @@ class DelayedOnFilter : public Filter, public Component {
class DelayedOffFilter : public Filter, public Component {
public:
optional<bool> new_value(bool value, bool is_initial) override;
optional<bool> new_value(bool value) override;
float get_setup_priority() const override;
@@ -68,7 +68,7 @@ class DelayedOffFilter : public Filter, public Component {
class InvertFilter : public Filter {
public:
optional<bool> new_value(bool value, bool is_initial) override;
optional<bool> new_value(bool value) override;
};
struct AutorepeatFilterTiming {
@@ -86,7 +86,7 @@ class AutorepeatFilter : public Filter, public Component {
public:
explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
optional<bool> new_value(bool value, bool is_initial) override;
optional<bool> new_value(bool value) override;
float get_setup_priority() const override;
@@ -102,7 +102,7 @@ class LambdaFilter : public Filter {
public:
explicit LambdaFilter(std::function<optional<bool>(bool)> f);
optional<bool> new_value(bool value, bool is_initial) override;
optional<bool> new_value(bool value) override;
protected:
std::function<optional<bool>(bool)> f_;
@@ -110,7 +110,7 @@ class LambdaFilter : public Filter {
class SettleFilter : public Filter, public Component {
public:
optional<bool> new_value(bool value, bool is_initial) override;
optional<bool> new_value(bool value) override;
float get_setup_priority() const override;

View File

@@ -100,7 +100,7 @@ void BL0906::handle_actions_() {
for (int i = 0; i < this->action_queue_.size(); i++) {
ptr_func = this->action_queue_[i];
if (ptr_func) {
ESP_LOGI(TAG, "HandleActionCallback[%d]", i);
ESP_LOGI(TAG, "HandleActionCallback[%d]...", i);
(this->*ptr_func)();
}
}

View File

@@ -9,7 +9,6 @@ from esphome.const import (
CONF_ID,
CONF_LINE_FREQUENCY,
CONF_POWER,
CONF_RESET,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
@@ -28,6 +27,7 @@ from esphome.const import (
CONF_CURRENT_REFERENCE = "current_reference"
CONF_ENERGY_REFERENCE = "energy_reference"
CONF_POWER_REFERENCE = "power_reference"
CONF_RESET = "reset"
CONF_VOLTAGE_REFERENCE = "voltage_reference"
DEPENDENCIES = ["uart"]

View File

@@ -2,7 +2,6 @@
#include "esphome/core/log.h"
#include "esphome/core/macros.h"
#include "esphome/core/application.h"
#ifdef USE_ESP32
@@ -178,7 +177,7 @@ void BluetoothProxy::loop() {
// Flush any pending BLE advertisements that have been accumulated but not yet sent
if (this->raw_advertisements_) {
static uint32_t last_flush_time = 0;
uint32_t now = App.get_loop_component_start_time();
uint32_t now = millis();
// Flush accumulated advertisements every 100ms
if (now - last_flush_time >= 100) {

View File

@@ -88,7 +88,7 @@ const char *oversampling_to_str(BME280Oversampling oversampling) { // NOLINT
}
void BME280Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up BME280...");
uint8_t chip_id = 0;
// Mark as not failed before initializing. Some devices will turn off sensors to save on batteries
@@ -182,7 +182,7 @@ void BME280Component::dump_config() {
ESP_LOGCONFIG(TAG, "BME280:");
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with BME280 failed!");
break;
case WRONG_CHIP_ID:
ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?");
@@ -207,7 +207,7 @@ inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return (
void BME280Component::update() {
// Enable sensor
ESP_LOGV(TAG, "Sending conversion request");
ESP_LOGV(TAG, "Sending conversion request...");
uint8_t meas_value = 0;
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;

View File

@@ -71,7 +71,7 @@ static const char *iir_filter_to_str(BME680IIRFilter filter) {
}
void BME680Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up BME680...");
uint8_t chip_id;
if (!this->read_byte(BME680_REGISTER_CHIPID, &chip_id) || chip_id != 0x61) {
this->mark_failed();
@@ -215,7 +215,7 @@ void BME680Component::dump_config() {
ESP_LOGCONFIG(TAG, "BME680:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with BME680 failed!");
}
ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_));
LOG_UPDATE_INTERVAL(this);
@@ -307,7 +307,7 @@ void BME680Component::read_data_() {
this->humidity_sensor_->publish_state(NAN);
if (this->gas_resistance_sensor_ != nullptr)
this->gas_resistance_sensor_->publish_state(NAN);
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGW(TAG, "Communication with BME680 failed!");
this->status_set_warning();
return;
}

View File

@@ -13,7 +13,7 @@ from esphome.const import (
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
ICON_GAS_CYLINDER,
STATE_CLASS_MEASUREMENT,
@@ -71,7 +71,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{

View File

@@ -15,7 +15,7 @@ std::vector<BME680BSECComponent *>
uint8_t BME680BSECComponent::work_buffer_[BSEC_MAX_WORKBUFFER_SIZE] = {0};
void BME680BSECComponent::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->device_id_.c_str());
ESP_LOGCONFIG(TAG, "Setting up BME680(%s) via BSEC...", this->device_id_.c_str());
uint8_t new_idx = BME680BSECComponent::instances.size();
BME680BSECComponent::instances.push_back(this);

View File

@@ -15,8 +15,6 @@ from esphome.const import (
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
ICON_GAS_CYLINDER,
ICON_GAUGE,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
@@ -29,11 +27,11 @@ from . import CONF_BME680_BSEC_ID, SAMPLE_RATE_OPTIONS, BME680BSECComponent
DEPENDENCIES = ["bme680_bsec"]
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_IAQ = "iaq"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
UNIT_IAQ = "IAQ"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
TYPES = [
CONF_TEMPERATURE,
@@ -51,7 +49,6 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
@@ -68,7 +65,6 @@ CONFIG_SCHEMA = cv.Schema(
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,

View File

@@ -16,7 +16,7 @@ CODEOWNERS = ["@neffs", "@kbx81"]
DOMAIN = "bme68x_bsec2"
BSEC2_LIBRARY_VERSION = "1.10.2610"
BSEC2_LIBRARY_VERSION = "v1.8.2610"
CONF_ALGORITHM_OUTPUT = "algorithm_output"
CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id"
@@ -145,6 +145,7 @@ CONFIG_SCHEMA_BASE = (
): cv.positive_time_period_minutes,
},
)
.add_extra(cv.only_with_arduino)
.add_extra(validate_bme68x)
.add_extra(download_bme68x_blob)
)
@@ -178,13 +179,11 @@ async def to_code_base(config):
bsec2_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.add(var.set_bsec2_configuration(bsec2_arr, len(rhs)))
# Although this component does not use SPI, the BSEC2 Arduino library requires the SPI library
if core.CORE.using_arduino:
cg.add_library("SPI", None)
# Although this component does not use SPI, the BSEC2 library requires the SPI library
cg.add_library("SPI", None)
cg.add_library(
"BME68x Sensor library",
"1.3.40408",
"https://github.com/boschsensortec/Bosch-BME68x-Library",
"1.1.40407",
)
cg.add_library(
"BSEC2 Software Library",

View File

@@ -1,5 +1,4 @@
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -21,7 +20,7 @@ static const char *const TAG = "bme68x_bsec2.sensor";
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
void BME68xBSEC2Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up BME68X via BSEC2...");
this->bsec_status_ = bsec_init_m(&this->bsec_instance_);
if (this->bsec_status_ != BSEC_OK) {

View File

@@ -9,10 +9,8 @@ from esphome.const import (
CONF_SAMPLE_RATE,
CONF_TEMPERATURE,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
ICON_GAS_CYLINDER,
ICON_GAUGE,
ICON_THERMOMETER,
@@ -34,6 +32,7 @@ CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_IAQ = "iaq"
CONF_IAQ_STATIC = "iaq_static"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
ICON_TEST_TUBE = "mdi:test-tube"
UNIT_IAQ = "IAQ"
TYPES = [
@@ -62,6 +61,7 @@ CONFIG_SCHEMA = cv.Schema(
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
icon=ICON_GAUGE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
@@ -102,14 +102,14 @@ CONFIG_SCHEMA = cv.Schema(
),
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=STATE_CLASS_MEASUREMENT,
),
}

View File

@@ -1,5 +1,4 @@
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"

View File

@@ -119,44 +119,44 @@ const float GRAVITY_EARTH = 9.80665f;
void BMI160Component::internal_setup_(int stage) {
switch (stage) {
case 0:
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up BMI160...");
uint8_t chipid;
if (!this->read_byte(BMI160_REGISTER_CHIPID, &chipid) || (chipid != 0b11010001)) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Bringing accelerometer out of sleep");
ESP_LOGV(TAG, " Bringing accelerometer out of sleep...");
if (!this->write_byte(BMI160_REGISTER_CMD, (uint8_t) Cmd::ACCL_SET_PMU_MODE | (uint8_t) AcclPmuMode::NORMAL)) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Waiting for accelerometer to wake up");
ESP_LOGV(TAG, " Waiting for accelerometer to wake up...");
// need to wait (max delay in datasheet) because we can't send commands while another is in progress
// min 5ms, 10ms
this->set_timeout(10, [this]() { this->internal_setup_(1); });
break;
case 1:
ESP_LOGV(TAG, " Bringing gyroscope out of sleep");
ESP_LOGV(TAG, " Bringing gyroscope out of sleep...");
if (!this->write_byte(BMI160_REGISTER_CMD, (uint8_t) Cmd::GYRO_SET_PMU_MODE | (uint8_t) GyroPmuMode::NORMAL)) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Waiting for gyroscope to wake up");
ESP_LOGV(TAG, " Waiting for gyroscope to wake up...");
// wait between 51 & 81ms, doing 100 to be safe
this->set_timeout(10, [this]() { this->internal_setup_(2); });
break;
case 2:
ESP_LOGV(TAG, " Setting up Gyro Config");
ESP_LOGV(TAG, " Setting up Gyro Config...");
uint8_t gyro_config = (uint8_t) GyroBandwidth::OSR4 | (uint8_t) GyroOuputDataRate::HZ_25;
ESP_LOGV(TAG, " Output gyro_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config));
if (!this->write_byte(BMI160_REGISTER_GYRO_CONFIG, gyro_config)) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Setting up Gyro Range");
ESP_LOGV(TAG, " Setting up Gyro Range...");
uint8_t gyro_range = (uint8_t) GyroRange::RANGE_2000_DPS;
ESP_LOGV(TAG, " Output gyro_range: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_range));
if (!this->write_byte(BMI160_REGISTER_GYRO_RANGE, gyro_range)) {
@@ -164,7 +164,7 @@ void BMI160Component::internal_setup_(int stage) {
return;
}
ESP_LOGV(TAG, " Setting up Accel Config");
ESP_LOGV(TAG, " Setting up Accel Config...");
uint8_t accel_config =
(uint8_t) AcclFilterMode::PERF | (uint8_t) AcclBandwidth::RES_AVG16 | (uint8_t) AccelOutputDataRate::HZ_25;
ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config));
@@ -172,7 +172,7 @@ void BMI160Component::internal_setup_(int stage) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Setting up Accel Range");
ESP_LOGV(TAG, " Setting up Accel Range...");
uint8_t accel_range = (uint8_t) AccelRange::RANGE_16G;
ESP_LOGV(TAG, " Output accel_range: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_range));
if (!this->write_byte(BMI160_REGISTER_ACCEL_RANGE, accel_range)) {
@@ -189,7 +189,7 @@ void BMI160Component::dump_config() {
ESP_LOGCONFIG(TAG, "BMI160:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with BMI160 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Acceleration X", this->accel_x_sensor_);
@@ -219,7 +219,7 @@ void BMI160Component::update() {
return;
}
ESP_LOGV(TAG, " Updating BMI160");
ESP_LOGV(TAG, " Updating BMI160...");
int16_t data[6];
if (this->read_le_int16_(BMI160_REGISTER_DATA_GYRO_X_LSB, data, 6) != i2c::ERROR_OK) {
this->status_set_warning();

View File

@@ -20,7 +20,7 @@ void BMP085Component::update() {
this->set_timeout("temperature", 5, [this]() { this->read_temperature_(); });
}
void BMP085Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up BMP085...");
uint8_t data[22];
if (!this->read_bytes(BMP085_REGISTER_AC1_H, data, 22)) {
this->mark_failed();
@@ -129,7 +129,7 @@ void BMP085Component::read_pressure_() {
this->status_clear_warning();
}
bool BMP085Component::set_mode_(uint8_t mode) {
ESP_LOGV(TAG, "Setting mode to 0x%02X", mode);
ESP_LOGV(TAG, "Setting mode to 0x%02X...", mode);
return this->write_byte(BMP085_REGISTER_CONTROL, mode);
}
float BMP085Component::get_setup_priority() const { return setup_priority::DATA; }

View File

@@ -57,7 +57,7 @@ static const char *iir_filter_to_str(BMP280IIRFilter filter) {
}
void BMP280Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up BMP280...");
uint8_t chip_id = 0;
// Read the chip id twice, to work around a bug where the first read is 0.
@@ -132,7 +132,7 @@ void BMP280Component::dump_config() {
ESP_LOGCONFIG(TAG, "BMP280:");
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with BMP280 failed!");
break;
case WRONG_CHIP_ID:
ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BME280?");
@@ -155,7 +155,7 @@ inline uint8_t oversampling_to_time(BMP280Oversampling over_sampling) { return (
void BMP280Component::update() {
// Enable sensor
ESP_LOGV(TAG, "Sending conversion request");
ESP_LOGV(TAG, "Sending conversion request...");
uint8_t meas_value = 0;
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;

View File

@@ -70,10 +70,10 @@ static const LogString *iir_filter_to_str(IIRFilter filter) {
void BMP3XXComponent::setup() {
this->error_code_ = NONE;
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up BMP3XX...");
// Call the Device base class "initialise" function
if (!reset()) {
ESP_LOGE(TAG, "Failed to reset");
ESP_LOGE(TAG, "Failed to reset BMP3XX...");
this->error_code_ = ERROR_SENSOR_RESET;
this->mark_failed();
}
@@ -154,17 +154,19 @@ void BMP3XXComponent::dump_config() {
case NONE:
break;
case ERROR_COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with BMP3XX failed!");
break;
case ERROR_WRONG_CHIP_ID:
ESP_LOGE(TAG, "Wrong chip ID (reported id: 0x%X) - please check if you are really using a BMP 388 or BMP 390",
this->chip_id_.reg);
ESP_LOGE(
TAG,
"BMP3XX has wrong chip ID (reported id: 0x%X) - please check if you are really using a BMP 388 or BMP 390",
this->chip_id_.reg);
break;
case ERROR_SENSOR_RESET:
ESP_LOGE(TAG, "Failed to reset");
ESP_LOGE(TAG, "BMP3XX failed to reset");
break;
default:
ESP_LOGE(TAG, "Error code %d", (int) this->error_code_);
ESP_LOGE(TAG, "BMP3XX error code %d", (int) this->error_code_);
break;
}
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_filter_)));
@@ -184,7 +186,7 @@ inline uint8_t oversampling_to_time(Oversampling over_sampling) { return (1 << u
void BMP3XXComponent::update() {
// Enable sensor
ESP_LOGV(TAG, "Sending conversion request");
ESP_LOGV(TAG, "Sending conversion request...");
float meas_time = 1.0f;
// Ref: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf 3.9.2
meas_time += 2.02f * oversampling_to_time(this->temperature_oversampling_) + 0.163f;
@@ -294,7 +296,7 @@ bool BMP3XXComponent::get_pressure(float &pressure) {
bool BMP3XXComponent::get_measurements(float &temperature, float &pressure) {
// Check if a measurement is ready
if (!data_ready()) {
ESP_LOGD(TAG, "Get measurement - data not ready skipping update");
ESP_LOGD(TAG, "BMP3XX Get measurement - data not ready skipping update");
return false;
}

View File

@@ -72,22 +72,22 @@ void BMP581Component::dump_config() {
case NONE:
break;
case ERROR_COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, " Communication with BMP581 failed!");
break;
case ERROR_WRONG_CHIP_ID:
ESP_LOGE(TAG, "Unknown chip ID");
ESP_LOGE(TAG, " BMP581 has wrong chip ID - please verify you are using a BMP 581");
break;
case ERROR_SENSOR_RESET:
ESP_LOGE(TAG, "Reset failed");
ESP_LOGE(TAG, " BMP581 failed to reset");
break;
case ERROR_SENSOR_STATUS:
ESP_LOGE(TAG, "Get status failed");
ESP_LOGE(TAG, " BMP581 sensor status failed, there were NVM problems");
break;
case ERROR_PRIME_IIR_FAILED:
ESP_LOGE(TAG, "IIR Filter failed to prime with initial measurement");
ESP_LOGE(TAG, " BMP581's IIR Filter failed to prime with an initial measurement");
break;
default:
ESP_LOGE(TAG, "Error %d", (int) this->error_code_);
ESP_LOGE(TAG, " BMP581 error code %d", (int) this->error_code_);
break;
}
@@ -122,7 +122,7 @@ void BMP581Component::setup() {
*/
this->error_code_ = NONE;
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up BMP581...");
////////////////////
// 1) Soft reboot //
@@ -130,7 +130,7 @@ void BMP581Component::setup() {
// Power-On-Reboot bit is asserted if sensor successfully reset
if (!this->reset_()) {
ESP_LOGE(TAG, "Reset failed");
ESP_LOGE(TAG, "BMP581 failed to reset");
this->error_code_ = ERROR_SENSOR_RESET;
this->mark_failed();
@@ -146,7 +146,7 @@ void BMP581Component::setup() {
// read chip id from sensor
if (!this->read_byte(BMP581_CHIP_ID, &chip_id)) {
ESP_LOGE(TAG, "Read chip ID failed");
ESP_LOGE(TAG, "Failed to read chip id");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
@@ -156,7 +156,7 @@ void BMP581Component::setup() {
// verify id
if (chip_id != BMP581_ASIC_ID) {
ESP_LOGE(TAG, "Unknown chip ID");
ESP_LOGE(TAG, "Unknown chip ID, is this a BMP581?");
this->error_code_ = ERROR_WRONG_CHIP_ID;
this->mark_failed();
@@ -179,7 +179,7 @@ void BMP581Component::setup() {
// verify status_nvm_rdy bit (it is asserted if boot was successful)
if (!(this->status_.bit.status_nvm_rdy)) {
ESP_LOGE(TAG, "NVM not ready");
ESP_LOGE(TAG, "NVM not ready after boot");
this->error_code_ = ERROR_SENSOR_STATUS;
this->mark_failed();
@@ -189,7 +189,7 @@ void BMP581Component::setup() {
// verify status_nvm_err bit (it is asserted if an error is detected)
if (this->status_.bit.status_nvm_err) {
ESP_LOGE(TAG, "NVM error detected");
ESP_LOGE(TAG, "NVM error detected on boot");
this->error_code_ = ERROR_SENSOR_STATUS;
this->mark_failed();
@@ -254,7 +254,7 @@ void BMP581Component::setup() {
}
if (!this->prime_iir_filter_()) {
ESP_LOGE(TAG, "Failed to prime the IIR filter with an initial measurement");
ESP_LOGE(TAG, "Failed to prime the IIR filter with an intiial measurement");
this->error_code_ = ERROR_PRIME_IIR_FAILED;
this->mark_failed();
@@ -286,10 +286,10 @@ void BMP581Component::update() {
// 1) Request a measurement //
//////////////////////////////
ESP_LOGVV(TAG, "Requesting measurement");
ESP_LOGVV(TAG, "Requesting a measurement from sensor");
if (!this->start_measurement_()) {
ESP_LOGW(TAG, "Requesting forced measurement failed");
ESP_LOGW(TAG, "Failed to request forced measurement of sensor");
this->status_set_warning();
return;
@@ -299,7 +299,7 @@ void BMP581Component::update() {
// 2) Wait for measurement to finish (based on oversampling rates) //
//////////////////////////////////////////////////////////////////////
ESP_LOGVV(TAG, "Measurement should take %d ms", this->conversion_time_);
ESP_LOGVV(TAG, "Measurement is expected to take %d ms to complete", this->conversion_time_);
this->set_timeout("measurement", this->conversion_time_, [this]() {
float temperature = 0.0;
@@ -311,14 +311,14 @@ void BMP581Component::update() {
if (this->pressure_sensor_) {
if (!this->read_temperature_and_pressure_(temperature, pressure)) {
ESP_LOGW(TAG, "Failed to read temperature and pressure; skipping update");
ESP_LOGW(TAG, "Failed to read temperature and pressure measurements, skipping update");
this->status_set_warning();
return;
}
} else {
if (!this->read_temperature_(temperature)) {
ESP_LOGW(TAG, "Failed to read temperature; skipping update");
ESP_LOGW(TAG, "Failed to read temperature measurement, skipping update");
this->status_set_warning();
return;
@@ -349,7 +349,7 @@ bool BMP581Component::check_data_readiness_() {
// - returns data readiness state
if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
ESP_LOGD(TAG, "Data not ready, sensor is in standby mode");
ESP_LOGD(TAG, "Data is not ready, sensor is in standby mode");
return false;
}
@@ -443,7 +443,7 @@ bool BMP581Component::read_temperature_(float &temperature) {
// - the measured temperature (in degrees Celsius)
if (!this->check_data_readiness_()) {
ESP_LOGW(TAG, "Data not ready, skipping this update");
ESP_LOGW(TAG, "Data from sensor isn't ready, skipping this update");
this->status_set_warning();
return false;
@@ -451,7 +451,7 @@ bool BMP581Component::read_temperature_(float &temperature) {
uint8_t data[3];
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
ESP_LOGW(TAG, "Failed to read measurement");
ESP_LOGW(TAG, "Failed to read sensor's measurement data");
this->status_set_warning();
return false;
@@ -472,7 +472,7 @@ bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &
// - the measured pressure (in Pa)
if (!this->check_data_readiness_()) {
ESP_LOGW(TAG, "Data not ready, skipping this update");
ESP_LOGW(TAG, "Data from sensor isn't ready, skipping this update");
this->status_set_warning();
return false;
@@ -480,7 +480,7 @@ bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &
uint8_t data[6];
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
ESP_LOGW(TAG, "Failed to read measurement");
ESP_LOGW(TAG, "Failed to read sensor's measurement data");
this->status_set_warning();
return false;

View File

@@ -15,7 +15,7 @@ static const uint8_t BP1658CJ_ADDR_START_5CH = 0x30;
static const uint8_t BP1658CJ_DELAY = 2;
void BP1658CJ::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up BP1658CJ Output Component...");
this->data_pin_->setup();
this->data_pin_->digital_write(false);
this->clock_pin_->setup();

View File

@@ -20,7 +20,7 @@ static const uint8_t BP5758D_ALL_DATA_CHANNEL_ENABLEMENT = 0b00011111;
static const uint8_t BP5758D_DELAY = 2;
void BP5758D::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up BP5758D Output Component...");
this->data_pin_->setup();
this->data_pin_->digital_write(false);
delayMicroseconds(BP5758D_DELAY);

View File

@@ -7,7 +7,7 @@ namespace canbus {
static const char *const TAG = "canbus";
void Canbus::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up Canbus...");
if (!this->setup_internal()) {
ESP_LOGE(TAG, "setup error!");
this->mark_failed();

View File

@@ -8,7 +8,7 @@ namespace cap1188 {
static const char *const TAG = "cap1188";
void CAP1188Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up CAP1188...");
// Reset device using the reset pin
if (this->reset_pin_ != nullptr) {

View File

@@ -29,7 +29,7 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
std::string ssid = request->arg("ssid").c_str();
std::string psk = request->arg("psk").c_str();
ESP_LOGI(TAG, "Requested WiFi Settings Change:");
ESP_LOGI(TAG, "Captive Portal Requested WiFi Settings Change:");
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
wifi::global_wifi_component->save_wifi_sta(ssid, psk);

View File

@@ -163,7 +163,7 @@ void CCS811Component::dump_config() {
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
break;
case INVALID_ID:
ESP_LOGW(TAG, "Sensor reported an invalid ID. Is this a CCS811?");

View File

@@ -10,7 +10,7 @@ static const char *const TAG = "cd74hc4067";
float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; }
void CD74HC4067Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up CD74HC4067...");
this->pin_s0_->setup();
this->pin_s1_->setup();

View File

@@ -14,7 +14,7 @@ static const uint8_t CH422G_REG_OUT_UPPER = 0x23; // write reg for output bit
static const char *const TAG = "ch422g";
void CH422GComponent::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up CH422G...");
// set outputs before mode
this->write_outputs_();
// Set mode and check for errors
@@ -37,7 +37,7 @@ void CH422GComponent::dump_config() {
ESP_LOGCONFIG(TAG, "CH422G:");
LOG_I2C_DEVICE(this)
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with CH422G failed!");
}
}

View File

@@ -4,7 +4,7 @@ namespace esphome {
namespace chsc6x {
void CHSC6XTouchscreen::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up CHSC6X Touchscreen...");
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);

View File

@@ -40,7 +40,7 @@ def climate_ir_schema(
)
def climate_ir_with_receiver_schema(
def climare_ir_with_receiver_schema(
class_: MockObjClass,
) -> cv.Schema:
return climate_ir_schema(class_).extend(
@@ -59,7 +59,7 @@ def deprecated_schema_constant(config):
type = str(id.type).split("::", maxsplit=1)[0]
_LOGGER.warning(
"Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
"Please use `climate_ir.climate_ir_with_receiver_schema(...)` instead. "
"Please use `climate_ir.climare_ir_with_receiver_schema(...)` instead. "
"If you are seeing this, report an issue to the external_component author and ask them to update it. "
"https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
"Component using this schema: %s",
@@ -68,7 +68,7 @@ def deprecated_schema_constant(config):
return config
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climate_ir_with_receiver_schema(ClimateIR)
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climare_ir_with_receiver_schema(ClimateIR)
CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant)

View File

@@ -13,7 +13,7 @@ CONF_BIT_HIGH = "bit_high"
CONF_BIT_ONE_LOW = "bit_one_low"
CONF_BIT_ZERO_LOW = "bit_zero_low"
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(LgIrClimate).extend(
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(LgIrClimate).extend(
{
cv.Optional(
CONF_HEADER_HIGH, default="8000us"

View File

@@ -1 +0,0 @@
"""CM1106 component for ESPHome."""

View File

@@ -1,112 +0,0 @@
#include "cm1106.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace cm1106 {
static const char *const TAG = "cm1106";
static const uint8_t C_M1106_CMD_GET_CO2[4] = {0x11, 0x01, 0x01, 0xED};
static const uint8_t C_M1106_CMD_SET_CO2_CALIB[6] = {0x11, 0x03, 0x03, 0x00, 0x00, 0x00};
static const uint8_t C_M1106_CMD_SET_CO2_CALIB_RESPONSE[4] = {0x16, 0x01, 0x03, 0xE6};
uint8_t cm1106_checksum(const uint8_t *response, size_t len) {
uint8_t crc = 0;
for (int i = 0; i < len - 1; i++) {
crc -= response[i];
}
return crc;
}
void CM1106Component::setup() {
ESP_LOGCONFIG(TAG, "Running 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))) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
this->mark_failed();
return;
}
}
void CM1106Component::update() {
uint8_t response[8] = {0};
if (!this->cm1106_write_command_(C_M1106_CMD_GET_CO2, sizeof(C_M1106_CMD_GET_CO2), response, sizeof(response))) {
ESP_LOGW(TAG, "Reading data from CM1106 failed!");
this->status_set_warning();
return;
}
if (response[0] != 0x16 || response[1] != 0x05 || response[2] != 0x01) {
ESP_LOGW(TAG, "Got wrong UART response from CM1106: %02X %02X %02X %02X", response[0], response[1], response[2],
response[3]);
this->status_set_warning();
return;
}
uint8_t checksum = cm1106_checksum(response, sizeof(response));
if (response[7] != checksum) {
ESP_LOGW(TAG, "CM1106 Checksum doesn't match: 0x%02X!=0x%02X", response[7], checksum);
this->status_set_warning();
return;
}
this->status_clear_warning();
uint16_t ppm = response[3] << 8 | response[4];
ESP_LOGD(TAG, "CM1106 Received CO₂=%uppm DF3=%02X DF4=%02X", ppm, response[5], response[6]);
if (this->co2_sensor_ != nullptr)
this->co2_sensor_->publish_state(ppm);
}
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;
uint8_t response[4] = {0};
if (!this->cm1106_write_command_(cmd, sizeof(cmd), response, sizeof(response))) {
ESP_LOGW(TAG, "Reading data from CM1106 failed!");
this->status_set_warning();
return;
}
// check if correct response received
if (memcmp(response, C_M1106_CMD_SET_CO2_CALIB_RESPONSE, sizeof(response)) != 0) {
ESP_LOGW(TAG, "Got wrong UART response from CM1106: %02X %02X %02X %02X", response[0], response[1], response[2],
response[3]);
this->status_set_warning();
return;
}
this->status_clear_warning();
ESP_LOGD(TAG, "CM1106 Successfully calibrated sensor to %uppm", ppm);
}
bool CM1106Component::cm1106_write_command_(const uint8_t *command, size_t command_len, uint8_t *response,
size_t response_len) {
// Empty RX Buffer
while (this->available())
this->read();
this->write_array(command, command_len - 1);
this->write_byte(cm1106_checksum(command, command_len));
this->flush();
if (response == nullptr)
return true;
return this->read_array(response, response_len);
}
void CM1106Component::dump_config() {
ESP_LOGCONFIG(TAG, "CM1106:");
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
this->check_uart_settings(9600);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
}
} // namespace cm1106
} // namespace esphome

View File

@@ -1,40 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace cm1106 {
class CM1106Component : public PollingComponent, public uart::UARTDevice {
public:
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
void setup() override;
void update() override;
void dump_config() override;
void calibrate_zero(uint16_t ppm);
void set_co2_sensor(sensor::Sensor *co2_sensor) { this->co2_sensor_ = co2_sensor; }
protected:
sensor::Sensor *co2_sensor_{nullptr};
bool cm1106_write_command_(const uint8_t *command, size_t command_len, uint8_t *response, size_t response_len);
};
template<typename... Ts> class CM1106CalibrateZeroAction : public Action<Ts...> {
public:
CM1106CalibrateZeroAction(CM1106Component *cm1106) : cm1106_(cm1106) {}
void play(Ts... x) override { this->cm1106_->calibrate_zero(400); }
protected:
CM1106Component *cm1106_;
};
} // namespace cm1106
} // namespace esphome

View File

@@ -1,72 +0,0 @@
"""CM1106 Sensor component for ESPHome."""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import sensor, uart
from esphome.const import (
CONF_CO2,
CONF_ID,
DEVICE_CLASS_CARBON_DIOXIDE,
ICON_MOLECULE_CO2,
STATE_CLASS_MEASUREMENT,
UNIT_PARTS_PER_MILLION,
)
DEPENDENCIES = ["uart"]
CODEOWNERS = ["@andrewjswan"]
cm1106_ns = cg.esphome_ns.namespace("cm1106")
CM1106Component = cm1106_ns.class_(
"CM1106Component", cg.PollingComponent, uart.UARTDevice
)
CM1106CalibrateZeroAction = cm1106_ns.class_(
"CM1106CalibrateZeroAction",
automation.Action,
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CM1106Component),
cv.Optional(CONF_CO2): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_MOLECULE_CO2,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
},
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
async def to_code(config) -> None:
"""Code generation entry point."""
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
if co2_config := config.get(CONF_CO2):
sens = await sensor.new_sensor(co2_config)
cg.add(var.set_co2_sensor(sens))
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(): cv.use_id(CM1106Component),
},
)
@automation.register_action(
"cm1106.calibrate_zero",
CM1106CalibrateZeroAction,
CALIBRATION_ACTION_SCHEMA,
)
async def cm1106_calibration_to_code(config, action_id, template_arg, args) -> None:
"""Service code generation entry point."""
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)

View File

@@ -7,7 +7,7 @@ CODEOWNERS = ["@glmnet"]
coolix_ns = cg.esphome_ns.namespace("coolix")
CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(CoolixClimate)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(CoolixClimate)
async def to_code(config):

View File

@@ -52,7 +52,7 @@ bool CS5460AComponent::softreset_() {
}
void CS5460AComponent::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up CS5460A...");
float current_full_scale = (pga_gain_ == CS5460A_PGA_GAIN_10X) ? 0.25 : 0.10;
float voltage_full_scale = 0.25;

View File

@@ -42,7 +42,7 @@ static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
void CSE7761Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up CSE7761...");
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
if ((0x0A04 == syscon) && this->chip_init_()) {
@@ -57,7 +57,7 @@ void CSE7761Component::setup() {
void CSE7761Component::dump_config() {
ESP_LOGCONFIG(TAG, "CSE7761:");
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with CSE7761 failed!");
}
LOG_UPDATE_INTERVAL(this);
this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);

View File

@@ -1,6 +1,5 @@
#include "cse7766.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace cse7766 {
@@ -8,7 +7,7 @@ namespace cse7766 {
static const char *const TAG = "cse7766";
void CSE7766Component::loop() {
const uint32_t now = App.get_loop_component_start_time();
const uint32_t now = millis();
if (now - this->last_transmission_ >= 500) {
// last transmission too long ago. Reset RX index.
this->raw_data_index_ = 0;

View File

@@ -6,7 +6,7 @@ namespace cst226 {
static const char *const TAG = "cst226.touchscreen";
void CST226Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up CST226 Touchscreen...");
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);

View File

@@ -38,7 +38,7 @@ void CST816Touchscreen::continue_setup_() {
}
void CST816Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up CST816 Touchscreen...");
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);

View File

@@ -1,7 +1,6 @@
#include "current_based_cover.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <cfloat>
namespace esphome {
@@ -61,7 +60,7 @@ void CurrentBasedCover::loop() {
if (this->current_operation == COVER_OPERATION_IDLE)
return;
const uint32_t now = App.get_loop_component_start_time();
const uint32_t now = millis();
if (this->current_operation == COVER_OPERATION_OPENING) {
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction

View File

@@ -20,9 +20,9 @@ static const uint8_t DAC7678_REG_INTERNAL_REF_0 = 0x80;
static const uint8_t DAC7678_REG_INTERNAL_REF_1 = 0x90;
void DAC7678Output::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up DAC7678OutputComponent...");
ESP_LOGV(TAG, "Resetting device");
ESP_LOGV(TAG, "Resetting device...");
// Reset device
if (!this->write_byte_16(DAC7678_REG_SOFTWARE_RESET, 0x0000)) {

View File

@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
daikin_ns = cg.esphome_ns.namespace("daikin")
DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinClimate)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinClimate)
async def to_code(config):

View File

@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
daikin_arc_ns = cg.esphome_ns.namespace("daikin_arc")
DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinArcClimate)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinArcClimate)
async def to_code(config):

View File

@@ -9,7 +9,7 @@ daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc")
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinBrcClimate).extend(
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinBrcClimate).extend(
{
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
}

View File

@@ -70,7 +70,7 @@ bool DallasTemperatureSensor::read_scratch_pad_() {
}
void DallasTemperatureSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "setting up Dallas temperature sensor...");
if (!this->check_address_())
return;
if (!this->read_scratch_pad_())
@@ -80,7 +80,7 @@ void DallasTemperatureSensor::setup() {
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
// DS18S20 doesn't support resolution.
ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution");
ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
return;
}
@@ -125,6 +125,7 @@ bool DallasTemperatureSensor::check_scratch_pad_() {
crc8(this->scratch_pad_, 8));
#endif
if (!chksum_validity) {
ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
this->status_set_warning("scratch pad checksum invalid");
ESP_LOGD(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],

View File

@@ -1,7 +1,6 @@
#include "daly_bms.h"
#include <vector>
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace daly_bms {
@@ -33,7 +32,7 @@ void DalyBmsComponent::update() {
}
void DalyBmsComponent::loop() {
const uint32_t now = App.get_loop_component_start_time();
const uint32_t now = millis();
if (this->receiving_ && (now - this->last_transmission_ >= 200)) {
// last transmission too long ago. Reset RX index.
ESP_LOGW(TAG, "Last transmission too long ago. Reset RX index.");

Some files were not shown because too many files have changed in this diff Show More