#include <inttypes.h> #include "esp_log.h" #include "tinyusb.h" #include "class/hid/hid_device.h" #include "common.h" #include "usb.h" #include "lcd.h" #include "ui.h" #define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_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 char* val_usb_string_descriptor[5] = { // 0: 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", }; const uint8_t val_hid_report_descriptor[] = { TUD_HID_REPORT_DESC_GENERIC_INOUT(USB_EP_BUFSIZE), }; 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), // HID requires an IN endpoint even if we don't care about it // Interface number, string index, protocol, report descriptor len, EP Out & In address, size & polling interval (ms) // 0x80 in endpoint address indicates IN TUD_HID_INOUT_DESCRIPTOR(0, 4, HID_ITF_PROTOCOL_NONE, sizeof(val_hid_report_descriptor), EPNUM_HID, 0x80 | EPNUM_HID, USB_EP_BUFSIZE, 100) }; // 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* buf, uint16_t reqlen) { (void) instance; if (report_id != 0 || reqlen < 1) { return 0; } // ESP_LOGI(TAG, "Got %hu bytes report %hhu", reqlen, report_id); // for (uint16_t i = 0; i < reqlen; i++) { // ESP_LOGI(TAG, "b: %02hhx", buf[i]); // } buf[0] = val_ui_state_ready(); return 1; } // 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* buf, uint16_t bufsize) { (void) instance; assert(report_id == 0 && report_type == HID_REPORT_TYPE_OUTPUT && bufsize >= 1); if (!val_lvgl_lock(-1)) { ESP_LOGE(TAG, "Failed to grab LVGL lock"); return; } if (buf[0] > ST_GAME_OVER) { ESP_LOGW(TAG, "Unknown state %hhu", buf[0]); goto ret; } if (!val_ui_state_ready()) { goto ret; } switch (buf[0]) { case ST_NONE: val_ui_none(); break; case ST_MENU: if (bufsize < 2) { ESP_LOGE(TAG, "Invalid ST_MENU command"); goto ret; } val_ui_menu((bool)buf[1]); break; case ST_IDLE: val_ui_idle(); break; case ST_QUEUE_START: if (bufsize < 2) { ESP_LOGE(TAG, "Invalid ST_QUEUE_START command"); goto ret; } val_ui_queue_start((bool)buf[1]); break; case ST_MATCH_FOUND: if (bufsize < 2) { ESP_LOGE(TAG, "Invalid ST_MATCH_FOUND command"); goto ret; } val_ui_match_found((bool)buf[1]); break; case ST_PREGAME: if (bufsize < 2) { ESP_LOGE(TAG, "Invalid ST_PREGAME command"); goto ret; } val_ui_pregame((bool)buf[1]); break; case ST_GAME_GENERIC: if (bufsize < 2 || buf[1] > VAL_EXT_GAMEMODES_SIZE) { ESP_LOGE(TAG, "Invalid ST_PREGAME command"); goto ret; } val_ui_game_generic(val_ext_gamemodes[buf[1]]); break; case ST_GAME_START: val_ui_game_start(); break; case ST_GAME_OVER: if (bufsize < 2) { ESP_LOGE(TAG, "Invalid ST_GAME_OVER command"); goto ret; } val_ui_game_over((bool)buf[1]); break; } ret: val_lvgl_unlock(); } void val_usb_init(void) { ESP_LOGI(TAG, "Initializing USB"); const tinyusb_config_t cfg = { .device_descriptor = &val_usb_dev_descriptor, .string_descriptor = val_usb_string_descriptor, .string_descriptor_count = sizeof(val_usb_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)); }