feat: split up usb transfer logic in preparation for controller mode
This commit is contained in:
parent
4e1af50ebd
commit
753cb59fe6
2 changed files with 256 additions and 174 deletions
|
@ -16,7 +16,10 @@ use packed_struct::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
gcc_hid::{Buttons1, Buttons2, MUTEX_INPUT_CONSISTENCY_MODE, SIGNAL_CHANGE_RUMBLE_STRENGTH},
|
||||
gcc_hid::{
|
||||
GcButtons1, GcButtons2, MUTEX_CONTROLLER_MODE, MUTEX_INPUT_CONSISTENCY_MODE,
|
||||
SIGNAL_CHANGE_RUMBLE_STRENGTH,
|
||||
},
|
||||
helpers::{PackedFloat, ToPackedFloatArray, ToRegularArray, XyValuePair},
|
||||
input::{
|
||||
read_ext_adc, Stick, StickAxis, FLOAT_ORIGIN, SPI_ACS_SHARED, SPI_CCS_SHARED, SPI_SHARED,
|
||||
|
@ -500,7 +503,7 @@ enum NotchAdjustmentType {
|
|||
|
||||
/// This needs to be incremented for ANY change to ControllerConfig
|
||||
/// else we risk loading uninitialized memory.
|
||||
pub const CONTROLLER_CONFIG_REVISION: u8 = 1;
|
||||
pub const CONTROLLER_CONFIG_REVISION: u8 = 2;
|
||||
|
||||
#[derive(Debug, Clone, Format, PackedStruct)]
|
||||
#[packed_struct(endian = "msb")]
|
||||
|
@ -563,6 +566,14 @@ pub enum InputConsistencyMode {
|
|||
PC = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Format, PrimitiveEnum_u8, PartialEq, Eq)]
|
||||
pub enum ControllerMode {
|
||||
/// Advertise itself as a GCC adapter with 1 controller (itself) connected.
|
||||
GcAdapter = 0,
|
||||
/// Pretend to be a Nintendo Switch Pro Controller connected via USB.
|
||||
Procon = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Format, PackedStruct)]
|
||||
#[packed_struct(endian = "msb")]
|
||||
pub struct ControllerConfig {
|
||||
|
@ -580,6 +591,8 @@ pub struct ControllerConfig {
|
|||
pub astick_config: StickConfig,
|
||||
#[packed_field(size_bytes = "328")]
|
||||
pub cstick_config: StickConfig,
|
||||
#[packed_field(size_bits = "8", ty = "enum")]
|
||||
pub controller_mode: ControllerMode,
|
||||
}
|
||||
|
||||
impl Default for ControllerConfig {
|
||||
|
@ -590,6 +603,7 @@ impl Default for ControllerConfig {
|
|||
astick_config: StickConfig::default(),
|
||||
rumble_strength: 9,
|
||||
cstick_config: StickConfig::default(),
|
||||
controller_mode: ControllerMode::GcAdapter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1197,12 +1211,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1225,12 +1239,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1254,12 +1268,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1313,12 +1327,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1399,12 +1413,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1485,12 +1499,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1560,12 +1574,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1616,12 +1630,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1664,13 +1678,13 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
button_z: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1700,12 +1714,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1734,12 +1748,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1760,12 +1774,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1786,12 +1800,12 @@ async fn configuration_main_loop<
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_r: true,
|
||||
button_l: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
@ -1840,6 +1854,11 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) {
|
|||
*m_input_consistency = Some(current_config.input_consistency_mode);
|
||||
}
|
||||
|
||||
{
|
||||
let mut m_is_procon = MUTEX_CONTROLLER_MODE.lock().await;
|
||||
*m_is_procon = Some(current_config.controller_mode);
|
||||
}
|
||||
|
||||
SIGNAL_CHANGE_RUMBLE_STRENGTH.signal(current_config.rumble_strength);
|
||||
SIGNAL_CONFIG_CHANGE.signal(current_config.clone());
|
||||
|
||||
|
@ -1857,12 +1876,12 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) {
|
|||
report: GcReport {
|
||||
trigger_r: 255,
|
||||
trigger_l: 255,
|
||||
buttons_2: Buttons2 {
|
||||
buttons_2: GcButtons2 {
|
||||
button_l: true,
|
||||
button_r: true,
|
||||
..Default::default()
|
||||
},
|
||||
buttons_1: Buttons1 {
|
||||
buttons_1: GcButtons1 {
|
||||
button_x: true,
|
||||
button_y: true,
|
||||
button_a: true,
|
||||
|
|
351
src/gcc_hid.rs
351
src/gcc_hid.rs
|
@ -2,28 +2,32 @@
|
|||
* Communication with the console / PC over USB HID.
|
||||
* Includes the HID report descriptor, and the GcReport struct.
|
||||
*/
|
||||
use core::default::Default;
|
||||
use core::{default::Default, future::Future};
|
||||
|
||||
use defmt::{debug, info, trace, warn, Format};
|
||||
use embassy_futures::join::join;
|
||||
use embassy_rp::{
|
||||
peripherals::{PIN_25, PIN_29, PWM_CH4, PWM_CH6, USB},
|
||||
pwm::Pwm,
|
||||
usb::Driver,
|
||||
usb::Driver as EmbassyDriver,
|
||||
};
|
||||
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, signal::Signal};
|
||||
use embassy_time::{Duration, Instant, Timer};
|
||||
use embassy_usb::{
|
||||
class::hid::{HidReaderWriter, ReportId, RequestHandler, State},
|
||||
class::hid::{HidReader, HidReaderWriter, HidWriter, ReportId, RequestHandler, State},
|
||||
control::OutResponse,
|
||||
driver::Driver,
|
||||
msos::{self, windows_version},
|
||||
Builder, Handler,
|
||||
Builder, Handler, UsbDevice,
|
||||
};
|
||||
use libm::powf;
|
||||
use packed_struct::{derive::PackedStruct, PackedStruct};
|
||||
|
||||
use crate::{config::InputConsistencyMode, input::CHANNEL_GCC_STATE};
|
||||
use crate::{
|
||||
config::{ControllerMode, InputConsistencyMode},
|
||||
input::CHANNEL_GCC_STATE,
|
||||
};
|
||||
|
||||
static SIGNAL_RUMBLE: Signal<CriticalSectionRawMutex, bool> = Signal::new();
|
||||
|
||||
|
@ -37,6 +41,10 @@ pub static MUTEX_INPUT_CONSISTENCY_MODE: Mutex<
|
|||
Option<InputConsistencyMode>,
|
||||
> = Mutex::new(None);
|
||||
|
||||
/// Only dispatched ONCE after powerup, to determine how to advertise itself via USB.
|
||||
pub static MUTEX_CONTROLLER_MODE: Mutex<CriticalSectionRawMutex, Option<ControllerMode>> =
|
||||
Mutex::new(None);
|
||||
|
||||
/// Vendor-defined property data
|
||||
const DEVICE_INTERFACE_GUID: &str = "{ecceff35-146c-4ff3-acd9-8f992d09acdd}";
|
||||
|
||||
|
@ -97,9 +105,19 @@ pub const GCC_REPORT_DESCRIPTOR: &[u8] = &[
|
|||
0xC0, // End Collection
|
||||
];
|
||||
|
||||
trait HidReportable<const LEN: usize> {
|
||||
fn get_hid_report(&self) -> [u8; LEN];
|
||||
}
|
||||
|
||||
struct UsbConfig {
|
||||
vid: u16,
|
||||
pid: u16,
|
||||
report_descriptor: &'static [u8],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||
pub struct Buttons1 {
|
||||
pub struct GcButtons1 {
|
||||
#[packed_field(bits = "0")]
|
||||
pub button_a: bool,
|
||||
#[packed_field(bits = "1")]
|
||||
|
@ -120,7 +138,7 @@ pub struct Buttons1 {
|
|||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||
pub struct Buttons2 {
|
||||
pub struct GcButtons2 {
|
||||
#[packed_field(bits = "0")]
|
||||
pub button_start: bool,
|
||||
#[packed_field(bits = "1")]
|
||||
|
@ -137,9 +155,9 @@ pub struct Buttons2 {
|
|||
#[packed_struct(bit_numbering = "msb0", size_bytes = "8")]
|
||||
pub struct GcReport {
|
||||
#[packed_field(bits = "0..=7")]
|
||||
pub buttons_1: Buttons1,
|
||||
pub buttons_1: GcButtons1,
|
||||
#[packed_field(bits = "8..=15")]
|
||||
pub buttons_2: Buttons2,
|
||||
pub buttons_2: GcButtons2,
|
||||
#[packed_field(bits = "16..=23")]
|
||||
pub stick_x: u8,
|
||||
#[packed_field(bits = "24..=31")]
|
||||
|
@ -157,8 +175,8 @@ pub struct GcReport {
|
|||
impl Default for GcReport {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buttons_1: Buttons1::default(),
|
||||
buttons_2: Buttons2::default(),
|
||||
buttons_1: GcButtons1::default(),
|
||||
buttons_2: GcButtons2::default(),
|
||||
stick_x: 127,
|
||||
stick_y: 127,
|
||||
cstick_x: 127,
|
||||
|
@ -169,15 +187,29 @@ impl Default for GcReport {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[repr(C, align(8))]
|
||||
pub struct RawConsoleReport {
|
||||
pub packet: [u8; 64],
|
||||
}
|
||||
impl HidReportable<37> for GcReport {
|
||||
fn get_hid_report(&self) -> [u8; 37] {
|
||||
static mut GC_FIRST: bool = false;
|
||||
|
||||
impl Default for RawConsoleReport {
|
||||
fn default() -> Self {
|
||||
Self { packet: [0u8; 64] }
|
||||
let mut buffer = [0u8; 37];
|
||||
|
||||
buffer[0] = 0x21;
|
||||
buffer[1] |= 0x14;
|
||||
|
||||
let data = self.pack().expect("Failed to pack GC input data");
|
||||
|
||||
if unsafe { !GC_FIRST } {
|
||||
buffer[1] |= 0x04;
|
||||
buffer[10] |= 0x04;
|
||||
buffer[19] |= 0x04;
|
||||
buffer[28] |= 0x04;
|
||||
unsafe { GC_FIRST = true };
|
||||
} else {
|
||||
// controller in "port 1"
|
||||
buffer[2..=9].copy_from_slice(&data[0..=7]);
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,30 +236,6 @@ impl RequestHandler for GccRequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_gcinput_hid_report(input_state: &GcReport) -> [u8; 37] {
|
||||
static mut GC_FIRST: bool = false;
|
||||
|
||||
let mut buffer = [0u8; 37];
|
||||
|
||||
buffer[0] = 0x21;
|
||||
buffer[1] |= 0x14;
|
||||
|
||||
let data = input_state.pack().expect("Failed to pack GC input data");
|
||||
|
||||
if unsafe { !GC_FIRST } {
|
||||
buffer[1] |= 0x04;
|
||||
buffer[10] |= 0x04;
|
||||
buffer[19] |= 0x04;
|
||||
buffer[28] |= 0x04;
|
||||
unsafe { GC_FIRST = true };
|
||||
} else {
|
||||
// controller in "port 1"
|
||||
buffer[2..=9].copy_from_slice(&data[0..=7]);
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
struct MyDeviceHandler {
|
||||
configured: bool,
|
||||
}
|
||||
|
@ -270,8 +278,154 @@ impl Handler for MyDeviceHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn mk_hid_reader_writer<'d, D: Driver<'d>, const R: usize, const W: usize>(
|
||||
input_consistency_mode: InputConsistencyMode,
|
||||
report_descriptor: &'d [u8],
|
||||
request_handler: &'d GccRequestHandler,
|
||||
mut builder: Builder<'d, D>,
|
||||
state: &'d mut State<'d>,
|
||||
) -> (UsbDevice<'d, D>, HidReader<'d, D, R>, HidWriter<'d, D, W>) {
|
||||
let hid_config = embassy_usb::class::hid::Config {
|
||||
report_descriptor,
|
||||
request_handler: Some(request_handler),
|
||||
poll_ms: match input_consistency_mode {
|
||||
InputConsistencyMode::Original => 8,
|
||||
InputConsistencyMode::ConsistencyHack
|
||||
| InputConsistencyMode::SuperHack
|
||||
| InputConsistencyMode::PC => 1,
|
||||
},
|
||||
max_packet_size_in: W as u16,
|
||||
max_packet_size_out: R as u16,
|
||||
};
|
||||
|
||||
let hid: HidReaderWriter<'d, D, R, W> =
|
||||
HidReaderWriter::<'_, D, R, W>::new(&mut builder, state, hid_config);
|
||||
|
||||
let usb = builder.build();
|
||||
|
||||
let (reader, writer) = hid.split();
|
||||
|
||||
(usb, reader, writer)
|
||||
}
|
||||
|
||||
fn mk_usb_transfer_futures<'d, D, F, H, const R: usize, const W: usize>(
|
||||
input_consistency_mode: InputConsistencyMode,
|
||||
usb_config: UsbConfig,
|
||||
request_handler: &'d GccRequestHandler,
|
||||
builder: Builder<'d, D>,
|
||||
state: &'d mut State<'d>,
|
||||
preprocess_report: F,
|
||||
) -> (
|
||||
impl Future<Output = ()> + 'd,
|
||||
impl Future<Output = ()> + 'd,
|
||||
impl Future<Output = ()> + 'd,
|
||||
)
|
||||
where
|
||||
D: Driver<'d> + 'd,
|
||||
F: Fn(GcReport) -> H + 'd,
|
||||
H: HidReportable<W>,
|
||||
{
|
||||
let (mut usb, mut reader, mut writer) = mk_hid_reader_writer::<_, R, W>(
|
||||
input_consistency_mode,
|
||||
usb_config.report_descriptor,
|
||||
request_handler,
|
||||
builder,
|
||||
state,
|
||||
);
|
||||
|
||||
let usb_fut = async move {
|
||||
loop {
|
||||
usb.run_until_suspend().await;
|
||||
debug!("Suspended");
|
||||
usb.wait_resume().await;
|
||||
debug!("RESUMED!");
|
||||
}
|
||||
};
|
||||
|
||||
let in_fut = async move {
|
||||
let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap();
|
||||
|
||||
let mut last_report_time = Instant::now();
|
||||
let mut rate_limit_end_time = Instant::now();
|
||||
|
||||
loop {
|
||||
// This is what we like to call a "hack".
|
||||
// It forces reports to be sent at least every 8.33ms instead of every 8ms.
|
||||
// 8.33ms is a multiple of the game's frame interval (16.66ms), so if we
|
||||
// send a report every 8.33ms, it should (in theory) ensure (close to)
|
||||
// 100% input accuracy.
|
||||
//
|
||||
// From the console's perspective, we are basically a laggy adapter, taking
|
||||
// a minimum of 333 extra us to send a report every time it's polled, but it
|
||||
// works to our advantage.
|
||||
match input_consistency_mode {
|
||||
InputConsistencyMode::SuperHack | InputConsistencyMode::ConsistencyHack => {
|
||||
// "Ticker at home", so we can use this for both consistency and SuperHack mode
|
||||
Timer::at(rate_limit_end_time).await;
|
||||
}
|
||||
InputConsistencyMode::Original | InputConsistencyMode::PC => {}
|
||||
}
|
||||
|
||||
writer.ready().await;
|
||||
|
||||
let state = preprocess_report(gcc_subscriber.next_message_pure().await);
|
||||
|
||||
let report = state.get_hid_report();
|
||||
|
||||
trace!("Writing report: {:08b}", report);
|
||||
|
||||
match writer.write(&report).await {
|
||||
Ok(()) => {
|
||||
let currtime = Instant::now();
|
||||
let polltime = currtime.duration_since(last_report_time);
|
||||
let micros = polltime.as_micros();
|
||||
debug!("Report written in {}us", micros);
|
||||
if input_consistency_mode != InputConsistencyMode::Original
|
||||
&& input_consistency_mode != InputConsistencyMode::PC
|
||||
{
|
||||
while rate_limit_end_time < currtime {
|
||||
rate_limit_end_time += Duration::from_micros(8333);
|
||||
}
|
||||
}
|
||||
last_report_time = currtime;
|
||||
}
|
||||
Err(e) => warn!("Failed to send report: {:?}", e),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let out_fut = async move {
|
||||
loop {
|
||||
trace!("Readery loop");
|
||||
let mut buf = [0u8; R];
|
||||
match reader.read(&mut buf).await {
|
||||
Ok(_e) => {
|
||||
debug!("READ SOMETHIN: {:08b}", buf);
|
||||
SIGNAL_RUMBLE.signal((buf[1] & 0x01) != 0);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to read: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let usb_fut_wrapped = async {
|
||||
usb_fut.await;
|
||||
debug!("USB FUT DED");
|
||||
};
|
||||
|
||||
(usb_fut_wrapped, in_fut, out_fut)
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>) {
|
||||
pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'static, USB>) {
|
||||
let config = UsbConfig {
|
||||
vid: 0x057e,
|
||||
pid: 0x0337,
|
||||
report_descriptor: GCC_REPORT_DESCRIPTOR,
|
||||
};
|
||||
|
||||
let input_consistency_mode = {
|
||||
while MUTEX_INPUT_CONSISTENCY_MODE.lock().await.is_none() {
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
|
@ -300,7 +454,7 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
|
|||
info!("Detected flash with unique serial number {}", serial);
|
||||
|
||||
trace!("Start of config");
|
||||
let mut usb_config = embassy_usb::Config::new(0x057e, 0x0337);
|
||||
let mut usb_config = embassy_usb::Config::new(config.vid, config.pid);
|
||||
usb_config.manufacturer = Some("Naxdy");
|
||||
usb_config.product = Some(match input_consistency_mode {
|
||||
InputConsistencyMode::Original => "NaxGCC (OG Mode)",
|
||||
|
@ -349,105 +503,14 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
|
|||
|
||||
builder.handler(&mut device_handler);
|
||||
|
||||
let hid_config = embassy_usb::class::hid::Config {
|
||||
report_descriptor: GCC_REPORT_DESCRIPTOR,
|
||||
request_handler: Some(&request_handler),
|
||||
poll_ms: match input_consistency_mode {
|
||||
InputConsistencyMode::Original => 8,
|
||||
InputConsistencyMode::ConsistencyHack
|
||||
| InputConsistencyMode::SuperHack
|
||||
| InputConsistencyMode::PC => 1,
|
||||
},
|
||||
max_packet_size_in: 37,
|
||||
max_packet_size_out: 5,
|
||||
};
|
||||
let hid = HidReaderWriter::<_, 5, 37>::new(&mut builder, &mut state, hid_config);
|
||||
|
||||
let mut usb = builder.build();
|
||||
|
||||
let usb_fut = async {
|
||||
loop {
|
||||
usb.run_until_suspend().await;
|
||||
debug!("Suspended");
|
||||
usb.wait_resume().await;
|
||||
debug!("RESUMED!");
|
||||
}
|
||||
};
|
||||
|
||||
let (mut reader, mut writer) = hid.split();
|
||||
|
||||
let in_fut = async {
|
||||
let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap();
|
||||
|
||||
let mut last_report_time = Instant::now();
|
||||
let mut rate_limit_end_time = Instant::now();
|
||||
|
||||
loop {
|
||||
// This is what we like to call a "hack".
|
||||
// It forces reports to be sent at least every 8.33ms instead of every 8ms.
|
||||
// 8.33ms is a multiple of the game's frame interval (16.66ms), so if we
|
||||
// send a report every 8.33ms, it should (in theory) ensure (close to)
|
||||
// 100% input accuracy.
|
||||
//
|
||||
// From the console's perspective, we are basically a laggy adapter, taking
|
||||
// a minimum of 333 extra us to send a report every time it's polled, but it
|
||||
// works to our advantage.
|
||||
match input_consistency_mode {
|
||||
InputConsistencyMode::SuperHack | InputConsistencyMode::ConsistencyHack => {
|
||||
// "Ticker at home", so we can use this for both consistency and SuperHack mode
|
||||
Timer::at(rate_limit_end_time).await;
|
||||
}
|
||||
InputConsistencyMode::Original | InputConsistencyMode::PC => {}
|
||||
}
|
||||
|
||||
match writer
|
||||
.write(&{
|
||||
let state = gcc_subscriber.next_message_pure().await;
|
||||
let report = get_gcinput_hid_report(&state);
|
||||
trace!("Report Written: {:08b}", report);
|
||||
report
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
let currtime = Instant::now();
|
||||
let polltime = currtime.duration_since(last_report_time);
|
||||
let micros = polltime.as_micros();
|
||||
debug!("Report written in {}us", micros);
|
||||
if input_consistency_mode != InputConsistencyMode::Original
|
||||
&& input_consistency_mode != InputConsistencyMode::PC
|
||||
{
|
||||
while rate_limit_end_time < currtime {
|
||||
rate_limit_end_time += Duration::from_micros(8333);
|
||||
}
|
||||
}
|
||||
last_report_time = currtime;
|
||||
}
|
||||
Err(e) => warn!("Failed to send report: {:?}", e),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let out_fut = async {
|
||||
loop {
|
||||
trace!("Readery loop");
|
||||
let mut buf = [0u8; 5];
|
||||
match reader.read(&mut buf).await {
|
||||
Ok(_e) => {
|
||||
debug!("READ SOMETHIN: {:08b}", buf);
|
||||
SIGNAL_RUMBLE.signal((buf[1] & 0x01) != 0);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to read: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let usb_fut_wrapped = async {
|
||||
usb_fut.await;
|
||||
debug!("USB FUT DED");
|
||||
};
|
||||
let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 5, 37>(
|
||||
input_consistency_mode,
|
||||
config,
|
||||
&request_handler,
|
||||
builder,
|
||||
&mut state,
|
||||
|state| state,
|
||||
);
|
||||
|
||||
join(usb_fut_wrapped, join(in_fut, out_fut)).await;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue