esphome 2025.4.2__py3-none-any.whl → 2025.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. esphome/__main__.py +16 -14
  2. esphome/components/ac_dimmer/ac_dimmer.cpp +3 -2
  3. esphome/components/adc/__init__.py +51 -34
  4. esphome/components/airthings_wave_base/__init__.py +1 -1
  5. esphome/components/alarm_control_panel/__init__.py +37 -2
  6. esphome/components/am43/cover/__init__.py +4 -5
  7. esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +6 -4
  8. esphome/components/analog_threshold/analog_threshold_binary_sensor.h +4 -5
  9. esphome/components/analog_threshold/binary_sensor.py +10 -8
  10. esphome/components/anova/climate.py +4 -5
  11. esphome/components/api/__init__.py +25 -8
  12. esphome/components/api/api_connection.cpp +416 -662
  13. esphome/components/api/api_connection.h +256 -57
  14. esphome/components/api/api_frame_helper.cpp +232 -177
  15. esphome/components/api/api_frame_helper.h +61 -8
  16. esphome/components/api/api_noise_context.h +13 -4
  17. esphome/components/api/api_pb2.cpp +1422 -1
  18. esphome/components/api/api_pb2.h +255 -1
  19. esphome/components/api/api_pb2_service.cpp +162 -49
  20. esphome/components/api/api_pb2_service.h +90 -51
  21. esphome/components/api/api_pb2_size.h +361 -0
  22. esphome/components/api/api_server.cpp +110 -34
  23. esphome/components/api/api_server.h +8 -0
  24. esphome/components/api/proto.h +86 -17
  25. esphome/components/as7341/as7341.h +1 -1
  26. esphome/components/at581x/at581x.h +4 -4
  27. esphome/components/atm90e32/__init__.py +1 -0
  28. esphome/components/atm90e32/atm90e32.cpp +576 -199
  29. esphome/components/atm90e32/atm90e32.h +128 -31
  30. esphome/components/atm90e32/atm90e32_reg.h +4 -2
  31. esphome/components/atm90e32/button/__init__.py +62 -10
  32. esphome/components/atm90e32/button/atm90e32_button.cpp +63 -4
  33. esphome/components/atm90e32/button/atm90e32_button.h +36 -4
  34. esphome/components/atm90e32/number/__init__.py +130 -0
  35. esphome/components/atm90e32/number/atm90e32_number.h +16 -0
  36. esphome/components/atm90e32/sensor.py +21 -4
  37. esphome/components/atm90e32/text_sensor/__init__.py +48 -0
  38. esphome/components/audio/__init__.py +96 -49
  39. esphome/components/audio/audio.h +48 -0
  40. esphome/components/audio/audio_decoder.cpp +1 -1
  41. esphome/components/audio/audio_resampler.cpp +2 -0
  42. esphome/components/audio/audio_resampler.h +1 -0
  43. esphome/components/ballu/climate.py +2 -9
  44. esphome/components/bang_bang/climate.py +5 -6
  45. esphome/components/bedjet/bedjet_hub.cpp +1 -0
  46. esphome/components/bedjet/climate/__init__.py +3 -8
  47. esphome/components/bedjet/fan/__init__.py +2 -11
  48. esphome/components/binary/fan/__init__.py +13 -16
  49. esphome/components/binary_sensor/__init__.py +13 -10
  50. esphome/components/bl0906/constants.h +16 -16
  51. esphome/components/ble_client/text_sensor/__init__.py +3 -5
  52. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +4 -6
  53. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +136 -21
  54. esphome/components/bluetooth_proxy/bluetooth_proxy.h +7 -0
  55. esphome/components/button/__init__.py +11 -8
  56. esphome/components/canbus/canbus.cpp +3 -0
  57. esphome/components/canbus/canbus.h +16 -0
  58. esphome/components/ccs811/sensor.py +9 -6
  59. esphome/components/climate/__init__.py +35 -2
  60. esphome/components/climate/climate_mode.h +1 -1
  61. esphome/components/climate/climate_traits.h +63 -57
  62. esphome/components/climate_ir/__init__.py +57 -17
  63. esphome/components/climate_ir_lg/climate.py +2 -5
  64. esphome/components/climate_ir_lg/climate_ir_lg.cpp +7 -7
  65. esphome/components/climate_ir_lg/climate_ir_lg.h +1 -1
  66. esphome/components/color/__init__.py +2 -0
  67. esphome/components/const/__init__.py +5 -0
  68. esphome/components/coolix/climate.py +2 -9
  69. esphome/components/copy/cover/__init__.py +10 -9
  70. esphome/components/copy/fan/__init__.py +11 -9
  71. esphome/components/copy/lock/__init__.py +11 -9
  72. esphome/components/copy/text/__init__.py +9 -6
  73. esphome/components/cover/__init__.py +37 -2
  74. esphome/components/cse7766/cse7766.cpp +2 -1
  75. esphome/components/cst226/binary_sensor/__init__.py +28 -0
  76. esphome/components/cst226/binary_sensor/cs226_button.h +22 -0
  77. esphome/components/cst226/binary_sensor/cstt6_button.cpp +19 -0
  78. esphome/components/cst226/touchscreen/cst226_touchscreen.cpp +27 -5
  79. esphome/components/cst226/touchscreen/cst226_touchscreen.h +10 -10
  80. esphome/components/current_based/cover.py +37 -36
  81. esphome/components/current_based/current_based_cover.cpp +2 -1
  82. esphome/components/daikin/climate.py +2 -9
  83. esphome/components/daikin/daikin.cpp +15 -9
  84. esphome/components/daikin/daikin.h +5 -5
  85. esphome/components/daikin_arc/climate.py +2 -7
  86. esphome/components/daikin_brc/climate.py +3 -5
  87. esphome/components/dallas_temp/dallas_temp.cpp +17 -24
  88. esphome/components/dallas_temp/dallas_temp.h +0 -1
  89. esphome/components/daly_bms/daly_bms.cpp +2 -1
  90. esphome/components/debug/debug_component.cpp +6 -1
  91. esphome/components/debug/debug_component.h +8 -0
  92. esphome/components/debug/debug_esp32.cpp +109 -254
  93. esphome/components/debug/sensor.py +14 -0
  94. esphome/components/deep_sleep/deep_sleep_esp32.cpp +13 -1
  95. esphome/components/delonghi/climate.py +2 -9
  96. esphome/components/demo/__init__.py +18 -20
  97. esphome/components/dfrobot_sen0395/switch/__init__.py +21 -22
  98. esphome/components/dps310/sensor.py +6 -6
  99. esphome/components/ee895/sensor.py +9 -9
  100. esphome/components/emmeti/climate.py +2 -9
  101. esphome/components/endstop/cover.py +17 -16
  102. esphome/components/endstop/endstop_cover.cpp +2 -1
  103. esphome/components/ens160_base/__init__.py +12 -9
  104. esphome/components/esp32/__init__.py +60 -3
  105. esphome/components/esp32/core.cpp +11 -5
  106. esphome/components/esp32/gpio.cpp +86 -24
  107. esphome/components/esp32/gpio.py +15 -16
  108. esphome/components/esp32/gpio_esp32.py +1 -2
  109. esphome/components/esp32/gpio_esp32_c2.py +1 -1
  110. esphome/components/esp32/gpio_esp32_c3.py +1 -1
  111. esphome/components/esp32/gpio_esp32_c6.py +1 -1
  112. esphome/components/esp32/gpio_esp32_h2.py +1 -1
  113. esphome/components/esp32_ble/ble.cpp +1 -0
  114. esphome/components/esp32_ble/ble.h +5 -3
  115. esphome/components/esp32_ble/ble_advertising.cpp +2 -1
  116. esphome/components/esp32_ble/ble_advertising.h +1 -0
  117. esphome/components/esp32_ble_server/__init__.py +3 -0
  118. esphome/components/esp32_ble_tracker/__init__.py +7 -1
  119. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +192 -118
  120. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +29 -3
  121. esphome/components/esp32_camera/__init__.py +1 -1
  122. esphome/components/esp32_camera/esp32_camera.cpp +2 -10
  123. esphome/components/esp32_camera/esp32_camera.h +1 -1
  124. esphome/components/esp32_can/esp32_can.cpp +1 -1
  125. esphome/components/esp32_improv/esp32_improv_component.cpp +1 -1
  126. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  127. esphome/components/esp32_rmt_led_strip/led_strip.h +7 -5
  128. esphome/components/esp32_rmt_led_strip/light.py +9 -1
  129. esphome/components/esp32_touch/esp32_touch.cpp +1 -1
  130. esphome/components/esp8266/gpio.cpp +69 -8
  131. esphome/components/ethernet/ethernet_component.cpp +1 -1
  132. esphome/components/event/__init__.py +13 -10
  133. esphome/components/factory_reset/switch/__init__.py +7 -21
  134. esphome/components/fan/__init__.py +52 -5
  135. esphome/components/fastled_base/__init__.py +1 -4
  136. esphome/components/fastled_base/fastled_light.cpp +1 -1
  137. esphome/components/feedback/cover.py +38 -33
  138. esphome/components/feedback/feedback_cover.cpp +2 -1
  139. esphome/components/fujitsu_general/climate.py +2 -9
  140. esphome/components/gcja5/gcja5.cpp +2 -1
  141. esphome/components/gpio/one_wire/gpio_one_wire.cpp +45 -43
  142. esphome/components/gpio/one_wire/gpio_one_wire.h +2 -1
  143. esphome/components/gpio_expander/cached_gpio.h +22 -7
  144. esphome/components/gps/__init__.py +47 -17
  145. esphome/components/gps/gps.cpp +42 -23
  146. esphome/components/gps/gps.h +17 -13
  147. esphome/components/graph/__init__.py +1 -2
  148. esphome/components/gree/climate.py +4 -6
  149. esphome/components/gree/gree.cpp +16 -2
  150. esphome/components/gree/gree.h +2 -2
  151. esphome/components/growatt_solar/growatt_solar.cpp +2 -1
  152. esphome/components/haier/climate.py +37 -34
  153. esphome/components/hbridge/fan/__init__.py +19 -17
  154. esphome/components/he60r/cover.py +4 -5
  155. esphome/components/heatpumpir/climate.py +3 -6
  156. esphome/components/hitachi_ac344/climate.py +2 -9
  157. esphome/components/hitachi_ac424/climate.py +2 -9
  158. esphome/components/hm3301/hm3301.h +1 -1
  159. esphome/components/hte501/sensor.py +6 -6
  160. esphome/components/http_request/__init__.py +39 -6
  161. esphome/components/http_request/http_request.cpp +20 -0
  162. esphome/components/http_request/http_request.h +57 -15
  163. esphome/components/http_request/http_request_arduino.cpp +22 -6
  164. esphome/components/http_request/http_request_arduino.h +4 -3
  165. esphome/components/http_request/http_request_host.cpp +141 -0
  166. esphome/components/http_request/http_request_host.h +37 -0
  167. esphome/components/http_request/http_request_idf.cpp +35 -3
  168. esphome/components/http_request/http_request_idf.h +10 -3
  169. esphome/components/http_request/httplib.h +9691 -0
  170. esphome/components/http_request/update/__init__.py +11 -8
  171. esphome/components/hyt271/sensor.py +6 -6
  172. esphome/components/i2c/i2c.h +4 -0
  173. esphome/components/i2c/i2c_bus_esp_idf.cpp +1 -1
  174. esphome/components/i2s_audio/__init__.py +131 -22
  175. esphome/components/i2s_audio/i2s_audio.h +44 -4
  176. esphome/components/i2s_audio/media_player/__init__.py +19 -9
  177. esphome/components/i2s_audio/microphone/__init__.py +63 -5
  178. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +351 -61
  179. esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +40 -6
  180. esphome/components/i2s_audio/speaker/__init__.py +31 -5
  181. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +155 -19
  182. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +17 -4
  183. esphome/components/ili9xxx/ili9xxx_init.h +1 -1
  184. esphome/components/image/__init__.py +37 -17
  185. esphome/components/image/image.cpp +25 -8
  186. esphome/components/internal_temperature/internal_temperature.cpp +6 -4
  187. esphome/components/key_collector/__init__.py +35 -0
  188. esphome/components/key_collector/key_collector.cpp +8 -0
  189. esphome/components/key_collector/key_collector.h +10 -0
  190. esphome/components/kuntze/kuntze.cpp +2 -1
  191. esphome/components/ld2410/ld2410.h +1 -1
  192. esphome/components/ld2450/ld2450.h +1 -1
  193. esphome/components/light/__init__.py +57 -0
  194. esphome/components/lock/__init__.py +51 -4
  195. esphome/components/lock/automation.h +2 -13
  196. esphome/components/logger/__init__.py +22 -0
  197. esphome/components/logger/logger.cpp +154 -103
  198. esphome/components/logger/logger.h +211 -36
  199. esphome/components/logger/task_log_buffer.cpp +138 -0
  200. esphome/components/logger/task_log_buffer.h +69 -0
  201. esphome/components/lvgl/__init__.py +13 -5
  202. esphome/components/lvgl/automation.py +50 -1
  203. esphome/components/lvgl/defines.py +0 -1
  204. esphome/components/lvgl/lvgl_esphome.cpp +5 -1
  205. esphome/components/lvgl/text/__init__.py +1 -2
  206. esphome/components/mapping/__init__.py +134 -0
  207. esphome/components/matrix_keypad/matrix_keypad.cpp +2 -1
  208. esphome/components/max7219digit/max7219digit.cpp +28 -27
  209. esphome/components/mdns/__init__.py +11 -5
  210. esphome/components/mdns/mdns_component.cpp +11 -5
  211. esphome/components/mdns/mdns_component.h +3 -2
  212. esphome/components/mdns/mdns_esp32.cpp +4 -3
  213. esphome/components/mdns/mdns_esp8266.cpp +4 -2
  214. esphome/components/mdns/mdns_libretiny.cpp +4 -2
  215. esphome/components/mdns/mdns_rp2040.cpp +4 -2
  216. esphome/components/media_player/__init__.py +33 -1
  217. esphome/components/mhz19/sensor.py +11 -7
  218. esphome/components/micro_wake_word/__init__.py +99 -31
  219. esphome/components/micro_wake_word/automation.h +54 -0
  220. esphome/components/micro_wake_word/micro_wake_word.cpp +331 -319
  221. esphome/components/micro_wake_word/micro_wake_word.h +58 -105
  222. esphome/components/micro_wake_word/preprocessor_settings.h +19 -2
  223. esphome/components/micro_wake_word/streaming_model.cpp +158 -41
  224. esphome/components/micro_wake_word/streaming_model.h +85 -13
  225. esphome/components/microphone/__init__.py +139 -9
  226. esphome/components/microphone/automation.h +14 -2
  227. esphome/components/microphone/microphone.cpp +21 -0
  228. esphome/components/microphone/microphone.h +14 -5
  229. esphome/components/microphone/microphone_source.cpp +95 -0
  230. esphome/components/microphone/microphone_source.h +80 -0
  231. esphome/components/mics_4514/sensor.py +25 -14
  232. esphome/components/midea/climate.py +3 -4
  233. esphome/components/midea_ir/climate.py +3 -5
  234. esphome/components/mipi_spi/__init__.py +15 -0
  235. esphome/components/mipi_spi/display.py +474 -0
  236. esphome/components/mipi_spi/mipi_spi.cpp +481 -0
  237. esphome/components/mipi_spi/mipi_spi.h +171 -0
  238. esphome/components/mipi_spi/models/__init__.py +65 -0
  239. esphome/components/mipi_spi/models/amoled.py +72 -0
  240. esphome/components/mipi_spi/models/commands.py +82 -0
  241. esphome/components/mipi_spi/models/cyd.py +10 -0
  242. esphome/components/mipi_spi/models/ili.py +749 -0
  243. esphome/components/mipi_spi/models/jc.py +260 -0
  244. esphome/components/mipi_spi/models/lanbon.py +15 -0
  245. esphome/components/mipi_spi/models/lilygo.py +60 -0
  246. esphome/components/mipi_spi/models/waveshare.py +139 -0
  247. esphome/components/mitsubishi/climate.py +2 -5
  248. esphome/components/mitsubishi/mitsubishi.cpp +9 -9
  249. esphome/components/mixer/speaker/mixer_speaker.cpp +12 -22
  250. esphome/components/mixer/speaker/mixer_speaker.h +1 -3
  251. esphome/components/mlx90393/sensor.py +5 -0
  252. esphome/components/mlx90393/sensor_mlx90393.cpp +195 -13
  253. esphome/components/mlx90393/sensor_mlx90393.h +21 -4
  254. esphome/components/modbus/modbus.cpp +2 -1
  255. esphome/components/mqtt/__init__.py +1 -1
  256. esphome/components/mqtt/mqtt_client.cpp +6 -2
  257. esphome/components/mqtt/mqtt_const.h +4 -0
  258. esphome/components/mqtt/mqtt_fan.cpp +39 -0
  259. esphome/components/mqtt/mqtt_fan.h +2 -0
  260. esphome/components/ms5611/sensor.py +6 -6
  261. esphome/components/ms8607/sensor.py +3 -3
  262. esphome/components/network/__init__.py +1 -1
  263. esphome/components/nextion/base_component.py +17 -16
  264. esphome/components/nextion/display.py +11 -2
  265. esphome/components/nextion/nextion.cpp +39 -1
  266. esphome/components/nextion/nextion.h +50 -0
  267. esphome/components/noblex/climate.py +2 -9
  268. esphome/components/number/__init__.py +12 -9
  269. esphome/components/one_wire/one_wire_bus.cpp +14 -10
  270. esphome/components/one_wire/one_wire_bus.h +14 -8
  271. esphome/components/online_image/bmp_image.cpp +48 -11
  272. esphome/components/online_image/bmp_image.h +2 -0
  273. esphome/components/opentherm/binary_sensor/__init__.py +2 -4
  274. esphome/components/opentherm/number/__init__.py +11 -20
  275. esphome/components/opentherm/sensor/__init__.py +3 -3
  276. esphome/components/opentherm/switch/__init__.py +3 -5
  277. esphome/components/output/lock/__init__.py +11 -9
  278. esphome/components/packages/__init__.py +33 -31
  279. esphome/components/packet_transport/__init__.py +201 -0
  280. esphome/components/packet_transport/binary_sensor.py +19 -0
  281. esphome/components/packet_transport/packet_transport.cpp +534 -0
  282. esphome/components/packet_transport/packet_transport.h +154 -0
  283. esphome/components/packet_transport/sensor.py +19 -0
  284. esphome/components/pca9685/pca9685_output.cpp +2 -1
  285. esphome/components/pid/climate.py +2 -4
  286. esphome/components/pm2005/__init__.py +1 -0
  287. esphome/components/pm2005/pm2005.cpp +123 -0
  288. esphome/components/pm2005/pm2005.h +46 -0
  289. esphome/components/pm2005/sensor.py +86 -0
  290. esphome/components/pmsa003i/pmsa003i.cpp +43 -16
  291. esphome/components/pmsa003i/pmsa003i.h +25 -25
  292. esphome/components/pmsx003/pmsx003.cpp +195 -230
  293. esphome/components/pmsx003/pmsx003.h +51 -33
  294. esphome/components/pmsx003/sensor.py +21 -11
  295. esphome/components/pn7150/pn7150.h +2 -2
  296. esphome/components/pn7160/pn7160.h +2 -2
  297. esphome/components/prometheus/prometheus_handler.cpp +174 -0
  298. esphome/components/prometheus/prometheus_handler.h +17 -0
  299. esphome/components/psram/__init__.py +7 -5
  300. esphome/components/pulse_meter/pulse_meter_sensor.cpp +32 -12
  301. esphome/components/pulse_meter/pulse_meter_sensor.h +5 -5
  302. esphome/components/pzem004t/pzem004t.cpp +2 -1
  303. esphome/components/qspi_dbi/__init__.py +0 -1
  304. esphome/components/qspi_dbi/display.py +2 -1
  305. esphome/components/qspi_dbi/models.py +1 -2
  306. esphome/components/remote_base/__init__.py +91 -0
  307. esphome/components/remote_base/beo4_protocol.cpp +153 -0
  308. esphome/components/remote_base/beo4_protocol.h +43 -0
  309. esphome/components/remote_base/gobox_protocol.cpp +131 -0
  310. esphome/components/remote_base/gobox_protocol.h +54 -0
  311. esphome/components/remote_receiver/remote_receiver_esp32.cpp +16 -9
  312. esphome/components/resampler/speaker/resampler_speaker.cpp +12 -10
  313. esphome/components/resampler/speaker/resampler_speaker.h +1 -1
  314. esphome/components/rf_bridge/rf_bridge.cpp +2 -1
  315. esphome/components/scd30/sensor.py +2 -3
  316. esphome/components/scd4x/sensor.py +4 -5
  317. esphome/components/sdp3x/sensor.py +2 -1
  318. esphome/components/sds011/sds011.cpp +2 -1
  319. esphome/components/select/__init__.py +19 -20
  320. esphome/components/sen5x/sen5x.cpp +55 -36
  321. esphome/components/sen5x/sensor.py +1 -1
  322. esphome/components/senseair/sensor.py +3 -3
  323. esphome/components/sensor/__init__.py +158 -14
  324. esphome/components/sensor/filter.cpp +23 -0
  325. esphome/components/sensor/filter.h +22 -0
  326. esphome/components/sgp30/sensor.py +14 -16
  327. esphome/components/sgp4x/sensor.py +1 -1
  328. esphome/components/sht4x/sht4x.cpp +43 -22
  329. esphome/components/sht4x/sht4x.h +1 -1
  330. esphome/components/shtcx/sensor.py +6 -6
  331. esphome/components/slow_pwm/slow_pwm_output.cpp +2 -1
  332. esphome/components/sml/text_sensor/__init__.py +4 -6
  333. esphome/components/sound_level/__init__.py +0 -0
  334. esphome/components/sound_level/sensor.py +97 -0
  335. esphome/components/sound_level/sound_level.cpp +194 -0
  336. esphome/components/sound_level/sound_level.h +73 -0
  337. esphome/components/speaker/media_player/__init__.py +4 -8
  338. esphome/components/speaker/media_player/speaker_media_player.cpp +0 -18
  339. esphome/components/speaker/media_player/speaker_media_player.h +0 -11
  340. esphome/components/speaker/speaker.h +4 -7
  341. esphome/components/speed/fan/__init__.py +17 -16
  342. esphome/components/spi/spi.h +11 -1
  343. esphome/components/sprinkler/__init__.py +18 -19
  344. esphome/components/sprinkler/sprinkler.cpp +6 -5
  345. esphome/components/switch/__init__.py +32 -42
  346. esphome/components/syslog/__init__.py +41 -0
  347. esphome/components/syslog/esphome_syslog.cpp +49 -0
  348. esphome/components/syslog/esphome_syslog.h +27 -0
  349. esphome/components/t6615/sensor.py +3 -3
  350. esphome/components/t6615/t6615.cpp +2 -1
  351. esphome/components/tca9555/tca9555.cpp +11 -6
  352. esphome/components/tcl112/climate.py +2 -9
  353. esphome/components/template/alarm_control_panel/__init__.py +7 -6
  354. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +21 -17
  355. esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +2 -1
  356. esphome/components/template/cover/__init__.py +27 -21
  357. esphome/components/template/fan/__init__.py +14 -12
  358. esphome/components/template/lock/__init__.py +20 -25
  359. esphome/components/template/lock/automation.h +18 -0
  360. esphome/components/template/text/__init__.py +4 -3
  361. esphome/components/template/valve/__init__.py +32 -21
  362. esphome/components/template/valve/automation.h +24 -0
  363. esphome/components/text/__init__.py +32 -1
  364. esphome/components/text_sensor/__init__.py +24 -29
  365. esphome/components/thermostat/climate.py +5 -5
  366. esphome/components/time_based/cover.py +17 -16
  367. esphome/components/time_based/time_based_cover.cpp +2 -1
  368. esphome/components/tm1638/switch/__init__.py +10 -7
  369. esphome/components/tormatic/cover.py +4 -5
  370. esphome/components/toshiba/climate.py +3 -5
  371. esphome/components/touchscreen/touchscreen.cpp +3 -1
  372. esphome/components/tuya/climate/__init__.py +5 -6
  373. esphome/components/tuya/cover/__init__.py +6 -11
  374. esphome/components/tuya/select/__init__.py +15 -5
  375. esphome/components/tuya/select/tuya_select.cpp +6 -1
  376. esphome/components/tuya/select/tuya_select.h +5 -1
  377. esphome/components/uart/packet_transport/__init__.py +20 -0
  378. esphome/components/uart/packet_transport/uart_transport.cpp +88 -0
  379. esphome/components/uart/packet_transport/uart_transport.h +41 -0
  380. esphome/components/uart/switch/uart_switch.cpp +2 -1
  381. esphome/components/udp/__init__.py +126 -128
  382. esphome/components/udp/automation.h +40 -0
  383. esphome/components/udp/binary_sensor.py +3 -25
  384. esphome/components/udp/packet_transport/__init__.py +29 -0
  385. esphome/components/udp/packet_transport/udp_transport.cpp +36 -0
  386. esphome/components/udp/packet_transport/udp_transport.h +28 -0
  387. esphome/components/udp/sensor.py +3 -25
  388. esphome/components/udp/udp_component.cpp +26 -470
  389. esphome/components/udp/udp_component.h +21 -128
  390. esphome/components/update/__init__.py +31 -1
  391. esphome/components/uponor_smatrix/climate/__init__.py +4 -9
  392. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +2 -1
  393. esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
  394. esphome/components/uptime/text_sensor/__init__.py +47 -7
  395. esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +12 -7
  396. esphome/components/uptime/text_sensor/uptime_text_sensor.h +19 -0
  397. esphome/components/valve/__init__.py +34 -3
  398. esphome/components/valve/automation.h +1 -19
  399. esphome/components/vl53l0x/sensor.py +11 -0
  400. esphome/components/vl53l0x/vl53l0x_sensor.cpp +5 -1
  401. esphome/components/vl53l0x/vl53l0x_sensor.h +2 -1
  402. esphome/components/voice_assistant/__init__.py +36 -10
  403. esphome/components/voice_assistant/voice_assistant.cpp +170 -144
  404. esphome/components/voice_assistant/voice_assistant.h +26 -25
  405. esphome/components/waveshare_epaper/display.py +6 -0
  406. esphome/components/waveshare_epaper/waveshare_epaper.cpp +439 -37
  407. esphome/components/waveshare_epaper/waveshare_epaper.h +60 -11
  408. esphome/components/weikai/weikai.cpp +0 -52
  409. esphome/components/whirlpool/climate.py +3 -5
  410. esphome/components/whynter/climate.py +3 -5
  411. esphome/components/xpt2046/touchscreen/xpt2046.cpp +1 -1
  412. esphome/components/yashima/climate.py +6 -6
  413. esphome/components/zhlt01/climate.py +2 -7
  414. esphome/config.py +13 -13
  415. esphome/config_validation.py +38 -58
  416. esphome/const.py +15 -1
  417. esphome/core/__init__.py +2 -0
  418. esphome/core/application.cpp +27 -10
  419. esphome/core/application.h +9 -1
  420. esphome/core/automation.h +4 -3
  421. esphome/core/component.cpp +28 -7
  422. esphome/core/component.h +10 -1
  423. esphome/core/defines.h +23 -17
  424. esphome/core/doxygen.h +13 -0
  425. esphome/core/macros.h +4 -0
  426. esphome/core/scheduler.cpp +7 -1
  427. esphome/cpp_generator.py +6 -2
  428. esphome/dashboard/web_server.py +3 -3
  429. esphome/helpers.py +39 -0
  430. esphome/loader.py +4 -0
  431. esphome/log.py +15 -19
  432. esphome/mqtt.py +23 -10
  433. esphome/platformio_api.py +1 -1
  434. esphome/schema_extractors.py +0 -1
  435. esphome/voluptuous_schema.py +3 -1
  436. esphome/vscode.py +15 -0
  437. esphome/wizard.py +47 -37
  438. esphome/zeroconf.py +7 -3
  439. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/METADATA +10 -11
  440. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/RECORD +444 -383
  441. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/WHEEL +1 -1
  442. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/entry_points.txt +0 -0
  443. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/licenses/LICENSE +0 -0
  444. {esphome-2025.4.2.dist-info → esphome-2025.5.0.dist-info}/top_level.txt +0 -0
