diff --git a/CODEOWNERS b/CODEOWNERS index fee0e98f4..b8a4df6a8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -480,6 +480,7 @@ esphome/components/template/fan/* @ssieb esphome/components/text/* @mauritskorse esphome/components/thermostat/* @kbx81 esphome/components/time/* @esphome/core +esphome/components/tinyusb/* @kbx81 esphome/components/tlc5947/* @rnauber esphome/components/tlc5971/* @IJIJI esphome/components/tm1621/* @Philippe12 diff --git a/esphome/components/tinyusb/__init__.py b/esphome/components/tinyusb/__init__.py new file mode 100644 index 000000000..72afc1838 --- /dev/null +++ b/esphome/components/tinyusb/__init__.py @@ -0,0 +1,60 @@ +import esphome.codegen as cg +from esphome.components import esp32 +from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option +from esphome.components.esp32.const import ( + VARIANT_ESP32P4, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CODEOWNERS = ["@kbx81"] +CONFLICTS_WITH = ["usb_host"] + +CONF_USB_LANG_ID = "usb_lang_id" +CONF_USB_MANUFACTURER_STR = "usb_manufacturer_str" +CONF_USB_PRODUCT_ID = "usb_product_id" +CONF_USB_PRODUCT_STR = "usb_product_str" +CONF_USB_SERIAL_STR = "usb_serial_str" +CONF_USB_VENDOR_ID = "usb_vendor_id" + +tinyusb_ns = cg.esphome_ns.namespace("tinyusb") +TinyUSB = tinyusb_ns.class_("TinyUSB", cg.Component) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TinyUSB), + cv.Optional(CONF_USB_PRODUCT_ID, default=0x4001): cv.uint16_t, + cv.Optional(CONF_USB_VENDOR_ID, default=0x303A): cv.uint16_t, + cv.Optional(CONF_USB_LANG_ID, default=0x0409): cv.uint16_t, + cv.Optional(CONF_USB_MANUFACTURER_STR, default="ESPHome"): cv.string, + cv.Optional(CONF_USB_PRODUCT_STR, default="ESPHome"): cv.string, + cv.Optional(CONF_USB_SERIAL_STR, default=""): cv.string, + } + ).extend(cv.COMPONENT_SCHEMA), + esp32.only_on_variant( + supported=[VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3], + ), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + # Set USB device descriptor properties + cg.add(var.set_usb_desc_product_id(config[CONF_USB_PRODUCT_ID])) + cg.add(var.set_usb_desc_vendor_id(config[CONF_USB_VENDOR_ID])) + cg.add(var.set_usb_desc_lang_id(config[CONF_USB_LANG_ID])) + cg.add(var.set_usb_desc_manufacturer(config[CONF_USB_MANUFACTURER_STR])) + cg.add(var.set_usb_desc_product(config[CONF_USB_PRODUCT_STR])) + if config[CONF_USB_SERIAL_STR]: + cg.add(var.set_usb_desc_serial(config[CONF_USB_SERIAL_STR])) + + add_idf_component(name="espressif/esp_tinyusb", ref="1.7.6~1") + + add_idf_sdkconfig_option("CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID", False) + add_idf_sdkconfig_option("CONFIG_TINYUSB_DESC_USE_DEFAULT_PID", False) + add_idf_sdkconfig_option("CONFIG_TINYUSB_DESC_BCD_DEVICE", 0x0100) diff --git a/esphome/components/tinyusb/tinyusb_component.cpp b/esphome/components/tinyusb/tinyusb_component.cpp new file mode 100644 index 000000000..a2057c90c --- /dev/null +++ b/esphome/components/tinyusb/tinyusb_component.cpp @@ -0,0 +1,44 @@ +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#include "tinyusb_component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome::tinyusb { + +static const char *TAG = "tinyusb"; + +void TinyUSB::setup() { + // Use the device's MAC address as its serial number if no serial number is defined + if (this->string_descriptor_[SERIAL_NUMBER] == nullptr) { + static char mac_addr_buf[13]; + get_mac_address_into_buffer(mac_addr_buf); + this->string_descriptor_[SERIAL_NUMBER] = mac_addr_buf; + } + + this->tusb_cfg_ = { + .descriptor = &this->usb_descriptor_, + .string_descriptor = this->string_descriptor_, + .string_descriptor_count = SIZE, + .external_phy = false, + }; + + esp_err_t result = tinyusb_driver_install(&this->tusb_cfg_); + if (result != ESP_OK) { + this->mark_failed(); + } +} + +void TinyUSB::dump_config() { + ESP_LOGCONFIG(TAG, + "TinyUSB:\n" + " Product ID: 0x%04X\n" + " Vendor ID: 0x%04X\n" + " Manufacturer: '%s'\n" + " Product: '%s'\n" + " Serial: '%s'\n", + this->usb_descriptor_.idProduct, this->usb_descriptor_.idVendor, this->string_descriptor_[MANUFACTURER], + this->string_descriptor_[PRODUCT], this->string_descriptor_[SERIAL_NUMBER]); +} + +} // namespace esphome::tinyusb +#endif diff --git a/esphome/components/tinyusb/tinyusb_component.h b/esphome/components/tinyusb/tinyusb_component.h new file mode 100644 index 000000000..56c286f45 --- /dev/null +++ b/esphome/components/tinyusb/tinyusb_component.h @@ -0,0 +1,72 @@ +#pragma once +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#include "esphome/core/component.h" + +#include "tinyusb.h" +#include "tusb.h" + +namespace esphome::tinyusb { + +enum USBDStringDescriptor : uint8_t { + LANGUAGE_ID = 0, + MANUFACTURER = 1, + PRODUCT = 2, + SERIAL_NUMBER = 3, + INTERFACE = 4, + TERMINATOR = 5, + SIZE = 6, +}; + +static const char *DEFAULT_USB_STR = "ESPHome"; + +class TinyUSB : public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void set_usb_desc_product_id(uint16_t product_id) { this->usb_descriptor_.idProduct = product_id; } + void set_usb_desc_vendor_id(uint16_t vendor_id) { this->usb_descriptor_.idVendor = vendor_id; } + void set_usb_desc_lang_id(uint16_t lang_id) { + this->usb_desc_lang_id_[0] = lang_id & 0xFF; + this->usb_desc_lang_id_[1] = lang_id >> 8; + } + void set_usb_desc_manufacturer(const char *usb_desc_manufacturer) { + this->string_descriptor_[MANUFACTURER] = usb_desc_manufacturer; + } + void set_usb_desc_product(const char *usb_desc_product) { this->string_descriptor_[PRODUCT] = usb_desc_product; } + void set_usb_desc_serial(const char *usb_desc_serial) { this->string_descriptor_[SERIAL_NUMBER] = usb_desc_serial; } + + protected: + char usb_desc_lang_id_[2] = {0x09, 0x04}; // defaults to english + + const char *string_descriptor_[SIZE] = { + this->usb_desc_lang_id_, // 0: supported language is English (0x0409) + DEFAULT_USB_STR, // 1: Manufacturer + DEFAULT_USB_STR, // 2: Product + nullptr, // 3: Serial Number + nullptr, // 4: Interface + nullptr, // 5: Terminator + }; + + tinyusb_config_t tusb_cfg_{}; + tusb_desc_device_t usb_descriptor_{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = 0x303A, + .idProduct = 0x4001, + .bcdDevice = CONFIG_TINYUSB_DESC_BCD_DEVICE, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1, + }; +}; + +} // namespace esphome::tinyusb +#endif diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 31112caf0..fcb3a4f43 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -23,3 +23,7 @@ dependencies: version: "2.0.0" rules: - if: "target in [esp32, esp32p4]" + espressif/esp_tinyusb: + version: "1.7.6~1" + rules: + - if: "target in [esp32s2, esp32s3, esp32p4]" diff --git a/tests/components/tinyusb/common.yaml b/tests/components/tinyusb/common.yaml new file mode 100644 index 000000000..cb3f48836 --- /dev/null +++ b/tests/components/tinyusb/common.yaml @@ -0,0 +1,8 @@ +tinyusb: + id: tinyusb_test + usb_lang_id: 0x0123 + usb_manufacturer_str: ESPHomeTestManufacturer + usb_product_id: 0x1234 + usb_product_str: ESPHomeTestProduct + usb_serial_str: ESPHomeTestSerialNumber + usb_vendor_id: 0x2345 diff --git a/tests/components/tinyusb/test.esp32-p4-idf.yaml b/tests/components/tinyusb/test.esp32-p4-idf.yaml new file mode 100644 index 000000000..dade44d14 --- /dev/null +++ b/tests/components/tinyusb/test.esp32-p4-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tinyusb/test.esp32-s2-idf.yaml b/tests/components/tinyusb/test.esp32-s2-idf.yaml new file mode 100644 index 000000000..dade44d14 --- /dev/null +++ b/tests/components/tinyusb/test.esp32-s2-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tinyusb/test.esp32-s3-idf.yaml b/tests/components/tinyusb/test.esp32-s3-idf.yaml new file mode 100644 index 000000000..dade44d14 --- /dev/null +++ b/tests/components/tinyusb/test.esp32-s3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml