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

Compare commits

...

164 Commits

Author SHA1 Message Date
Jesse Hills
11cc5aef62 Include Arduino header when using Arduino
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2025-05-05 19:49:11 +12:00
Clyde Stubbs
3b8a5db97c [syslog] Implement logging via syslog (#8637)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-05 16:48:13 +12:00
Clyde Stubbs
b8d83d0765 [debug] Show source of last software reboot (#8595) 2025-05-04 23:31:37 -05:00
Clyde Stubbs
e7a2b395fd [uart] Add packet_transport platform (#8214)
Co-authored-by: Faidon Liambotis <paravoid@debian.org>
Co-authored-by: clydeps <U5yx99dok9>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-05 16:15:46 +12:00
Clyde Stubbs
ad99d7fb45 [image] Support the other Pictogrammers icon sets memory: and mdil: (#8676) 2025-05-05 15:31:16 +12:00
Clyde Stubbs
0b032e5c19 [lvgl] Add refresh action to re-evaluate initial widget properties (#8675) 2025-05-05 15:26:16 +12:00
Clyde Stubbs
c7523ace78 [lvgl] Fix image property processing (#8691) 2025-05-05 12:31:22 +12:00
Clyde Stubbs
2a6827e1d2 [lvgl] Allow padding to be negative (#8671) 2025-05-05 12:30:11 +12:00
Clyde Stubbs
125aff79ec [as3935_i2c] Remove redundant includes (#8677) 2025-05-05 12:28:00 +12:00
Clyde Stubbs
a31d8ec309 [packages] Allow list instead of dict for packages (#8688) 2025-05-05 12:26:59 +12:00
Clyde Stubbs
3ed03edfec [display] Fix Rect::inside (#8679) 2025-05-05 12:04:33 +12:00
Clyde Stubbs
4dc6cbe2d7 [esp32_ble_server] Add appearance advertising field (#8672) 2025-05-05 12:02:33 +12:00
Clyde Stubbs
524cd4b4e3 [packet_transport] Extract packet encoding functionality (#8187) 2025-05-05 09:29:17 +12:00
Thomas Rupprecht
84ebbf0762 [climate_ir_lg] use this-> (#8687) 2025-05-05 09:21:57 +12:00
Thomas Rupprecht
670ad7192c unify lowercase x in hexadecimal values (#8686) 2025-05-05 08:47:57 +12:00
Pat Satyshur
bc6ee20270 Add CONF_CONTINUOUS to const.py (#8682) 2025-05-03 22:44:54 -05:00
Thomas Rupprecht
e869a3aec3 [climate] Fix typo and use `this->` (#8678) 2025-05-03 22:41:52 -05:00
Jani
8aff6d2fdd Add GDEY0583T81 support (#8668) 2025-05-03 13:02:35 +10:00
Michał Obrembski
8d33c6de36 Added Banking support to tca9555, fixed input bug (#8003)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-03 10:54:27 +12:00
DJTerentjev
f4b5f32cb4 Update const.py (#8665) 2025-05-01 20:43:58 -05:00
Kevin Ahrendt
2eb9582d0f [micro_wake_word] Clarify spectrogram features calculation (#8669) 2025-05-01 14:04:23 -05:00
Kevin Ahrendt
db97440b04 [microphone] Add software mute and fix wrong type for automations (#8667) 2025-05-01 14:02:33 -05:00
Kevin Ahrendt
ced7ae1d7a [debug] add missing header (#8666) 2025-05-01 08:50:32 -04:00
Trent Houliston
d6699fa3c0 Check for missed pulse_meter ISRs in the main loop (#6126) 2025-05-01 12:29:12 +00:00
functionpointer
836e5ffa43 [mlx90393] Add verification for register contents (#8279)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-02 00:01:02 +12:00
Kevin Ahrendt
c7f597bc75 [voice_assistant] voice assistant can configure enabled wake words (#8657) 2025-05-01 11:11:09 +00:00
Clyde Stubbs
e215fafebe [esp32, debug] Add `cpu_frequency` config option and debug sensor (#8542) 2025-05-01 03:28:07 -05:00
Ralf Habacker
da9c755f67 Add to_ntc_resistance|temperature sensor filter (esphome/feature-requests#2967) (#7898)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-05-01 07:53:12 +00:00
Clyde Stubbs
087ff865a7 [binary_sensor] initial state refactor (#8648)
Co-authored-by: Zsombor Welker <flaktack@welker.hu>
2025-05-01 15:58:35 +12:00
scaiper
8cd62c0308 support self-signed cert in mqtt (#8650) 2025-05-01 15:57:52 +12:00
rwrozelle
f5241ff777 Fix CONFIG_LWIP_TCP_RCV_SCALE and CONFIG_TCP_WND_DEFAULT (#8425) 2025-05-01 15:55:30 +12:00
Clyde Stubbs
1aa2b79311 [i2c] Allow buffers in PSRAM (#8640) 2025-05-01 03:54:56 +00:00
Benjamin Pearce
2dca2d5f85 Daikin IR Climate Remote Target Temperature and Fan Modes (#7946)
Co-authored-by: Benjamin Pearce <gitlab@bcpearce.com>
2025-05-01 15:52:51 +12:00
lastradanet
f03b42ced5 Adding timing budget support for vl53l0x (#7991)
Co-authored-by: Brian Davis <bdavis@mimecast.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-05-01 15:17:27 +12:00
Jesse Hills
0f8a0af244 [defines] Fix USE_MICRO_WAKE_WORD position (#8663) 2025-04-30 21:32:23 -05:00
Keith Burzinski
62646f5f32 [remote_base] Fix compile error on IDF (#8664) 2025-04-30 21:30:36 -05:00
uae007
71f81d2f18 Component pca9685 - phase_begin always set to zero (#8379)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-05-01 02:27:59 +00:00
nworbneb
4ec8414050 [alarm_control_panel] Allow sensor to trigger when alarm disarmed (#7746) 2025-05-01 14:27:14 +12:00
Anton Sergunov
807925fd38 Fix second scrolling run ussue (#8347) 2025-05-01 14:03:35 +12:00
Pat Satyshur
b597565165 Add a function to return the I2C address from an I2CDevice object (#8454)
Co-authored-by: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com>
2025-05-01 13:14:29 +12:00
Jannik
9a9b91b180 Fix HLW8012 sensor not returning values if change_mode_every is set to never (#8456) 2025-05-01 13:12:51 +12:00
Simon
9dcf295df8 [gree] Add support for YAG remotes (#7418) 2025-05-01 13:12:17 +12:00
Andrew J.Swan
e8a3de2642 Bump FastLed version to 3.9.16 (#8402) 2025-05-01 13:07:55 +12:00
Ben Winslow
d2b4dba51f Fix typo preventing tt21100 from autosetting the touchscreen res. (#8662) 2025-05-01 12:55:36 +12:00
Kevin Ahrendt
bf527b0331 [microphone] Bugfix: protect against starting mic if already started (#8656) 2025-05-01 12:45:33 +12:00
Kevin Ahrendt
cdc77506de [micro_wake_word] add new VPE features (#8655) 2025-05-01 12:22:48 +12:00
Stanislav Meduna
6de6a0c82c Only warn if the component blocked for a longer time than the last time (#8064) 2025-05-01 11:57:01 +12:00
Kevin Ahrendt
20062576a3 [i2s_audio] Move microphone reads into a task (#8651)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-04-30 21:50:56 +12:00
Clyde Stubbs
07ba9fdf8f [canbus] Add callback for use by other components (#8578)
Co-authored-by: clydeps <U5yx99dok9>
2025-04-30 21:10:54 +12:00
Jesse Hills
caa255f5d1 [media_player] Fix actions with id as value (#8654) 2025-04-30 20:08:46 +12:00
StriboYar
c0be2c14f3 [debug] Fix compile errors when using the ESP32-C2 (#7474)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-04-30 06:15:56 +00:00
Kevin Ahrendt
9f629dcaa2 [i2s_audio, microphone, micro_wake_word, voice_assistant] Use microphone source to process incoming audio (#8645)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-04-30 10:27:03 +12:00
Thomas Rupprecht
0fe6c65ba3 [adc] sort variants and add links to reference implementations (#8327) 2025-04-29 15:08:08 -05:00
Thomas Rupprecht
c756bb3b3e [pmsa003i] code improvements (#8485) 2025-04-29 14:29:04 -05:00
Jesse Hills
ecb91b0101 [bluetooth_proxy] Allow changing active/passive via api (#8649) 2025-04-29 12:43:55 +00:00
cvwillegen
5f9a509bdc Add code to send/receive GoBox infrared control messages. (#7554)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-04-29 20:21:05 +12:00
Jesse Hills
dc6dd9fe0d Merge branch 'release' into dev 2025-04-29 14:21:09 +12:00
Jesse Hills
5baa034d0d Merge pull request #8647 from esphome/bump-2025.4.1
2025.4.1
2025-04-29 14:20:26 +12:00
Thomas Rupprecht
b8ba26787e [pmsx003] Refactor Imports, Extract Constants, Improve Data Handling & Logging (#8344) 2025-04-28 19:24:48 -05:00
Kevin Ahrendt
844569e96b [audio, microphone] Add MicrophoneSource helper class (#8641)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-04-29 00:05:07 +00:00
Jesse Hills
43580739ac Ensure new const file stays in order (#8642) 2025-04-28 18:58:13 -05:00
aanban
c9f7ab6948 add beo4_protocol to remote_base component (#8307) 2025-04-29 11:50:40 +12:00
Jesse Hills
7900660bb8 Bump version to 2025.4.1 2025-04-29 11:46:20 +12:00
Steffen Banhardt
f096567ac7 Update ens160_base.cpp – fix wrong double negative (#8639) 2025-04-29 11:46:19 +12:00
Clyde Stubbs
5bfb5ccc34 [core] Fix setting of log level/verbose (#8600) 2025-04-29 11:46:19 +12:00
Jesse Hills
1c60038111 [watchdog] Fix for variants with single core (#8602) 2025-04-29 11:46:19 +12:00
Clyde Stubbs
b940db6549 [online_image] Fix printf format; comment fixes (#8607) 2025-04-29 11:46:19 +12:00
J. Nick Koston
aa6e172e14 Fix BLE connection loop caused by timeout and pending disconnect race (#8597) 2025-04-29 11:46:19 +12:00
Clyde Stubbs
86033b6612 [lvgl] Ensure pages are created on the correct display (#8596) 2025-04-29 11:46:19 +12:00
Jesse Hills
59b4a1f554 Fix psram below idf 5 (#8584) 2025-04-29 11:46:19 +12:00
Jesse Hills
b5bdfb3089 [http_request] Fix request headers (#8644)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-29 11:45:41 +12:00
Jesse Hills
a31a5e74bd [const] Move CONF_GAIN_FACTOR to const.py (#8646) 2025-04-29 11:35:38 +12:00
Jesse Hills
629481a526 [esp32_ble] Remove explicit and now incorrect ble override for esp32-c6 (#8643) 2025-04-29 10:46:39 +12:00
Steffen Banhardt
3291a11824 Update ens160_base.cpp – fix wrong double negative (#8639) 2025-04-29 07:18:46 +12:00
baal86
d2ee2d3b23 Fix support for ESP32-H2 in deep_sleep (#8290) 2025-04-28 00:21:24 -05:00
Nate Clark
253e3ec6f6 [mdns] Support templatable config options for MDNS extra services (#8606) 2025-04-28 16:27:39 +12:00
Ben Winslow
fdc4ec8a57 [touchscreen] Clear interrupt flag before reading touch data. (#8632) 2025-04-28 14:29:47 +12:00
Lucas Hartmann
1da0dff8b1 Take advantage of clipping to speed image drawing. (#8630) 2025-04-28 14:18:47 +12:00
Clyde Stubbs
38dae8489e [http_request] Implement for host platform (#8040) 2025-04-28 13:45:28 +12:00
Clyde Stubbs
22c0e1079e [const] Create component-level const repository (#8385) 2025-04-28 12:23:18 +12:00
Clyde Stubbs
2d3f141140 [core] Fix setting of log level/verbose (#8600) 2025-04-28 12:19:50 +12:00
Kevin Ahrendt
e49252ca3d [voice_assistant] Use mic callback and remove esp_adf code (#8627)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-04-28 00:15:28 +00:00
Kevin Ahrendt
c9d1476ae0 [voice_assisant] support start/continue conversation and deallocate buffers (#8610) 2025-04-28 11:30:21 +12:00
Kevin Ahrendt
ee646d7324 [micro_wake_word] Use microphone callback and avoid unnecessary allocation attempts (#8626) 2025-04-28 11:23:25 +12:00
Kevin Ahrendt
e557bca420 [i2s_audio] Microphone reads in loop for callbacks shouldn't ever delay (#8625) 2025-04-28 11:19:01 +12:00
Jesse Hills
adcd6517db [docker] Use new base container image (#8582) 2025-04-28 11:14:50 +12:00
Thomas Rupprecht
4c8f5275f9 replace http with https (#8628) 2025-04-25 14:47:45 -05:00
dependabot[bot]
526db0102c Bump actions/setup-python from 5.5.0 to 5.6.0 in /.github/actions/restore-python (#8616) 2025-04-24 16:18:33 -10:00
dependabot[bot]
8a3fe9ce4c Bump actions/setup-python from 5.5.0 to 5.6.0 (#8618) 2025-04-24 16:18:13 -10:00
dependabot[bot]
fb97ef33a8 Bump setuptools from 78.1.0 to 79.0.1 (#8614)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-24 16:17:39 -10:00
dependabot[bot]
805a6d85a5 Bump ruff from 0.11.6 to 0.11.7 (#8615)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-24 16:12:13 -10:00
dependabot[bot]
8f9fbb15b8 Bump docker/build-push-action from 6.15.0 to 6.16.0 in /.github/actions/build-image (#8619)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-24 15:31:50 -10:00
Guillermo Ruffino
3d24dea455 fix schema-gen-ci failures (#8621) 2025-04-24 15:30:22 -10:00
dependabot[bot]
666d5374ea Bump actions/download-artifact from 4.2.1 to 4.3.0 (#8617)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-24 13:08:24 -10:00
luar123
6792ff6d58 [i2s_audio, i2s_audio_microphone, i2s_audio_speaker] Add basic support for new esp-idf 5.x.x i2s driver. (#8181) 2025-04-24 15:33:58 -05:00
Guillermo Ruffino
f29ccb9e75 Schema gen action (#8593)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-04-24 15:43:37 +12:00
Jesse Hills
911bd54765 [watchdog] Fix for variants with single core (#8602) 2025-04-23 03:49:33 -05:00
Clyde Stubbs
89b1b12993 [online_image] Fix printf format; comment fixes (#8607) 2025-04-23 03:47:15 -05:00
Djordje Mandic
33d79e03d9 [sht4x] Reduce warn spam, added communication check in setup (#8250)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-04-23 03:45:29 -05:00
Craig Andrews
991f3d3a10 [http_request] Ability to get response headers (#8224)
Co-authored-by: guillempages <guillempages@users.noreply.github.com>
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-04-23 14:30:50 +10:00
J. Nick Koston
97823ddd16 Rewrite BLE scanner to use a state machine (#8601) 2025-04-22 08:09:28 -10:00
Vasil Kalchev
6ff180152a Implement min_power for component ac_dimmer using method trailing (#8472) 2025-04-22 07:39:21 -03:00
bdm310
dbb7cbed3e [lvgl] Fix unexpected widget update behavior (#8260) 2025-04-22 20:13:43 +10:00
J. Nick Koston
fbf00f0af4 Fix BLE connection loop caused by timeout and pending disconnect race (#8597) 2025-04-22 21:17:09 +12:00
Roving Ronin
82c6a40371 Update const.py - Add UNIT_MEGAJOULE = "MJ" (#8594) 2025-04-22 09:13:31 +12:00
Clyde Stubbs
0242ac56df [lvgl] Ensure pages are created on the correct display (#8596) 2025-04-22 08:51:52 +12:00
J. Nick Koston
b82666002d Bump esp-idf to 5.1.6 (#8566)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-04-18 05:21:07 +00:00
Jonathan Swoboda
e11883e431 [psram] Add version check to fix 5.3.2 (#8588) 2025-04-17 21:06:41 -05:00
dependabot[bot]
ff5b9df607 Bump ruff from 0.11.5 to 0.11.6 (#8587)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-17 20:24:31 +00:00
Jesse Hills
e5b7e3039a Fix psram below idf 5 (#8584) 2025-04-17 00:04:43 -05:00
Jonathan Swoboda
31ed1eb6f0 [remote_receiver] Filtering fixes (#7777)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2025-04-16 23:33:46 -05:00
Michael Giacomelli
0c3daab649 Remove duplicate co2 and pressure constants (#8583) 2025-04-17 13:40:33 +12:00
dependabot[bot]
816371e3e9 Bump aioesphomeapi from 29.10.0 to 30.0.1 (#8579) 2025-04-16 15:24:15 -10:00
David Woodhouse
3c7bb65a23 Sort resolved IP addresses for dashboard (#8536)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-04-17 13:19:55 +12:00
Jonathan Swoboda
4a65fd76b3 [internal_temperature] Add p4 ifdefs (#8484) 2025-04-17 13:19:33 +12:00
Clyde Stubbs
2704db5eef [analog_threshold] Make thresholds templatable (#8452) 2025-04-17 13:19:12 +12:00
Clyde Stubbs
f10bc73d31 [mapping] Implement yaml-configured maps (#8333) 2025-04-17 13:18:48 +12:00
Clyde Stubbs
55e099450c [uptime] Add format config for text_sensor (#8304) 2025-04-17 13:18:08 +12:00
Andrew J.Swan
248dbd32a5 Add CUBIC PM2005/PM2105 Laser Particle Sensor Module (#8292)
Co-authored-by: Djordje <6750655+DjordjeMandic@users.noreply.github.com>
2025-04-17 13:17:35 +12:00
Jonathan Swoboda
a7b676231a [esp32_rmt_led_strip] Add use_dma option (#8270) 2025-04-17 13:16:10 +12:00
Keith Burzinski
2fd5f9ac58 [api] Allow noise encryption key to be set at runtime (#7296)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-04-17 13:15:25 +12:00
Jordan Zucker
ca4838a5f4 [prometheus] Add climate metrics (#8247) 2025-04-17 13:13:24 +12:00
Jesse Hills
1b72550236 Merge branch 'release' into dev 2025-04-17 12:56:09 +12:00
Jesse Hills
71afd49e3e Merge pull request #8581 from esphome/bump-2025.4.0
2025.4.0
2025-04-17 12:55:25 +12:00
Thomas Rupprecht
e5d718d1b1 fix typo USE_ESP32_VARIANT_ESP32H6 (#8580) 2025-04-16 21:23:58 +00:00
Jesse Hills
61f33d6401 Bump version to 2025.4.0 2025-04-17 09:03:08 +12:00
Jesse Hills
af9b568778 Merge branch 'beta' into dev 2025-04-16 12:13:17 +12:00
Jesse Hills
4a1eec567f Merge pull request #8573 from esphome/bump-2025.4.0b3
2025.4.0b3
2025-04-16 12:12:43 +12:00
Jesse Hills
5706b8476f Bump version to 2025.4.0b3 2025-04-16 08:48:38 +12:00
Marius Greuel
8981a86793 SML runtime optimizations (#8571) 2025-04-16 08:48:38 +12:00
Guillermo Ruffino
a7fd6dc382 Fix vscode validation not showing error squiggles (#8500)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-04-16 08:48:38 +12:00
dependabot[bot]
cb0a87c1f9 Bump zeroconf from 0.146.4 to 0.146.5 (#8569)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 08:48:38 +12:00
Jonathan Swoboda
b913a0b178 [am2315c] Use warning not fail during update (#8499) 2025-04-16 08:48:38 +12:00
David Woodhouse
214454ff51 Fix '--device MQTT' for devices with static IP (#8535) 2025-04-16 08:48:38 +12:00
J. Nick Koston
3677ef71d1 Add typing to protobuf code generator (#8541) 2025-04-16 08:19:22 +12:00
Marius Greuel
7e133171e0 SML runtime optimizations (#8571) 2025-04-16 07:58:14 +12:00
Guillermo Ruffino
bc56d319b5 Fix vscode validation not showing error squiggles (#8500)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-04-16 07:52:07 +12:00
dependabot[bot]
c423a6fb61 Bump codecov/codecov-action from 5.4.0 to 5.4.2 (#8572)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 19:48:51 +00:00
dependabot[bot]
4034bf4f04 Bump zeroconf from 0.146.4 to 0.146.5 (#8569)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 07:58:57 +00:00
Jonathan Swoboda
477abc05ae [am2315c] Use warning not fail during update (#8499) 2025-04-15 17:00:44 +12:00
David Woodhouse
ff2b93a3e4 Fix '--device MQTT' for devices with static IP (#8535) 2025-04-15 16:30:07 +12:00
Calum McConnell
a52d6388a9 Use python3 in place of python, as some systems don't emulate the former (#8568) 2025-04-15 04:13:37 +00:00
Clyde Stubbs
6259ca9ded [lvgl] Small buffers in internal RAM (#8523) 2025-04-15 13:10:44 +12:00
Jesse Hills
f6ef50505b Merge branch 'beta' into dev 2025-04-15 12:13:03 +12:00
Jonathan Swoboda
3c242b7296 Bump esphome-dashboard to 20250415.0 (#8565) 2025-04-14 21:03:43 +00:00
Jesse Hills
00dd5b8339 [ci] Pin codecov action to v5.4.0 (#8564) 2025-04-15 08:09:51 +12:00
dependabot[bot]
a007a8237a Bump zeroconf from 0.146.3 to 0.146.4 (#8563)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-04-15 08:07:29 +12:00
dependabot[bot]
9b86cc37f0 Bump aioesphomeapi from 29.9.0 to 29.10.0 (#8562)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 08:06:48 +12:00
Clyde Stubbs
2dfcf950fa [lvgl] Ensure captured lambdas are in correct order (#8560) 2025-04-15 07:39:56 +12:00
Mischa Siekmann
5908b93e82 Speaker-Media-Player: Fix potential deadlock in audio pipeline (#8548) 2025-04-14 13:51:10 -05:00
Norbert Schulz
995db1f961 Add support for Waveshare 5.65" ACeP 7-Color display (#8557) 2025-04-14 12:02:40 +10:00
dependabot[bot]
abcc656a6f Bump ruff from 0.11.4 to 0.11.5 (#8546)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-13 14:41:33 -10:00
tomaszduda23
4a9f323d92 [core] make require_framework_version generic (#8412) 2025-04-14 10:59:40 +12:00
Calum McConnell
34a4e70cc5 Update setup to make .temp directory (#8558) 2025-04-14 10:48:45 +12:00
Clyde Stubbs
fb5d697c22 [lvgl] Add restore_value to select and number (#8494) 2025-04-14 10:45:30 +12:00
Clyde Stubbs
df4642208e [lvgl] Fix initial focus on roller (#8547) 2025-04-14 10:41:42 +12:00
Clyde Stubbs
264e234efc [axs15231] Don't overwrite manual dimensions (#8553) 2025-04-14 10:41:11 +12:00
J. Nick Koston
ca78dd44b5 Include MAC address in noise hello (#8551) 2025-04-12 09:16:14 -10:00
Jonathan Swoboda
7edf458898 [esp32] Allow pioarduino version 5.3.3 and 5.5.0 (#8526) 2025-04-11 21:34:43 -05:00
Clyde Stubbs
d9873e24a7 [lvgl] Fix use of image without canvas (Bugfix) (#8540) 2025-04-10 01:28:44 +00:00
dependabot[bot]
645bd490ba Bump pytest-cov from 6.0.0 to 6.1.1 (#8537)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-09 10:28:22 -10:00
dependabot[bot]
27f6d00e7a Bump ruff from 0.11.2 to 0.11.4 (#8538)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-09 10:28:07 -10:00
Jesse Hills
f9d668eeca Merge branch 'beta' into dev 2025-04-09 19:50:59 +12:00
Jesse Hills
6b930595e2 Bump version to 2025.5.0-dev 2025-04-09 14:19:05 +12:00
347 changed files with 19655 additions and 3786 deletions

View File

@@ -114,4 +114,5 @@ config/
examples/
Dockerfile
.git/
tests/build/
tests/
.*

View File

@@ -1,15 +1,11 @@
name: Build Image
inputs:
platform:
description: "Platform to build for"
required: true
example: "linux/amd64"
target:
description: "Target to build"
required: true
example: "docker"
baseimg:
description: "Base image type"
build_type:
description: "Build type"
required: true
example: "docker"
suffix:
@@ -19,6 +15,11 @@ inputs:
description: "Version to build"
required: true
example: "2023.12.0"
base_os:
description: "Base OS to use"
required: false
default: "debian"
example: "debian"
runs:
using: "composite"
steps:
@@ -46,52 +47,52 @@ runs:
- name: Build and push to ghcr by digest
id: build-ghcr
uses: docker/build-push-action@v6.15.0
uses: docker/build-push-action@v6.16.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with:
context: .
file: ./docker/Dockerfile
platforms: ${{ inputs.platform }}
target: ${{ inputs.target }}
cache-from: type=gha
cache-to: ${{ steps.cache-to.outputs.value }}
build-args: |
BASEIMGTYPE=${{ inputs.baseimg }}
BUILD_TYPE=${{ inputs.build_type }}
BUILD_VERSION=${{ inputs.version }}
BUILD_OS=${{ inputs.base_os }}
outputs: |
type=image,name=ghcr.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
- name: Export ghcr digests
shell: bash
run: |
mkdir -p /tmp/digests/${{ inputs.target }}/ghcr
mkdir -p /tmp/digests/${{ inputs.build_type }}/ghcr
digest="${{ steps.build-ghcr.outputs.digest }}"
touch "/tmp/digests/${{ inputs.target }}/ghcr/${digest#sha256:}"
touch "/tmp/digests/${{ inputs.build_type }}/ghcr/${digest#sha256:}"
- name: Build and push to dockerhub by digest
id: build-dockerhub
uses: docker/build-push-action@v6.15.0
uses: docker/build-push-action@v6.16.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with:
context: .
file: ./docker/Dockerfile
platforms: ${{ inputs.platform }}
target: ${{ inputs.target }}
cache-from: type=gha
cache-to: ${{ steps.cache-to.outputs.value }}
build-args: |
BASEIMGTYPE=${{ inputs.baseimg }}
BUILD_TYPE=${{ inputs.build_type }}
BUILD_VERSION=${{ inputs.version }}
BUILD_OS=${{ inputs.base_os }}
outputs: |
type=image,name=docker.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
- name: Export dockerhub digests
shell: bash
run: |
mkdir -p /tmp/digests/${{ inputs.target }}/dockerhub
mkdir -p /tmp/digests/${{ inputs.build_type }}/dockerhub
digest="${{ steps.build-dockerhub.outputs.digest }}"
touch "/tmp/digests/${{ inputs.target }}/dockerhub/${digest#sha256:}"
touch "/tmp/digests/${{ inputs.build_type }}/dockerhub/${digest#sha256:}"

View File

@@ -17,7 +17,7 @@ runs:
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@v5.5.0
uses: actions/setup-python@v5.6.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
@@ -34,7 +34,7 @@ runs:
python -m venv venv
source venv/bin/activate
python --version
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -r requirements.txt -r requirements_test.txt
pip install -e .
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
@@ -43,5 +43,5 @@ runs:
python -m venv venv
./venv/Scripts/activate
python --version
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -r requirements.txt -r requirements_test.txt
pip install -e .

View File

@@ -17,7 +17,6 @@ updates:
docker-actions:
applies-to: version-updates
patterns:
- "docker/setup-qemu-action"
- "docker/login-action"
- "docker/setup-buildx-action"
- package-ecosystem: github-actions

View File

@@ -23,7 +23,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.5.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.11"

View File

@@ -37,12 +37,15 @@ jobs:
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "ubuntu-24.04-arm"]
build_type: ["ha-addon", "docker", "lint"]
os: ["ubuntu-24.04", "ubuntu-24.04-arm"]
build_type:
- "ha-addon"
- "docker"
# - "lint"
steps:
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.5.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.9"
- name: Set up Docker Buildx

View File

@@ -39,10 +39,10 @@ jobs:
uses: actions/checkout@v4.1.7
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.5.0
uses: actions/setup-python@v5.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
@@ -58,7 +58,7 @@ jobs:
python -m venv venv
. venv/bin/activate
python --version
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -r requirements.txt -r requirements_test.txt
pip install -e .
ruff:
@@ -165,6 +165,7 @@ jobs:
. venv/bin/activate
script/ci-custom.py
script/build_codeowners.py --check
script/build_language_schema.py --check
pytest:
name: Run pytest
@@ -220,7 +221,7 @@ jobs:
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native tests
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v5.4.2
with:
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -53,7 +53,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.5.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.x"
- name: Set up python environment
@@ -68,31 +68,31 @@ jobs:
uses: pypa/gh-action-pypi-publish@v1.12.4
deploy-docker:
name: Build ESPHome ${{ matrix.platform }}
name: Build ESPHome ${{ matrix.platform.arch }}
if: github.repository == 'esphome/esphome'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
runs-on: ${{ matrix.platform.os }}
needs: [init]
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
- arch: amd64
os: "ubuntu-24.04"
- arch: arm64
os: "ubuntu-24.04-arm"
steps:
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.5.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.9"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.10.0
- name: Set up QEMU
if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.6.0
- name: Log in to docker hub
uses: docker/login-action@v3.4.0
@@ -109,45 +109,36 @@ jobs:
- name: Build docker
uses: ./.github/actions/build-image
with:
platform: ${{ matrix.platform }}
target: docker
baseimg: docker
target: final
build_type: docker
suffix: ""
version: ${{ needs.init.outputs.tag }}
- name: Build ha-addon
uses: ./.github/actions/build-image
with:
platform: ${{ matrix.platform }}
target: hassio
baseimg: hassio
target: final
build_type: ha-addon
suffix: "hassio"
version: ${{ needs.init.outputs.tag }}
- name: Build lint
uses: ./.github/actions/build-image
with:
platform: ${{ matrix.platform }}
target: lint
baseimg: docker
suffix: lint
version: ${{ needs.init.outputs.tag }}
- name: Sanitize platform name
id: sanitize
run: |
echo "${{ matrix.platform }}" | sed 's|/|-|g' > /tmp/platform
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
# - name: Build lint
# uses: ./.github/actions/build-image
# with:
# target: lint
# build_type: lint
# suffix: lint
# version: ${{ needs.init.outputs.tag }}
- name: Upload digests
uses: actions/upload-artifact@v4.6.2
with:
name: digests-${{ steps.sanitize.outputs.name }}
name: digests-${{ matrix.platform.arch }}
path: /tmp/digests
retention-days: 1
deploy-manifest:
name: Publish ESPHome ${{ matrix.image.title }} to ${{ matrix.registry }}
name: Publish ESPHome ${{ matrix.image.build_type }} to ${{ matrix.registry }}
runs-on: ubuntu-latest
needs:
- init
@@ -160,15 +151,12 @@ jobs:
fail-fast: false
matrix:
image:
- title: "ha-addon"
target: "hassio"
suffix: "hassio"
- title: "docker"
target: "docker"
- build_type: "docker"
suffix: ""
- title: "lint"
target: "lint"
suffix: "lint"
- build_type: "ha-addon"
suffix: "hassio"
# - build_type: "lint"
# suffix: "lint"
registry:
- ghcr
- dockerhub
@@ -176,7 +164,7 @@ jobs:
- uses: actions/checkout@v4.1.7
- name: Download digests
uses: actions/download-artifact@v4.2.1
uses: actions/download-artifact@v4.3.0
with:
pattern: digests-*
path: /tmp/digests
@@ -212,7 +200,7 @@ jobs:
done
- name: Create manifest list and push
working-directory: /tmp/digests/${{ matrix.image.target }}/${{ matrix.registry }}
working-directory: /tmp/digests/${{ matrix.image.build_type }}/${{ matrix.registry }}
run: |
docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \
$(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *)

View File

@@ -22,7 +22,7 @@ jobs:
path: lib/home-assistant
- name: Setup Python
uses: actions/setup-python@v5.5.0
uses: actions/setup-python@v5.6.0
with:
python-version: 3.12

View File

@@ -98,6 +98,7 @@ esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/color_temperature/* @jesserockz
esphome/components/combination/* @Cat-Ion @kahrendt
esphome/components/const/* @esphome/core
esphome/components/coolix/* @glmnet
esphome/components/copy/* @OttoWinter
esphome/components/cover/* @esphome/core
@@ -250,6 +251,7 @@ esphome/components/ltr501/* @latonita
esphome/components/ltr_als_ps/* @latonita
esphome/components/lvgl/* @clydebarrow
esphome/components/m5stack_8angle/* @rnauber
esphome/components/mapping/* @clydebarrow
esphome/components/matrix_keypad/* @ssieb
esphome/components/max17043/* @blacknell
esphome/components/max31865/* @DAVe3283
@@ -276,7 +278,7 @@ esphome/components/mdns/* @esphome/core
esphome/components/media_player/* @jesserockz
esphome/components/micro_wake_word/* @jesserockz @kahrendt
esphome/components/micronova/* @jorre05
esphome/components/microphone/* @jesserockz
esphome/components/microphone/* @jesserockz @kahrendt
esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
@@ -317,6 +319,7 @@ esphome/components/online_image/* @clydebarrow @guillempages
esphome/components/opentherm/* @olegtarasov
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/packet_transport/* @clydebarrow
esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @clydebarrow @hwstar
esphome/components/pcf85063/* @brogon
@@ -324,7 +327,9 @@ esphome/components/pcf8563/* @KoenBreeman
esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984
esphome/components/pm1006/* @habbie
esphome/components/pm2005/* @andrewjswan
esphome/components/pmsa003i/* @sjtrny
esphome/components/pmsx003/* @ximex
esphome/components/pmwcs3/* @SeByDocKy
esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
@@ -424,6 +429,7 @@ esphome/components/sun/* @OttoWinter
esphome/components/sun_gtil2/* @Mat931
esphome/components/switch/* @esphome/core
esphome/components/switch/binary_sensor/* @ssieb
esphome/components/syslog/* @clydebarrow
esphome/components/t6615/* @tylermenezes
esphome/components/tc74/* @sethgirvan
esphome/components/tca9548a/* @andreashergert1984
@@ -463,6 +469,7 @@ esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core
esphome/components/uart/button/* @ssieb
esphome/components/uart/packet_transport/* @clydebarrow
esphome/components/udp/* @clydebarrow
esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli

View File

@@ -1,131 +1,54 @@
# Build these with the build.py script
# Example:
# python3 docker/build.py --tag dev --arch amd64 --build-type docker build
ARG BUILD_VERSION=dev
ARG BUILD_OS=alpine
ARG BUILD_BASE_VERSION=2025.04.0
ARG BUILD_TYPE=docker
# One of "docker", "hassio"
ARG BASEIMGTYPE=docker
FROM ghcr.io/esphome/docker-base:${BUILD_OS}-${BUILD_BASE_VERSION} AS base-source-docker
FROM ghcr.io/esphome/docker-base:${BUILD_OS}-ha-addon-${BUILD_BASE_VERSION} AS base-source-ha-addon
ARG BUILD_TYPE
FROM base-source-${BUILD_TYPE} AS base
# https://github.com/hassio-addons/addon-debian-base/releases
FROM ghcr.io/hassio-addons/debian-base:7.2.0 AS base-hassio
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bookworm
FROM debian:12.2-slim AS base-docker
RUN git config --system --add safe.directory "*"
FROM base-${BASEIMGTYPE} AS base
RUN pip install uv==0.6.14
ARG TARGETARCH
ARG TARGETVARIANT
# Note that --break-system-packages is used below because
# https://peps.python.org/pep-0668/ added a safety check that prevents
# installing packages with the same name as a system package. This is
# not a problem for us because we are not concerned about overwriting
# system packages because we are running in an isolated container.
COPY requirements.txt /
RUN \
apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
python3-pip=23.0.1+dfsg-1 \
python3-setuptools=66.1.1-1+deb12u1 \
python3-venv=3.11.2-1+b1 \
python3-wheel=0.38.4-2 \
iputils-ping=3:20221126-1+deb12u1 \
git=1:2.39.5-0+deb12u2 \
curl=7.88.1-10+deb12u12 \
openssh-client=1:9.2p1-2+deb12u5 \
python3-cffi=1.15.1-5 \
libcairo2=1.16.0-7 \
libmagic1=1:5.44-3 \
patch=2.7.6-7 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
ENV \
# Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/
LANG=C.UTF-8 LC_ALL=C.UTF-8 \
# Store globally installed pio libs in /piolibs
PLATFORMIO_GLOBALLIB_DIR=/piolibs
uv pip install --no-cache-dir \
-r /requirements.txt
RUN \
pip3 install \
--break-system-packages --no-cache-dir \
# Keep platformio version in sync with requirements.txt
platformio==6.1.18 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \
&& mkdir -p /piolibs
# First install requirements to leverage caching when requirements don't change
# tmpfs is for https://github.com/rust-lang/cargo/issues/8719
COPY requirements.txt requirements_optional.txt /
RUN --mount=type=tmpfs,target=/root/.cargo <<END-OF-RUN
# Fail on any non-zero status
set -e
# install build tools in case wheels are not available
BUILD_DEPS="
build-essential=12.9
python3-dev=3.11.2-1+b1
zlib1g-dev=1:1.2.13.dfsg-1
libjpeg-dev=1:2.1.5-2
libfreetype-dev=2.12.1+dfsg-5+deb12u4
libssl-dev=3.0.15-1~deb12u1
libffi-dev=3.4.4-1
cargo=0.66.0+ds1-1
pkg-config=1.8.1-1
"
LIB_DEPS="
libtiff6=4.5.0-6+deb12u1
libopenjp2-7=2.5.0-2+deb12u1
"
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
then
apt-get update
apt-get install -y --no-install-recommends $BUILD_DEPS $LIB_DEPS
fi
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo
pip3 install --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
then
apt-get remove -y --purge --auto-remove $BUILD_DEPS
rm -rf /tmp/* /var/{cache,log}/* /var/lib/apt/lists/*
fi
END-OF-RUN
COPY script/platformio_install_deps.py platformio.ini /
RUN /platformio_install_deps.py /platformio.ini --libraries
# Avoid unsafe git error when container user and file config volume permissions don't match
RUN git config --system --add safe.directory '*'
ARG BUILD_VERSION
LABEL \
org.opencontainers.image.authors="The ESPHome Authors" \
org.opencontainers.image.title="ESPHome" \
org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
org.opencontainers.image.url="https://esphome.io/" \
org.opencontainers.image.documentation="https://esphome.io/" \
org.opencontainers.image.source="https://github.com/esphome/esphome" \
org.opencontainers.image.licenses="ESPHome" \
org.opencontainers.image.version=${BUILD_VERSION}
# ======================= docker-type image =======================
FROM base AS docker
# Copy esphome and install
COPY . /esphome
RUN pip3 install --break-system-packages --no-cache-dir -e /esphome
# Settings for dashboard
ENV USERNAME="" PASSWORD=""
FROM base AS base-docker
# Expose the dashboard to Docker
EXPOSE 6052
# Run healthcheck (heartbeat)
HEALTHCHECK --interval=30s --timeout=30s \
CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
COPY docker/docker_entrypoint.sh /entrypoint.sh
@@ -139,43 +62,13 @@ ENTRYPOINT ["/entrypoint.sh"]
CMD ["dashboard", "/config"]
ARG BUILD_VERSION=dev
# Labels
LABEL \
org.opencontainers.image.authors="The ESPHome Authors" \
org.opencontainers.image.title="ESPHome" \
org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
org.opencontainers.image.url="https://esphome.io/" \
org.opencontainers.image.documentation="https://esphome.io/" \
org.opencontainers.image.source="https://github.com/esphome/esphome" \
org.opencontainers.image.licenses="ESPHome" \
org.opencontainers.image.version=${BUILD_VERSION}
# ======================= hassio-type image =======================
FROM base AS hassio
RUN \
apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
nginx-light=1.22.1-9+deb12u1 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
ARG BUILD_VERSION=dev
# ======================= ha-addon-type image =======================
FROM base AS base-ha-addon
# Copy root filesystem
COPY docker/ha-addon-rootfs/ /
# Copy esphome and install
COPY . /esphome
RUN pip3 install --break-system-packages --no-cache-dir -e /esphome
# Labels
ARG BUILD_VERSION
LABEL \
io.hass.name="ESPHome" \
io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
@@ -183,35 +76,9 @@ LABEL \
io.hass.version="${BUILD_VERSION}"
# io.hass.arch is inherited from addon-debian-base
ARG BUILD_TYPE
FROM base-${BUILD_TYPE} AS final
# ======================= lint-type image =======================
FROM base AS lint
ENV \
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
RUN \
curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
&& echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \
&& apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
clang-format-13=1:13.0.1-11+b2 \
patch=2.7.6-7 \
software-properties-common=0.99.30-4.1~deb12u1 \
nano=7.2-1+deb12u1 \
build-essential=12.9 \
python3-dev=3.11.2-1+b1 \
clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
COPY requirements_test.txt /
RUN pip3 install --break-system-packages --no-cache-dir -r /requirements_test.txt
VOLUME ["/esphome"]
WORKDIR /esphome
# Copy esphome and install
COPY . /esphome
RUN uv pip install --no-cache-dir -e /esphome

View File

@@ -54,7 +54,7 @@ manifest_parser = subparsers.add_parser(
class DockerParams:
build_to: str
manifest_to: str
baseimgtype: str
build_type: str
platform: str
target: str
@@ -66,24 +66,19 @@ class DockerParams:
TYPE_LINT: "esphome/esphome-lint",
}[build_type]
build_to = f"{prefix}-{arch}"
baseimgtype = {
TYPE_DOCKER: "docker",
TYPE_HA_ADDON: "hassio",
TYPE_LINT: "docker",
}[build_type]
platform = {
ARCH_AMD64: "linux/amd64",
ARCH_AARCH64: "linux/arm64",
}[arch]
target = {
TYPE_DOCKER: "docker",
TYPE_HA_ADDON: "hassio",
TYPE_DOCKER: "final",
TYPE_HA_ADDON: "final",
TYPE_LINT: "lint",
}[build_type]
return cls(
build_to=build_to,
manifest_to=prefix,
baseimgtype=baseimgtype,
build_type=build_type,
platform=platform,
target=target,
)
@@ -145,7 +140,7 @@ def main():
"buildx",
"build",
"--build-arg",
f"BASEIMGTYPE={params.baseimgtype}",
f"BUILD_TYPE={params.build_type}",
"--build-arg",
f"BUILD_VERSION={args.tag}",
"--cache-from",

View File

@@ -375,10 +375,12 @@ def upload_program(config, args, host):
password = ota_conf.get(CONF_PASSWORD, "")
if (
not is_ip_address(CORE.address) # pylint: disable=too-many-boolean-expressions
and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
and CONF_MQTT in config
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
and (not args.device or args.device in ("MQTT", "OTA"))
and (
((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
or get_port_type(host) == "MQTT"
)
):
from esphome import mqtt

View File

@@ -114,13 +114,14 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
// fully off, disable output immediately
this->gate_pin.digital_write(false);
} else {
auto min_us = this->cycle_time_us * this->min_power / 1000;
if (this->method == DIM_METHOD_TRAILING) {
this->enable_time_us = 1; // cannot be 0
this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
// calculate time until disable in µs with integer arithmetic and take into account min_power
this->disable_time_us = std::max((uint32_t) 10, this->value * (this->cycle_time_us - min_us) / 65535 + min_us);
} else {
// calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
// also take into account min_power
auto min_us = this->cycle_time_us * this->min_power / 1000;
this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
if (this->method == DIM_METHOD_LEADING_PULSE) {

View File

@@ -47,9 +47,10 @@ SAMPLING_MODES = {
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
# pin to adc1 channel mapping
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
VARIANT_ESP32: {
36: adc1_channel_t.ADC1_CHANNEL_0,
37: adc1_channel_t.ADC1_CHANNEL_1,
@@ -60,6 +61,41 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
34: adc1_channel_t.ADC1_CHANNEL_6,
35: adc1_channel_t.ADC1_CHANNEL_7,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
VARIANT_ESP32C2: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
VARIANT_ESP32C3: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
VARIANT_ESP32C6: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
5: adc1_channel_t.ADC1_CHANNEL_5,
6: adc1_channel_t.ADC1_CHANNEL_6,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
VARIANT_ESP32H2: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
VARIANT_ESP32S2: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
@@ -72,6 +108,7 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
VARIANT_ESP32S3: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
@@ -84,40 +121,12 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
VARIANT_ESP32C3: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
VARIANT_ESP32C2: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
VARIANT_ESP32C6: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
5: adc1_channel_t.ADC1_CHANNEL_5,
6: adc1_channel_t.ADC1_CHANNEL_6,
},
VARIANT_ESP32H2: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
},
}
# pin to adc2 channel mapping
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
# TODO: add other variants
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
VARIANT_ESP32: {
4: adc2_channel_t.ADC2_CHANNEL_0,
0: adc2_channel_t.ADC2_CHANNEL_1,
@@ -130,6 +139,19 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
25: adc2_channel_t.ADC2_CHANNEL_8,
26: adc2_channel_t.ADC2_CHANNEL_9,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
VARIANT_ESP32C2: {
5: adc2_channel_t.ADC2_CHANNEL_0,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
VARIANT_ESP32C3: {
5: adc2_channel_t.ADC2_CHANNEL_0,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
VARIANT_ESP32C6: {}, # no ADC2
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
VARIANT_ESP32H2: {}, # no ADC2
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
VARIANT_ESP32S2: {
11: adc2_channel_t.ADC2_CHANNEL_0,
12: adc2_channel_t.ADC2_CHANNEL_1,
@@ -142,6 +164,7 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
19: adc2_channel_t.ADC2_CHANNEL_8,
20: adc2_channel_t.ADC2_CHANNEL_9,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
VARIANT_ESP32S3: {
11: adc2_channel_t.ADC2_CHANNEL_0,
12: adc2_channel_t.ADC2_CHANNEL_1,
@@ -154,12 +177,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
19: adc2_channel_t.ADC2_CHANNEL_8,
20: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32C3: {
5: adc2_channel_t.ADC2_CHANNEL_0,
},
VARIANT_ESP32C2: {},
VARIANT_ESP32C6: {},
VARIANT_ESP32H2: {},
}

View File

@@ -128,7 +128,7 @@ void AM2315C::update() {
data[2] = 0x00;
if (this->write(data, 3) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Write failed!");
this->mark_failed();
this->status_set_warning();
return;
}
@@ -138,12 +138,12 @@ void AM2315C::update() {
uint8_t status = 0;
if (this->read(&status, 1) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Read failed!");
this->mark_failed();
this->status_set_warning();
return;
}
if ((status & 0x80) == 0x80) {
ESP_LOGE(TAG, "HW still busy!");
this->mark_failed();
this->status_set_warning();
return;
}
@@ -151,7 +151,7 @@ void AM2315C::update() {
uint8_t data[7];
if (this->read(data, 7) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Read failed!");
this->mark_failed();
this->status_set_warning();
return;
}

View File

@@ -14,7 +14,8 @@ void AnalogThresholdBinarySensor::setup() {
if (std::isnan(sensor_value)) {
this->publish_initial_state(false);
} else {
this->publish_initial_state(sensor_value >= (this->lower_threshold_ + this->upper_threshold_) / 2.0f);
this->publish_initial_state(sensor_value >=
(this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f);
}
}
@@ -24,7 +25,8 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
this->sensor_->add_on_state_callback([this](float sensor_value) {
// if there is an invalid sensor reading, ignore the change and keep the current state
if (!std::isnan(sensor_value)) {
this->publish_state(sensor_value >= (this->state ? this->lower_threshold_ : this->upper_threshold_));
this->publish_state(sensor_value >=
(this->state ? this->lower_threshold_.value() : this->upper_threshold_.value()));
}
});
}
@@ -32,8 +34,8 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
void AnalogThresholdBinarySensor::dump_config() {
LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this);
LOG_SENSOR(" ", "Sensor", this->sensor_);
ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_);
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_);
ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_.value());
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_.value());
}
} // namespace analog_threshold

View File

@@ -15,14 +15,13 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
float get_setup_priority() const override { return setup_priority::DATA; }
void set_sensor(sensor::Sensor *analog_sensor);
void set_upper_threshold(float threshold) { this->upper_threshold_ = threshold; }
void set_lower_threshold(float threshold) { this->lower_threshold_ = threshold; }
template<typename T> void set_upper_threshold(T upper_threshold) { this->upper_threshold_ = upper_threshold; }
template<typename T> void set_lower_threshold(T lower_threshold) { this->lower_threshold_ = lower_threshold; }
protected:
sensor::Sensor *sensor_{nullptr};
float upper_threshold_;
float lower_threshold_;
TemplatableValue<float> upper_threshold_{};
TemplatableValue<float> lower_threshold_{};
};
} // namespace analog_threshold

View File

@@ -18,11 +18,11 @@ CONFIG_SCHEMA = (
{
cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
cv.Required(CONF_THRESHOLD): cv.Any(
cv.float_,
cv.templatable(cv.float_),
cv.Schema(
{
cv.Required(CONF_UPPER): cv.float_,
cv.Required(CONF_LOWER): cv.float_,
cv.Required(CONF_UPPER): cv.templatable(cv.float_),
cv.Required(CONF_LOWER): cv.templatable(cv.float_),
}
),
),
@@ -39,9 +39,11 @@ async def to_code(config):
sens = await cg.get_variable(config[CONF_SENSOR_ID])
cg.add(var.set_sensor(sens))
if isinstance(config[CONF_THRESHOLD], float):
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD]))
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD]))
if isinstance(config[CONF_THRESHOLD], dict):
lower = await cg.templatable(config[CONF_THRESHOLD][CONF_LOWER], [], float)
upper = await cg.templatable(config[CONF_THRESHOLD][CONF_UPPER], [], float)
else:
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD][CONF_UPPER]))
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD][CONF_LOWER]))
lower = await cg.templatable(config[CONF_THRESHOLD], [], float)
upper = lower
cg.add(var.set_upper_threshold(upper))
cg.add(var.set_lower_threshold(lower))

View File

@@ -82,6 +82,19 @@ ACTIONS_SCHEMA = automation.validate_automation(
),
)
ENCRYPTION_SCHEMA = cv.Schema(
{
cv.Optional(CONF_KEY): validate_encryption_key,
}
)
def _encryption_schema(config):
if config is None:
config = {}
return ENCRYPTION_SCHEMA(config)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -95,11 +108,7 @@ CONFIG_SCHEMA = cv.All(
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
): ACTIONS_SCHEMA,
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
cv.Optional(CONF_ENCRYPTION): cv.Schema(
{
cv.Required(CONF_KEY): validate_encryption_key,
}
),
cv.Optional(CONF_ENCRYPTION): _encryption_schema,
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True
),
@@ -151,9 +160,17 @@ async def to_code(config):
config[CONF_ON_CLIENT_DISCONNECTED],
)
if encryption_config := config.get(CONF_ENCRYPTION):
decoded = base64.b64decode(encryption_config[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded)))
if (encryption_config := config.get(CONF_ENCRYPTION, None)) is not None:
if key := encryption_config.get(CONF_KEY):
decoded = base64.b64decode(key)
cg.add(var.set_noise_psk(list(decoded)))
else:
# No key provided, but encryption desired
# This will allow a plaintext client to provide a noise key,
# send it to the device, and then switch to noise.
# The key will be saved in flash and used for future connections
# and plaintext disabled. Only a factory reset can remove it.
cg.add_define("USE_API_PLAINTEXT")
cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.6")
else:

View File

@@ -31,6 +31,7 @@ service APIConnection {
option (needs_authentication) = false;
}
rpc execute_service (ExecuteServiceRequest) returns (void) {}
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
rpc cover_command (CoverCommandRequest) returns (void) {}
rpc fan_command (FanCommandRequest) returns (void) {}
@@ -60,6 +61,7 @@ service APIConnection {
rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_scanner_set_mode(BluetoothScannerSetModeRequest) returns (void) {}
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {}
@@ -230,6 +232,9 @@ message DeviceInfoResponse {
// The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
string bluetooth_mac_address = 18;
// Supports receiving and saving api encryption key
bool api_encryption_supported = 19;
}
message ListEntitiesRequest {
@@ -654,6 +659,23 @@ message SubscribeLogsResponse {
bool send_failed = 4;
}
// ==================== NOISE ENCRYPTION ====================
message NoiseEncryptionSetKeyRequest {
option (id) = 124;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_API_NOISE";
bytes key = 1;
}
message NoiseEncryptionSetKeyResponse {
option (id) = 125;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_API_NOISE";
bool success = 1;
}
// ==================== HOMEASSISTANT.SERVICE ====================
message SubscribeHomeassistantServicesRequest {
option (id) = 34;
@@ -1451,6 +1473,37 @@ message BluetoothDeviceClearCacheResponse {
int32 error = 3;
}
enum BluetoothScannerState {
BLUETOOTH_SCANNER_STATE_IDLE = 0;
BLUETOOTH_SCANNER_STATE_STARTING = 1;
BLUETOOTH_SCANNER_STATE_RUNNING = 2;
BLUETOOTH_SCANNER_STATE_FAILED = 3;
BLUETOOTH_SCANNER_STATE_STOPPING = 4;
BLUETOOTH_SCANNER_STATE_STOPPED = 5;
}
enum BluetoothScannerMode {
BLUETOOTH_SCANNER_MODE_PASSIVE = 0;
BLUETOOTH_SCANNER_MODE_ACTIVE = 1;
}
message BluetoothScannerStateResponse {
option(id) = 126;
option(source) = SOURCE_SERVER;
option(ifdef) = "USE_BLUETOOTH_PROXY";
BluetoothScannerState state = 1;
BluetoothScannerMode mode = 2;
}
message BluetoothScannerSetModeRequest {
option(id) = 127;
option(source) = SOURCE_CLIENT;
option(ifdef) = "USE_BLUETOOTH_PROXY";
BluetoothScannerMode mode = 1;
}
// ==================== PUSH TO TALK ====================
enum VoiceAssistantSubscribeFlag {
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;

View File

@@ -62,7 +62,14 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
: parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) {
this->proto_write_buffer_.reserve(64);
#if defined(USE_API_PLAINTEXT)
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
auto noise_ctx = parent->get_noise_ctx();
if (noise_ctx->has_psk()) {
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
} else {
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
}
#elif defined(USE_API_PLAINTEXT)
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
#elif defined(USE_API_NOISE)
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
@@ -1468,6 +1475,11 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
return resp;
}
void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_scanner_set_mode(
msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE);
}
#endif
#ifdef USE_VOICE_ASSISTANT
@@ -1848,6 +1860,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#ifdef USE_VOICE_ASSISTANT
resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version();
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
#endif
#ifdef USE_API_NOISE
resp.api_encryption_supported = true;
#endif
return resp;
}
@@ -1869,6 +1884,26 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
ESP_LOGV(TAG, "Could not find matching service!");
}
}
#ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) {
psk_t psk{};
NoiseEncryptionSetKeyResponse resp;
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
ESP_LOGW(TAG, "Invalid encryption key length");
resp.success = false;
return resp;
}
if (!this->parent_->save_noise_psk(psk, true)) {
ESP_LOGW(TAG, "Failed to save encryption key");
resp.success = false;
return resp;
}
resp.success = true;
return resp;
}
#endif
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
state_subs_at_ = 0;
}

View File

@@ -221,6 +221,7 @@ class APIConnection : public APIServerConnection {
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
const SubscribeBluetoothConnectionsFreeRequest &msg) override;
void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
#endif
#ifdef USE_HOMEASSISTANT_TIME
@@ -300,6 +301,9 @@ class APIConnection : public APIServerConnection {
return {};
}
void execute_service(const ExecuteServiceRequest &msg) override;
#ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
#endif
bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
bool is_connection_setup() override {

View File

@@ -1,6 +1,6 @@
#pragma once
#include <cstdint>
#include <array>
#include <cstdint>
#include "esphome/core/defines.h"
namespace esphome {
@@ -11,11 +11,20 @@ using psk_t = std::array<uint8_t, 32>;
class APINoiseContext {
public:
void set_psk(psk_t psk) { psk_ = psk; }
const psk_t &get_psk() const { return psk_; }
void set_psk(psk_t psk) {
this->psk_ = psk;
bool has_psk = false;
for (auto i : psk) {
has_psk |= i;
}
this->has_psk_ = has_psk;
}
const psk_t &get_psk() const { return this->psk_; }
bool has_psk() const { return this->has_psk_; }
protected:
psk_t psk_;
psk_t psk_{};
bool has_psk_{false};
};
#endif // USE_API_NOISE

View File

@@ -422,6 +422,38 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::BluetoothScannerState>(enums::BluetoothScannerState value) {
switch (value) {
case enums::BLUETOOTH_SCANNER_STATE_IDLE:
return "BLUETOOTH_SCANNER_STATE_IDLE";
case enums::BLUETOOTH_SCANNER_STATE_STARTING:
return "BLUETOOTH_SCANNER_STATE_STARTING";
case enums::BLUETOOTH_SCANNER_STATE_RUNNING:
return "BLUETOOTH_SCANNER_STATE_RUNNING";
case enums::BLUETOOTH_SCANNER_STATE_FAILED:
return "BLUETOOTH_SCANNER_STATE_FAILED";
case enums::BLUETOOTH_SCANNER_STATE_STOPPING:
return "BLUETOOTH_SCANNER_STATE_STOPPING";
case enums::BLUETOOTH_SCANNER_STATE_STOPPED:
return "BLUETOOTH_SCANNER_STATE_STOPPED";
default:
return "UNKNOWN";
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::BluetoothScannerMode>(enums::BluetoothScannerMode value) {
switch (value) {
case enums::BLUETOOTH_SCANNER_MODE_PASSIVE:
return "BLUETOOTH_SCANNER_MODE_PASSIVE";
case enums::BLUETOOTH_SCANNER_MODE_ACTIVE:
return "BLUETOOTH_SCANNER_MODE_ACTIVE";
default:
return "UNKNOWN";
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<>
const char *proto_enum_to_string<enums::VoiceAssistantSubscribeFlag>(enums::VoiceAssistantSubscribeFlag value) {
switch (value) {
@@ -792,6 +824,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->voice_assistant_feature_flags = value.as_uint32();
return true;
}
case 19: {
this->api_encryption_supported = value.as_bool();
return true;
}
default:
return false;
}
@@ -865,6 +901,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(17, this->voice_assistant_feature_flags);
buffer.encode_string(16, this->suggested_area);
buffer.encode_string(18, this->bluetooth_mac_address);
buffer.encode_bool(19, this->api_encryption_supported);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const {
@@ -946,6 +983,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" bluetooth_mac_address: ");
out.append("'").append(this->bluetooth_mac_address).append("'");
out.append("\n");
out.append(" api_encryption_supported: ");
out.append(YESNO(this->api_encryption_supported));
out.append("\n");
out.append("}");
}
#endif
@@ -3009,6 +3050,48 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->key = value.as_string();
return true;
}
default:
return false;
}
}
void NoiseEncryptionSetKeyRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->key); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("NoiseEncryptionSetKeyRequest {\n");
out.append(" key: ");
out.append("'").append(this->key).append("'");
out.append("\n");
out.append("}");
}
#endif
bool NoiseEncryptionSetKeyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->success = value.as_bool();
return true;
}
default:
return false;
}
}
void NoiseEncryptionSetKeyResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("NoiseEncryptionSetKeyResponse {\n");
out.append(" success: ");
out.append(YESNO(this->success));
out.append("\n");
out.append("}");
}
#endif
void SubscribeHomeassistantServicesRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
@@ -6724,6 +6807,61 @@ void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool BluetoothScannerStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->state = value.as_enum<enums::BluetoothScannerState>();
return true;
}
case 2: {
this->mode = value.as_enum<enums::BluetoothScannerMode>();
return true;
}
default:
return false;
}
}
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::BluetoothScannerState>(1, this->state);
buffer.encode_enum<enums::BluetoothScannerMode>(2, this->mode);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothScannerStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothScannerStateResponse {\n");
out.append(" state: ");
out.append(proto_enum_to_string<enums::BluetoothScannerState>(this->state));
out.append("\n");
out.append(" mode: ");
out.append(proto_enum_to_string<enums::BluetoothScannerMode>(this->mode));
out.append("\n");
out.append("}");
}
#endif
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->mode = value.as_enum<enums::BluetoothScannerMode>();
return true;
}
default:
return false;
}
}
void BluetoothScannerSetModeRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::BluetoothScannerMode>(1, this->mode);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothScannerSetModeRequest {\n");
out.append(" mode: ");
out.append(proto_enum_to_string<enums::BluetoothScannerMode>(this->mode));
out.append("\n");
out.append("}");
}
#endif
bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {

View File

@@ -169,6 +169,18 @@ enum BluetoothDeviceRequestType : uint32_t {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5,
BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6,
};
enum BluetoothScannerState : uint32_t {
BLUETOOTH_SCANNER_STATE_IDLE = 0,
BLUETOOTH_SCANNER_STATE_STARTING = 1,
BLUETOOTH_SCANNER_STATE_RUNNING = 2,
BLUETOOTH_SCANNER_STATE_FAILED = 3,
BLUETOOTH_SCANNER_STATE_STOPPING = 4,
BLUETOOTH_SCANNER_STATE_STOPPED = 5,
};
enum BluetoothScannerMode : uint32_t {
BLUETOOTH_SCANNER_MODE_PASSIVE = 0,
BLUETOOTH_SCANNER_MODE_ACTIVE = 1,
};
enum VoiceAssistantSubscribeFlag : uint32_t {
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0,
VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1,
@@ -355,6 +367,7 @@ class DeviceInfoResponse : public ProtoMessage {
uint32_t voice_assistant_feature_flags{0};
std::string suggested_area{};
std::string bluetooth_mac_address{};
bool api_encryption_supported{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -791,6 +804,28 @@ class SubscribeLogsResponse : public ProtoMessage {
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class NoiseEncryptionSetKeyRequest : public ProtoMessage {
public:
std::string key{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class NoiseEncryptionSetKeyResponse : public ProtoMessage {
public:
bool success{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
@@ -1719,6 +1754,29 @@ class BluetoothDeviceClearCacheResponse : public ProtoMessage {
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothScannerStateResponse : public ProtoMessage {
public:
enums::BluetoothScannerState state{};
enums::BluetoothScannerMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothScannerSetModeRequest : public ProtoMessage {
public:
enums::BluetoothScannerMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SubscribeVoiceAssistantRequest : public ProtoMessage {
public:
bool subscribe{false};

View File

@@ -179,6 +179,16 @@ bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorSt
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
return this->send_message_<SubscribeLogsResponse>(msg, 29);
}
#ifdef USE_API_NOISE
#endif
#ifdef USE_API_NOISE
bool APIServerConnectionBase::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_noise_encryption_set_key_response: %s", msg.dump().c_str());
#endif
return this->send_message_<NoiseEncryptionSetKeyResponse>(msg, 125);
}
#endif
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
@@ -462,6 +472,16 @@ bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const B
return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_scanner_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothScannerStateResponse>(msg, 126);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_VOICE_ASSISTANT
@@ -1191,6 +1211,28 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_set_configuration(msg);
#endif
break;
}
case 124: {
#ifdef USE_API_NOISE
NoiseEncryptionSetKeyRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str());
#endif
this->on_noise_encryption_set_key_request(msg);
#endif
break;
}
case 127: {
#ifdef USE_BLUETOOTH_PROXY
BluetoothScannerSetModeRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str());
#endif
this->on_bluetooth_scanner_set_mode_request(msg);
#endif
break;
}
@@ -1311,6 +1353,22 @@ void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest
}
this->execute_service(msg);
}
#ifdef USE_API_NOISE
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg);
if (!this->send_noise_encryption_set_key_response(ret)) {
this->on_fatal_error();
}
}
#endif
#ifdef USE_COVER
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
if (!this->is_connection_setup()) {
@@ -1668,6 +1726,19 @@ void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
this->unsubscribe_bluetooth_le_advertisements(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->bluetooth_scanner_set_mode(msg);
}
#endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
if (!this->is_connection_setup()) {

View File

@@ -83,6 +83,12 @@ class APIServerConnectionBase : public ProtoService {
#endif
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
#ifdef USE_API_NOISE
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
#endif
#ifdef USE_API_NOISE
bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg);
#endif
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
@@ -228,6 +234,12 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
#endif
@@ -349,6 +361,9 @@ class APIServerConnection : public APIServerConnectionBase {
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#ifdef USE_API_NOISE
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
#endif
#ifdef USE_COVER
virtual void cover_command(const CoverCommandRequest &msg) = 0;
#endif
@@ -431,6 +446,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_BLUETOOTH_PROXY
virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) = 0;
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
#endif
@@ -457,6 +475,9 @@ class APIServerConnection : public APIServerConnectionBase {
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
void on_get_time_request(const GetTimeRequest &msg) override;
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#ifdef USE_API_NOISE
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
#endif
#ifdef USE_COVER
void on_cover_command_request(const CoverCommandRequest &msg) override;
#endif
@@ -539,6 +560,9 @@ class APIServerConnection : public APIServerConnectionBase {
void on_unsubscribe_bluetooth_le_advertisements_request(
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
#endif
#ifdef USE_VOICE_ASSISTANT
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
#endif

View File

@@ -22,22 +22,40 @@ namespace api {
static const char *const TAG = "api";
// APIServer
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
APIServer::APIServer() { global_api_server = this; }
void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
this->setup_controller();
socket_ = socket::socket_ip(SOCK_STREAM, 0);
if (socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket.");
#ifdef USE_API_NOISE
uint32_t hash = 88491486UL;
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
SavedNoisePsk noise_pref_saved{};
if (this->noise_pref_.load(&noise_pref_saved)) {
ESP_LOGD(TAG, "Loaded saved Noise PSK");
this->set_noise_psk(noise_pref_saved.psk);
}
#endif
this->socket_ = socket::socket_ip(SOCK_STREAM, 0);
if (this->socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket");
this->mark_failed();
return;
}
int enable = 1;
int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
// we can still continue
}
err = socket_->setblocking(false);
err = this->socket_->setblocking(false);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
this->mark_failed();
@@ -53,14 +71,14 @@ void APIServer::setup() {
return;
}
err = socket_->bind((struct sockaddr *) &server, sl);
err = this->socket_->bind((struct sockaddr *) &server, sl);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
this->mark_failed();
return;
}
err = socket_->listen(4);
err = this->socket_->listen(4);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
this->mark_failed();
@@ -92,18 +110,19 @@ void APIServer::setup() {
}
#endif
}
void APIServer::loop() {
// Accept new clients
while (true) {
struct sockaddr_storage source_addr;
socklen_t addr_len = sizeof(source_addr);
auto sock = socket_->accept((struct sockaddr *) &source_addr, &addr_len);
auto sock = this->socket_->accept((struct sockaddr *) &source_addr, &addr_len);
if (!sock)
break;
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this);
clients_.emplace_back(conn);
this->clients_.emplace_back(conn);
conn->start();
}
@@ -136,16 +155,22 @@ void APIServer::loop() {
}
}
}
void APIServer::dump_config() {
ESP_LOGCONFIG(TAG, "API Server:");
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
#ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Using noise encryption: YES");
ESP_LOGCONFIG(TAG, " Using noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
if (!this->noise_ctx_->has_psk()) {
ESP_LOGCONFIG(TAG, " Supports noise encryption: YES");
}
#else
ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
#endif
}
bool APIServer::uses_password() const { return !this->password_.empty(); }
bool APIServer::check_password(const std::string &password) const {
// depend only on input password length
const char *a = this->password_.c_str();
@@ -174,7 +199,9 @@ bool APIServer::check_password(const std::string &password) const {
return result == 0;
}
void APIServer::handle_disconnect(APIConnection *conn) {}
#ifdef USE_BINARY_SENSOR
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
if (obj->is_internal())
@@ -342,57 +369,6 @@ void APIServer::on_update(update::UpdateEntity *obj) {
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void APIServer::set_password(const std::string &password) { this->password_ = password; }
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
for (auto &client : this->clients_) {
client->send_homeassistant_service_call(call);
}
}
APIServer::APIServer() { global_api_server = this; }
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
.once = false,
});
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
.once = true,
});
};
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
return this->state_subs_;
}
uint16_t APIServer::get_port() const { return this->port_; }
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
#ifdef USE_HOMEASSISTANT_TIME
void APIServer::request_time() {
for (auto &client : this->clients_) {
if (!client->remove_ && client->is_authenticated())
client->send_time_request();
}
}
#endif
bool APIServer::is_connected() const { return !this->clients_.empty(); }
void APIServer::on_shutdown() {
for (auto &c : this->clients_) {
c->send_disconnect_request(DisconnectRequest());
}
delay(10);
}
#ifdef USE_ALARM_CONTROL_PANEL
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
if (obj->is_internal())
@@ -402,6 +378,96 @@ void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; }
void APIServer::set_password(const std::string &password) { this->password_ = password; }
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
for (auto &client : this->clients_) {
client->send_homeassistant_service_call(call);
}
}
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
.once = false,
});
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
.once = true,
});
};
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
return this->state_subs_;
}
uint16_t APIServer::get_port() const { return this->port_; }
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
#ifdef USE_API_NOISE
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
auto &old_psk = this->noise_ctx_->get_psk();
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
ESP_LOGW(TAG, "New PSK matches old");
return true;
}
SavedNoisePsk new_saved_psk{psk};
if (!this->noise_pref_.save(&new_saved_psk)) {
ESP_LOGW(TAG, "Failed to save Noise PSK");
return false;
}
// ensure it's written immediately
if (!global_preferences->sync()) {
ESP_LOGW(TAG, "Failed to sync preferences");
return false;
}
ESP_LOGD(TAG, "Noise PSK saved");
if (make_active) {
this->set_timeout(100, [this, psk]() {
ESP_LOGW(TAG, "Disconnecting all clients to reset connections");
this->set_noise_psk(psk);
for (auto &c : this->clients_) {
c->send_disconnect_request(DisconnectRequest());
}
});
}
return true;
}
#endif
#ifdef USE_HOMEASSISTANT_TIME
void APIServer::request_time() {
for (auto &client : this->clients_) {
if (!client->remove_ && client->is_authenticated())
client->send_time_request();
}
}
#endif
bool APIServer::is_connected() const { return !this->clients_.empty(); }
void APIServer::on_shutdown() {
for (auto &c : this->clients_) {
c->send_disconnect_request(DisconnectRequest());
}
delay(10);
}
} // namespace api
} // namespace esphome
#endif

View File

@@ -19,6 +19,12 @@
namespace esphome {
namespace api {
#ifdef USE_API_NOISE
struct SavedNoisePsk {
psk_t psk;
} PACKED; // NOLINT
#endif
class APIServer : public Component, public Controller {
public:
APIServer();
@@ -35,6 +41,7 @@ class APIServer : public Component, public Controller {
void set_reboot_timeout(uint32_t reboot_timeout);
#ifdef USE_API_NOISE
bool save_noise_psk(psk_t psk, bool make_active = true);
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
#endif // USE_API_NOISE
@@ -142,6 +149,7 @@ class APIServer : public Component, public Controller {
#ifdef USE_API_NOISE
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
ESPPreferenceObject noise_pref_;
#endif // USE_API_NOISE
};

View File

@@ -1,10 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/as3935/as3935.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace as3935_i2c {

View File

@@ -7,7 +7,7 @@
namespace esphome {
namespace as7341 {
static const uint8_t AS7341_CHIP_ID = 0X09;
static const uint8_t AS7341_CHIP_ID = 0x09;
static const uint8_t AS7341_CONFIG = 0x70;
static const uint8_t AS7341_LED = 0x74;

View File

@@ -48,6 +48,12 @@ def set_stream_limits(
min_sample_rate: int = _UNDEF,
max_sample_rate: int = _UNDEF,
):
"""Sets the limits for the audio stream that audio component can handle
When the component sinks audio (e.g., a speaker), these indicate the limits to the audio it can receive.
When the component sources audio (e.g., a microphone), these indicate the limits to the audio it can send.
"""
def set_limits_in_config(config):
if min_bits_per_sample is not _UNDEF:
config[CONF_MIN_BITS_PER_SAMPLE] = min_bits_per_sample
@@ -69,43 +75,87 @@ def final_validate_audio_schema(
name: str,
*,
audio_device: str,
bits_per_sample: int,
channels: int,
sample_rate: int,
bits_per_sample: int = _UNDEF,
channels: int = _UNDEF,
sample_rate: int = _UNDEF,
enabled_channels: list[int] = _UNDEF,
audio_device_issue: bool = False,
):
"""Validates audio compatibility when passed between different components.
The component derived from ``AUDIO_COMPONENT_SCHEMA`` should call ``set_stream_limits`` in a validator to specify its compatible settings
- If audio_device_issue is True, then the error message indicates the user should adjust the AUDIO_COMPONENT_SCHEMA derived component's configuration to match the values passed to this function
- If audio_device_issue is False, then the error message indicates the user should adjust the configuration of the component calling this function, as it falls out of the valid stream limits
Args:
name (str): Friendly name of the component calling this function with an audio component to validate
audio_device (str): The configuration parameter name that contains the ID of an AUDIO_COMPONENT_SCHEMA derived component to validate against
bits_per_sample (int, optional): The desired bits per sample
channels (int, optional): The desired number of channels
sample_rate (int, optional): The desired sample rate
enabled_channels (list[int], optional): The desired enabled channels
audio_device_issue (bool, optional): Format the error message to indicate the problem is in the configuration for the ``audio_device`` component. Defaults to False.
"""
def validate_audio_compatiblity(audio_config):
audio_schema = {}
try:
cv.int_range(
min=audio_config.get(CONF_MIN_BITS_PER_SAMPLE),
max=audio_config.get(CONF_MAX_BITS_PER_SAMPLE),
)(bits_per_sample)
except cv.Invalid as exc:
raise cv.Invalid(
f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
) from exc
if bits_per_sample is not _UNDEF:
try:
cv.int_range(
min=audio_config.get(CONF_MIN_BITS_PER_SAMPLE),
max=audio_config.get(CONF_MAX_BITS_PER_SAMPLE),
)(bits_per_sample)
except cv.Invalid as exc:
if audio_device_issue:
error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires {bits_per_sample} bits per sample."
else:
error_string = f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
raise cv.Invalid(error_string) from exc
try:
cv.int_range(
min=audio_config.get(CONF_MIN_CHANNELS),
max=audio_config.get(CONF_MAX_CHANNELS),
)(channels)
except cv.Invalid as exc:
raise cv.Invalid(
f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
) from exc
if channels is not _UNDEF:
try:
cv.int_range(
min=audio_config.get(CONF_MIN_CHANNELS),
max=audio_config.get(CONF_MAX_CHANNELS),
)(channels)
except cv.Invalid as exc:
if audio_device_issue:
error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires {channels} channels."
else:
error_string = f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
raise cv.Invalid(error_string) from exc
try:
cv.int_range(
min=audio_config.get(CONF_MIN_SAMPLE_RATE),
max=audio_config.get(CONF_MAX_SAMPLE_RATE),
)(sample_rate)
return cv.Schema(audio_schema, extra=cv.ALLOW_EXTRA)(audio_config)
except cv.Invalid as exc:
raise cv.Invalid(
f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
) from exc
if sample_rate is not _UNDEF:
try:
cv.int_range(
min=audio_config.get(CONF_MIN_SAMPLE_RATE),
max=audio_config.get(CONF_MAX_SAMPLE_RATE),
)(sample_rate)
except cv.Invalid as exc:
if audio_device_issue:
error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires a {sample_rate} sample rate."
else:
error_string = f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
raise cv.Invalid(error_string) from exc
if enabled_channels is not _UNDEF:
for channel in enabled_channels:
try:
# Channels are 0-indexed
cv.int_range(
min=0,
max=audio_config.get(CONF_MAX_CHANNELS) - 1,
)(channel)
except cv.Invalid as exc:
if audio_device_issue:
error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires channel {channel}."
else:
error_string = f"Invalid configuration for the {name} component. Enabled channel {channel} {str(exc)}"
raise cv.Invalid(error_string) from exc
return cv.Schema(audio_schema, extra=cv.ALLOW_EXTRA)(audio_config)
return cv.Schema(
{

View File

@@ -4,6 +4,8 @@
#include "esphome/core/hal.h"
#include <cstring>
namespace esphome {
namespace audio {

View File

@@ -6,6 +6,7 @@
#include "audio_transfer_buffer.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/ring_buffer.h"
#ifdef USE_SPEAKER

View File

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

View File

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

View File

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

View File

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

View File

@@ -45,7 +45,7 @@ static const uint8_t BL0906_WRITE_COMMAND = 0xCA;
static const uint8_t BL0906_V_RMS = 0x16;
// Total power
static const uint8_t BL0906_WATT_SUM = 0X2C;
static const uint8_t BL0906_WATT_SUM = 0x2C;
// Current1~6
static const uint8_t BL0906_I_1_RMS = 0x0D; // current_1
@@ -56,29 +56,29 @@ static const uint8_t BL0906_I_5_RMS = 0x13;
static const uint8_t BL0906_I_6_RMS = 0x14; // current_6
// Power1~6
static const uint8_t BL0906_WATT_1 = 0X23; // power_1
static const uint8_t BL0906_WATT_2 = 0X24;
static const uint8_t BL0906_WATT_3 = 0X25;
static const uint8_t BL0906_WATT_4 = 0X26;
static const uint8_t BL0906_WATT_5 = 0X29;
static const uint8_t BL0906_WATT_6 = 0X2A; // power_6
static const uint8_t BL0906_WATT_1 = 0x23; // power_1
static const uint8_t BL0906_WATT_2 = 0x24;
static const uint8_t BL0906_WATT_3 = 0x25;
static const uint8_t BL0906_WATT_4 = 0x26;
static const uint8_t BL0906_WATT_5 = 0x29;
static const uint8_t BL0906_WATT_6 = 0x2A; // power_6
// Active pulse count, unsigned
static const uint8_t BL0906_CF_1_CNT = 0X30; // Channel_1
static const uint8_t BL0906_CF_2_CNT = 0X31;
static const uint8_t BL0906_CF_3_CNT = 0X32;
static const uint8_t BL0906_CF_4_CNT = 0X33;
static const uint8_t BL0906_CF_5_CNT = 0X36;
static const uint8_t BL0906_CF_6_CNT = 0X37; // Channel_6
static const uint8_t BL0906_CF_1_CNT = 0x30; // Channel_1
static const uint8_t BL0906_CF_2_CNT = 0x31;
static const uint8_t BL0906_CF_3_CNT = 0x32;
static const uint8_t BL0906_CF_4_CNT = 0x33;
static const uint8_t BL0906_CF_5_CNT = 0x36;
static const uint8_t BL0906_CF_6_CNT = 0x37; // Channel_6
// Total active pulse count, unsigned
static const uint8_t BL0906_CF_SUM_CNT = 0X39;
static const uint8_t BL0906_CF_SUM_CNT = 0x39;
// Voltage frequency cycle
static const uint8_t BL0906_FREQUENCY = 0X4E;
static const uint8_t BL0906_FREQUENCY = 0x4E;
// Internal temperature
static const uint8_t BL0906_TEMPERATURE = 0X5E;
static const uint8_t BL0906_TEMPERATURE = 0x5E;
// Calibration register
// RMS gain adjustment register

View File

@@ -25,6 +25,22 @@ std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
void BluetoothProxy::setup() {
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
if (this->api_connection_ != nullptr) {
this->send_bluetooth_scanner_state_(state);
}
});
}
void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) {
api::BluetoothScannerStateResponse resp;
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
this->api_connection_->send_bluetooth_scanner_state_response(resp);
}
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
return false;
@@ -265,6 +281,12 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
connection->get_connection_index(), connection->address_str().c_str());
return;
} else if (connection->state() == espbt::ClientState::CONNECTING) {
if (connection->disconnect_pending()) {
ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
connection->get_connection_index(), connection->address_str().c_str());
connection->cancel_pending_disconnect();
return;
}
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already connecting", connection->get_connection_index(),
connection->address_str().c_str());
return;
@@ -447,6 +469,8 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
this->api_connection_ = api_connection;
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
this->parent_->recalculate_advertisement_parser_types();
this->send_bluetooth_scanner_state_(this->parent_->get_scanner_state());
}
void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
@@ -519,6 +543,17 @@ void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_e
this->api_connection_->send_bluetooth_device_unpairing_response(call);
}
void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
if (this->parent_->get_scan_active() == active) {
return;
}
ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
this->parent_->set_scan_active(active);
this->parent_->stop_scan();
this->parent_->set_scan_continuous(
true); // Set this to true to automatically start scanning again when it has cleaned up.
}
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace bluetooth_proxy

View File

@@ -41,6 +41,7 @@ enum BluetoothProxyFeature : uint32_t {
FEATURE_PAIRING = 1 << 3,
FEATURE_CACHE_CLEARING = 1 << 4,
FEATURE_RAW_ADVERTISEMENTS = 1 << 5,
FEATURE_STATE_AND_MODE = 1 << 6,
};
enum BluetoothProxySubscriptionFlag : uint32_t {
@@ -53,6 +54,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
void dump_config() override;
void setup() override;
void loop() override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
@@ -84,6 +86,8 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
void send_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
void send_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
void bluetooth_scanner_set_mode(bool active);
static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
bd_addr[0] = (address >> 40) & 0xff;
bd_addr[1] = (address >> 32) & 0xff;
@@ -107,6 +111,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
uint32_t flags = 0;
flags |= BluetoothProxyFeature::FEATURE_PASSIVE_SCAN;
flags |= BluetoothProxyFeature::FEATURE_RAW_ADVERTISEMENTS;
flags |= BluetoothProxyFeature::FEATURE_STATE_AND_MODE;
if (this->active_) {
flags |= BluetoothProxyFeature::FEATURE_ACTIVE_CONNECTIONS;
flags |= BluetoothProxyFeature::FEATURE_REMOTE_CACHING;
@@ -124,6 +129,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
protected:
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
BluetoothConnection *get_connection_(uint64_t address, bool reserve);

View File

@@ -86,6 +86,9 @@ void Canbus::loop() {
data.push_back(can_message.data[i]);
}
this->callback_manager_(can_message.can_id, can_message.use_extended_id, can_message.remote_transmission_request,
data);
// fire all triggers
for (auto *trigger : this->triggers_) {
if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) &&

View File

@@ -81,6 +81,20 @@ class Canbus : public Component {
void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
void add_trigger(CanbusTrigger *trigger);
/**
* Add a callback to be called when a CAN message is received. All received messages
* are passed to the callback without filtering.
*
* The callback function receives:
* - can_id of the received data
* - extended_id True if the can_id is an extended id
* - rtr If this is a remote transmission request
* - data The message data
*/
void add_callback(
std::function<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)> callback) {
this->callback_manager_.add(std::move(callback));
}
protected:
template<typename... Ts> friend class CanbusSendAction;
@@ -88,6 +102,8 @@ class Canbus : public Component {
uint32_t can_id_;
bool use_extended_id_;
CanSpeed bit_rate_;
CallbackManager<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)>
callback_manager_{};
virtual bool setup_internal();
virtual Error send_message(struct CanFrame *frame);

View File

@@ -20,7 +20,7 @@ enum ClimateMode : uint8_t {
CLIMATE_MODE_FAN_ONLY = 4,
/// The climate device is set to dry/humidity mode
CLIMATE_MODE_DRY = 5,
/** The climate device is adjusting the temperatre dynamically.
/** The climate device is adjusting the temperature dynamically.
* For example, the target temperature can be adjusted based on a schedule, or learned behavior.
* The target temperature can't be adjusted when in this mode.
*/

View File

@@ -40,24 +40,24 @@ namespace climate {
*/
class ClimateTraits {
public:
bool get_supports_current_temperature() const { return supports_current_temperature_; }
bool get_supports_current_temperature() const { return this->supports_current_temperature_; }
void set_supports_current_temperature(bool supports_current_temperature) {
supports_current_temperature_ = supports_current_temperature;
this->supports_current_temperature_ = supports_current_temperature;
}
bool get_supports_current_humidity() const { return supports_current_humidity_; }
bool get_supports_current_humidity() const { return this->supports_current_humidity_; }
void set_supports_current_humidity(bool supports_current_humidity) {
supports_current_humidity_ = supports_current_humidity;
this->supports_current_humidity_ = supports_current_humidity;
}
bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
bool get_supports_two_point_target_temperature() const { return this->supports_two_point_target_temperature_; }
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
this->supports_two_point_target_temperature_ = supports_two_point_target_temperature;
}
bool get_supports_target_humidity() const { return supports_target_humidity_; }
bool get_supports_target_humidity() const { return this->supports_target_humidity_; }
void set_supports_target_humidity(bool supports_target_humidity) {
supports_target_humidity_ = supports_target_humidity;
this->supports_target_humidity_ = supports_target_humidity;
}
void set_supported_modes(std::set<ClimateMode> modes) { supported_modes_ = std::move(modes); }
void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); }
void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); }
void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
@@ -72,15 +72,15 @@ class ClimateTraits {
}
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
const std::set<ClimateMode> &get_supported_modes() const { return supported_modes_; }
bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); }
const std::set<ClimateMode> &get_supported_modes() const { return this->supported_modes_; }
void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
bool get_supports_action() const { return supports_action_; }
void set_supports_action(bool supports_action) { this->supports_action_ = supports_action; }
bool get_supports_action() const { return this->supports_action_; }
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { supported_fan_modes_ = std::move(modes); }
void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); }
void add_supported_custom_fan_mode(const std::string &mode) { supported_custom_fan_modes_.insert(mode); }
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { this->supported_fan_modes_ = std::move(modes); }
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.insert(mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
@@ -99,35 +99,37 @@ class ClimateTraits {
void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); }
const std::set<ClimateFanMode> &get_supported_fan_modes() const { return supported_fan_modes_; }
bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); }
bool get_supports_fan_modes() const {
return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty();
}
const std::set<ClimateFanMode> &get_supported_fan_modes() const { return this->supported_fan_modes_; }
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
}
const std::set<std::string> &get_supported_custom_fan_modes() const { return supported_custom_fan_modes_; }
const std::set<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
return supported_custom_fan_modes_.count(custom_fan_mode);
return this->supported_custom_fan_modes_.count(custom_fan_mode);
}
void set_supported_presets(std::set<ClimatePreset> presets) { supported_presets_ = std::move(presets); }
void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); }
void add_supported_custom_preset(const std::string &preset) { supported_custom_presets_.insert(preset); }
bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); }
bool get_supports_presets() const { return !supported_presets_.empty(); }
const std::set<climate::ClimatePreset> &get_supported_presets() const { return supported_presets_; }
void set_supported_presets(std::set<ClimatePreset> presets) { this->supported_presets_ = std::move(presets); }
void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); }
void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.insert(preset); }
bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); }
bool get_supports_presets() const { return !this->supported_presets_.empty(); }
const std::set<climate::ClimatePreset> &get_supported_presets() const { return this->supported_presets_; }
void set_supported_custom_presets(std::set<std::string> supported_custom_presets) {
supported_custom_presets_ = std::move(supported_custom_presets);
this->supported_custom_presets_ = std::move(supported_custom_presets);
}
const std::set<std::string> &get_supported_custom_presets() const { return supported_custom_presets_; }
const std::set<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
bool supports_custom_preset(const std::string &custom_preset) const {
return supported_custom_presets_.count(custom_preset);
return this->supported_custom_presets_.count(custom_preset);
}
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { this->supported_swing_modes_ = std::move(modes); }
void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
@@ -138,54 +140,58 @@ class ClimateTraits {
void set_supports_swing_mode_horizontal(bool supported) {
set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported);
}
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return supported_swing_modes_; }
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); }
bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); }
const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return this->supported_swing_modes_; }
float get_visual_min_temperature() const { return visual_min_temperature_; }
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
float get_visual_max_temperature() const { return visual_max_temperature_; }
void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
float get_visual_target_temperature_step() const { return visual_target_temperature_step_; }
float get_visual_current_temperature_step() const { return visual_current_temperature_step_; }
float get_visual_min_temperature() const { return this->visual_min_temperature_; }
void set_visual_min_temperature(float visual_min_temperature) {
this->visual_min_temperature_ = visual_min_temperature;
}
float get_visual_max_temperature() const { return this->visual_max_temperature_; }
void set_visual_max_temperature(float visual_max_temperature) {
this->visual_max_temperature_ = visual_max_temperature;
}
float get_visual_target_temperature_step() const { return this->visual_target_temperature_step_; }
float get_visual_current_temperature_step() const { return this->visual_current_temperature_step_; }
void set_visual_target_temperature_step(float temperature_step) {
visual_target_temperature_step_ = temperature_step;
this->visual_target_temperature_step_ = temperature_step;
}
void set_visual_current_temperature_step(float temperature_step) {
visual_current_temperature_step_ = temperature_step;
this->visual_current_temperature_step_ = temperature_step;
}
void set_visual_temperature_step(float temperature_step) {
visual_target_temperature_step_ = temperature_step;
visual_current_temperature_step_ = temperature_step;
this->visual_target_temperature_step_ = temperature_step;
this->visual_current_temperature_step_ = temperature_step;
}
int8_t get_target_temperature_accuracy_decimals() const;
int8_t get_current_temperature_accuracy_decimals() const;
float get_visual_min_humidity() const { return visual_min_humidity_; }
void set_visual_min_humidity(float visual_min_humidity) { visual_min_humidity_ = visual_min_humidity; }
float get_visual_max_humidity() const { return visual_max_humidity_; }
void set_visual_max_humidity(float visual_max_humidity) { visual_max_humidity_ = visual_max_humidity; }
float get_visual_min_humidity() const { return this->visual_min_humidity_; }
void set_visual_min_humidity(float visual_min_humidity) { this->visual_min_humidity_ = visual_min_humidity; }
float get_visual_max_humidity() const { return this->visual_max_humidity_; }
void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; }
protected:
void set_mode_support_(climate::ClimateMode mode, bool supported) {
if (supported) {
supported_modes_.insert(mode);
this->supported_modes_.insert(mode);
} else {
supported_modes_.erase(mode);
this->supported_modes_.erase(mode);
}
}
void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) {
if (supported) {
supported_fan_modes_.insert(mode);
this->supported_fan_modes_.insert(mode);
} else {
supported_fan_modes_.erase(mode);
this->supported_fan_modes_.erase(mode);
}
}
void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) {
if (supported) {
supported_swing_modes_.insert(mode);
this->supported_swing_modes_.insert(mode);
} else {
supported_swing_modes_.erase(mode);
this->supported_swing_modes_.erase(mode);
}
}

View File

@@ -32,7 +32,7 @@ const uint32_t FAN_MAX = 0x40;
// Temperature
const uint8_t TEMP_RANGE = TEMP_MAX - TEMP_MIN + 1;
const uint32_t TEMP_MASK = 0XF00;
const uint32_t TEMP_MASK = 0xF00;
const uint32_t TEMP_SHIFT = 8;
const uint16_t BITS = 28;
@@ -43,11 +43,11 @@ void LgIrClimate::transmit_state() {
// ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_);
// Set command
if (send_swing_cmd_) {
send_swing_cmd_ = false;
if (this->send_swing_cmd_) {
this->send_swing_cmd_ = false;
remote_state |= COMMAND_SWING;
} else {
bool climate_is_off = (mode_before_ == climate::CLIMATE_MODE_OFF);
bool climate_is_off = (this->mode_before_ == climate::CLIMATE_MODE_OFF);
switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
remote_state |= climate_is_off ? COMMAND_ON_COOL : COMMAND_COOL;
@@ -71,7 +71,7 @@ void LgIrClimate::transmit_state() {
}
}
mode_before_ = this->mode;
this->mode_before_ = this->mode;
ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
@@ -102,7 +102,7 @@ void LgIrClimate::transmit_state() {
remote_state |= ((temp - 15) << TEMP_SHIFT);
}
transmit_(remote_state);
this->transmit_(remote_state);
this->publish_state();
}
@@ -187,7 +187,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
}
void LgIrClimate::transmit_(uint32_t value) {
calc_checksum_(value);
this->calc_checksum_(value);
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value);
auto transmit = this->transmitter_->transmit();

View File

@@ -21,7 +21,7 @@ class LgIrClimate : public climate_ir::ClimateIR {
/// Override control to change settings of the climate device.
void control(const climate::ClimateCall &call) override {
send_swing_cmd_ = call.get_swing_mode().has_value();
this->send_swing_cmd_ = call.get_swing_mode().has_value();
// swing resets after unit powered off
if (call.get_mode().has_value() && *call.get_mode() == climate::CLIMATE_MODE_OFF)
this->swing_mode = climate::CLIMATE_SWING_OFF;

View File

@@ -3,6 +3,8 @@ from esphome.const import CONF_BLUE, CONF_GREEN, CONF_ID, CONF_RED, CONF_WHITE
ColorStruct = cg.esphome_ns.struct("Color")
INSTANCE_TYPE = ColorStruct
MULTI_CONF = True
CONF_RED_INT = "red_int"

View File

@@ -0,0 +1,5 @@
"""Constants used by esphome components."""
CODEOWNERS = ["@esphome/core"]
CONF_DRAW_ROUNDING = "draw_rounding"

View File

@@ -65,7 +65,7 @@ void DaikinClimate::transmit_state() {
transmit.perform();
}
uint8_t DaikinClimate::operation_mode_() {
uint8_t DaikinClimate::operation_mode_() const {
uint8_t operating_mode = DAIKIN_MODE_ON;
switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
@@ -92,9 +92,12 @@ uint8_t DaikinClimate::operation_mode_() {
return operating_mode;
}
uint16_t DaikinClimate::fan_speed_() {
uint16_t DaikinClimate::fan_speed_() const {
uint16_t fan_speed;
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_QUIET:
fan_speed = DAIKIN_FAN_SILENT << 8;
break;
case climate::CLIMATE_FAN_LOW:
fan_speed = DAIKIN_FAN_1 << 8;
break;
@@ -126,12 +129,11 @@ uint16_t DaikinClimate::fan_speed_() {
return fan_speed;
}
uint8_t DaikinClimate::temperature_() {
uint8_t DaikinClimate::temperature_() const {
// Force special temperatures depending on the mode
switch (this->mode) {
case climate::CLIMATE_MODE_FAN_ONLY:
return 0x32;
case climate::CLIMATE_MODE_HEAT_COOL:
case climate::CLIMATE_MODE_DRY:
return 0xc0;
default:
@@ -148,19 +150,25 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
if (frame[DAIKIN_STATE_FRAME_SIZE - 1] != checksum)
return false;
uint8_t mode = frame[5];
// Temperature is given in degrees celcius * 2
// only update for states that use the temperature
uint8_t temperature = frame[6];
if (mode & DAIKIN_MODE_ON) {
switch (mode & 0xF0) {
case DAIKIN_MODE_COOL:
this->mode = climate::CLIMATE_MODE_COOL;
this->target_temperature = static_cast<float>(temperature * 0.5f);
break;
case DAIKIN_MODE_DRY:
this->mode = climate::CLIMATE_MODE_DRY;
break;
case DAIKIN_MODE_HEAT:
this->mode = climate::CLIMATE_MODE_HEAT;
this->target_temperature = static_cast<float>(temperature * 0.5f);
break;
case DAIKIN_MODE_AUTO:
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
this->target_temperature = static_cast<float>(temperature * 0.5f);
break;
case DAIKIN_MODE_FAN:
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
@@ -169,10 +177,6 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
} else {
this->mode = climate::CLIMATE_MODE_OFF;
}
uint8_t temperature = frame[6];
if (!(temperature & 0xC0)) {
this->target_temperature = temperature >> 1;
}
uint8_t fan_mode = frame[8];
uint8_t swing_mode = frame[9];
if (fan_mode & 0xF && swing_mode & 0xF) {
@@ -187,7 +191,6 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
switch (fan_mode & 0xF0) {
case DAIKIN_FAN_1:
case DAIKIN_FAN_2:
case DAIKIN_FAN_SILENT:
this->fan_mode = climate::CLIMATE_FAN_LOW;
break;
case DAIKIN_FAN_3:
@@ -200,6 +203,9 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
case DAIKIN_FAN_AUTO:
this->fan_mode = climate::CLIMATE_FAN_AUTO;
break;
case DAIKIN_FAN_SILENT:
this->fan_mode = climate::CLIMATE_FAN_QUIET;
break;
}
this->publish_state();
return true;

View File

@@ -44,17 +44,17 @@ class DaikinClimate : public climate_ir::ClimateIR {
public:
DaikinClimate()
: climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_FAN_QUIET, climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
protected:
// Transmit via IR the state of this climate controller.
void transmit_state() override;
uint8_t operation_mode_();
uint16_t fan_speed_();
uint8_t temperature_();
uint8_t operation_mode_() const;
uint16_t fan_speed_() const;
uint8_t temperature_() const;
// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;
bool parse_state_frame_(const uint8_t frame[]);

View File

@@ -1,6 +1,7 @@
#include "debug_component.h"
#include <algorithm>
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
@@ -25,6 +26,7 @@ void DebugComponent::dump_config() {
#ifdef USE_SENSOR
LOG_SENSOR(" ", "Free space on heap", this->free_sensor_);
LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_);
LOG_SENSOR(" ", "CPU frequency", this->cpu_frequency_sensor_);
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_);
#endif // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
@@ -86,6 +88,9 @@ void DebugComponent::update() {
this->loop_time_sensor_->publish_state(this->max_loop_time_);
this->max_loop_time_ = 0;
}
if (this->cpu_frequency_sensor_ != nullptr) {
this->cpu_frequency_sensor_->publish_state(arch_get_cpu_freq_hz());
}
#endif // USE_SENSOR
update_platform_();

View File

@@ -34,8 +34,12 @@ class DebugComponent : public PollingComponent {
#endif
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
#ifdef USE_ESP32
void on_shutdown() override;
void set_psram_sensor(sensor::Sensor *psram_sensor) { this->psram_sensor_ = psram_sensor; }
#endif // USE_ESP32
void set_cpu_frequency_sensor(sensor::Sensor *cpu_frequency_sensor) {
this->cpu_frequency_sensor_ = cpu_frequency_sensor;
}
#endif // USE_SENSOR
protected:
uint32_t free_heap_{};
@@ -53,6 +57,7 @@ class DebugComponent : public PollingComponent {
#ifdef USE_ESP32
sensor::Sensor *psram_sensor_{nullptr};
#endif // USE_ESP32
sensor::Sensor *cpu_frequency_sensor_{nullptr};
#endif // USE_SENSOR
#ifdef USE_ESP32
@@ -75,6 +80,7 @@ class DebugComponent : public PollingComponent {
#endif // USE_TEXT_SENSOR
std::string get_reset_reason_();
std::string get_wakeup_cause_();
uint32_t get_free_heap_();
void get_device_info_(std::string &device_info);
void update_platform_();

View File

@@ -1,25 +1,18 @@
#include "debug_component.h"
#ifdef USE_ESP32
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include <esp_sleep.h>
#include <esp_heap_caps.h>
#include <esp_system.h>
#include <esp_chip_info.h>
#include <esp_partition.h>
#if defined(USE_ESP32_VARIANT_ESP32)
#include <esp32/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32C3)
#include <esp32c3/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32C6)
#include <esp32c6/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32S2)
#include <esp32s2/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32S3)
#include <esp32s3/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32H2)
#include <esp32h2/rom/rtc.h>
#endif
#include <map>
#ifdef USE_ARDUINO
#include <Esp.h>
#endif
@@ -29,6 +22,90 @@ namespace debug {
static const char *const TAG = "debug";
// index by values returned by esp_reset_reason
static const char *const RESET_REASONS[] = {
"unknown source",
"power-on event",
"external pin",
"software via esp_restart",
"exception/panic",
"interrupt watchdog",
"task watchdog",
"other watchdogs",
"exiting deep sleep mode",
"brownout",
"SDIO",
"USB peripheral",
"JTAG",
"efuse error",
"power glitch detected",
"CPU lock up",
};
static const char *const REBOOT_KEY = "reboot_source";
static const size_t REBOOT_MAX_LEN = 24;
// on shutdown, store the source of the reboot request
void DebugComponent::on_shutdown() {
auto *component = App.get_current_component();
char buffer[REBOOT_MAX_LEN]{};
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
if (component != nullptr) {
strncpy(buffer, component->get_component_source(), REBOOT_MAX_LEN - 1);
}
ESP_LOGD(TAG, "Storing reboot source: %s", buffer);
pref.save(&buffer);
global_preferences->sync();
}
std::string DebugComponent::get_reset_reason_() {
std::string reset_reason;
unsigned reason = esp_reset_reason();
if (reason < sizeof(RESET_REASONS) / sizeof(RESET_REASONS[0])) {
reset_reason = RESET_REASONS[reason];
if (reason == ESP_RST_SW) {
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
char buffer[REBOOT_MAX_LEN]{};
if (pref.load(&buffer)) {
reset_reason = "Reboot request from " + std::string(buffer);
}
}
} else {
reset_reason = "unknown source";
}
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
return reset_reason;
}
static const char *const WAKEUP_CAUSES[] = {
"undefined",
"undefined",
"external signal using RTC_IO",
"external signal using RTC_CNTL",
"timer",
"touchpad",
"ULP program",
"GPIO",
"UART",
"WIFI",
"COCPU int",
"COCPU crash",
"BT",
};
std::string DebugComponent::get_wakeup_cause_() {
const char *wake_reason;
unsigned reason = esp_sleep_get_wakeup_cause();
if (reason < sizeof(WAKEUP_CAUSES) / sizeof(WAKEUP_CAUSES[0])) {
wake_reason = WAKEUP_CAUSES[reason];
} else {
wake_reason = "unknown source";
}
ESP_LOGD(TAG, "Wakeup Reason: %s", wake_reason);
return wake_reason;
}
void DebugComponent::log_partition_info_() {
ESP_LOGCONFIG(TAG, "Partition table:");
ESP_LOGCONFIG(TAG, " %-12s %-4s %-8s %-10s %-10s", "Name", "Type", "Subtype", "Address", "Size");
@@ -42,171 +119,16 @@ void DebugComponent::log_partition_info_() {
esp_partition_iterator_release(it);
}
std::string DebugComponent::get_reset_reason_() {
std::string reset_reason;
switch (esp_reset_reason()) {
case ESP_RST_POWERON:
reset_reason = "Reset due to power-on event";
break;
case ESP_RST_EXT:
reset_reason = "Reset by external pin";
break;
case ESP_RST_SW:
reset_reason = "Software reset via esp_restart";
break;
case ESP_RST_PANIC:
reset_reason = "Software reset due to exception/panic";
break;
case ESP_RST_INT_WDT:
reset_reason = "Reset (software or hardware) due to interrupt watchdog";
break;
case ESP_RST_TASK_WDT:
reset_reason = "Reset due to task watchdog";
break;
case ESP_RST_WDT:
reset_reason = "Reset due to other watchdogs";
break;
case ESP_RST_DEEPSLEEP:
reset_reason = "Reset after exiting deep sleep mode";
break;
case ESP_RST_BROWNOUT:
reset_reason = "Brownout reset (software or hardware)";
break;
case ESP_RST_SDIO:
reset_reason = "Reset over SDIO";
break;
#ifdef USE_ESP32_VARIANT_ESP32
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4))
case ESP_RST_USB:
reset_reason = "Reset by USB peripheral";
break;
case ESP_RST_JTAG:
reset_reason = "Reset by JTAG";
break;
case ESP_RST_EFUSE:
reset_reason = "Reset due to efuse error";
break;
case ESP_RST_PWR_GLITCH:
reset_reason = "Reset due to power glitch detected";
break;
case ESP_RST_CPU_LOCKUP:
reset_reason = "Reset due to CPU lock up (double exception)";
break;
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4)
#endif // USE_ESP32_VARIANT_ESP32
default: // Includes ESP_RST_UNKNOWN
switch (rtc_get_reset_reason(0)) {
case POWERON_RESET:
reset_reason = "Power On Reset";
break;
#if defined(USE_ESP32_VARIANT_ESP32)
case SW_RESET:
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case RTC_SW_SYS_RESET:
#endif
reset_reason = "Software Reset Digital Core";
break;
#if defined(USE_ESP32_VARIANT_ESP32)
case OWDT_RESET:
reset_reason = "Watch Dog Reset Digital Core";
break;
#endif
case DEEPSLEEP_RESET:
reset_reason = "Deep Sleep Reset Digital Core";
break;
#if defined(USE_ESP32_VARIANT_ESP32)
case SDIO_RESET:
reset_reason = "SLC Module Reset Digital Core";
break;
#endif
case TG0WDT_SYS_RESET:
reset_reason = "Timer Group 0 Watch Dog Reset Digital Core";
break;
case TG1WDT_SYS_RESET:
reset_reason = "Timer Group 1 Watch Dog Reset Digital Core";
break;
case RTCWDT_SYS_RESET:
reset_reason = "RTC Watch Dog Reset Digital Core";
break;
#if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
case INTRUSION_RESET:
reset_reason = "Intrusion Reset CPU";
break;
#endif
#if defined(USE_ESP32_VARIANT_ESP32)
case TGWDT_CPU_RESET:
reset_reason = "Timer Group Reset CPU";
break;
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case TG0WDT_CPU_RESET:
reset_reason = "Timer Group 0 Reset CPU";
break;
#endif
#if defined(USE_ESP32_VARIANT_ESP32)
case SW_CPU_RESET:
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case RTC_SW_CPU_RESET:
#endif
reset_reason = "Software Reset CPU";
break;
case RTCWDT_CPU_RESET:
reset_reason = "RTC Watch Dog Reset CPU";
break;
#if defined(USE_ESP32_VARIANT_ESP32)
case EXT_CPU_RESET:
reset_reason = "External CPU Reset";
break;
#endif
case RTCWDT_BROWN_OUT_RESET:
reset_reason = "Voltage Unstable Reset";
break;
case RTCWDT_RTC_RESET:
reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
break;
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
defined(USE_ESP32_VARIANT_ESP32C6)
case TG1WDT_CPU_RESET:
reset_reason = "Timer Group 1 Reset CPU";
break;
case SUPER_WDT_RESET:
reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
break;
case EFUSE_RESET:
reset_reason = "eFuse Reset Digital Core";
break;
#endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
case GLITCH_RTC_RESET:
reset_reason = "Glitch Reset Digital Core And RTC Module";
break;
#endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case USB_UART_CHIP_RESET:
reset_reason = "USB UART Reset Digital Core";
break;
case USB_JTAG_CHIP_RESET:
reset_reason = "USB JTAG Reset Digital Core";
break;
#endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
case POWER_GLITCH_RESET:
reset_reason = "Power Glitch Reset Digital Core And RTC Module";
break;
#endif
default:
reset_reason = "Unknown Reset Reason";
}
break;
}
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
return reset_reason;
}
uint32_t DebugComponent::get_free_heap_() { return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); }
static const std::map<int, const char *> CHIP_FEATURES = {
{CHIP_FEATURE_BLE, "BLE"},
{CHIP_FEATURE_BT, "BT"},
{CHIP_FEATURE_EMB_FLASH, "EMB Flash"},
{CHIP_FEATURE_EMB_PSRAM, "EMB PSRAM"},
{CHIP_FEATURE_WIFI_BGN, "2.4GHz WiFi"},
};
void DebugComponent::get_device_info_(std::string &device_info) {
#if defined(USE_ARDUINO)
const char *flash_mode;
@@ -242,44 +164,16 @@ void DebugComponent::get_device_info_(std::string &device_info) {
esp_chip_info_t info;
esp_chip_info(&info);
const char *model;
#if defined(USE_ESP32_VARIANT_ESP32)
model = "ESP32";
#elif defined(USE_ESP32_VARIANT_ESP32C3)
model = "ESP32-C3";
#elif defined(USE_ESP32_VARIANT_ESP32C6)
model = "ESP32-C6";
#elif defined(USE_ESP32_VARIANT_ESP32S2)
model = "ESP32-S2";
#elif defined(USE_ESP32_VARIANT_ESP32S3)
model = "ESP32-S3";
#elif defined(USE_ESP32_VARIANT_ESP32H2)
model = "ESP32-H2";
#else
model = "UNKNOWN";
#endif
const char *model = ESPHOME_VARIANT;
std::string features;
if (info.features & CHIP_FEATURE_EMB_FLASH) {
features += "EMB_FLASH,";
info.features &= ~CHIP_FEATURE_EMB_FLASH;
for (auto feature : CHIP_FEATURES) {
if (info.features & feature.first) {
features += feature.second;
features += ", ";
info.features &= ~feature.first;
}
}
if (info.features & CHIP_FEATURE_WIFI_BGN) {
features += "WIFI_BGN,";
info.features &= ~CHIP_FEATURE_WIFI_BGN;
}
if (info.features & CHIP_FEATURE_BLE) {
features += "BLE,";
info.features &= ~CHIP_FEATURE_BLE;
}
if (info.features & CHIP_FEATURE_BT) {
features += "BT,";
info.features &= ~CHIP_FEATURE_BT;
}
if (info.features & CHIP_FEATURE_EMB_PSRAM) {
features += "EMB_PSRAM,";
info.features &= ~CHIP_FEATURE_EMB_PSRAM;
}
if (info.features)
if (info.features != 0)
features += "Other:" + format_hex(info.features);
ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores,
info.revision);
@@ -289,6 +183,8 @@ void DebugComponent::get_device_info_(std::string &device_info) {
device_info += features;
device_info += " Cores:" + to_string(info.cores);
device_info += " Revision:" + to_string(info.revision);
device_info += str_sprintf("|CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000);
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000);
// Framework detection
device_info += "|Framework: ";
@@ -315,48 +211,7 @@ void DebugComponent::get_device_info_(std::string &device_info) {
device_info += "|Reset: ";
device_info += get_reset_reason_();
const char *wakeup_reason;
switch (rtc_get_wakeup_cause()) {
case NO_SLEEP:
wakeup_reason = "No Sleep";
break;
case EXT_EVENT0_TRIG:
wakeup_reason = "External Event 0";
break;
case EXT_EVENT1_TRIG:
wakeup_reason = "External Event 1";
break;
case GPIO_TRIG:
wakeup_reason = "GPIO";
break;
case TIMER_EXPIRE:
wakeup_reason = "Wakeup Timer";
break;
case SDIO_TRIG:
wakeup_reason = "SDIO";
break;
case MAC_TRIG:
wakeup_reason = "MAC";
break;
case UART0_TRIG:
wakeup_reason = "UART0";
break;
case UART1_TRIG:
wakeup_reason = "UART1";
break;
case TOUCH_TRIG:
wakeup_reason = "Touch";
break;
case SAR_TRIG:
wakeup_reason = "SAR";
break;
case BT_TRIG:
wakeup_reason = "BT";
break;
default:
wakeup_reason = "Unknown";
}
ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason);
std::string wakeup_reason = this->get_wakeup_cause_();
device_info += "|Wakeup: ";
device_info += wakeup_reason;
}

View File

@@ -1,5 +1,6 @@
import esphome.codegen as cg
from esphome.components import sensor
from esphome.components.esp32 import CONF_CPU_FREQUENCY
import esphome.config_validation as cv
from esphome.const import (
CONF_BLOCK,
@@ -10,6 +11,7 @@ from esphome.const import (
ICON_COUNTER,
ICON_TIMER,
UNIT_BYTES,
UNIT_HERTZ,
UNIT_MILLISECOND,
UNIT_PERCENT,
)
@@ -60,6 +62,14 @@ CONFIG_SCHEMA = {
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
),
cv.Optional(CONF_CPU_FREQUENCY): cv.All(
sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
icon="mdi:speedometer",
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
),
}
@@ -85,3 +95,7 @@ async def to_code(config):
if psram_conf := config.get(CONF_PSRAM):
sens = await sensor.new_sensor(psram_conf)
cg.add(debug_component.set_psram_sensor(sens))
if cpu_freq_conf := config.get(CONF_CPU_FREQUENCY):
sens = await sensor.new_sensor(cpu_freq_conf)
cg.add(debug_component.set_cpu_frequency_sensor(sens))

View File

@@ -31,9 +31,12 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
#if !defined(USE_ESP32_VARIANT_ESP32H2)
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
#endif
#endif
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration;
}
@@ -65,7 +68,7 @@ bool DeepSleepComponent::prepare_to_sleep_() {
}
void DeepSleepComponent::deep_sleep_() {
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->wakeup_pin_ != nullptr) {
@@ -84,6 +87,15 @@ void DeepSleepComponent::deep_sleep_() {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
}
#endif
#if defined(USE_ESP32_VARIANT_ESP32H2)
if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->ext1_wakeup_.has_value()) {
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
}
#endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);

View File

@@ -69,21 +69,16 @@ bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) const { // NOL
return true;
}
if (absolute) {
return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2()));
} else {
return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h));
return test_x >= this->x && test_x < this->x2() && test_y >= this->y && test_y < this->y2();
}
return test_x >= 0 && test_x < this->w && test_y >= 0 && test_y < this->h;
}
bool Rect::inside(Rect rect, bool absolute) const {
bool Rect::inside(Rect rect) const {
if (!this->is_set() || !rect.is_set()) {
return true;
}
if (absolute) {
return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
} else {
return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
}
return this->x2() >= rect.x && this->x <= rect.x2() && this->y2() >= rect.y && this->y <= rect.y2();
}
void Rect::info(const std::string &prefix) {

View File

@@ -26,7 +26,7 @@ class Rect {
void extend(Rect rect);
void shrink(Rect rect);
bool inside(Rect rect, bool absolute = true) const;
bool inside(Rect rect) const;
bool inside(int16_t test_x, int16_t test_y, bool absolute = true) const;
bool equal(Rect rect) const;
void info(const std::string &prefix = "rect info:");

View File

@@ -187,7 +187,7 @@ void ENS160Component::update() {
}
return;
case INVALID_OUTPUT:
ESP_LOGE(TAG, "ENS160 Invalid Status - No Invalid Output");
ESP_LOGE(TAG, "ENS160 Invalid Status - No valid output");
this->status_set_warning();
return;
}

View File

@@ -1,4 +1,5 @@
from dataclasses import dataclass
import itertools
import logging
import os
from pathlib import Path
@@ -37,6 +38,7 @@ from esphome.const import (
__version__,
)
from esphome.core import CORE, HexInt, TimePeriod
from esphome.cpp_generator import RawExpression
import esphome.final_validate as fv
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
@@ -54,6 +56,12 @@ from .const import ( # noqa
KEY_SUBMODULES,
KEY_VARIANT,
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_FRIENDLY,
VARIANTS,
)
@@ -70,7 +78,43 @@ CONF_RELEASE = "release"
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
def get_cpu_frequencies(*frequencies):
return [str(x) + "MHZ" for x in frequencies]
CPU_FREQUENCIES = {
VARIANT_ESP32: get_cpu_frequencies(80, 160, 240),
VARIANT_ESP32S2: get_cpu_frequencies(80, 160, 240),
VARIANT_ESP32S3: get_cpu_frequencies(80, 160, 240),
VARIANT_ESP32C2: get_cpu_frequencies(80, 120),
VARIANT_ESP32C3: get_cpu_frequencies(80, 160),
VARIANT_ESP32C6: get_cpu_frequencies(80, 120, 160),
VARIANT_ESP32H2: get_cpu_frequencies(16, 32, 48, 64, 96),
}
# Make sure not missed here if a new variant added.
assert all(v in CPU_FREQUENCIES for v in VARIANTS)
FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values()))
def set_core_data(config):
cpu_frequency = config.get(CONF_CPU_FREQUENCY, None)
variant = config[CONF_VARIANT]
# if not specified in config, set to 160MHz if supported, the fastest otherwise
if cpu_frequency is None:
choices = CPU_FREQUENCIES[variant]
if "160MHZ" in choices:
cpu_frequency = "160MHZ"
else:
cpu_frequency = choices[-1]
config[CONF_CPU_FREQUENCY] = cpu_frequency
elif cpu_frequency not in CPU_FREQUENCIES[variant]:
raise cv.Invalid(
f"Invalid CPU frequency '{cpu_frequency}' for {config[CONF_VARIANT]}",
path=[CONF_CPU_FREQUENCY],
)
CORE.data[KEY_ESP32] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_ESP32
conf = config[CONF_FRAMEWORK]
@@ -83,6 +127,7 @@ def set_core_data(config):
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
config[CONF_FRAMEWORK][CONF_VERSION]
)
CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT]
CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {}
@@ -251,7 +296,7 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 5)
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 6)
# The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
@@ -274,12 +319,15 @@ SUPPORTED_PLATFORMIO_ESP_IDF_5X = [
# pioarduino versions that don't require a release number
# List based on https://github.com/pioarduino/esp-idf/releases
SUPPORTED_PIOARDUINO_ESP_IDF_5X = [
cv.Version(5, 5, 0),
cv.Version(5, 4, 1),
cv.Version(5, 4, 0),
cv.Version(5, 3, 3),
cv.Version(5, 3, 2),
cv.Version(5, 3, 1),
cv.Version(5, 3, 0),
cv.Version(5, 1, 5),
cv.Version(5, 1, 6),
]
@@ -321,8 +369,8 @@ def _arduino_check_versions(value):
def _esp_idf_check_versions(value):
value = value.copy()
lookups = {
"dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 1, 5), None),
"dev": (cv.Version(5, 1, 6), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 1, 6), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
}
@@ -550,11 +598,15 @@ FLASH_SIZES = [
]
CONF_FLASH_SIZE = "flash_size"
CONF_CPU_FREQUENCY = "cpu_frequency"
CONF_PARTITIONS = "partitions"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_BOARD): cv.string_strict,
cv.Optional(CONF_CPU_FREQUENCY): cv.one_of(
*FULL_CPU_FREQUENCIES, upper=True
),
cv.Optional(CONF_FLASH_SIZE, default="4MB"): cv.one_of(
*FLASH_SIZES, upper=True
),
@@ -595,6 +647,7 @@ async def to_code(config):
os.path.join(os.path.dirname(__file__), "post_build.py.script"),
)
freq = config[CONF_CPU_FREQUENCY][:-3]
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
cg.add_platformio_option("framework", "espidf")
cg.add_build_flag("-DUSE_ESP_IDF")
@@ -628,6 +681,9 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False)
# Set default CPU frequency
add_idf_sdkconfig_option(f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_{freq}", True)
cg.add_platformio_option("board_build.partitions", "partitions.csv")
if CONF_PARTITIONS in config:
add_extra_build_file(
@@ -693,6 +749,7 @@ async def to_code(config):
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
),
)
cg.add(RawExpression(f"setCpuFrequencyMhz({freq})"))
APP_PARTITION_SIZES = {

View File

@@ -13,11 +13,13 @@
#include <hal/cpu_hal.h>
#ifdef USE_ARDUINO
#include <esp32-hal.h>
#endif
#include <Esp.h>
#else
#include <esp_clk_tree.h>
void setup();
void loop();
#endif
namespace esphome {
@@ -59,9 +61,13 @@ uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); }
#endif
uint32_t arch_get_cpu_freq_hz() {
rtc_cpu_freq_config_t config;
rtc_clk_cpu_freq_get_config(&config);
return config.freq_mhz * 1000000U;
uint32_t freq = 0;
#ifdef USE_ESP_IDF
esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq);
#elif defined(USE_ARDUINO)
freq = ESP.getCpuFreqMHz() * 1000000;
#endif
return freq;
}
#ifdef USE_ESP_IDF

View File

@@ -2,10 +2,6 @@
#include "ble.h"
#ifdef USE_ESP32_VARIANT_ESP32C6
#include "const_esp32c6.h"
#endif // USE_ESP32_VARIANT_ESP32C6
#include "esphome/core/application.h"
#include "esphome/core/log.h"
@@ -114,6 +110,7 @@ void ESP32BLE::advertising_init_() {
this->advertising_->set_scan_response(true);
this->advertising_->set_min_preferred_interval(0x06);
this->advertising_->set_appearance(this->appearance_);
}
bool ESP32BLE::ble_setup_() {
@@ -127,11 +124,7 @@ bool ESP32BLE::ble_setup_() {
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
// start bt controller
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
#ifdef USE_ESP32_VARIANT_ESP32C6
esp_bt_controller_config_t cfg = BT_CONTROLLER_CONFIG;
#else
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
#endif
err = esp_bt_controller_init(&cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));

View File

@@ -95,6 +95,7 @@ class ESP32BLE : public Component {
void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; }
void advertising_add_service_uuid(ESPBTUUID uuid);
void advertising_remove_service_uuid(ESPBTUUID uuid);
void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback);
@@ -128,11 +129,12 @@ class ESP32BLE : public Component {
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
Queue<BLEEvent> ble_events_;
BLEAdvertising *advertising_;
BLEAdvertising *advertising_{};
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
uint32_t advertising_cycle_time_;
bool enable_on_boot_;
uint32_t advertising_cycle_time_{};
bool enable_on_boot_{};
optional<std::string> name_;
uint16_t appearance_{0};
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -32,6 +32,7 @@ class BLEAdvertising {
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(const std::vector<uint8_t> &data);
void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; }
void set_service_data(const std::vector<uint8_t> &data);
void register_raw_advertisement_callback(std::function<void(bool)> &&callback);

View File

@@ -1,74 +0,0 @@
#pragma once
#ifdef USE_ESP32_VARIANT_ESP32C6
#include <esp_bt.h>
namespace esphome {
namespace esp32_ble {
static const esp_bt_controller_config_t BT_CONTROLLER_CONFIG = {
.config_version = CONFIG_VERSION,
.ble_ll_resolv_list_size = CONFIG_BT_LE_LL_RESOLV_LIST_SIZE,
.ble_hci_evt_hi_buf_count = DEFAULT_BT_LE_HCI_EVT_HI_BUF_COUNT,
.ble_hci_evt_lo_buf_count = DEFAULT_BT_LE_HCI_EVT_LO_BUF_COUNT,
.ble_ll_sync_list_cnt = DEFAULT_BT_LE_MAX_PERIODIC_ADVERTISER_LIST,
.ble_ll_sync_cnt = DEFAULT_BT_LE_MAX_PERIODIC_SYNCS,
.ble_ll_rsp_dup_list_count = CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT,
.ble_ll_adv_dup_list_count = CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT,
.ble_ll_tx_pwr_dbm = BLE_LL_TX_PWR_DBM_N,
.rtc_freq = RTC_FREQ_N,
.ble_ll_sca = CONFIG_BT_LE_LL_SCA,
.ble_ll_scan_phy_number = BLE_LL_SCAN_PHY_NUMBER_N,
.ble_ll_conn_def_auth_pyld_tmo = BLE_LL_CONN_DEF_AUTH_PYLD_TMO_N,
.ble_ll_jitter_usecs = BLE_LL_JITTER_USECS_N,
.ble_ll_sched_max_adv_pdu_usecs = BLE_LL_SCHED_MAX_ADV_PDU_USECS_N,
.ble_ll_sched_direct_adv_max_usecs = BLE_LL_SCHED_DIRECT_ADV_MAX_USECS_N,
.ble_ll_sched_adv_max_usecs = BLE_LL_SCHED_ADV_MAX_USECS_N,
.ble_scan_rsp_data_max_len = DEFAULT_BT_LE_SCAN_RSP_DATA_MAX_LEN_N,
.ble_ll_cfg_num_hci_cmd_pkts = BLE_LL_CFG_NUM_HCI_CMD_PKTS_N,
.ble_ll_ctrl_proc_timeout_ms = BLE_LL_CTRL_PROC_TIMEOUT_MS_N,
.nimble_max_connections = DEFAULT_BT_LE_MAX_CONNECTIONS,
.ble_whitelist_size = DEFAULT_BT_NIMBLE_WHITELIST_SIZE, // NOLINT
.ble_acl_buf_size = DEFAULT_BT_LE_ACL_BUF_SIZE,
.ble_acl_buf_count = DEFAULT_BT_LE_ACL_BUF_COUNT,
.ble_hci_evt_buf_size = DEFAULT_BT_LE_HCI_EVT_BUF_SIZE,
.ble_multi_adv_instances = DEFAULT_BT_LE_MAX_EXT_ADV_INSTANCES,
.ble_ext_adv_max_size = DEFAULT_BT_LE_EXT_ADV_MAX_SIZE,
.controller_task_stack_size = NIMBLE_LL_STACK_SIZE,
.controller_task_prio = ESP_TASK_BT_CONTROLLER_PRIO,
.controller_run_cpu = 0,
.enable_qa_test = RUN_QA_TEST,
.enable_bqb_test = RUN_BQB_TEST,
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 1)
// The following fields have been removed since ESP IDF version 5.3.1, see commit:
// https://github.com/espressif/esp-idf/commit/e761c1de8f9c0777829d597b4d5a33bb070a30a8
.enable_uart_hci = HCI_UART_EN,
.ble_hci_uart_port = DEFAULT_BT_LE_HCI_UART_PORT,
.ble_hci_uart_baud = DEFAULT_BT_LE_HCI_UART_BAUD,
.ble_hci_uart_data_bits = DEFAULT_BT_LE_HCI_UART_DATA_BITS,
.ble_hci_uart_stop_bits = DEFAULT_BT_LE_HCI_UART_STOP_BITS,
.ble_hci_uart_flow_ctrl = DEFAULT_BT_LE_HCI_UART_FLOW_CTRL,
.ble_hci_uart_uart_parity = DEFAULT_BT_LE_HCI_UART_PARITY,
#endif
.enable_tx_cca = DEFAULT_BT_LE_TX_CCA_ENABLED,
.cca_rssi_thresh = 256 - DEFAULT_BT_LE_CCA_RSSI_THRESH,
.sleep_en = NIMBLE_SLEEP_ENABLE,
.coex_phy_coded_tx_rx_time_limit = DEFAULT_BT_LE_COEX_PHY_CODED_TX_RX_TLIM_EFF,
.dis_scan_backoff = NIMBLE_DISABLE_SCAN_BACKOFF,
.ble_scan_classify_filter_enable = 1,
.main_xtal_freq = CONFIG_XTAL_FREQ,
.version_num = (uint8_t) efuse_hal_chip_revision(),
.cpu_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ,
.ignore_wl_for_direct_adv = 0,
.enable_pcl = DEFAULT_BT_LE_POWER_CONTROL_ENABLED,
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 3)
.csa2_select = DEFAULT_BT_LE_50_FEATURE_SUPPORT,
#endif
.config_magic = CONFIG_MAGIC,
};
} // namespace esp32_ble
} // namespace esphome
#endif // USE_ESP32_VARIANT_ESP32C6

View File

@@ -32,6 +32,7 @@ DEPENDENCIES = ["esp32"]
DOMAIN = "esp32_ble_server"
CONF_ADVERTISE = "advertise"
CONF_APPEARANCE = "appearance"
CONF_BROADCAST = "broadcast"
CONF_CHARACTERISTICS = "characteristics"
CONF_DESCRIPTION = "description"
@@ -421,6 +422,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(): cv.declare_id(BLEServer),
cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
cv.Optional(CONF_MANUFACTURER): value_schema("string", templatable=False),
cv.Optional(CONF_APPEARANCE, default=0): cv.uint16_t,
cv.Optional(CONF_MODEL): value_schema("string", templatable=False),
cv.Optional(CONF_FIRMWARE_VERSION): value_schema("string", templatable=False),
cv.Optional(CONF_MANUFACTURER_DATA): cv.Schema([cv.uint8_t]),
@@ -531,6 +533,7 @@ async def to_code(config):
cg.add(parent.register_gatts_event_handler(var))
cg.add(parent.register_ble_status_event_handler(var))
cg.add(var.set_parent(parent))
cg.add(parent.advertising_set_appearance(config[CONF_APPEARANCE]))
if CONF_MANUFACTURER_DATA in config:
cg.add(var.set_manufacturer_data(config[CONF_MANUFACTURER_DATA]))
for service_config in config[CONF_SERVICES]:

View File

@@ -17,6 +17,7 @@ from esphome.components.esp32_ble import (
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTIVE,
CONF_CONTINUOUS,
CONF_DURATION,
CONF_ID,
CONF_INTERVAL,
@@ -42,7 +43,6 @@ CONF_MAX_CONNECTIONS = "max_connections"
CONF_ESP32_BLE_ID = "esp32_ble_id"
CONF_SCAN_PARAMETERS = "scan_parameters"
CONF_WINDOW = "window"
CONF_CONTINUOUS = "continuous"
CONF_ON_SCAN_END = "on_scan_end"
DEFAULT_MAX_CONNECTIONS = 3

View File

@@ -57,7 +57,6 @@ void ESP32BLETracker::setup() {
global_esp32_ble_tracker = this;
this->scan_result_lock_ = xSemaphoreCreateMutex();
this->scan_end_lock_ = xSemaphoreCreateMutex();
#ifdef USE_OTA
ota::get_global_ota_callback()->add_on_state_callback(
@@ -117,119 +116,104 @@ void ESP32BLETracker::loop() {
}
bool promote_to_connecting = discovered && !searching && !connecting;
if (!this->scanner_idle_) {
if (this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
uint32_t index = this->scan_result_index_;
if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
}
if (this->scanner_state_ == ScannerState::RUNNING &&
this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
uint32_t index = this->scan_result_index_;
if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
}
if (this->raw_advertisements_) {
if (this->raw_advertisements_) {
for (auto *listener : this->listeners_) {
listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
}
for (auto *client : this->clients_) {
client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
}
}
if (this->parse_advertisements_) {
for (size_t i = 0; i < index; i++) {
ESPBTDevice device;
device.parse_scan_rst(this->scan_result_buffer_[i]);
bool found = false;
for (auto *listener : this->listeners_) {
listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
if (listener->parse_device(device))
found = true;
}
for (auto *client : this->clients_) {
client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
}
}
if (this->parse_advertisements_) {
for (size_t i = 0; i < index; i++) {
ESPBTDevice device;
device.parse_scan_rst(this->scan_result_buffer_[i]);
bool found = false;
for (auto *listener : this->listeners_) {
if (listener->parse_device(device))
found = true;
}
for (auto *client : this->clients_) {
if (client->parse_device(device)) {
found = true;
if (!connecting && client->state() == ClientState::DISCOVERED) {
promote_to_connecting = true;
}
if (client->parse_device(device)) {
found = true;
if (!connecting && client->state() == ClientState::DISCOVERED) {
promote_to_connecting = true;
}
}
}
if (!found && !this->scan_continuous_) {
this->print_bt_device_info(device);
}
if (!found && !this->scan_continuous_) {
this->print_bt_device_info(device);
}
}
this->scan_result_index_ = 0;
xSemaphoreGive(this->scan_result_lock_);
}
/*
Avoid starting the scanner if:
- we are already scanning
- we are connecting to a device
- we are disconnecting from a device
Otherwise the scanner could fail to ever start again
and our only way to recover is to reboot.
https://github.com/espressif/esp-idf/issues/6688
*/
if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) {
if (this->scan_continuous_) {
if (!disconnecting && !promote_to_connecting && !this->scan_start_failed_ && !this->scan_set_param_failed_) {
this->start_scan_(false);
} else {
// We didn't start the scan, so we need to release the lock
xSemaphoreGive(this->scan_end_lock_);
}
} else if (!this->scanner_idle_) {
this->end_of_scan_();
return;
}
this->scan_result_index_ = 0;
xSemaphoreGive(this->scan_result_lock_);
}
if (this->scanner_state_ == ScannerState::STOPPED) {
this->end_of_scan_(); // Change state to IDLE
}
if (this->scanner_state_ == ScannerState::FAILED ||
(this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) {
this->stop_scan_();
if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after %d attempts, rebooting to restore BLE stack...",
std::numeric_limits<uint8_t>::max());
App.reboot();
}
if (this->scan_start_failed_ || this->scan_set_param_failed_) {
if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after %d attempts, rebooting to restore BLE stack...",
std::numeric_limits<uint8_t>::max());
App.reboot();
}
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
xSemaphoreGive(this->scan_end_lock_);
} else {
ESP_LOGD(TAG, "Stopping scan after failure...");
this->stop_scan_();
}
if (this->scan_start_failed_) {
ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
}
if (this->scan_set_param_failed_) {
ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
}
if (this->scan_start_failed_) {
ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
}
if (this->scan_set_param_failed_) {
ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
}
}
/*
Avoid starting the scanner if:
- we are already scanning
- we are connecting to a device
- we are disconnecting from a device
Otherwise the scanner could fail to ever start again
and our only way to recover is to reboot.
https://github.com/espressif/esp-idf/issues/6688
*/
if (this->scanner_state_ == ScannerState::IDLE && this->scan_continuous_ && !connecting && !disconnecting &&
!promote_to_connecting) {
this->start_scan_(false); // first = false
}
// If there is a discovered client and no connecting
// clients and no clients using the scanner to search for
// devices, then stop scanning and promote the discovered
// client to ready to connect.
if (promote_to_connecting) {
if (promote_to_connecting &&
(this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
for (auto *client : this->clients_) {
if (client->state() == ClientState::DISCOVERED) {
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
// Scanner is not running since we got the
// lock, so we can promote the client.
xSemaphoreGive(this->scan_end_lock_);
if (this->scanner_state_ == ScannerState::RUNNING) {
ESP_LOGD(TAG, "Stopping scan to make connection...");
this->stop_scan_();
} else if (this->scanner_state_ == ScannerState::IDLE) {
ESP_LOGD(TAG, "Promoting client to connect...");
// We only want to promote one client at a time.
// once the scanner is fully stopped.
client->set_state(ClientState::READY_TO_CONNECT);
} else {
ESP_LOGD(TAG, "Pausing scan to make connection...");
this->stop_scan_();
}
break;
}
@@ -237,13 +221,7 @@ void ESP32BLETracker::loop() {
}
}
void ESP32BLETracker::start_scan() {
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
this->start_scan_(true);
} else {
ESP_LOGW(TAG, "Scan requested when a scan is already in progress. Ignoring.");
}
}
void ESP32BLETracker::start_scan() { this->start_scan_(true); }
void ESP32BLETracker::stop_scan() {
ESP_LOGD(TAG, "Stopping scan.");
@@ -251,16 +229,23 @@ void ESP32BLETracker::stop_scan() {
this->stop_scan_();
}
void ESP32BLETracker::ble_before_disabled_event_handler() {
this->stop_scan_();
xSemaphoreGive(this->scan_end_lock_);
}
void ESP32BLETracker::ble_before_disabled_event_handler() { this->stop_scan_(); }
void ESP32BLETracker::stop_scan_() {
this->cancel_timeout("scan");
if (this->scanner_idle_) {
if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) {
if (this->scanner_state_ == ScannerState::IDLE) {
ESP_LOGE(TAG, "Scan is already stopped while trying to stop.");
} else if (this->scanner_state_ == ScannerState::STARTING) {
ESP_LOGE(TAG, "Scan is starting while trying to stop.");
} else if (this->scanner_state_ == ScannerState::STOPPING) {
ESP_LOGE(TAG, "Scan is already stopping while trying to stop.");
} else if (this->scanner_state_ == ScannerState::STOPPED) {
ESP_LOGE(TAG, "Scan is already stopped while trying to stop.");
}
return;
}
this->cancel_timeout("scan");
this->set_scanner_state_(ScannerState::STOPPING);
esp_err_t err = esp_ble_gap_stop_scanning();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err);
@@ -273,13 +258,22 @@ void ESP32BLETracker::start_scan_(bool first) {
ESP_LOGW(TAG, "Cannot start scan while ESP32BLE is disabled.");
return;
}
// The lock must be held when calling this function.
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
ESP_LOGE(TAG, "start_scan called without holding scan_end_lock_");
if (this->scanner_state_ != ScannerState::IDLE) {
if (this->scanner_state_ == ScannerState::STARTING) {
ESP_LOGE(TAG, "Cannot start scan while already starting.");
} else if (this->scanner_state_ == ScannerState::RUNNING) {
ESP_LOGE(TAG, "Cannot start scan while already running.");
} else if (this->scanner_state_ == ScannerState::STOPPING) {
ESP_LOGE(TAG, "Cannot start scan while already stopping.");
} else if (this->scanner_state_ == ScannerState::FAILED) {
ESP_LOGE(TAG, "Cannot start scan while already failed.");
} else if (this->scanner_state_ == ScannerState::STOPPED) {
ESP_LOGE(TAG, "Cannot start scan while already stopped.");
}
return;
}
ESP_LOGD(TAG, "Starting scan...");
this->set_scanner_state_(ScannerState::STARTING);
ESP_LOGD(TAG, "Starting scan, set scanner state to STARTING.");
if (!first) {
for (auto *listener : this->listeners_)
listener->on_scan_end();
@@ -307,24 +301,21 @@ void ESP32BLETracker::start_scan_(bool first) {
ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", err);
return;
}
this->scanner_idle_ = false;
}
void ESP32BLETracker::end_of_scan_() {
// The lock must be held when calling this function.
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
ESP_LOGE(TAG, "end_of_scan_ called without holding the scan_end_lock_");
if (this->scanner_state_ != ScannerState::STOPPED) {
ESP_LOGE(TAG, "end_of_scan_ called while scanner is not stopped.");
return;
}
ESP_LOGD(TAG, "End of scan.");
this->scanner_idle_ = true;
ESP_LOGD(TAG, "End of scan, set scanner state to IDLE.");
this->already_discovered_.clear();
xSemaphoreGive(this->scan_end_lock_);
this->cancel_timeout("scan");
for (auto *listener : this->listeners_)
listener->on_scan_end();
this->set_scanner_state_(ScannerState::IDLE);
}
void ESP32BLETracker::register_client(ESPBTClient *client) {
@@ -392,19 +383,46 @@ void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t:
void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
this->scan_start_failed_ = param.status;
if (this->scanner_state_ != ScannerState::STARTING) {
if (this->scanner_state_ == ScannerState::RUNNING) {
ESP_LOGE(TAG, "Scan was already running when start complete.");
} else if (this->scanner_state_ == ScannerState::STOPPING) {
ESP_LOGE(TAG, "Scan was stopping when start complete.");
} else if (this->scanner_state_ == ScannerState::FAILED) {
ESP_LOGE(TAG, "Scan was in failed state when start complete.");
} else if (this->scanner_state_ == ScannerState::IDLE) {
ESP_LOGE(TAG, "Scan was idle when start complete.");
} else if (this->scanner_state_ == ScannerState::STOPPED) {
ESP_LOGE(TAG, "Scan was stopped when start complete.");
}
}
if (param.status == ESP_BT_STATUS_SUCCESS) {
this->scan_start_fail_count_ = 0;
this->set_scanner_state_(ScannerState::RUNNING);
} else {
this->set_scanner_state_(ScannerState::FAILED);
if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) {
this->scan_start_fail_count_++;
}
xSemaphoreGive(this->scan_end_lock_);
}
}
void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
xSemaphoreGive(this->scan_end_lock_);
if (this->scanner_state_ != ScannerState::STOPPING) {
if (this->scanner_state_ == ScannerState::RUNNING) {
ESP_LOGE(TAG, "Scan was not running when stop complete.");
} else if (this->scanner_state_ == ScannerState::STARTING) {
ESP_LOGE(TAG, "Scan was not started when stop complete.");
} else if (this->scanner_state_ == ScannerState::FAILED) {
ESP_LOGE(TAG, "Scan was in failed state when stop complete.");
} else if (this->scanner_state_ == ScannerState::IDLE) {
ESP_LOGE(TAG, "Scan was idle when stop complete.");
} else if (this->scanner_state_ == ScannerState::STOPPED) {
ESP_LOGE(TAG, "Scan was stopped when stop complete.");
}
}
this->set_scanner_state_(ScannerState::STOPPED);
}
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {
@@ -417,7 +435,21 @@ void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_re
xSemaphoreGive(this->scan_result_lock_);
}
} else if (param.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
xSemaphoreGive(this->scan_end_lock_);
// Scan finished on its own
if (this->scanner_state_ != ScannerState::RUNNING) {
if (this->scanner_state_ == ScannerState::STOPPING) {
ESP_LOGE(TAG, "Scan was not running when scan completed.");
} else if (this->scanner_state_ == ScannerState::STARTING) {
ESP_LOGE(TAG, "Scan was not started when scan completed.");
} else if (this->scanner_state_ == ScannerState::FAILED) {
ESP_LOGE(TAG, "Scan was in failed state when scan completed.");
} else if (this->scanner_state_ == ScannerState::IDLE) {
ESP_LOGE(TAG, "Scan was idle when scan completed.");
} else if (this->scanner_state_ == ScannerState::STOPPED) {
ESP_LOGE(TAG, "Scan was stopped when scan completed.");
}
}
this->set_scanner_state_(ScannerState::STOPPED);
}
}
@@ -428,6 +460,11 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
}
}
void ESP32BLETracker::set_scanner_state_(ScannerState state) {
this->scanner_state_ = state;
this->scanner_state_callbacks_.call(state);
}
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) {
if (!data.uuid.contains(0x4C, 0x00))
@@ -680,8 +717,26 @@ void ESP32BLETracker::dump_config() {
ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f);
ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE");
ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", YESNO(this->scan_continuous_));
ESP_LOGCONFIG(TAG, " Scanner Idle: %s", YESNO(this->scanner_idle_));
ESP_LOGCONFIG(TAG, " Scan End: %s", YESNO(xSemaphoreGetMutexHolder(this->scan_end_lock_) == nullptr));
switch (this->scanner_state_) {
case ScannerState::IDLE:
ESP_LOGCONFIG(TAG, " Scanner State: IDLE");
break;
case ScannerState::STARTING:
ESP_LOGCONFIG(TAG, " Scanner State: STARTING");
break;
case ScannerState::RUNNING:
ESP_LOGCONFIG(TAG, " Scanner State: RUNNING");
break;
case ScannerState::STOPPING:
ESP_LOGCONFIG(TAG, " Scanner State: STOPPING");
break;
case ScannerState::STOPPED:
ESP_LOGCONFIG(TAG, " Scanner State: STOPPED");
break;
case ScannerState::FAILED:
ESP_LOGCONFIG(TAG, " Scanner State: FAILED");
break;
}
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
searching_, disconnecting_);
if (this->scan_start_fail_count_) {

View File

@@ -154,6 +154,21 @@ enum class ClientState {
ESTABLISHED,
};
enum class ScannerState {
// Scanner is idle, init state, set from the main loop when processing STOPPED
IDLE,
// Scanner is starting, set from the main loop only
STARTING,
// Scanner is running, set from the ESP callback only
RUNNING,
// Scanner failed to start, set from the ESP callback only
FAILED,
// Scanner is stopping, set from the main loop only
STOPPING,
// Scanner is stopped, set from the ESP callback only
STOPPED,
};
enum class ConnectionType {
// The default connection type, we hold all the services in ram
// for the duration of the connection.
@@ -173,6 +188,8 @@ class ESPBTClient : public ESPBTDeviceListener {
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
virtual void connect() = 0;
virtual void disconnect() = 0;
bool disconnect_pending() const { return this->want_disconnect_; }
void cancel_pending_disconnect() { this->want_disconnect_ = false; }
virtual void set_state(ClientState st) {
this->state_ = st;
if (st == ClientState::IDLE) {
@@ -201,6 +218,7 @@ class ESP32BLETracker : public Component,
void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; }
void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; }
void set_scan_active(bool scan_active) { scan_active_ = scan_active; }
bool get_scan_active() const { return scan_active_; }
void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; }
/// Setup the FreeRTOS task and the Bluetooth stack.
@@ -224,6 +242,11 @@ class ESP32BLETracker : public Component,
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
void ble_before_disabled_event_handler() override;
void add_scanner_state_callback(std::function<void(ScannerState)> &&callback) {
this->scanner_state_callbacks_.add(std::move(callback));
}
ScannerState get_scanner_state() const { return this->scanner_state_; }
protected:
void stop_scan_();
/// Start a single scan by setting up the parameters and doing some esp-idf calls.
@@ -238,6 +261,8 @@ class ESP32BLETracker : public Component,
void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param);
/// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received.
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param);
/// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed.
void set_scanner_state_(ScannerState state);
int app_id_{0};
@@ -255,12 +280,12 @@ class ESP32BLETracker : public Component,
uint8_t scan_start_fail_count_{0};
bool scan_continuous_;
bool scan_active_;
bool scanner_idle_{true};
ScannerState scanner_state_{ScannerState::IDLE};
CallbackManager<void(ScannerState)> scanner_state_callbacks_;
bool ble_was_disabled_{true};
bool raw_advertisements_{false};
bool parse_advertisements_{false};
SemaphoreHandle_t scan_result_lock_;
SemaphoreHandle_t scan_end_lock_;
size_t scan_result_index_{0};
#ifdef USE_PSRAM
const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 32;

View File

@@ -17,7 +17,7 @@ static const char *const TAG = "esp32_can";
static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) {
switch (bitrate) {
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H6)
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
case canbus::CAN_1KBPS:
*t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS();
return true;

View File

@@ -58,7 +58,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
channel.flags.io_loop_back = 0;
channel.flags.io_od_mode = 0;
channel.flags.invert_out = 0;
channel.flags.with_dma = 0;
channel.flags.with_dma = this->use_dma_;
channel.intr_priority = 0;
if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) {
ESP_LOGE(TAG, "Channel creation failed");

View File

@@ -51,6 +51,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
void set_use_dma(bool use_dma) { this->use_dma_ = use_dma; }
void set_use_psram(bool use_psram) { this->use_psram_ = use_psram; }
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
@@ -85,7 +86,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
rmt_encoder_handle_t encoder_{nullptr};
rmt_symbol_word_t *rmt_buf_{nullptr};
rmt_symbol_word_t bit0_, bit1_, reset_;
uint32_t rmt_symbols_;
uint32_t rmt_symbols_{48};
#else
rmt_item32_t *rmt_buf_{nullptr};
rmt_item32_t bit0_, bit1_, reset_;
@@ -94,11 +95,12 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
uint8_t pin_;
uint16_t num_leds_;
bool is_rgbw_;
bool is_wrgb_;
bool use_psram_;
bool is_rgbw_{false};
bool is_wrgb_{false};
bool use_dma_{false};
bool use_psram_{false};
RGBOrder rgb_order_;
RGBOrder rgb_order_{ORDER_RGB};
uint32_t last_refresh_{0};
optional<uint32_t> max_refresh_rate_{};

View File

@@ -3,7 +3,7 @@ import logging
from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32_rmt, light
from esphome.components import esp32, esp32_rmt, light
import esphome.config_validation as cv
from esphome.const import (
CONF_CHIPSET,
@@ -15,6 +15,7 @@ from esphome.const import (
CONF_RGB_ORDER,
CONF_RMT_CHANNEL,
CONF_RMT_SYMBOLS,
CONF_USE_DMA,
)
from esphome.core import CORE
@@ -138,6 +139,11 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
cv.Optional(CONF_USE_DMA): cv.All(
esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32S3]),
cv.only_with_esp_idf,
cv.boolean,
),
cv.Optional(CONF_USE_PSRAM, default=True): cv.boolean,
cv.Inclusive(
CONF_BIT0_HIGH,
@@ -211,6 +217,8 @@ async def to_code(config):
if esp32_rmt.use_new_rmt_driver():
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
if CONF_USE_DMA in config:
cg.add(var.set_use_dma(config[CONF_USE_DMA]))
else:
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
cg.add(

View File

@@ -40,9 +40,6 @@ async def new_fastled_light(config):
if CONF_MAX_REFRESH_RATE in config:
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
cg.add_library("fastled/FastLED", "3.9.16")
await light.register_light(var, config)
# https://github.com/FastLED/FastLED/blob/master/library.json
# 3.3.3 has an issue on ESP32 with RMT and fastled_clockless:
# https://github.com/esphome/issues/issues/1375
cg.add_library("fastled/FastLED", "3.3.2")
return var

View File

@@ -34,7 +34,7 @@ void FastLEDLightOutput::write_state(light::LightState *state) {
this->mark_shown_();
ESP_LOGVV(TAG, "Writing RGB values to bus...");
this->controller_->showLeds();
this->controller_->showLeds(this->state_parent_->current_values.get_brightness() * 255);
}
} // namespace fastled_base

View File

@@ -8,30 +8,45 @@ namespace esphome {
namespace gpio_expander {
/// @brief A class to cache the read state of a GPIO expander.
/// This class caches reads between GPIO Pins which are on the same bank.
/// This means that for reading whole Port (ex. 8 pins) component needs only one
/// I2C/SPI read per main loop call. It assumes, that one bit in byte identifies one GPIO pin
/// Template parameters:
/// T - Type which represents internal register. Could be uint8_t or uint16_t. Adjust to
/// match size of your internal GPIO bank register.
/// N - Number of pins
template<typename T, T N> class CachedGpioExpander {
public:
bool digital_read(T pin) {
if (!this->read_cache_invalidated_[pin]) {
this->read_cache_invalidated_[pin] = true;
return this->digital_read_cache(pin);
uint8_t bank = pin / (sizeof(T) * BITS_PER_BYTE);
if (this->read_cache_invalidated_[bank]) {
this->read_cache_invalidated_[bank] = false;
if (!this->digital_read_hw(pin))
return false;
}
return this->digital_read_hw(pin);
return this->digital_read_cache(pin);
}
void digital_write(T pin, bool value) { this->digital_write_hw(pin, value); }
protected:
/// @brief Call component low level function to read GPIO state from device
virtual bool digital_read_hw(T pin) = 0;
/// @brief Call component read function from internal cache.
virtual bool digital_read_cache(T pin) = 0;
/// @brief Call component low level function to write GPIO state to device
virtual void digital_write_hw(T pin, bool value) = 0;
const uint8_t cache_byte_size_ = N / (sizeof(T) * BITS_PER_BYTE);
/// @brief Invalidate cache. This function should be called in component loop().
void reset_pin_cache_() {
for (T i = 0; i < N; i++) {
this->read_cache_invalidated_[i] = false;
for (T i = 0; i < this->cache_byte_size_; i++) {
this->read_cache_invalidated_[i] = true;
}
}
std::array<bool, N> read_cache_invalidated_{};
static const uint8_t BITS_PER_BYTE = 8;
std::array<bool, N / (sizeof(T) * BITS_PER_BYTE)> read_cache_invalidated_{};
};
} // namespace gpio_expander

View File

@@ -5,6 +5,7 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_BORDER,
CONF_COLOR,
CONF_CONTINUOUS,
CONF_DIRECTION,
CONF_DURATION,
CONF_HEIGHT,
@@ -61,8 +62,6 @@ VALUE_POSITION_TYPE = {
"BELOW": ValuePositionType.VALUE_POSITION_TYPE_BELOW,
}
CONF_CONTINUOUS = "continuous"
GRAPH_TRACE_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(GraphTrace),

View File

@@ -18,6 +18,7 @@ MODELS = {
"yac": Model.GREE_YAC,
"yac1fb9": Model.GREE_YAC1FB9,
"yx1ff": Model.GREE_YX1FF,
"yag": Model.GREE_YAG,
}
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(

View File

@@ -22,13 +22,21 @@ void GreeClimate::transmit_state() {
remote_state[0] = this->fan_speed_() | this->operation_mode_();
remote_state[1] = this->temperature_();
if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) {
if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF || this->model_ == GREE_YAG) {
remote_state[2] = 0x60;
remote_state[3] = 0x50;
remote_state[4] = this->vertical_swing_();
}
if (this->model_ == GREE_YAC) {
if (this->model_ == GREE_YAG) {
remote_state[5] = 0x40;
if (this->vertical_swing_() == GREE_VDIR_SWING || this->horizontal_swing_() == GREE_HDIR_SWING) {
remote_state[0] |= (1 << 6);
}
}
if (this->model_ == GREE_YAC || this->model_ == GREE_YAG) {
remote_state[4] |= (this->horizontal_swing_() << 4);
}
@@ -57,6 +65,12 @@ void GreeClimate::transmit_state() {
// Calculate the checksum
if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) {
remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0);
} else if (this->model_ == GREE_YAG) {
remote_state[7] =
((((remote_state[0] & 0x0F) + (remote_state[1] & 0x0F) + (remote_state[2] & 0x0F) + (remote_state[3] & 0x0F) +
((remote_state[4] & 0xF0) >> 4) + ((remote_state[5] & 0xF0) >> 4) + ((remote_state[6] & 0xF0) >> 4) + 0x0A) &
0x0F)
<< 4);
} else {
remote_state[7] =
((((remote_state[0] & 0x0F) + (remote_state[1] & 0x0F) + (remote_state[2] & 0x0F) + (remote_state[3] & 0x0F) +

View File

@@ -58,7 +58,7 @@ const uint8_t GREE_VDIR_MIDDLE = 0x04;
const uint8_t GREE_VDIR_MDOWN = 0x05;
const uint8_t GREE_VDIR_DOWN = 0x06;
// Only available on YAC
// Only available on YAC/YAG
// Horizontal air directions. Note that these cannot be set on all heat pumps
const uint8_t GREE_HDIR_AUTO = 0x00;
const uint8_t GREE_HDIR_MANUAL = 0x00;
@@ -78,7 +78,7 @@ const uint8_t GREE_PRESET_SLEEP = 0x01;
const uint8_t GREE_PRESET_SLEEP_BIT = 0x80;
// Model codes
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF };
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF, GREE_YAG };
class GreeClimate : public climate_ir::ClimateIR {
public:

View File

@@ -69,7 +69,7 @@ void HLW8012Component::update() {
float power = cf_hz * this->power_multiplier_;
if (this->change_mode_at_ != 0) {
if (this->change_mode_at_ != 0 || this->change_mode_every_ == 0) {
// Only read cf1 after one cycle. Apparently it's quite unstable after being changed.
if (this->current_mode_) {
float current = cf1_hz * this->current_multiplier_;

View File

@@ -8,7 +8,7 @@
namespace esphome {
namespace hm3301 {
static const uint8_t SELECT_COMM_CMD = 0X88;
static const uint8_t SELECT_COMM_CMD = 0x88;
class HM3301Component : public PollingComponent, public i2c::I2CDevice {
public:

View File

@@ -10,9 +10,11 @@ from esphome.const import (
CONF_TIMEOUT,
CONF_TRIGGER_ID,
CONF_URL,
PLATFORM_HOST,
__version__,
)
from esphome.core import CORE, Lambda
from esphome.helpers import IS_MACOS
DEPENDENCIES = ["network"]
AUTO_LOAD = ["json", "watchdog"]
@@ -21,6 +23,7 @@ http_request_ns = cg.esphome_ns.namespace("http_request")
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
HttpRequestArduino = http_request_ns.class_("HttpRequestArduino", HttpRequestComponent)
HttpRequestIDF = http_request_ns.class_("HttpRequestIDF", HttpRequestComponent)
HttpRequestHost = http_request_ns.class_("HttpRequestHost", HttpRequestComponent)
HttpContainer = http_request_ns.class_("HttpContainer")
@@ -43,10 +46,13 @@ CONF_REDIRECT_LIMIT = "redirect_limit"
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
CONF_BUFFER_SIZE_RX = "buffer_size_rx"
CONF_BUFFER_SIZE_TX = "buffer_size_tx"
CONF_CA_CERTIFICATE_PATH = "ca_certificate_path"
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
CONF_ON_RESPONSE = "on_response"
CONF_HEADERS = "headers"
CONF_REQUEST_HEADERS = "request_headers"
CONF_COLLECT_HEADERS = "collect_headers"
CONF_BODY = "body"
CONF_JSON = "json"
CONF_CAPTURE_RESPONSE = "capture_response"
@@ -85,6 +91,8 @@ def validate_ssl_verification(config):
def _declare_request_class(value):
if CORE.is_host:
return cv.declare_id(HttpRequestHost)(value)
if CORE.using_esp_idf:
return cv.declare_id(HttpRequestIDF)(value)
if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
@@ -119,6 +127,10 @@ CONFIG_SCHEMA = cv.All(
cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32_idf=512): cv.All(
cv.uint16_t, cv.only_with_esp_idf
),
cv.Optional(CONF_CA_CERTIFICATE_PATH): cv.All(
cv.file_,
cv.only_on(PLATFORM_HOST),
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.require_framework_version(
@@ -126,6 +138,7 @@ CONFIG_SCHEMA = cv.All(
esp32_arduino=cv.Version(0, 0, 0),
esp_idf=cv.Version(0, 0, 0),
rp2040_arduino=cv.Version(0, 0, 0),
host=cv.Version(0, 0, 0),
),
validate_ssl_verification,
)
@@ -168,6 +181,21 @@ async def to_code(config):
cg.add_library("ESP8266HTTPClient", None)
if CORE.is_rp2040 and CORE.using_arduino:
cg.add_library("HTTPClient", None)
if CORE.is_host:
if IS_MACOS:
cg.add_build_flag("-I/opt/homebrew/opt/openssl/include")
cg.add_build_flag("-L/opt/homebrew/opt/openssl/lib")
cg.add_build_flag("-lssl")
cg.add_build_flag("-lcrypto")
cg.add_build_flag("-Wl,-framework,CoreFoundation")
cg.add_build_flag("-Wl,-framework,Security")
cg.add_define("CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN")
cg.add_define("CPPHTTPLIB_OPENSSL_SUPPORT")
elif path := config.get(CONF_CA_CERTIFICATE_PATH):
cg.add_define("CPPHTTPLIB_OPENSSL_SUPPORT")
cg.add(var.set_ca_path(path))
cg.add_build_flag("-lssl")
cg.add_build_flag("-lcrypto")
await cg.register_component(var, config)
@@ -176,9 +204,13 @@ HTTP_REQUEST_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(HttpRequestComponent),
cv.Required(CONF_URL): cv.templatable(validate_url),
cv.Optional(CONF_HEADERS): cv.All(
cv.Optional(CONF_HEADERS): cv.invalid(
"The 'headers' options has been renamed to 'request_headers'"
),
cv.Optional(CONF_REQUEST_HEADERS): cv.All(
cv.Schema({cv.string: cv.templatable(cv.string)})
),
cv.Optional(CONF_COLLECT_HEADERS): cv.ensure_list(cv.string),
cv.Optional(CONF_VERIFY_SSL): cv.invalid(
f"{CONF_VERIFY_SSL} has moved to the base component configuration."
),
@@ -263,11 +295,12 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
for key in json_:
template_ = await cg.templatable(json_[key], args, cg.std_string)
cg.add(var.add_json(key, template_))
for key in config.get(CONF_HEADERS, []):
template_ = await cg.templatable(
config[CONF_HEADERS][key], args, cg.const_char_ptr
)
cg.add(var.add_header(key, template_))
for key, value in config.get(CONF_REQUEST_HEADERS, {}).items():
template_ = await cg.templatable(value, args, cg.const_char_ptr)
cg.add(var.add_request_header(key, template_))
for value in config.get(CONF_COLLECT_HEADERS, []):
cg.add(var.add_collect_header(value))
for conf in config.get(CONF_ON_RESPONSE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])

View File

@@ -20,5 +20,25 @@ void HttpRequestComponent::dump_config() {
}
}
std::string HttpContainer::get_response_header(const std::string &header_name) {
auto response_headers = this->get_response_headers();
auto header_name_lower_case = str_lower_case(header_name);
if (response_headers.count(header_name_lower_case) == 0) {
ESP_LOGW(TAG, "No header with name %s found", header_name_lower_case.c_str());
return "";
} else {
auto values = response_headers[header_name_lower_case];
if (values.empty()) {
ESP_LOGE(TAG, "header with name %s returned an empty list, this shouldn't happen",
header_name_lower_case.c_str());
return "";
} else {
auto header_value = values.front();
ESP_LOGD(TAG, "Header with name %s found with value %s", header_name_lower_case.c_str(), header_value.c_str());
return header_value;
}
}
}
} // namespace http_request
} // namespace esphome

View File

@@ -3,6 +3,7 @@
#include <list>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>
@@ -95,9 +96,19 @@ class HttpContainer : public Parented<HttpRequestComponent> {
size_t get_bytes_read() const { return this->bytes_read_; }
/**
* @brief Get response headers.
*
* @return The key is the lower case response header name, the value is the header value.
*/
std::map<std::string, std::list<std::string>> get_response_headers() { return this->response_headers_; }
std::string get_response_header(const std::string &header_name);
protected:
size_t bytes_read_{0};
bool secure_{false};
std::map<std::string, std::list<std::string>> response_headers_{};
};
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string &> {
@@ -119,21 +130,46 @@ class HttpRequestComponent : public Component {
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
std::shared_ptr<HttpContainer> get(std::string url) { return this->start(std::move(url), "GET", "", {}); }
std::shared_ptr<HttpContainer> get(std::string url, std::list<Header> headers) {
return this->start(std::move(url), "GET", "", std::move(headers));
std::shared_ptr<HttpContainer> get(const std::string &url) { return this->start(url, "GET", "", {}); }
std::shared_ptr<HttpContainer> get(const std::string &url, const std::list<Header> &request_headers) {
return this->start(url, "GET", "", request_headers);
}
std::shared_ptr<HttpContainer> post(std::string url, std::string body) {
return this->start(std::move(url), "POST", std::move(body), {});
std::shared_ptr<HttpContainer> get(const std::string &url, const std::list<Header> &request_headers,
const std::set<std::string> &collect_headers) {
return this->start(url, "GET", "", request_headers, collect_headers);
}
std::shared_ptr<HttpContainer> post(std::string url, std::string body, std::list<Header> headers) {
return this->start(std::move(url), "POST", std::move(body), std::move(headers));
std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body) {
return this->start(url, "POST", body, {});
}
std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body,
const std::list<Header> &request_headers) {
return this->start(url, "POST", body, request_headers);
}
std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body,
const std::list<Header> &request_headers,
const std::set<std::string> &collect_headers) {
return this->start(url, "POST", body, request_headers, collect_headers);
}
virtual std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
std::list<Header> headers) = 0;
std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers) {
return this->start(url, method, body, request_headers, {});
}
std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers,
const std::set<std::string> &collect_headers) {
std::set<std::string> lower_case_collect_headers;
for (const std::string &collect_header : collect_headers) {
lower_case_collect_headers.insert(str_lower_case(collect_header));
}
return this->perform(url, method, body, request_headers, lower_case_collect_headers);
}
protected:
virtual std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
std::list<Header> request_headers,
std::set<std::string> collect_headers) = 0;
const char *useragent_{nullptr};
bool follow_redirects_{};
uint16_t redirect_limit_{};
@@ -149,7 +185,11 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
TEMPLATABLE_VALUE(std::string, body)
TEMPLATABLE_VALUE(bool, capture_response)
void add_header(const char *key, TemplatableValue<const char *, Ts...> value) { this->headers_.insert({key, value}); }
void add_request_header(const char *key, TemplatableValue<const char *, Ts...> value) {
this->request_headers_.insert({key, value});
}
void add_collect_header(const char *value) { this->collect_headers_.insert(value); }
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.insert({key, value}); }
@@ -176,16 +216,17 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
body = json::build_json(f);
}
std::list<Header> headers;
for (const auto &item : this->headers_) {
std::list<Header> request_headers;
for (const auto &item : this->request_headers_) {
auto val = item.second;
Header header;
header.name = item.first;
header.value = val.value(x...);
headers.push_back(header);
request_headers.push_back(header);
}
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, headers);
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, request_headers,
this->collect_headers_);
if (container == nullptr) {
for (auto *trigger : this->error_triggers_)
@@ -238,7 +279,8 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
}
void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); }
HttpRequestComponent *parent_;
std::map<const char *, TemplatableValue<const char *, Ts...>> headers_{};
std::map<const char *, TemplatableValue<const char *, Ts...>> request_headers_{};
std::set<std::string> collect_headers_{"content-type", "content-length"};
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
std::vector<HttpRequestResponseTrigger *> response_triggers_{};

View File

@@ -14,8 +14,9 @@ namespace http_request {
static const char *const TAG = "http_request.arduino";
std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::string method, std::string body,
std::list<Header> headers) {
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(std::string url, std::string method, std::string body,
std::list<Header> request_headers,
std::set<std::string> collect_headers) {
if (!network::is_connected()) {
this->status_momentary_error("failed", 1000);
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
@@ -95,14 +96,17 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s
if (this->useragent_ != nullptr) {
container->client_.setUserAgent(this->useragent_);
}
for (const auto &header : headers) {
for (const auto &header : request_headers) {
container->client_.addHeader(header.name.c_str(), header.value.c_str(), false, true);
}
// returned needed headers must be collected before the requests
static const char *header_keys[] = {"Content-Length", "Content-Type"};
static const size_t HEADER_COUNT = sizeof(header_keys) / sizeof(header_keys[0]);
container->client_.collectHeaders(header_keys, HEADER_COUNT);
const char *header_keys[collect_headers.size()];
int index = 0;
for (auto const &header_name : collect_headers) {
header_keys[index++] = header_name.c_str();
}
container->client_.collectHeaders(header_keys, index);
App.feed_wdt();
container->status_code = container->client_.sendRequest(method.c_str(), body.c_str());
@@ -121,6 +125,18 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s
// Still return the container, so it can be used to get the status code and error message
}
container->response_headers_ = {};
auto header_count = container->client_.headers();
for (int i = 0; i < header_count; i++) {
const std::string header_name = str_lower_case(container->client_.headerName(i).c_str());
if (collect_headers.count(header_name) > 0) {
std::string header_value = container->client_.header(i).c_str();
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
container->response_headers_[header_name].push_back(header_value);
break;
}
}
int content_length = container->client_.getSize();
ESP_LOGD(TAG, "Content-Length: %d", content_length);
container->content_length = (size_t) content_length;

View File

@@ -29,9 +29,10 @@ class HttpContainerArduino : public HttpContainer {
};
class HttpRequestArduino : public HttpRequestComponent {
public:
std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
std::list<Header> headers) override;
protected:
std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
std::list<Header> request_headers,
std::set<std::string> collect_headers) override;
};
} // namespace http_request

View File

@@ -0,0 +1,141 @@
#include "http_request_host.h"
#ifdef USE_HOST
#include <regex>
#include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
namespace esphome {
namespace http_request {
static const char *const TAG = "http_request.host";
std::shared_ptr<HttpContainer> HttpRequestHost::perform(std::string url, std::string method, std::string body,
std::list<Header> request_headers,
std::set<std::string> response_headers) {
if (!network::is_connected()) {
this->status_momentary_error("failed", 1000);
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
return nullptr;
}
std::regex url_regex(R"(^(([^:\/?#]+):)?(//([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)", std::regex::extended);
std::smatch url_match_result;
if (!std::regex_match(url, url_match_result, url_regex) || url_match_result.length() < 7) {
ESP_LOGE(TAG, "HTTP Request failed; Malformed URL: %s", url.c_str());
return nullptr;
}
auto host = url_match_result[4].str();
auto scheme_host = url_match_result[1].str() + url_match_result[3].str();
auto path = url_match_result[5].str() + url_match_result[6].str();
if (path.empty())
path = "/";
std::shared_ptr<HttpContainerHost> container = std::make_shared<HttpContainerHost>();
container->set_parent(this);
const uint32_t start = millis();
watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
httplib::Headers h_headers;
h_headers.emplace("Host", host.c_str());
h_headers.emplace("User-Agent", this->useragent_);
for (const auto &[name, value] : request_headers) {
h_headers.emplace(name, value);
}
httplib::Client client(scheme_host.c_str());
if (!client.is_valid()) {
ESP_LOGE(TAG, "HTTP Request failed; Invalid URL: %s", url.c_str());
return nullptr;
}
client.set_follow_location(this->follow_redirects_);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
if (this->ca_path_ != nullptr)
client.set_ca_cert_path(this->ca_path_);
#endif
httplib::Result result;
if (method == "GET") {
result = client.Get(path, h_headers, [&](const char *data, size_t data_length) {
ESP_LOGV(TAG, "Got data length: %zu", data_length);
container->response_body_.insert(container->response_body_.end(), (const uint8_t *) data,
(const uint8_t *) data + data_length);
return true;
});
} else if (method == "HEAD") {
result = client.Head(path, h_headers);
} else if (method == "PUT") {
result = client.Put(path, h_headers, body, "");
if (result) {
auto data = std::vector<uint8_t>(result->body.begin(), result->body.end());
container->response_body_.insert(container->response_body_.end(), data.begin(), data.end());
}
} else if (method == "PATCH") {
result = client.Patch(path, h_headers, body, "");
if (result) {
auto data = std::vector<uint8_t>(result->body.begin(), result->body.end());
container->response_body_.insert(container->response_body_.end(), data.begin(), data.end());
}
} else if (method == "POST") {
result = client.Post(path, h_headers, body, "");
if (result) {
auto data = std::vector<uint8_t>(result->body.begin(), result->body.end());
container->response_body_.insert(container->response_body_.end(), data.begin(), data.end());
}
} else {
ESP_LOGW(TAG, "HTTP Request failed - unsupported method %s; URL: %s", method.c_str(), url.c_str());
container->end();
return nullptr;
}
App.feed_wdt();
if (!result) {
ESP_LOGW(TAG, "HTTP Request failed; URL: %s, error code: %u", url.c_str(), (unsigned) result.error());
container->end();
this->status_momentary_error("failed", 1000);
return nullptr;
}
App.feed_wdt();
auto response = *result;
container->status_code = response.status;
if (!is_success(response.status)) {
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), response.status);
this->status_momentary_error("failed", 1000);
// Still return the container, so it can be used to get the status code and error message
}
container->content_length = container->response_body_.size();
for (auto header : response.headers) {
ESP_LOGD(TAG, "Header: %s: %s", header.first.c_str(), header.second.c_str());
auto lower_name = str_lower_case(header.first);
if (response_headers.find(lower_name) != response_headers.end()) {
container->response_headers_[lower_name].emplace_back(header.second);
}
}
container->duration_ms = millis() - start;
return container;
}
int HttpContainerHost::read(uint8_t *buf, size_t max_len) {
auto bytes_remaining = this->response_body_.size() - this->bytes_read_;
auto read_len = std::min(max_len, bytes_remaining);
memcpy(buf, this->response_body_.data() + this->bytes_read_, read_len);
this->bytes_read_ += read_len;
return read_len;
}
void HttpContainerHost::end() {
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
this->response_body_ = std::vector<uint8_t>();
this->bytes_read_ = 0;
}
} // namespace http_request
} // namespace esphome
#endif // USE_HOST

View File

@@ -0,0 +1,37 @@
#pragma once
#include "http_request.h"
#ifdef USE_HOST
#define CPPHTTPLIB_NO_EXCEPTIONS
#include "httplib.h"
namespace esphome {
namespace http_request {
class HttpRequestHost;
class HttpContainerHost : public HttpContainer {
public:
int read(uint8_t *buf, size_t max_len) override;
void end() override;
protected:
friend class HttpRequestHost;
std::vector<uint8_t> response_body_{};
};
class HttpRequestHost : public HttpRequestComponent {
public:
std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
std::list<Header> request_headers,
std::set<std::string> response_headers) override;
void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; }
protected:
const char *ca_path_{};
};
} // namespace http_request
} // namespace esphome
#endif // USE_HOST

View File

@@ -19,14 +19,41 @@ namespace http_request {
static const char *const TAG = "http_request.idf";
struct UserData {
const std::set<std::string> &collect_headers;
std::map<std::string, std::list<std::string>> response_headers;
};
void HttpRequestIDF::dump_config() {
HttpRequestComponent::dump_config();
ESP_LOGCONFIG(TAG, " Buffer Size RX: %u", this->buffer_size_rx_);
ESP_LOGCONFIG(TAG, " Buffer Size TX: %u", this->buffer_size_tx_);
}
std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::string method, std::string body,
std::list<Header> headers) {
esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
UserData *user_data = (UserData *) evt->user_data;
switch (evt->event_id) {
case HTTP_EVENT_ON_HEADER: {
const std::string header_name = str_lower_case(evt->header_key);
if (user_data->collect_headers.count(header_name)) {
const std::string header_value = evt->header_value;
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
user_data->response_headers[header_name].push_back(header_value);
break;
}
break;
}
default: {
break;
}
}
return ESP_OK;
}
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(std::string url, std::string method, std::string body,
std::list<Header> request_headers,
std::set<std::string> collect_headers) {
if (!network::is_connected()) {
this->status_momentary_error("failed", 1000);
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
@@ -76,6 +103,10 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
const uint32_t start = millis();
watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
config.event_handler = http_event_handler;
auto user_data = UserData{collect_headers, {}};
config.user_data = static_cast<void *>(&user_data);
esp_http_client_handle_t client = esp_http_client_init(&config);
std::shared_ptr<HttpContainerIDF> container = std::make_shared<HttpContainerIDF>(client);
@@ -83,7 +114,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
container->set_secure(secure);
for (const auto &header : headers) {
for (const auto &header : request_headers) {
esp_http_client_set_header(client, header.name.c_str(), header.value.c_str());
}
@@ -124,6 +155,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
container->feed_wdt();
container->status_code = esp_http_client_get_status_code(client);
container->feed_wdt();
container->set_response_headers(user_data.response_headers);
if (is_success(container->status_code)) {
container->duration_ms = millis() - start;
return container;

View File

@@ -21,6 +21,10 @@ class HttpContainerIDF : public HttpContainer {
/// @brief Feeds the watchdog timer if the executing task has one attached
void feed_wdt();
void set_response_headers(std::map<std::string, std::list<std::string>> &response_headers) {
this->response_headers_ = std::move(response_headers);
}
protected:
esp_http_client_handle_t client_;
};
@@ -29,16 +33,19 @@ class HttpRequestIDF : public HttpRequestComponent {
public:
void dump_config() override;
std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
std::list<Header> headers) override;
void set_buffer_size_rx(uint16_t buffer_size_rx) { this->buffer_size_rx_ = buffer_size_rx; }
void set_buffer_size_tx(uint16_t buffer_size_tx) { this->buffer_size_tx_ = buffer_size_tx; }
protected:
std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
std::list<Header> request_headers,
std::set<std::string> collect_headers) override;
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
uint16_t buffer_size_rx_{};
uint16_t buffer_size_tx_{};
/// @brief Monitors the http client events to gather response headers
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
};
} // namespace http_request

File diff suppressed because it is too large Load Diff

View File

@@ -139,6 +139,10 @@ class I2CDevice {
/// @param address of the device
void set_i2c_address(uint8_t address) { address_ = address; }
/// @brief Returns the I2C address of the object.
/// @return the I2C address
uint8_t get_i2c_address() const { return this->address_; }
/// @brief we store the pointer to the I2CBus to use
/// @param bus pointer to the I2CBus object
void set_i2c_bus(I2CBus *bus) { bus_ = bus; }

View File

@@ -67,7 +67,7 @@ void IDFI2CBus::setup() {
ESP_LOGV(TAG, "i2c_timeout set to %" PRIu32 " ticks (%" PRIu32 " us)", timeout_ * 80, timeout_);
}
}
err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, ESP_INTR_FLAG_IRAM);
err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, 0);
if (err != ESP_OK) {
ESP_LOGW(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err));
this->mark_failed();

View File

@@ -8,7 +8,15 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S3,
)
import esphome.config_validation as cv
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
from esphome.const import (
CONF_BITS_PER_SAMPLE,
CONF_CHANNEL,
CONF_ID,
CONF_SAMPLE_RATE,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
)
from esphome.core import CORE
from esphome.cpp_generator import MockObjClass
import esphome.final_validate as fv
@@ -31,10 +39,14 @@ CONF_SECONDARY = "secondary"
CONF_USE_APLL = "use_apll"
CONF_BITS_PER_CHANNEL = "bits_per_channel"
CONF_MCLK_MULTIPLE = "mclk_multiple"
CONF_MONO = "mono"
CONF_LEFT = "left"
CONF_RIGHT = "right"
CONF_STEREO = "stereo"
CONF_BOTH = "both"
CONF_USE_LEGACY = "use_legacy"
i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio")
I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component)
@@ -50,6 +62,12 @@ I2S_MODE_OPTIONS = {
CONF_SECONDARY: i2s_mode_t.I2S_MODE_SLAVE, # NOLINT
}
i2s_role_t = cg.global_ns.enum("i2s_role_t")
I2S_ROLE_OPTIONS = {
CONF_PRIMARY: i2s_role_t.I2S_ROLE_MASTER, # NOLINT
CONF_SECONDARY: i2s_role_t.I2S_ROLE_SLAVE, # NOLINT
}
# https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h
I2S_PORTS = {
VARIANT_ESP32: 2,
@@ -60,10 +78,23 @@ I2S_PORTS = {
i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t")
I2S_CHANNELS = {
CONF_MONO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ALL_LEFT,
CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT,
CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT,
CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT,
CONF_MONO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ALL_LEFT, # left data to both channels
CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT, # mono data
CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT, # mono data
CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT, # stereo data to both channels
}
i2s_slot_mode_t = cg.global_ns.enum("i2s_slot_mode_t")
I2S_SLOT_MODE = {
CONF_MONO: i2s_slot_mode_t.I2S_SLOT_MODE_MONO,
CONF_STEREO: i2s_slot_mode_t.I2S_SLOT_MODE_STEREO,
}
i2s_std_slot_mask_t = cg.global_ns.enum("i2s_std_slot_mask_t")
I2S_STD_SLOT_MASK = {
CONF_LEFT: i2s_std_slot_mask_t.I2S_STD_SLOT_LEFT,
CONF_RIGHT: i2s_std_slot_mask_t.I2S_STD_SLOT_RIGHT,
CONF_BOTH: i2s_std_slot_mask_t.I2S_STD_SLOT_BOTH,
}
i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t")
@@ -83,9 +114,37 @@ I2S_BITS_PER_CHANNEL = {
32: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_32BIT,
}
i2s_slot_bit_width_t = cg.global_ns.enum("i2s_slot_bit_width_t")
I2S_SLOT_BIT_WIDTH = {
"default": i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_AUTO,
8: i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_8BIT,
16: i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_16BIT,
24: i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_24BIT,
32: i2s_slot_bit_width_t.I2S_SLOT_BIT_WIDTH_32BIT,
}
i2s_mclk_multiple_t = cg.global_ns.enum("i2s_mclk_multiple_t")
I2S_MCLK_MULTIPLE = {
128: i2s_mclk_multiple_t.I2S_MCLK_MULTIPLE_128,
256: i2s_mclk_multiple_t.I2S_MCLK_MULTIPLE_256,
384: i2s_mclk_multiple_t.I2S_MCLK_MULTIPLE_384,
512: i2s_mclk_multiple_t.I2S_MCLK_MULTIPLE_512,
}
_validate_bits = cv.float_with_unit("bits", "bit")
def validate_mclk_divisible_by_3(config):
if config[CONF_BITS_PER_SAMPLE] == 24 and config[CONF_MCLK_MULTIPLE] % 3 != 0:
raise cv.Invalid(
f"{CONF_MCLK_MULTIPLE} must be divisible by 3 when bits per sample is 24"
)
return config
_use_legacy_driver = None
def i2s_audio_component_schema(
class_: MockObjClass,
*,
@@ -97,43 +156,83 @@ def i2s_audio_component_schema(
{
cv.GenerateID(): cv.declare_id(class_),
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
cv.Optional(CONF_CHANNEL, default=default_channel): cv.enum(I2S_CHANNELS),
cv.Optional(CONF_CHANNEL, default=default_channel): cv.one_of(
*I2S_CHANNELS
),
cv.Optional(CONF_SAMPLE_RATE, default=default_sample_rate): cv.int_range(
min=1
),
cv.Optional(CONF_BITS_PER_SAMPLE, default=default_bits_per_sample): cv.All(
_validate_bits, cv.enum(I2S_BITS_PER_SAMPLE)
_validate_bits, cv.one_of(*I2S_BITS_PER_SAMPLE)
),
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum(
I2S_MODE_OPTIONS, lower=True
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.one_of(
*I2S_MODE_OPTIONS, lower=True
),
cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All(
cv.Any(cv.float_with_unit("bits", "bit"), "default"),
cv.enum(I2S_BITS_PER_CHANNEL),
cv.one_of(*I2S_BITS_PER_CHANNEL),
),
cv.Optional(CONF_MCLK_MULTIPLE, default=256): cv.one_of(*I2S_MCLK_MULTIPLE),
}
)
async def register_i2s_audio_component(var, config):
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
cg.add(var.set_i2s_mode(config[CONF_I2S_MODE]))
cg.add(var.set_channel(config[CONF_CHANNEL]))
if use_legacy():
cg.add(var.set_i2s_mode(I2S_MODE_OPTIONS[config[CONF_I2S_MODE]]))
cg.add(var.set_channel(I2S_CHANNELS[config[CONF_CHANNEL]]))
cg.add(
var.set_bits_per_sample(I2S_BITS_PER_SAMPLE[config[CONF_BITS_PER_SAMPLE]])
)
cg.add(
var.set_bits_per_channel(
I2S_BITS_PER_CHANNEL[config[CONF_BITS_PER_CHANNEL]]
)
)
else:
cg.add(var.set_i2s_role(I2S_ROLE_OPTIONS[config[CONF_I2S_MODE]]))
slot_mode = config[CONF_CHANNEL]
if slot_mode != CONF_STEREO:
slot_mode = CONF_MONO
slot_mask = config[CONF_CHANNEL]
if slot_mask not in [CONF_LEFT, CONF_RIGHT]:
slot_mask = CONF_BOTH
cg.add(var.set_slot_mode(I2S_SLOT_MODE[slot_mode]))
cg.add(var.set_std_slot_mask(I2S_STD_SLOT_MASK[slot_mask]))
cg.add(var.set_slot_bit_width(I2S_SLOT_BIT_WIDTH[config[CONF_BITS_PER_SAMPLE]]))
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
cg.add(var.set_bits_per_channel(config[CONF_BITS_PER_CHANNEL]))
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
cg.add(var.set_mclk_multiple(I2S_MCLK_MULTIPLE[config[CONF_MCLK_MULTIPLE]]))
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(I2SAudioComponent),
cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_I2S_MCLK_PIN): pins.internal_gpio_output_pin_number,
}
def validate_use_legacy(value):
global _use_legacy_driver # noqa: PLW0603
if CONF_USE_LEGACY in value:
if (_use_legacy_driver is not None) and (
_use_legacy_driver != value[CONF_USE_LEGACY]
):
raise cv.Invalid(
f"All i2s_audio components must set {CONF_USE_LEGACY} to the same value."
)
if (not value[CONF_USE_LEGACY]) and (CORE.using_arduino):
raise cv.Invalid("Arduino supports only the legacy i2s driver.")
_use_legacy_driver = value[CONF_USE_LEGACY]
return value
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(I2SAudioComponent),
cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_I2S_MCLK_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_USE_LEGACY): cv.boolean,
},
),
validate_use_legacy,
)
@@ -148,12 +247,22 @@ def _final_validate(_):
)
def use_legacy():
framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
if CORE.using_esp_idf and framework_version >= cv.Version(5, 0, 0):
if not _use_legacy_driver:
return False
return True
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if use_legacy():
cg.add_define("USE_I2S_LEGACY")
cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN]))
if CONF_I2S_BCLK_PIN in config:

View File

@@ -2,9 +2,14 @@
#ifdef USE_ESP32
#include <driver/i2s.h>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/defines.h"
#ifdef USE_I2S_LEGACY
#include <driver/i2s.h>
#else
#include <driver/i2s_std.h>
#endif
namespace esphome {
namespace i2s_audio {
@@ -13,20 +18,36 @@ class I2SAudioComponent;
class I2SAudioBase : public Parented<I2SAudioComponent> {
public:
#ifdef USE_I2S_LEGACY
void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; }
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
void set_bits_per_channel(i2s_bits_per_chan_t bits_per_channel) { this->bits_per_channel_ = bits_per_channel; }
#else
void set_i2s_role(i2s_role_t role) { this->i2s_role_ = role; }
void set_slot_mode(i2s_slot_mode_t slot_mode) { this->slot_mode_ = slot_mode; }
void set_std_slot_mask(i2s_std_slot_mask_t std_slot_mask) { this->std_slot_mask_ = std_slot_mask; }
void set_slot_bit_width(i2s_slot_bit_width_t slot_bit_width) { this->slot_bit_width_ = slot_bit_width; }
#endif
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; }
void set_mclk_multiple(i2s_mclk_multiple_t mclk_multiple) { this->mclk_multiple_ = mclk_multiple; }
protected:
#ifdef USE_I2S_LEGACY
i2s_mode_t i2s_mode_{};
i2s_channel_fmt_t channel_;
uint32_t sample_rate_;
i2s_bits_per_sample_t bits_per_sample_;
i2s_bits_per_chan_t bits_per_channel_;
#else
i2s_role_t i2s_role_{};
i2s_slot_mode_t slot_mode_;
i2s_std_slot_mask_t std_slot_mask_;
i2s_slot_bit_width_t slot_bit_width_;
#endif
uint32_t sample_rate_;
bool use_apll_;
i2s_mclk_multiple_t mclk_multiple_;
};
class I2SAudioIn : public I2SAudioBase {};
@@ -37,6 +58,7 @@ class I2SAudioComponent : public Component {
public:
void setup() override;
#ifdef USE_I2S_LEGACY
i2s_pin_config_t get_pin_config() const {
return {
.mck_io_num = this->mclk_pin_,
@@ -46,6 +68,20 @@ class I2SAudioComponent : public Component {
.data_in_num = I2S_PIN_NO_CHANGE,
};
}
#else
i2s_std_gpio_config_t get_pin_config() const {
return {.mclk = (gpio_num_t) this->mclk_pin_,
.bclk = (gpio_num_t) this->bclk_pin_,
.ws = (gpio_num_t) this->lrclk_pin_,
.dout = I2S_GPIO_UNUSED, // add local ports
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
}};
}
#endif
void set_mclk_pin(int pin) { this->mclk_pin_ = pin; }
void set_bclk_pin(int pin) { this->bclk_pin_ = pin; }
@@ -62,9 +98,13 @@ class I2SAudioComponent : public Component {
I2SAudioIn *audio_in_{nullptr};
I2SAudioOut *audio_out_{nullptr};
#ifdef USE_I2S_LEGACY
int mclk_pin_{I2S_PIN_NO_CHANGE};
int bclk_pin_{I2S_PIN_NO_CHANGE};
#else
int mclk_pin_{I2S_GPIO_UNUSED};
int bclk_pin_{I2S_GPIO_UNUSED};
#endif
int lrclk_pin_;
i2s_port_t port_{};
};

View File

@@ -14,6 +14,7 @@ from .. import (
I2SAudioComponent,
I2SAudioOut,
i2s_audio_ns,
use_legacy,
)
CODEOWNERS = ["@jesserockz"]
@@ -87,6 +88,14 @@ CONFIG_SCHEMA = cv.All(
)
def _final_validate(_):
if not use_legacy():
raise cv.Invalid("I2S media player is only compatible with legacy i2s driver.")
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -1,17 +1,28 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32, microphone
from esphome.components import audio, esp32, microphone
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NUMBER
from esphome.const import (
CONF_BITS_PER_SAMPLE,
CONF_CHANNEL,
CONF_ID,
CONF_NUM_CHANNELS,
CONF_NUMBER,
CONF_SAMPLE_RATE,
)
from .. import (
CONF_I2S_DIN_PIN,
CONF_LEFT,
CONF_MONO,
CONF_RIGHT,
I2SAudioIn,
i2s_audio_component_schema,
i2s_audio_ns,
register_i2s_audio_component,
use_legacy,
validate_mclk_divisible_by_3,
)
CODEOWNERS = ["@jesserockz"]
@@ -29,7 +40,7 @@ INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32]
PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3]
def validate_esp32_variant(config):
def _validate_esp32_variant(config):
variant = esp32.get_esp32_variant()
if config[CONF_ADC_TYPE] == "external":
if config[CONF_PDM]:
@@ -43,6 +54,34 @@ def validate_esp32_variant(config):
raise NotImplementedError
def _validate_channel(config):
if config[CONF_CHANNEL] == CONF_MONO:
raise cv.Invalid(f"I2S microphone does not support {CONF_MONO}.")
return config
def _set_num_channels_from_config(config):
if config[CONF_CHANNEL] in (CONF_LEFT, CONF_RIGHT):
config[CONF_NUM_CHANNELS] = 1
else:
config[CONF_NUM_CHANNELS] = 2
return config
def _set_stream_limits(config):
audio.set_stream_limits(
min_bits_per_sample=config.get(CONF_BITS_PER_SAMPLE),
max_bits_per_sample=config.get(CONF_BITS_PER_SAMPLE),
min_channels=config.get(CONF_NUM_CHANNELS),
max_channels=config.get(CONF_NUM_CHANNELS),
min_sample_rate=config.get(CONF_SAMPLE_RATE),
max_sample_rate=config.get(CONF_SAMPLE_RATE),
)(config)
return config
BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
i2s_audio_component_schema(
I2SAudioMicrophone,
@@ -70,10 +109,23 @@ CONFIG_SCHEMA = cv.All(
},
key=CONF_ADC_TYPE,
),
validate_esp32_variant,
_validate_esp32_variant,
_validate_channel,
_set_num_channels_from_config,
_set_stream_limits,
validate_mclk_divisible_by_3,
)
def _final_validate(config):
if not use_legacy():
if config[CONF_ADC_TYPE] == "internal":
raise cv.Invalid("Internal ADC is only compatible with legacy i2s driver.")
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -2,7 +2,12 @@
#ifdef USE_ESP32
#ifdef USE_I2S_LEGACY
#include <driver/i2s.h>
#else
#include <driver/i2s_std.h>
#include <driver/i2s_pdm.h>
#endif
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
@@ -10,12 +15,28 @@
namespace esphome {
namespace i2s_audio {
static const size_t BUFFER_SIZE = 512;
static const UBaseType_t MAX_LISTENERS = 16;
static const uint32_t READ_DURATION_MS = 16;
static const size_t TASK_STACK_SIZE = 4096;
static const ssize_t TASK_PRIORITY = 23;
static const char *const TAG = "i2s_audio.microphone";
enum MicrophoneEventGroupBits : uint32_t {
COMMAND_STOP = (1 << 0), // stops the microphone task
TASK_STARTING = (1 << 10),
TASK_RUNNING = (1 << 11),
TASK_STOPPING = (1 << 12),
TASK_STOPPED = (1 << 13),
ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits
};
void I2SAudioMicrophone::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone...");
#ifdef USE_I2S_LEGACY
#if SOC_I2S_SUPPORTS_ADC
if (this->adc_) {
if (this->parent_->get_port() != I2S_NUM_0) {
@@ -24,6 +45,7 @@ void I2SAudioMicrophone::setup() {
return;
}
} else
#endif
#endif
{
if (this->pdm_) {
@@ -34,19 +56,65 @@ void I2SAudioMicrophone::setup() {
}
}
}
this->active_listeners_semaphore_ = xSemaphoreCreateCounting(MAX_LISTENERS, MAX_LISTENERS);
if (this->active_listeners_semaphore_ == nullptr) {
ESP_LOGE(TAG, "Failed to create semaphore");
this->mark_failed();
return;
}
this->event_group_ = xEventGroupCreate();
if (this->event_group_ == nullptr) {
ESP_LOGE(TAG, "Failed to create event group");
this->mark_failed();
return;
}
}
void I2SAudioMicrophone::start() {
if (this->is_failed())
return;
if (this->state_ == microphone::STATE_RUNNING)
return; // Already running
this->state_ = microphone::STATE_STARTING;
xSemaphoreTake(this->active_listeners_semaphore_, 0);
}
void I2SAudioMicrophone::start_() {
bool I2SAudioMicrophone::start_driver_() {
if (!this->parent_->try_lock()) {
return; // Waiting for another i2s to return lock
return false; // Waiting for another i2s to return lock
}
esp_err_t err;
uint8_t channel_count = 1;
#ifdef USE_I2S_LEGACY
uint8_t bits_per_sample = this->bits_per_sample_;
if (this->channel_ == I2S_CHANNEL_FMT_RIGHT_LEFT) {
channel_count = 2;
}
#else
if (this->slot_bit_width_ == I2S_SLOT_BIT_WIDTH_AUTO) {
this->slot_bit_width_ = I2S_SLOT_BIT_WIDTH_16BIT;
}
uint8_t bits_per_sample = this->slot_bit_width_;
if (this->slot_mode_ == I2S_SLOT_MODE_STEREO) {
channel_count = 2;
}
#endif
#ifdef USE_ESP32_VARIANT_ESP32
// ESP32 reads audio aligned to a multiple of 2 bytes. For example, if configured for 24 bits per sample, then it will
// produce 32 bits per sample, where the actual data is in the most significant bits. Other ESP32 variants produce 24
// bits per sample in this situation.
if (bits_per_sample < 16) {
bits_per_sample = 16;
} else if ((bits_per_sample > 16) && (bits_per_sample <= 32)) {
bits_per_sample = 32;
}
#endif
#ifdef USE_I2S_LEGACY
i2s_driver_config_t config = {
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX),
.sample_rate = this->sample_rate_,
@@ -55,16 +123,14 @@ void I2SAudioMicrophone::start_() {
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = 256,
.dma_buf_len = 240, // Must be divisible by 3 to support 24 bits per sample on old driver and newer variants
.use_apll = this->use_apll_,
.tx_desc_auto_clear = false,
.fixed_mclk = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
.mclk_multiple = this->mclk_multiple_,
.bits_per_chan = this->bits_per_channel_,
};
esp_err_t err;
#if SOC_I2S_SUPPORTS_ADC
if (this->adc_) {
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
@@ -72,20 +138,20 @@ void I2SAudioMicrophone::start_() {
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
this->status_set_error();
return;
return false;
}
err = i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error setting ADC mode: %s", esp_err_to_name(err));
this->status_set_error();
return;
return false;
}
err = i2s_adc_enable(this->parent_->get_port());
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error enabling ADC: %s", esp_err_to_name(err));
this->status_set_error();
return;
return false;
}
} else
@@ -98,7 +164,7 @@ void I2SAudioMicrophone::start_() {
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
this->status_set_error();
return;
return false;
}
i2s_pin_config_t pin_config = this->parent_->get_pin_config();
@@ -108,26 +174,126 @@ void I2SAudioMicrophone::start_() {
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error setting I2S pin: %s", esp_err_to_name(err));
this->status_set_error();
return;
return false;
}
}
this->state_ = microphone::STATE_RUNNING;
this->high_freq_.start();
#else
i2s_chan_config_t chan_cfg = {
.id = this->parent_->get_port(),
.role = this->i2s_role_,
.dma_desc_num = 4,
.dma_frame_num = 256,
.auto_clear = false,
};
/* Allocate a new RX channel and get the handle of this channel */
err = i2s_new_channel(&chan_cfg, NULL, &this->rx_handle_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error creating new I2S channel: %s", esp_err_to_name(err));
this->status_set_error();
return false;
}
i2s_clock_src_t clk_src = I2S_CLK_SRC_DEFAULT;
#ifdef I2S_CLK_SRC_APLL
if (this->use_apll_) {
clk_src = I2S_CLK_SRC_APLL;
}
#endif
i2s_std_gpio_config_t pin_config = this->parent_->get_pin_config();
#if SOC_I2S_SUPPORTS_PDM_RX
if (this->pdm_) {
bits_per_sample = 16; // PDM mics are always 16 bits per sample with the IDF 5 driver
i2s_pdm_rx_clk_config_t clk_cfg = {
.sample_rate_hz = this->sample_rate_,
.clk_src = clk_src,
.mclk_multiple = this->mclk_multiple_,
.dn_sample_mode = I2S_PDM_DSR_8S,
};
i2s_pdm_rx_slot_config_t slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, this->slot_mode_);
switch (this->std_slot_mask_) {
case I2S_STD_SLOT_LEFT:
slot_cfg.slot_mask = I2S_PDM_SLOT_LEFT;
break;
case I2S_STD_SLOT_RIGHT:
slot_cfg.slot_mask = I2S_PDM_SLOT_RIGHT;
break;
case I2S_STD_SLOT_BOTH:
slot_cfg.slot_mask = I2S_PDM_SLOT_BOTH;
break;
}
/* Init the channel into PDM RX mode */
i2s_pdm_rx_config_t pdm_rx_cfg = {
.clk_cfg = clk_cfg,
.slot_cfg = slot_cfg,
.gpio_cfg =
{
.clk = pin_config.ws,
.din = this->din_pin_,
.invert_flags =
{
.clk_inv = pin_config.invert_flags.ws_inv,
},
},
};
err = i2s_channel_init_pdm_rx_mode(this->rx_handle_, &pdm_rx_cfg);
} else
#endif
{
i2s_std_clk_config_t clk_cfg = {
.sample_rate_hz = this->sample_rate_,
.clk_src = clk_src,
.mclk_multiple = this->mclk_multiple_,
};
i2s_std_slot_config_t std_slot_cfg =
I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t) this->slot_bit_width_, this->slot_mode_);
std_slot_cfg.slot_bit_width = this->slot_bit_width_;
std_slot_cfg.slot_mask = this->std_slot_mask_;
pin_config.din = this->din_pin_;
i2s_std_config_t std_cfg = {
.clk_cfg = clk_cfg,
.slot_cfg = std_slot_cfg,
.gpio_cfg = pin_config,
};
/* Initialize the channel */
err = i2s_channel_init_std_mode(this->rx_handle_, &std_cfg);
}
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error initializing I2S channel: %s", esp_err_to_name(err));
this->status_set_error();
return false;
}
/* Before reading data, start the RX channel first */
i2s_channel_enable(this->rx_handle_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error enabling I2S Microphone: %s", esp_err_to_name(err));
this->status_set_error();
return false;
}
#endif
this->audio_stream_info_ = audio::AudioStreamInfo(bits_per_sample, channel_count, this->sample_rate_);
this->status_clear_error();
return true;
}
void I2SAudioMicrophone::stop() {
if (this->state_ == microphone::STATE_STOPPED || this->is_failed())
return;
if (this->state_ == microphone::STATE_STARTING) {
this->state_ = microphone::STATE_STOPPED;
return;
}
this->state_ = microphone::STATE_STOPPING;
xSemaphoreGive(this->active_listeners_semaphore_);
}
void I2SAudioMicrophone::stop_() {
void I2SAudioMicrophone::stop_driver_() {
esp_err_t err;
#ifdef USE_I2S_LEGACY
#if SOC_I2S_SUPPORTS_ADC
if (this->adc_) {
err = i2s_adc_disable(this->parent_->get_port());
@@ -150,68 +316,155 @@ void I2SAudioMicrophone::stop_() {
this->status_set_error();
return;
}
#else
/* Have to stop the channel before deleting it */
err = i2s_channel_disable(this->rx_handle_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error stopping I2S microphone: %s", esp_err_to_name(err));
this->status_set_error();
return;
}
/* If the handle is not needed any more, delete it to release the channel resources */
err = i2s_del_channel(this->rx_handle_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error deleting I2S channel: %s", esp_err_to_name(err));
this->status_set_error();
return;
}
#endif
this->parent_->unlock();
this->state_ = microphone::STATE_STOPPED;
this->high_freq_.stop();
this->status_clear_error();
}
size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
void I2SAudioMicrophone::mic_task(void *params) {
I2SAudioMicrophone *this_microphone = (I2SAudioMicrophone *) params;
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STARTING);
uint8_t start_counter = 0;
bool started = this_microphone->start_driver_();
while (!started && start_counter < 10) {
// Attempt to load the driver again in 100 ms. Doesn't slow down main loop since its in a task.
vTaskDelay(pdMS_TO_TICKS(100));
++start_counter;
started = this_microphone->start_driver_();
}
if (started) {
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
const size_t bytes_to_read = this_microphone->audio_stream_info_.ms_to_bytes(READ_DURATION_MS);
std::vector<uint8_t> samples;
samples.reserve(bytes_to_read);
while (!(xEventGroupGetBits(this_microphone->event_group_) & COMMAND_STOP)) {
if (this_microphone->data_callbacks_.size() > 0) {
samples.resize(bytes_to_read);
size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * pdMS_TO_TICKS(READ_DURATION_MS));
samples.resize(bytes_read);
this_microphone->data_callbacks_.call(samples);
} else {
delay(READ_DURATION_MS);
}
}
}
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPING);
this_microphone->stop_driver_();
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPED);
while (true) {
// Continuously delay until the loop method delete the task
delay(10);
}
}
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
size_t bytes_read = 0;
esp_err_t err = i2s_read(this->parent_->get_port(), buf, len, &bytes_read, (100 / portTICK_PERIOD_MS));
if (err != ESP_OK) {
#ifdef USE_I2S_LEGACY
esp_err_t err = i2s_read(this->parent_->get_port(), buf, len, &bytes_read, ticks_to_wait);
#else
// i2s_channel_read expects the timeout value in ms, not ticks
esp_err_t err = i2s_channel_read(this->rx_handle_, buf, len, &bytes_read, pdTICKS_TO_MS(ticks_to_wait));
#endif
if ((err != ESP_OK) && ((err != ESP_ERR_TIMEOUT) || (ticks_to_wait != 0))) {
// Ignore ESP_ERR_TIMEOUT if ticks_to_wait = 0, as it will read the data on the next call
ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err));
this->status_set_warning();
return 0;
}
if (bytes_read == 0) {
if ((bytes_read == 0) && (ticks_to_wait > 0)) {
this->status_set_warning();
return 0;
}
this->status_clear_warning();
// ESP-IDF I2S implementation right-extends 8-bit data to 16 bits,
// and 24-bit data to 32 bits.
switch (this->bits_per_sample_) {
case I2S_BITS_PER_SAMPLE_8BIT:
case I2S_BITS_PER_SAMPLE_16BIT:
return bytes_read;
case I2S_BITS_PER_SAMPLE_24BIT:
case I2S_BITS_PER_SAMPLE_32BIT: {
size_t samples_read = bytes_read / sizeof(int32_t);
for (size_t i = 0; i < samples_read; i++) {
int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14;
buf[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX);
}
return samples_read * sizeof(int16_t);
#if defined(USE_ESP32_VARIANT_ESP32) and not defined(USE_I2S_LEGACY)
// For ESP32 8/16 bit standard mono mode samples need to be switched.
if (this->slot_mode_ == I2S_SLOT_MODE_MONO && this->slot_bit_width_ <= 16 && !this->pdm_) {
size_t samples_read = bytes_read / sizeof(int16_t);
for (int i = 0; i < samples_read; i += 2) {
int16_t tmp = buf[i];
buf[i] = buf[i + 1];
buf[i + 1] = tmp;
}
default:
ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
return 0;
}
}
void I2SAudioMicrophone::read_() {
std::vector<int16_t> samples;
samples.resize(BUFFER_SIZE);
size_t bytes_read = this->read(samples.data(), BUFFER_SIZE / sizeof(int16_t));
samples.resize(bytes_read / sizeof(int16_t));
this->data_callbacks_.call(samples);
#endif
return bytes_read;
}
void I2SAudioMicrophone::loop() {
uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
if (event_group_bits & MicrophoneEventGroupBits::TASK_STARTING) {
ESP_LOGD(TAG, "Task has started, attempting to setup I2S audio driver");
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STARTING);
}
if (event_group_bits & MicrophoneEventGroupBits::TASK_RUNNING) {
ESP_LOGD(TAG, "Task is running and reading data");
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
this->state_ = microphone::STATE_RUNNING;
}
if (event_group_bits & MicrophoneEventGroupBits::TASK_STOPPING) {
ESP_LOGD(TAG, "Task is stopping, attempting to unload the I2S audio driver");
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STOPPING);
}
if ((event_group_bits & MicrophoneEventGroupBits::TASK_STOPPED)) {
ESP_LOGD(TAG, "Task is finished, freeing resources");
vTaskDelete(this->task_handle_);
this->task_handle_ = nullptr;
xEventGroupClearBits(this->event_group_, ALL_BITS);
this->state_ = microphone::STATE_STOPPED;
}
if ((uxSemaphoreGetCount(this->active_listeners_semaphore_) < MAX_LISTENERS) &&
(this->state_ == microphone::STATE_STOPPED)) {
this->state_ = microphone::STATE_STARTING;
}
if ((uxSemaphoreGetCount(this->active_listeners_semaphore_) == MAX_LISTENERS) &&
(this->state_ == microphone::STATE_RUNNING)) {
this->state_ = microphone::STATE_STOPPING;
}
switch (this->state_) {
case microphone::STATE_STOPPED:
break;
case microphone::STATE_STARTING:
this->start_();
break;
case microphone::STATE_RUNNING:
if (this->data_callbacks_.size() > 0) {
this->read_();
if ((this->task_handle_ == nullptr) && !this->status_has_error()) {
xTaskCreate(I2SAudioMicrophone::mic_task, "mic_task", TASK_STACK_SIZE, (void *) this, TASK_PRIORITY,
&this->task_handle_);
if (this->task_handle_ == nullptr) {
this->status_momentary_error("Task failed to start, attempting again in 1 second", 1000);
}
}
break;
case microphone::STATE_RUNNING:
break;
case microphone::STATE_STOPPING:
this->stop_();
xEventGroupSetBits(this->event_group_, MicrophoneEventGroupBits::COMMAND_STOP);
break;
case microphone::STATE_STOPPED:
break;
}
}

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