@@ -2,12 +2,19 @@
2
2
 
3
3
  #include <cstdarg>
4
4
  #include <map>
5
+ #ifdef USE_ESP32
6
+ #include <pthread.h>
7
+ #endif
5
8
  #include "esphome/core/automation.h"
6
9
  #include "esphome/core/component.h"
7
10
  #include "esphome/core/defines.h"
8
11
  #include "esphome/core/helpers.h"
9
12
  #include "esphome/core/log.h"
10
13
 
14
+ #ifdef USE_ESPHOME_TASK_LOG_BUFFER
15
+ #include "task_log_buffer.h"
16
+ #endif
17
+
11
18
  #ifdef USE_ARDUINO
12
19
  #if defined(USE_ESP8266) || defined(USE_ESP32)
13
20
  #include <HardwareSerial.h>
@@ -26,6 +33,29 @@ namespace esphome {
26
33
 
27
34
  namespace logger {
28
35
 
36
+ // Color and letter constants for log levels
37
+ static const char *const LOG_LEVEL_COLORS[] = {
38
+ "", // NONE
39
+ ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), // ERROR
40
+ ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_YELLOW), // WARNING
41
+ ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GREEN), // INFO
42
+ ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_MAGENTA), // CONFIG
43
+ ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_CYAN), // DEBUG
44
+ ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GRAY), // VERBOSE
45
+ ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_WHITE), // VERY_VERBOSE
46
+ };
47
+
48
+ static const char *const LOG_LEVEL_LETTERS[] = {
49
+ "", // NONE
50
+ "E", // ERROR
51
+ "W", // WARNING
52
+ "I", // INFO
53
+ "C", // CONFIG
54
+ "D", // DEBUG
55
+ "V", // VERBOSE
56
+ "VV", // VERY_VERBOSE
57
+ };
58
+
29
59
  #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
30
60
  /** Enum for logging UART selection
31
61
  *
@@ -54,10 +84,30 @@ enum UARTSelection {
54
84
  };
55
85
  #endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY
56
86
 
87
+ /**
88
+ * @brief Logger component for all ESPHome logging.
89
+ *
90
+ * This class implements a multi-platform logging system with protection against recursion.
91
+ *
92
+ * Recursion Protection Strategy:
93
+ * - On ESP32: Uses task-specific recursion guards
94
+ * * Main task: Uses a dedicated boolean member variable for efficiency
95
+ * * Other tasks: Uses pthread TLS with a dynamically allocated key for task-specific state
96
+ * - On other platforms: Uses a simple global recursion guard
97
+ *
98
+ * We use pthread TLS via pthread_key_create to create a unique key for storing
99
+ * task-specific recursion state, which:
100
+ * 1. Efficiently handles multiple tasks without locks or mutexes
101
+ * 2. Works with ESP-IDF's pthread implementation that uses a linked list for TLS variables
102
+ * 3. Avoids the limitations of the fixed FreeRTOS task local storage slots
103
+ */
57
104
  class Logger : public Component {
58
105
  public:
59
106
  explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
60
- #ifdef USE_LOGGER_USB_CDC
107
+ #ifdef USE_ESPHOME_TASK_LOG_BUFFER
108
+ void init_log_buffer(size_t total_buffer_size);
109
+ #endif
110
+ #if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32)
61
111
  void loop() override;
62
112
  #endif
63
113
  /// Manually set the baud rate for serial, set to 0 to disable.
@@ -69,6 +119,9 @@ class Logger : public Component {
69
119
  #ifdef USE_ESP_IDF
70
120
  uart_port_t get_uart_num() const { return uart_num_; }
71
121
  #endif
122
+ #ifdef USE_ESP32
123
+ void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
124
+ #endif
72
125
  #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
73
126
  void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
74
127
  /// Get the UART used by the logger.
@@ -87,7 +140,7 @@ class Logger : public Component {
87
140
  void pre_setup();
88
141
  void dump_config() override;
89
142
 
90
- int level_for(const char *tag);
143
+ inline int level_for(const char *tag);
91
144
 
92
145
  /// Register a callback that will be called for every log message sent
93
146
  void add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback);
@@ -103,46 +156,66 @@ class Logger : public Component {
103
156
  #endif
104
157
 
105
158
  protected:
106
- void write_header_(int level, const char *tag, int line);
107
- void write_footer_();
108
- void log_message_(int level, const char *tag, int offset = 0);
159
+ void call_log_callbacks_(int level, const char *tag, const char *msg);
109
160
  void write_msg_(const char *msg);
110
161
 
111
- inline bool is_buffer_full_() const { return this->tx_buffer_at_ >= this->tx_buffer_size_; }
112
- inline int buffer_remaining_capacity_() const { return this->tx_buffer_size_ - this->tx_buffer_at_; }
113
- inline void reset_buffer_() { this->tx_buffer_at_ = 0; }
114
- inline void set_null_terminator_() {
115
- // does not increment buffer_at
116
- this->tx_buffer_[this->tx_buffer_at_] = '\0';
117
- }
118
- inline void write_to_buffer_(char value) {
119
- if (!this->is_buffer_full_())
120
- this->tx_buffer_[this->tx_buffer_at_++] = value;
162
+ // Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
163
+ // It's the caller's responsibility to initialize buffer_at (typically to 0)
164
+ inline void HOT format_log_to_buffer_with_terminator_(int level, const char *tag, int line, const char *format,
165
+ va_list args, char *buffer, int *buffer_at, int buffer_size) {
166
+ #if defined(USE_ESP32) || defined(USE_LIBRETINY)
167
+ this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size);
168
+ #else
169
+ this->write_header_to_buffer_(level, tag, line, nullptr, buffer, buffer_at, buffer_size);
170
+ #endif
171
+ this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, args);
172
+ this->write_footer_to_buffer_(buffer, buffer_at, buffer_size);
173
+
174
+ // Always ensure the buffer has a null terminator, even if we need to
175
+ // overwrite the last character of the actual content
176
+ if (*buffer_at >= buffer_size) {
177
+ buffer[buffer_size - 1] = '\0'; // Truncate and ensure null termination
178
+ } else {
179
+ buffer[*buffer_at] = '\0'; // Normal case, append null terminator
180
+ }
121
181
  }
122
- inline void write_to_buffer_(const char *value, int length) {
123
- for (int i = 0; i < length && !this->is_buffer_full_(); i++) {
124
- this->tx_buffer_[this->tx_buffer_at_++] = value[i];
182
+
183
+ // Helper to format and send a log message to both console and callbacks
184
+ inline void HOT log_message_to_buffer_and_send_(int level, const char *tag, int line, const char *format,
185
+ va_list args) {
186
+ // Format to tx_buffer and prepare for output
187
+ this->tx_buffer_at_ = 0; // Initialize buffer position
188
+ this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_,
189
+ this->tx_buffer_size_);
190
+
191
+ if (this->baud_rate_ > 0) {
192
+ this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
125
193
  }
194
+ this->call_log_callbacks_(level, tag, this->tx_buffer_);
126
195
  }
127
- inline void vprintf_to_buffer_(const char *format, va_list args) {
128
- if (this->is_buffer_full_())
129
- return;
130
- int remaining = this->buffer_remaining_capacity_();
131
- int ret = vsnprintf(this->tx_buffer_ + this->tx_buffer_at_, remaining, format, args);
132
- if (ret < 0) {
133
- // Encoding error, do not increment buffer_at
196
+
197
+ // Write the body of the log message to the buffer
198
+ inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, int *buffer_at, int buffer_size) {
199
+ // Calculate available space
200
+ const int available = buffer_size - *buffer_at;
201
+ if (available <= 0)
134
202
  return;
203
+
204
+ // Determine copy length (minimum of remaining capacity and string length)
205
+ const size_t copy_len = (length < static_cast<size_t>(available)) ? length : available;
206
+
207
+ // Copy the data
208
+ if (copy_len > 0) {
209
+ memcpy(buffer + *buffer_at, value, copy_len);
210
+ *buffer_at += copy_len;
135
211
  }
136
- if (ret >= remaining) {
137
- // output was too long, truncated
138
- ret = remaining;
139
- }
140
- this->tx_buffer_at_ += ret;
141
212
  }
142
- inline void printf_to_buffer_(const char *format, ...) {
213
+
214
+ // Format string to explicit buffer with varargs
215
+ inline void printf_to_buffer_(const char *format, char *buffer, int *buffer_at, int buffer_size, ...) {
143
216
  va_list arg;
144
- va_start(arg, format);
145
- this->vprintf_to_buffer_(format, arg);
217
+ va_start(arg, buffer_size);
218
+ this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg);
146
219
  va_end(arg);
147
220
  }
148
221
 
@@ -169,10 +242,112 @@ class Logger : public Component {
169
242
  std::map<std::string, int> log_levels_{};
170
243
  CallbackManager<void(int, const char *, const char *)> log_callback_{};
171
244
  int current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
172
- /// Prevents recursive log calls, if true a log message is already being processed.
173
- bool recursion_guard_ = false;
174
- void *main_task_ = nullptr;
245
+ #ifdef USE_ESPHOME_TASK_LOG_BUFFER
246
+ std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
247
+ #endif
248
+ #ifdef USE_ESP32
249
+ // Task-specific recursion guards:
250
+ // - Main task uses a dedicated member variable for efficiency
251
+ // - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
252
+ bool main_task_recursion_guard_{false};
253
+ pthread_key_t log_recursion_key_;
254
+ #else
255
+ bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
256
+ #endif
175
257
  CallbackManager<void(int)> level_callback_{};
258
+
259
+ #if defined(USE_ESP32) || defined(USE_LIBRETINY)
260
+ void *main_task_ = nullptr; // Only used for thread name identification
261
+ const char *HOT get_thread_name_() {
262
+ TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
263
+ if (current_task == main_task_) {
264
+ return nullptr; // Main task
265
+ } else {
266
+ #if defined(USE_ESP32)
267
+ return pcTaskGetName(current_task);
268
+ #elif defined(USE_LIBRETINY)
269
+ return pcTaskGetTaskName(current_task);
270
+ #endif
271
+ }
272
+ }
273
+ #endif
274
+
275
+ #ifdef USE_ESP32
276
+ inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) {
277
+ if (is_main_task) {
278
+ const bool was_recursive = main_task_recursion_guard_;
279
+ main_task_recursion_guard_ = true;
280
+ return was_recursive;
281
+ }
282
+
283
+ intptr_t current = (intptr_t) pthread_getspecific(log_recursion_key_);
284
+ if (current != 0)
285
+ return true;
286
+
287
+ pthread_setspecific(log_recursion_key_, (void *) 1);
288
+ return false;
289
+ }
290
+
291
+ inline void HOT reset_task_log_recursion_(bool is_main_task) {
292
+ if (is_main_task) {
293
+ main_task_recursion_guard_ = false;
294
+ return;
295
+ }
296
+
297
+ pthread_setspecific(log_recursion_key_, (void *) 0);
298
+ }
299
+ #endif
300
+
301
+ inline void HOT write_header_to_buffer_(int level, const char *tag, int line, const char *thread_name, char *buffer,
302
+ int *buffer_at, int buffer_size) {
303
+ // Format header
304
+ if (level < 0)
305
+ level = 0;
306
+ if (level > 7)
307
+ level = 7;
308
+
309
+ const char *color = esphome::logger::LOG_LEVEL_COLORS[level];
310
+ const char *letter = esphome::logger::LOG_LEVEL_LETTERS[level];
311
+
312
+ #if defined(USE_ESP32) || defined(USE_LIBRETINY)
313
+ if (thread_name != nullptr) {
314
+ // Non-main task with thread name
315
+ this->printf_to_buffer_("%s[%s][%s:%03u]%s[%s]%s: ", buffer, buffer_at, buffer_size, color, letter, tag, line,
316
+ ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), thread_name, color);
317
+ return;
318
+ }
319
+ #endif
320
+ // Main task or non ESP32/LibreTiny platform
321
+ this->printf_to_buffer_("%s[%s][%s:%03u]: ", buffer, buffer_at, buffer_size, color, letter, tag, line);
322
+ }
323
+
324
+ inline void HOT format_body_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format,
325
+ va_list args) {
326
+ // Get remaining capacity in the buffer
327
+ const int remaining = buffer_size - *buffer_at;
328
+ if (remaining <= 0)
329
+ return;
330
+
331
+ const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args);
332
+
333
+ if (ret < 0) {
334
+ return; // Encoding error, do not increment buffer_at
335
+ }
336
+
337
+ // Update buffer_at with the formatted length (handle truncation)
338
+ int formatted_len = (ret >= remaining) ? remaining : ret;
339
+ *buffer_at += formatted_len;
340
+
341
+ // Remove all trailing newlines right after formatting
342
+ while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n') {
343
+ (*buffer_at)--;
344
+ }
345
+ }
346
+
347
+ inline void HOT write_footer_to_buffer_(char *buffer, int *buffer_at, int buffer_size) {
348
+ static const int RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
349
+ this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
350
+ }
176
351
  };
