From 768ee410d8f012a4fffdda0e960b37a63e10efbf Mon Sep 17 00:00:00 2001 From: Jack O'Sullivan Date: Mon, 9 Dec 2024 13:41:22 +0000 Subject: [PATCH] firmware+controller: Initial USB device --- controller/.envrc | 2 + controller/.python-version | 1 + controller/default.nix | 30 +++++++++ controller/pyproject.toml | 8 +++ controller/uv.lock | 22 ++++++ controller/valconomy.py | 8 +++ firmware/dependencies.lock | 31 ++++++++- firmware/main/CMakeLists.txt | 2 +- firmware/main/idf_component.yml | 1 + firmware/main/usb.c | 116 ++++++++++++++++++++++++++++++++ firmware/main/valconomy.c | 4 +- firmware/sdkconfig.defaults | 4 ++ flake.nix | 3 + 13 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 controller/.envrc create mode 100644 controller/.python-version create mode 100644 controller/default.nix create mode 100644 controller/pyproject.toml create mode 100644 controller/uv.lock create mode 100755 controller/valconomy.py create mode 100644 firmware/main/usb.c diff --git a/controller/.envrc b/controller/.envrc new file mode 100644 index 0000000..df48e7a --- /dev/null +++ b/controller/.envrc @@ -0,0 +1,2 @@ +watch_file default.nix +use flake ..#controller --override-input rootdir "file+file://"<(printf %s "$PWD") diff --git a/controller/.python-version b/controller/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/controller/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/controller/default.nix b/controller/default.nix new file mode 100644 index 0000000..8e50663 --- /dev/null +++ b/controller/default.nix @@ -0,0 +1,30 @@ +{ + perSystem = { libMy, pkgs, ... }: { + devenv.shells.controller = libMy.withRootdir { + languages = { + python = { + enable = true; + version = "3.13"; + libraries = with pkgs; [ + libusb1 + ]; + uv = { + enable = true; + sync.enable = true; + }; + }; + }; + + packages = with pkgs; [ ]; + + env = { }; + + scripts = { + # build.exec = '' + # cmake -S . -B build -D CMAKE_BUILD_TYPE=Debug -D PICO_STDIO_SEMIHOSTING=1 + # cmake --build build --parallel + # ''; + }; + }; + }; +} diff --git a/controller/pyproject.toml b/controller/pyproject.toml new file mode 100644 index 0000000..91a2d73 --- /dev/null +++ b/controller/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "valconomy" +version = "0.1.0" +description = "Valconomy controller" +requires-python = ">=3.13" +dependencies = [ + "pyusb>=1.2.1", +] diff --git a/controller/uv.lock b/controller/uv.lock new file mode 100644 index 0000000..b125b5f --- /dev/null +++ b/controller/uv.lock @@ -0,0 +1,22 @@ +version = 1 +requires-python = ">=3.13" + +[[package]] +name = "pyusb" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/6e/433a5614132576289b8643fe598dd5d51b16e130fd591564be952e15bb45/pyusb-1.2.1.tar.gz", hash = "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9", size = 75292 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/a8/4982498b2ab44d1fcd5c49f07ea3795eab01601dc143b009d333fcace3b9/pyusb-1.2.1-py3-none-any.whl", hash = "sha256:2b4c7cb86dbadf044dfb9d3a4ff69fd217013dbe78a792177a3feb172449ea36", size = 58439 }, +] + +[[package]] +name = "valconomy" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pyusb" }, +] + +[package.metadata] +requires-dist = [{ name = "pyusb", specifier = ">=1.2.1" }] diff --git a/controller/valconomy.py b/controller/valconomy.py new file mode 100755 index 0000000..007564a --- /dev/null +++ b/controller/valconomy.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +import usb.core + +def main(): + print(usb.core.find(idVendor=0x6969, idProduct=0x0004)) + +if __name__ == '__main__': + main() diff --git a/firmware/dependencies.lock b/firmware/dependencies.lock index 15186fe..727b510 100644 --- a/firmware/dependencies.lock +++ b/firmware/dependencies.lock @@ -24,6 +24,34 @@ dependencies: registry_url: https://components.espressif.com/ type: service version: 1.1.1~2 + espressif/esp_tinyusb: + component_hash: 4878831be091116ec8d0e5eaedcae54a5e9866ebf15a44ef101886fd42c0b91f + dependencies: + - name: idf + require: private + version: '>=5.0' + - name: espressif/tinyusb + registry_url: https://components.espressif.com + require: public + version: '>=0.14.2' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.5.0 + espressif/tinyusb: + component_hash: 8a9e23b8cdc733b51fed357979139f5ae63a2fed3ce4e7c41d505f685f7d741a + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com + type: service + targets: + - esp32s2 + - esp32s3 + - esp32p4 + version: 0.17.0~1 idf: source: type: idf @@ -37,8 +65,9 @@ dependencies: version: 9.2.2 direct_dependencies: - espressif/esp_lcd_touch_gt911 +- espressif/esp_tinyusb - idf - lvgl/lvgl -manifest_hash: fdff5bb68ec2efda1d9ba20223b9e2213f1e52139964d15c1177212d5b447ce9 +manifest_hash: a301075b9c0715323f037caea9def414e47e90934bf7fd0c3b2c9d8ffa30bc49 target: esp32s3 version: 2.0.0 diff --git a/firmware/main/CMakeLists.txt b/firmware/main/CMakeLists.txt index 61eea84..7f6ad4d 100644 --- a/firmware/main/CMakeLists.txt +++ b/firmware/main/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( - SRCS "valconomy.c" "ui.c" + SRCS "valconomy.c" "ui.c" "usb.c" INCLUDE_DIRS ".") diff --git a/firmware/main/idf_component.yml b/firmware/main/idf_component.yml index 1b9c0eb..1d89b45 100644 --- a/firmware/main/idf_component.yml +++ b/firmware/main/idf_component.yml @@ -1,5 +1,6 @@ ## IDF Component Manager Manifest File dependencies: + espressif/esp_tinyusb: "^1.5.0" lvgl/lvgl: "^9.2.2" esp_lcd_touch_gt911: "^1.1.1" ## Required IDF version diff --git a/firmware/main/usb.c b/firmware/main/usb.c new file mode 100644 index 0000000..316b017 --- /dev/null +++ b/firmware/main/usb.c @@ -0,0 +1,116 @@ +#include + +#include "esp_log.h" +#include "esp_mac.h" + +#include "tinyusb.h" +#include "class/hid/hid_device.h" + +#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_HID * TUD_HID_DESC_LEN) + +static const char *TAG = "valconomy-usb"; + +// See https://github.com/hathach/tinyusb/blob/cb22301f91f0465a5578be35d9be284657ddd31d/src/common/tusb_types.h#L331 +const tusb_desc_device_t val_usb_dev_descriptor = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + // BCD-coded USB version (?) + .bcdUSB = 0x0200, + + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0x6969, + .idProduct = 0x0004, + // BCD-coded device version (0.1) + .bcdDevice = 0x0010, + + // Indices of strings + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01, +}; + +const uint8_t val_hid_report_descriptor[] = { + TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD)), +}; + +char val_dev_serial[13]; +const char* val_hid_string_descriptor[5] = { + // 0: is supported language is English (0x0409) + (char[]){0x09, 0x04}, + // 1: Manufacturer + "/dev/player0", + // 2: Product + "Valorant Economy Helper", + // 3: Serials, should use chip ID + val_dev_serial, + // 4: HID + "Valconomy HID interface", +}; + +static const uint8_t val_hid_conf_descriptor[] = { + // Configuration number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval + TUD_HID_DESCRIPTOR(0, 4, false, sizeof(val_hid_report_descriptor), 0x81, 16, 10), +}; + +// TinyUSB callbacks + +// Invoked when received GET HID REPORT DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) { + // We use only one interface and one HID report descriptor, so we can ignore parameter 'instance' + return val_hid_report_descriptor; +} + +// Invoked when received GET_REPORT control request +// Application must fill buffer report's content and return its length. +// Return zero will cause the stack to STALL request +uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) { + (void) instance; + (void) report_id; + (void) report_type; + (void) buffer; + (void) reqlen; + + return 0; +} + +// Invoked when received SET_REPORT control request or +// received data on OUT endpoint ( Report ID = 0, Type = 0 ) +void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) {} + +void val_usb_init(void) { + ESP_LOGI(TAG, "Initializing USB"); + + // Use MAC address for serial number + uint8_t mac[6] = { 0 }; + ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_EFUSE_FACTORY)); + snprintf( + val_dev_serial, sizeof(val_dev_serial), + "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + const tinyusb_config_t cfg = { + .device_descriptor = &val_usb_dev_descriptor, + .string_descriptor = val_hid_string_descriptor, + .string_descriptor_count = sizeof(val_hid_string_descriptor) / sizeof(char *), + .external_phy = false, + +#if (TUD_OPT_HIGH_SPEED) + // HID configuration descriptor for full-speed and high-speed are the same + .fs_configuration_descriptor = val_hid_conf_descriptor, + .hs_configuration_descriptor = val_hid_conf_descriptor, + .qualifier_descriptor = NULL, +#else + .configuration_descriptor = val_hid_conf_descriptor, +#endif // TUD_OPT_HIGH_SPEED + }; + ESP_ERROR_CHECK(tinyusb_driver_install(&cfg)); +} diff --git a/firmware/main/valconomy.c b/firmware/main/valconomy.c index 71bc70b..22a564c 100644 --- a/firmware/main/valconomy.c +++ b/firmware/main/valconomy.c @@ -1,5 +1,4 @@ #include -#include #include "sdkconfig.h" #include "freertos/FreeRTOS.h" @@ -37,6 +36,7 @@ static i2c_master_dev_handle_t exio_cfg_handle, exio_o_handle; // LVGL library is not thread-safe, we will call LVGL APIs from different tasks, so use a mutex to protect it static SemaphoreHandle_t lvgl_mtx = NULL; +extern void val_usb_init(void); extern void val_lvgl_ui(lv_display_t *disp); bool val_lvgl_lock(int timeout_ms) { @@ -293,6 +293,8 @@ void val_setup_lvgl(esp_lcd_panel_handle_t lcd_panel, esp_lcd_touch_handle_t tou void app_main(void) { val_i2c_master_init(); + val_usb_init(); + esp_lcd_panel_handle_t lcd_panel = val_setup_lcd(); assert(lcd_panel); esp_lcd_touch_handle_t touch_panel = val_setup_touch(); diff --git a/firmware/sdkconfig.defaults b/firmware/sdkconfig.defaults index 50690c9..d445844 100644 --- a/firmware/sdkconfig.defaults +++ b/firmware/sdkconfig.defaults @@ -6,6 +6,7 @@ CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_120M=y CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=n CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y @@ -13,8 +14,11 @@ CONFIG_SPIRAM_RODATA=y CONFIG_SPIRAM_SPEED_120M=y CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y CONFIG_ESP_WIFI_DPP_SUPPORT=y CONFIG_FREERTOS_HZ=1000 +CONFIG_TINYUSB_DEBUG_LEVEL=0 +CONFIG_TINYUSB_HID_COUNT=1 CONFIG_LV_DEF_REFR_PERIOD=24 CONFIG_LV_USE_SYSMON=y CONFIG_LV_USE_PERF_MONITOR=y diff --git a/flake.nix b/flake.nix index fdd4eba..52a8ab1 100644 --- a/flake.nix +++ b/flake.nix @@ -4,6 +4,8 @@ devenv.url = "github:cachix/devenv"; devenv.inputs.nixpkgs.follows = "nixpkgs"; + nixpkgs-python.url = "github:cachix/nixpkgs-python"; + nixpkgs-python.inputs.nixpkgs.follows = "nixpkgs"; esp-dev.url = "github:mirrexagon/nixpkgs-esp-dev"; esp-dev.inputs.nixpkgs.follows = "nixpkgs"; @@ -19,6 +21,7 @@ devenv.flakeModule ./firmware + ./controller ]; systems = [ "x86_64-linux" ];