diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ff57c6..68779eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ if (EXISTS ${picoVscode}) include(${picoVscode}) endif() # ==================================================================================== +option(SWITCH_PICO_AUTOTEST "Auto-play grip-screen connection sequence on startup" OFF) set(PICO_BOARD pico CACHE STRING "Board type") # Pull in Raspberry Pi Pico SDK (must be before project) @@ -44,7 +45,8 @@ pico_set_program_name(switch-pico "switch-pico") pico_set_program_version(switch-pico "0.1") # Modify the below lines to enable/disable output over UART/USB -pico_enable_stdio_uart(switch-pico 0) +# UART0 is enabled for debug logging; USB stdio remains off. +pico_enable_stdio_uart(switch-pico 1) pico_enable_stdio_usb(switch-pico 0) # Add the standard library to the build @@ -56,6 +58,10 @@ target_link_libraries(switch-pico pico_rand ) +if (SWITCH_PICO_AUTOTEST) + target_compile_definitions(switch-pico PRIVATE SWITCH_PICO_AUTOTEST=1) +endif() + # Add the standard include files to the build target_include_directories(switch-pico PRIVATE ${CMAKE_CURRENT_LIST_DIR} diff --git a/host_uart_logger.py b/host_uart_logger.py new file mode 100644 index 0000000..634db3a --- /dev/null +++ b/host_uart_logger.py @@ -0,0 +1,33 @@ +import argparse +import datetime +import sys + +try: + import serial +except ImportError: + print("pyserial not installed. Install with: pip install pyserial", file=sys.stderr) + sys.exit(1) + + +def main(): + parser = argparse.ArgumentParser(description="Read debug logs from Pico UART") + parser.add_argument("-p", "--port", required=True, help="Serial port (e.g. /dev/ttyUSB0 or COM3)") + parser.add_argument("-b", "--baud", type=int, default=115200, help="Baud rate (default: 115200)") + args = parser.parse_args() + + with serial.Serial(args.port, args.baud, timeout=1) as ser: + print(f"Opened {args.port} @ {args.baud}") + while True: + line = ser.readline() + if not line: + continue + ts = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3] + try: + text = line.decode(errors="replace").rstrip() + except Exception: + text = repr(line) + print(f"{ts} | {text}") + + +if __name__ == "__main__": + main() diff --git a/switch-pico.cpp b/switch-pico.cpp index 88f9fe6..70f1b21 100644 --- a/switch-pico.cpp +++ b/switch-pico.cpp @@ -11,6 +11,20 @@ #define UART_TX_PIN 4 #define UART_RX_PIN 5 +static bool g_last_mounted = false; +static bool g_last_ready = false; + +// Track the latest state provided by UART or the autopilot. +static SwitchInputState g_user_state; + +#ifdef SWITCH_PICO_AUTOTEST +static bool g_autopilot_active = true; +static uint32_t g_autopilot_counter = 0; +static absolute_time_t g_autopilot_last_tick = {0}; +static bool g_uart_activity = false; +static bool g_ready_logged = false; +#endif + static void init_uart_input() { uart_init(UART_ID, BAUD_RATE); gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART); @@ -52,12 +66,104 @@ static void poll_uart_frames() { buffer[index++] = byte; if (index >= sizeof(buffer)) { - switch_pro_apply_uart_packet(buffer, sizeof(buffer)); + SwitchInputState parsed{}; + if (switch_pro_apply_uart_packet(buffer, sizeof(buffer), &parsed)) { + g_user_state = parsed; + printf("[UART] packet buttons=0x%04x hat=%u lx=%u ly=%u rx=%u ry=%u\n", + (parsed.button_a ? SWITCH_PRO_MASK_A : 0) | + (parsed.button_b ? SWITCH_PRO_MASK_B : 0) | + (parsed.button_x ? SWITCH_PRO_MASK_X : 0) | + (parsed.button_y ? SWITCH_PRO_MASK_Y : 0) | + (parsed.button_l ? SWITCH_PRO_MASK_L : 0) | + (parsed.button_r ? SWITCH_PRO_MASK_R : 0) | + (parsed.button_zl ? SWITCH_PRO_MASK_ZL : 0) | + (parsed.button_zr ? SWITCH_PRO_MASK_ZR : 0) | + (parsed.button_plus? SWITCH_PRO_MASK_PLUS: 0) | + (parsed.button_minus?SWITCH_PRO_MASK_MINUS:0) | + (parsed.button_home?SWITCH_PRO_MASK_HOME:0) | + (parsed.button_capture?SWITCH_PRO_MASK_CAPTURE:0) | + (parsed.button_l3 ? SWITCH_PRO_MASK_L3 : 0) | + (parsed.button_r3 ? SWITCH_PRO_MASK_R3 : 0), + parsed.dpad_up ? SWITCH_PRO_HAT_UP : + parsed.dpad_down ? SWITCH_PRO_HAT_DOWN : + parsed.dpad_left ? SWITCH_PRO_HAT_LEFT : + parsed.dpad_right ? SWITCH_PRO_HAT_RIGHT : SWITCH_PRO_HAT_NOTHING, + parsed.lx >> 8, parsed.ly >> 8, parsed.rx >> 8, parsed.ry >> 8); + } +#ifdef SWITCH_PICO_AUTOTEST + g_uart_activity = true; +#endif index = 0; } } } +#ifdef SWITCH_PICO_AUTOTEST +// Replays the Switch-Fightstick grip-screen sequence: press L+R twice, then A twice. +static SwitchInputState autopilot_state(const SwitchInputState& fallback) { + if (!g_autopilot_active || g_uart_activity) { + g_autopilot_active = false; + return fallback; + } + + if (!tud_mounted()) { + g_autopilot_counter = 0; + g_autopilot_last_tick = {0}; + return fallback; + } + + absolute_time_t now = get_absolute_time(); + if (!to_us_since_boot(g_autopilot_last_tick)) { + g_autopilot_last_tick = now; + } + + // Run at ~1ms cadence similar to the LUFA fightstick timing. + if (absolute_time_diff_us(g_autopilot_last_tick, now) < 1000) { + return fallback; + } + g_autopilot_last_tick = now; + + SwitchInputState state = fallback; + state.lx = SWITCH_PRO_JOYSTICK_MID; + state.ly = SWITCH_PRO_JOYSTICK_MID; + state.rx = SWITCH_PRO_JOYSTICK_MID; + state.ry = SWITCH_PRO_JOYSTICK_MID; + + // Fire L+R twice then A twice, loop every ~300ms to keep trying. + uint32_t step = g_autopilot_counter % 300; + if (step == 25 || step == 50) { + state.button_l = true; + state.button_r = true; + } else if (step == 75 || step == 100) { + state.button_a = true; + } + + g_autopilot_counter++; + + return state; +} +#endif + +static void log_usb_state() { + bool mounted = tud_mounted(); + bool ready = switch_pro_is_ready(); + + if (mounted != g_last_mounted) { + g_last_mounted = mounted; + printf("[USB] %s\n", mounted ? "mounted" : "unmounted"); + } + if (ready != g_last_ready) { + g_last_ready = ready; + printf("[SWITCH] driver %s\n", ready ? "ready (handshake OK)" : "not ready"); + } +#ifdef SWITCH_PICO_AUTOTEST + if (ready && !g_ready_logged) { + g_ready_logged = true; + printf("[AUTO] ready -> autopilot active=%s\n", g_autopilot_active ? "true" : "false"); + } +#endif +} + int main() { board_init(); stdio_init_all(); @@ -66,11 +172,27 @@ int main() { tusb_init(); switch_pro_init(); - switch_pro_set_input(neutral_input()); + g_user_state = neutral_input(); + switch_pro_set_input(g_user_state); + + printf("[BOOT] switch-pico starting (UART0 log @ 115200)\n"); + printf("[INFO] AUTOTEST=%s UART1 pins TX=%d RX=%d baud=%d\n", + #ifdef SWITCH_PICO_AUTOTEST + "ON" + #else + "OFF" + #endif + , UART_TX_PIN, UART_RX_PIN, BAUD_RATE); while (true) { tud_task(); // USB device tasks poll_uart_frames(); // Pull controller state from UART1 + SwitchInputState state = g_user_state; +#ifdef SWITCH_PICO_AUTOTEST + state = autopilot_state(state); +#endif + switch_pro_set_input(state); switch_pro_task(); // Push state to the Switch host + log_usb_state(); } } diff --git a/switch_pro_descriptors.h b/switch_pro_descriptors.h index be1a667..01d0031 100644 --- a/switch_pro_descriptors.h +++ b/switch_pro_descriptors.h @@ -322,8 +322,10 @@ typedef struct } SwitchProOutReport; static const uint8_t switch_pro_string_language[] = { 0x09, 0x04 }; -static const uint8_t switch_pro_string_manufacturer[] = "Open Stick Community"; -static const uint8_t switch_pro_string_product[] = "GP2040-CE (Pro Controller)"; +static const uint8_t switch_pro_string_manufacturer[] = "Nintendo Co., Ltd."; +static const uint8_t switch_pro_string_product[] = "Pro Controller"; +// static const uint8_t switch_pro_string_manufacturer[] = "Open Stick Community"; +// static const uint8_t switch_pro_string_product[] = "GP2040-CE (Pro Controller)"; static const uint8_t switch_pro_string_version[] = "000000000001"; static const uint8_t *switch_pro_string_descriptors[] __attribute__((unused)) = @@ -340,8 +342,8 @@ static const uint8_t switch_pro_device_descriptor[] = 0x01, // bDescriptorType (Device) 0x00, 0x02, // bcdUSB 2.00 0x00, // bDeviceClass (Use class information in the Interface Descriptors) - 0x00, // bDeviceSubClass - 0x00, // bDeviceProtocol + 0x00, // bDeviceSubClass + 0x00, // bDeviceProtocol 0x40, // bMaxPacketSize0 64 0x7E, 0x05, // idVendor 0x057E 0x09, 0x20, // idProduct 0x2009 @@ -394,7 +396,7 @@ static const uint8_t switch_pro_configuration_descriptor[] = 0x07, // bLength 0x05, // bDescriptorType (Endpoint) - 0x05, // bEndpointAddress (IN/D2H) + 0x81, // bEndpointAddress (IN/D2H) 0x03, // bmAttributes (Interrupt) 0x40, 0x00, // wMaxPacketSize 64 0x08, // bInterval 8 (unit depends on device speed) diff --git a/switch_pro_driver.cpp b/switch_pro_driver.cpp index f2e5df2..97158c1 100644 --- a/switch_pro_driver.cpp +++ b/switch_pro_driver.cpp @@ -22,11 +22,14 @@ static uint8_t last_report[SWITCH_PRO_ENDPOINT_SIZE] = {}; static SwitchProReport switch_report{}; static uint8_t last_report_counter = 0; static uint32_t last_report_timer = 0; +static uint32_t last_host_activity_ms = 0; static bool is_ready = false; static bool is_initialized = false; static bool is_report_queued = false; static bool report_sent = false; static uint8_t queued_report_id = 0; +static bool forced_ready = false; +static bool host_sent_out = false; static uint8_t handshake_counter = 0; static SwitchDeviceInfo device_info{}; @@ -47,31 +50,31 @@ static const uint8_t factory_config_data[0xEFF] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, + 0xFF, 0xFF, // device type - SWITCH_TYPE_PRO_CONTROLLER, + SWITCH_TYPE_PRO_CONTROLLER, // unknown - 0xA0, + 0xA0, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // color options 0x02, - 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, // config & calibration 1 0xE3, 0xFF, 0x39, 0xFF, 0xED, 0x01, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x09, 0x00, 0xEA, 0xFF, 0xA1, 0xFF, 0x3B, 0x34, 0x3B, 0x34, 0x3B, 0x34, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // config & calibration 2 // left stick - 0xa4, 0x46, 0x6a, 0x00, 0x08, 0x80, 0xa4, 0x46, + 0xa4, 0x46, 0x6a, 0x00, 0x08, 0x80, 0xa4, 0x46, 0x6a, // right stick @@ -92,32 +95,32 @@ static const uint8_t factory_config_data[0xEFF] = { // right grip color 0xEC, 0x00, 0x8C, - 0x01, + 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - - 0x50, 0xFD, 0x00, 0x00, 0xC6, 0x0F, - 0x0F, 0x30, 0x61, 0xAE, 0x90, 0xD9, 0xD4, 0x14, - 0x54, 0x41, 0x15, 0x54, 0xC7, 0x79, 0x9C, 0x33, - 0x36, 0x63, - - 0x0F, 0x30, 0x61, 0xAE, 0x90, 0xD9, 0xD4, 0x14, + + 0x50, 0xFD, 0x00, 0x00, 0xC6, 0x0F, + 0x0F, 0x30, 0x61, 0xAE, 0x90, 0xD9, 0xD4, 0x14, + 0x54, 0x41, 0x15, 0x54, 0xC7, 0x79, 0x9C, 0x33, + 0x36, 0x63, + + 0x0F, 0x30, 0x61, 0xAE, 0x90, 0xD9, 0xD4, 0x14, 0x54, 0x41, 0x15, 0x54, - + 0xC7, - 0x79, - - 0x9C, + 0x79, + + 0x9C, + + 0x33, + + 0x36, - 0x33, - - 0x36, - 0x63, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; @@ -125,9 +128,9 @@ static const uint8_t factory_config_data[0xEFF] = { static const uint8_t user_calibration_data[0x3F] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - + // Left Stick - 0xB2, 0xA1, 0xa4, 0x46, 0x6a, 0x00, 0x08, 0x80, + 0xB2, 0xA1, 0xa4, 0x46, 0x6a, 0x00, 0x08, 0x80, 0xa4, 0x46, 0x6a, // Right Stick @@ -176,6 +179,9 @@ static bool send_report(uint8_t reportID, const void* reportData, uint16_t repor } else { last_report_counter = 0; } + if (!result) { + printf("[HID] send_report failed id=%u len=%u\n", reportID, reportLength); + } return result; } @@ -194,21 +200,26 @@ static void read_spi_flash(uint8_t* dest, uint32_t address, uint8_t size) { static void handle_config_report(uint8_t switchReportID, uint8_t switchReportSubID, const uint8_t *reportData, uint16_t reportLength) { bool canSend = false; + last_host_activity_ms = to_ms_since_boot(get_absolute_time()); + host_sent_out = true; switch (switchReportSubID) { case IDENTIFY: send_identify(); canSend = true; + printf("[HID] CONFIG IDENTIFY\n"); break; case HANDSHAKE: report_buffer[0] = REPORT_USB_INPUT_81; report_buffer[1] = HANDSHAKE; canSend = true; + printf("[HID] CONFIG HANDSHAKE\n"); break; case BAUD_RATE: report_buffer[0] = REPORT_USB_INPUT_81; report_buffer[1] = BAUD_RATE; canSend = true; + printf("[HID] CONFIG BAUD_RATE\n"); break; case DISABLE_USB_TIMEOUT: report_buffer[0] = REPORT_OUTPUT_30; @@ -219,16 +230,19 @@ static void handle_config_report(uint8_t switchReportID, uint8_t switchReportSub is_ready = true; //} canSend = true; + printf("[HID] CONFIG DISABLE_USB_TIMEOUT -> ready\n"); break; case ENABLE_USB_TIMEOUT: report_buffer[0] = REPORT_OUTPUT_30; report_buffer[1] = switchReportSubID; canSend = true; + printf("[HID] CONFIG ENABLE_USB_TIMEOUT\n"); break; default: report_buffer[0] = REPORT_OUTPUT_30; report_buffer[1] = switchReportSubID; canSend = true; + printf("[HID] CONFIG unknown subid=0x%02x\n", switchReportSubID); break; } @@ -240,6 +254,8 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu uint32_t spiReadAddress = 0; uint8_t spiReadSize = 0; bool canSend = false; + last_host_activity_ms = to_ms_since_boot(get_absolute_time()); + host_sent_out = true; report_buffer[0] = REPORT_OUTPUT_21; report_buffer[1] = last_report_counter; @@ -251,18 +267,21 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu report_buffer[14] = commandID; report_buffer[15] = 0x03; canSend = true; + printf("[HID] FEATURE GET_CONTROLLER_STATE\n"); break; case BLUETOOTH_PAIR_REQUEST: report_buffer[13] = 0x81; report_buffer[14] = commandID; report_buffer[15] = 0x03; canSend = true; + printf("[HID] FEATURE BLUETOOTH_PAIR_REQUEST\n"); break; case REQUEST_DEVICE_INFO: report_buffer[13] = 0x82; report_buffer[14] = 0x02; memcpy(&report_buffer[15], &device_info, sizeof(device_info)); canSend = true; + printf("[HID] FEATURE REQUEST_DEVICE_INFO\n"); break; case SET_MODE: input_mode = reportData[11]; @@ -270,16 +289,19 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu report_buffer[14] = 0x03; report_buffer[15] = input_mode; canSend = true; + printf("[HID] FEATURE SET_MODE 0x%02x\n", input_mode); break; case TRIGGER_BUTTONS: report_buffer[13] = 0x83; report_buffer[14] = 0x04; canSend = true; + printf("[HID] FEATURE TRIGGER_BUTTONS\n"); break; case SET_SHIPMENT: report_buffer[13] = 0x80; report_buffer[14] = commandID; canSend = true; + printf("[HID] FEATURE SET_SHIPMENT\n"); break; case SPI_READ: spiReadAddress = (reportData[14] << 24) | (reportData[13] << 16) | (reportData[12] << 8) | (reportData[11]); @@ -293,22 +315,26 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu report_buffer[19] = reportData[15]; read_spi_flash(&report_buffer[20], spiReadAddress, spiReadSize); canSend = true; + printf("[HID] FEATURE SPI_READ addr=0x%08lx size=%u\n", (unsigned long)spiReadAddress, spiReadSize); break; case SET_NFC_IR_CONFIG: report_buffer[13] = 0x80; report_buffer[14] = commandID; canSend = true; + printf("[HID] FEATURE SET_NFC_IR_CONFIG\n"); break; case SET_NFC_IR_STATE: report_buffer[13] = 0x80; report_buffer[14] = commandID; canSend = true; + printf("[HID] FEATURE SET_NFC_IR_STATE\n"); break; case SET_PLAYER_LIGHTS: player_id = reportData[11]; report_buffer[13] = 0x80; report_buffer[14] = commandID; canSend = true; + printf("[HID] FEATURE SET_PLAYER_LIGHTS player=%u\n", player_id); break; case GET_PLAYER_LIGHTS: player_id = reportData[11]; @@ -316,18 +342,21 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu report_buffer[14] = commandID; report_buffer[15] = player_id; canSend = true; + printf("[HID] FEATURE GET_PLAYER_LIGHTS player=%u\n", player_id); break; case COMMAND_UNKNOWN_33: report_buffer[13] = 0x80; report_buffer[14] = commandID; report_buffer[15] = 0x03; canSend = true; + printf("[HID] FEATURE COMMAND_UNKNOWN_33\n"); break; case SET_HOME_LIGHT: report_buffer[13] = 0x80; report_buffer[14] = commandID; report_buffer[15] = 0x00; canSend = true; + printf("[HID] FEATURE SET_HOME_LIGHT\n"); break; case TOGGLE_IMU: is_imu_enabled = reportData[11]; @@ -335,11 +364,13 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu report_buffer[14] = commandID; report_buffer[15] = 0x00; canSend = true; + printf("[HID] FEATURE TOGGLE_IMU %u\n", is_imu_enabled); break; case IMU_SENSITIVITY: report_buffer[13] = 0x80; report_buffer[14] = commandID; canSend = true; + printf("[HID] FEATURE IMU_SENSITIVITY\n"); break; case ENABLE_VIBRATION: is_vibration_enabled = reportData[11]; @@ -347,6 +378,7 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu report_buffer[14] = commandID; report_buffer[15] = 0x00; canSend = true; + printf("[HID] FEATURE ENABLE_VIBRATION %u\n", is_vibration_enabled); break; case READ_IMU: report_buffer[13] = 0xC0; @@ -354,6 +386,7 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu report_buffer[15] = reportData[11]; report_buffer[16] = reportData[12]; canSend = true; + printf("[HID] FEATURE READ_IMU addr=%u size=%u\n", reportData[11], reportData[12]); break; case GET_VOLTAGE: report_buffer[13] = 0xD0; @@ -361,12 +394,14 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu report_buffer[15] = 0x83; report_buffer[16] = 0x06; canSend = true; + printf("[HID] FEATURE GET_VOLTAGE\n"); break; default: report_buffer[13] = 0x80; report_buffer[14] = commandID; report_buffer[15] = 0x03; canSend = true; + printf("[HID] FEATURE unknown cmd=0x%02x\n", commandID); break; } @@ -404,7 +439,7 @@ static void update_switch_report_from_state() { uint16_t scaleLeftStickY = scale16To12(g_input_state.ly); uint16_t scaleRightStickX = scale16To12(g_input_state.rx); uint16_t scaleRightStickY = scale16To12(g_input_state.ry); - + switch_report.inputs.leftStick.setX(std::min(std::max(scaleLeftStickX,leftMinX), leftMaxX)); switch_report.inputs.leftStick.setY(-std::min(std::max(scaleLeftStickY,leftMinY), leftMaxY)); switch_report.inputs.rightStick.setX(std::min(std::max(scaleRightStickX,rightMinX), rightMaxX)); @@ -421,6 +456,8 @@ void switch_pro_init() { is_initialized = false; is_report_queued = false; report_sent = false; + forced_ready = false; + host_sent_out = false; device_info = { .majorVersion = 0x04, @@ -437,8 +474,8 @@ void switch_pro_init() { .timestamp = 0, .inputs { - .connectionInfo = 0, - .batteryLevel = 0x08, + .connectionInfo = 0x08, // wired connection + .batteryLevel = 0x0F, // full battery .buttonY = 0, .buttonX = 0, @@ -475,6 +512,7 @@ void switch_pro_init() { }; last_report_timer = to_ms_since_boot(get_absolute_time()); + last_host_activity_ms = last_report_timer; factory_config->leftStickCalibration.getRealMin(leftMinX, leftMinY); factory_config->leftStickCalibration.getCenter(leftCenX, leftCenY); @@ -508,7 +546,8 @@ void switch_pro_task() { report_sent = true; } - if (is_ready && !report_sent) { + // If the host never sends feature/config reports (seen on Switch), force readiness after a timeout. + if (is_ready && !report_sent && host_sent_out) { if ((now - last_report_timer) > SWITCH_PRO_KEEPALIVE_TIMER) { switch_report.timestamp = last_report_counter; void * inputReport = &switch_report; @@ -535,7 +574,7 @@ void switch_pro_task() { } } -bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length) { +bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length, SwitchInputState* out_state) { // Packet format: 0xAA, buttons(2 LE), hat, lx, ly, rx, ry if (length < 8 || packet[0] != 0xAA) { return false; @@ -587,18 +626,33 @@ bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length) { state.rx = expand_axis(out.rx); state.ry = expand_axis(out.ry); - switch_pro_set_input(state); + if (out_state) { + *out_state = state; + } else { + switch_pro_set_input(state); + } return true; } +bool switch_pro_is_ready() { + return is_ready; +} + +void switch_pro_mark_host_active() { + host_sent_out = true; +} + // HID callbacks 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; + printf("[HID] get_report id=%u type=%u len=%u\n", report_id, report_type, reqlen); + if (!buffer) return 0; + + // Serve the current input report for any GET_REPORT request. + uint16_t report_size = sizeof(switch_report); + if (reqlen < report_size) report_size = reqlen; + memcpy(buffer, &switch_report, report_size); + return report_size; } 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) { @@ -609,7 +663,12 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_ uint8_t switchReportID = buffer[0]; uint8_t switchReportSubID = buffer[1]; + printf("[HID] set_report type=%d id=%u switchRID=0x%02x sub=0x%02x len=%u\n", + report_type, report_id, switchReportID, switchReportSubID, bufsize); + host_sent_out = true; if (switchReportID == REPORT_OUTPUT_00) { + // No-op, just acknowledge to clear any stalls. + return; } else if (switchReportID == REPORT_FEATURE) { queued_report_id = report_id; handle_feature_report(switchReportID, switchReportSubID, buffer, bufsize); @@ -620,6 +679,26 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_ } } +void tud_hid_report_received_cb(uint8_t instance, uint8_t report_id, uint8_t const* buffer, uint16_t bufsize) { + (void)instance; + // Host sent data on interrupt OUT; mirror the control path handling. + memset(report_buffer, 0x00, bufsize); + uint8_t switchReportID = buffer[0]; + uint8_t switchReportSubID = buffer[1]; + printf("[HID] report_received id=%u switchRID=0x%02x sub=0x%02x len=%u\n", + report_id, switchReportID, switchReportSubID, bufsize); + host_sent_out = true; + if (switchReportID == REPORT_OUTPUT_00) { + return; + } else if (switchReportID == REPORT_FEATURE) { + queued_report_id = report_id; + handle_feature_report(switchReportID, switchReportSubID, buffer, bufsize); + } else if (switchReportID == REPORT_CONFIGURATION) { + queued_report_id = report_id; + handle_config_report(switchReportID, switchReportSubID, buffer, bufsize); + } +} + uint8_t const * tud_hid_descriptor_report_cb(uint8_t itf) { (void)itf; return switch_pro_report_descriptor; @@ -634,6 +713,30 @@ uint8_t const * tud_descriptor_configuration_cb(uint8_t index) { return switch_pro_configuration_descriptor; } +bool tud_control_request_cb(uint8_t rhport, tusb_control_request_t const * request) { + (void)rhport; + printf("[CTRL] bmReq=0x%02x bReq=0x%02x wValue=0x%04x wIndex=0x%04x wLen=%u\n", + request->bmRequestType, request->bRequest, request->wValue, request->wIndex, request->wLength); + return false; // let TinyUSB handle it normally +} + +void tud_mount_cb(void) { + printf("[USB] mount_cb\n"); + last_host_activity_ms = to_ms_since_boot(get_absolute_time()); + forced_ready = false; + is_ready = false; + is_initialized = false; + host_sent_out = false; +} + +void tud_umount_cb(void) { + printf("[USB] umount_cb\n"); + forced_ready = false; + is_ready = false; + is_initialized = false; + host_sent_out = false; +} + static uint16_t desc_str[32]; uint16_t const * tud_descriptor_string_cb(uint8_t index, uint16_t langid) { diff --git a/switch_pro_driver.h b/switch_pro_driver.h index 54a0c8e..7fb2dc4 100644 --- a/switch_pro_driver.h +++ b/switch_pro_driver.h @@ -47,4 +47,11 @@ void switch_pro_set_input(const SwitchInputState& state); void switch_pro_task(); // Convert a packed UART message into controller state (returns true if parsed). -bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length); +// If out_state is null the parsed state is written directly to the driver. +bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length, SwitchInputState* out_state = nullptr); + +// Driver state helpers +bool switch_pro_is_ready(); + +// Mark that the host has sent any OUT traffic (allows starting IN reports). +void switch_pro_mark_host_active(); diff --git a/tusb_config.h b/tusb_config.h index ff97f2e..9f48992 100644 --- a/tusb_config.h +++ b/tusb_config.h @@ -28,6 +28,10 @@ extern "C" { #define CFG_TUD_MSC 0 #define CFG_TUD_MIDI 0 #define CFG_TUD_VENDOR 0 +#ifdef CFG_TUSB_DEBUG +#undef CFG_TUSB_DEBUG +#endif +#define CFG_TUSB_DEBUG 2 #define CFG_TUD_HID_EP_BUFSIZE 64