esphome 2025.9.3__py3-none-any.whl → 2025.10.0b2__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 (351) hide show
  1. esphome/__main__.py +94 -31
  2. esphome/address_cache.py +142 -0
  3. esphome/automation.py +130 -32
  4. esphome/build_gen/platformio.py +1 -3
  5. esphome/codegen.py +1 -0
  6. esphome/components/animation/animation.cpp +2 -2
  7. esphome/components/api/__init__.py +166 -3
  8. esphome/components/api/api_connection.cpp +84 -41
  9. esphome/components/api/api_connection.h +22 -16
  10. esphome/components/api/api_frame_helper.cpp +33 -19
  11. esphome/components/api/api_frame_helper.h +19 -4
  12. esphome/components/api/api_frame_helper_noise.cpp +41 -53
  13. esphome/components/api/api_frame_helper_noise.h +1 -1
  14. esphome/components/api/api_frame_helper_plaintext.cpp +22 -31
  15. esphome/components/api/api_frame_helper_plaintext.h +1 -1
  16. esphome/components/api/api_pb2.cpp +189 -15
  17. esphome/components/api/api_pb2.h +132 -20
  18. esphome/components/api/api_pb2_dump.cpp +97 -9
  19. esphome/components/api/api_pb2_service.cpp +118 -160
  20. esphome/components/api/api_pb2_service.h +31 -3
  21. esphome/components/api/api_server.cpp +68 -10
  22. esphome/components/api/api_server.h +32 -4
  23. esphome/components/api/custom_api_device.h +8 -8
  24. esphome/components/api/homeassistant_service.h +123 -6
  25. esphome/components/api/proto.h +6 -2
  26. esphome/components/api/user_services.h +2 -2
  27. esphome/components/as7341/sensor.py +1 -1
  28. esphome/components/audio/__init__.py +1 -1
  29. esphome/components/audio/audio.cpp +1 -1
  30. esphome/components/audio/audio_decoder.cpp +9 -9
  31. esphome/components/bl0906/bl0906.cpp +2 -2
  32. esphome/components/bl0942/bl0942.cpp +2 -2
  33. esphome/components/ble_client/__init__.py +1 -1
  34. esphome/components/bluetooth_proxy/__init__.py +4 -30
  35. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +11 -4
  36. esphome/components/bluetooth_proxy/bluetooth_connection.h +2 -2
  37. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +2 -2
  38. esphome/components/camera_encoder/__init__.py +2 -4
  39. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +4 -2
  40. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +3 -1
  41. esphome/components/canbus/canbus.cpp +7 -5
  42. esphome/components/canbus/canbus.h +7 -7
  43. esphome/components/captive_portal/__init__.py +18 -1
  44. esphome/components/captive_portal/captive_portal.cpp +40 -46
  45. esphome/components/captive_portal/captive_portal.h +20 -22
  46. esphome/components/captive_portal/dns_server_esp32_idf.cpp +205 -0
  47. esphome/components/captive_portal/dns_server_esp32_idf.h +27 -0
  48. esphome/components/ccs811/ccs811.cpp +1 -1
  49. esphome/components/climate/climate.cpp +10 -7
  50. esphome/components/cm1106/cm1106.cpp +1 -1
  51. esphome/components/copy/lock/copy_lock.cpp +1 -1
  52. esphome/components/cover/cover.cpp +1 -0
  53. esphome/components/daikin_arc/daikin_arc.cpp +19 -12
  54. esphome/components/dashboard_import/dashboard_import.cpp +1 -1
  55. esphome/components/dashboard_import/dashboard_import.h +1 -1
  56. esphome/components/deep_sleep/__init__.py +9 -2
  57. esphome/components/deep_sleep/deep_sleep_component.h +11 -9
  58. esphome/components/deep_sleep/deep_sleep_esp32.cpp +51 -27
  59. esphome/components/ektf2232/touchscreen/__init__.py +8 -5
  60. esphome/components/ektf2232/touchscreen/ektf2232.cpp +4 -4
  61. esphome/components/ektf2232/touchscreen/ektf2232.h +2 -2
  62. esphome/components/epaper_spi/__init__.py +1 -0
  63. esphome/components/epaper_spi/display.py +80 -0
  64. esphome/components/epaper_spi/epaper_spi.cpp +227 -0
  65. esphome/components/epaper_spi/epaper_spi.h +93 -0
  66. esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp +42 -0
  67. esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h +45 -0
  68. esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +135 -0
  69. esphome/components/epaper_spi/epaper_spi_spectra_e6.h +23 -0
  70. esphome/components/es7210/es7210.cpp +3 -3
  71. esphome/components/esp32/__init__.py +256 -340
  72. esphome/components/esp32/boards.py +81 -0
  73. esphome/components/esp32/preferences.cpp +23 -17
  74. esphome/components/esp32_ble/__init__.py +167 -44
  75. esphome/components/esp32_ble/ble.cpp +47 -3
  76. esphome/components/esp32_ble/ble.h +18 -0
  77. esphome/components/esp32_ble/ble_advertising.cpp +7 -3
  78. esphome/components/esp32_ble/ble_advertising.h +4 -0
  79. esphome/components/esp32_ble/ble_uuid.cpp +16 -42
  80. esphome/components/esp32_ble_beacon/__init__.py +3 -4
  81. esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +0 -4
  82. esphome/components/esp32_ble_client/ble_client_base.cpp +14 -12
  83. esphome/components/esp32_ble_server/__init__.py +28 -14
  84. esphome/components/esp32_ble_server/ble_characteristic.cpp +67 -57
  85. esphome/components/esp32_ble_server/ble_characteristic.h +27 -16
  86. esphome/components/esp32_ble_server/ble_descriptor.cpp +4 -3
  87. esphome/components/esp32_ble_server/ble_descriptor.h +13 -9
  88. esphome/components/esp32_ble_server/ble_server.cpp +59 -24
  89. esphome/components/esp32_ble_server/ble_server.h +38 -20
  90. esphome/components/esp32_ble_server/ble_server_automations.cpp +49 -33
  91. esphome/components/esp32_ble_server/ble_server_automations.h +39 -24
  92. esphome/components/esp32_ble_tracker/__init__.py +25 -80
  93. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +2 -8
  94. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +0 -3
  95. esphome/components/esp32_camera/__init__.py +1 -3
  96. esphome/components/esp32_can/esp32_can.cpp +22 -4
  97. esphome/components/esp32_can/esp32_can.h +3 -0
  98. esphome/components/esp32_hosted/__init__.py +2 -1
  99. esphome/components/esp32_improv/esp32_improv_component.cpp +135 -65
  100. esphome/components/esp32_improv/esp32_improv_component.h +7 -1
  101. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  102. esphome/components/esp8266/__init__.py +3 -3
  103. esphome/components/esphome/ota/__init__.py +21 -2
  104. esphome/components/esphome/ota/ota_esphome.cpp +456 -146
  105. esphome/components/esphome/ota/ota_esphome.h +49 -2
  106. esphome/components/ethernet/__init__.py +39 -22
  107. esphome/components/ethernet/ethernet_component.cpp +28 -5
  108. esphome/components/ethernet/ethernet_component.h +5 -1
  109. esphome/components/external_components/__init__.py +8 -6
  110. esphome/components/fingerprint_grow/fingerprint_grow.cpp +1 -1
  111. esphome/components/fingerprint_grow/fingerprint_grow.h +2 -1
  112. esphome/components/font/__init__.py +5 -5
  113. esphome/components/graph/graph.cpp +1 -1
  114. esphome/components/graphical_display_menu/graphical_display_menu.cpp +3 -2
  115. esphome/components/haier/hon_climate.cpp +2 -2
  116. esphome/components/haier/hon_climate.h +1 -1
  117. esphome/components/hdc1080/hdc1080.cpp +42 -34
  118. esphome/components/hdc1080/hdc1080.h +1 -3
  119. esphome/components/homeassistant/number/homeassistant_number.cpp +2 -2
  120. esphome/components/homeassistant/switch/homeassistant_switch.cpp +2 -2
  121. esphome/components/http_request/__init__.py +3 -3
  122. esphome/components/htu21d/htu21d.cpp +13 -18
  123. esphome/components/htu21d/htu21d.h +1 -1
  124. esphome/components/i2s_audio/__init__.py +1 -2
  125. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
  126. esphome/components/ili9xxx/ili9xxx_display.cpp +2 -2
  127. esphome/components/improv_serial/improv_serial_component.cpp +12 -15
  128. esphome/components/improv_serial/improv_serial_component.h +6 -8
  129. esphome/components/json/json_util.cpp +42 -44
  130. esphome/components/json/json_util.h +57 -0
  131. esphome/components/kamstrup_kmp/kamstrup_kmp.cpp +2 -2
  132. esphome/components/key_collector/key_collector.h +4 -4
  133. esphome/components/libretiny/__init__.py +6 -6
  134. esphome/components/libretiny/preferences.cpp +23 -16
  135. esphome/components/light/light_call.cpp +98 -120
  136. esphome/components/light/light_call.h +17 -7
  137. esphome/components/lm75b/__init__.py +0 -0
  138. esphome/components/lm75b/lm75b.cpp +39 -0
  139. esphome/components/lm75b/lm75b.h +19 -0
  140. esphome/components/lm75b/sensor.py +34 -0
  141. esphome/components/lock/lock.h +12 -6
  142. esphome/components/logger/__init__.py +15 -27
  143. esphome/components/logger/logger.cpp +10 -20
  144. esphome/components/logger/logger.h +105 -62
  145. esphome/components/logger/logger_esp32.cpp +0 -48
  146. esphome/components/logger/logger_zephyr.cpp +2 -3
  147. esphome/components/logger/select/logger_level_select.cpp +6 -7
  148. esphome/components/logger/select/logger_level_select.h +7 -0
  149. esphome/components/ltr501/ltr501.cpp +7 -6
  150. esphome/components/ltr_als_ps/ltr_als_ps.cpp +7 -6
  151. esphome/components/matrix_keypad/matrix_keypad.h +4 -4
  152. esphome/components/max7219digit/max7219digit.cpp +1 -1
  153. esphome/components/mcp23xxx_base/mcp23xxx_base.h +3 -3
  154. esphome/components/mcp2515/mcp2515.cpp +31 -3
  155. esphome/components/mcp2515/mcp2515_defs.h +3 -1
  156. esphome/components/md5/md5.cpp +0 -26
  157. esphome/components/md5/md5.h +10 -20
  158. esphome/components/mdns/__init__.py +93 -19
  159. esphome/components/mdns/mdns_component.cpp +57 -94
  160. esphome/components/mdns/mdns_component.h +35 -11
  161. esphome/components/mdns/mdns_esp32.cpp +7 -13
  162. esphome/components/mdns/mdns_esp8266.cpp +7 -7
  163. esphome/components/mdns/mdns_libretiny.cpp +3 -4
  164. esphome/components/mdns/mdns_rp2040.cpp +3 -4
  165. esphome/components/mipi/__init__.py +1 -5
  166. esphome/components/mipi_spi/display.py +24 -8
  167. esphome/components/mipi_spi/mipi_spi.h +3 -3
  168. esphome/components/mixer/speaker/mixer_speaker.cpp +3 -3
  169. esphome/components/mmc5603/mmc5603.cpp +3 -3
  170. esphome/components/modbus/modbus.cpp +27 -13
  171. esphome/components/modbus/modbus.h +5 -3
  172. esphome/components/modbus/modbus_definitions.h +86 -0
  173. esphome/components/modbus_controller/__init__.py +29 -1
  174. esphome/components/modbus_controller/const.py +4 -0
  175. esphome/components/modbus_controller/modbus_controller.cpp +38 -13
  176. esphome/components/modbus_controller/modbus_controller.h +18 -29
  177. esphome/components/mpr121/mpr121.cpp +41 -42
  178. esphome/components/mpr121/mpr121.h +0 -1
  179. esphome/components/nau7802/nau7802.cpp +2 -2
  180. esphome/components/network/__init__.py +7 -3
  181. esphome/components/nextion/display.py +4 -4
  182. esphome/components/nextion/nextion.cpp +8 -8
  183. esphome/components/number/__init__.py +2 -0
  184. esphome/components/number/number_call.cpp +23 -12
  185. esphome/components/number/number_call.h +5 -0
  186. esphome/components/online_image/bmp_image.cpp +2 -1
  187. esphome/components/online_image/jpeg_image.cpp +4 -2
  188. esphome/components/opentherm/opentherm.cpp +5 -5
  189. esphome/components/opentherm/opentherm.h +3 -3
  190. esphome/components/openthread/openthread.cpp +11 -10
  191. esphome/components/openthread/openthread.h +0 -1
  192. esphome/components/ota/ota_backend.h +1 -0
  193. esphome/components/packages/__init__.py +10 -8
  194. esphome/components/packet_transport/packet_transport.cpp +2 -0
  195. esphome/components/pid/pid_controller.cpp +1 -1
  196. esphome/components/prometheus/prometheus_handler.cpp +239 -239
  197. esphome/components/psram/__init__.py +30 -28
  198. esphome/components/qmc5883l/qmc5883l.cpp +15 -0
  199. esphome/components/qmc5883l/qmc5883l.h +3 -0
  200. esphome/components/qmc5883l/sensor.py +31 -12
  201. esphome/components/remote_base/gobox_protocol.cpp +3 -3
  202. esphome/components/remote_receiver/__init__.py +14 -2
  203. esphome/components/remote_receiver/{remote_receiver_esp8266.cpp → remote_receiver.cpp} +2 -2
  204. esphome/components/remote_receiver/remote_receiver.h +4 -0
  205. esphome/components/remote_receiver/remote_receiver_esp32.cpp +18 -1
  206. esphome/components/remote_transmitter/__init__.py +2 -2
  207. esphome/components/remote_transmitter/remote_transmitter.cpp +103 -0
  208. esphome/components/rp2040/__init__.py +11 -11
  209. esphome/components/rtttl/rtttl.cpp +2 -2
  210. esphome/components/scd30/sensor.py +1 -1
  211. esphome/components/script/__init__.py +1 -1
  212. esphome/components/script/script.h +7 -7
  213. esphome/components/select/select.cpp +5 -4
  214. esphome/components/select/select_call.cpp +1 -1
  215. esphome/components/sensirion_common/i2c_sensirion.cpp +2 -1
  216. esphome/components/sensor/__init__.py +2 -0
  217. esphome/components/sha256/__init__.py +22 -0
  218. esphome/components/sha256/sha256.cpp +116 -0
  219. esphome/components/sha256/sha256.h +60 -0
  220. esphome/components/socket/lwip_raw_tcp_impl.cpp +34 -6
  221. esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
  222. esphome/components/spi/__init__.py +0 -3
  223. esphome/components/split_buffer/__init__.py +5 -0
  224. esphome/components/split_buffer/split_buffer.cpp +133 -0
  225. esphome/components/split_buffer/split_buffer.h +40 -0
  226. esphome/components/sps30/sps30.cpp +14 -10
  227. esphome/components/sps30/sps30.h +2 -0
  228. esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
  229. esphome/components/st7789v/st7789v.cpp +3 -2
  230. esphome/components/statsd/statsd.cpp +1 -1
  231. esphome/components/substitutions/__init__.py +3 -1
  232. esphome/components/substitutions/jinja.py +13 -3
  233. esphome/components/sx126x/__init__.py +16 -0
  234. esphome/components/sx126x/sx126x.cpp +15 -1
  235. esphome/components/sx126x/sx126x.h +9 -1
  236. esphome/components/sx126x/sx126x_reg.h +2 -0
  237. esphome/components/text_sensor/text_sensor.cpp +16 -0
  238. esphome/components/text_sensor/text_sensor.h +3 -10
  239. esphome/components/tormatic/tormatic_cover.cpp +1 -1
  240. esphome/components/tuya/select/tuya_select.cpp +1 -1
  241. esphome/components/tuya/tuya.cpp +29 -4
  242. esphome/components/uart/__init__.py +37 -27
  243. esphome/components/uart/uart.h +6 -0
  244. esphome/components/uart/uart_component.cpp +8 -0
  245. esphome/components/uart/uart_component.h +28 -0
  246. esphome/components/uart/uart_component_esp_idf.cpp +64 -10
  247. esphome/components/uart/uart_component_esp_idf.h +5 -2
  248. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
  249. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
  250. esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
  251. esphome/components/usb_host/__init__.py +12 -2
  252. esphome/components/usb_host/usb_host.h +89 -14
  253. esphome/components/usb_host/usb_host_client.cpp +157 -22
  254. esphome/components/usb_host/usb_host_component.cpp +1 -1
  255. esphome/components/usb_uart/__init__.py +0 -1
  256. esphome/components/usb_uart/ch34x.cpp +4 -4
  257. esphome/components/usb_uart/cp210x.cpp +3 -3
  258. esphome/components/usb_uart/usb_uart.cpp +88 -32
  259. esphome/components/usb_uart/usb_uart.h +30 -6
  260. esphome/components/valve/valve.cpp +1 -0
  261. esphome/components/veml7700/veml7700.cpp +7 -6
  262. esphome/components/version/version_text_sensor.cpp +2 -1
  263. esphome/components/voice_assistant/voice_assistant.cpp +3 -2
  264. esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
  265. esphome/components/web_server/list_entities.cpp +3 -4
  266. esphome/components/web_server/list_entities.h +8 -10
  267. esphome/components/web_server/ota/__init__.py +1 -1
  268. esphome/components/web_server/ota/ota_web_server.cpp +9 -3
  269. esphome/components/web_server/web_server.cpp +509 -404
  270. esphome/components/web_server/web_server.h +5 -6
  271. esphome/components/web_server/web_server_v1.cpp +21 -19
  272. esphome/components/web_server_base/__init__.py +5 -2
  273. esphome/components/web_server_base/web_server_base.h +27 -7
  274. esphome/components/web_server_idf/__init__.py +1 -1
  275. esphome/components/web_server_idf/multipart.cpp +2 -2
  276. esphome/components/web_server_idf/multipart.h +2 -2
  277. esphome/components/web_server_idf/utils.cpp +2 -2
  278. esphome/components/web_server_idf/utils.h +2 -2
  279. esphome/components/web_server_idf/web_server_idf.cpp +118 -26
  280. esphome/components/web_server_idf/web_server_idf.h +12 -10
  281. esphome/components/wifi/__init__.py +13 -11
  282. esphome/components/wifi/wifi_component.cpp +74 -56
  283. esphome/components/wifi/wifi_component.h +4 -4
  284. esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
  285. esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
  286. esphome/components/wireguard/__init__.py +1 -1
  287. esphome/components/wts01/__init__.py +0 -0
  288. esphome/components/wts01/sensor.py +41 -0
  289. esphome/components/wts01/wts01.cpp +91 -0
  290. esphome/components/wts01/wts01.h +27 -0
  291. esphome/components/zephyr/__init__.py +5 -5
  292. esphome/components/zwave_proxy/__init__.py +43 -0
  293. esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
  294. esphome/components/zwave_proxy/zwave_proxy.h +93 -0
  295. esphome/config.py +79 -24
  296. esphome/config_validation.py +13 -15
  297. esphome/const.py +9 -2
  298. esphome/core/__init__.py +33 -22
  299. esphome/core/component.cpp +28 -18
  300. esphome/core/component_iterator.h +2 -1
  301. esphome/core/config.py +15 -15
  302. esphome/core/defines.h +21 -0
  303. esphome/core/entity_helpers.py +9 -6
  304. esphome/core/hash_base.h +56 -0
  305. esphome/core/helpers.cpp +19 -3
  306. esphome/core/helpers.h +26 -0
  307. esphome/core/scheduler.cpp +5 -21
  308. esphome/core/scheduler.h +19 -8
  309. esphome/core/string_ref.h +1 -1
  310. esphome/core/time.cpp +5 -5
  311. esphome/cpp_generator.py +4 -29
  312. esphome/dashboard/const.py +21 -4
  313. esphome/dashboard/core.py +10 -8
  314. esphome/dashboard/dns.py +15 -0
  315. esphome/dashboard/entries.py +15 -21
  316. esphome/dashboard/models.py +76 -0
  317. esphome/dashboard/settings.py +7 -7
  318. esphome/dashboard/status/mdns.py +46 -2
  319. esphome/dashboard/web_server.py +367 -93
  320. esphome/espota2.py +112 -32
  321. esphome/external_files.py +6 -7
  322. esphome/git.py +8 -0
  323. esphome/helpers.py +124 -77
  324. esphome/loader.py +8 -9
  325. esphome/pins.py +2 -2
  326. esphome/platformio_api.py +56 -18
  327. esphome/storage_json.py +26 -21
  328. esphome/types.py +30 -2
  329. esphome/util.py +32 -16
  330. esphome/vscode.py +8 -8
  331. esphome/wizard.py +10 -10
  332. esphome/writer.py +50 -15
  333. esphome/yaml_util.py +37 -31
  334. esphome/zeroconf.py +12 -3
  335. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/METADATA +12 -12
  336. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/RECORD +340 -320
  337. esphome/components/event_emitter/__init__.py +0 -5
  338. esphome/components/event_emitter/event_emitter.cpp +0 -14
  339. esphome/components/event_emitter/event_emitter.h +0 -63
  340. esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
  341. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
  342. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
  343. esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
  344. esphome/components/uart/uart_component_esp32_arduino.h +0 -60
  345. esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
  346. esphome/core/string_ref.cpp +0 -12
  347. esphome/dashboard/util/file.py +0 -63
  348. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/WHEEL +0 -0
  349. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/entry_points.txt +0 -0
  350. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/licenses/LICENSE +0 -0
  351. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/top_level.txt +0 -0
