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.
- esphome/__main__.py +94 -31
- esphome/address_cache.py +142 -0
- esphome/automation.py +130 -32
- esphome/build_gen/platformio.py +1 -3
- esphome/codegen.py +1 -0
- esphome/components/animation/animation.cpp +2 -2
- esphome/components/api/__init__.py +166 -3
- esphome/components/api/api_connection.cpp +84 -41
- esphome/components/api/api_connection.h +22 -16
- esphome/components/api/api_frame_helper.cpp +33 -19
- esphome/components/api/api_frame_helper.h +19 -4
- esphome/components/api/api_frame_helper_noise.cpp +41 -53
- esphome/components/api/api_frame_helper_noise.h +1 -1
- esphome/components/api/api_frame_helper_plaintext.cpp +22 -31
- esphome/components/api/api_frame_helper_plaintext.h +1 -1
- esphome/components/api/api_pb2.cpp +189 -15
- esphome/components/api/api_pb2.h +132 -20
- esphome/components/api/api_pb2_dump.cpp +97 -9
- esphome/components/api/api_pb2_service.cpp +118 -160
- esphome/components/api/api_pb2_service.h +31 -3
- esphome/components/api/api_server.cpp +68 -10
- esphome/components/api/api_server.h +32 -4
- esphome/components/api/custom_api_device.h +8 -8
- esphome/components/api/homeassistant_service.h +123 -6
- esphome/components/api/proto.h +6 -2
- esphome/components/api/user_services.h +2 -2
- esphome/components/as7341/sensor.py +1 -1
- esphome/components/audio/__init__.py +1 -1
- esphome/components/audio/audio.cpp +1 -1
- esphome/components/audio/audio_decoder.cpp +9 -9
- esphome/components/bl0906/bl0906.cpp +2 -2
- esphome/components/bl0942/bl0942.cpp +2 -2
- esphome/components/ble_client/__init__.py +1 -1
- esphome/components/bluetooth_proxy/__init__.py +4 -30
- esphome/components/bluetooth_proxy/bluetooth_connection.cpp +11 -4
- esphome/components/bluetooth_proxy/bluetooth_connection.h +2 -2
- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +2 -2
- esphome/components/camera_encoder/__init__.py +2 -4
- esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +4 -2
- esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +3 -1
- esphome/components/canbus/canbus.cpp +7 -5
- esphome/components/canbus/canbus.h +7 -7
- esphome/components/captive_portal/__init__.py +18 -1
- esphome/components/captive_portal/captive_portal.cpp +40 -46
- esphome/components/captive_portal/captive_portal.h +20 -22
- esphome/components/captive_portal/dns_server_esp32_idf.cpp +205 -0
- esphome/components/captive_portal/dns_server_esp32_idf.h +27 -0
- esphome/components/ccs811/ccs811.cpp +1 -1
- esphome/components/climate/climate.cpp +10 -7
- esphome/components/cm1106/cm1106.cpp +1 -1
- esphome/components/copy/lock/copy_lock.cpp +1 -1
- esphome/components/cover/cover.cpp +1 -0
- esphome/components/daikin_arc/daikin_arc.cpp +19 -12
- esphome/components/dashboard_import/dashboard_import.cpp +1 -1
- esphome/components/dashboard_import/dashboard_import.h +1 -1
- esphome/components/deep_sleep/__init__.py +9 -2
- esphome/components/deep_sleep/deep_sleep_component.h +11 -9
- esphome/components/deep_sleep/deep_sleep_esp32.cpp +51 -27
- esphome/components/ektf2232/touchscreen/__init__.py +8 -5
- esphome/components/ektf2232/touchscreen/ektf2232.cpp +4 -4
- esphome/components/ektf2232/touchscreen/ektf2232.h +2 -2
- esphome/components/epaper_spi/__init__.py +1 -0
- esphome/components/epaper_spi/display.py +80 -0
- esphome/components/epaper_spi/epaper_spi.cpp +227 -0
- esphome/components/epaper_spi/epaper_spi.h +93 -0
- esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp +42 -0
- esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h +45 -0
- esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +135 -0
- esphome/components/epaper_spi/epaper_spi_spectra_e6.h +23 -0
- esphome/components/es7210/es7210.cpp +3 -3
- esphome/components/esp32/__init__.py +256 -340
- esphome/components/esp32/boards.py +81 -0
- esphome/components/esp32/preferences.cpp +23 -17
- esphome/components/esp32_ble/__init__.py +167 -44
- esphome/components/esp32_ble/ble.cpp +47 -3
- esphome/components/esp32_ble/ble.h +18 -0
- esphome/components/esp32_ble/ble_advertising.cpp +7 -3
- esphome/components/esp32_ble/ble_advertising.h +4 -0
- esphome/components/esp32_ble/ble_uuid.cpp +16 -42
- esphome/components/esp32_ble_beacon/__init__.py +3 -4
- esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +0 -4
- esphome/components/esp32_ble_client/ble_client_base.cpp +14 -12
- esphome/components/esp32_ble_server/__init__.py +28 -14
- esphome/components/esp32_ble_server/ble_characteristic.cpp +67 -57
- esphome/components/esp32_ble_server/ble_characteristic.h +27 -16
- esphome/components/esp32_ble_server/ble_descriptor.cpp +4 -3
- esphome/components/esp32_ble_server/ble_descriptor.h +13 -9
- esphome/components/esp32_ble_server/ble_server.cpp +59 -24
- esphome/components/esp32_ble_server/ble_server.h +38 -20
- esphome/components/esp32_ble_server/ble_server_automations.cpp +49 -33
- esphome/components/esp32_ble_server/ble_server_automations.h +39 -24
- esphome/components/esp32_ble_tracker/__init__.py +25 -80
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +2 -8
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +0 -3
- esphome/components/esp32_camera/__init__.py +1 -3
- esphome/components/esp32_can/esp32_can.cpp +22 -4
- esphome/components/esp32_can/esp32_can.h +3 -0
- esphome/components/esp32_hosted/__init__.py +2 -1
- esphome/components/esp32_improv/esp32_improv_component.cpp +135 -65
- esphome/components/esp32_improv/esp32_improv_component.h +7 -1
- esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
- esphome/components/esp8266/__init__.py +3 -3
- esphome/components/esphome/ota/__init__.py +21 -2
- esphome/components/esphome/ota/ota_esphome.cpp +456 -146
- esphome/components/esphome/ota/ota_esphome.h +49 -2
- esphome/components/ethernet/__init__.py +39 -22
- esphome/components/ethernet/ethernet_component.cpp +28 -5
- esphome/components/ethernet/ethernet_component.h +5 -1
- esphome/components/external_components/__init__.py +8 -6
- esphome/components/fingerprint_grow/fingerprint_grow.cpp +1 -1
- esphome/components/fingerprint_grow/fingerprint_grow.h +2 -1
- esphome/components/font/__init__.py +5 -5
- esphome/components/graph/graph.cpp +1 -1
- esphome/components/graphical_display_menu/graphical_display_menu.cpp +3 -2
- esphome/components/haier/hon_climate.cpp +2 -2
- esphome/components/haier/hon_climate.h +1 -1
- esphome/components/hdc1080/hdc1080.cpp +42 -34
- esphome/components/hdc1080/hdc1080.h +1 -3
- esphome/components/homeassistant/number/homeassistant_number.cpp +2 -2
- esphome/components/homeassistant/switch/homeassistant_switch.cpp +2 -2
- esphome/components/http_request/__init__.py +3 -3
- esphome/components/htu21d/htu21d.cpp +13 -18
- esphome/components/htu21d/htu21d.h +1 -1
- esphome/components/i2s_audio/__init__.py +1 -2
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
- esphome/components/ili9xxx/ili9xxx_display.cpp +2 -2
- esphome/components/improv_serial/improv_serial_component.cpp +12 -15
- esphome/components/improv_serial/improv_serial_component.h +6 -8
- esphome/components/json/json_util.cpp +42 -44
- esphome/components/json/json_util.h +57 -0
- esphome/components/kamstrup_kmp/kamstrup_kmp.cpp +2 -2
- esphome/components/key_collector/key_collector.h +4 -4
- esphome/components/libretiny/__init__.py +6 -6
- esphome/components/libretiny/preferences.cpp +23 -16
- esphome/components/light/light_call.cpp +98 -120
- esphome/components/light/light_call.h +17 -7
- esphome/components/lm75b/__init__.py +0 -0
- esphome/components/lm75b/lm75b.cpp +39 -0
- esphome/components/lm75b/lm75b.h +19 -0
- esphome/components/lm75b/sensor.py +34 -0
- esphome/components/lock/lock.h +12 -6
- esphome/components/logger/__init__.py +15 -27
- esphome/components/logger/logger.cpp +10 -20
- esphome/components/logger/logger.h +105 -62
- esphome/components/logger/logger_esp32.cpp +0 -48
- esphome/components/logger/logger_zephyr.cpp +2 -3
- esphome/components/logger/select/logger_level_select.cpp +6 -7
- esphome/components/logger/select/logger_level_select.h +7 -0
- esphome/components/ltr501/ltr501.cpp +7 -6
- esphome/components/ltr_als_ps/ltr_als_ps.cpp +7 -6
- esphome/components/matrix_keypad/matrix_keypad.h +4 -4
- esphome/components/max7219digit/max7219digit.cpp +1 -1
- esphome/components/mcp23xxx_base/mcp23xxx_base.h +3 -3
- esphome/components/mcp2515/mcp2515.cpp +31 -3
- esphome/components/mcp2515/mcp2515_defs.h +3 -1
- esphome/components/md5/md5.cpp +0 -26
- esphome/components/md5/md5.h +10 -20
- esphome/components/mdns/__init__.py +93 -19
- esphome/components/mdns/mdns_component.cpp +57 -94
- esphome/components/mdns/mdns_component.h +35 -11
- esphome/components/mdns/mdns_esp32.cpp +7 -13
- esphome/components/mdns/mdns_esp8266.cpp +7 -7
- esphome/components/mdns/mdns_libretiny.cpp +3 -4
- esphome/components/mdns/mdns_rp2040.cpp +3 -4
- esphome/components/mipi/__init__.py +1 -5
- esphome/components/mipi_spi/display.py +24 -8
- esphome/components/mipi_spi/mipi_spi.h +3 -3
- esphome/components/mixer/speaker/mixer_speaker.cpp +3 -3
- esphome/components/mmc5603/mmc5603.cpp +3 -3
- esphome/components/modbus/modbus.cpp +27 -13
- esphome/components/modbus/modbus.h +5 -3
- esphome/components/modbus/modbus_definitions.h +86 -0
- esphome/components/modbus_controller/__init__.py +29 -1
- esphome/components/modbus_controller/const.py +4 -0
- esphome/components/modbus_controller/modbus_controller.cpp +38 -13
- esphome/components/modbus_controller/modbus_controller.h +18 -29
- esphome/components/mpr121/mpr121.cpp +41 -42
- esphome/components/mpr121/mpr121.h +0 -1
- esphome/components/nau7802/nau7802.cpp +2 -2
- esphome/components/network/__init__.py +7 -3
- esphome/components/nextion/display.py +4 -4
- esphome/components/nextion/nextion.cpp +8 -8
- esphome/components/number/__init__.py +2 -0
- esphome/components/number/number_call.cpp +23 -12
- esphome/components/number/number_call.h +5 -0
- esphome/components/online_image/bmp_image.cpp +2 -1
- esphome/components/online_image/jpeg_image.cpp +4 -2
- esphome/components/opentherm/opentherm.cpp +5 -5
- esphome/components/opentherm/opentherm.h +3 -3
- esphome/components/openthread/openthread.cpp +11 -10
- esphome/components/openthread/openthread.h +0 -1
- esphome/components/ota/ota_backend.h +1 -0
- esphome/components/packages/__init__.py +10 -8
- esphome/components/packet_transport/packet_transport.cpp +2 -0
- esphome/components/pid/pid_controller.cpp +1 -1
- esphome/components/prometheus/prometheus_handler.cpp +239 -239
- esphome/components/psram/__init__.py +30 -28
- esphome/components/qmc5883l/qmc5883l.cpp +15 -0
- esphome/components/qmc5883l/qmc5883l.h +3 -0
- esphome/components/qmc5883l/sensor.py +31 -12
- esphome/components/remote_base/gobox_protocol.cpp +3 -3
- esphome/components/remote_receiver/__init__.py +14 -2
- esphome/components/remote_receiver/{remote_receiver_esp8266.cpp → remote_receiver.cpp} +2 -2
- esphome/components/remote_receiver/remote_receiver.h +4 -0
- esphome/components/remote_receiver/remote_receiver_esp32.cpp +18 -1
- esphome/components/remote_transmitter/__init__.py +2 -2
- esphome/components/remote_transmitter/remote_transmitter.cpp +103 -0
- esphome/components/rp2040/__init__.py +11 -11
- esphome/components/rtttl/rtttl.cpp +2 -2
- esphome/components/scd30/sensor.py +1 -1
- esphome/components/script/__init__.py +1 -1
- esphome/components/script/script.h +7 -7
- esphome/components/select/select.cpp +5 -4
- esphome/components/select/select_call.cpp +1 -1
- esphome/components/sensirion_common/i2c_sensirion.cpp +2 -1
- esphome/components/sensor/__init__.py +2 -0
- esphome/components/sha256/__init__.py +22 -0
- esphome/components/sha256/sha256.cpp +116 -0
- esphome/components/sha256/sha256.h +60 -0
- esphome/components/socket/lwip_raw_tcp_impl.cpp +34 -6
- esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
- esphome/components/spi/__init__.py +0 -3
- esphome/components/split_buffer/__init__.py +5 -0
- esphome/components/split_buffer/split_buffer.cpp +133 -0
- esphome/components/split_buffer/split_buffer.h +40 -0
- esphome/components/sps30/sps30.cpp +14 -10
- esphome/components/sps30/sps30.h +2 -0
- esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
- esphome/components/st7789v/st7789v.cpp +3 -2
- esphome/components/statsd/statsd.cpp +1 -1
- esphome/components/substitutions/__init__.py +3 -1
- esphome/components/substitutions/jinja.py +13 -3
- esphome/components/sx126x/__init__.py +16 -0
- esphome/components/sx126x/sx126x.cpp +15 -1
- esphome/components/sx126x/sx126x.h +9 -1
- esphome/components/sx126x/sx126x_reg.h +2 -0
- esphome/components/text_sensor/text_sensor.cpp +16 -0
- esphome/components/text_sensor/text_sensor.h +3 -10
- esphome/components/tormatic/tormatic_cover.cpp +1 -1
- esphome/components/tuya/select/tuya_select.cpp +1 -1
- esphome/components/tuya/tuya.cpp +29 -4
- esphome/components/uart/__init__.py +37 -27
- esphome/components/uart/uart.h +6 -0
- esphome/components/uart/uart_component.cpp +8 -0
- esphome/components/uart/uart_component.h +28 -0
- esphome/components/uart/uart_component_esp_idf.cpp +64 -10
- esphome/components/uart/uart_component_esp_idf.h +5 -2
- esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
- esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
- esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
- esphome/components/usb_host/__init__.py +12 -2
- esphome/components/usb_host/usb_host.h +89 -14
- esphome/components/usb_host/usb_host_client.cpp +157 -22
- esphome/components/usb_host/usb_host_component.cpp +1 -1
- esphome/components/usb_uart/__init__.py +0 -1
- esphome/components/usb_uart/ch34x.cpp +4 -4
- esphome/components/usb_uart/cp210x.cpp +3 -3
- esphome/components/usb_uart/usb_uart.cpp +88 -32
- esphome/components/usb_uart/usb_uart.h +30 -6
- esphome/components/valve/valve.cpp +1 -0
- esphome/components/veml7700/veml7700.cpp +7 -6
- esphome/components/version/version_text_sensor.cpp +2 -1
- esphome/components/voice_assistant/voice_assistant.cpp +3 -2
- esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
- esphome/components/web_server/list_entities.cpp +3 -4
- esphome/components/web_server/list_entities.h +8 -10
- esphome/components/web_server/ota/__init__.py +1 -1
- esphome/components/web_server/ota/ota_web_server.cpp +9 -3
- esphome/components/web_server/web_server.cpp +509 -404
- esphome/components/web_server/web_server.h +5 -6
- esphome/components/web_server/web_server_v1.cpp +21 -19
- esphome/components/web_server_base/__init__.py +5 -2
- esphome/components/web_server_base/web_server_base.h +27 -7
- esphome/components/web_server_idf/__init__.py +1 -1
- esphome/components/web_server_idf/multipart.cpp +2 -2
- esphome/components/web_server_idf/multipart.h +2 -2
- esphome/components/web_server_idf/utils.cpp +2 -2
- esphome/components/web_server_idf/utils.h +2 -2
- esphome/components/web_server_idf/web_server_idf.cpp +118 -26
- esphome/components/web_server_idf/web_server_idf.h +12 -10
- esphome/components/wifi/__init__.py +13 -11
- esphome/components/wifi/wifi_component.cpp +74 -56
- esphome/components/wifi/wifi_component.h +4 -4
- esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
- esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
- esphome/components/wireguard/__init__.py +1 -1
- esphome/components/wts01/__init__.py +0 -0
- esphome/components/wts01/sensor.py +41 -0
- esphome/components/wts01/wts01.cpp +91 -0
- esphome/components/wts01/wts01.h +27 -0
- esphome/components/zephyr/__init__.py +5 -5
- esphome/components/zwave_proxy/__init__.py +43 -0
- esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
- esphome/components/zwave_proxy/zwave_proxy.h +93 -0
- esphome/config.py +79 -24
- esphome/config_validation.py +13 -15
- esphome/const.py +9 -2
- esphome/core/__init__.py +33 -22
- esphome/core/component.cpp +28 -18
- esphome/core/component_iterator.h +2 -1
- esphome/core/config.py +15 -15
- esphome/core/defines.h +21 -0
- esphome/core/entity_helpers.py +9 -6
- esphome/core/hash_base.h +56 -0
- esphome/core/helpers.cpp +19 -3
- esphome/core/helpers.h +26 -0
- esphome/core/scheduler.cpp +5 -21
- esphome/core/scheduler.h +19 -8
- esphome/core/string_ref.h +1 -1
- esphome/core/time.cpp +5 -5
- esphome/cpp_generator.py +4 -29
- esphome/dashboard/const.py +21 -4
- esphome/dashboard/core.py +10 -8
- esphome/dashboard/dns.py +15 -0
- esphome/dashboard/entries.py +15 -21
- esphome/dashboard/models.py +76 -0
- esphome/dashboard/settings.py +7 -7
- esphome/dashboard/status/mdns.py +46 -2
- esphome/dashboard/web_server.py +367 -93
- esphome/espota2.py +112 -32
- esphome/external_files.py +6 -7
- esphome/git.py +8 -0
- esphome/helpers.py +124 -77
- esphome/loader.py +8 -9
- esphome/pins.py +2 -2
- esphome/platformio_api.py +56 -18
- esphome/storage_json.py +26 -21
- esphome/types.py +30 -2
- esphome/util.py +32 -16
- esphome/vscode.py +8 -8
- esphome/wizard.py +10 -10
- esphome/writer.py +50 -15
- esphome/yaml_util.py +37 -31
- esphome/zeroconf.py +12 -3
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/METADATA +12 -12
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/RECORD +340 -320
- esphome/components/event_emitter/__init__.py +0 -5
- esphome/components/event_emitter/event_emitter.cpp +0 -14
- esphome/components/event_emitter/event_emitter.h +0 -63
- esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
- esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
- esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
- esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
- esphome/components/uart/uart_component_esp32_arduino.h +0 -60
- esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
- esphome/core/string_ref.cpp +0 -12
- esphome/dashboard/util/file.py +0 -63
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/WHEEL +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/entry_points.txt +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/licenses/LICENSE +0 -0
- {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 (
|
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 (
|
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 (
|
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 <
|
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 =
|
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)
|
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() {
|
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
|
-
|
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
|
-
|
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
|
-
|
153
|
-
|
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
|
-
|
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
|
173
|
-
|
174
|
-
|
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
|
-
|
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
|
-
|
262
|
-
|
263
|
-
|
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
|
-
|
266
|
-
|
267
|
-
|
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
|
-
|
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
|
-
|
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_
|
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_
|
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_
|
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);
|