177
352
  extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
178
353
 
@@ -0,0 +1,138 @@
1
+
2
+ #include "task_log_buffer.h"
3
+ #include "esphome/core/helpers.h"
4
+ #include "esphome/core/log.h"
5
+
6
+ #ifdef USE_ESPHOME_TASK_LOG_BUFFER
7
+
8
+ namespace esphome {
9
+ namespace logger {
10
+
11
+ TaskLogBuffer::TaskLogBuffer(size_t total_buffer_size) {
12
+ // Store the buffer size
13
+ this->size_ = total_buffer_size;
14
+ // Allocate memory for the ring buffer using ESPHome's RAM allocator
15
+ RAMAllocator<uint8_t> allocator;
16
+ this->storage_ = allocator.allocate(this->size_);
17
+ // Create a static ring buffer with RINGBUF_TYPE_NOSPLIT for message integrity
18
+ this->ring_buffer_ = xRingbufferCreateStatic(this->size_, RINGBUF_TYPE_NOSPLIT, this->storage_, &this->structure_);
19
+ }
20
+
21
+ TaskLogBuffer::~TaskLogBuffer() {
22
+ if (this->ring_buffer_ != nullptr) {
23
+ // Delete the ring buffer
24
+ vRingbufferDelete(this->ring_buffer_);
25
+ this->ring_buffer_ = nullptr;
26
+
27
+ // Free the allocated memory
28
+ RAMAllocator<uint8_t> allocator;
29
+ allocator.deallocate(this->storage_, this->size_);
30
+ this->storage_ = nullptr;
31
+ }
32
+ }
33
+
34
+ bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char **text, void **received_token) {
35
+ if (message == nullptr || text == nullptr || received_token == nullptr) {
36
+ return false;
37
+ }
38
+
39
+ size_t item_size = 0;
40
+ void *received_item = xRingbufferReceive(ring_buffer_, &item_size, 0);
41
+ if (received_item == nullptr) {
42
+ return false;
43
+ }
44
+
45
+ LogMessage *msg = static_cast<LogMessage *>(received_item);
46
+ *message = msg;
47
+ *text = msg->text_data();
48
+ *received_token = received_item;
49
+
50
+ return true;
51
+ }
52
+
53
+ void TaskLogBuffer::release_message_main_loop(void *token) {
54
+ if (token == nullptr) {
55
+ return;
56
+ }
57
+ vRingbufferReturnItem(ring_buffer_, token);
58
+ // Update counter to mark all messages as processed
59
+ last_processed_counter_ = message_counter_.load(std::memory_order_relaxed);
60
+ }
61
+
62
+ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle,
63
+ const char *format, va_list args) {
64
+ // First, calculate the exact length needed using a null buffer (no actual writing)
65
+ va_list args_copy;
66
+ va_copy(args_copy, args);
67
+ int ret = vsnprintf(nullptr, 0, format, args_copy);
68
+ va_end(args_copy);
69
+
70
+ if (ret <= 0) {
71
+ return false; // Formatting error or empty message
72
+ }
73
+
74
+ // Calculate actual text length (capped to maximum size)
75
+ static constexpr size_t MAX_TEXT_SIZE = 255;
76
+ size_t text_length = (static_cast<size_t>(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret;
77
+
78
+ // Calculate total size needed (header + text length + null terminator)
79
+ size_t total_size = sizeof(LogMessage) + text_length + 1;
80
+
81
+ // Acquire memory directly from the ring buffer
82
+ void *acquired_memory = nullptr;
83
+ BaseType_t result = xRingbufferSendAcquire(ring_buffer_, &acquired_memory, total_size, 0);
84
+
85
+ if (result != pdTRUE || acquired_memory == nullptr) {
86
+ return false; // Failed to acquire memory
87
+ }
88
+
89
+ // Set up the message header in the acquired memory
90
+ LogMessage *msg = static_cast<LogMessage *>(acquired_memory);
91
+ msg->level = level;
92
+ msg->tag = tag;
93
+ msg->line = line;
94
+
95
+ // Store the thread name now instead of waiting until main loop processing
96
+ // This avoids crashes if the task completes or is deleted between when this message
97
+ // is enqueued and when it's processed by the main loop
98
+ const char *thread_name = pcTaskGetName(task_handle);
99
+ if (thread_name != nullptr) {
100
+ strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1);
101
+ msg->thread_name[sizeof(msg->thread_name) - 1] = '\0'; // Ensure null termination
102
+ } else {
103
+ msg->thread_name[0] = '\0'; // Empty string if no thread name
104
+ }
105
+
106
+ // Format the message text directly into the acquired memory
107
+ // We add 1 to text_length to ensure space for null terminator during formatting
108
+ char *text_area = msg->text_data();
109
+ ret = vsnprintf(text_area, text_length + 1, format, args);
110
+
111
+ // Handle unexpected formatting error
112
+ if (ret <= 0) {
113
+ vRingbufferReturnItem(ring_buffer_, acquired_memory);
114
+ return false;
115
+ }
116
+
117
+ // Remove trailing newlines
118
+ while (text_length > 0 && text_area[text_length - 1] == '\n') {
119
+ text_length--;
120
+ }
121
+
122
+ msg->text_length = text_length;
123
+ // Complete the send operation with the acquired memory
124
+ result = xRingbufferSendComplete(ring_buffer_, acquired_memory);
125
+
126
+ if (result != pdTRUE) {
127
+ return false; // Failed to complete the message send
128
+ }
129
+
130
+ // Message sent successfully, increment the counter
131
+ message_counter_.fetch_add(1, std::memory_order_relaxed);
132
+ return true;
133
+ }
134
+
135
+ } // namespace logger
136
+ } // namespace esphome
137
+
138
+ #endif // USE_ESPHOME_TASK_LOG_BUFFER
@@ -0,0 +1,69 @@
1
+ #pragma once
2
+
3
+ #include "esphome/core/defines.h"
4
+ #include "esphome/core/helpers.h"
5
+
6
+ #ifdef USE_ESPHOME_TASK_LOG_BUFFER
7
+ #include <cstddef>
8
+ #include <cstring>
9
+ #include <memory>
10
+ #include <atomic>
11
+ #include <freertos/FreeRTOS.h>
12
+ #include <freertos/ringbuf.h>
13
+
14
+ namespace esphome {
15
+ namespace logger {
16
+
17
+ class TaskLogBuffer {
18
+ public:
19
+ // Structure for a log message header (text data follows immediately after)
20
+ struct LogMessage {
21
+ const char *tag; // We store the pointer, assuming tags are static
22
+ char thread_name[16]; // Store thread name directly (only used for non-main threads)
23
+ uint16_t text_length; // Length of the message text (up to ~64KB)
24
+ uint16_t line; // Source code line number
25
+ uint8_t level; // Log level (0-7)
26
+
27
+ // Methods for accessing message contents
28
+ inline char *text_data() { return reinterpret_cast<char *>(this) + sizeof(LogMessage); }
29
+
30
+ inline const char *text_data() const { return reinterpret_cast<const char *>(this) + sizeof(LogMessage); }
31
+ };
32
+
33
+ // Constructor that takes a total buffer size
34
+ explicit TaskLogBuffer(size_t total_buffer_size);
35
+ ~TaskLogBuffer();
36
+
37
+ // NOT thread-safe - borrow a message from the ring buffer, only call from main loop
38
+ bool borrow_message_main_loop(LogMessage **message, const char **text, void **received_token);
39
+
40
+ // NOT thread-safe - release a message buffer and update the counter, only call from main loop
41
+ void release_message_main_loop(void *token);
42
+
43
+ // Thread-safe - send a message to the ring buffer from any thread
44
+ bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle,
45
+ const char *format, va_list args);
46
+
47
+ // Check if there are messages ready to be processed using an atomic counter for performance
48
+ inline bool HOT has_messages() const {
49
+ return message_counter_.load(std::memory_order_relaxed) != last_processed_counter_;
50
+ }
51
+
52
+ // Get the total buffer size in bytes
53
+ inline size_t size() const { return size_; }
54
+
55
+ private:
56
+ RingbufHandle_t ring_buffer_{nullptr}; // FreeRTOS ring buffer handle
57
+ StaticRingbuffer_t structure_; // Static structure for the ring buffer
58
+ uint8_t *storage_{nullptr}; // Pointer to allocated memory
59
+ size_t size_{0}; // Size of allocated memory
60
+
61
+ // Atomic counter for message tracking (only differences matter)
62
+ std::atomic<uint16_t> message_counter_{0}; // Incremented when messages are committed
63
+ mutable uint16_t last_processed_counter_{0}; // Tracks last processed message
64
+ };
65
+
66
+ } // namespace logger
67
+ } // namespace esphome
68
+
69
+ #endif // USE_ESPHOME_TASK_LOG_BUFFER
@@ -2,6 +2,7 @@ import logging
2
2
 