@@ -122,7 +122,7 @@ bool UponorSmatrixComponent::parse_byte_(uint8_t byte) {
122
122
 
123
123
  // Decode packet payload data for easy access
124
124
  UponorSmatrixData data[data_len];
125
- for (int i = 0; i < data_len; i++) {
125
+ for (size_t i = 0; i < data_len; i++) {
126
126
  data[i].id = packet[(i * 3) + 4];
127
127
  data[i].value = encode_uint16(packet[(i * 3) + 5], packet[(i * 3) + 6]);
128
128
  }
@@ -135,7 +135,7 @@ bool UponorSmatrixComponent::parse_byte_(uint8_t byte) {
135
135
  // thermostat sending both room temperature and time information.
136
136
  bool found_temperature = false;
137
137
  bool found_time = false;
138
- for (int i = 0; i < data_len; i++) {
138
+ for (size_t i = 0; i < data_len; i++) {
139
139
  if (data[i].id == UPONOR_ID_ROOM_TEMP)
140
140
  found_temperature = true;
141
141
  if (data[i].id == UPONOR_ID_DATETIME1)
@@ -181,7 +181,7 @@ bool UponorSmatrixComponent::send(uint16_t device_address, const UponorSmatrixDa
181
181
  packet.push_back(device_address >> 8);
182
182
  packet.push_back(device_address >> 0);
183
183
 
184
- for (int i = 0; i < data_len; i++) {
184
+ for (size_t i = 0; i < data_len; i++) {
185
185
  packet.push_back(data[i].id);
186
186
  packet.push_back(data[i].value >> 8);
187
187
  packet.push_back(data[i].value >> 0);
@@ -1,5 +1,6 @@
1
1
  import esphome.codegen as cg
2
2
  from esphome.components.esp32 import (
3
+ VARIANT_ESP32P4,
3
4
  VARIANT_ESP32S2,
4
5
  VARIANT_ESP32S3,
5
6
  add_idf_sdkconfig_option,
@@ -8,6 +9,7 @@ from esphome.components.esp32 import (
8
9
  import esphome.config_validation as cv
9
10
  from esphome.const import CONF_DEVICES, CONF_ID
10
11
  from esphome.cpp_types import Component
12
+ from esphome.types import ConfigType
11
13
 
12
14
  AUTO_LOAD = ["bytebuffer"]
13
15
  CODEOWNERS = ["@clydebarrow"]
@@ -19,6 +21,7 @@ USBClient = usb_host_ns.class_("USBClient", Component)
19
21
  CONF_VID = "vid"
20
22
  CONF_PID = "pid"
21
23
  CONF_ENABLE_HUBS = "enable_hubs"
24
+ CONF_MAX_TRANSFER_REQUESTS = "max_transfer_requests"
22
25
 
23
26
 
24
27
  def usb_device_schema(cls=USBClient, vid: int = None, pid: [int] = None) -> cv.Schema:
@@ -43,11 +46,14 @@ CONFIG_SCHEMA = cv.All(
43
46
  {
44
47
  cv.GenerateID(): cv.declare_id(USBHost),
45
48
  cv.Optional(CONF_ENABLE_HUBS, default=False): cv.boolean,
49
+ cv.Optional(CONF_MAX_TRANSFER_REQUESTS, default=16): cv.int_range(
50
+ min=1, max=32
51
+ ),
46
52
  cv.Optional(CONF_DEVICES): cv.ensure_list(usb_device_schema()),
47
53
  }
48
54
  ),
49
55
  cv.only_with_esp_idf,
50
- only_on_variant(supported=[VARIANT_ESP32S2, VARIANT_ESP32S3]),
56
+ only_on_variant(supported=[VARIANT_ESP32S2, VARIANT_ESP32S3, VARIANT_ESP32P4]),
51
57
  )
52
58
 
53
59
 
@@ -57,10 +63,14 @@ async def register_usb_client(config):
57
63
  return var
58
64
 
59
65
 
60
- async def to_code(config):
66
+ async def to_code(config: ConfigType) -> None:
61
67
  add_idf_sdkconfig_option("CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE", 1024)
62
68
  if config.get(CONF_ENABLE_HUBS):
63
69
  add_idf_sdkconfig_option("CONFIG_USB_HOST_HUBS_SUPPORTED", True)
70
+
71
+ max_requests = config[CONF_MAX_TRANSFER_REQUESTS]
72
+ cg.add_define("USB_HOST_MAX_REQUESTS", max_requests)
73
+
64
74
  var = cg.new_Pvariable(config[CONF_ID])
65
75
  await cg.register_component(var, config)
66
76
  for device in config.get(CONF_DEVICES) or ():
@@ -1,18 +1,48 @@
1
1
  #pragma once
2
2
 
3
3
  // Should not be needed, but it's required to pass CI clang-tidy checks
4
- #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
4
+ #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
5
+ #include "esphome/core/defines.h"
5
6
  #include "esphome/core/component.h"
6
7
  #include <vector>
7
8
  #include "usb/usb_host.h"
8
-
9
- #include <list>
9
+ #include <freertos/FreeRTOS.h>
10
+ #include <freertos/task.h>
11
+ #include "esphome/core/lock_free_queue.h"
12
+ #include "esphome/core/event_pool.h"
13
+ #include <atomic>
10
14
 
11
15
  namespace esphome {
12
16
  namespace usb_host {
13
17
 
18
+ // THREADING MODEL:
19
+ // This component uses a dedicated USB task for event processing to prevent data loss.
20
+ // - USB Task (high priority): Handles USB events, executes transfer callbacks, releases transfer slots
21
+ // - Main Loop Task: Initiates transfers, processes device connect/disconnect events
22
+ //
23
+ // Thread-safe communication:
24
+ // - Lock-free queues for USB task -> main loop events (SPSC pattern)
25
+ // - Lock-free TransferRequest pool using atomic bitmask (MCMP pattern - multi-consumer, multi-producer)
26
+ //
27
+ // TransferRequest pool access pattern:
28
+ // - get_trq_() [allocate]: Called from BOTH USB task and main loop threads
29
+ // * USB task: via USB UART input callbacks that restart transfers immediately
30
+ // * Main loop: for output transfers and flow-controlled input restarts
31
+ // - release_trq() [deallocate]: Called from BOTH USB task and main loop threads
32
+ // * USB task: immediately after transfer callback completes (critical for preventing slot exhaustion)
33
+ // * Main loop: when transfer submission fails
34
+ //
35
+ // The multi-threaded allocation/deallocation is intentional for performance:
36
+ // - USB task can immediately restart input transfers and release slots without context switching
37
+ // - Main loop controls backpressure by deciding when to restart after consuming data
38
+ // The atomic bitmask ensures thread-safe allocation/deallocation without mutex blocking.
39
+
14
40
  static const char *const TAG = "usb_host";
15
41
 
42
+ // Forward declarations
43
+ struct TransferRequest;
44
+ class USBClient;
45
+
16
46
  // constants for setup packet type
17
47
  static const uint8_t USB_RECIP_DEVICE = 0;
18
48
  static const uint8_t USB_RECIP_INTERFACE = 1;
@@ -25,7 +55,20 @@ static const uint8_t USB_DIR_IN = 1 << 7;
25
55
  static const uint8_t USB_DIR_OUT = 0;
26
56
  static const size_t SETUP_PACKET_SIZE = 8;
27
57
 
28
- static const size_t MAX_REQUESTS = 16; // maximum number of outstanding requests possible.
58
+ static const size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible.
59
+ static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be between 1 and 32");
60
+
61
+ // Select appropriate bitmask type for tracking allocation of TransferRequest slots.
62
+ // The bitmask must have at least as many bits as MAX_REQUESTS, so:
63
+ // - Use uint16_t for up to 16 requests (MAX_REQUESTS <= 16)
64
+ // - Use uint32_t for 17-32 requests (MAX_REQUESTS > 16)
65
+ // This is tied to the static_assert above, which enforces MAX_REQUESTS is between 1 and 32.
66
+ // If MAX_REQUESTS is increased above 32, this logic and the static_assert must be updated.
67
+ using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type;
68
+
69
+ static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop
70
+ static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples)
71
+ static constexpr UBaseType_t USB_TASK_PRIORITY = 5; // Higher priority than main loop (tskIDLE_PRIORITY + 5)
29
72
 
30
73
  // used to report a transfer status
31
74
  struct TransferStatus {
@@ -49,6 +92,26 @@ struct TransferRequest {
49
92
  USBClient *client;
50
93
  };
51
94
 
95
+ enum EventType : uint8_t {
96
+ EVENT_DEVICE_NEW,
97
+ EVENT_DEVICE_GONE,
98
+ };
99
+
100
+ struct UsbEvent {
101
+ EventType type;
102
+ union {
103
+ struct {
104
+ uint8_t address;
105
+ } device_new;
106
+ struct {
107
+ usb_device_handle_t handle;
108
+ } device_gone;
109
+ } data;
110
+
111
+ // Required for EventPool - no cleanup needed for POD types
112
+ void release() {}
113
+ };
114
+
52
115
  // callback function type.
53
116
 
54
117
  enum ClientState {
@@ -63,13 +126,7 @@ class USBClient : public Component {
63
126
  friend class USBHost;
64
127
 
65
128
  public:
66
- USBClient(uint16_t vid, uint16_t pid) : vid_(vid), pid_(pid) { init_pool(); }
67
-
68
- void init_pool() {
69
- this->trq_pool_.clear();
70
- for (size_t i = 0; i != MAX_REQUESTS; i++)
71
- this->trq_pool_.push_back(&this->requests_[i]);
72
- }
129
+ USBClient(uint16_t vid, uint16_t pid) : vid_(vid), pid_(pid), trq_in_use_(0) {}
73
130
  void setup() override;
74
131
  void loop() override;
75
132
  // setup must happen after the host bus has been setup
@@ -84,12 +141,26 @@ class USBClient : public Component {
84
141
  bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback,
85
142
  const std::vector<uint8_t> &data = {});
86
143
 
144
+ // Lock-free event queue and pool for USB task to main loop communication
145
+ // Must be public for access from static callbacks
146
+ LockFreeQueue<UsbEvent, USB_EVENT_QUEUE_SIZE> event_queue;
147
+ EventPool<UsbEvent, USB_EVENT_QUEUE_SIZE> event_pool;
148
+
87
149
  protected:
88
150
  bool register_();
89
- TransferRequest *get_trq_();
151
+ TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe)
90
152
  virtual void disconnect();
91
153
  virtual void on_connected() {}
92
- virtual void on_disconnected() { this->init_pool(); }
154
+ virtual void on_disconnected() {
155
+ // Reset all requests to available (all bits to 0)
156
+ this->trq_in_use_.store(0);
157
+ }
158
+
159
+ // USB task management
160
+ static void usb_task_fn(void *arg);
161
+ void usb_task_loop();
162
+
163
+ TaskHandle_t usb_task_handle_{nullptr};
93
164
 
94
165
  usb_host_client_handle_t handle_{};
95
166
  usb_device_handle_t device_handle_{};
@@ -97,7 +168,11 @@ class USBClient : public Component {
97
168
  int state_{USB_CLIENT_INIT};
98
169
  uint16_t vid_{};
99
170
  uint16_t pid_{};
100
- std::list<TransferRequest *> trq_pool_{};
171
+ // Lock-free pool management using atomic bitmask (no dynamic allocation)
172
+ // Bit i = 1: requests_[i] is in use, Bit i = 0: requests_[i] is available
173
+ // Supports multiple concurrent consumers and producers (both threads can allocate/deallocate)
174
+ // Bitmask type automatically selected: uint16_t for <= 16 slots, uint32_t for 17-32 slots
175
+ std::atomic<trq_bitmask_t> trq_in_use_;
101
176
  TransferRequest requests_[MAX_REQUESTS]{};
102
177
  };
103
178
  class USBHost : public Component {
@@ -1,5 +1,5 @@
1
1
  // Should not be needed, but it's required to pass CI clang-tidy checks
2
- #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
2
+ #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
3
3
  #include "usb_host.h"
4
4
  #include "esphome/core/log.h"
5
5
  #include "esphome/core/hal.h"
@@ -7,6 +7,7 @@
7
7
 
8
8
  #include <cinttypes>
9
9
  #include <cstring>
10
+ #include <atomic>
10
11
  namespace esphome {
11
12
  namespace usb_host {
12
13
 
@@ -139,24 +140,40 @@ static std::string get_descriptor_string(const usb_str_desc_t *desc) {
139
140
  return {buffer};
140
141
  }
141
142
 
143
+ // CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)
142
144
  static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *ptr) {
143
145
  auto *client = static_cast<USBClient *>(ptr);
146
+
147
+ // Allocate event from pool
148
+ UsbEvent *event = client->event_pool.allocate();
149
+ if (event == nullptr) {
150
+ // No events available - increment counter for periodic logging
151
+ client->event_queue.increment_dropped_count();
152
+ return;
153
+ }
154
+
155
+ // Queue events to be processed in main loop
144
156
  switch (event_msg->event) {
145
157
  case USB_HOST_CLIENT_EVENT_NEW_DEV: {
146
- auto addr = event_msg->new_dev.address;
147
158
  ESP_LOGD(TAG, "New device %d", event_msg->new_dev.address);
148
- client->on_opened(addr);
159
+ event->type = EVENT_DEVICE_NEW;
160
+ event->data.device_new.address = event_msg->new_dev.address;
149
161
  break;
150
162
  }
151
163
  case USB_HOST_CLIENT_EVENT_DEV_GONE: {
152
- client->on_removed(event_msg->dev_gone.dev_hdl);
153
- ESP_LOGD(TAG, "Device gone %d", event_msg->new_dev.address);
164
+ ESP_LOGD(TAG, "Device gone");
165
+ event->type = EVENT_DEVICE_GONE;
166
+ event->data.device_gone.handle = event_msg->dev_gone.dev_hdl;
154
167
  break;
155
168
  }
156
169
  default:
157
170
  ESP_LOGD(TAG, "Unknown event %d", event_msg->event);
158
- break;
171
+ client->event_pool.release(event);
172
+ return;
159
173
  }
174
+
175
+ // Push to lock-free queue (always succeeds since pool size == queue size)
176
+ client->event_queue.push(event);
160
177
  }
161
178
  void USBClient::setup() {
162
179
  usb_host_client_config_t config{.is_synchronous = false,
@@ -169,13 +186,59 @@ void USBClient::setup() {
169
186
  this->mark_failed();
170
187
  return;
171
188
  }
172
- for (auto *trq : this->trq_pool_) {
173
- usb_host_transfer_alloc(64, 0, &trq->transfer);
174
- trq->client = this;
189
+ // Pre-allocate USB transfer buffers for all slots at startup
190
+ // This avoids any dynamic allocation during runtime
191
+ for (size_t i = 0; i < MAX_REQUESTS; i++) {
192
+ usb_host_transfer_alloc(64, 0, &this->requests_[i].transfer);
193
+ this->requests_[i].client = this; // Set once, never changes
194
+ }
195
+
196
+ // Create and start USB task
197
+ xTaskCreate(usb_task_fn, "usb_task",
198
+ USB_TASK_STACK_SIZE, // Stack size
199
+ this, // Task parameter
200
+ USB_TASK_PRIORITY, // Priority (higher than main loop)
201
+ &this->usb_task_handle_);
202
+
203
+ if (this->usb_task_handle_ == nullptr) {
204
+ ESP_LOGE(TAG, "Failed to create USB task");
205
+ this->mark_failed();
206
+ }
207
+ }
208
+
209
+ void USBClient::usb_task_fn(void *arg) {
210
+ auto *client = static_cast<USBClient *>(arg);
211
+ client->usb_task_loop();
212
+ }
213
+
214
+ void USBClient::usb_task_loop() {
215
+ while (true) {
216
+ usb_host_client_handle_events(this->handle_, portMAX_DELAY);
175
217
  }
176
218
  }
177
219
 
178
220
  void USBClient::loop() {
221
+ // Process any events from the USB task
222
+ UsbEvent *event;
223
+ while ((event = this->event_queue.pop()) != nullptr) {
224
+ switch (event->type) {
225
+ case EVENT_DEVICE_NEW:
226
+ this->on_opened(event->data.device_new.address);
227
+ break;
228
+ case EVENT_DEVICE_GONE:
229
+ this->on_removed(event->data.device_gone.handle);
230
+ break;
231
+ }
232
+ // Return event to pool for reuse
233
+ this->event_pool.release(event);
234
+ }
235
+
236
+ // Log dropped events periodically
237
+ uint16_t dropped = this->event_queue.get_and_reset_dropped_count();
238
+ if (dropped > 0) {
239
+ ESP_LOGW(TAG, "Dropped %u USB events due to queue overflow", dropped);
240
+ }
241
+
179
242
  switch (this->state_) {
180
243
  case USB_CLIENT_OPEN: {
181
244
  int err;
@@ -228,7 +291,6 @@ void USBClient::loop() {
228
291
  }
229
292
 
230
293
  default:
231
- usb_host_client_handle_events(this->handle_, 0);
232
294
  break;
233
295
  }
234
296
  }
@@ -245,6 +307,7 @@ void USBClient::on_removed(usb_device_handle_t handle) {
245
307
  }
246
308
  }
247
309
 
310
+ // CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)
248
311
  static void control_callback(const usb_transfer_t *xfer) {
249
312
  auto *trq = static_cast<TransferRequest *>(xfer->context);
250
313
  trq->status.error_code = xfer->status;
@@ -252,22 +315,55 @@ static void control_callback(const usb_transfer_t *xfer) {
252
315
  trq->status.endpoint = xfer->bEndpointAddress;
253
316
  trq->status.data = xfer->data_buffer;
254
317
  trq->status.data_len = xfer->actual_num_bytes;
255
- if (trq->callback != nullptr)
318
+
319
+ // Execute callback in USB task context
320
+ if (trq->callback != nullptr) {
256
321
  trq->callback(trq->status);
322
+ }
323
+
324
+ // Release transfer slot immediately in USB task
325
+ // The release_trq() uses thread-safe atomic operations
257
326
  trq->client->release_trq(trq);
258
327
  }
259
328
 
329
+ // THREAD CONTEXT: Called from both USB task and main loop threads (multi-consumer)
330
+ // - USB task: USB UART input callbacks restart transfers for immediate data reception
331
+ // - Main loop: Output transfers and flow-controlled input restarts after consuming data
332
+ //
333
+ // THREAD SAFETY: Lock-free using atomic compare-and-swap on bitmask
334
+ // This multi-threaded access is intentional for performance - USB task can
335
+ // immediately restart transfers without waiting for main loop scheduling.
260
336
  TransferRequest *USBClient::get_trq_() {
261
- if (this->trq_pool_.empty()) {
262
- ESP_LOGE(TAG, "Too many requests queued");
263
- return nullptr;
337
+ trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_relaxed);
338
+
339
+ // Find first available slot (bit = 0) and try to claim it atomically
340
+ // We use a while loop to allow retrying the same slot after CAS failure
341
+ size_t i = 0;
342
+ while (i != MAX_REQUESTS) {
343
+ if (mask & (static_cast<trq_bitmask_t>(1) << i)) {
344
+ // Slot is in use, move to next slot
345
+ i++;
346
+ continue;
347
+ }
348
+
349
+ // Slot i appears available, try to claim it atomically
350
+ trq_bitmask_t desired = mask | (static_cast<trq_bitmask_t>(1) << i); // Set bit i to mark as in-use
351
+
352
+ if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order_acquire, std::memory_order_relaxed)) {
353
+ // Successfully claimed slot i - prepare the TransferRequest
354
+ auto *trq = &this->requests_[i];
355
+ trq->transfer->context = trq;
356
+ trq->transfer->device_handle = this->device_handle_;
357
+ return trq;
358
+ }
359
+ // CAS failed - another thread modified the bitmask
360
+ // mask was already updated by compare_exchange_weak with the current value
361
+ // No need to reload - the CAS already did that for us
362
+ i = 0;
264
363
  }
265
- auto *trq = this->trq_pool_.front();
266
- this->trq_pool_.pop_front();
267
- trq->client = this;
268
- trq->transfer->context = trq;
269
- trq->transfer->device_handle = this->device_handle_;
270
- return trq;
364
+
365
+ ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS);
366
+ return nullptr;
271
367
  }
272
368
  void USBClient::disconnect() {
273
369
  this->on_disconnected();
@@ -280,6 +376,8 @@ void USBClient::disconnect() {
280
376
  this->device_addr_ = -1;
281
377
  }
282
378
 
379
+ // THREAD CONTEXT: Called from main loop thread only
380
+ // - Used for device configuration and control operations
283
381
  bool USBClient::control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index,
284
382
  const transfer_cb_t &callback, const std::vector<uint8_t> &data) {
285
383
  auto *trq = this->get_trq_();
@@ -315,6 +413,7 @@ bool USBClient::control_transfer(uint8_t type, uint8_t request, uint16_t value,
315
413
  return true;
316
414
  }
317
415
 
416
+ // CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)
318
417
  static void transfer_callback(usb_transfer_t *xfer) {
319
418
  auto *trq = static_cast<TransferRequest *>(xfer->context);
320
419
  trq->status.error_code = xfer->status;
@@ -322,12 +421,24 @@ static void transfer_callback(usb_transfer_t *xfer) {
322
421
  trq->status.endpoint = xfer->bEndpointAddress;
323
422
  trq->status.data = xfer->data_buffer;
324
423
  trq->status.data_len = xfer->actual_num_bytes;
325
- if (trq->callback != nullptr)
424
+
425
+ // Always execute callback in USB task context
426
+ // Callbacks should be fast and non-blocking (e.g., copy data to queue)
427
+ if (trq->callback != nullptr) {
326
428
  trq->callback(trq->status);
429
+ }
430
+
431
+ // Release transfer slot AFTER callback completes to prevent slot exhaustion
432
+ // This is critical for high-throughput transfers (e.g., USB UART at 115200 baud)
433
+ // The callback has finished accessing xfer->data_buffer, so it's safe to release
434
+ // The release_trq() uses thread-safe atomic operations
327
435
  trq->client->release_trq(trq);
328
436
  }
329
437
  /**
330
438
  * Performs a transfer input operation.
439
+ * THREAD CONTEXT: Called from both USB task and main loop threads!
440
+ * - USB task: USB UART input callbacks call start_input() which calls this
441
+ * - Main loop: Initial setup and other components
331
442
  *
332
443
  * @param ep_address The endpoint address.
333
444
  * @param callback The callback function to be called when the transfer is complete.
@@ -354,6 +465,9 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
354
465
 
355
466
  /**
356
467
  * Performs an output transfer operation.
468
+ * THREAD CONTEXT: Called from main loop thread only
469
+ * - USB UART output uses defer() to ensure main loop context
470
+ * - Modbus and other components call from loop()
357
471
  *
358
472
  * @param ep_address The endpoint address.
359
473
  * @param callback The callback function to be called when the transfer is complete.
@@ -386,7 +500,28 @@ void USBClient::dump_config() {
386
500
  " Product id %04X",
387
501
  this->vid_, this->pid_);
388
502
  }
389
- void USBClient::release_trq(TransferRequest *trq) { this->trq_pool_.push_back(trq); }
503
+ // THREAD CONTEXT: Called from both USB task and main loop threads
504
+ // - USB task: Immediately after transfer callback completes
505
+ // - Main loop: When transfer submission fails
506
+ //
507
+ // THREAD SAFETY: Lock-free using atomic AND to clear bit
508
+ // Thread-safe atomic operation allows multi-threaded deallocation
509
+ void USBClient::release_trq(TransferRequest *trq) {
510
+ if (trq == nullptr)
511
+ return;
512
+
513
+ // Calculate index from pointer arithmetic
514
+ size_t index = trq - this->requests_;
515
+ if (index >= MAX_REQUESTS) {
516
+ ESP_LOGE(TAG, "Invalid TransferRequest pointer");
517
+ return;
518
+ }
519
+
520
+ // Atomically clear bit i to mark slot as available
521
+ // fetch_and with inverted bitmask clears the bit atomically
522
+ trq_bitmask_t bit = static_cast<trq_bitmask_t>(1) << index;
523
+ this->trq_in_use_.fetch_and(static_cast<trq_bitmask_t>(~bit), std::memory_order_release);
524
+ }
390
525
 
391
526
  } // namespace usb_host
392
527
  } // namespace esphome
@@ -1,5 +1,5 @@
1
1
  // Should not be needed, but it's required to pass CI clang-tidy checks
2
- #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
2
+ #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
3
3
  #include "usb_host.h"
4
4
  #include <cinttypes>
5
5
  #include "esphome/core/log.h"
@@ -24,7 +24,6 @@ usb_uart_ns = cg.esphome_ns.namespace("usb_uart")
24
24
  USBUartComponent = usb_uart_ns.class_("USBUartComponent", Component)
25
25
  USBUartChannel = usb_uart_ns.class_("USBUartChannel", UARTComponent)
26
26
 
27
-
28
27
  UARTParityOptions = usb_uart_ns.enum("UARTParityOptions")
29
28
  UART_PARITY_OPTIONS = {
30
29
  "NONE": UARTParityOptions.UART_CONFIG_PARITY_NONE,
@@ -1,4 +1,4 @@
1
- #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
1
+ #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
2
2
  #include "usb_uart.h"
3
3
  #include "usb/usb_host.h"
4
4
  #include "esphome/core/log.h"
@@ -16,12 +16,12 @@ using namespace bytebuffer;
16
16
  void USBUartTypeCH34X::enable_channels() {
17
17
  // enable the channels
18
18
  for (auto channel : this->channels_) {
19
- if (!channel->initialised_)
19
+ if (!channel->initialised_.load())
20
20
  continue;
21
21
  usb_host::transfer_cb_t callback = [=](const usb_host::TransferStatus &status) {
22
22
  if (!status.success) {
23
23
  ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
24
- channel->initialised_ = false;
24
+ channel->initialised_.store(false);
25
25
  }
26
26
  };
27
27
 
@@ -48,7 +48,7 @@ void USBUartTypeCH34X::enable_channels() {
48
48
  auto factor = static_cast<uint8_t>(clk / baud_rate);
49
49
  if (factor == 0 || factor == 0xFF) {
50
50
  ESP_LOGE(TAG, "Invalid baud rate %" PRIu32, baud_rate);
51
- channel->initialised_ = false;
51
+ channel->initialised_.store(false);
52
52
  continue;
53
53
  }
54
54
  if ((clk / factor - baud_rate) > (baud_rate - clk / (factor + 1)))
@@ -1,4 +1,4 @@
1
- #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
1
+ #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
2
2
  #include "usb_uart.h"
3
3
  #include "usb/usb_host.h"
4
4
  #include "esphome/core/log.h"
@@ -100,12 +100,12 @@ std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev
100
100
  void USBUartTypeCP210X::enable_channels() {
101
101
  // enable the channels
102
102
  for (auto channel : this->channels_) {
103
- if (!channel->initialised_)
103
+ if (!channel->initialised_.load())
104
104
  continue;
105
105
  usb_host::transfer_cb_t callback = [=](const usb_host::TransferStatus &status) {
106
106
  if (!status.success) {
107
107
  ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
108
- channel->initialised_ = false;
108
+ channel->initialised_.store(false);
109
109
  }
110
110
  };
111
111
  this->control_transfer(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, IFC_ENABLE, 1, channel->index_, callback);