mirror of
https://github.com/esphome/esphome.git
synced 2026-02-08 08:41:59 +00:00
Merge remote-tracking branch 'origin/web_server_match_flash' into integration
This commit is contained in:
@@ -74,3 +74,4 @@ async def to_code(config: ConfigType) -> None:
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_TINYUSB_CDC_TX_BUFSIZE", config[CONF_TX_BUFFER_SIZE]
|
||||
)
|
||||
cg.add_define("ESPHOME_MAX_USB_CDC_INSTANCES", num_interfaces)
|
||||
|
||||
@@ -3,181 +3,17 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <sys/param.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "tusb.h"
|
||||
#include "tusb_cdc_acm.h"
|
||||
|
||||
namespace esphome::usb_cdc_acm {
|
||||
|
||||
static const char *TAG = "usb_cdc_acm";
|
||||
|
||||
// Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512)
|
||||
static constexpr size_t USB_CDC_MAX_LOG_BYTES = 168;
|
||||
|
||||
static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096;
|
||||
static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192;
|
||||
static const char *const TAG = "usb_cdc_acm";
|
||||
|
||||
// Global component instance for managing USB device
|
||||
USBCDCACMComponent *global_usb_cdc_component = nullptr;
|
||||
|
||||
static USBCDCACMInstance *get_instance_by_itf(int itf) {
|
||||
if (global_usb_cdc_component == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return global_usb_cdc_component->get_interface_by_number(itf);
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "RX callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t rx_size = 0;
|
||||
static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE] = {0};
|
||||
|
||||
// read from USB
|
||||
esp_err_t ret =
|
||||
tinyusb_cdcacm_read(static_cast<tinyusb_cdcacm_itf_t>(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size);
|
||||
ESP_LOGV(TAG, "tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char rx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)];
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty_to(rx_hex_buf, rx_buf, rx_size));
|
||||
|
||||
if (ret == ESP_OK && rx_size > 0) {
|
||||
RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf();
|
||||
if (rx_ringbuf != nullptr) {
|
||||
BaseType_t send_res = xRingbufferSend(rx_ringbuf, rx_buf, rx_size, 0);
|
||||
if (send_res != pdTRUE) {
|
||||
ESP_LOGE(TAG, "USB RX itf=%d: buffer full, %u bytes lost", itf, rx_size);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "USB RX itf=%d: queued %u bytes", itf, rx_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "Line state callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
int dtr = event->line_state_changed_data.dtr;
|
||||
int rts = event->line_state_changed_data.rts;
|
||||
ESP_LOGV(TAG, "Line state itf=%d: DTR=%d, RTS=%d", itf, dtr, rts);
|
||||
|
||||
// Queue event for processing in main loop
|
||||
instance->queue_line_state_event(dtr != 0, rts != 0);
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_line_coding_changed_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "Line coding callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t bit_rate = event->line_coding_changed_data.p_line_coding->bit_rate;
|
||||
uint8_t stop_bits = event->line_coding_changed_data.p_line_coding->stop_bits;
|
||||
uint8_t parity = event->line_coding_changed_data.p_line_coding->parity;
|
||||
uint8_t data_bits = event->line_coding_changed_data.p_line_coding->data_bits;
|
||||
ESP_LOGV(TAG, "Line coding itf=%d: bit_rate=%" PRIu32 " stop_bits=%u parity=%u data_bits=%u", itf, bit_rate,
|
||||
stop_bits, parity, data_bits);
|
||||
|
||||
// Queue event for processing in main loop
|
||||
instance->queue_line_coding_event(bit_rate, stop_bits, parity, data_bits);
|
||||
}
|
||||
|
||||
static esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size,
|
||||
TickType_t xTicksToWait) {
|
||||
size_t read_sz;
|
||||
uint8_t *buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(ring_buf, &read_sz, xTicksToWait, out_buf_sz));
|
||||
|
||||
if (buf == nullptr) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
memcpy(out_buf, buf, read_sz);
|
||||
vRingbufferReturnItem(ring_buf, (void *) buf);
|
||||
*rx_data_size = read_sz;
|
||||
|
||||
// Buffer's data can be wrapped, in which case we should perform another read
|
||||
buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(ring_buf, &read_sz, 0, out_buf_sz - *rx_data_size));
|
||||
if (buf != nullptr) {
|
||||
memcpy(out_buf + *rx_data_size, buf, read_sz);
|
||||
vRingbufferReturnItem(ring_buf, (void *) buf);
|
||||
*rx_data_size += read_sz;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
USBCDCACMComponent *global_usb_cdc_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
//==============================================================================
|
||||
// USBCDCACMInstance Implementation
|
||||
//==============================================================================
|
||||
|
||||
void USBCDCACMInstance::setup() {
|
||||
this->usb_tx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_TX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (this->usb_tx_ringbuf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "USB TX buffer creation error for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->usb_rx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_RX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (this->usb_rx_ringbuf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "USB RX buffer creation error for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure this CDC interface
|
||||
const tinyusb_config_cdcacm_t acm_cfg = {
|
||||
.usb_dev = TINYUSB_USBDEV_0,
|
||||
.cdc_port = this->itf_,
|
||||
.callback_rx = &tinyusb_cdc_rx_callback,
|
||||
.callback_rx_wanted_char = NULL,
|
||||
.callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback,
|
||||
.callback_line_coding_changed = &tinyusb_cdc_line_coding_changed_callback,
|
||||
};
|
||||
|
||||
esp_err_t result = tusb_cdc_acm_init(&acm_cfg);
|
||||
if (result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "tusb_cdc_acm_init failed: %d", result);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a larger stack size for (very) verbose logging
|
||||
const size_t stack_size = esp_log_level_get(TAG) > ESP_LOG_DEBUG ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE;
|
||||
|
||||
// Create a simple, unique task name per interface
|
||||
char task_name[] = "usb_tx_0";
|
||||
task_name[sizeof(task_name) - 1] = format_hex_char(static_cast<char>(this->itf_));
|
||||
xTaskCreate(usb_tx_task_fn, task_name, stack_size, this, 4, &this->usb_tx_task_handle_);
|
||||
|
||||
if (this->usb_tx_task_handle_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create USB TX task for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::loop() {
|
||||
// Process events from the lock-free queue
|
||||
this->process_events_();
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::queue_line_state_event(bool dtr, bool rts) {
|
||||
// Allocate event from pool
|
||||
CDCEvent *event = this->event_pool_.allocate();
|
||||
@@ -288,170 +124,6 @@ void USBCDCACMInstance::process_events_() {
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::usb_tx_task_fn(void *arg) {
|
||||
auto *instance = static_cast<USBCDCACMInstance *>(arg);
|
||||
instance->usb_tx_task();
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::usb_tx_task() {
|
||||
uint8_t data[CONFIG_TINYUSB_CDC_TX_BUFSIZE] = {0};
|
||||
size_t tx_data_size = 0;
|
||||
|
||||
while (1) {
|
||||
// Wait for a notification from the bridge component
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
// When we do wake up, we can be sure there is data in the ring buffer
|
||||
esp_err_t ret = ringbuf_read_bytes(this->usb_tx_ringbuf_, data, CONFIG_TINYUSB_CDC_TX_BUFSIZE, &tx_data_size, 0);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "USB TX itf=%d: RingBuf read failed", this->itf_);
|
||||
continue;
|
||||
} else if (tx_data_size == 0) {
|
||||
ESP_LOGD(TAG, "USB TX itf=%d: RingBuf empty, skipping", this->itf_);
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: Read %d bytes from buffer", this->itf_, tx_data_size);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char tx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)];
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "data = %s", format_hex_pretty_to(tx_hex_buf, data, tx_data_size));
|
||||
|
||||
// Serial data will be split up into 64 byte chunks to be sent over USB so this
|
||||
// usually will take multiple iterations
|
||||
uint8_t *data_head = &data[0];
|
||||
|
||||
while (tx_data_size > 0) {
|
||||
size_t queued = tinyusb_cdcacm_write_queue(this->itf_, data_head, tx_data_size);
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: enqueued: size=%d, queued=%u", this->itf_, tx_data_size, queued);
|
||||
|
||||
tx_data_size -= queued;
|
||||
data_head += queued;
|
||||
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: waiting 10ms for flush", this->itf_);
|
||||
esp_err_t flush_ret = tinyusb_cdcacm_write_flush(this->itf_, pdMS_TO_TICKS(10));
|
||||
|
||||
if (flush_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "USB TX itf=%d: flush failed", this->itf_);
|
||||
tud_cdc_n_write_clear(this->itf_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// UARTComponent Interface Implementation
|
||||
//==============================================================================
|
||||
|
||||
void USBCDCACMInstance::write_array(const uint8_t *data, size_t len) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write data to TX ring buffer
|
||||
BaseType_t send_res = xRingbufferSend(this->usb_tx_ringbuf_, data, len, 0);
|
||||
if (send_res != pdTRUE) {
|
||||
ESP_LOGW(TAG, "USB TX itf=%d: buffer full, %u bytes dropped", this->itf_, len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify TX task that data is available
|
||||
if (this->usb_tx_task_handle_ != nullptr) {
|
||||
xTaskNotifyGive(this->usb_tx_task_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool USBCDCACMInstance::peek_byte(uint8_t *data) {
|
||||
if (this->has_peek_) {
|
||||
*data = this->peek_buffer_;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->read_byte(&this->peek_buffer_)) {
|
||||
*data = this->peek_buffer_;
|
||||
this->has_peek_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool USBCDCACMInstance::read_array(uint8_t *data, size_t len) {
|
||||
if (len == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t original_len = len;
|
||||
size_t bytes_read = 0;
|
||||
|
||||
// First, use the peek buffer if available
|
||||
if (this->has_peek_) {
|
||||
data[0] = this->peek_buffer_;
|
||||
this->has_peek_ = false;
|
||||
bytes_read = 1;
|
||||
data++;
|
||||
if (--len == 0) { // Decrement len first, then check it...
|
||||
return true; // No more to read
|
||||
}
|
||||
}
|
||||
|
||||
// Read remaining bytes from RX ring buffer
|
||||
size_t rx_size = 0;
|
||||
uint8_t *buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len));
|
||||
if (buf == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(data, buf, rx_size);
|
||||
vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf);
|
||||
bytes_read += rx_size;
|
||||
data += rx_size;
|
||||
len -= rx_size;
|
||||
if (len == 0) {
|
||||
return true; // No more to read
|
||||
}
|
||||
|
||||
// Buffer's data may wrap around, in which case we should perform another read
|
||||
buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len));
|
||||
if (buf == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(data, buf, rx_size);
|
||||
vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf);
|
||||
bytes_read += rx_size;
|
||||
|
||||
return bytes_read == original_len;
|
||||
}
|
||||
|
||||
int USBCDCACMInstance::available() {
|
||||
UBaseType_t waiting = 0;
|
||||
if (this->usb_rx_ringbuf_ != nullptr) {
|
||||
vRingbufferGetInfo(this->usb_rx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
||||
}
|
||||
return static_cast<int>(waiting) + (this->has_peek_ ? 1 : 0);
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::flush() {
|
||||
// Wait for TX ring buffer to be empty
|
||||
if (this->usb_tx_ringbuf_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
UBaseType_t waiting = 1;
|
||||
while (waiting > 0) {
|
||||
vRingbufferGetInfo(this->usb_tx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
||||
if (waiting > 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
|
||||
// Also wait for USB to finish transmitting
|
||||
tinyusb_cdcacm_write_flush(this->itf_, pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// USBCDCACMComponent Implementation
|
||||
//==============================================================================
|
||||
@@ -460,7 +132,7 @@ USBCDCACMComponent::USBCDCACMComponent() { global_usb_cdc_component = this; }
|
||||
|
||||
void USBCDCACMComponent::setup() {
|
||||
// Setup all registered interfaces
|
||||
for (auto interface : this->interfaces_) {
|
||||
for (auto *interface : this->interfaces_) {
|
||||
if (interface != nullptr) {
|
||||
interface->setup();
|
||||
}
|
||||
@@ -469,7 +141,7 @@ void USBCDCACMComponent::setup() {
|
||||
|
||||
void USBCDCACMComponent::loop() {
|
||||
// Call loop() on all registered interfaces to process events
|
||||
for (auto interface : this->interfaces_) {
|
||||
for (auto *interface : this->interfaces_) {
|
||||
if (interface != nullptr) {
|
||||
interface->loop();
|
||||
}
|
||||
@@ -480,21 +152,28 @@ void USBCDCACMComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"USB CDC-ACM:\n"
|
||||
" Number of Interfaces: %d",
|
||||
this->interfaces_[MAX_USB_CDC_INSTANCES - 1] != nullptr ? MAX_USB_CDC_INSTANCES : 1);
|
||||
ESPHOME_MAX_USB_CDC_INSTANCES);
|
||||
for (uint8_t i = 0; i < ESPHOME_MAX_USB_CDC_INSTANCES; ++i) {
|
||||
if (this->interfaces_[i] != nullptr) {
|
||||
this->interfaces_[i]->dump_config();
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Interface %u is disabled", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDCACMComponent::add_interface(USBCDCACMInstance *interface) {
|
||||
uint8_t itf_num = static_cast<uint8_t>(interface->get_itf());
|
||||
if (itf_num < MAX_USB_CDC_INSTANCES) {
|
||||
if (itf_num < ESPHOME_MAX_USB_CDC_INSTANCES) {
|
||||
this->interfaces_[itf_num] = interface;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Interface number must be less than %u", MAX_USB_CDC_INSTANCES);
|
||||
ESP_LOGE(TAG, "Interface number must be less than %u", ESPHOME_MAX_USB_CDC_INSTANCES);
|
||||
}
|
||||
}
|
||||
|
||||
USBCDCACMInstance *USBCDCACMComponent::get_interface_by_number(uint8_t itf) {
|
||||
for (auto interface : this->interfaces_) {
|
||||
if ((interface != nullptr) && (interface->get_itf() == static_cast<tinyusb_cdcacm_itf_t>(itf))) {
|
||||
for (auto *interface : this->interfaces_) {
|
||||
if ((interface != nullptr) && (interface->get_itf() == itf)) {
|
||||
return interface;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
namespace esphome::usb_cdc_acm {
|
||||
|
||||
static const uint8_t EVENT_QUEUE_SIZE = 12;
|
||||
static const uint8_t MAX_USB_CDC_INSTANCES = 2;
|
||||
|
||||
// Callback types for line coding and line state changes
|
||||
using LineCodingCallback = std::function<void(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits)>;
|
||||
@@ -53,14 +52,13 @@ class USBCDCACMComponent;
|
||||
/// Represents a single CDC ACM interface instance
|
||||
class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMComponent> {
|
||||
public:
|
||||
void set_interface_number(uint8_t itf) { this->itf_ = static_cast<tinyusb_cdcacm_itf_t>(itf); }
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
void dump_config();
|
||||
|
||||
void set_interface_number(uint8_t itf) { this->itf_ = itf; }
|
||||
// Get the CDC port number for this instance
|
||||
tinyusb_cdcacm_itf_t get_itf() const { return this->itf_; }
|
||||
|
||||
uint8_t get_itf() const { return this->itf_; }
|
||||
// Ring buffer accessors for bridge components
|
||||
RingbufHandle_t get_tx_ringbuf() const { return this->usb_tx_ringbuf_; }
|
||||
RingbufHandle_t get_rx_ringbuf() const { return this->usb_rx_ringbuf_; }
|
||||
@@ -72,7 +70,7 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMC
|
||||
void set_line_coding_callback(LineCodingCallback callback) { this->line_coding_callback_ = std::move(callback); }
|
||||
void set_line_state_callback(LineStateCallback callback) { this->line_state_callback_ = std::move(callback); }
|
||||
|
||||
// Called from TinyUSB task context (SPSC producer) - queues event for processing in main loop
|
||||
// Called from USB core task context queues event for processing in main loop
|
||||
void queue_line_coding_event(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits);
|
||||
void queue_line_state_event(bool dtr, bool rts);
|
||||
|
||||
@@ -87,17 +85,18 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMC
|
||||
void flush() override;
|
||||
|
||||
protected:
|
||||
void check_logger_conflict() override {}
|
||||
void check_logger_conflict() override;
|
||||
|
||||
// Process queued events and invoke callbacks (called from main loop)
|
||||
void process_events_();
|
||||
|
||||
TaskHandle_t usb_tx_task_handle_{nullptr};
|
||||
tinyusb_cdcacm_itf_t itf_{TINYUSB_CDC_ACM_0};
|
||||
|
||||
RingbufHandle_t usb_tx_ringbuf_{nullptr};
|
||||
RingbufHandle_t usb_rx_ringbuf_{nullptr};
|
||||
|
||||
// RX buffer for peek functionality
|
||||
uint8_t peek_buffer_{0};
|
||||
bool has_peek_{false};
|
||||
uint8_t itf_{0};
|
||||
// User-registered callbacks (called from main loop)
|
||||
LineCodingCallback line_coding_callback_{nullptr};
|
||||
LineStateCallback line_state_callback_{nullptr};
|
||||
@@ -105,10 +104,6 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMC
|
||||
// Lock-free queue and event pool for cross-task event passing
|
||||
EventPool<CDCEvent, EVENT_QUEUE_SIZE> event_pool_;
|
||||
LockFreeQueue<CDCEvent, EVENT_QUEUE_SIZE> event_queue_;
|
||||
|
||||
// RX buffer for peek functionality
|
||||
uint8_t peek_buffer_{0};
|
||||
bool has_peek_{false};
|
||||
};
|
||||
|
||||
/// Main USB CDC ACM component that manages the USB device and all CDC interfaces
|
||||
@@ -126,7 +121,7 @@ class USBCDCACMComponent : public Component {
|
||||
USBCDCACMInstance *get_interface_by_number(uint8_t itf);
|
||||
|
||||
protected:
|
||||
std::array<USBCDCACMInstance *, MAX_USB_CDC_INSTANCES> interfaces_{nullptr, nullptr};
|
||||
std::array<USBCDCACMInstance *, ESPHOME_MAX_USB_CDC_INSTANCES> interfaces_{};
|
||||
};
|
||||
|
||||
extern USBCDCACMComponent *global_usb_cdc_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
349
esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp
Normal file
349
esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp
Normal file
@@ -0,0 +1,349 @@
|
||||
#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include "usb_cdc_acm.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <sys/param.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "tusb.h"
|
||||
#include "tusb_cdc_acm.h"
|
||||
|
||||
namespace esphome::usb_cdc_acm {
|
||||
|
||||
static const char *const TAG = "usb_cdc_acm";
|
||||
|
||||
// Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512)
|
||||
static constexpr size_t USB_CDC_MAX_LOG_BYTES = 168;
|
||||
|
||||
static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096;
|
||||
static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192;
|
||||
|
||||
static USBCDCACMInstance *get_instance_by_itf(int itf) {
|
||||
if (global_usb_cdc_component == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return global_usb_cdc_component->get_interface_by_number(itf);
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "RX callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t rx_size = 0;
|
||||
static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE] = {0};
|
||||
|
||||
// read from USB
|
||||
esp_err_t ret =
|
||||
tinyusb_cdcacm_read(static_cast<tinyusb_cdcacm_itf_t>(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size);
|
||||
ESP_LOGV(TAG, "tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char rx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)];
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty_to(rx_hex_buf, rx_buf, rx_size));
|
||||
|
||||
if (ret == ESP_OK && rx_size > 0) {
|
||||
RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf();
|
||||
if (rx_ringbuf != nullptr) {
|
||||
BaseType_t send_res = xRingbufferSend(rx_ringbuf, rx_buf, rx_size, 0);
|
||||
if (send_res != pdTRUE) {
|
||||
ESP_LOGE(TAG, "USB RX itf=%d: buffer full, %u bytes lost", itf, rx_size);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "USB RX itf=%d: queued %u bytes", itf, rx_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "Line state callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
int dtr = event->line_state_changed_data.dtr;
|
||||
int rts = event->line_state_changed_data.rts;
|
||||
ESP_LOGV(TAG, "Line state itf=%d: DTR=%d, RTS=%d", itf, dtr, rts);
|
||||
|
||||
// Queue event for processing in main loop
|
||||
instance->queue_line_state_event(dtr != 0, rts != 0);
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_line_coding_changed_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "Line coding callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t bit_rate = event->line_coding_changed_data.p_line_coding->bit_rate;
|
||||
uint8_t stop_bits = event->line_coding_changed_data.p_line_coding->stop_bits;
|
||||
uint8_t parity = event->line_coding_changed_data.p_line_coding->parity;
|
||||
uint8_t data_bits = event->line_coding_changed_data.p_line_coding->data_bits;
|
||||
ESP_LOGV(TAG, "Line coding itf=%d: bit_rate=%" PRIu32 " stop_bits=%u parity=%u data_bits=%u", itf, bit_rate,
|
||||
stop_bits, parity, data_bits);
|
||||
|
||||
// Queue event for processing in main loop
|
||||
instance->queue_line_coding_event(bit_rate, stop_bits, parity, data_bits);
|
||||
}
|
||||
|
||||
static esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size,
|
||||
TickType_t xTicksToWait) {
|
||||
size_t read_sz;
|
||||
uint8_t *buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(ring_buf, &read_sz, xTicksToWait, out_buf_sz));
|
||||
|
||||
if (buf == nullptr) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
memcpy(out_buf, buf, read_sz);
|
||||
vRingbufferReturnItem(ring_buf, (void *) buf);
|
||||
*rx_data_size = read_sz;
|
||||
|
||||
// Buffer's data can be wrapped, in which case we should perform another read
|
||||
buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(ring_buf, &read_sz, 0, out_buf_sz - *rx_data_size));
|
||||
if (buf != nullptr) {
|
||||
memcpy(out_buf + *rx_data_size, buf, read_sz);
|
||||
vRingbufferReturnItem(ring_buf, (void *) buf);
|
||||
*rx_data_size += read_sz;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// USBCDCACMInstance Implementation
|
||||
//==============================================================================
|
||||
|
||||
void USBCDCACMInstance::setup() {
|
||||
this->usb_tx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_TX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (this->usb_tx_ringbuf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "USB TX buffer creation error for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->usb_rx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_RX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (this->usb_rx_ringbuf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "USB RX buffer creation error for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure this CDC interface
|
||||
const tinyusb_config_cdcacm_t acm_cfg = {
|
||||
.usb_dev = TINYUSB_USBDEV_0,
|
||||
.cdc_port = static_cast<tinyusb_cdcacm_itf_t>(this->itf_),
|
||||
.callback_rx = &tinyusb_cdc_rx_callback,
|
||||
.callback_rx_wanted_char = NULL,
|
||||
.callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback,
|
||||
.callback_line_coding_changed = &tinyusb_cdc_line_coding_changed_callback,
|
||||
};
|
||||
|
||||
esp_err_t result = tusb_cdc_acm_init(&acm_cfg);
|
||||
if (result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "tusb_cdc_acm_init failed: %d", result);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a larger stack size for (very) verbose logging
|
||||
const size_t stack_size = esp_log_level_get(TAG) > ESP_LOG_DEBUG ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE;
|
||||
|
||||
// Create a simple, unique task name per interface
|
||||
char task_name[] = "usb_tx_0";
|
||||
task_name[sizeof(task_name) - 1] = format_hex_char(static_cast<char>(this->itf_));
|
||||
xTaskCreate(usb_tx_task_fn, task_name, stack_size, this, 4, &this->usb_tx_task_handle_);
|
||||
|
||||
if (this->usb_tx_task_handle_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create USB TX task for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::loop() {
|
||||
// Process events from the lock-free queue
|
||||
this->process_events_();
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::dump_config() {}
|
||||
|
||||
void USBCDCACMInstance::usb_tx_task_fn(void *arg) {
|
||||
auto *instance = static_cast<USBCDCACMInstance *>(arg);
|
||||
instance->usb_tx_task();
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::usb_tx_task() {
|
||||
uint8_t data[CONFIG_TINYUSB_CDC_TX_BUFSIZE] = {0};
|
||||
size_t tx_data_size = 0;
|
||||
|
||||
while (1) {
|
||||
// Wait for a notification from the bridge component
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
// When we do wake up, we can be sure there is data in the ring buffer
|
||||
esp_err_t ret = ringbuf_read_bytes(this->usb_tx_ringbuf_, data, CONFIG_TINYUSB_CDC_TX_BUFSIZE, &tx_data_size, 0);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "USB TX itf=%d: RingBuf read failed", this->itf_);
|
||||
continue;
|
||||
} else if (tx_data_size == 0) {
|
||||
ESP_LOGD(TAG, "USB TX itf=%d: RingBuf empty, skipping", this->itf_);
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: Read %d bytes from buffer", this->itf_, tx_data_size);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char tx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)];
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "data = %s", format_hex_pretty_to(tx_hex_buf, data, tx_data_size));
|
||||
|
||||
// Serial data will be split up into 64 byte chunks to be sent over USB so this
|
||||
// usually will take multiple iterations
|
||||
uint8_t *data_head = &data[0];
|
||||
|
||||
while (tx_data_size > 0) {
|
||||
size_t queued =
|
||||
tinyusb_cdcacm_write_queue(static_cast<tinyusb_cdcacm_itf_t>(this->itf_), data_head, tx_data_size);
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: enqueued: size=%d, queued=%u", this->itf_, tx_data_size, queued);
|
||||
|
||||
tx_data_size -= queued;
|
||||
data_head += queued;
|
||||
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: waiting 10ms for flush", this->itf_);
|
||||
esp_err_t flush_ret =
|
||||
tinyusb_cdcacm_write_flush(static_cast<tinyusb_cdcacm_itf_t>(this->itf_), pdMS_TO_TICKS(10));
|
||||
|
||||
if (flush_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "USB TX itf=%d: flush failed", this->itf_);
|
||||
tud_cdc_n_write_clear(this->itf_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// UARTComponent Interface Implementation
|
||||
//==============================================================================
|
||||
|
||||
void USBCDCACMInstance::write_array(const uint8_t *data, size_t len) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write data to TX ring buffer
|
||||
BaseType_t send_res = xRingbufferSend(this->usb_tx_ringbuf_, data, len, 0);
|
||||
if (send_res != pdTRUE) {
|
||||
ESP_LOGW(TAG, "USB TX itf=%d: buffer full, %u bytes dropped", this->itf_, len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify TX task that data is available
|
||||
if (this->usb_tx_task_handle_ != nullptr) {
|
||||
xTaskNotifyGive(this->usb_tx_task_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool USBCDCACMInstance::peek_byte(uint8_t *data) {
|
||||
if (this->has_peek_) {
|
||||
*data = this->peek_buffer_;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->read_byte(&this->peek_buffer_)) {
|
||||
*data = this->peek_buffer_;
|
||||
this->has_peek_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool USBCDCACMInstance::read_array(uint8_t *data, size_t len) {
|
||||
if (len == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t original_len = len;
|
||||
size_t bytes_read = 0;
|
||||
|
||||
// First, use the peek buffer if available
|
||||
if (this->has_peek_) {
|
||||
data[0] = this->peek_buffer_;
|
||||
this->has_peek_ = false;
|
||||
bytes_read = 1;
|
||||
data++;
|
||||
if (--len == 0) { // Decrement len first, then check it...
|
||||
return true; // No more to read
|
||||
}
|
||||
}
|
||||
|
||||
// Read remaining bytes from RX ring buffer
|
||||
size_t rx_size = 0;
|
||||
uint8_t *buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len));
|
||||
if (buf == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(data, buf, rx_size);
|
||||
vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf);
|
||||
bytes_read += rx_size;
|
||||
data += rx_size;
|
||||
len -= rx_size;
|
||||
if (len == 0) {
|
||||
return true; // No more to read
|
||||
}
|
||||
|
||||
// Buffer's data may wrap around, in which case we should perform another read
|
||||
buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len));
|
||||
if (buf == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(data, buf, rx_size);
|
||||
vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf);
|
||||
bytes_read += rx_size;
|
||||
|
||||
return bytes_read == original_len;
|
||||
}
|
||||
|
||||
int USBCDCACMInstance::available() {
|
||||
UBaseType_t waiting = 0;
|
||||
if (this->usb_rx_ringbuf_ != nullptr) {
|
||||
vRingbufferGetInfo(this->usb_rx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
||||
}
|
||||
return static_cast<int>(waiting) + (this->has_peek_ ? 1 : 0);
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::flush() {
|
||||
// Wait for TX ring buffer to be empty
|
||||
if (this->usb_tx_ringbuf_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
UBaseType_t waiting = 1;
|
||||
while (waiting > 0) {
|
||||
vRingbufferGetInfo(this->usb_tx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
||||
if (waiting > 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
|
||||
// Also wait for USB to finish transmitting
|
||||
tinyusb_cdcacm_write_flush(static_cast<tinyusb_cdcacm_itf_t>(this->itf_), pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::check_logger_conflict() {}
|
||||
|
||||
} // namespace esphome::usb_cdc_acm
|
||||
#endif
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "web_server.h"
|
||||
#ifdef USE_WEBSERVER
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
@@ -679,11 +680,11 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
|
||||
enum SwitchAction { NONE, TOGGLE, TURN_ON, TURN_OFF };
|
||||
SwitchAction action = NONE;
|
||||
|
||||
if (match.method_equals("toggle")) {
|
||||
if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||
action = TOGGLE;
|
||||
} else if (match.method_equals("turn_on")) {
|
||||
} else if (match.method_equals(ESPHOME_F("turn_on"))) {
|
||||
action = TURN_ON;
|
||||
} else if (match.method_equals("turn_off")) {
|
||||
} else if (match.method_equals(ESPHOME_F("turn_off"))) {
|
||||
action = TURN_OFF;
|
||||
}
|
||||
|
||||
@@ -741,7 +742,7 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
|
||||
auto detail = get_request_detail(request);
|
||||
std::string data = this->button_json_(obj, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals("press")) {
|
||||
} else if (match.method_equals(ESPHOME_F("press"))) {
|
||||
this->defer([obj]() { obj->press(); });
|
||||
request->send(200);
|
||||
return;
|
||||
@@ -829,12 +830,12 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
||||
auto detail = get_request_detail(request);
|
||||
std::string data = this->fan_json_(obj, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals("toggle")) {
|
||||
} else if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||
this->defer([obj]() { obj->toggle().perform(); });
|
||||
request->send(200);
|
||||
} else {
|
||||
bool is_on = match.method_equals("turn_on");
|
||||
bool is_off = match.method_equals("turn_off");
|
||||
bool is_on = match.method_equals(ESPHOME_F("turn_on"));
|
||||
bool is_off = match.method_equals(ESPHOME_F("turn_off"));
|
||||
if (!is_on && !is_off) {
|
||||
request->send(404);
|
||||
return;
|
||||
@@ -910,12 +911,12 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
||||
auto detail = get_request_detail(request);
|
||||
std::string data = this->light_json_(obj, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals("toggle")) {
|
||||
} else if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||
this->defer([obj]() { obj->toggle().perform(); });
|
||||
request->send(200);
|
||||
} else {
|
||||
bool is_on = match.method_equals("turn_on");
|
||||
bool is_off = match.method_equals("turn_off");
|
||||
bool is_on = match.method_equals(ESPHOME_F("turn_on"));
|
||||
bool is_off = match.method_equals(ESPHOME_F("turn_off"));
|
||||
if (!is_on && !is_off) {
|
||||
request->send(404);
|
||||
return;
|
||||
@@ -1014,7 +1015,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && !match.method_equals("set")) {
|
||||
if (!found && !match.method_equals(ESPHOME_F("set"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -1080,7 +1081,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
||||
request->send(200, "application/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
if (!match.method_equals("set")) {
|
||||
if (!match.method_equals(ESPHOME_F("set"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -1147,7 +1148,7 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat
|
||||
request->send(200, "application/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
if (!match.method_equals("set")) {
|
||||
if (!match.method_equals(ESPHOME_F("set"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -1211,7 +1212,7 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat
|
||||
request->send(200, "application/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
if (!match.method_equals("set")) {
|
||||
if (!match.method_equals(ESPHOME_F("set"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -1274,7 +1275,7 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur
|
||||
request->send(200, "application/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
if (!match.method_equals("set")) {
|
||||
if (!match.method_equals(ESPHOME_F("set"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -1340,7 +1341,7 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat
|
||||
request->send(200, "application/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
if (!match.method_equals("set")) {
|
||||
if (!match.method_equals(ESPHOME_F("set"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -1398,7 +1399,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
|
||||
return;
|
||||
}
|
||||
|
||||
if (!match.method_equals("set")) {
|
||||
if (!match.method_equals(ESPHOME_F("set"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -1457,7 +1458,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
|
||||
return;
|
||||
}
|
||||
|
||||
if (!match.method_equals("set")) {
|
||||
if (!match.method_equals(ESPHOME_F("set"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -1613,11 +1614,11 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
|
||||
enum LockAction { NONE, LOCK, UNLOCK, OPEN };
|
||||
LockAction action = NONE;
|
||||
|
||||
if (match.method_equals("lock")) {
|
||||
if (match.method_equals(ESPHOME_F("lock"))) {
|
||||
action = LOCK;
|
||||
} else if (match.method_equals("unlock")) {
|
||||
} else if (match.method_equals(ESPHOME_F("unlock"))) {
|
||||
action = UNLOCK;
|
||||
} else if (match.method_equals("open")) {
|
||||
} else if (match.method_equals(ESPHOME_F("open"))) {
|
||||
action = OPEN;
|
||||
}
|
||||
|
||||
@@ -1706,7 +1707,7 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && !match.method_equals("set")) {
|
||||
if (!found && !match.method_equals(ESPHOME_F("set"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -1849,7 +1850,7 @@ void WebServer::handle_water_heater_request(AsyncWebServerRequest *request, cons
|
||||
request->send(200, "application/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
if (!match.method_equals("set")) {
|
||||
if (!match.method_equals(ESPHOME_F("set"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -2029,7 +2030,7 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM
|
||||
return;
|
||||
}
|
||||
|
||||
if (!match.method_equals("install")) {
|
||||
if (!match.method_equals(ESPHOME_F("install"))) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@@ -2244,102 +2245,102 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||
if (false) { // Start chain for else-if macro pattern
|
||||
}
|
||||
#ifdef USE_SENSOR
|
||||
else if (match.domain_equals("sensor")) {
|
||||
else if (match.domain_equals(ESPHOME_F("sensor"))) {
|
||||
this->handle_sensor_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
else if (match.domain_equals("switch")) {
|
||||
else if (match.domain_equals(ESPHOME_F("switch"))) {
|
||||
this->handle_switch_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
else if (match.domain_equals("button")) {
|
||||
else if (match.domain_equals(ESPHOME_F("button"))) {
|
||||
this->handle_button_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
else if (match.domain_equals("binary_sensor")) {
|
||||
else if (match.domain_equals(ESPHOME_F("binary_sensor"))) {
|
||||
this->handle_binary_sensor_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
else if (match.domain_equals("fan")) {
|
||||
else if (match.domain_equals(ESPHOME_F("fan"))) {
|
||||
this->handle_fan_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
else if (match.domain_equals("light")) {
|
||||
else if (match.domain_equals(ESPHOME_F("light"))) {
|
||||
this->handle_light_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
else if (match.domain_equals("text_sensor")) {
|
||||
else if (match.domain_equals(ESPHOME_F("text_sensor"))) {
|
||||
this->handle_text_sensor_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
else if (match.domain_equals("cover")) {
|
||||
else if (match.domain_equals(ESPHOME_F("cover"))) {
|
||||
this->handle_cover_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
else if (match.domain_equals("number")) {
|
||||
else if (match.domain_equals(ESPHOME_F("number"))) {
|
||||
this->handle_number_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
else if (match.domain_equals("date")) {
|
||||
else if (match.domain_equals(ESPHOME_F("date"))) {
|
||||
this->handle_date_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
else if (match.domain_equals("time")) {
|
||||
else if (match.domain_equals(ESPHOME_F("time"))) {
|
||||
this->handle_time_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
else if (match.domain_equals("datetime")) {
|
||||
else if (match.domain_equals(ESPHOME_F("datetime"))) {
|
||||
this->handle_datetime_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
else if (match.domain_equals("text")) {
|
||||
else if (match.domain_equals(ESPHOME_F("text"))) {
|
||||
this->handle_text_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
else if (match.domain_equals("select")) {
|
||||
else if (match.domain_equals(ESPHOME_F("select"))) {
|
||||
this->handle_select_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
else if (match.domain_equals("climate")) {
|
||||
else if (match.domain_equals(ESPHOME_F("climate"))) {
|
||||
this->handle_climate_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
else if (match.domain_equals("lock")) {
|
||||
else if (match.domain_equals(ESPHOME_F("lock"))) {
|
||||
this->handle_lock_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
else if (match.domain_equals("valve")) {
|
||||
else if (match.domain_equals(ESPHOME_F("valve"))) {
|
||||
this->handle_valve_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
else if (match.domain_equals("alarm_control_panel")) {
|
||||
else if (match.domain_equals(ESPHOME_F("alarm_control_panel"))) {
|
||||
this->handle_alarm_control_panel_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
else if (match.domain_equals("update")) {
|
||||
else if (match.domain_equals(ESPHOME_F("update"))) {
|
||||
this->handle_update_request(request, match);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
else if (match.domain_equals("water_heater")) {
|
||||
else if (match.domain_equals(ESPHOME_F("water_heater"))) {
|
||||
this->handle_water_heater_request(request, match);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -62,6 +62,12 @@ struct UrlMatch {
|
||||
bool domain_equals(const char *str) const { return this->domain == str; }
|
||||
bool method_equals(const char *str) const { return this->method == str; }
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
// Overloads for flash strings on ESP8266
|
||||
bool domain_equals(const __FlashStringHelper *str) const { return this->domain == str; }
|
||||
bool method_equals(const __FlashStringHelper *str) const { return this->method == str; }
|
||||
#endif
|
||||
|
||||
/// Match entity by name first, then fall back to object_id with deprecation warning
|
||||
/// Returns EntityMatchResult with match status and whether action segment is empty
|
||||
EntityMatchResult match_entity(EntityBase *entity) const;
|
||||
|
||||
@@ -358,3 +358,4 @@
|
||||
#define ESPHOME_ENTITY_UPDATE_COUNT 1
|
||||
#define ESPHOME_ENTITY_VALVE_COUNT 1
|
||||
#define ESPHOME_ENTITY_WATER_HEATER_COUNT 1
|
||||
#define ESPHOME_MAX_USB_CDC_INSTANCES 1
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#endif // USE_JSON
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
#endif // USE_ESP8266
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/**
|
||||
@@ -107,6 +111,22 @@ inline bool operator!=(const StringRef &lhs, const char *rhs) { return !(lhs ==
|
||||
|
||||
inline bool operator!=(const char *lhs, const StringRef &rhs) { return !(rhs == lhs); }
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
inline bool operator==(const StringRef &lhs, const __FlashStringHelper *rhs) {
|
||||
PGM_P p = reinterpret_cast<PGM_P>(rhs);
|
||||
size_t rhs_len = strlen_P(p);
|
||||
if (lhs.size() != rhs_len)
|
||||
return false;
|
||||
return memcmp_P(lhs.c_str(), p, rhs_len) == 0;
|
||||
}
|
||||
|
||||
inline bool operator==(const __FlashStringHelper *lhs, const StringRef &rhs) { return rhs == lhs; }
|
||||
|
||||
inline bool operator!=(const StringRef &lhs, const __FlashStringHelper *rhs) { return !(lhs == rhs); }
|
||||
|
||||
inline bool operator!=(const __FlashStringHelper *lhs, const StringRef &rhs) { return !(rhs == lhs); }
|
||||
#endif // USE_ESP8266
|
||||
|
||||
inline bool operator<(const StringRef &lhs, const StringRef &rhs) {
|
||||
return std::lexicographical_compare(std::begin(lhs), std::end(lhs), std::begin(rhs), std::end(rhs));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user