3
3
  from esphome.automation import build_automation, register_action, validate_automation
4
4
  import esphome.codegen as cg
5
+ from esphome.components.const import CONF_DRAW_ROUNDING
5
6
  from esphome.components.display import Display
6
7
  import esphome.config_validation as cv
7
8
  from esphome.const import (
@@ -17,14 +18,14 @@ from esphome.const import (
17
18
  CONF_TRIGGER_ID,
18
19
  CONF_TYPE,
19
20
  )
20
- from esphome.core import CORE, ID
21
+ from esphome.core import CORE, ID, Lambda
21
22
  from esphome.cpp_generator import MockObj
22
23
  from esphome.final_validate import full_config
23
24
  from esphome.helpers import write_file_if_changed
24
25
 
25
26
  from . import defines as df, helpers, lv_validation as lvalid
26
- from .automation import disp_update, focused_widgets, update_to_code
27
- from .defines import CONF_DRAW_ROUNDING, add_define
27
+ from .automation import disp_update, focused_widgets, refreshed_widgets, update_to_code
28
+ from .defines import add_define
28
29
  from .encoders import (
29
30
  ENCODERS_CONFIG,
30
31
  encoders_to_code,
@@ -239,6 +240,13 @@ def final_validation(configs):
239
240
  "A non adjustable arc may not be focused",
240
241
  path,
241
242
  )
243
+ for w in refreshed_widgets:
244
+ path = global_config.get_path_for_id(w)
245
+ widget_conf = global_config.get_config_for_path(path[:-1])
246
+ if not any(isinstance(v, Lambda) for v in widget_conf.values()):
247
+ raise cv.Invalid(
248
+ f"Widget '{w}' does not have any templated properties to refresh",
249
+ )
242
250
 
243
251
 
244
252
  async def to_code(configs):
@@ -323,7 +331,7 @@ async def to_code(configs):
323
331
  displays,
324
332
  frac,
325
333
  config[df.CONF_FULL_REFRESH],
326
- config[df.CONF_DRAW_ROUNDING],
334
+ config[CONF_DRAW_ROUNDING],
327
335
  config[df.CONF_RESUME_ON_INPUT],
328
336
  )
329
337
  await cg.register_component(lv_component, config)
@@ -413,7 +421,7 @@ LVGL_SCHEMA = cv.All(
413
421
  df.CONF_DEFAULT_FONT, default="montserrat_14"
414
422
  ): lvalid.lv_font,
415
423
  cv.Optional(df.CONF_FULL_REFRESH, default=False): cv.boolean,
416
- cv.Optional(df.CONF_DRAW_ROUNDING, default=2): cv.positive_int,
424
+ cv.Optional(CONF_DRAW_ROUNDING, default=2): cv.positive_int,
417
425
  cv.Optional(CONF_BUFFER_SIZE, default="100%"): cv.percentage,
418
426
  cv.Optional(df.CONF_LOG_LEVEL, default="WARN"): cv.one_of(
419
427
  *df.LV_LOG_LEVELS, upper=True
@@ -35,7 +35,13 @@ from .lvcode import (
35
35
  lv_obj,
36
36
  lvgl_comp,
37
37
  )
38
- from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA, base_update_schema
38
+ from .schemas import (
39
+ ALL_STYLES,
40
+ DISP_BG_SCHEMA,
41
+ LIST_ACTION_SCHEMA,
42
+ LVGL_SCHEMA,
43
+ base_update_schema,
44
+ )
39
45
  from .types import (
40
46
  LV_STATE,
41
47
  LvglAction,
@@ -57,6 +63,7 @@ from .widgets import (
57
63
 
58
64
  # Record widgets that are used in a focused action here
59
65
  focused_widgets = set()
66
+ refreshed_widgets = set()
60
67
 
61
68
 
62
69
  async def action_to_code(
@@ -361,3 +368,45 @@ async def obj_update_to_code(config, action_id, template_arg, args):
361
368
  return await action_to_code(
362
369
  widgets, do_update, action_id, template_arg, args, config
363
370
  )
371
+
372
+
373
+ def validate_refresh_config(config):
374
+ for w in config:
375
+ refreshed_widgets.add(w[CONF_ID])
376
+ return config
377
+
378
+
379
+ @automation.register_action(
380
+ "lvgl.widget.refresh",
381
+ ObjUpdateAction,
382
+ cv.All(
383
+ cv.ensure_list(
384
+ cv.maybe_simple_value(
385
+ {
386
+ cv.Required(CONF_ID): cv.use_id(lv_obj_t),
387
+ },
388
+ key=CONF_ID,
389
+ )
390
+ ),
391
+ validate_refresh_config,
392
+ ),
393
+ )
394
+ async def obj_refresh_to_code(config, action_id, template_arg, args):
395
+ widget = await get_widgets(config)
396
+
397
+ async def do_refresh(widget: Widget):
398
+ # only update style properties that might have changed, i.e. are templated
399
+ config = {k: v for k, v in widget.config.items() if isinstance(v, Lambda)}
400
+ await set_obj_properties(widget, config)
401
+ # must pass all widget-specific options here, even if not templated, but only do so if at least one is
402
+ # templated. First filter out common style properties.
403
+ config = {k: v for k, v in widget.config.items() if k not in ALL_STYLES}
404
+ if any(isinstance(v, Lambda) for v in config.values()):
405
+ await widget.type.to_code(widget, config)
406
+ if (
407
+ widget.type.w_type.value_property is not None
408
+ and widget.type.w_type.value_property in config
409
+ ):
410
+ lv.event_send(widget.obj, UPDATE_EVENT, nullptr)
411
+
412
+ return await action_to_code(widget, do_refresh, action_id, template_arg, args)
@@ -424,7 +424,6 @@ CONF_DEFAULT_FONT = "default_font"
424
424
  CONF_DEFAULT_GROUP = "default_group"
425
425
  CONF_DIR = "dir"
426
426
  CONF_DISPLAYS = "displays"
427
- CONF_DRAW_ROUNDING = "draw_rounding"
428
427
  CONF_EDITING = "editing"
429
428
  CONF_ENCODERS = "encoders"
430
429
  CONF_END_ANGLE = "end_angle"
@@ -434,7 +434,11 @@ void LvglComponent::setup() {
434
434
  auto height = display->get_height();
435
435
  size_t buffer_pixels = width * height / this->buffer_frac_;
436
436
  auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
437
- auto *buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
437
+ void *buffer = nullptr;
438
+ if (this->buffer_frac_ >= 4)
439
+ buffer = malloc(buf_bytes); // NOLINT
440
+ if (buffer == nullptr)
441
+ buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
438
442
  if (buffer == nullptr) {
439
443
  this->mark_failed();
440
444
  this->status_set_error("Memory allocation failure");
@@ -19,9 +19,8 @@ from ..widgets import get_widgets, wait_for_widgets
19
19
 
20
20
  LVGLText = lvgl_ns.class_("LVGLText", text.Text)
21
21
 
22
- CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(
22
+ CONFIG_SCHEMA = text.text_schema(LVGLText).extend(
23
23
  {
24
- cv.GenerateID(): cv.declare_id(LVGLText),
25
24
  cv.Required(CONF_WIDGET): cv.use_id(LvText),
26
25
  }
27
26
  )