1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-11 04:15:52 +00:00

Compare commits

..

141 Commits

Author SHA1 Message Date
J. Nick Koston
595ee4da39 add bluetooth proxy stats 2025-05-29 12:13:48 -05:00
Keith Burzinski
4031077f6d [dht] Clean-up, shorten some log messages (#8949) 2025-05-29 21:37:41 +12:00
J. Nick Koston
fd72a64053 Redundant Log Messages Cleanup (#8944)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-05-29 09:36:23 +00:00
Jesse Hills
959a8b91bd [demo] FIx some of the entities (#8943) 2025-05-29 03:22:46 -05:00
Keith Burzinski
44f1ff10e6 Introduce "communication failed" log macro (#8939) 2025-05-29 01:59:38 -05:00
Jesse Hills
64e4589f4e [esp32] Regenerate boards from recommended platform version (#8938) 2025-05-29 05:21:05 +00:00
Kuba Szczodrzyński
20aba45cbe [rp2040] Allow changing watchdog timeout (#8868) 2025-05-29 17:15:26 +12:00
gotnone
0b1c5b825e [modbus] [modbus_controller] Fix server role read coil 0x1 crc (#8859)
Co-authored-by: Stanley Pinchak <stanley.pinchak@gmail.com>
2025-05-29 17:12:58 +12:00
Leicas
455624105b Add flip X and Y on inkplate6 component (#7904)
Co-authored-by: Antoine Weill--Duflos <antoine@haply.co>
Co-authored-by: David Sichau <sichau@inf.ethz.ch>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: David Sichau <DavidSichau@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-29 17:10:06 +12:00
Jonathan Swoboda
7ac5746e0d Fix colors in update all (#8854) 2025-05-29 15:00:27 +12:00
Thomas Rupprecht
12997451f6 particle matter improvements (#8846) 2025-05-29 14:57:20 +12:00
J. Nick Koston
8c77e40695 Fix select() logging flood in very verbose mode (#8942) 2025-05-29 02:29:37 +00:00
Nate Clark
2ddd91acf2 [alarm_control_panel] BYPASS_AUTO option for Template Alarm Control Panel sensors left open when armed (#8795) 2025-05-29 14:22:26 +12:00
Keith Burzinski
729e49cdc3 [gcja5] Remove unused setup() method (#8935) 2025-05-29 14:01:00 +12:00
J. Nick Koston
d64b49cc13 Optimize plaintext API header reading to reduce system calls (#8941)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-28 20:46:23 -05:00
Keith Burzinski
cfa8b3b272 [tmp102] Remove setup(), optimize logging (#8937) 2025-05-29 11:44:31 +12:00
Keith Burzinski
51981335d5 [pmwcs3] Optimize logging (#8936) 2025-05-29 11:44:03 +12:00
J. Nick Koston
70c5e1bbf1 Improve logging in integration tests when port does not open (#8932)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-29 11:42:19 +12:00
J. Nick Koston
43e88af28a Optimize socket operations by checking readiness in the main loop (#8918) 2025-05-28 18:16:37 -05:00
J. Nick Koston
ffc66f539f Migrate wifi component to use App.get_loop_component_start_time (#8931) 2025-05-29 09:33:54 +12:00
Jesse Hills
c4cb694d77 Add more demo platforms (#8903) 2025-05-28 15:23:45 -05:00
Jesse Hills
3fb9577ad9 [i2s_audio] Bump esphome/ESP32-audioI2S to 2.2.0 (#8920) 2025-05-29 08:12:03 +12:00
mrtoy-me
34169491ac [speaker mediaplayer] Yaml config initial volume (on first boot) (#8898) 2025-05-28 15:37:25 -04:00
Keith Burzinski
8eac859bab Streamline setup() logging (s, t, u, v, w, x, y, z) (#8930) 2025-05-29 07:28:41 +12:00
Keith Burzinski
d99e3237f9 Streamline setup() logging (n, o, p, q, r) (#8929) 2025-05-29 07:23:53 +12:00
Keith Burzinski
d9a9e0aea3 Streamline setup() logging (k, l, m) (#8928) 2025-05-29 06:57:58 +12:00
Keith Burzinski
0ce03ae26b Streamline setup() logging (g, h, i) (#8927) 2025-05-29 06:55:02 +12:00
Keith Burzinski
18653f8f69 Streamline setup() logging (e, f) (#8926) 2025-05-28 12:12:46 +00:00
Keith Burzinski
6e0523109a Streamline setup() logging (c, d) (#8925) 2025-05-28 22:54:38 +12:00
Keith Burzinski
b6fa4f641d Streamline setup() logging (a, b) (#8924) 2025-05-28 22:53:51 +12:00
Keith Burzinski
ca6295d1bd [ledc] Various optimizations/clean-up (#8922) 2025-05-28 22:50:48 +12:00
Keith Burzinski
18a1d31845 [rtttl] Various optimizations/clean-up (#8923) 2025-05-28 22:48:54 +12:00
Keith Burzinski
c5239a63ab [aht10] Various optimizations/clean-up (#8921) 2025-05-28 22:22:05 +12:00
Craig Andrews
1911269dc9 [online_image] Last-Modified-Date and ETag response caching (#8782) 2025-05-28 17:17:57 +12:00
Thomas SAMTER
04ee1a87e9 Add es8388 audio_dac (#8342)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-28 14:00:00 +12:00
Kevin Ahrendt
a8fdb6db4d [i2s-audio] ensure mic task isn't pinned to a core (#8879) 2025-05-28 08:47:42 +12:00
dependabot[bot]
8860c74f0c Bump docker/build-push-action from 6.17.0 to 6.18.0 in /.github/actions/build-image (#8919)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-28 08:45:24 +12:00
Jesse Hills
d585440d54 Merge branch 'release' into dev 2025-05-27 21:02:03 +12:00
dependabot[bot]
7d049a61bb Bump pytest-xdist from 3.6.1 to 3.7.0 (#8916)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 06:55:47 +00:00
dependabot[bot]
f2e4dc7907 Bump setuptools from 80.8.0 to 80.9.0 (#8915)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 06:54:33 +00:00
dependabot[bot]
0c7589caeb Bump pytest-mock from 3.14.0 to 3.14.1 (#8909)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 17:26:14 +12:00
dependabot[bot]
321411e355 Bump ruamel-yaml from 0.18.10 to 0.18.11 (#8910)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 17:26:08 +12:00
Samuel Sieb
361de22370 [sx1509] add support for keys (#8413)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-27 17:16:27 +12:00
Jesse Hills
95a17387a8 Bump actions/checkout from 4.1.7 to 4.2.2 (#8904) 2025-05-27 16:26:01 +12:00
J. Nick Koston
caf9930ff9 Fix flakey tests (#8914) 2025-05-27 16:20:14 +12:00
J. Nick Koston
4ac433fddb Add integration tests for host (#8912) 2025-05-26 21:31:32 -05:00
Jesse Hills
73771d5c50 [web_server] Fix download list where external_components has a substitution value (#8911)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-27 09:08:16 +12:00
Jesse Hills
af7b1a3a23 [api] Fix crash with gcc compiler on host (#8902) 2025-05-27 06:46:51 +12:00
dependabot[bot]
430f63fcbb Bump pyupgrade from 3.19.1 to 3.20.0 (#8891)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-26 08:13:45 +00:00
Emmanuel Ferdman
5921a9cd68 Resolve regex library warnings (#8890) 2025-05-26 19:45:47 +12:00
Jonathan Swoboda
ca0037d076 [esp32, logger, core] Add initial c5 support (#8895) 2025-05-26 13:33:41 +12:00
Jesse Hills
1e18d0b06c [i2s_audio] Add basic support for esp32-p4 (#8887) 2025-05-26 11:55:51 +12:00
luar123
4b5c3e7e2b [bme68x_bsec2_i2c] Remove arduino dependency (#7815) 2025-05-25 03:08:51 -05:00
Keith Burzinski
d4c4b75eb3 [esp32] Fix building on IDF 4 (#8892) 2025-05-25 02:15:24 +12:00
Jesse Hills
9dd4045984 [const] Move `CONF_RESET` to const.py (#8889) 2025-05-23 21:54:06 -05:00
gotnone
19e2460af2 [modbus_controller] Add assumed_state to switch (#8880)
Co-authored-by: Stanley Pinchak <stanley.pinchak@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-23 21:34:10 +12:00
Rodrigo Martín
149f787035 feat: wifi.configure now emits error after reconnecting to old AP (#8653) 2025-05-23 21:32:47 +12:00
J. Nick Koston
2ab1fe1abf Use UINT16_MAX instead of hard coded 65535 in api (#8884)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-22 23:51:06 +00:00
Jesse Hills
926b42ba1c [logger] Fix options in select (#8875)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-05-23 09:33:38 +10:00
J. Nick Koston
377ed2e212 Optimize API frame helper buffer management (#8805)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-22 21:11:16 +00:00
esphomebot
42912447fb Synchronise Device Classes from Home Assistant (#8874) 2025-05-23 08:50:31 +12:00
Pi57
25ead44f1c Add const DEVICE_CLASS_WIND_DIRECTION (#8870)
Co-authored-by: PierreYvesHB <pierre-yves.henius-beck@act-blue.eu>
2025-05-23 08:49:37 +12:00
dependabot[bot]
03b003af47 Bump ruff from 0.11.10 to 0.11.11 (#8883)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-22 20:22:04 +00:00
dependabot[bot]
5baccf0ce7 Bump tornado from 6.4.2 to 6.5.1 (#8882)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-22 20:21:46 +00:00
Kevin Ahrendt
e95c92773c [speaker] ensure the pipeline returns an error state before returning its stopped (#8878) 2025-05-23 07:20:15 +12:00
Kevin Ahrendt
c23ea384fb [micro_wake_word] avoid duplicated detections from same event (#8877) 2025-05-23 07:19:16 +12:00
Lưu Oa Oa (宰相劉羅鍋)
69da17742f OTA: Close and clean up client when setsockopt fails (#8865)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-05-22 11:37:42 -05:00
Clyde Stubbs
1ec57a74b5 [usb_uart] Implement USB Host mode UART (#8334) 2025-05-22 13:54:40 +12:00
Clyde Stubbs
d1e55252d0 [lvgl] Improve error messages from text validation (#8872) 2025-05-22 13:49:56 +12:00
Clyde Stubbs
090feb55e9 [lvgl] Add content styling to tabview (#8823) 2025-05-22 13:47:38 +12:00
Clyde Stubbs
6109acb6f3 [lvgl] Try to allocate smaller buffer on failure (#8814)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-22 13:45:56 +12:00
Jesse Hills
5aa13db815 [online_image] Allocate pngle manually to potentially use psram (#8354)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-05-22 13:40:53 +12:00
Jesse Hills
1b67dd4232 [sync] Update and fix sync workflow (#8873) 2025-05-22 13:32:19 +12:00
Cossid
ba6efcedcb [tuya_select] - Fix datapoint config error. (#8871) 2025-05-22 13:26:19 +12:00
Jesse Hills
bd7c2a680c Updates for development environment (#8801) 2025-05-22 13:24:34 +12:00
Andrew J.Swan
1466aa7703 Add CUBIC CM1106 Single Beam NDIR CO2 Sensor Module (#8293)
Co-authored-by: Djordje <6750655+DjordjeMandic@users.noreply.github.com>
Co-authored-by: Patrick <info@patagona.dev>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-22 13:23:54 +12:00
Jonathan Swoboda
787f4860db [esp32, logger] Add initial P4 support (#8439)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-22 13:22:01 +12:00
Thomas Rupprecht
aeb4e63950 update minimal python version to 3.10 (#8850) 2025-05-22 13:21:43 +12:00
Jonathan Swoboda
026f47bfb3 [esp32] Use IDF 5.3.2 as default for IDF builds (#8464)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2025-05-22 13:21:27 +12:00
Jesse Hills
dd47d063b5 Merge branch 'release' into dev 2025-05-21 20:33:34 +12:00
Jesse Hills
a6fa963605 [core] Add some missing includes (#8864) 2025-05-21 20:02:14 +12:00
Jesse Hills
f2d7720a4e Merge branch 'beta' into dev 2025-05-21 13:09:35 +12:00
Jesse Hills
e9d832d64a [api-docs] Move netlify.toml to root (#8861) 2025-05-21 11:43:19 +12:00
Jesse Hills
f8f09bca02 Merge branch 'beta' into dev 2025-05-21 11:26:20 +12:00
dependabot[bot]
c5d809b3dd Bump setuptools from 80.7.1 to 80.8.0 (#8858)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-20 16:27:53 -04:00
Thomas Rupprecht
b1cf08b261 add python 3.13 to ci pipeline (#8855) 2025-05-20 10:04:09 -04:00
Gustavo Ambrozio
6ae83dfe3d [at581x] Fix issue with methods not being public (#8852) 2025-05-20 02:36:44 -05:00
Thomas Rupprecht
0932e83b15 update ruff version to `0.11.10 in .pre-commit-config.yaml` (#8851) 2025-05-20 00:42:43 -04:00
Clyde Stubbs
86670c4d39 Fix #ifdefs (#8853) 2025-05-20 13:19:24 +10:00
dependabot[bot]
4ce55b94ec Bump aioesphomeapi from 31.0.1 to 31.1.0 (#8849) 2025-05-19 20:30:30 -04:00
Jesse Hills
1c5dc63eb4 Merge branch 'beta' into dev 2025-05-20 01:19:32 +12:00
Jesse Hills
ef7a22ff04 [api-docs] Run using netlify builders (#8842)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-19 19:48:04 +12:00
Jesse Hills
dfda0e5c7c [docker] Update pip on build (#8835)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-05-19 18:22:24 +12:00
J. Nick Koston
78c63311c6 Fix ethernet connection timeout issue caused by incorrect time value during setup (#8841) 2025-05-19 00:25:21 -05:00
Jesse Hills
1ac51e7b3e Merge branch 'beta' into dev 2025-05-19 16:03:18 +12:00
J. Nick Koston
5b552b9ec5 Fix API connection sending ping too early after connection establishment (#8840) 2025-05-19 15:22:36 +12:00
Jesse Hills
d36ce7c010 [release] Don't wait for docker to be finished before deploying schema (#8838) 2025-05-19 14:17:01 +12:00
Jesse Hills
b8a96f59f0 [release] Fix output value (#8839) 2025-05-19 14:16:39 +12:00
Jesse Hills
2e15ee232d Deploy doxygen docs to netlify (#8837) 2025-05-19 14:09:38 +12:00
Jesse Hills
904495e1b8 Fix api doc homepage (#8836)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-19 13:57:41 +12:00
Fexiven
99c4f88c3f Update esp32-camera library version (#8832) 2025-05-19 11:01:31 +12:00
DanielV
87a9dd18c8 Improve stability for a test that crashes intermittently in CI (#8699)
Co-authored-by: NP v/d Spek <github_mail@lumensoft.nl>
2025-05-19 10:01:30 +12:00
Thomas Rupprecht
dbce54477a unify and add missing metric suffixes (#8816) 2025-05-18 21:44:33 +00:00
dependabot[bot]
660030d157 Bump docker/build-push-action from 6.16.0 to 6.17.0 in /.github/actions/build-image (#8810)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-19 09:20:29 +12:00
dependabot[bot]
24fbe602dd Bump codecov/codecov-action from 5.4.2 to 5.4.3 (#8820)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-19 09:13:20 +12:00
J. Nick Koston
b0c1e0e28c Refactor API frame helpers to enable buffer reuse (#8825) 2025-05-19 09:05:20 +12:00
J. Nick Koston
574aabdede Reduce number of calls to fetch time in the main loop (#8804) 2025-05-19 07:48:57 +12:00
J. Nick Koston
e47741d471 Fix ESP32 console logging corruption and message loss in multi-task (#8806) 2025-05-19 07:43:41 +12:00
Kent Gibson
a78bea78f9 Fix misspelling of climate in climate_ir.climate_ir_with_receiver_schema (#8829) 2025-05-18 03:45:12 +00:00
Clyde Stubbs
44470f31f6 Revert "[binary_sensor] initial state refactor" (#8828) 2025-05-18 03:30:08 +00:00
Anton Sergunov
18ac1b7c54 Fix the case of single error (#8824) 2025-05-18 15:11:09 +12:00
Keith Burzinski
e87b659483 [sen5x] Fix validation for values read from hardware (#8769) 2025-05-18 15:05:03 +12:00
J. Nick Koston
fefcb45e1f Bump cryptography to 45.0.1 (#8826) 2025-05-18 14:50:06 +12:00
Clyde Stubbs
5c92367ca2 [script] Use local import for zephyr (#8822) 2025-05-16 23:41:19 +00:00
dependabot[bot]
b469a504e4 Bump cairosvg from 2.8.1 to 2.8.2 (#8817)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-16 15:42:51 -04:00
dependabot[bot]
218f8e0caf Bump ruff from 0.11.9 to 0.11.10 (#8818)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-16 15:42:44 -04:00
J. Nick Koston
7965558d5e Fix ESP32 Camera class inheritance (#8811) 2025-05-16 11:42:54 +12:00
dependabot[bot]
d9b860088e Bump setuptools from 80.4.0 to 80.7.1 (#8808)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-15 15:45:01 -05:00
dependabot[bot]
115975c409 Bump aioesphomeapi from 31.0.0 to 31.0.1 (#8809)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-15 15:44:47 -05:00
Thomas Rupprecht
4761ffe023 [gps] update lib, improve code/tests/config (#8768) 2025-05-15 22:07:41 +12:00
Thomas Rupprecht
88edddf07a [log] improve/refactor log (#8708) 2025-05-15 21:45:07 +12:00
J. Nick Koston
0b77cb1d16 Logger Recursion Guard per Task on ESP32 (#8765) 2025-05-15 21:36:28 +12:00
J. Nick Koston
efa6745a5e Optimize protobuf varint decoder for ESPHome use case (#8791) 2025-05-15 17:16:25 +12:00
J. Nick Koston
dd8d8ad952 Use fixed buffer for plaintext protocol like noise protocol (#8800) 2025-05-15 17:16:08 +12:00
dependabot[bot]
57284b1ac3 Bump cairosvg from 2.8.0 to 2.8.1 (#8799)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 23:26:28 -05:00
Jesse Hills
1a651ce66d Update some sensor schemas to be Optional (#8803) 2025-05-15 02:40:11 +00:00
Jesse Hills
730441c120 [api] Update api proto to add legacy value (#8802) 2025-05-14 21:26:21 -05:00
J. Nick Koston
bb1f24ab43 Avoid protobuf message construction when tx buffer is full (#8787)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-14 21:25:44 -05:00
NP v/d Spek
edb8d187be add actions to the MAX7219Component (#6462)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-15 09:15:04 +12:00
Jesse Hills
e7b6081c5c Merge branch 'beta' into dev 2025-05-15 06:51:16 +12:00
dependabot[bot]
5454500024 Bump cairosvg from 2.7.1 to 2.8.0 (#8780)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-14 11:20:22 -05:00
Jesse Hills
191afd3e69 Bump esphome-dashboard to 20250514.0 (#8790) 2025-05-14 02:08:16 -05:00
Jesse Hills
de27ce79dc [climate] Update components to use `climate_schema(...)` (#8788) 2025-05-14 05:36:21 +00:00
Jesse Hills
a12bd78ceb Fix release to pypi (#8789) 2025-05-14 16:35:30 +12:00
J. Nick Koston
ddb986b4fa Improve batching of BLE advertisements for better airtime efficiency (#8778) 2025-05-14 04:34:33 +00:00
Jesse Hills
c98c78e368 Merge branch 'beta' into dev 2025-05-14 15:55:25 +12:00
dependabot[bot]
5570a788fd Bump aioesphomeapi from 30.2.0 to 31.0.0 (#8779)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-13 21:23:32 -05:00
Jesse Hills
42c355e6d7 [fan] Update components to use `fan_schema(...)` (#8786) 2025-05-13 20:30:11 -05:00
Jesse Hills
a835ab48bc [schema] Get component name if available for deprecation warning (#8785) 2025-05-13 20:25:21 -05:00
Jesse Hills
f28a373898 [media_player] Deprecate `MEDIA_PLAYER_SCHEMA` (#8784) 2025-05-13 23:48:54 +00:00
Jesse Hills
28e29efd98 Bump version to 2025.6.0-dev 2025-05-14 09:54:26 +12:00
490 changed files with 6517 additions and 1556 deletions

View File

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

37
.devcontainer/Dockerfile Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ jobs:
branch_build: ${{ steps.tag.outputs.branch_build }} branch_build: ${{ steps.tag.outputs.branch_build }}
deploy_env: ${{ steps.tag.outputs.deploy_env }} deploy_env: ${{ steps.tag.outputs.deploy_env }}
steps: steps:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.2.2
- name: Get tag - name: Get tag
id: tag id: tag
# yamllint disable rule:line-length # yamllint disable rule:line-length
@@ -60,7 +60,7 @@ jobs:
contents: read contents: read
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.2.2
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@v5.6.0
with: with:
@@ -92,11 +92,11 @@ jobs:
os: "ubuntu-24.04-arm" os: "ubuntu-24.04-arm"
steps: steps:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.2.2
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@v5.6.0
with: with:
python-version: "3.9" python-version: "3.10"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.10.0 uses: docker/setup-buildx-action@v3.10.0
@@ -168,7 +168,7 @@ jobs:
- ghcr - ghcr
- dockerhub - dockerhub
steps: steps:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.2.2
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4.3.0 uses: actions/download-artifact@v4.3.0

View File

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

View File

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

View File

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

View File

@@ -96,6 +96,7 @@ esphome/components/ch422g/* @clydebarrow @jesterret
esphome/components/chsc6x/* @kkosik20 esphome/components/chsc6x/* @kkosik20
esphome/components/climate/* @esphome/core esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet esphome/components/climate_ir/* @glmnet
esphome/components/cm1106/* @andrewjswan
esphome/components/color_temperature/* @jesserockz esphome/components/color_temperature/* @jesserockz
esphome/components/combination/* @Cat-Ion @kahrendt esphome/components/combination/* @Cat-Ion @kahrendt
esphome/components/const/* @esphome/core esphome/components/const/* @esphome/core
@@ -138,6 +139,7 @@ esphome/components/es7210/* @kahrendt
esphome/components/es7243e/* @kbx81 esphome/components/es7243e/* @kbx81
esphome/components/es8156/* @kbx81 esphome/components/es8156/* @kbx81
esphome/components/es8311/* @kahrendt @kroimon esphome/components/es8311/* @kahrendt @kroimon
esphome/components/es8388/* @P4uLT
esphome/components/esp32/* @esphome/core esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @Rapsssito @jesserockz esphome/components/esp32_ble/* @Rapsssito @jesserockz
esphome/components/esp32_ble_client/* @jesserockz esphome/components/esp32_ble_client/* @jesserockz
@@ -478,6 +480,8 @@ esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter esphome/components/ultrasonic/* @OttoWinter
esphome/components/update/* @jesserockz esphome/components/update/* @jesserockz
esphome/components/uponor_smatrix/* @kroimon esphome/components/uponor_smatrix/* @kroimon
esphome/components/usb_host/* @clydebarrow
esphome/components/usb_uart/* @clydebarrow
esphome/components/valve/* @esphome/core esphome/components/valve/* @esphome/core
esphome/components/vbus/* @ssieb esphome/components/vbus/* @ssieb
esphome/components/veml3235/* @kbx81 esphome/components/veml3235/* @kbx81

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = 2025.5.2 PROJECT_NUMBER = 2025.6.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ namespace absolute_humidity {
static const char *const TAG = "absolute_humidity.sensor"; static const char *const TAG = "absolute_humidity.sensor";
void AbsoluteHumidityComponent::setup() { void AbsoluteHumidityComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up absolute humidity '%s'...", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->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); }); 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; static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;
void ADCSensor::setup() { void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "Running setup for '%s'...", this->get_name().c_str());
if (this->channel1_ != ADC1_CHANNEL_MAX) { if (this->channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -54,7 +54,7 @@ enum { // APDS9306 registers
} }
void APDS9306::setup() { void APDS9306::setup() {
ESP_LOGCONFIG(TAG, "Setting up APDS9306..."); ESP_LOGCONFIG(TAG, "Running setup");
uint8_t id; uint8_t id;
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
@@ -97,7 +97,7 @@ void APDS9306::dump_config() {
if (this->is_failed()) { if (this->is_failed()) {
switch (this->error_code_) { switch (this->error_code_) {
case COMMUNICATION_FAILED: case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with APDS9306 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break; break;
case WRONG_ID: case WRONG_ID:
ESP_LOGE(TAG, "APDS9306 has invalid 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)); #define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
void APDS9960::setup() { void APDS9960::setup() {
ESP_LOGCONFIG(TAG, "Setting up APDS9960..."); ESP_LOGCONFIG(TAG, "Running setup");
uint8_t id; uint8_t id;
if (!this->read_byte(0x92, &id)) { // ID register if (!this->read_byte(0x92, &id)) { // ID register
this->error_code_ = COMMUNICATION_FAILED; this->error_code_ = COMMUNICATION_FAILED;
@@ -141,7 +141,7 @@ void APDS9960::dump_config() {
if (this->is_failed()) { if (this->is_failed()) {
switch (this->error_code_) { switch (this->error_code_) {
case COMMUNICATION_FAILED: case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with APDS9960 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break; break;
case WRONG_ID: case WRONG_ID:
ESP_LOGE(TAG, "APDS9960 has invalid id!"); ESP_LOGE(TAG, "APDS9960 has invalid id!");

View File

@@ -432,7 +432,8 @@ message FanCommandRequest {
enum ColorMode { enum ColorMode {
COLOR_MODE_UNKNOWN = 0; COLOR_MODE_UNKNOWN = 0;
COLOR_MODE_ON_OFF = 1; COLOR_MODE_ON_OFF = 1;
COLOR_MODE_BRIGHTNESS = 2; COLOR_MODE_LEGACY_BRIGHTNESS = 2;
COLOR_MODE_BRIGHTNESS = 3;
COLOR_MODE_WHITE = 7; COLOR_MODE_WHITE = 7;
COLOR_MODE_COLOR_TEMPERATURE = 11; COLOR_MODE_COLOR_TEMPERATURE = 11;
COLOR_MODE_COLD_WARM_WHITE = 19; COLOR_MODE_COLD_WARM_WHITE = 19;

View File

@@ -135,6 +135,9 @@ void APIConnection::loop() {
api_error_to_str(err), errno); api_error_to_str(err), errno);
return; return;
} }
// Check if socket has data ready before attempting to read
if (this->helper_->is_socket_ready()) {
ReadPacketBuffer buffer; ReadPacketBuffer buffer;
err = this->helper_->read_packet(&buffer); err = this->helper_->read_packet(&buffer);
if (err == APIError::WOULD_BLOCK) { if (err == APIError::WOULD_BLOCK) {
@@ -161,6 +164,7 @@ void APIConnection::loop() {
if (this->remove_) if (this->remove_)
return; return;
} }
}
if (!this->deferred_message_queue_.empty() && this->helper_->can_write_without_blocking()) { if (!this->deferred_message_queue_.empty() && this->helper_->can_write_without_blocking()) {
this->deferred_message_queue_.process_queue(); this->deferred_message_queue_.process_queue();

View File

@@ -7,20 +7,13 @@
#include "proto.h" #include "proto.h"
#include "api_pb2_size.h" #include "api_pb2_size.h"
#include <cstring> #include <cstring>
#include <cinttypes>
namespace esphome { namespace esphome {
namespace api { namespace api {
static const char *const TAG = "api.socket"; static const char *const TAG = "api.socket";
/// Is the given return value (from write syscalls) a wouldblock error?
bool is_would_block(ssize_t ret) {
if (ret == -1) {
return errno == EWOULDBLOCK || errno == EAGAIN;
}
return ret == 0;
}
const char *api_error_to_str(APIError err) { const char *api_error_to_str(APIError err) {
// not using switch to ensure compiler doesn't try to build a big table out of it // not using switch to ensure compiler doesn't try to build a big table out of it
if (err == APIError::OK) { if (err == APIError::OK) {
@@ -73,92 +66,154 @@ const char *api_error_to_str(APIError err) {
return "UNKNOWN"; return "UNKNOWN";
} }
// Common implementation for writing raw data to socket // Helper method to buffer data from IOVs
template<typename StateEnum> void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, SendBuffer buffer;
std::vector<uint8_t> &tx_buf, const std::string &info, StateEnum &state, buffer.data.reserve(total_write_len);
StateEnum failed_state) { for (int i = 0; i < iovcnt; i++) {
const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base);
buffer.data.insert(buffer.data.end(), data, data + iov[i].iov_len);
}
this->tx_buf_.push_back(std::move(buffer));
}
// This method writes data to socket or buffers it // This method writes data to socket or buffers it
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
// Returns APIError::OK if successful (or would block, but data has been buffered) // Returns APIError::OK if successful (or would block, but data has been buffered)
// Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to failed_state // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to FAILED
if (iovcnt == 0) if (iovcnt == 0)
return APIError::OK; // Nothing to do, success return APIError::OK; // Nothing to do, success
size_t total_write_len = 0; uint16_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) { for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str()); format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif #endif
total_write_len += iov[i].iov_len; total_write_len += static_cast<uint16_t>(iov[i].iov_len);
} }
if (!tx_buf.empty()) { // Try to send any existing buffered data first if there is any
// try to empty tx_buf first if (!this->tx_buf_.empty()) {
while (!tx_buf.empty()) { APIError send_result = try_send_tx_buf_();
ssize_t sent = socket->write(tx_buf.data(), tx_buf.size()); // If real error occurred (not just WOULD_BLOCK), return it
if (is_would_block(sent)) { if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
break; return send_result;
} else if (sent == -1) {
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
state = failed_state;
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
}
// TODO: inefficient if multiple packets in txbuf
// replace with deque of buffers
tx_buf.erase(tx_buf.begin(), tx_buf.begin() + sent);
}
} }
if (!tx_buf.empty()) { // If there is still data in the buffer, we can't send, buffer
// tx buf not empty, can't write now because then stream would be inconsistent // the new data and return
// Reserve space upfront to avoid multiple reallocations if (!this->tx_buf_.empty()) {
tx_buf.reserve(tx_buf.size() + total_write_len); this->buffer_data_from_iov_(iov, iovcnt, total_write_len);
for (int i = 0; i < iovcnt; i++) {
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
return APIError::OK; // Success, data buffered return APIError::OK; // Success, data buffered
} }
ssize_t sent = socket->writev(iov, iovcnt);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
// Reserve space upfront to avoid multiple reallocations
tx_buf.reserve(tx_buf.size() + total_write_len);
for (int i = 0; i < iovcnt; i++) {
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
} }
return APIError::OK; // Success, data buffered
} else if (sent == -1) {
// an error occurred
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
state = failed_state;
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
} else if ((size_t) sent != total_write_len) {
// partially sent, add end to tx_buf
size_t remaining = total_write_len - sent;
// Reserve space upfront to avoid multiple reallocations
tx_buf.reserve(tx_buf.size() + remaining);
size_t to_consume = sent; // Try to send directly if no buffered data
ssize_t sent = this->socket_->writev(iov, iovcnt);
if (sent == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
// Socket would block, buffer the data
this->buffer_data_from_iov_(iov, iovcnt, total_write_len);
return APIError::OK; // Success, data buffered
}
// Socket error
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) {
// 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);
buffer.data.reserve(remaining);
for (int i = 0; i < iovcnt; i++) { for (int i = 0; i < iovcnt; i++) {
if (to_consume >= iov[i].iov_len) { if (to_consume >= iov[i].iov_len) {
to_consume -= iov[i].iov_len; // This segment was fully sent
to_consume -= static_cast<uint16_t>(iov[i].iov_len);
} else { } else {
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume, // This segment was partially sent or not sent at all
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len); 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;
buffer.data.insert(buffer.data.end(), data, data + len);
to_consume = 0; to_consume = 0;
} }
} }
return APIError::OK; // Success, data buffered
} this->tx_buf_.push_back(std::move(buffer));
return APIError::OK; // Success, all data sent
} }
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) return APIError::OK; // Success, all data sent or buffered
}
// Common implementation for trying to send buffered data
// IMPORTANT: Caller MUST ensure tx_buf_ is not empty before calling this method
APIError APIFrameHelper::try_send_tx_buf_() {
// Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
bool tx_buf_empty = false;
while (!tx_buf_empty) {
// Get the first buffer in the queue
SendBuffer &front_buffer = this->tx_buf_.front();
// Try to send the remaining data in this buffer
ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining());
if (sent == -1) {
if (errno != EWOULDBLOCK && errno != EAGAIN) {
// Real socket error (not just would block)
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
}
// Socket would block, we'll try again later
return APIError::WOULD_BLOCK;
} else if (sent == 0) {
// Nothing sent but not an error
return APIError::WOULD_BLOCK;
} else if (static_cast<uint16_t>(sent) < front_buffer.remaining()) {
// Partially sent, update offset
// Cast to ensure no overflow issues with uint16_t
front_buffer.offset += static_cast<uint16_t>(sent);
return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
} else {
// Buffer completely sent, remove it from the queue
this->tx_buf_.pop_front();
// Update empty status for the loop condition
tx_buf_empty = this->tx_buf_.empty();
// Continue loop to try sending the next buffer
}
}
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 // uncomment to log raw packets
//#define HELPER_LOG_PACKETS //#define HELPER_LOG_PACKETS
@@ -206,23 +261,9 @@ std::string noise_err_to_str(int err) {
/// Initialize the frame helper, returns OK if successful. /// Initialize the frame helper, returns OK if successful.
APIError APINoiseFrameHelper::init() { APIError APINoiseFrameHelper::init() {
if (state_ != State::INITIALIZE || socket_ == nullptr) { APIError err = init_common_();
HELPER_LOG("Bad state for init %d", (int) state_); if (err != APIError::OK) {
return APIError::BAD_STATE; return err;
}
int err = 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 = 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 // init prologue
@@ -234,17 +275,16 @@ APIError APINoiseFrameHelper::init() {
/// Run through handshake messages (if in that phase) /// Run through handshake messages (if in that phase)
APIError APINoiseFrameHelper::loop() { APIError APINoiseFrameHelper::loop() {
APIError err = state_action_(); APIError err = state_action_();
if (err == APIError::WOULD_BLOCK) if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return APIError::OK;
if (err != APIError::OK)
return err; return err;
if (!tx_buf_.empty()) { }
if (!this->tx_buf_.empty()) {
err = try_send_tx_buf_(); err = try_send_tx_buf_();
if (err != APIError::OK) { if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err; return err;
} }
} }
return APIError::OK; return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
} }
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
@@ -270,8 +310,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// read header // read header
if (rx_header_buf_len_ < 3) { if (rx_header_buf_len_ < 3) {
// no header information yet // no header information yet
size_t to_read = 3 - rx_header_buf_len_; uint8_t to_read = 3 - rx_header_buf_len_;
ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
if (received == -1) { if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
@@ -284,8 +324,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
HELPER_LOG("Connection closed"); HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
rx_header_buf_len_ += received; rx_header_buf_len_ += static_cast<uint8_t>(received);
if ((size_t) received != to_read) { if (static_cast<uint8_t>(received) != to_read) {
// not a full read // not a full read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@@ -317,8 +357,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
if (rx_buf_len_ < msg_size) { if (rx_buf_len_ < msg_size) {
// more data to read // more data to read
size_t to_read = msg_size - rx_buf_len_; uint16_t to_read = msg_size - rx_buf_len_;
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (received == -1) { if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
@@ -331,8 +371,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
HELPER_LOG("Connection closed"); HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
rx_buf_len_ += received; rx_buf_len_ += static_cast<uint16_t>(received);
if ((size_t) received != to_read) { if (static_cast<uint16_t>(received) != to_read) {
// not all read // not all read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@@ -381,6 +421,8 @@ APIError APINoiseFrameHelper::state_action_() {
if (aerr != APIError::OK) if (aerr != APIError::OK)
return aerr; return aerr;
// ignore contents, may be used in future for flags // 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() >> 8));
prologue_.push_back((uint8_t) frame.msg.size()); prologue_.push_back((uint8_t) frame.msg.size());
prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end()); prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
@@ -389,16 +431,20 @@ APIError APINoiseFrameHelper::state_action_() {
} }
if (state_ == State::SERVER_HELLO) { if (state_ == State::SERVER_HELLO) {
// send server hello // send server hello
const std::string &name = App.get_name();
const std::string &mac = get_mac_address();
std::vector<uint8_t> msg; 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 // chosen proto
msg.push_back(0x01); msg.push_back(0x01);
// node name, terminated by null byte // 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()); const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1); msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
// node mac, terminated by null byte // 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()); const uint8_t *mac_ptr = reinterpret_cast<const uint8_t *>(mac.c_str());
msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1); msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1);
@@ -505,7 +551,6 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
write_frame_(data.data(), data.size()); write_frame_(data.data(), data.size());
state_ = orig_state; state_ = orig_state;
} }
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
int err; int err;
APIError aerr; APIError aerr;
@@ -533,7 +578,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return APIError::CIPHERSTATE_DECRYPT_FAILED; return APIError::CIPHERSTATE_DECRYPT_FAILED;
} }
size_t msg_size = mbuf.size; uint16_t msg_size = mbuf.size;
uint8_t *msg_data = frame.msg.data(); uint8_t *msg_data = frame.msg.data();
if (msg_size < 4) { if (msg_size < 4) {
state_ = State::FAILED; state_ = State::FAILED;
@@ -559,7 +604,6 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = type; buffer->type = type;
return APIError::OK; return APIError::OK;
} }
bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) { APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
int err; int err;
APIError aerr; APIError aerr;
@@ -574,9 +618,9 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
std::vector<uint8_t> *raw_buffer = buffer.get_buffer(); std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
// Message data starts after padding // Message data starts after padding
size_t payload_len = raw_buffer->size() - frame_header_padding_; uint16_t payload_len = raw_buffer->size() - frame_header_padding_;
size_t padding = 0; uint16_t padding = 0;
size_t msg_len = 4 + payload_len + padding; uint16_t msg_len = 4 + payload_len + padding;
// We need to resize to include MAC space, but we already reserved it in create_buffer // 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_); raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
@@ -609,7 +653,7 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
return APIError::CIPHERSTATE_ENCRYPT_FAILED; return APIError::CIPHERSTATE_ENCRYPT_FAILED;
} }
size_t total_len = 3 + mbuf.size; uint16_t total_len = 3 + mbuf.size;
buf_start[1] = (uint8_t) (mbuf.size >> 8); buf_start[1] = (uint8_t) (mbuf.size >> 8);
buf_start[2] = (uint8_t) mbuf.size; buf_start[2] = (uint8_t) mbuf.size;
@@ -620,29 +664,9 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
iov.iov_len = total_len; iov.iov_len = total_len;
// write raw to not have two packets sent if NAGLE disabled // write raw to not have two packets sent if NAGLE disabled
return write_raw_(&iov, 1); return this->write_raw_(&iov, 1);
} }
APIError APINoiseFrameHelper::try_send_tx_buf_() { APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
// try send from tx_buf
while (state_ != State::CLOSED && !tx_buf_.empty()) {
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
if (sent == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
break;
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if (sent == 0) {
break;
}
// TODO: inefficient if multiple packets in txbuf
// replace with deque of buffers
tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
}
return APIError::OK;
}
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
uint8_t header[3]; uint8_t header[3];
header[0] = 0x01; // indicator header[0] = 0x01; // indicator
header[1] = (uint8_t) (len >> 8); header[1] = (uint8_t) (len >> 8);
@@ -652,12 +676,12 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
iov[0].iov_base = header; iov[0].iov_base = header;
iov[0].iov_len = 3; iov[0].iov_len = 3;
if (len == 0) { if (len == 0) {
return write_raw_(iov, 1); return this->write_raw_(iov, 1);
} }
iov[1].iov_base = const_cast<uint8_t *>(data); iov[1].iov_base = const_cast<uint8_t *>(data);
iov[1].iov_len = len; iov[1].iov_len = len;
return write_raw_(iov, 2); return this->write_raw_(iov, 2);
} }
/** Initiate the data structures for the handshake. /** Initiate the data structures for the handshake.
@@ -752,22 +776,6 @@ APINoiseFrameHelper::~APINoiseFrameHelper() {
} }
} }
APIError APINoiseFrameHelper::close() {
state_ = State::CLOSED;
int err = socket_->close();
if (err == -1)
return APIError::CLOSE_FAILED;
return APIError::OK;
}
APIError APINoiseFrameHelper::shutdown(int how) {
int err = socket_->shutdown(how);
if (err == -1)
return APIError::SHUTDOWN_FAILED;
if (how == SHUT_RDWR) {
state_ = State::CLOSED;
}
return APIError::OK;
}
extern "C" { extern "C" {
// declare how noise generates random bytes (here with a good HWRNG based on the RF system) // 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) { void noise_rand_bytes(void *output, size_t len) {
@@ -778,32 +786,15 @@ void noise_rand_bytes(void *output, size_t len) {
} }
} }
// Explicit template instantiation for Noise
template APIError APIFrameHelper::write_raw_<APINoiseFrameHelper::State>(
const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
APINoiseFrameHelper::State &state, APINoiseFrameHelper::State failed_state);
#endif // USE_API_NOISE #endif // USE_API_NOISE
#ifdef USE_API_PLAINTEXT #ifdef USE_API_PLAINTEXT
/// Initialize the frame helper, returns OK if successful. /// Initialize the frame helper, returns OK if successful.
APIError APIPlaintextFrameHelper::init() { APIError APIPlaintextFrameHelper::init() {
if (state_ != State::INITIALIZE || socket_ == nullptr) { APIError err = init_common_();
HELPER_LOG("Bad state for init %d", (int) state_); if (err != APIError::OK) {
return APIError::BAD_STATE; return err;
}
int err = 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 = 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; state_ = State::DATA;
@@ -814,14 +805,13 @@ APIError APIPlaintextFrameHelper::loop() {
if (state_ != State::DATA) { if (state_ != State::DATA) {
return APIError::BAD_STATE; return APIError::BAD_STATE;
} }
// try send pending TX data if (!this->tx_buf_.empty()) {
if (!tx_buf_.empty()) {
APIError err = try_send_tx_buf_(); APIError err = try_send_tx_buf_();
if (err != APIError::OK) { if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err; return err;
} }
} }
return APIError::OK; return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
} }
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
@@ -841,12 +831,15 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// read header // read header
while (!rx_header_parsed_) { while (!rx_header_parsed_) {
uint8_t data; // Now that we know when the socket is ready, we can read up to 3 bytes
// Reading one byte at a time is fastest in practice for ESP32 when // into the rx_header_buf_ before we have to switch back to reading
// there is no data on the wire (which is the common case). // one byte at a time to ensure we don't read past the message and
// This results in faster failure detection compared to // into the next one.
// attempting to read multiple bytes at once.
ssize_t received = socket_->read(&data, 1); // 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);
if (received == -1) { if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
@@ -860,64 +853,74 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
// Successfully read a byte // If this was the first read, validate the indicator byte
if (rx_header_buf_pos_ == 0 && received > 0) {
// Process byte according to current buffer position if (rx_header_buf_[0] != 0x00) {
if (rx_header_buf_pos_ == 0) { // Case 1: First byte (indicator byte)
if (data != 0x00) {
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Bad indicator byte %u", data); HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
return APIError::BAD_INDICATOR; 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
} }
// Check buffer overflow before storing rx_header_buf_pos_ += received;
if (rx_header_buf_pos_ == 5) { // Case 2: Buffer would overflow (5 bytes is max allowed)
// Check for buffer overflow
if (rx_header_buf_pos_ >= sizeof(rx_header_buf_)) {
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Header buffer overflow"); HELPER_LOG("Header buffer overflow");
return APIError::BAD_DATA_PACKET; return APIError::BAD_DATA_PACKET;
} }
// Store byte in buffer (adjust index to account for skipped indicator byte) // Need at least 3 bytes total (indicator + 2 varint bytes) before trying to parse
rx_header_buf_[rx_header_buf_pos_ - 1] = data; if (rx_header_buf_pos_ < 3) {
continue;
// 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: // At this point, we have at least 3 bytes total:
// - Validated indicator byte (0x00) but not stored // - Validated indicator byte (0x00) stored at position 0
// - At least 2 bytes in the buffer for the varints // - At least 2 bytes in the buffer for the varints
// Buffer layout: // Buffer layout:
// First 1-3 bytes: Message size varint (variable length) // [0]: indicator byte (0x00)
// - 2 bytes would only allow up to 16383, which is less than noise's 65535 // [1-3]: Message size varint (variable length)
// - 2 bytes would only allow up to 16383, which is less than noise's UINT16_MAX (65535)
// - 3 bytes allows up to 2097151, ensuring we support at least as much as noise // - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
// Remaining 1-2 bytes: Message type varint (variable length) // [2-5]: Message type varint (variable length)
// We now attempt to parse both varints. If either is incomplete, // We now attempt to parse both varints. If either is incomplete,
// we'll continue reading more bytes. // we'll continue reading more bytes.
// Skip indicator byte at position 0
uint8_t varint_pos = 1;
uint32_t consumed = 0; uint32_t consumed = 0;
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[0], rx_header_buf_pos_ - 1, &consumed);
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
if (!msg_size_varint.has_value()) { if (!msg_size_varint.has_value()) {
// not enough data there yet // not enough data there yet
continue; continue;
} }
rx_header_parsed_len_ = msg_size_varint->as_uint32(); 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();
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[consumed], rx_header_buf_pos_ - 1 - consumed, &consumed); // 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);
if (!msg_type_varint.has_value()) { if (!msg_type_varint.has_value()) {
// not enough data there yet // not enough data there yet
continue; continue;
} }
rx_header_parsed_type_ = msg_type_varint->as_uint32(); 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_ = true; rx_header_parsed_ = true;
} }
// header reading done // header reading done
@@ -929,8 +932,8 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
if (rx_buf_len_ < rx_header_parsed_len_) { if (rx_buf_len_ < rx_header_parsed_len_) {
// more data to read // more data to read
size_t to_read = rx_header_parsed_len_ - rx_buf_len_; uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (received == -1) { if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
@@ -943,8 +946,8 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
HELPER_LOG("Connection closed"); HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
rx_buf_len_ += received; rx_buf_len_ += static_cast<uint16_t>(received);
if ((size_t) received != to_read) { if (static_cast<uint16_t>(received) != to_read) {
// not all read // not all read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@@ -962,7 +965,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
rx_header_parsed_ = false; rx_header_parsed_ = false;
return APIError::OK; return APIError::OK;
} }
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
APIError aerr; APIError aerr;
@@ -990,7 +992,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
"Bad indicator byte"; "Bad indicator byte";
iov[0].iov_base = (void *) msg; iov[0].iov_base = (void *) msg;
iov[0].iov_len = 19; iov[0].iov_len = 19;
write_raw_(iov, 1); this->write_raw_(iov, 1);
} }
return aerr; return aerr;
} }
@@ -1001,7 +1003,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = rx_header_parsed_type_; buffer->type = rx_header_parsed_type_;
return APIError::OK; return APIError::OK;
} }
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) { APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
if (state_ != State::DATA) { if (state_ != State::DATA) {
return APIError::BAD_STATE; return APIError::BAD_STATE;
@@ -1009,12 +1010,12 @@ APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWrit
std::vector<uint8_t> *raw_buffer = buffer.get_buffer(); std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
// Message data starts after padding (frame_header_padding_ = 6) // Message data starts after padding (frame_header_padding_ = 6)
size_t payload_len = raw_buffer->size() - frame_header_padding_; uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
// Calculate varint sizes for header components // Calculate varint sizes for header components
size_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len)); uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
size_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type)); uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
size_t total_header_len = 1 + size_varint_len + type_varint_len; uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
if (total_header_len > frame_header_padding_) { if (total_header_len > frame_header_padding_) {
// Header is too large to fit in the padding // Header is too large to fit in the padding
@@ -1044,7 +1045,7 @@ APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWrit
// [4-5] - Message type varint (2 bytes, for types 128-32767) // [4-5] - Message type varint (2 bytes, for types 128-32767)
// [6...] - Actual payload data // [6...] - Actual payload data
uint8_t *buf_start = raw_buffer->data(); uint8_t *buf_start = raw_buffer->data();
size_t header_offset = frame_header_padding_ - total_header_len; uint8_t header_offset = frame_header_padding_ - total_header_len;
// Write the plaintext header // Write the plaintext header
buf_start[header_offset] = 0x00; // indicator buf_start[header_offset] = 0x00; // indicator
@@ -1063,46 +1064,7 @@ APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWrit
return write_raw_(&iov, 1); return write_raw_(&iov, 1);
} }
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
// try send from tx_buf
while (state_ != State::CLOSED && !tx_buf_.empty()) {
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
if (is_would_block(sent)) {
break;
} else if (sent == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
}
// TODO: inefficient if multiple packets in txbuf
// replace with deque of buffers
tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
}
return APIError::OK;
}
APIError APIPlaintextFrameHelper::close() {
state_ = State::CLOSED;
int err = socket_->close();
if (err == -1)
return APIError::CLOSE_FAILED;
return APIError::OK;
}
APIError APIPlaintextFrameHelper::shutdown(int how) {
int err = socket_->shutdown(how);
if (err == -1)
return APIError::SHUTDOWN_FAILED;
if (how == SHUT_RDWR) {
state_ = State::CLOSED;
}
return APIError::OK;
}
// Explicit template instantiation for Plaintext
template APIError APIFrameHelper::write_raw_<APIPlaintextFrameHelper::State>(
const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
APIPlaintextFrameHelper::State &state, APIPlaintextFrameHelper::State failed_state);
#endif // USE_API_PLAINTEXT #endif // USE_API_PLAINTEXT
} // namespace api } // namespace api

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <deque> #include <deque>
#include <limits>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -12,6 +13,7 @@
#include "api_noise_context.h" #include "api_noise_context.h"
#include "esphome/components/socket/socket.h" #include "esphome/components/socket/socket.h"
#include "esphome/core/application.h"
namespace esphome { namespace esphome {
namespace api { namespace api {
@@ -21,15 +23,8 @@ class ProtoWriteBuffer;
struct ReadPacketBuffer { struct ReadPacketBuffer {
std::vector<uint8_t> container; std::vector<uint8_t> container;
uint16_t type; uint16_t type;
size_t data_offset; uint16_t data_offset;
size_t data_len; uint16_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 { enum class APIError : int {
@@ -62,38 +57,119 @@ const char *api_error_to_str(APIError err);
class APIFrameHelper { class APIFrameHelper {
public: public:
APIFrameHelper() = default;
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_owned_(std::move(socket)) {
socket_ = socket_owned_.get();
}
virtual ~APIFrameHelper() = default; virtual ~APIFrameHelper() = default;
virtual APIError init() = 0; virtual APIError init() = 0;
virtual APIError loop() = 0; virtual APIError loop() = 0;
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
virtual bool can_write_without_blocking() = 0; bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0; std::string getpeername() { return socket_->getpeername(); }
virtual std::string getpeername() = 0; int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; APIError close() {
virtual APIError close() = 0; state_ = State::CLOSED;
virtual APIError shutdown(int how) = 0; int err = this->socket_->close();
if (err == -1)
return APIError::CLOSE_FAILED;
return APIError::OK;
}
APIError shutdown(int how) {
int err = this->socket_->shutdown(how);
if (err == -1)
return APIError::SHUTDOWN_FAILED;
if (how == SHUT_RDWR) {
state_ = State::CLOSED;
}
return APIError::OK;
}
// Give this helper a name for logging // Give this helper a name for logging
virtual void set_log_info(std::string info) = 0; 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 // Get the frame header padding required by this protocol
virtual uint8_t frame_header_padding() = 0; virtual uint8_t frame_header_padding() = 0;
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol
virtual uint8_t frame_footer_size() = 0; 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: protected:
// Struct for holding parsed frame data
struct ParsedFrame {
std::vector<uint8_t> msg;
};
// Buffer containing data to be sent
struct SendBuffer {
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
uint16_t remaining() const { return static_cast<uint16_t>(data.size()) - offset; }
const uint8_t *current_data() const { return data.data() + offset; }
};
// Queue of data buffers to be sent
std::deque<SendBuffer> tx_buf_;
// Common state enum for all frame helpers
// Note: Not all states are used by all implementations
// - INITIALIZE: Used by both Noise and Plaintext
// - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol
// - DATA: Used by both Noise and Plaintext
// - CLOSED: Used by both Noise and Plaintext
// - FAILED: Used by both Noise and Plaintext
// - EXPLICIT_REJECT: Only used by Noise protocol
enum class State {
INITIALIZE = 1,
CLIENT_HELLO = 2, // Noise only
SERVER_HELLO = 3, // Noise only
HANDSHAKE = 4, // Noise only
DATA = 5,
CLOSED = 6,
FAILED = 7,
EXPLICIT_REJECT = 8, // Noise only
};
// Current state of the frame helper
State state_{State::INITIALIZE};
// Helper name for logging
std::string info_;
// Socket for communication
socket::Socket *socket_{nullptr};
std::unique_ptr<socket::Socket> socket_owned_;
// Common implementation for writing raw data to socket // Common implementation for writing raw data to socket
APIError write_raw_(const struct iovec *iov, int iovcnt);
// Try to send data from the tx buffer
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> template<typename StateEnum>
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf, 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); const std::string &info, StateEnum &state, StateEnum failed_state);
uint8_t frame_header_padding_{0}; uint8_t frame_header_padding_{0};
uint8_t frame_footer_size_{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_();
}; };
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
class APINoiseFrameHelper : public APIFrameHelper { class APINoiseFrameHelper : public APIFrameHelper {
public: public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx) APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
: socket_(std::move(socket)), ctx_(std::move(ctx)) { : APIFrameHelper(std::move(socket)), ctx_(std::move(ctx)) {
// Noise header structure: // Noise header structure:
// Pos 0: indicator (0x01) // Pos 0: indicator (0x01)
// Pos 1-2: encrypted payload size (16-bit big-endian) // Pos 1-2: encrypted payload size (16-bit big-endian)
@@ -105,49 +181,25 @@ class APINoiseFrameHelper : public APIFrameHelper {
APIError init() override; APIError init() override;
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
bool can_write_without_blocking() override;
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
std::string getpeername() override { return this->socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
return this->socket_->getpeername(addr, addrlen);
}
APIError close() override;
APIError shutdown(int how) override;
// Give this helper a name for logging
void set_log_info(std::string info) override { info_ = std::move(info); }
// Get the frame header padding required by this protocol // Get the frame header padding required by this protocol
uint8_t frame_header_padding() override { return frame_header_padding_; } uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; } uint8_t frame_footer_size() override { return frame_footer_size_; }
protected: protected:
struct ParsedFrame {
std::vector<uint8_t> msg;
};
APIError state_action_(); APIError state_action_();
APIError try_read_frame_(ParsedFrame *frame); APIError try_read_frame_(ParsedFrame *frame);
APIError try_send_tx_buf_(); APIError write_frame_(const uint8_t *data, uint16_t len);
APIError write_frame_(const uint8_t *data, size_t len);
inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
}
APIError init_handshake_(); APIError init_handshake_();
APIError check_handshake_finished_(); APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const std::string &reason); void send_explicit_handshake_reject_(const std::string &reason);
std::unique_ptr<socket::Socket> socket_;
std::string info_;
// Fixed-size header buffer for noise protocol: // Fixed-size header buffer for noise protocol:
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint) // 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
// Note: Maximum message size is 65535, with a limit of 128 bytes during handshake phase // Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase
uint8_t rx_header_buf_[3]; uint8_t rx_header_buf_[3];
size_t rx_header_buf_len_ = 0; uint8_t rx_header_buf_len_ = 0;
std::vector<uint8_t> rx_buf_;
size_t rx_buf_len_ = 0;
std::vector<uint8_t> tx_buf_;
std::vector<uint8_t> prologue_; std::vector<uint8_t> prologue_;
std::shared_ptr<APINoiseContext> ctx_; std::shared_ptr<APINoiseContext> ctx_;
@@ -155,24 +207,13 @@ class APINoiseFrameHelper : public APIFrameHelper {
NoiseCipherState *send_cipher_{nullptr}; NoiseCipherState *send_cipher_{nullptr};
NoiseCipherState *recv_cipher_{nullptr}; NoiseCipherState *recv_cipher_{nullptr};
NoiseProtocolId nid_; NoiseProtocolId nid_;
enum class State {
INITIALIZE = 1,
CLIENT_HELLO = 2,
SERVER_HELLO = 3,
HANDSHAKE = 4,
DATA = 5,
CLOSED = 6,
FAILED = 7,
EXPLICIT_REJECT = 8,
} state_ = State::INITIALIZE;
}; };
#endif // USE_API_NOISE #endif // USE_API_NOISE
#ifdef USE_API_PLAINTEXT #ifdef USE_API_PLAINTEXT
class APIPlaintextFrameHelper : public APIFrameHelper { class APIPlaintextFrameHelper : public APIFrameHelper {
public: public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) { APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {
// Plaintext header structure (worst case): // Plaintext header structure (worst case):
// Pos 0: indicator (0x00) // Pos 0: indicator (0x00)
// Pos 1-3: payload size varint (up to 3 bytes) // Pos 1-3: payload size varint (up to 3 bytes)
@@ -184,60 +225,26 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
APIError init() override; APIError init() override;
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
bool can_write_without_blocking() override;
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
std::string getpeername() override { return this->socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
return this->socket_->getpeername(addr, addrlen);
}
APIError close() override;
APIError shutdown(int how) override;
// Give this helper a name for logging
void set_log_info(std::string info) override { info_ = std::move(info); }
// Get the frame header padding required by this protocol
uint8_t frame_header_padding() override { return frame_header_padding_; } uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; } uint8_t frame_footer_size() override { return frame_footer_size_; }
protected: protected:
struct ParsedFrame {
std::vector<uint8_t> msg;
};
APIError try_read_frame_(ParsedFrame *frame); APIError try_read_frame_(ParsedFrame *frame);
APIError try_send_tx_buf_();
inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
}
std::unique_ptr<socket::Socket> socket_;
std::string info_;
// Fixed-size header buffer for plaintext protocol: // Fixed-size header buffer for plaintext protocol:
// We only need space for the two varints since we validate the indicator byte separately. // We now store the indicator byte + the two varints.
// To match noise protocol's maximum message size (65535), we need: // To match noise protocol's maximum message size (UINT16_MAX = 65535), we need:
// 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint // 1 byte for indicator + 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, // 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 // attempting to process messages with headers that large would likely crash the
// ESP32 due to memory constraints. // ESP32 due to memory constraints.
uint8_t rx_header_buf_[5]; // 5 bytes for varints (3 for size + 2 for type) uint8_t rx_header_buf_[6]; // 1 byte indicator + 5 bytes for varints (3 for size + 2 for type)
uint8_t rx_header_buf_pos_ = 0; uint8_t rx_header_buf_pos_ = 0;
bool rx_header_parsed_ = false; bool rx_header_parsed_ = false;
uint32_t rx_header_parsed_type_ = 0; uint16_t rx_header_parsed_type_ = 0;
uint32_t rx_header_parsed_len_ = 0; uint16_t rx_header_parsed_len_ = 0;
std::vector<uint8_t> rx_buf_;
size_t rx_buf_len_ = 0;
std::vector<uint8_t> tx_buf_;
enum class State {
INITIALIZE = 1,
DATA = 2,
CLOSED = 3,
FAILED = 4,
} state_ = State::INITIALIZE;
}; };
#endif #endif

View File

@@ -96,6 +96,8 @@ template<> const char *proto_enum_to_string<enums::ColorMode>(enums::ColorMode v
return "COLOR_MODE_UNKNOWN"; return "COLOR_MODE_UNKNOWN";
case enums::COLOR_MODE_ON_OFF: case enums::COLOR_MODE_ON_OFF:
return "COLOR_MODE_ON_OFF"; return "COLOR_MODE_ON_OFF";
case enums::COLOR_MODE_LEGACY_BRIGHTNESS:
return "COLOR_MODE_LEGACY_BRIGHTNESS";
case enums::COLOR_MODE_BRIGHTNESS: case enums::COLOR_MODE_BRIGHTNESS:
return "COLOR_MODE_BRIGHTNESS"; return "COLOR_MODE_BRIGHTNESS";
case enums::COLOR_MODE_WHITE: case enums::COLOR_MODE_WHITE:

View File

@@ -41,7 +41,8 @@ enum FanDirection : uint32_t {
enum ColorMode : uint32_t { enum ColorMode : uint32_t {
COLOR_MODE_UNKNOWN = 0, COLOR_MODE_UNKNOWN = 0,
COLOR_MODE_ON_OFF = 1, COLOR_MODE_ON_OFF = 1,
COLOR_MODE_BRIGHTNESS = 2, COLOR_MODE_LEGACY_BRIGHTNESS = 2,
COLOR_MODE_BRIGHTNESS = 3,
COLOR_MODE_WHITE = 7, COLOR_MODE_WHITE = 7,
COLOR_MODE_COLOR_TEMPERATURE = 11, COLOR_MODE_COLOR_TEMPERATURE = 11,
COLOR_MODE_COLD_WARM_WHITE = 19, COLOR_MODE_COLD_WARM_WHITE = 19,

View File

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

View File

@@ -55,6 +55,7 @@ class ProtoVarInt {
return {}; // Incomplete or invalid varint return {}; // Incomplete or invalid varint
} }
uint16_t as_uint16() const { return this->value_; }
uint32_t as_uint32() const { return this->value_; } uint32_t as_uint32() const { return this->value_; }
uint64_t as_uint64() const { return this->value_; } uint64_t as_uint64() const { return this->value_; }
bool as_bool() const { return this->value_; } bool as_bool() const { return this->value_; }

View File

@@ -7,7 +7,7 @@ namespace as3935 {
static const char *const TAG = "as3935"; static const char *const TAG = "as3935";
void AS3935Component::setup() { void AS3935Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up AS3935..."); ESP_LOGCONFIG(TAG, "Running setup");
this->irq_pin_->setup(); this->irq_pin_->setup();
LOG_PIN(" IRQ Pin: ", this->irq_pin_); LOG_PIN(" IRQ Pin: ", this->irq_pin_);

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

View File

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

View File

@@ -71,7 +71,7 @@ bool AT581XComponent::i2c_read_reg(uint8_t addr, uint8_t &data) {
return this->read_register(addr, &data, 1) == esphome::i2c::NO_ERROR; return this->read_register(addr, &data, 1) == esphome::i2c::NO_ERROR;
} }
void AT581XComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up AT581X..."); } void AT581XComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); }
void AT581XComponent::dump_config() { LOG_I2C_DEVICE(this); } void AT581XComponent::dump_config() { LOG_I2C_DEVICE(this); }
#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0])) #define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
bool AT581XComponent::i2c_write_config() { bool AT581XComponent::i2c_write_config() {

View File

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

View File

@@ -108,7 +108,7 @@ void ATM90E32Component::update() {
} }
void ATM90E32Component::setup() { void ATM90E32Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component..."); ESP_LOGCONFIG(TAG, "Running setup");
this->spi_setup(); this->spi_setup();
uint16_t mmode0 = 0x87; // 3P4W 50Hz uint16_t mmode0 = 0x87; // 3P4W 50Hz
@@ -217,7 +217,7 @@ void ATM90E32Component::dump_config() {
ESP_LOGCONFIG("", "ATM90E32:"); ESP_LOGCONFIG("", "ATM90E32:");
LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" CS Pin: ", this->cs_);
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with ATM90E32 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
} }
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_); LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_);

View File

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

View File

@@ -119,7 +119,7 @@ void spi_dma_tx_finish_callback(unsigned int param) {
} }
void BekenSPILEDStripLightOutput::setup() { void BekenSPILEDStripLightOutput::setup() {
ESP_LOGCONFIG(TAG, "Setting up Beken SPI LED Strip..."); ESP_LOGCONFIG(TAG, "Running setup");
size_t buffer_size = this->get_buffer_size_(); size_t buffer_size = this->get_buffer_size_();
size_t dma_buffer_size = (buffer_size * 8) + (2 * 64); size_t dma_buffer_size = (buffer_size * 8) + (2 * 64);

View File

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

View File

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

View File

@@ -4,6 +4,9 @@
#include "esphome/core/macros.h" #include "esphome/core/macros.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include <algorithm>
#include <cinttypes>
#ifdef USE_ESP32 #ifdef USE_ESP32
namespace esphome { namespace esphome {
@@ -46,9 +49,16 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_) if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
return false; return false;
// Measure time for processing single device
const uint32_t start_time = millis();
ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
device.get_rssi()); device.get_rssi());
this->send_api_packet_(device); this->send_api_packet_(device);
const uint32_t duration = millis() - start_time;
this->section_stats_["parse_device"].record_time(duration);
return true; return true;
} }
@@ -62,6 +72,9 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_) if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
return false; return false;
// Measure time for processing batch of devices
const uint32_t start_time = millis();
// Get the batch buffer reference // Get the batch buffer reference
auto &batch_buffer = get_batch_buffer(); auto &batch_buffer = get_batch_buffer();
@@ -93,6 +106,9 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p
this->flush_pending_advertisements(); this->flush_pending_advertisements();
} }
const uint32_t duration = millis() - start_time;
this->section_stats_["parse_devices"].record_time(duration);
return true; return true;
} }
@@ -101,12 +117,52 @@ void BluetoothProxy::flush_pending_advertisements() {
if (batch_buffer.empty() || !api::global_api_server->is_connected() || this->api_connection_ == nullptr) if (batch_buffer.empty() || !api::global_api_server->is_connected() || this->api_connection_ == nullptr)
return; return;
// Measure time for flushing advertisements
const uint32_t start_time = millis();
// Track the batch size for analysis
size_t batch_size = batch_buffer.size();
// Measure swap operation
uint32_t swap_start = millis();
api::BluetoothLERawAdvertisementsResponse resp; api::BluetoothLERawAdvertisementsResponse resp;
resp.advertisements.swap(batch_buffer); resp.advertisements.swap(batch_buffer);
uint32_t swap_duration = millis() - swap_start;
if (swap_duration > 0) {
this->section_stats_["flush_swap"].record_time(swap_duration);
}
// Measure API send operation
uint32_t send_start = millis();
this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp); this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
uint32_t send_duration = millis() - send_start;
this->section_stats_["flush_api_send"].record_time(send_duration);
const uint32_t duration = millis() - start_time;
this->section_stats_["flush_advertisements"].record_time(duration);
// Log if this was a particularly slow flush
if (duration > 10) {
ESP_LOGW(TAG, "Slow flush: %dms for %d advertisements", duration, batch_size);
}
// Track average advertisements per flush
static uint32_t total_ads_flushed = 0;
static uint32_t total_flushes = 0;
total_ads_flushed += batch_size;
total_flushes++;
if (total_flushes % 100 == 0) {
float avg_ads_per_flush = static_cast<float>(total_ads_flushed) / total_flushes;
ESP_LOGD(TAG, "Avg advertisements per flush: %.2f (total: %d ads in %d flushes)", avg_ads_per_flush,
total_ads_flushed, total_flushes);
}
} }
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) { void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
// Measure time for sending API packet
const uint32_t start_time = millis();
api::BluetoothLEAdvertisementResponse resp; api::BluetoothLEAdvertisementResponse resp;
resp.address = device.address_uint64(); resp.address = device.address_uint64();
resp.address_type = device.get_address_type(); resp.address_type = device.get_address_type();
@@ -142,6 +198,9 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
} }
this->api_connection_->send_bluetooth_le_advertisement(resp); this->api_connection_->send_bluetooth_le_advertisement(resp);
const uint32_t duration = millis() - start_time;
this->section_stats_["send_api_packet"].record_time(duration);
} }
void BluetoothProxy::dump_config() { void BluetoothProxy::dump_config() {
@@ -149,6 +208,8 @@ void BluetoothProxy::dump_config() {
ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_)); ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_));
ESP_LOGCONFIG(TAG, " Connections: %d", this->connections_.size()); ESP_LOGCONFIG(TAG, " Connections: %d", this->connections_.size());
ESP_LOGCONFIG(TAG, " Raw advertisements: %s", YESNO(this->raw_advertisements_)); ESP_LOGCONFIG(TAG, " Raw advertisements: %s", YESNO(this->raw_advertisements_));
ESP_LOGCONFIG(TAG, " Stats enabled: %s", YESNO(this->stats_enabled_));
ESP_LOGCONFIG(TAG, " Stats interval: %" PRIu32 "ms", this->stats_log_interval_);
} }
int BluetoothProxy::get_bluetooth_connections_free() { int BluetoothProxy::get_bluetooth_connections_free() {
@@ -166,6 +227,9 @@ int BluetoothProxy::get_bluetooth_connections_free() {
} }
void BluetoothProxy::loop() { void BluetoothProxy::loop() {
// Measure total time for entire loop function
const uint32_t loop_start_time = millis();
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) { if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
for (auto *connection : this->connections_) { for (auto *connection : this->connections_) {
if (connection->get_address() != 0) { if (connection->get_address() != 0) {
@@ -175,17 +239,28 @@ void BluetoothProxy::loop() {
return; return;
} }
// Flush any pending BLE advertisements that have been accumulated but not yet sent const uint32_t now = millis();
uint32_t start_time;
uint32_t duration;
// Section: Flush advertisements
if (this->raw_advertisements_) { if (this->raw_advertisements_) {
static uint32_t last_flush_time = 0; static uint32_t last_flush_time = 0;
uint32_t now = App.get_loop_component_start_time(); uint32_t app_time = App.get_loop_component_start_time();
// Flush accumulated advertisements every 100ms // Flush accumulated advertisements every 100ms
if (now - last_flush_time >= 100) { if (app_time - last_flush_time >= 100) {
start_time = millis();
this->flush_pending_advertisements(); this->flush_pending_advertisements();
last_flush_time = now; duration = millis() - start_time;
this->section_stats_["loop_flush_ads"].record_time(duration);
last_flush_time = app_time;
} }
} }
// Section: Service discovery
start_time = millis();
bool did_service_discovery = false;
for (auto *connection : this->connections_) { for (auto *connection : this->connections_) {
if (connection->send_service_ == connection->service_count_) { if (connection->send_service_ == connection->service_count_) {
connection->send_service_ = DONE_SENDING_SERVICES; connection->send_service_ = DONE_SENDING_SERVICES;
@@ -194,7 +269,9 @@ void BluetoothProxy::loop() {
connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
connection->release_services(); connection->release_services();
} }
did_service_discovery = true;
} else if (connection->send_service_ >= 0) { } else if (connection->send_service_ >= 0) {
did_service_discovery = true;
esp_gattc_service_elem_t service_result; esp_gattc_service_elem_t service_result;
uint16_t service_count = 1; uint16_t service_count = 1;
esp_gatt_status_t service_status = esp_gatt_status_t service_status =
@@ -303,6 +380,27 @@ void BluetoothProxy::loop() {
this->api_connection_->send_bluetooth_gatt_get_services_response(resp); this->api_connection_->send_bluetooth_gatt_get_services_response(resp);
} }
} }
if (did_service_discovery) {
duration = millis() - start_time;
this->section_stats_["service_discovery"].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_;
} else if (now >= this->next_stats_log_) {
this->log_section_stats_();
this->reset_section_stats_();
this->next_stats_log_ = now + this->stats_log_interval_;
}
}
// 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);
} }
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() { esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
@@ -337,6 +435,9 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
} }
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) { void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
// Measure time for processing device requests
const uint32_t start_time = millis();
switch (msg.request_type) { switch (msg.request_type) {
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE: case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE:
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
@@ -458,6 +559,9 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
break; break;
} }
} }
const uint32_t duration = millis() - start_time;
this->section_stats_["device_request"].record_time(duration);
} }
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) { void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
@@ -640,6 +744,74 @@ void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
true); // Set this to true to automatically start scanning again when it has cleaned up. true); // Set this to true to automatically start scanning again when it has cleaned up.
} }
void BluetoothProxy::log_section_stats_() {
const char *STATS_TAG = "bluetooth_proxy.stats";
ESP_LOGI(STATS_TAG,
"Logging Bluetooth Proxy section stats now (current time: %" PRIu32 ", scheduled time: %" PRIu32 ")",
millis(), this->next_stats_log_);
ESP_LOGI(STATS_TAG, "Stats collection status: enabled=%d, sections=%zu", this->stats_enabled_,
this->section_stats_.size());
// Check if we have minimal data
bool has_data = false;
for (const auto &it : this->section_stats_) {
if (it.second.get_period_count() > 0) {
has_data = true;
break;
}
}
if (!has_data) {
ESP_LOGI(STATS_TAG, "No stats data collected in this period");
return;
}
ESP_LOGI(STATS_TAG, "Bluetooth Proxy Section Runtime Statistics");
ESP_LOGI(STATS_TAG, "Period stats (last %" PRIu32 "ms):", this->stats_log_interval_);
// First collect stats we want to display
std::vector<std::pair<std::string, const BluetoothProxySectionStats *>> stats_to_display;
for (const auto &it : this->section_stats_) {
const BluetoothProxySectionStats &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 sections by period runtime
for (const auto &it : stats_to_display) {
const std::string &section_name = it.first;
const BluetoothProxySectionStats &stats = *it.second;
ESP_LOGI(STATS_TAG, " %-25s: count=%-6" PRIu32 " runtime=%-8" PRIu32 "ms avg=%-6.2fms max=%-6" PRIu32 "ms",
section_name.c_str(), stats.get_period_count(), stats.get_period_time_ms(), stats.get_period_avg_time_ms(),
stats.get_period_max_time_ms());
}
// Log total accumulated stats
ESP_LOGI(STATS_TAG, "Total accumulated stats:");
for (const auto &it : stats_to_display) {
const std::string &section_name = it.first;
const BluetoothProxySectionStats &stats = *it.second;
ESP_LOGI(STATS_TAG, " %-25s: count=%-8" PRIu32 " runtime=%-10" PRIu32 "ms avg=%-6.2fms max=%-6" PRIu32 "ms",
section_name.c_str(), stats.get_total_count(), stats.get_total_time_ms(), stats.get_total_avg_time_ms(),
stats.get_total_max_time_ms());
}
}
void BluetoothProxy::reset_section_stats_() {
for (auto &it : this->section_stats_) {
it.second.reset_period_stats();
}
}
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace bluetooth_proxy } // namespace bluetooth_proxy

View File

@@ -4,6 +4,7 @@
#include <map> #include <map>
#include <vector> #include <vector>
#include <string>
#include "esphome/components/api/api_connection.h" #include "esphome/components/api/api_connection.h"
#include "esphome/components/api/api_pb2.h" #include "esphome/components/api/api_pb2.h"
@@ -12,6 +13,8 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "bluetooth_connection.h" #include "bluetooth_connection.h"
@@ -25,6 +28,62 @@ static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
using namespace esp32_ble_client; using namespace esp32_ble_client;
// Stats class for tracking section performance
class BluetoothProxySectionStats {
public:
BluetoothProxySectionStats()
: 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;
}
void reset_period_stats() {
this->period_count_ = 0;
this->period_time_ms_ = 0;
this->period_max_time_ms_ = 0;
}
// Getters for period stats
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 ? static_cast<float>(this->period_time_ms_) / this->period_count_ : 0.0f;
}
// Getters for total stats
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 ? static_cast<float>(this->total_time_ms_) / this->total_count_ : 0.0f;
}
private:
uint32_t period_count_;
uint32_t total_count_;
uint32_t period_time_ms_;
uint32_t total_time_ms_;
uint32_t period_max_time_ms_;
uint32_t total_max_time_ms_;
};
// Legacy versions: // Legacy versions:
// Version 1: Initial version without active connections // Version 1: Initial version without active connections
// Version 2: Support for active connections // Version 2: Support for active connections
@@ -139,6 +198,14 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
std::vector<BluetoothConnection *> connections_{}; std::vector<BluetoothConnection *> connections_{};
api::APIConnection *api_connection_{nullptr}; api::APIConnection *api_connection_{nullptr};
bool raw_advertisements_{false}; bool raw_advertisements_{false};
// Performance statistics tracking
std::map<std::string, BluetoothProxySectionStats> 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_();
}; };
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -88,7 +88,7 @@ const char *oversampling_to_str(BME280Oversampling oversampling) { // NOLINT
} }
void BME280Component::setup() { void BME280Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up BME280..."); ESP_LOGCONFIG(TAG, "Running setup");
uint8_t chip_id = 0; uint8_t chip_id = 0;
// Mark as not failed before initializing. Some devices will turn off sensors to save on batteries // 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:"); ESP_LOGCONFIG(TAG, "BME280:");
switch (this->error_code_) { switch (this->error_code_) {
case COMMUNICATION_FAILED: case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with BME280 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break; break;
case WRONG_CHIP_ID: case WRONG_CHIP_ID:
ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?"); ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -119,7 +119,7 @@ const float GRAVITY_EARTH = 9.80665f;
void BMI160Component::internal_setup_(int stage) { void BMI160Component::internal_setup_(int stage) {
switch (stage) { switch (stage) {
case 0: case 0:
ESP_LOGCONFIG(TAG, "Setting up BMI160..."); ESP_LOGCONFIG(TAG, "Running setup");
uint8_t chipid; uint8_t chipid;
if (!this->read_byte(BMI160_REGISTER_CHIPID, &chipid) || (chipid != 0b11010001)) { if (!this->read_byte(BMI160_REGISTER_CHIPID, &chipid) || (chipid != 0b11010001)) {
this->mark_failed(); this->mark_failed();
@@ -189,7 +189,7 @@ void BMI160Component::dump_config() {
ESP_LOGCONFIG(TAG, "BMI160:"); ESP_LOGCONFIG(TAG, "BMI160:");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with BMI160 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
} }
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Acceleration X", this->accel_x_sensor_); LOG_SENSOR(" ", "Acceleration X", this->accel_x_sensor_);

View File

@@ -20,7 +20,7 @@ void BMP085Component::update() {
this->set_timeout("temperature", 5, [this]() { this->read_temperature_(); }); this->set_timeout("temperature", 5, [this]() { this->read_temperature_(); });
} }
void BMP085Component::setup() { void BMP085Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up BMP085..."); ESP_LOGCONFIG(TAG, "Running setup");
uint8_t data[22]; uint8_t data[22];
if (!this->read_bytes(BMP085_REGISTER_AC1_H, data, 22)) { if (!this->read_bytes(BMP085_REGISTER_AC1_H, data, 22)) {
this->mark_failed(); this->mark_failed();

View File

@@ -57,7 +57,7 @@ static const char *iir_filter_to_str(BMP280IIRFilter filter) {
} }
void BMP280Component::setup() { void BMP280Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up BMP280..."); ESP_LOGCONFIG(TAG, "Running setup");
uint8_t chip_id = 0; uint8_t chip_id = 0;
// Read the chip id twice, to work around a bug where the first read is 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:"); ESP_LOGCONFIG(TAG, "BMP280:");
switch (this->error_code_) { switch (this->error_code_) {
case COMMUNICATION_FAILED: case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with BMP280 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break; break;
case WRONG_CHIP_ID: case WRONG_CHIP_ID:
ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BME280?"); ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BME280?");

View File

@@ -70,7 +70,7 @@ static const LogString *iir_filter_to_str(IIRFilter filter) {
void BMP3XXComponent::setup() { void BMP3XXComponent::setup() {
this->error_code_ = NONE; this->error_code_ = NONE;
ESP_LOGCONFIG(TAG, "Setting up BMP3XX..."); ESP_LOGCONFIG(TAG, "Running setup");
// Call the Device base class "initialise" function // Call the Device base class "initialise" function
if (!reset()) { if (!reset()) {
ESP_LOGE(TAG, "Failed to reset BMP3XX..."); ESP_LOGE(TAG, "Failed to reset BMP3XX...");
@@ -154,7 +154,7 @@ void BMP3XXComponent::dump_config() {
case NONE: case NONE:
break; break;
case ERROR_COMMUNICATION_FAILED: case ERROR_COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with BMP3XX failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break; break;
case ERROR_WRONG_CHIP_ID: case ERROR_WRONG_CHIP_ID:
ESP_LOGE( ESP_LOGE(

View File

@@ -122,7 +122,7 @@ void BMP581Component::setup() {
*/ */
this->error_code_ = NONE; this->error_code_ = NONE;
ESP_LOGCONFIG(TAG, "Setting up BMP581..."); ESP_LOGCONFIG(TAG, "Running setup");
//////////////////// ////////////////////
// 1) Soft reboot // // 1) Soft reboot //

View File

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

View File

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

View File

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

View File

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

View File

@@ -163,7 +163,7 @@ void CCS811Component::dump_config() {
if (this->is_failed()) { if (this->is_failed()) {
switch (this->error_code_) { switch (this->error_code_) {
case COMMUNICATION_FAILED: case COMMUNICATION_FAILED:
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?"); ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
break; break;
case INVALID_ID: case INVALID_ID:
ESP_LOGW(TAG, "Sensor reported an invalid ID. Is this a CCS811?"); 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; } float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; }
void CD74HC4067Component::setup() { void CD74HC4067Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up CD74HC4067..."); ESP_LOGCONFIG(TAG, "Running setup");
this->pin_s0_->setup(); this->pin_s0_->setup();
this->pin_s1_->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"; static const char *const TAG = "ch422g";
void CH422GComponent::setup() { void CH422GComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up CH422G..."); ESP_LOGCONFIG(TAG, "Running setup");
// set outputs before mode // set outputs before mode
this->write_outputs_(); this->write_outputs_();
// Set mode and check for errors // Set mode and check for errors
@@ -37,7 +37,7 @@ void CH422GComponent::dump_config() {
ESP_LOGCONFIG(TAG, "CH422G:"); ESP_LOGCONFIG(TAG, "CH422G:");
LOG_I2C_DEVICE(this) LOG_I2C_DEVICE(this)
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with CH422G failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
} }
} }

View File

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

View File

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

View File

@@ -0,0 +1,112 @@
#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

@@ -0,0 +1,40 @@
#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

@@ -0,0 +1,72 @@
"""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

@@ -52,7 +52,7 @@ bool CS5460AComponent::softreset_() {
} }
void CS5460AComponent::setup() { void CS5460AComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up CS5460A..."); ESP_LOGCONFIG(TAG, "Running setup");
float current_full_scale = (pga_gain_ == CS5460A_PGA_GAIN_10X) ? 0.25 : 0.10; float current_full_scale = (pga_gain_ == CS5460A_PGA_GAIN_10X) ? 0.25 : 0.10;
float voltage_full_scale = 0.25; 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 }; enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
void CSE7761Component::setup() { void CSE7761Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up CSE7761..."); ESP_LOGCONFIG(TAG, "Running setup");
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET); this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04 uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
if ((0x0A04 == syscon) && this->chip_init_()) { if ((0x0A04 == syscon) && this->chip_init_()) {
@@ -57,7 +57,7 @@ void CSE7761Component::setup() {
void CSE7761Component::dump_config() { void CSE7761Component::dump_config() {
ESP_LOGCONFIG(TAG, "CSE7761:"); ESP_LOGCONFIG(TAG, "CSE7761:");
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with CSE7761 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
} }
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8); this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ static const uint8_t DAC7678_REG_INTERNAL_REF_0 = 0x80;
static const uint8_t DAC7678_REG_INTERNAL_REF_1 = 0x90; static const uint8_t DAC7678_REG_INTERNAL_REF_1 = 0x90;
void DAC7678Output::setup() { void DAC7678Output::setup() {
ESP_LOGCONFIG(TAG, "Setting up DAC7678OutputComponent..."); ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGV(TAG, "Resetting device..."); ESP_LOGV(TAG, "Resetting device...");

View File

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

View File

@@ -2,7 +2,6 @@ import base64
from pathlib import Path from pathlib import Path
import re import re
import secrets import secrets
from typing import Optional
import requests import requests
from ruamel.yaml import YAML from ruamel.yaml import YAML
@@ -84,7 +83,7 @@ async def to_code(config):
def import_config( def import_config(
path: str, path: str,
name: str, name: str,
friendly_name: Optional[str], friendly_name: str | None,
project_name: str, project_name: str,
import_url: str, import_url: str,
network: str = CONF_WIFI, network: str = CONF_WIFI,

View File

@@ -15,6 +15,10 @@ namespace debug {
static const char *const TAG = "debug"; static const char *const TAG = "debug";
void DebugComponent::dump_config() { void DebugComponent::dump_config() {
#ifndef ESPHOME_LOG_HAS_DEBUG
return; // Can't log below if debug logging is disabled
#endif
ESP_LOGCONFIG(TAG, "Debug component:"); ESP_LOGCONFIG(TAG, "Debug component:");
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "Device info", this->device_info_); LOG_TEXT_SENSOR(" ", "Device info", this->device_info_);

View File

@@ -10,20 +10,20 @@ static const char *const TAG = "deep_sleep";
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void DeepSleepComponent::setup() { void DeepSleepComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep..."); ESP_LOGCONFIG(TAG, "Running setup");
global_has_deep_sleep = true; global_has_deep_sleep = true;
const optional<uint32_t> run_duration = get_run_duration_(); const optional<uint32_t> run_duration = get_run_duration_();
if (run_duration.has_value()) { if (run_duration.has_value()) {
ESP_LOGI(TAG, "Scheduling Deep Sleep to start in %" PRIu32 " ms", *run_duration); ESP_LOGI(TAG, "Scheduling in %" PRIu32 " ms", *run_duration);
this->set_timeout(*run_duration, [this]() { this->begin_sleep(); }); this->set_timeout(*run_duration, [this]() { this->begin_sleep(); });
} else { } else {
ESP_LOGD(TAG, "Not scheduling Deep Sleep, as no run duration is configured."); ESP_LOGD(TAG, "Not scheduling; no run duration configured");
} }
} }
void DeepSleepComponent::dump_config() { void DeepSleepComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep..."); ESP_LOGCONFIG(TAG, "Deep sleep:");
if (this->sleep_duration_.has_value()) { if (this->sleep_duration_.has_value()) {
uint32_t duration = *this->sleep_duration_ / 1000; uint32_t duration = *this->sleep_duration_ / 1000;
ESP_LOGCONFIG(TAG, " Sleep Duration: %" PRIu32 " ms", duration); ESP_LOGCONFIG(TAG, " Sleep Duration: %" PRIu32 " ms", duration);
@@ -57,7 +57,7 @@ void DeepSleepComponent::begin_sleep(bool manual) {
return; return;
} }
ESP_LOGI(TAG, "Beginning Deep Sleep"); ESP_LOGI(TAG, "Beginning sleep");
if (this->sleep_duration_.has_value()) { if (this->sleep_duration_.has_value()) {
ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_); ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_);
} }

View File

@@ -59,7 +59,7 @@ bool DeepSleepComponent::prepare_to_sleep_() {
// Defer deep sleep until inactive // Defer deep sleep until inactive
if (!this->next_enter_deep_sleep_) { if (!this->next_enter_deep_sleep_) {
this->status_set_warning(); this->status_set_warning();
ESP_LOGW(TAG, "Waiting wakeup pin state change to enter deep sleep..."); ESP_LOGW(TAG, "Waiting for wakeup pin state change");
} }
this->next_enter_deep_sleep_ = true; this->next_enter_deep_sleep_ = true;
return false; return false;

View File

@@ -1,14 +1,22 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ( from esphome.components import (
alarm_control_panel,
binary_sensor, binary_sensor,
button,
climate, climate,
cover, cover,
datetime,
event,
fan, fan,
light, light,
lock,
number, number,
select,
sensor, sensor,
switch, switch,
text,
text_sensor, text_sensor,
valve,
) )
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@@ -20,7 +28,9 @@ from esphome.const import (
CONF_INVERTED, CONF_INVERTED,
CONF_MAX_VALUE, CONF_MAX_VALUE,
CONF_MIN_VALUE, CONF_MIN_VALUE,
CONF_MODE,
CONF_NAME, CONF_NAME,
CONF_OPTIONS,
CONF_OUTPUT_ID, CONF_OUTPUT_ID,
CONF_SENSORS, CONF_SENSORS,
CONF_STATE_CLASS, CONF_STATE_CLASS,
@@ -31,9 +41,11 @@ from esphome.const import (
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_MOISTURE, DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MOTION, DEVICE_CLASS_MOTION,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_UPDATE,
ICON_BLUETOOTH, ICON_BLUETOOTH,
ICON_BLUR, ICON_BLUR,
ICON_THERMOMETER, ICON_THERMOMETER,
@@ -45,38 +57,68 @@ from esphome.const import (
) )
AUTO_LOAD = [ AUTO_LOAD = [
"alarm_control_panel",
"binary_sensor", "binary_sensor",
"button",
"climate", "climate",
"cover", "cover",
"datetime",
"event",
"fan", "fan",
"light", "light",
"lock",
"number", "number",
"select",
"sensor", "sensor",
"switch", "switch",
"text",
"text_sensor", "text_sensor",
"valve",
] ]
demo_ns = cg.esphome_ns.namespace("demo") demo_ns = cg.esphome_ns.namespace("demo")
DemoAlarmControlPanel = demo_ns.class_(
"DemoAlarmControlPanel", alarm_control_panel.AlarmControlPanel, cg.Component
)
DemoAlarmControlPanelType = demo_ns.enum("DemoAlarmControlPanelType", is_class=True)
DemoBinarySensor = demo_ns.class_( DemoBinarySensor = demo_ns.class_(
"DemoBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent "DemoBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent
) )
DemoButton = demo_ns.class_("DemoButton", button.Button)
DemoClimate = demo_ns.class_("DemoClimate", climate.Climate, cg.Component) DemoClimate = demo_ns.class_("DemoClimate", climate.Climate, cg.Component)
DemoClimateType = demo_ns.enum("DemoClimateType", is_class=True) DemoClimateType = demo_ns.enum("DemoClimateType", is_class=True)
DemoCover = demo_ns.class_("DemoCover", cover.Cover, cg.Component) DemoCover = demo_ns.class_("DemoCover", cover.Cover, cg.Component)
DemoCoverType = demo_ns.enum("DemoCoverType", is_class=True) DemoCoverType = demo_ns.enum("DemoCoverType", is_class=True)
DemoDate = demo_ns.class_("DemoDate", datetime.DateEntity, cg.Component)
DemoDateTime = demo_ns.class_("DemoDateTime", datetime.DateTimeEntity, cg.Component)
DemoTime = demo_ns.class_("DemoTime", datetime.TimeEntity, cg.Component)
DemoEvent = demo_ns.class_("DemoEvent", event.Event, cg.Component)
DemoFan = demo_ns.class_("DemoFan", fan.Fan, cg.Component) DemoFan = demo_ns.class_("DemoFan", fan.Fan, cg.Component)
DemoFanType = demo_ns.enum("DemoFanType", is_class=True) DemoFanType = demo_ns.enum("DemoFanType", is_class=True)
DemoLight = demo_ns.class_("DemoLight", light.LightOutput, cg.Component) DemoLight = demo_ns.class_("DemoLight", light.LightOutput, cg.Component)
DemoLightType = demo_ns.enum("DemoLightType", is_class=True) DemoLightType = demo_ns.enum("DemoLightType", is_class=True)
DemoLock = demo_ns.class_("DemoLock", lock.Lock, cg.Component)
DemoLockType = demo_ns.enum("DemoLockType", is_class=True)
DemoNumber = demo_ns.class_("DemoNumber", number.Number, cg.Component) DemoNumber = demo_ns.class_("DemoNumber", number.Number, cg.Component)
DemoNumberType = demo_ns.enum("DemoNumberType", is_class=True) DemoNumberType = demo_ns.enum("DemoNumberType", is_class=True)
DemoSelect = demo_ns.class_("DemoSelect", select.Select, cg.Component)
DemoSelectType = demo_ns.enum("DemoSelectType", is_class=True)
DemoSensor = demo_ns.class_("DemoSensor", sensor.Sensor, cg.PollingComponent) DemoSensor = demo_ns.class_("DemoSensor", sensor.Sensor, cg.PollingComponent)
DemoSwitch = demo_ns.class_("DemoSwitch", switch.Switch, cg.Component) DemoSwitch = demo_ns.class_("DemoSwitch", switch.Switch, cg.Component)
DemoText = demo_ns.class_("DemoText", text.Text, cg.Component)
DemoTextType = demo_ns.enum("DemoTextType", is_class=True)
DemoTextSensor = demo_ns.class_( DemoTextSensor = demo_ns.class_(
"DemoTextSensor", text_sensor.TextSensor, cg.PollingComponent "DemoTextSensor", text_sensor.TextSensor, cg.PollingComponent
) )
DemoValve = demo_ns.class_("DemoValve", valve.Valve)
DemoValveType = demo_ns.enum("DemoValveType", is_class=True)
ALARM_CONTROL_PANEL_TYPES = {
1: DemoAlarmControlPanelType.TYPE_1,
2: DemoAlarmControlPanelType.TYPE_2,
3: DemoAlarmControlPanelType.TYPE_3,
}
CLIMATE_TYPES = { CLIMATE_TYPES = {
1: DemoClimateType.TYPE_1, 1: DemoClimateType.TYPE_1,
2: DemoClimateType.TYPE_2, 2: DemoClimateType.TYPE_2,
@@ -103,21 +145,67 @@ LIGHT_TYPES = {
6: DemoLightType.TYPE_6, 6: DemoLightType.TYPE_6,
7: DemoLightType.TYPE_7, 7: DemoLightType.TYPE_7,
} }
LOCK_TYPES = {
1: DemoLockType.TYPE_1,
2: DemoLockType.TYPE_2,
}
NUMBER_TYPES = { NUMBER_TYPES = {
1: DemoNumberType.TYPE_1, 1: DemoNumberType.TYPE_1,
2: DemoNumberType.TYPE_2, 2: DemoNumberType.TYPE_2,
3: DemoNumberType.TYPE_3, 3: DemoNumberType.TYPE_3,
} }
TEXT_TYPES = {
1: DemoTextType.TYPE_1,
2: DemoTextType.TYPE_2,
}
VALVE_TYPES = {
1: DemoValveType.TYPE_1,
2: DemoValveType.TYPE_2,
}
CONF_ALARM_CONTROL_PANELS = "alarm_control_panels"
CONF_BUTTONS = "buttons"
CONF_CLIMATES = "climates" CONF_CLIMATES = "climates"
CONF_COVERS = "covers" CONF_COVERS = "covers"
CONF_DATETIMES = "datetimes"
CONF_FANS = "fans" CONF_FANS = "fans"
CONF_LIGHTS = "lights" CONF_LIGHTS = "lights"
CONF_LOCKS = "locks"
CONF_NUMBERS = "numbers" CONF_NUMBERS = "numbers"
CONF_SELECTS = "selects"
CONF_TEXTS = "texts"
CONF_VALVES = "valves"
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.Optional(
CONF_ALARM_CONTROL_PANELS,
default=[
{
CONF_NAME: "Demo Alarm Control Panel",
CONF_TYPE: 1,
},
{
CONF_NAME: "Demo Alarm Control Panel Code",
CONF_TYPE: 2,
},
{
CONF_NAME: "Demo Alarm Control Panel Code to Arm",
CONF_TYPE: 3,
},
],
): [
alarm_control_panel.alarm_control_panel_schema(
DemoAlarmControlPanel
).extend(
{
cv.Required(CONF_TYPE): cv.enum(
ALARM_CONTROL_PANEL_TYPES, int=True
),
}
)
],
cv.Optional( cv.Optional(
CONF_BINARY_SENSORS, CONF_BINARY_SENSORS,
default=[ default=[
@@ -135,6 +223,21 @@ CONFIG_SCHEMA = cv.Schema(
cv.polling_component_schema("60s") cv.polling_component_schema("60s")
) )
], ],
cv.Optional(
CONF_BUTTONS,
default=[
{
CONF_NAME: "Demo Update Button",
CONF_DEVICE_CLASS: DEVICE_CLASS_UPDATE,
},
{
CONF_NAME: "Demo Button Identify",
CONF_DEVICE_CLASS: DEVICE_CLASS_IDENTIFY,
},
],
): [
button.button_schema(DemoButton),
],
cv.Optional( cv.Optional(
CONF_CLIMATES, CONF_CLIMATES,
default=[ default=[
@@ -191,6 +294,20 @@ CONFIG_SCHEMA = cv.Schema(
} }
) )
], ],
cv.Optional(
CONF_DATETIMES,
default=[
{CONF_NAME: "Demo DateTime", CONF_TYPE: "DATETIME"},
{CONF_NAME: "Demo Date", CONF_TYPE: "DATE"},
{CONF_NAME: "Demo Time", CONF_TYPE: "TIME"},
],
): [
cv.Any(
datetime.date_schema(DemoDate),
datetime.datetime_schema(DemoDateTime),
datetime.time_schema(DemoTime),
)
],
cv.Optional( cv.Optional(
CONF_FANS, CONF_FANS,
default=[ default=[
@@ -262,6 +379,19 @@ CONFIG_SCHEMA = cv.Schema(
} }
) )
], ],
cv.Optional(
CONF_LOCKS,
default=[
{CONF_NAME: "Demo Lock", CONF_TYPE: 1},
{CONF_NAME: "Demo Lock and Open", CONF_TYPE: 2},
],
): [
lock.lock_schema(DemoLock).extend(
{
cv.Required(CONF_TYPE): cv.enum(LOCK_TYPES, int=True),
}
)
],
cv.Optional( cv.Optional(
CONF_NUMBERS, CONF_NUMBERS,
default=[ default=[
@@ -299,6 +429,25 @@ CONFIG_SCHEMA = cv.Schema(
} }
) )
], ],
cv.Optional(
CONF_SELECTS,
default=[
{
CONF_NAME: "Demo Select 1",
CONF_OPTIONS: ["Option 1", "Option 2", "Option 3"],
},
{
CONF_NAME: "Demo Select 2",
CONF_OPTIONS: ["Option A", "Option B", "Option C"],
},
],
): [
select.select_schema(DemoSelect).extend(
{
cv.Required(CONF_OPTIONS): cv.ensure_list(cv.string_strict),
}
)
],
cv.Optional( cv.Optional(
CONF_SENSORS, CONF_SENSORS,
default=[ default=[
@@ -355,6 +504,19 @@ CONFIG_SCHEMA = cv.Schema(
}, },
], ],
): [switch.switch_schema(DemoSwitch).extend(cv.COMPONENT_SCHEMA)], ): [switch.switch_schema(DemoSwitch).extend(cv.COMPONENT_SCHEMA)],
cv.Optional(
CONF_TEXTS,
default=[
{CONF_NAME: "Demo Text 1", CONF_MODE: "TEXT", CONF_TYPE: 1},
{CONF_NAME: "Demo Text 2", CONF_MODE: "PASSWORD", CONF_TYPE: 2},
],
): [
text.text_schema(DemoText).extend(
{
cv.Required(CONF_TYPE): cv.enum(TEXT_TYPES, int=True),
}
)
],
cv.Optional( cv.Optional(
CONF_TEXT_SENSORS, CONF_TEXT_SENSORS,
default=[ default=[
@@ -371,15 +533,36 @@ CONFIG_SCHEMA = cv.Schema(
cv.polling_component_schema("60s") cv.polling_component_schema("60s")
) )
], ],
cv.Optional(
CONF_VALVES,
default=[
{CONF_NAME: "Demo Valve 1", CONF_TYPE: 1},
{CONF_NAME: "Demo Valve 2", CONF_TYPE: 2},
],
): [
valve.valve_schema(DemoValve).extend(
{
cv.Required(CONF_TYPE): cv.enum(VALVE_TYPES, int=True),
}
)
],
} }
) )
async def to_code(config): async def to_code(config):
for conf in config[CONF_ALARM_CONTROL_PANELS]:
var = await alarm_control_panel.new_alarm_control_panel(conf)
cg.add(var.set_type(conf[CONF_TYPE]))
await cg.register_component(var, conf)
for conf in config[CONF_BINARY_SENSORS]: for conf in config[CONF_BINARY_SENSORS]:
var = await binary_sensor.new_binary_sensor(conf) var = await binary_sensor.new_binary_sensor(conf)
await cg.register_component(var, conf) await cg.register_component(var, conf)
for conf in config[CONF_BUTTONS]:
await button.new_button(conf)
for conf in config[CONF_CLIMATES]: for conf in config[CONF_CLIMATES]:
var = await climate.new_climate(conf) var = await climate.new_climate(conf)
await cg.register_component(var, conf) await cg.register_component(var, conf)
@@ -390,6 +573,10 @@ async def to_code(config):
await cg.register_component(var, conf) await cg.register_component(var, conf)
cg.add(var.set_type(conf[CONF_TYPE])) cg.add(var.set_type(conf[CONF_TYPE]))
for conf in config[CONF_DATETIMES]:
var = await datetime.new_datetime(conf)
await cg.register_component(var, conf)
for conf in config[CONF_FANS]: for conf in config[CONF_FANS]:
var = await fan.new_fan(conf) var = await fan.new_fan(conf)
await cg.register_component(var, conf) await cg.register_component(var, conf)
@@ -400,6 +587,11 @@ async def to_code(config):
await cg.register_component(var, conf) await cg.register_component(var, conf)
cg.add(var.set_type(conf[CONF_TYPE])) cg.add(var.set_type(conf[CONF_TYPE]))
for conf in config[CONF_LOCKS]:
var = await lock.new_lock(conf)
if conf[CONF_TYPE] == 2:
cg.add(var.traits.set_supports_open(True))
for conf in config[CONF_NUMBERS]: for conf in config[CONF_NUMBERS]:
var = await number.new_number( var = await number.new_number(
conf, conf,
@@ -410,6 +602,10 @@ async def to_code(config):
await cg.register_component(var, conf) await cg.register_component(var, conf)
cg.add(var.set_type(conf[CONF_TYPE])) cg.add(var.set_type(conf[CONF_TYPE]))
for conf in config[CONF_SELECTS]:
var = await select.new_select(conf, options=conf[CONF_OPTIONS])
await cg.register_component(var, conf)
for conf in config[CONF_SENSORS]: for conf in config[CONF_SENSORS]:
var = await sensor.new_sensor(conf) var = await sensor.new_sensor(conf)
await cg.register_component(var, conf) await cg.register_component(var, conf)
@@ -418,6 +614,16 @@ async def to_code(config):
var = await switch.new_switch(conf) var = await switch.new_switch(conf)
await cg.register_component(var, conf) await cg.register_component(var, conf)
for conf in config[CONF_TEXTS]:
var = await text.new_text(conf)
await cg.register_component(var, conf)
if conf[CONF_TYPE] == 2:
cg.add(var.traits.set_mode(text.TextMode.TEXT_MODE_PASSWORD))
for conf in config[CONF_TEXT_SENSORS]: for conf in config[CONF_TEXT_SENSORS]:
var = await text_sensor.new_text_sensor(conf) var = await text_sensor.new_text_sensor(conf)
await cg.register_component(var, conf) await cg.register_component(var, conf)
for conf in config[CONF_VALVES]:
var = await valve.new_valve(conf)
cg.add(var.set_type(conf[CONF_TYPE]))

View File

@@ -0,0 +1,65 @@
#pragma once
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#include "esphome/core/component.h"
namespace esphome {
namespace demo {
using namespace alarm_control_panel;
enum class DemoAlarmControlPanelType {
TYPE_1,
TYPE_2,
TYPE_3,
};
class DemoAlarmControlPanel : public AlarmControlPanel, public Component {
public:
void setup() override {}
uint32_t get_supported_features() const override { return ACP_FEAT_ARM_AWAY | ACP_FEAT_TRIGGER; }
bool get_requires_code() const override { return this->type_ != DemoAlarmControlPanelType::TYPE_1; }
bool get_requires_code_to_arm() const override { return this->type_ == DemoAlarmControlPanelType::TYPE_3; }
void set_type(DemoAlarmControlPanelType type) { this->type_ = type; }
protected:
void control(const AlarmControlPanelCall &call) override {
auto state = call.get_state().value_or(ACP_STATE_DISARMED);
switch (state) {
case ACP_STATE_ARMED_AWAY:
if (this->get_requires_code_to_arm() && call.get_code().has_value()) {
if (call.get_code().value() != "1234") {
this->status_momentary_error("Invalid code", 5000);
return;
}
}
this->publish_state(ACP_STATE_ARMED_AWAY);
break;
case ACP_STATE_DISARMED:
if (this->get_requires_code() && call.get_code().has_value()) {
if (call.get_code().value() != "1234") {
this->status_momentary_error("Invalid code", 5000);
return;
}
}
this->publish_state(ACP_STATE_DISARMED);
return;
case ACP_STATE_TRIGGERED:
this->publish_state(ACP_STATE_TRIGGERED);
return;
case ACP_STATE_PENDING:
this->publish_state(ACP_STATE_PENDING);
return;
default:
break;
}
}
DemoAlarmControlPanelType type_{};
};
} // namespace demo
} // namespace esphome

View File

@@ -0,0 +1,15 @@
#pragma once
#include "esphome/components/button/button.h"
#include "esphome/core/log.h"
namespace esphome {
namespace demo {
class DemoButton : public button::Button {
protected:
void press_action() override {}
};
} // namespace demo
} // namespace esphome

View File

@@ -0,0 +1,34 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_DATETIME_DATE
#include "esphome/components/datetime/date_entity.h"
#include "esphome/core/component.h"
namespace esphome {
namespace demo {
class DemoDate : public datetime::DateEntity, public Component {
public:
void setup() override {
this->year_ = 2038;
this->month_ = 01;
this->day_ = 19;
this->publish_state();
}
protected:
void control(const datetime::DateCall &call) override {
this->year_ = call.get_year().value_or(this->year_);
this->month_ = call.get_month().value_or(this->month_);
this->day_ = call.get_day().value_or(this->day_);
this->publish_state();
}
};
} // namespace demo
} // namespace esphome
#endif

View File

@@ -0,0 +1,40 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_DATETIME_DATETIME
#include "esphome/components/datetime/datetime_entity.h"
#include "esphome/core/component.h"
namespace esphome {
namespace demo {
class DemoDateTime : public datetime::DateTimeEntity, public Component {
public:
void setup() override {
this->year_ = 2038;
this->month_ = 01;
this->day_ = 19;
this->hour_ = 3;
this->minute_ = 14;
this->second_ = 8;
this->publish_state();
}
protected:
void control(const datetime::DateTimeCall &call) override {
this->year_ = call.get_year().value_or(this->year_);
this->month_ = call.get_month().value_or(this->month_);
this->day_ = call.get_day().value_or(this->day_);
this->hour_ = call.get_hour().value_or(this->hour_);
this->minute_ = call.get_minute().value_or(this->minute_);
this->second_ = call.get_second().value_or(this->second_);
this->publish_state();
}
};
} // namespace demo
} // namespace esphome
#endif

View File

@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/lock/lock.h"
namespace esphome {
namespace demo {
class DemoLock : public lock::Lock {
protected:
void control(const lock::LockCall &call) override {
auto state = *call.get_state();
this->publish_state(state);
}
};
} // namespace demo
} // namespace esphome

View File

@@ -0,0 +1,15 @@
#pragma once
#include "esphome/components/select/select.h"
#include "esphome/core/component.h"
namespace esphome {
namespace demo {
class DemoSelect : public select::Select, public Component {
protected:
void control(const std::string &value) override { this->publish_state(value); }
};
} // namespace demo
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/text/text.h"
#include "esphome/core/component.h"
namespace esphome {
namespace demo {
class DemoText : public text::Text, public Component {
public:
void setup() override { this->publish_state("I am a text entity"); }
protected:
void control(const std::string &value) override { this->publish_state(value); }
};
} // namespace demo
} // namespace esphome

View File

@@ -0,0 +1,34 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_DATETIME_TIME
#include "esphome/components/datetime/time_entity.h"
#include "esphome/core/component.h"
namespace esphome {
namespace demo {
class DemoTime : public datetime::TimeEntity, public Component {
public:
void setup() override {
this->hour_ = 3;
this->minute_ = 14;
this->second_ = 8;
this->publish_state();
}
protected:
void control(const datetime::TimeCall &call) override {
this->hour_ = call.get_hour().value_or(this->hour_);
this->minute_ = call.get_minute().value_or(this->minute_);
this->second_ = call.get_second().value_or(this->second_);
this->publish_state();
}
};
} // namespace demo
} // namespace esphome
#endif

View File

@@ -0,0 +1,54 @@
#pragma once
#include "esphome/components/valve/valve.h"
namespace esphome {
namespace demo {
enum class DemoValveType {
TYPE_1,
TYPE_2,
};
class DemoValve : public valve::Valve {
public:
valve::ValveTraits get_traits() override {
valve::ValveTraits traits;
if (this->type_ == DemoValveType::TYPE_2) {
traits.set_supports_position(true);
traits.set_supports_toggle(true);
traits.set_supports_stop(true);
}
return traits;
}
void set_type(DemoValveType type) { this->type_ = type; }
protected:
void control(const valve::ValveCall &call) override {
if (call.get_position().has_value()) {
this->position = *call.get_position();
this->publish_state();
return;
} else if (call.get_toggle().has_value()) {
if (call.get_toggle().value()) {
if (this->position == valve::VALVE_OPEN) {
this->position = valve::VALVE_CLOSED;
this->publish_state();
} else {
this->position = valve::VALVE_OPEN;
this->publish_state();
}
}
return;
} else if (call.get_stop()) {
this->current_operation = valve::VALVE_OPERATION_IDLE;
this->publish_state(); // Keep the current position
return;
}
}
DemoValveType type_{};
};
} // namespace demo
} // namespace esphome

View File

@@ -8,25 +8,19 @@ namespace dht {
static const char *const TAG = "dht"; static const char *const TAG = "dht";
void DHT::setup() { void DHT::setup() {
ESP_LOGCONFIG(TAG, "Setting up DHT..."); ESP_LOGCONFIG(TAG, "Running setup");
this->pin_->digital_write(true); this->pin_->digital_write(true);
this->pin_->setup(); this->pin_->setup();
this->pin_->digital_write(true); this->pin_->digital_write(true);
} }
void DHT::dump_config() { void DHT::dump_config() {
ESP_LOGCONFIG(TAG, "DHT:"); ESP_LOGCONFIG(TAG, "DHT:");
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
if (this->is_auto_detect_) { ESP_LOGCONFIG(TAG, " %sModel: %s", this->is_auto_detect_ ? "Auto-detected " : "",
ESP_LOGCONFIG(TAG, " Auto-detected model: %s", this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22"); this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent");
} else if (this->model_ == DHT_MODEL_DHT11) { ESP_LOGCONFIG(TAG, " Internal pull-up: %s", ONOFF(this->pin_->get_flags() & gpio::FLAG_PULLUP));
ESP_LOGCONFIG(TAG, " Model: DHT11");
} else {
ESP_LOGCONFIG(TAG, " Model: DHT22 (or equivalent)");
}
ESP_LOGCONFIG(TAG, " Internal Pull-up: %s", ONOFF(this->pin_->get_flags() & gpio::FLAG_PULLUP));
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
} }
@@ -46,7 +40,7 @@ void DHT::update() {
} }
if (success) { if (success) {
ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); ESP_LOGD(TAG, "Temperature %.1f°C Humidity %.1f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr) if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature); this->temperature_sensor_->publish_state(temperature);
@@ -54,11 +48,8 @@ void DHT::update() {
this->humidity_sensor_->publish_state(humidity); this->humidity_sensor_->publish_state(humidity);
this->status_clear_warning(); this->status_clear_warning();
} else { } else {
const char *str = ""; ESP_LOGW(TAG, "Invalid readings! Check pin number and pull-up resistor%s.",
if (this->is_auto_detect_) { this->is_auto_detect_ ? " and try manually specifying the model" : "");
str = " and consider manually specifying the DHT model using the model option";
}
ESP_LOGW(TAG, "Invalid readings! Please check your wiring (pull-up resistor, pin number)%s.", str);
if (this->temperature_sensor_ != nullptr) if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(NAN); this->temperature_sensor_->publish_state(NAN);
if (this->humidity_sensor_ != nullptr) if (this->humidity_sensor_ != nullptr)
@@ -68,10 +59,12 @@ void DHT::update() {
} }
float DHT::get_setup_priority() const { return setup_priority::DATA; } float DHT::get_setup_priority() const { return setup_priority::DATA; }
void DHT::set_dht_model(DHTModel model) { void DHT::set_dht_model(DHTModel model) {
this->model_ = model; this->model_ = model;
this->is_auto_detect_ = model == DHT_MODEL_AUTO_DETECT; this->is_auto_detect_ = model == DHT_MODEL_AUTO_DETECT;
} }
bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool report_errors) { bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool report_errors) {
*humidity = NAN; *humidity = NAN;
*temperature = NAN; *temperature = NAN;
@@ -121,9 +114,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
while (!this->pin_->digital_read()) { while (!this->pin_->digital_read()) {
if (micros() - start_time > 90) { if (micros() - start_time > 90) {
if (i < 0) { if (i < 0) {
error_code = 1; error_code = 1; // line didn't clear
} else { } else {
error_code = 2; error_code = 2; // rising edge for bit i timeout
} }
break; break;
} }
@@ -139,9 +132,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
end_time = micros(); end_time = micros();
if (end_time - start_time > 90) { if (end_time - start_time > 90) {
if (i < 0) { if (i < 0) {
error_code = 3; error_code = 3; // requesting data failed
} else { } else {
error_code = 4; error_code = 4; // falling edge for bit i timeout
} }
break; break;
} }
@@ -166,22 +159,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
if (!report_errors && error_code != 0) if (!report_errors && error_code != 0)
return false; return false;
switch (error_code) { if (error_code) {
case 1: ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGW(TAG, "Waiting for DHT communication to clear failed!");
return false; return false;
case 2:
ESP_LOGW(TAG, "Rising edge for bit %d failed!", i);
return false;
case 3:
ESP_LOGW(TAG, "Requesting data from DHT failed!");
return false;
case 4:
ESP_LOGW(TAG, "Falling edge for bit %d failed!", i);
return false;
case 0:
default:
break;
} }
ESP_LOGVV(TAG, ESP_LOGVV(TAG,
@@ -206,15 +186,15 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
if (checksum_a == data[4]) { if (checksum_a == data[4]) {
// Data format: 8bit integral RH data + 8bit decimal RH data + 8bit integral T data + 8bit decimal T data + 8bit // Data format: 8bit integral RH data + 8bit decimal RH data + 8bit integral T data + 8bit decimal T data + 8bit
// check sum - some models always have 0 in the decimal part // check sum - some models always have 0 in the decimal part
const uint16_t raw_temperature = uint16_t(data[2]) * 10 + (data[3] & 0x7F); const uint16_t raw_temperature = static_cast<uint16_t>(data[2]) * 10 + (data[3] & 0x7F);
*temperature = raw_temperature / 10.0f; *temperature = static_cast<float>(raw_temperature) / 10.0f;
if ((data[3] & 0x80) != 0) { if ((data[3] & 0x80) != 0) {
// negative // negative
*temperature *= -1; *temperature *= -1;
} }
const uint16_t raw_humidity = uint16_t(data[0]) * 10 + data[1]; const uint16_t raw_humidity = static_cast<uint16_t>(data[0]) * 10 + data[1];
*humidity = raw_humidity / 10.0f; *humidity = static_cast<float>(raw_humidity) / 10.0f;
} else { } else {
// For compatibility with DHT11 models which might only use 2 bytes checksums, only use the data from these two // For compatibility with DHT11 models which might only use 2 bytes checksums, only use the data from these two
// bytes // bytes
@@ -222,8 +202,8 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
*humidity = data[0]; *humidity = data[0];
} }
} else { } else {
uint16_t raw_humidity = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF); uint16_t raw_humidity = encode_uint16(data[0], data[1]);
uint16_t raw_temperature = (uint16_t(data[2] & 0xFF) << 8) | (data[3] & 0xFF); uint16_t raw_temperature = encode_uint16(data[2], data[3]);
if (raw_temperature & 0x8000) { if (raw_temperature & 0x8000) {
if (!(raw_temperature & 0x4000)) if (!(raw_temperature & 0x4000))
@@ -234,24 +214,23 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
if (raw_temperature == 1 && raw_humidity == 10) { if (raw_temperature == 1 && raw_humidity == 10) {
if (report_errors) { if (report_errors) {
ESP_LOGW(TAG, "Invalid temperature+humidity! Sensor reported 1°C and 1%% Hum"); ESP_LOGW(TAG, "Invalid data");
} }
return false; return false;
} }
*humidity = raw_humidity * 0.1f; *humidity = static_cast<float>(raw_humidity) * 0.1f;
if (*humidity > 100) if (*humidity > 100.0f)
*humidity = NAN; *humidity = NAN;
*temperature = int16_t(raw_temperature) * 0.1f; *temperature = static_cast<int16_t>(raw_temperature) * 0.1f;
} }
if (*temperature == 0.0f && (*humidity == 1.0f || *humidity == 2.0f)) { if (*temperature == 0.0f && (*humidity == 1.0f || *humidity == 2.0f)) {
if (report_errors) { if (report_errors) {
ESP_LOGW(TAG, "DHT reports invalid data. Is the update interval too high or the sensor damaged?"); ESP_LOGW(TAG, "Invalid data");
} }
return false; return false;
} }
return true; return true;
} }

View File

@@ -34,7 +34,7 @@ void DHT12Component::update() {
this->status_clear_warning(); this->status_clear_warning();
} }
void DHT12Component::setup() { void DHT12Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up DHT12..."); ESP_LOGCONFIG(TAG, "Running setup");
uint8_t data[5]; uint8_t data[5];
if (!this->read_data_(data)) { if (!this->read_data_(data)) {
this->mark_failed(); this->mark_failed();
@@ -45,7 +45,7 @@ void DHT12Component::dump_config() {
ESP_LOGD(TAG, "DHT12:"); ESP_LOGD(TAG, "DHT12:");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with DHT12 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
} }
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);

View File

@@ -12,7 +12,7 @@ void DPS310Component::setup() {
auto timer = DPS310_INIT_TIMEOUT; auto timer = DPS310_INIT_TIMEOUT;
uint8_t reg = 0; uint8_t reg = 0;
ESP_LOGCONFIG(TAG, "Setting up DPS310..."); ESP_LOGCONFIG(TAG, "Running setup");
// first, reset the sensor // first, reset the sensor
if (!this->write_byte(DPS310_REG_RESET, DPS310_CMD_RESET)) { if (!this->write_byte(DPS310_REG_RESET, DPS310_CMD_RESET)) {
this->mark_failed(); this->mark_failed();
@@ -91,7 +91,7 @@ void DPS310Component::dump_config() {
ESP_LOGCONFIG(TAG, " Revision ID: %u", (this->prod_rev_id_ >> 4) & 0x0F); ESP_LOGCONFIG(TAG, " Revision ID: %u", (this->prod_rev_id_ >> 4) & 0x0F);
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with DPS310 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
} }
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);

View File

@@ -10,7 +10,7 @@ namespace ds1307 {
static const char *const TAG = "ds1307"; static const char *const TAG = "ds1307";
void DS1307Component::setup() { void DS1307Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up DS1307..."); ESP_LOGCONFIG(TAG, "Running setup");
if (!this->read_rtc_()) { if (!this->read_rtc_()) {
this->mark_failed(); this->mark_failed();
} }
@@ -22,7 +22,7 @@ void DS1307Component::dump_config() {
ESP_LOGCONFIG(TAG, "DS1307:"); ESP_LOGCONFIG(TAG, "DS1307:");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with DS1307 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
} }
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
} }

View File

@@ -8,7 +8,7 @@ namespace duty_cycle {
static const char *const TAG = "duty_cycle"; static const char *const TAG = "duty_cycle";
void DutyCycleSensor::setup() { void DutyCycleSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up Duty Cycle Sensor '%s'...", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
this->pin_->setup(); this->pin_->setup();
this->store_.pin = this->pin_->to_isr(); this->store_.pin = this->pin_->to_isr();
this->store_.last_level = this->pin_->digital_read(); this->store_.last_level = this->pin_->digital_read();

View File

@@ -16,7 +16,7 @@ static const uint16_t PRESSURE_ADDRESS = 0x04B0;
void EE895Component::setup() { void EE895Component::setup() {
uint16_t crc16_check = 0; uint16_t crc16_check = 0;
ESP_LOGCONFIG(TAG, "Setting up EE895..."); ESP_LOGCONFIG(TAG, "Running setup");
write_command_(SERIAL_NUMBER, 8); write_command_(SERIAL_NUMBER, 8);
uint8_t serial_number[20]; uint8_t serial_number[20];
this->read(serial_number, 20); this->read(serial_number, 20);
@@ -35,7 +35,7 @@ void EE895Component::dump_config() {
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
switch (this->error_code_) { switch (this->error_code_) {
case COMMUNICATION_FAILED: case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with EE895 failed!"); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break; break;
case CRC_CHECK_FAILED: case CRC_CHECK_FAILED:
ESP_LOGE(TAG, "The crc check failed"); ESP_LOGE(TAG, "The crc check failed");

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