feat(native-usb): route gameplay rumble and attach only after startup readiness
This commit is contained in:
parent
9f8678af96
commit
ae1ca51b9a
18 changed files with 1158 additions and 43 deletions
|
|
@ -489,6 +489,17 @@ extern "C" void probe_controller_input_set_full_stick_calibration(
|
|||
uint8_t instance, const uint8_t calibration[9]) {
|
||||
probe_native_gamepad_input_set_stick_calibration(instance, calibration);
|
||||
}
|
||||
|
||||
extern "C" bool probe_controller_input_submit_rumble(
|
||||
uint8_t instance, const uint8_t* magnitudes, uint8_t count) {
|
||||
return g_flash_ready && instance < PROBE_CONTROLLER_COUNT &&
|
||||
bluepad32_input_backend_native_rumble_submit(instance, magnitudes, count);
|
||||
}
|
||||
|
||||
extern "C" void probe_controller_input_cancel_rumble(uint8_t instance) {
|
||||
if (instance < PROBE_CONTROLLER_COUNT)
|
||||
bluepad32_input_backend_native_rumble_cancel(instance);
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" void probe_controller_input_set_native_stream(uint8_t instance, bool enabled) {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ void probe_controller_input_set_native_features(uint8_t features);
|
|||
// Supply each child's advertised, validated nine-byte stick record. Native
|
||||
// output stays unavailable until that child's calibration has been supplied.
|
||||
void probe_controller_input_set_full_stick_calibration(uint8_t instance, const uint8_t calibration[9]);
|
||||
// Native single-actuator gameplay envelope for this virtual half. Backend owns
|
||||
// binding/generation checks, profile gain, finite playback and stale-stream stop.
|
||||
bool probe_controller_input_submit_rumble(uint8_t instance, const uint8_t* magnitudes,
|
||||
uint8_t count);
|
||||
void probe_controller_input_cancel_rumble(uint8_t instance);
|
||||
#endif
|
||||
// Core0 native07/08 output. Disable discards queued/prepared data; repeated
|
||||
// enable preserves it. Joy-Con mode relays its bounded FIFO; right-only Wii
|
||||
|
|
@ -66,7 +71,7 @@ uint32_t probe_controller_input_peek_native_report(uint8_t instance, uint32_t no
|
|||
// Remove only the exact current head once. A stale/replaced token cannot pop a
|
||||
// new stream's packet. Before flash-ready startup peek/commit return 0/false.
|
||||
bool probe_controller_input_commit_native_report(uint8_t instance, uint32_t serial);
|
||||
// Built-in vibration samples only; raw HD-rumble output is not forwarded.
|
||||
// Built-in vibration samples; separate from full-controller gameplay envelopes.
|
||||
// A nonzero token means queued, not completed. Result:0 pending,1 completion,
|
||||
// -1 failed/stale. Joy-Con completion is its application ACK; Wii/DualSense
|
||||
// completion is actual bounded rumble-driver dispatch, not a source application
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ typedef struct {
|
|||
bool native_stream_ready;
|
||||
uint32_t last_hid_complete_ms;
|
||||
bool hid_completion_seen;
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
bool gameplay_rumble_seen;
|
||||
#endif
|
||||
uint32_t mouse_delivered_reports, mouse_logged_reports;
|
||||
int64_t mouse_delivered_x, mouse_delivered_y;
|
||||
#ifdef SWITCH2_PROBE_TRACE_NATIVE_INPUT
|
||||
|
|
@ -298,6 +301,21 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id,
|
|||
hid_report_type_t report_type, const uint8_t* buffer,
|
||||
uint16_t length) {
|
||||
++hid_packets;
|
||||
#if defined(SWITCH2_PROBE_USB_INIT) && SWITCH2_BRIDGE_FULL_INPUT
|
||||
if (instance < PROBE_CONTROLLER_COUNT && report_type == HID_REPORT_TYPE_OUTPUT &&
|
||||
controllers[instance].protocol.initialized &&
|
||||
probe_transport_mounted(instance) && !probe_transport_suspended(instance)) {
|
||||
probe_rumble_frame frame;
|
||||
if (probe_protocol_decode_rumble(report_id, buffer, length, &frame)) {
|
||||
// Count-zero HOLD leaves both motor state and watchdog untouched.
|
||||
// Valid gameplay traffic must not fill the slow UART log ring.
|
||||
if (frame.count && probe_controller_input_submit_rumble(
|
||||
instance, frame.magnitude, frame.count))
|
||||
controllers[instance].gameplay_rumble_seen = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
probe_debug_printf("[PROBE] HID_REPORT_TYPE=%u\n", report_type);
|
||||
log_packet("HID_OUT", instance, report_id, buffer, length);
|
||||
}
|
||||
|
|
@ -329,6 +347,9 @@ static void reset_controller_protocol(uint8_t instance) {
|
|||
probe_usb_controller* controller = &controllers[instance];
|
||||
#ifdef SWITCH_PICO_SWITCH2_USB_BRIDGE
|
||||
probe_controller_input_cancel_sample(instance);
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
probe_controller_input_cancel_rumble(instance);
|
||||
#endif
|
||||
probe_controller_input_set_native_stream(instance, false);
|
||||
#endif
|
||||
memset(controller, 0, sizeof(*controller));
|
||||
|
|
@ -483,8 +504,16 @@ static void controller_input_task(probe_usb_controller* controller, uint32_t now
|
|||
probe_protocol_state* protocol = &controller->protocol;
|
||||
probe_controller_input source = {0};
|
||||
probe_controller_input_poll(instance, now, &source);
|
||||
const bool output_active = source.active && probe_transport_mounted(instance) &&
|
||||
!probe_transport_suspended(instance);
|
||||
const bool usb_active = probe_transport_mounted(instance) &&
|
||||
!probe_transport_suspended(instance);
|
||||
const bool output_active = source.active && usb_active;
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
if (!usb_active) probe_controller_input_cancel_sample(instance);
|
||||
if (controller->gameplay_rumble_seen && (!output_active || !protocol->initialized)) {
|
||||
probe_controller_input_cancel_rumble(instance);
|
||||
controller->gameplay_rumble_seen = false;
|
||||
}
|
||||
#endif
|
||||
if (protocol->controller_active != output_active)
|
||||
probe_debug_printf("[PROBE] Controller input itf=%u %s\n",
|
||||
instance, output_active ? "active" : "neutral (disconnected/stale)");
|
||||
|
|
@ -783,6 +812,11 @@ void tud_suspend_cb(bool remote_wakeup_en) {
|
|||
controller->protocol.controller_active = false;
|
||||
#ifdef SWITCH_PICO_SWITCH2_USB_BRIDGE
|
||||
probe_controller_input_set_native_stream(instance, false);
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
probe_controller_input_cancel_sample(instance);
|
||||
probe_controller_input_cancel_rumble(instance);
|
||||
controller->gameplay_rumble_seen = false;
|
||||
#endif
|
||||
controller->native_stream_ready = false;
|
||||
controller->hid_completion_seen = false;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -417,27 +417,27 @@ function(switch2_usb_probe_configure target)
|
|||
endif()
|
||||
if(SWITCH2_PROBE_NEUTRAL_INPUT)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.97-neutral-hub-${SWITCH2_PROBE_PAIR_COUNT}pair-trace")
|
||||
pico_set_program_version(${target} "0.102-neutral-hub-${SWITCH2_PROBE_PAIR_COUNT}pair-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.97-neutral-hub-${SWITCH2_PROBE_PAIR_COUNT}pair")
|
||||
pico_set_program_version(${target} "0.102-neutral-hub-${SWITCH2_PROBE_PAIR_COUNT}pair")
|
||||
endif()
|
||||
elseif(SWITCH2_PROBE_HUB AND SWITCH2_BRIDGE_FULL_INPUT)
|
||||
if(probe_controller_count GREATER 2)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.97-live-two-pair-trace")
|
||||
pico_set_program_version(${target} "0.102-live-two-pair-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.97-live-two-pair")
|
||||
pico_set_program_version(${target} "0.102-live-two-pair")
|
||||
endif()
|
||||
elseif(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.97-native-digital-stick-trace")
|
||||
pico_set_program_version(${target} "0.102-native-digital-stick-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.97-native-digital-stick")
|
||||
pico_set_program_version(${target} "0.102-native-digital-stick")
|
||||
endif()
|
||||
elseif(SWITCH2_PROBE_HUB)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.97-native-hub-profiles-trace")
|
||||
pico_set_program_version(${target} "0.102-native-hub-profiles-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.97-native-hub-profiles")
|
||||
pico_set_program_version(${target} "0.102-native-hub-profiles")
|
||||
endif()
|
||||
elseif(SWITCH2_PROBE_JOIN_CHORD_GATE)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
|
|
|
|||
|
|
@ -407,3 +407,34 @@ void probe_protocol_gate_native_report(const probe_protocol_state* state,
|
|||
memset(input + imu_length_offset, 0, 41);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool probe_protocol_decode_rumble(uint8_t report_id, const uint8_t* data,
|
||||
size_t length, probe_rumble_frame* output) {
|
||||
if (!data || !output) return false;
|
||||
if (report_id == 0) {
|
||||
if (length < 17 || length > 64 || data[0] != 0x01) return false;
|
||||
++data;
|
||||
} else if (report_id != 0x01 || length < 16 || length > 63) {
|
||||
return false;
|
||||
}
|
||||
if ((data[0] & 0xc0u) != 0x40u) return false;
|
||||
|
||||
// Wire block: ndeadly/switch2_controller_research hid_reports.md#output-report-0x01.
|
||||
// SDL src/joystick/hidapi/SDL_hidapi_switch2.c (EncodeHDRumble / UpdateRumble)
|
||||
// packs frequency/amplitude/frequency/amplitude as four 10-bit LE fields.
|
||||
// Header: format 01 [7:6], sample count [5:4], sequence [3:0].
|
||||
// Sequence is informational; unused sample bytes and USB padding may be stale.
|
||||
probe_rumble_frame decoded = {.count = (data[0] >> 4) & 3u};
|
||||
for (unsigned i = 0; i < decoded.count; ++i) {
|
||||
const uint8_t* sample = data + 1u + 5u * i;
|
||||
const unsigned first = (sample[1] >> 2) | ((sample[2] & 0x0fu) << 6);
|
||||
const unsigned second = (sample[3] >> 6) | ((unsigned)sample[4] << 2);
|
||||
const unsigned amplitude = first > second ? first : second;
|
||||
// ERM compatibility, not HD waveform reproduction: ignore frequencies
|
||||
// and round max(amplitudes) across the full 10-bit range to 0..255.
|
||||
// SDL's conservative outbound clamp is not an inbound validity limit.
|
||||
decoded.magnitude[i] = (uint8_t)((amplitude * 255u + 511u) / 1023u);
|
||||
}
|
||||
*output = decoded;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,18 @@ typedef struct {
|
|||
uint32_t report_counter;
|
||||
} probe_protocol_state;
|
||||
|
||||
typedef struct {
|
||||
uint8_t count;
|
||||
uint8_t magnitude[3];
|
||||
} probe_rumble_frame;
|
||||
|
||||
// Stateless Output 01 compatibility rumble: report_id 0 includes the leading
|
||||
// wire ID (17..64 bytes); report_id 1 omits it (16..63 bytes).
|
||||
// Count zero means HOLD/no update, not stop or watchdog refresh. A nonempty
|
||||
// zero-amplitude sample is stop. Malformed input leaves output unchanged.
|
||||
bool probe_protocol_decode_rumble(uint8_t report_id, const uint8_t* data,
|
||||
size_t length, probe_rumble_frame* output);
|
||||
|
||||
void probe_protocol_reset(probe_protocol_state* state, bool is_left);
|
||||
// Blob: own address[6], count[1], zero-padded host addresses[42][6], AES key[16].
|
||||
// Rejects other identities, invalid counts/padding/lengths without mutation.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue