Merge pull request #11857 from esphome/bump-2025.10.5
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.12) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.11) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Has been cancelled
CI / Component test ${{ matrix.file }} (push) Has been cancelled
CI / Split components for intelligent grouping (40 weighted per batch) (push) Has been cancelled
CI / Test components batch (${{ matrix.components }}) (push) Has been cancelled
CI / pre-commit.ci lite (push) Has been cancelled
CI / CI Status (push) Has been cancelled

2025.10.5
This commit is contained in:
Jonathan Swoboda
2025-11-11 20:09:41 -05:00
committed by GitHub
12 changed files with 72 additions and 57 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = 2025.10.4 PROJECT_NUMBER = 2025.10.5
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a
+1
View File
@@ -11,4 +11,5 @@ CONF_DRAW_ROUNDING = "draw_rounding"
CONF_ON_RECEIVE = "on_receive" CONF_ON_RECEIVE = "on_receive"
CONF_ON_STATE_CHANGE = "on_state_change" CONF_ON_STATE_CHANGE = "on_state_change"
CONF_REQUEST_HEADERS = "request_headers" CONF_REQUEST_HEADERS = "request_headers"
CONF_ROWS = "rows"
CONF_USE_PSRAM = "use_psram" CONF_USE_PSRAM = "use_psram"
-1
View File
@@ -504,7 +504,6 @@ CONF_RESUME_ON_INPUT = "resume_on_input"
CONF_RIGHT_BUTTON = "right_button" CONF_RIGHT_BUTTON = "right_button"
CONF_ROLLOVER = "rollover" CONF_ROLLOVER = "rollover"
CONF_ROOT_BACK_BTN = "root_back_btn" CONF_ROOT_BACK_BTN = "root_back_btn"
CONF_ROWS = "rows"
CONF_SCALE_LINES = "scale_lines" CONF_SCALE_LINES = "scale_lines"
CONF_SCROLLBAR_MODE = "scrollbar_mode" CONF_SCROLLBAR_MODE = "scrollbar_mode"
CONF_SELECTED_INDEX = "selected_index" CONF_SELECTED_INDEX = "selected_index"
+13 -11
View File
@@ -156,6 +156,7 @@ bool LvPageType::is_showing() const { return this->parent_->get_current_page() =
void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) { void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
auto width = lv_area_get_width(area); auto width = lv_area_get_width(area);
auto height = lv_area_get_height(area); auto height = lv_area_get_height(area);
auto height_rounded = (height + this->draw_rounding - 1) / this->draw_rounding * this->draw_rounding;
auto x1 = area->x1; auto x1 = area->x1;
auto y1 = area->y1; auto y1 = area->y1;
lv_color_t *dst = this->rotate_buf_; lv_color_t *dst = this->rotate_buf_;
@@ -163,13 +164,13 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
case display::DISPLAY_ROTATION_90_DEGREES: case display::DISPLAY_ROTATION_90_DEGREES:
for (lv_coord_t x = height; x-- != 0;) { for (lv_coord_t x = height; x-- != 0;) {
for (lv_coord_t y = 0; y != width; y++) { for (lv_coord_t y = 0; y != width; y++) {
dst[y * height + x] = *ptr++; dst[y * height_rounded + x] = *ptr++;
} }
} }
y1 = x1; y1 = x1;
x1 = this->disp_drv_.ver_res - area->y1 - height; x1 = this->disp_drv_.ver_res - area->y1 - height;
width = height; height = width;
height = lv_area_get_width(area); width = height_rounded;
break; break;
case display::DISPLAY_ROTATION_180_DEGREES: case display::DISPLAY_ROTATION_180_DEGREES:
@@ -185,13 +186,13 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
case display::DISPLAY_ROTATION_270_DEGREES: case display::DISPLAY_ROTATION_270_DEGREES:
for (lv_coord_t x = 0; x != height; x++) { for (lv_coord_t x = 0; x != height; x++) {
for (lv_coord_t y = width; y-- != 0;) { for (lv_coord_t y = width; y-- != 0;) {
dst[y * height + x] = *ptr++; dst[y * height_rounded + x] = *ptr++;
} }
} }
x1 = y1; x1 = y1;
y1 = this->disp_drv_.hor_res - area->x1 - width; y1 = this->disp_drv_.hor_res - area->x1 - width;
width = height; height = width;
height = lv_area_get_width(area); width = height_rounded;
break; break;
default: default:
@@ -435,8 +436,10 @@ LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buf
void LvglComponent::setup() { void LvglComponent::setup() {
auto *display = this->displays_[0]; auto *display = this->displays_[0];
auto width = display->get_width(); auto rounding = this->draw_rounding;
auto height = display->get_height(); // cater for displays with dimensions that don't divide by the required rounding
auto width = (display->get_width() + rounding - 1) / rounding * rounding;
auto height = (display->get_height() + rounding - 1) / rounding * rounding;
auto frac = this->buffer_frac_; auto frac = this->buffer_frac_;
if (frac == 0) if (frac == 0)
frac = 1; frac = 1;
@@ -461,9 +464,8 @@ void LvglComponent::setup() {
} }
this->buffer_frac_ = frac; this->buffer_frac_ = frac;
lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buffer_pixels); lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buffer_pixels);
this->disp_drv_.hor_res = width; this->disp_drv_.hor_res = display->get_width();
this->disp_drv_.ver_res = height; this->disp_drv_.ver_res = display->get_height();
// this->setup_driver_(display->get_width(), display->get_height());
lv_disp_drv_update(this->disp_, &this->disp_drv_); lv_disp_drv_update(this->disp_, &this->disp_drv_);
this->rotation = display->get_rotation(); this->rotation = display->get_rotation();
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) { if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
@@ -1,5 +1,6 @@
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.const import CONF_ROWS
from esphome.components.key_provider import KeyProvider from esphome.components.key_provider import KeyProvider
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_ITEMS, CONF_TEXT, CONF_WIDTH from esphome.const import CONF_ID, CONF_ITEMS, CONF_TEXT, CONF_WIDTH
@@ -15,7 +16,6 @@ from ..defines import (
CONF_ONE_CHECKED, CONF_ONE_CHECKED,
CONF_PAD_COLUMN, CONF_PAD_COLUMN,
CONF_PAD_ROW, CONF_PAD_ROW,
CONF_ROWS,
CONF_SELECTED, CONF_SELECTED,
) )
from ..helpers import lvgl_components_required from ..helpers import lvgl_components_required
+1 -1
View File
@@ -1,6 +1,7 @@
from esphome import automation, pins from esphome import automation, pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import key_provider from esphome.components import key_provider
from esphome.components.const import CONF_ROWS
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID
@@ -19,7 +20,6 @@ MatrixKeyTrigger = matrix_keypad_ns.class_(
) )
CONF_KEYPAD_ID = "keypad_id" CONF_KEYPAD_ID = "keypad_id"
CONF_ROWS = "rows"
CONF_COLUMNS = "columns" CONF_COLUMNS = "columns"
CONF_KEYS = "keys" CONF_KEYS = "keys"
CONF_DEBOUNCE_TIME = "debounce_time" CONF_DEBOUNCE_TIME = "debounce_time"
+6 -6
View File
@@ -55,7 +55,7 @@ static const uint8_t USB_DIR_IN = 1 << 7;
static const uint8_t USB_DIR_OUT = 0; static const uint8_t USB_DIR_OUT = 0;
static const size_t SETUP_PACKET_SIZE = 8; static const size_t SETUP_PACKET_SIZE = 8;
static const size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible. static constexpr size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible.
static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be between 1 and 32"); static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be between 1 and 32");
// Select appropriate bitmask type for tracking allocation of TransferRequest slots. // Select appropriate bitmask type for tracking allocation of TransferRequest slots.
@@ -65,6 +65,7 @@ static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be bet
// This is tied to the static_assert above, which enforces MAX_REQUESTS is between 1 and 32. // This is tied to the static_assert above, which enforces MAX_REQUESTS is between 1 and 32.
// If MAX_REQUESTS is increased above 32, this logic and the static_assert must be updated. // If MAX_REQUESTS is increased above 32, this logic and the static_assert must be updated.
using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type; using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type;
static constexpr trq_bitmask_t ALL_REQUESTS_IN_USE = MAX_REQUESTS == 32 ? ~0 : (1 << MAX_REQUESTS) - 1;
static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop
static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples) static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples)
@@ -133,11 +134,11 @@ class USBClient : public Component {
float get_setup_priority() const override { return setup_priority::IO; } float get_setup_priority() const override { return setup_priority::IO; }
void on_opened(uint8_t addr); void on_opened(uint8_t addr);
void on_removed(usb_device_handle_t handle); void on_removed(usb_device_handle_t handle);
void control_transfer_callback(const usb_transfer_t *xfer) const; bool transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length);
void transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length); bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length);
void transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length);
void dump_config() override; void dump_config() override;
void release_trq(TransferRequest *trq); void release_trq(TransferRequest *trq);
trq_bitmask_t get_trq_in_use() const { return trq_in_use_; }
bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback, bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback,
const std::vector<uint8_t> &data = {}); const std::vector<uint8_t> &data = {});
@@ -147,7 +148,6 @@ class USBClient : public Component {
EventPool<UsbEvent, USB_EVENT_QUEUE_SIZE> event_pool; EventPool<UsbEvent, USB_EVENT_QUEUE_SIZE> event_pool;
protected: protected:
bool register_();
TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe) TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe)
virtual void disconnect(); virtual void disconnect();
virtual void on_connected() {} virtual void on_connected() {}
@@ -158,7 +158,7 @@ class USBClient : public Component {
// USB task management // USB task management
static void usb_task_fn(void *arg); static void usb_task_fn(void *arg);
void usb_task_loop(); [[noreturn]] void usb_task_loop() const;
TaskHandle_t usb_task_handle_{nullptr}; TaskHandle_t usb_task_handle_{nullptr};
+27 -27
View File
@@ -188,9 +188,9 @@ void USBClient::setup() {
} }
// Pre-allocate USB transfer buffers for all slots at startup // Pre-allocate USB transfer buffers for all slots at startup
// This avoids any dynamic allocation during runtime // This avoids any dynamic allocation during runtime
for (size_t i = 0; i < MAX_REQUESTS; i++) { for (auto &request : this->requests_) {
usb_host_transfer_alloc(64, 0, &this->requests_[i].transfer); usb_host_transfer_alloc(64, 0, &request.transfer);
this->requests_[i].client = this; // Set once, never changes request.client = this; // Set once, never changes
} }
// Create and start USB task // Create and start USB task
@@ -210,8 +210,7 @@ void USBClient::usb_task_fn(void *arg) {
auto *client = static_cast<USBClient *>(arg); auto *client = static_cast<USBClient *>(arg);
client->usb_task_loop(); client->usb_task_loop();
} }
void USBClient::usb_task_loop() const {
void USBClient::usb_task_loop() {
while (true) { while (true) {
usb_host_client_handle_events(this->handle_, portMAX_DELAY); usb_host_client_handle_events(this->handle_, portMAX_DELAY);
} }
@@ -334,22 +333,23 @@ static void control_callback(const usb_transfer_t *xfer) {
// This multi-threaded access is intentional for performance - USB task can // This multi-threaded access is intentional for performance - USB task can
// immediately restart transfers without waiting for main loop scheduling. // immediately restart transfers without waiting for main loop scheduling.
TransferRequest *USBClient::get_trq_() { TransferRequest *USBClient::get_trq_() {
trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_relaxed); trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_acquire);
// Find first available slot (bit = 0) and try to claim it atomically // Find first available slot (bit = 0) and try to claim it atomically
// We use a while loop to allow retrying the same slot after CAS failure // We use a while loop to allow retrying the same slot after CAS failure
size_t i = 0; for (;;) {
while (i != MAX_REQUESTS) { if (mask == ALL_REQUESTS_IN_USE) {
if (mask & (static_cast<trq_bitmask_t>(1) << i)) { ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS);
// Slot is in use, move to next slot return nullptr;
i++;
continue;
} }
// find the least significant zero bit
trq_bitmask_t lsb = ~mask & (mask + 1);
// Slot i appears available, try to claim it atomically // Slot i appears available, try to claim it atomically
trq_bitmask_t desired = mask | (static_cast<trq_bitmask_t>(1) << i); // Set bit i to mark as in-use trq_bitmask_t desired = mask | lsb;
if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order_acquire, std::memory_order_relaxed)) { if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order::acquire)) {
auto i = __builtin_ctz(lsb); // count trailing zeroes
// Successfully claimed slot i - prepare the TransferRequest // Successfully claimed slot i - prepare the TransferRequest
auto *trq = &this->requests_[i]; auto *trq = &this->requests_[i];
trq->transfer->context = trq; trq->transfer->context = trq;
@@ -358,13 +358,9 @@ TransferRequest *USBClient::get_trq_() {
} }
// CAS failed - another thread modified the bitmask // CAS failed - another thread modified the bitmask
// mask was already updated by compare_exchange_weak with the current value // mask was already updated by compare_exchange_weak with the current value
// No need to reload - the CAS already did that for us }
i = 0;
} }
ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS);
return nullptr;
}
void USBClient::disconnect() { void USBClient::disconnect() {
this->on_disconnected(); this->on_disconnected();
auto err = usb_host_device_close(this->handle_, this->device_handle_); auto err = usb_host_device_close(this->handle_, this->device_handle_);
@@ -446,11 +442,11 @@ static void transfer_callback(usb_transfer_t *xfer) {
* *
* @throws None. * @throws None.
*/ */
void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) { bool USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) {
auto *trq = this->get_trq_(); auto *trq = this->get_trq_();
if (trq == nullptr) { if (trq == nullptr) {
ESP_LOGE(TAG, "Too many requests queued"); ESP_LOGE(TAG, "Too many requests queued");
return; return false;
} }
trq->callback = callback; trq->callback = callback;
trq->transfer->callback = transfer_callback; trq->transfer->callback = transfer_callback;
@@ -460,7 +456,9 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err); ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
this->release_trq(trq); this->release_trq(trq);
return false;
} }
return true;
} }
/** /**
@@ -476,11 +474,11 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
* *
* @throws None. * @throws None.
*/ */
void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) { bool USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) {
auto *trq = this->get_trq_(); auto *trq = this->get_trq_();
if (trq == nullptr) { if (trq == nullptr) {
ESP_LOGE(TAG, "Too many requests queued"); ESP_LOGE(TAG, "Too many requests queued");
return; return false;
} }
trq->callback = callback; trq->callback = callback;
trq->transfer->callback = transfer_callback; trq->transfer->callback = transfer_callback;
@@ -491,7 +489,9 @@ void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback,
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err); ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
this->release_trq(trq); this->release_trq(trq);
return false;
} }
return true;
} }
void USBClient::dump_config() { void USBClient::dump_config() {
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
@@ -505,7 +505,7 @@ void USBClient::dump_config() {
// - Main loop: When transfer submission fails // - Main loop: When transfer submission fails
// //
// THREAD SAFETY: Lock-free using atomic AND to clear bit // THREAD SAFETY: Lock-free using atomic AND to clear bit
// Thread-safe atomic operation allows multi-threaded deallocation // Thread-safe atomic operation allows multithreaded deallocation
void USBClient::release_trq(TransferRequest *trq) { void USBClient::release_trq(TransferRequest *trq) {
if (trq == nullptr) if (trq == nullptr)
return; return;
@@ -517,10 +517,10 @@ void USBClient::release_trq(TransferRequest *trq) {
return; return;
} }
// Atomically clear bit i to mark slot as available // Atomically clear the bit to mark slot as available
// fetch_and with inverted bitmask clears the bit atomically // fetch_and with inverted bitmask clears the bit atomically
trq_bitmask_t bit = static_cast<trq_bitmask_t>(1) << index; trq_bitmask_t mask = ~(static_cast<trq_bitmask_t>(1) << index);
this->trq_in_use_.fetch_and(static_cast<trq_bitmask_t>(~bit), std::memory_order_release); this->trq_in_use_.fetch_and(mask, std::memory_order_release);
} }
} // namespace usb_host } // namespace usb_host
+15 -7
View File
@@ -214,7 +214,7 @@ void USBUartComponent::dump_config() {
} }
} }
void USBUartComponent::start_input(USBUartChannel *channel) { void USBUartComponent::start_input(USBUartChannel *channel) {
if (!channel->initialised_.load() || channel->input_started_.load()) if (!channel->initialised_.load())
return; return;
// THREAD CONTEXT: Called from both USB task and main loop threads // THREAD CONTEXT: Called from both USB task and main loop threads
// - USB task: Immediate restart after successful transfer for continuous data flow // - USB task: Immediate restart after successful transfer for continuous data flow
@@ -226,12 +226,18 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
// //
// The underlying transfer_in() uses lock-free atomic allocation from the // The underlying transfer_in() uses lock-free atomic allocation from the
// TransferRequest pool, making this multi-threaded access safe // TransferRequest pool, making this multi-threaded access safe
// if already started, don't restart. A spurious failure in compare_exchange_weak
// is not a problem, as it will be retried on the next read_array()
auto started = false;
if (!channel->input_started_.compare_exchange_weak(started, true))
return;
const auto *ep = channel->cdc_dev_.in_ep; const auto *ep = channel->cdc_dev_.in_ep;
// CALLBACK CONTEXT: This lambda is executed in USB task via transfer_callback // CALLBACK CONTEXT: This lambda is executed in USB task via transfer_callback
auto callback = [this, channel](const usb_host::TransferStatus &status) { auto callback = [this, channel](const usb_host::TransferStatus &status) {
ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code); ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
if (!status.success) { if (!status.success) {
ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code)); ESP_LOGE(TAG, "Input transfer failed, status=%s", esp_err_to_name(status.error_code));
// On failure, don't restart - let next read_array() trigger it // On failure, don't restart - let next read_array() trigger it
channel->input_started_.store(false); channel->input_started_.store(false);
return; return;
@@ -263,8 +269,9 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
channel->input_started_.store(false); channel->input_started_.store(false);
this->start_input(channel); this->start_input(channel);
}; };
channel->input_started_.store(true); if (!this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) {
this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize); channel->input_started_.store(false);
}
} }
void USBUartComponent::start_output(USBUartChannel *channel) { void USBUartComponent::start_output(USBUartChannel *channel) {
@@ -357,11 +364,12 @@ void USBUartTypeCdcAcm::on_disconnected() {
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress); usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
} }
usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number); usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number);
channel->initialised_.store(false); // Reset the input and output started flags to their initial state to avoid the possibility of spurious restarts
channel->input_started_.store(false); channel->input_started_.store(true);
channel->output_started_.store(false); channel->output_started_.store(true);
channel->input_buffer_.clear(); channel->input_buffer_.clear();
channel->output_buffer_.clear(); channel->output_buffer_.clear();
channel->initialised_.store(false);
} }
USBClient::on_disconnected(); USBClient::on_disconnected();
} }
+1 -1
View File
@@ -4,7 +4,7 @@ from enum import Enum
from esphome.enum import StrEnum from esphome.enum import StrEnum
__version__ = "2025.10.4" __version__ = "2025.10.5"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = ( VALID_SUBSTITUTIONS_CHARACTERS = (
+3 -1
View File
@@ -19,7 +19,9 @@ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Topic :: Home Automation", "Topic :: Home Automation",
] ]
requires-python = ">=3.11.0"
# Python 3.14 is currently not supported by IDF <= 5.5.1, see https://github.com/esphome/esphome/issues/11502
requires-python = ">=3.11.0,<3.14"
dynamic = ["dependencies", "optional-dependencies", "version"] dynamic = ["dependencies", "optional-dependencies", "version"]
+3
View File
@@ -1,3 +1,6 @@
usb_host:
max_transfer_requests: 32
usb_uart: usb_uart:
- id: uart_0 - id: uart_0
type: cdc_acm type: cdc_acm