It's connecting!!

This commit is contained in:
Joey Yakimowich-Payne 2025-11-20 17:17:47 -07:00
commit 2225e70e5e
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
7 changed files with 322 additions and 45 deletions

View file

@ -23,6 +23,7 @@ if (EXISTS ${picoVscode})
include(${picoVscode}) include(${picoVscode})
endif() endif()
# ==================================================================================== # ====================================================================================
option(SWITCH_PICO_AUTOTEST "Auto-play grip-screen connection sequence on startup" OFF)
set(PICO_BOARD pico CACHE STRING "Board type") set(PICO_BOARD pico CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project) # 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") pico_set_program_version(switch-pico "0.1")
# Modify the below lines to enable/disable output over UART/USB # 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) pico_enable_stdio_usb(switch-pico 0)
# Add the standard library to the build # Add the standard library to the build
@ -56,6 +58,10 @@ target_link_libraries(switch-pico
pico_rand 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 # Add the standard include files to the build
target_include_directories(switch-pico PRIVATE target_include_directories(switch-pico PRIVATE
${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}

33
host_uart_logger.py Normal file
View file

@ -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()

View file

@ -11,6 +11,20 @@
#define UART_TX_PIN 4 #define UART_TX_PIN 4
#define UART_RX_PIN 5 #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() { static void init_uart_input() {
uart_init(UART_ID, BAUD_RATE); uart_init(UART_ID, BAUD_RATE);
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART); gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
@ -52,12 +66,104 @@ static void poll_uart_frames() {
buffer[index++] = byte; buffer[index++] = byte;
if (index >= sizeof(buffer)) { 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; 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() { int main() {
board_init(); board_init();
stdio_init_all(); stdio_init_all();
@ -66,11 +172,27 @@ int main() {
tusb_init(); tusb_init();
switch_pro_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) { while (true) {
tud_task(); // USB device tasks tud_task(); // USB device tasks
poll_uart_frames(); // Pull controller state from UART1 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 switch_pro_task(); // Push state to the Switch host
log_usb_state();
} }
} }

View file

@ -322,8 +322,10 @@ typedef struct
} SwitchProOutReport; } SwitchProOutReport;
static const uint8_t switch_pro_string_language[] = { 0x09, 0x04 }; 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_manufacturer[] = "Nintendo Co., Ltd.";
static const uint8_t switch_pro_string_product[] = "GP2040-CE (Pro Controller)"; 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_version[] = "000000000001";
static const uint8_t *switch_pro_string_descriptors[] __attribute__((unused)) = static const uint8_t *switch_pro_string_descriptors[] __attribute__((unused)) =
@ -394,7 +396,7 @@ static const uint8_t switch_pro_configuration_descriptor[] =
0x07, // bLength 0x07, // bLength
0x05, // bDescriptorType (Endpoint) 0x05, // bDescriptorType (Endpoint)
0x05, // bEndpointAddress (IN/D2H) 0x81, // bEndpointAddress (IN/D2H)
0x03, // bmAttributes (Interrupt) 0x03, // bmAttributes (Interrupt)
0x40, 0x00, // wMaxPacketSize 64 0x40, 0x00, // wMaxPacketSize 64
0x08, // bInterval 8 (unit depends on device speed) 0x08, // bInterval 8 (unit depends on device speed)

View file

@ -22,11 +22,14 @@ static uint8_t last_report[SWITCH_PRO_ENDPOINT_SIZE] = {};
static SwitchProReport switch_report{}; static SwitchProReport switch_report{};
static uint8_t last_report_counter = 0; static uint8_t last_report_counter = 0;
static uint32_t last_report_timer = 0; static uint32_t last_report_timer = 0;
static uint32_t last_host_activity_ms = 0;
static bool is_ready = false; static bool is_ready = false;
static bool is_initialized = false; static bool is_initialized = false;
static bool is_report_queued = false; static bool is_report_queued = false;
static bool report_sent = false; static bool report_sent = false;
static uint8_t queued_report_id = 0; 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 uint8_t handshake_counter = 0;
static SwitchDeviceInfo device_info{}; static SwitchDeviceInfo device_info{};
@ -176,6 +179,9 @@ static bool send_report(uint8_t reportID, const void* reportData, uint16_t repor
} else { } else {
last_report_counter = 0; last_report_counter = 0;
} }
if (!result) {
printf("[HID] send_report failed id=%u len=%u\n", reportID, reportLength);
}
return result; 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) { static void handle_config_report(uint8_t switchReportID, uint8_t switchReportSubID, const uint8_t *reportData, uint16_t reportLength) {
bool canSend = false; bool canSend = false;
last_host_activity_ms = to_ms_since_boot(get_absolute_time());
host_sent_out = true;
switch (switchReportSubID) { switch (switchReportSubID) {
case IDENTIFY: case IDENTIFY:
send_identify(); send_identify();
canSend = true; canSend = true;
printf("[HID] CONFIG IDENTIFY\n");
break; break;
case HANDSHAKE: case HANDSHAKE:
report_buffer[0] = REPORT_USB_INPUT_81; report_buffer[0] = REPORT_USB_INPUT_81;
report_buffer[1] = HANDSHAKE; report_buffer[1] = HANDSHAKE;
canSend = true; canSend = true;
printf("[HID] CONFIG HANDSHAKE\n");
break; break;
case BAUD_RATE: case BAUD_RATE:
report_buffer[0] = REPORT_USB_INPUT_81; report_buffer[0] = REPORT_USB_INPUT_81;
report_buffer[1] = BAUD_RATE; report_buffer[1] = BAUD_RATE;
canSend = true; canSend = true;
printf("[HID] CONFIG BAUD_RATE\n");
break; break;
case DISABLE_USB_TIMEOUT: case DISABLE_USB_TIMEOUT:
report_buffer[0] = REPORT_OUTPUT_30; report_buffer[0] = REPORT_OUTPUT_30;
@ -219,16 +230,19 @@ static void handle_config_report(uint8_t switchReportID, uint8_t switchReportSub
is_ready = true; is_ready = true;
//} //}
canSend = true; canSend = true;
printf("[HID] CONFIG DISABLE_USB_TIMEOUT -> ready\n");
break; break;
case ENABLE_USB_TIMEOUT: case ENABLE_USB_TIMEOUT:
report_buffer[0] = REPORT_OUTPUT_30; report_buffer[0] = REPORT_OUTPUT_30;
report_buffer[1] = switchReportSubID; report_buffer[1] = switchReportSubID;
canSend = true; canSend = true;
printf("[HID] CONFIG ENABLE_USB_TIMEOUT\n");
break; break;
default: default:
report_buffer[0] = REPORT_OUTPUT_30; report_buffer[0] = REPORT_OUTPUT_30;
report_buffer[1] = switchReportSubID; report_buffer[1] = switchReportSubID;
canSend = true; canSend = true;
printf("[HID] CONFIG unknown subid=0x%02x\n", switchReportSubID);
break; break;
} }
@ -240,6 +254,8 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu
uint32_t spiReadAddress = 0; uint32_t spiReadAddress = 0;
uint8_t spiReadSize = 0; uint8_t spiReadSize = 0;
bool canSend = false; 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[0] = REPORT_OUTPUT_21;
report_buffer[1] = last_report_counter; 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[14] = commandID;
report_buffer[15] = 0x03; report_buffer[15] = 0x03;
canSend = true; canSend = true;
printf("[HID] FEATURE GET_CONTROLLER_STATE\n");
break; break;
case BLUETOOTH_PAIR_REQUEST: case BLUETOOTH_PAIR_REQUEST:
report_buffer[13] = 0x81; report_buffer[13] = 0x81;
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x03; report_buffer[15] = 0x03;
canSend = true; canSend = true;
printf("[HID] FEATURE BLUETOOTH_PAIR_REQUEST\n");
break; break;
case REQUEST_DEVICE_INFO: case REQUEST_DEVICE_INFO:
report_buffer[13] = 0x82; report_buffer[13] = 0x82;
report_buffer[14] = 0x02; report_buffer[14] = 0x02;
memcpy(&report_buffer[15], &device_info, sizeof(device_info)); memcpy(&report_buffer[15], &device_info, sizeof(device_info));
canSend = true; canSend = true;
printf("[HID] FEATURE REQUEST_DEVICE_INFO\n");
break; break;
case SET_MODE: case SET_MODE:
input_mode = reportData[11]; 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[14] = 0x03;
report_buffer[15] = input_mode; report_buffer[15] = input_mode;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_MODE 0x%02x\n", input_mode);
break; break;
case TRIGGER_BUTTONS: case TRIGGER_BUTTONS:
report_buffer[13] = 0x83; report_buffer[13] = 0x83;
report_buffer[14] = 0x04; report_buffer[14] = 0x04;
canSend = true; canSend = true;
printf("[HID] FEATURE TRIGGER_BUTTONS\n");
break; break;
case SET_SHIPMENT: case SET_SHIPMENT:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_SHIPMENT\n");
break; break;
case SPI_READ: case SPI_READ:
spiReadAddress = (reportData[14] << 24) | (reportData[13] << 16) | (reportData[12] << 8) | (reportData[11]); 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]; report_buffer[19] = reportData[15];
read_spi_flash(&report_buffer[20], spiReadAddress, spiReadSize); read_spi_flash(&report_buffer[20], spiReadAddress, spiReadSize);
canSend = true; canSend = true;
printf("[HID] FEATURE SPI_READ addr=0x%08lx size=%u\n", (unsigned long)spiReadAddress, spiReadSize);
break; break;
case SET_NFC_IR_CONFIG: case SET_NFC_IR_CONFIG:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_NFC_IR_CONFIG\n");
break; break;
case SET_NFC_IR_STATE: case SET_NFC_IR_STATE:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_NFC_IR_STATE\n");
break; break;
case SET_PLAYER_LIGHTS: case SET_PLAYER_LIGHTS:
player_id = reportData[11]; player_id = reportData[11];
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_PLAYER_LIGHTS player=%u\n", player_id);
break; break;
case GET_PLAYER_LIGHTS: case GET_PLAYER_LIGHTS:
player_id = reportData[11]; 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[14] = commandID;
report_buffer[15] = player_id; report_buffer[15] = player_id;
canSend = true; canSend = true;
printf("[HID] FEATURE GET_PLAYER_LIGHTS player=%u\n", player_id);
break; break;
case COMMAND_UNKNOWN_33: case COMMAND_UNKNOWN_33:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x03; report_buffer[15] = 0x03;
canSend = true; canSend = true;
printf("[HID] FEATURE COMMAND_UNKNOWN_33\n");
break; break;
case SET_HOME_LIGHT: case SET_HOME_LIGHT:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x00; report_buffer[15] = 0x00;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_HOME_LIGHT\n");
break; break;
case TOGGLE_IMU: case TOGGLE_IMU:
is_imu_enabled = reportData[11]; 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[14] = commandID;
report_buffer[15] = 0x00; report_buffer[15] = 0x00;
canSend = true; canSend = true;
printf("[HID] FEATURE TOGGLE_IMU %u\n", is_imu_enabled);
break; break;
case IMU_SENSITIVITY: case IMU_SENSITIVITY:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
canSend = true; canSend = true;
printf("[HID] FEATURE IMU_SENSITIVITY\n");
break; break;
case ENABLE_VIBRATION: case ENABLE_VIBRATION:
is_vibration_enabled = reportData[11]; 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[14] = commandID;
report_buffer[15] = 0x00; report_buffer[15] = 0x00;
canSend = true; canSend = true;
printf("[HID] FEATURE ENABLE_VIBRATION %u\n", is_vibration_enabled);
break; break;
case READ_IMU: case READ_IMU:
report_buffer[13] = 0xC0; 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[15] = reportData[11];
report_buffer[16] = reportData[12]; report_buffer[16] = reportData[12];
canSend = true; canSend = true;
printf("[HID] FEATURE READ_IMU addr=%u size=%u\n", reportData[11], reportData[12]);
break; break;
case GET_VOLTAGE: case GET_VOLTAGE:
report_buffer[13] = 0xD0; 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[15] = 0x83;
report_buffer[16] = 0x06; report_buffer[16] = 0x06;
canSend = true; canSend = true;
printf("[HID] FEATURE GET_VOLTAGE\n");
break; break;
default: default:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x03; report_buffer[15] = 0x03;
canSend = true; canSend = true;
printf("[HID] FEATURE unknown cmd=0x%02x\n", commandID);
break; break;
} }
@ -421,6 +456,8 @@ void switch_pro_init() {
is_initialized = false; is_initialized = false;
is_report_queued = false; is_report_queued = false;
report_sent = false; report_sent = false;
forced_ready = false;
host_sent_out = false;
device_info = { device_info = {
.majorVersion = 0x04, .majorVersion = 0x04,
@ -437,8 +474,8 @@ void switch_pro_init() {
.timestamp = 0, .timestamp = 0,
.inputs { .inputs {
.connectionInfo = 0, .connectionInfo = 0x08, // wired connection
.batteryLevel = 0x08, .batteryLevel = 0x0F, // full battery
.buttonY = 0, .buttonY = 0,
.buttonX = 0, .buttonX = 0,
@ -475,6 +512,7 @@ void switch_pro_init() {
}; };
last_report_timer = to_ms_since_boot(get_absolute_time()); 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.getRealMin(leftMinX, leftMinY);
factory_config->leftStickCalibration.getCenter(leftCenX, leftCenY); factory_config->leftStickCalibration.getCenter(leftCenX, leftCenY);
@ -508,7 +546,8 @@ void switch_pro_task() {
report_sent = true; 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) { if ((now - last_report_timer) > SWITCH_PRO_KEEPALIVE_TIMER) {
switch_report.timestamp = last_report_counter; switch_report.timestamp = last_report_counter;
void * inputReport = &switch_report; 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 // Packet format: 0xAA, buttons(2 LE), hat, lx, ly, rx, ry
if (length < 8 || packet[0] != 0xAA) { if (length < 8 || packet[0] != 0xAA) {
return false; 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.rx = expand_axis(out.rx);
state.ry = expand_axis(out.ry); state.ry = expand_axis(out.ry);
if (out_state) {
*out_state = state;
} else {
switch_pro_set_input(state); switch_pro_set_input(state);
}
return true; return true;
} }
bool switch_pro_is_ready() {
return is_ready;
}
void switch_pro_mark_host_active() {
host_sent_out = true;
}
// HID callbacks // 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) { 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)instance;
(void)report_id; printf("[HID] get_report id=%u type=%u len=%u\n", report_id, report_type, reqlen);
(void)report_type; if (!buffer) return 0;
(void)buffer;
(void)reqlen; // Serve the current input report for any GET_REPORT request.
return 0; 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) { 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 switchReportID = buffer[0];
uint8_t switchReportSubID = buffer[1]; 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) { if (switchReportID == REPORT_OUTPUT_00) {
// No-op, just acknowledge to clear any stalls.
return;
} else if (switchReportID == REPORT_FEATURE) { } else if (switchReportID == REPORT_FEATURE) {
queued_report_id = report_id; queued_report_id = report_id;
handle_feature_report(switchReportID, switchReportSubID, buffer, bufsize); 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) { uint8_t const * tud_hid_descriptor_report_cb(uint8_t itf) {
(void)itf; (void)itf;
return switch_pro_report_descriptor; return switch_pro_report_descriptor;
@ -634,6 +713,30 @@ uint8_t const * tud_descriptor_configuration_cb(uint8_t index) {
return switch_pro_configuration_descriptor; 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]; static uint16_t desc_str[32];
uint16_t const * tud_descriptor_string_cb(uint8_t index, uint16_t langid) { uint16_t const * tud_descriptor_string_cb(uint8_t index, uint16_t langid) {

View file

@ -47,4 +47,11 @@ void switch_pro_set_input(const SwitchInputState& state);
void switch_pro_task(); void switch_pro_task();
// Convert a packed UART message into controller state (returns true if parsed). // 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();

View file

@ -28,6 +28,10 @@ extern "C" {
#define CFG_TUD_MSC 0 #define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0 #define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 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 #define CFG_TUD_HID_EP_BUFSIZE 64