From 9455772a45fae215e540fbd91502c28651ffa317 Mon Sep 17 00:00:00 2001 From: Naxdy Date: Sun, 27 Oct 2024 21:47:29 +0100 Subject: [PATCH 1/5] chore: improve flake & ci --- .forgejo/workflows/clippy-check.yml | 4 +-- .forgejo/workflows/nightly-release.yml | 6 ++-- flake.nix | 44 ++++++++++++++------------ 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/.forgejo/workflows/clippy-check.yml b/.forgejo/workflows/clippy-check.yml index d04d941..d755c76 100644 --- a/.forgejo/workflows/clippy-check.yml +++ b/.forgejo/workflows/clippy-check.yml @@ -10,8 +10,6 @@ jobs: check: runs-on: nix-flakes - - steps: - uses: actions/checkout@v4 @@ -25,4 +23,4 @@ jobs: - name: Run Clippy run: | - nix build .#clippy --print-build-logs -j auto + nix flake check . --print-build-logs -j auto diff --git a/.forgejo/workflows/nightly-release.yml b/.forgejo/workflows/nightly-release.yml index b8949e9..4172b4f 100644 --- a/.forgejo/workflows/nightly-release.yml +++ b/.forgejo/workflows/nightly-release.yml @@ -21,9 +21,9 @@ jobs: cache: "${{ vars.PUBLIC_BINARY_CACHE_NAME }}" - uses: actions/checkout@v4 - - name: Run Clippy + - name: Run flake checks run: | - nix build .#clippy --print-build-logs -j auto + nix flake check . --print-build-logs -j auto - name: Build firmware image run: | @@ -38,7 +38,7 @@ jobs: git tag nightly -m "Nightly Release" git checkout nightly git push --set-upstream origin nightly --force - + - name: Publish nightly release uses: https://gitea.com/actions/gitea-release-action@v1.3.0 with: diff --git a/flake.nix b/flake.nix index d0cf0ad..5f2f211 100644 --- a/flake.nix +++ b/flake.nix @@ -47,33 +47,37 @@ CARGO_BUILD_TARGET = "thumbv6m-none-eabi"; in { - packages.default = self.packages.${system}.naxgcc-fw-uf2; + packages = { + default = self.packages.${system}.naxgcc-fw-uf2; - packages.naxgcc-fw-uf2 = pkgs.runCommandLocal "${self.packages.${system}.naxgcc-fw.pname}-uf2-${self.packages.${system}.naxgcc-fw.version}" { } '' - mkdir -p $out/bin - ${pkgs.elf2uf2-rs}/bin/elf2uf2-rs ${self.packages.${system}.naxgcc-fw}/bin/${self.packages.${system}.naxgcc-fw.pname} $out/bin/${self.packages.${system}.naxgcc-fw.pname}.uf2 - ''; + naxgcc-fw-uf2 = pkgs.runCommandLocal "${self.packages.${system}.naxgcc-fw.pname}-uf2-${self.packages.${system}.naxgcc-fw.version}" { } '' + mkdir -p $out/bin + ${pkgs.elf2uf2-rs}/bin/elf2uf2-rs ${self.packages.${system}.naxgcc-fw}/bin/${self.packages.${system}.naxgcc-fw.pname} $out/bin/${self.packages.${system}.naxgcc-fw.pname}.uf2 + ''; - packages.naxgcc-fw = pkgs.callPackage - ({ mode ? "build" }: naersk_lib.buildPackage { - pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name; - version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version; + naxgcc-fw = pkgs.callPackage + ({ mode ? "build" }: naersk_lib.buildPackage { + pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name; + version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version; - inherit mode; + inherit mode; - src = self; + src = self; - cargoBuildOptions = _orig: _orig ++ [ - "--target=${CARGO_BUILD_TARGET}" - ]; + cargoBuildOptions = _orig: _orig ++ [ + "--target=${CARGO_BUILD_TARGET}" + ]; - # if a tree falls in the forest and no one is around to hear it, does it make a sound? - DEFMT_LOG = "off"; - }) - { }; + # if a tree falls in the forest and no one is around to hear it, does it make a sound? + DEFMT_LOG = "off"; + }) + { }; + }; - packages.clippy = self.packages.${system}.naxgcc-fw.override { - mode = "clippy"; + checks = { + clippy = self.packages.${system}.naxgcc-fw.override { + mode = "clippy"; + }; }; devShells.default = pkgs.mkShell { -- 2.51.2 From 6475e844a9deb6b3a721231e8617511251a2779f Mon Sep 17 00:00:00 2001 From: Naxdy Date: Sun, 27 Oct 2024 20:49:20 +0000 Subject: [PATCH 2/5] chore: improve flake & ci (#28) Reviewed-on: https://git.naxdy.org/NaxdyOrg/NaxGCC-FW/pulls/28 --- .forgejo/workflows/clippy-check.yml | 4 +-- .forgejo/workflows/nightly-release.yml | 6 ++-- flake.nix | 44 ++++++++++++++------------ 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/.forgejo/workflows/clippy-check.yml b/.forgejo/workflows/clippy-check.yml index d04d941..d755c76 100644 --- a/.forgejo/workflows/clippy-check.yml +++ b/.forgejo/workflows/clippy-check.yml @@ -10,8 +10,6 @@ jobs: check: runs-on: nix-flakes - - steps: - uses: actions/checkout@v4 @@ -25,4 +23,4 @@ jobs: - name: Run Clippy run: | - nix build .#clippy --print-build-logs -j auto + nix flake check . --print-build-logs -j auto diff --git a/.forgejo/workflows/nightly-release.yml b/.forgejo/workflows/nightly-release.yml index b8949e9..4172b4f 100644 --- a/.forgejo/workflows/nightly-release.yml +++ b/.forgejo/workflows/nightly-release.yml @@ -21,9 +21,9 @@ jobs: cache: "${{ vars.PUBLIC_BINARY_CACHE_NAME }}" - uses: actions/checkout@v4 - - name: Run Clippy + - name: Run flake checks run: | - nix build .#clippy --print-build-logs -j auto + nix flake check . --print-build-logs -j auto - name: Build firmware image run: | @@ -38,7 +38,7 @@ jobs: git tag nightly -m "Nightly Release" git checkout nightly git push --set-upstream origin nightly --force - + - name: Publish nightly release uses: https://gitea.com/actions/gitea-release-action@v1.3.0 with: diff --git a/flake.nix b/flake.nix index d0cf0ad..5f2f211 100644 --- a/flake.nix +++ b/flake.nix @@ -47,33 +47,37 @@ CARGO_BUILD_TARGET = "thumbv6m-none-eabi"; in { - packages.default = self.packages.${system}.naxgcc-fw-uf2; + packages = { + default = self.packages.${system}.naxgcc-fw-uf2; - packages.naxgcc-fw-uf2 = pkgs.runCommandLocal "${self.packages.${system}.naxgcc-fw.pname}-uf2-${self.packages.${system}.naxgcc-fw.version}" { } '' - mkdir -p $out/bin - ${pkgs.elf2uf2-rs}/bin/elf2uf2-rs ${self.packages.${system}.naxgcc-fw}/bin/${self.packages.${system}.naxgcc-fw.pname} $out/bin/${self.packages.${system}.naxgcc-fw.pname}.uf2 - ''; + naxgcc-fw-uf2 = pkgs.runCommandLocal "${self.packages.${system}.naxgcc-fw.pname}-uf2-${self.packages.${system}.naxgcc-fw.version}" { } '' + mkdir -p $out/bin + ${pkgs.elf2uf2-rs}/bin/elf2uf2-rs ${self.packages.${system}.naxgcc-fw}/bin/${self.packages.${system}.naxgcc-fw.pname} $out/bin/${self.packages.${system}.naxgcc-fw.pname}.uf2 + ''; - packages.naxgcc-fw = pkgs.callPackage - ({ mode ? "build" }: naersk_lib.buildPackage { - pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name; - version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version; + naxgcc-fw = pkgs.callPackage + ({ mode ? "build" }: naersk_lib.buildPackage { + pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name; + version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version; - inherit mode; + inherit mode; - src = self; + src = self; - cargoBuildOptions = _orig: _orig ++ [ - "--target=${CARGO_BUILD_TARGET}" - ]; + cargoBuildOptions = _orig: _orig ++ [ + "--target=${CARGO_BUILD_TARGET}" + ]; - # if a tree falls in the forest and no one is around to hear it, does it make a sound? - DEFMT_LOG = "off"; - }) - { }; + # if a tree falls in the forest and no one is around to hear it, does it make a sound? + DEFMT_LOG = "off"; + }) + { }; + }; - packages.clippy = self.packages.${system}.naxgcc-fw.override { - mode = "clippy"; + checks = { + clippy = self.packages.${system}.naxgcc-fw.override { + mode = "clippy"; + }; }; devShells.default = pkgs.mkShell { -- 2.51.2 From 9838c4ee64f7743569b8387a5edc956a4962e650 Mon Sep 17 00:00:00 2001 From: Naxdy Date: Tue, 29 Oct 2024 19:53:18 +0000 Subject: [PATCH 3/5] feat: implement XInput mode (#27) Reviewed-on: https://git.naxdy.org/NaxdyOrg/NaxGCC-FW/pulls/27 --- .changelogs/v1.3.0.md | 10 + Cargo.lock | 2 +- Cargo.toml | 2 +- src/config.rs | 2 + src/hid/mod.rs | 68 ++++++ src/hid/xinput.rs | 544 ++++++++++++++++++++++++++++++++++++++++++ src/input.rs | 2 + src/usb_comms.rs | 139 ++++++++--- 8 files changed, 735 insertions(+), 34 deletions(-) create mode 100644 .changelogs/v1.3.0.md create mode 100644 src/hid/xinput.rs diff --git a/.changelogs/v1.3.0.md b/.changelogs/v1.3.0.md new file mode 100644 index 0000000..c5c1030 --- /dev/null +++ b/.changelogs/v1.3.0.md @@ -0,0 +1,10 @@ +This release introduces XInput mode for the NaxGCC. This mode is mostly useful for playing games on PC, as it offers the best out-of-the-box compatiblity experience with most titles. Similar to Pro-Controller Mode, this is not a permanent configuration that you set, but a mode that is activated by pressing a button while plugging your controller in. + +To enter XInput Mode, press and hold the `X` button while plugging in your controller. While in XInput mode, your NaxGCC will _always_ poll at 1ms intervals, regardless of your chosen input consistency mode setting. Once you go back to GCC or Pro-Controller Mode, your desired input consistency setting will be restored. + +> [!NOTE] +> As of this version, rumble will _not_ work while in XInput mode. + +--- + +To update your firmware, plug in your controller to your computer while keeping the `A+X+Y` buttons held. Then drag & drop the `.uf2` file (found below, under Downloads) onto the storage device that appears. diff --git a/Cargo.lock b/Cargo.lock index 018213b..ed7103a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -866,7 +866,7 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "naxgcc-fw" -version = "1.2.0" +version = "1.3.0" dependencies = [ "cortex-m", "cortex-m-rt", diff --git a/Cargo.toml b/Cargo.toml index 491a026..6da6e75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "naxgcc-fw" -version = "1.2.0" +version = "1.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/config.rs b/src/config.rs index 1ab9b36..343e429 100644 --- a/src/config.rs +++ b/src/config.rs @@ -571,6 +571,8 @@ pub enum ControllerMode { GcAdapter = 0, /// Pretend to be a Nintendo Switch Pro Controller connected via USB. Procon = 1, + /// Act as an XInput device, and also advertise itself with 1000Hz polling capability. + XInput = 2, } #[derive(Debug, Clone, Format, PackedStruct)] diff --git a/src/hid/mod.rs b/src/hid/mod.rs index 1b356d2..b570b21 100644 --- a/src/hid/mod.rs +++ b/src/hid/mod.rs @@ -1,2 +1,70 @@ +use embassy_usb::{ + class::hid::{HidReader, HidReaderWriter, HidWriter, ReadError, RequestHandler}, + driver::{Driver, EndpointError}, +}; + pub mod gcc; pub mod procon; +pub mod xinput; + +/// Custom trait to unify the API between embassy's HID writer, and our XInput reader/writer (and any +/// custom writers we may create in the future) +pub trait HidReaderWriterSplit<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { + fn split( + self, + ) -> ( + impl UsbReader<'d, D, READ_N>, + impl UsbWriter<'d, D, WRITE_N>, + ); +} + +/// Custom trait to unify the API between embassy's HID writer, and our XInput reader (and any +/// custom writers we may create in the future) +pub trait UsbReader<'d, D: Driver<'d>, const READ_N: usize> { + async fn run(self, use_report_ids: bool, handler: &mut T) -> !; + + async fn read(&mut self, buf: &mut [u8]) -> Result; +} + +/// Custom trait to unify the API between embassy's HID writer, and our XInput writer (and any +/// custom writers we may create in the future) +pub trait UsbWriter<'d, D: Driver<'d>, const WRITE_N: usize> { + async fn ready(&mut self); + + async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError>; +} + +impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> + HidReaderWriterSplit<'d, D, READ_N, WRITE_N> for HidReaderWriter<'d, D, READ_N, WRITE_N> +{ + fn split( + self, + ) -> ( + impl UsbReader<'d, D, READ_N>, + impl UsbWriter<'d, D, WRITE_N>, + ) { + self.split() + } +} + +impl<'d, D: Driver<'d>, const READ_N: usize> UsbReader<'d, D, READ_N> for HidReader<'d, D, READ_N> { + async fn run(self, use_report_ids: bool, handler: &mut T) -> ! { + self.run(use_report_ids, handler).await + } + + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await + } +} + +impl<'d, D: Driver<'d>, const WRITE_N: usize> UsbWriter<'d, D, WRITE_N> + for HidWriter<'d, D, WRITE_N> +{ + async fn ready(&mut self) { + self.ready().await + } + + async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { + self.write(report).await + } +} diff --git a/src/hid/xinput.rs b/src/hid/xinput.rs new file mode 100644 index 0000000..ddd8c20 --- /dev/null +++ b/src/hid/xinput.rs @@ -0,0 +1,544 @@ +/// +/// # XInput Protocol Implementation +/// +/// The implementations for `XInputReader` and `XInputWriter` and the logic surrounding them is +/// mostly taken from embassy. +/// +/// Unfortunately, the embassy hid classes don't allow us to specify a custom interface protocol, +/// hence the little bit of code duplication. +/// +use core::{ + mem::MaybeUninit, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use defmt::{info, trace, warn, Format}; +use embassy_usb::{ + class::hid::{Config, ReadError, ReportId, RequestHandler}, + control::{InResponse, OutResponse, Recipient, Request, RequestType}, + driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}, + types::InterfaceNumber, + Builder, Handler, +}; +use packed_struct::{derive::PackedStruct, PackedStruct}; + +use crate::usb_comms::HidReportBuilder; + +use super::{gcc::GcState, HidReaderWriterSplit, UsbReader, UsbWriter}; + +/// lol +pub const XINPUT_REPORT_DESCRIPTOR: &[u8] = &[]; + +const HID_DESC_DESCTYPE_HID: u8 = 0x21; +const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22; + +const HID_REQ_SET_IDLE: u8 = 0x0a; +const HID_REQ_GET_IDLE: u8 = 0x02; +const HID_REQ_GET_REPORT: u8 = 0x01; +const HID_REQ_SET_REPORT: u8 = 0x09; +const HID_REQ_GET_PROTOCOL: u8 = 0x03; +const HID_REQ_SET_PROTOCOL: u8 = 0x0b; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)] +#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")] +pub struct XInputButtons1 { + #[packed_field(bits = "0")] + pub dpad_up: bool, + #[packed_field(bits = "1")] + pub dpad_down: bool, + #[packed_field(bits = "2")] + pub dpad_left: bool, + #[packed_field(bits = "3")] + pub dpad_right: bool, + #[packed_field(bits = "4")] + pub button_menu: bool, + #[packed_field(bits = "5")] + pub button_back: bool, + #[packed_field(bits = "6")] + pub button_stick_l: bool, + #[packed_field(bits = "7")] + pub button_stick_r: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)] +#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")] +pub struct XInputButtons2 { + #[packed_field(bits = "0")] + pub bumper_l: bool, + #[packed_field(bits = "1")] + pub bumper_r: bool, + #[packed_field(bits = "2")] + pub button_guide: bool, + #[packed_field(bits = "3")] + pub blank_1: bool, + #[packed_field(bits = "4")] + pub button_a: bool, + #[packed_field(bits = "5")] + pub button_b: bool, + #[packed_field(bits = "6")] + pub button_x: bool, + #[packed_field(bits = "7")] + pub button_y: bool, +} + +/// +/// HID report that is sent back to the host. +/// +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)] +#[packed_struct(bit_numbering = "msb0", endian = "lsb", size_bytes = "32")] +pub struct XInputReport { + #[packed_field(bits = "0..=7")] + pub report_id: u8, + #[packed_field(bits = "8..=15")] + pub report_size: u8, + #[packed_field(bits = "16..=23")] + pub buttons_1: XInputButtons1, + #[packed_field(bits = "24..=31")] + pub buttons_2: XInputButtons2, + #[packed_field(bits = "32..=39")] + pub analog_trigger_l: u8, + #[packed_field(bits = "40..=47")] + pub analog_trigger_r: u8, + #[packed_field(bits = "48..=63")] + pub stick_left_x: i16, + #[packed_field(bits = "64..=79")] + pub stick_left_y: i16, + #[packed_field(bits = "80..=95")] + pub stick_right_x: i16, + #[packed_field(bits = "96..=111")] + pub stick_right_y: i16, + #[packed_field(bits = "112..=255")] + pub reserved: [u8; 18], +} + +impl From<&GcState> for XInputReport { + fn from(value: &GcState) -> Self { + Self { + report_id: 0, + report_size: 20, + buttons_1: XInputButtons1 { + dpad_up: value.buttons_1.dpad_up, + dpad_down: value.buttons_1.dpad_down, + dpad_right: value.buttons_1.dpad_right, + dpad_left: value.buttons_1.dpad_left, + button_menu: value.buttons_2.button_start, + button_back: false, + button_stick_l: false, + button_stick_r: false, + }, + buttons_2: XInputButtons2 { + blank_1: false, + bumper_l: false, + bumper_r: value.buttons_2.button_z, + button_a: value.buttons_1.button_a, + button_b: value.buttons_1.button_b, + button_x: value.buttons_1.button_x, + button_y: value.buttons_1.button_y, + button_guide: false, + }, + analog_trigger_l: value.trigger_l, + analog_trigger_r: value.trigger_r, + stick_left_x: (value.stick_x as i16 - 127).clamp(-127, 127) * 257, + stick_left_y: (value.stick_y as i16 - 127).clamp(-127, 127) * 257, + stick_right_x: (value.cstick_x as i16 - 127).clamp(-127, 127) * 257, + stick_right_y: (value.cstick_y as i16 - 127).clamp(-127, 127) * 257, + reserved: [0u8; 18], + } + } +} + +/// +/// Takes in a GcState, converts it to an `XInputReport` and returns its packed version. +/// +pub struct XInputReportBuilder; + +impl HidReportBuilder<32> for XInputReportBuilder { + async fn get_hid_report(&mut self, state: &super::gcc::GcState) -> [u8; 32] { + XInputReport::from(state) + .pack() + .expect("Failed to pack XInput State") + } +} + +/// +/// Handles packets sent from the host. +/// +pub struct XInputRequestHandler; + +impl RequestHandler for XInputRequestHandler { + fn get_report( + &mut self, + id: embassy_usb::class::hid::ReportId, + buf: &mut [u8], + ) -> Option { + let _ = (id, buf); + None + } + + fn set_report( + &mut self, + id: embassy_usb::class::hid::ReportId, + data: &[u8], + ) -> embassy_usb::control::OutResponse { + let _ = (id, data); + info!("Set report for {:?}: {:x}", id, data); + embassy_usb::control::OutResponse::Accepted + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + let _ = id; + None + } + + fn set_idle_ms(&mut self, id: Option, duration_ms: u32) { + let _ = (id, duration_ms); + } +} + +/// Taken from embassy. +pub struct XInputWriter<'d, D: Driver<'d>, const N: usize> { + ep_in: D::EndpointIn, +} + +impl<'d, D: Driver<'d>, const N: usize> UsbWriter<'d, D, N> for XInputWriter<'d, D, N> { + /// Waits for the interrupt in endpoint to be enabled. + async fn ready(&mut self) { + self.ep_in.wait_enabled().await; + } + + /// Writes `report` to its interrupt endpoint. + async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { + assert!(report.len() <= N); + + let max_packet_size = usize::from(self.ep_in.info().max_packet_size); + let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0); + for chunk in report.chunks(max_packet_size) { + self.ep_in.write(chunk).await?; + } + + if zlp_needed { + self.ep_in.write(&[]).await?; + } + + Ok(()) + } +} + +/// Taken from embassy. +pub struct XInputReader<'d, D: Driver<'d>, const N: usize> { + ep_out: D::EndpointOut, + offset: &'d AtomicUsize, +} + +impl<'d, D: Driver<'d>, const N: usize> UsbReader<'d, D, N> for XInputReader<'d, D, N> { + async fn run(mut self, use_report_ids: bool, handler: &mut T) -> ! { + let offset = self.offset.load(Ordering::Acquire); + assert!(offset == 0); + let mut buf = [0; N]; + loop { + match self.read(&mut buf).await { + Ok(len) => { + let id = if use_report_ids { buf[0] } else { 0 }; + handler.set_report(ReportId::Out(id), &buf[..len]); + } + Err(ReadError::BufferOverflow) => warn!( + "Host ent output report larger than the configured maximum output report length ({})", + N + ), + Err(ReadError::Disabled) => self.ep_out.wait_enabled().await, + Err(ReadError::Sync(_)) => unreachable!(), + } + } + } + + async fn read(&mut self, buf: &mut [u8]) -> Result { + assert!(N != 0); + assert!(buf.len() >= N); + + // Read packets from the endpoint + let max_packet_size = usize::from(self.ep_out.info().max_packet_size); + let starting_offset = self.offset.load(Ordering::Acquire); + let mut total = starting_offset; + loop { + for chunk in buf[starting_offset..N].chunks_mut(max_packet_size) { + match self.ep_out.read(chunk).await { + Ok(size) => { + total += size; + if size < max_packet_size || total == N { + self.offset.store(0, Ordering::Release); + break; + } + self.offset.store(total, Ordering::Release); + } + Err(err) => { + self.offset.store(0, Ordering::Release); + return Err(err.into()); + } + } + } + + // Some hosts may send ZLPs even when not required by the HID spec, so we'll loop as long as total == 0. + if total > 0 { + break; + } + } + + if starting_offset > 0 { + Err(ReadError::Sync(starting_offset..total)) + } else { + Ok(total) + } + } +} + +/// Taken from embassy, with a few modifications to the descriptor. +pub struct XInputReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { + reader: XInputReader<'d, D, READ_N>, + writer: XInputWriter<'d, D, WRITE_N>, +} + +impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> + XInputReaderWriter<'d, D, READ_N, WRITE_N> +{ + pub fn new( + builder: &mut Builder<'d, D>, + state: &'d mut XInputState<'d>, + config: Config<'d>, + ) -> Self { + let mut func = builder.function(0xff, 0x5d, 0x01); + let mut iface = func.interface(); + let if_num = iface.interface_number(); + let mut alt = iface.alt_setting(0xff, 0x5d, 0x01, None); + + #[rustfmt::skip] + alt.descriptor(0x21, &[ + 0x10, 0x01, // bcdHID 1.10 + 0x01, // bCountryCode + 0x24, // bNumDescriptors + 0x81, // bDescriptorType[0] (Unknown 0x81) + 0x14, 0x03, // wDescriptorLength[0] 788 + 0x00, // bDescriptorType[1] (Unknown 0x00) + 0x03, 0x13, // wDescriptorLength[1] 4867 + 0x02, // bDescriptorType[2] (Unknown 0x02) + 0x00, 0x03, // wDescriptorLength[2] 768 + 0x00, // bDescriptorType[3] (Unknown 0x00) + ]); + + let ep_in = alt.endpoint_interrupt_in(config.max_packet_size_in, config.poll_ms); + let ep_out = alt.endpoint_interrupt_out(config.max_packet_size_out, config.poll_ms); + + drop(func); + + let control = state.control.write(XInputControl::new( + if_num, + config.report_descriptor, + config.request_handler, + &state.out_report_offset, + )); + builder.handler(control); + + Self { + reader: XInputReader { + ep_out, + offset: &state.out_report_offset, + }, + writer: XInputWriter { ep_in }, + } + } +} + +impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> + HidReaderWriterSplit<'d, D, READ_N, WRITE_N> for XInputReaderWriter<'d, D, READ_N, WRITE_N> +{ + fn split( + self, + ) -> ( + impl UsbReader<'d, D, READ_N>, + impl UsbWriter<'d, D, WRITE_N>, + ) { + (self.reader, self.writer) + } +} + +pub struct XInputState<'d> { + control: MaybeUninit>, + out_report_offset: AtomicUsize, +} + +impl<'d> Default for XInputState<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> XInputState<'d> { + pub const fn new() -> Self { + XInputState { + control: MaybeUninit::uninit(), + out_report_offset: AtomicUsize::new(0), + } + } +} + +/// Taken from embassy. +struct XInputControl<'d> { + if_num: InterfaceNumber, + report_descriptor: &'d [u8], + request_handler: Option<&'d mut dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, + hid_descriptor: [u8; 16], +} + +impl<'d> XInputControl<'d> { + fn new( + if_num: InterfaceNumber, + report_descriptor: &'d [u8], + request_handler: Option<&'d mut dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, + ) -> Self { + XInputControl { + if_num, + report_descriptor, + request_handler, + out_report_offset, + #[rustfmt::skip] + hid_descriptor: [ + 0x10, // bLength + 0x21, // bDescriptorType (HID) + 0x10, 0x01, // bcdHID 1.10 + 0x01, // bCountryCode + 0x24, // bNumDescriptors + 0x81, // bDescriptorType[0] (Unknown 0x81) + 0x14, 0x03, // wDescriptorLength[0] 788 + 0x00, // bDescriptorType[1] (Unknown 0x00) + 0x03, 0x13, // wDescriptorLength[1] 4867 + 0x02, // bDescriptorType[2] (Unknown 0x02) + 0x00, 0x03, // wDescriptorLength[2] 768 + 0x00, // bDescriptorType[3] (Unknown 0x00) + ], + } + } +} + +/// Helper function, since the function in `ReportId` is private. +const fn try_u16_to_report_id(value: u16) -> Result { + match value >> 8 { + 1 => Ok(ReportId::In(value as u8)), + 2 => Ok(ReportId::Out(value as u8)), + 3 => Ok(ReportId::Feature(value as u8)), + _ => Err(()), + } +} + +impl<'d> Handler for XInputControl<'d> { + fn reset(&mut self) { + self.out_report_offset.store(0, Ordering::Release); + } + + fn control_out(&mut self, req: Request, data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != ( + RequestType::Class, + Recipient::Interface, + self.if_num.0 as u16, + ) + { + return None; + } + + trace!("HID control_out {:?} {=[u8]:x}", req, data); + + match req.request { + HID_REQ_SET_IDLE => { + if let Some(handler) = self.request_handler.as_mut() { + let id = req.value as u8; + let id = (id != 0).then_some(ReportId::In(id)); + let dur = u32::from(req.value >> 8); + let dur = if dur == 0 { u32::MAX } else { 4 * dur }; + handler.set_idle_ms(id, dur); + } + Some(OutResponse::Accepted) + } + HID_REQ_SET_REPORT => { + match ( + try_u16_to_report_id(req.value), + self.request_handler.as_mut(), + ) { + (Ok(id), Some(handler)) => Some(handler.set_report(id, data)), + _ => Some(OutResponse::Rejected), + } + } + HID_REQ_SET_PROTOCOL => { + if req.value == 1 { + Some(OutResponse::Accepted) + } else { + warn!("HID Boot Protocol is unsupported."); + Some(OutResponse::Rejected) // UNSUPPORTED: Boot Protocol + } + } + _ => Some(OutResponse::Rejected), + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if req.index != self.if_num.0 as u16 { + return None; + } + + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Interface) => match req.request { + Request::GET_DESCRIPTOR => match (req.value >> 8) as u8 { + HID_DESC_DESCTYPE_HID_REPORT => { + Some(InResponse::Accepted(self.report_descriptor)) + } + HID_DESC_DESCTYPE_HID => Some(InResponse::Accepted(&self.hid_descriptor)), + _ => Some(InResponse::Rejected), + }, + + _ => Some(InResponse::Rejected), + }, + (RequestType::Class, Recipient::Interface) => { + trace!("HID control_in {:?}", req); + match req.request { + HID_REQ_GET_REPORT => { + let size = match try_u16_to_report_id(req.value) { + Ok(id) => self + .request_handler + .as_mut() + .and_then(|x| x.get_report(id, buf)), + Err(_) => None, + }; + + if let Some(size) = size { + Some(InResponse::Accepted(&buf[0..size])) + } else { + Some(InResponse::Rejected) + } + } + HID_REQ_GET_IDLE => { + if let Some(handler) = self.request_handler.as_mut() { + let id = req.value as u8; + let id = (id != 0).then_some(ReportId::In(id)); + if let Some(dur) = handler.get_idle_ms(id) { + let dur = u8::try_from(dur / 4).unwrap_or(0); + buf[0] = dur; + Some(InResponse::Accepted(&buf[0..1])) + } else { + Some(InResponse::Rejected) + } + } else { + Some(InResponse::Rejected) + } + } + HID_REQ_GET_PROTOCOL => { + // UNSUPPORTED: Boot Protocol + buf[0] = 1; + Some(InResponse::Accepted(&buf[0..1])) + } + _ => Some(InResponse::Rejected), + } + } + _ => None, + } + } +} diff --git a/src/input.rs b/src/input.rs index c0d0d0b..3738ae0 100644 --- a/src/input.rs +++ b/src/input.rs @@ -428,6 +428,8 @@ pub async fn update_button_state_task( let mut m = MUTEX_CONTROLLER_MODE.lock().await; *m = if btn_start.is_low() { Some(ControllerMode::Procon) + } else if btn_x.is_low() { + Some(ControllerMode::XInput) } else { Some(ControllerMode::GcAdapter) }; diff --git a/src/usb_comms.rs b/src/usb_comms.rs index bf94edb..e89692d 100644 --- a/src/usb_comms.rs +++ b/src/usb_comms.rs @@ -15,7 +15,7 @@ use embassy_rp::{ use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, signal::Signal}; use embassy_time::{Duration, Instant, Timer}; use embassy_usb::{ - class::hid::{HidReader, HidReaderWriter, HidWriter, RequestHandler, State}, + class::hid::{HidReaderWriter, RequestHandler, State}, driver::Driver, msos::{self, windows_version}, Builder, Handler, UsbDevice, @@ -27,6 +27,11 @@ use crate::{ hid::{ gcc::{GcReportBuilder, GcState, GccRequestHandler, GCC_REPORT_DESCRIPTOR}, procon::{ProconReportBuilder, ProconRequestHandler, PROCON_REPORT_DESCRIPTOR}, + xinput::{ + XInputReaderWriter, XInputReportBuilder, XInputRequestHandler, XInputState, + XINPUT_REPORT_DESCRIPTOR, + }, + HidReaderWriterSplit, UsbReader, UsbWriter, }, input::CHANNEL_GCC_STATE, }; @@ -73,6 +78,11 @@ impl From for UsbConfig { pid: 0x2009, report_descriptor: PROCON_REPORT_DESCRIPTOR, }, + ControllerMode::XInput => Self { + vid: 0x045e, + pid: 0x028e, + report_descriptor: XINPUT_REPORT_DESCRIPTOR, + }, } } } @@ -122,27 +132,41 @@ impl Handler for MyDeviceHandler { } } -fn mk_hid_reader_writer<'d, D: Driver<'d>, const R: usize, const W: usize>( +fn mk_hid_reader_writer<'d, S, D, T, F, const R: usize, const W: usize>( input_consistency_mode: InputConsistencyMode, + controller_mode: ControllerMode, report_descriptor: &'d [u8], mut builder: Builder<'d, D>, - state: &'d mut State<'d>, -) -> (UsbDevice<'d, D>, HidReader<'d, D, R>, HidWriter<'d, D, W>) { + state: &'d mut S, + mut init_func: F, +) -> ( + UsbDevice<'d, D>, + impl UsbReader<'d, D, R>, + impl UsbWriter<'d, D, W>, +) +where + D: Driver<'d>, + T: HidReaderWriterSplit<'d, D, R, W> + 'd, + F: FnMut(&mut Builder<'d, D>, &'d mut S, embassy_usb::class::hid::Config<'d>) -> T + 'd, +{ let hid_config = embassy_usb::class::hid::Config { report_descriptor, request_handler: None, - poll_ms: match input_consistency_mode { - InputConsistencyMode::Original => 8, - InputConsistencyMode::ConsistencyHack - | InputConsistencyMode::SuperHack - | InputConsistencyMode::PC => 1, + poll_ms: if let ControllerMode::XInput = controller_mode { + 1 + } else { + 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 hid = init_func(&mut builder, state, hid_config); let usb = builder.build(); @@ -151,13 +175,16 @@ fn mk_hid_reader_writer<'d, D: Driver<'d>, const R: usize, const W: usize>( (usb, reader, writer) } -fn mk_usb_transfer_futures<'d, D, H, Rq, const R: usize, const W: usize>( +#[allow(clippy::too_many_arguments)] +fn mk_usb_transfer_futures<'d, S, D, H, F, Rq, T, const R: usize, const W: usize>( input_consistency_mode: InputConsistencyMode, + controller_mode: ControllerMode, usb_config: &UsbConfig, request_handler: &'d mut Rq, builder: Builder<'d, D>, - state: &'d mut State<'d>, mut hid_report_builder: H, + state: &'d mut S, + init_func: F, ) -> ( impl Future + 'd, impl Future + 'd, @@ -167,12 +194,16 @@ where D: Driver<'d> + 'd, H: HidReportBuilder + 'd, Rq: RequestHandler, + T: HidReaderWriterSplit<'d, D, R, W> + 'd, + F: FnMut(&mut Builder<'d, D>, &'d mut S, embassy_usb::class::hid::Config<'d>) -> T + 'd, { - let (mut usb, reader, mut writer) = mk_hid_reader_writer::<_, R, W>( + let (mut usb, reader, mut writer) = mk_hid_reader_writer( input_consistency_mode, + controller_mode, usb_config.report_descriptor, builder, state, + init_func, ); let usb_fut = async move { @@ -200,12 +231,14 @@ where // 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; + if controller_mode != ControllerMode::XInput { + 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 => {} } - InputConsistencyMode::Original | InputConsistencyMode::PC => {} } writer.ready().await; @@ -224,6 +257,7 @@ where debug!("Report written in {}us", micros); if input_consistency_mode != InputConsistencyMode::Original && input_consistency_mode != InputConsistencyMode::PC + && controller_mode != ControllerMode::XInput { while rate_limit_end_time < currtime { rate_limit_end_time += Duration::from_micros(8333); @@ -291,20 +325,37 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'stati trace!("Start of config"); 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)", - InputConsistencyMode::ConsistencyHack => "NaxGCC (Consistency Mode)", - InputConsistencyMode::SuperHack => "NaxGCC (SuperHack Mode)", - InputConsistencyMode::PC => "NaxGCC (PC Mode)", + usb_config.product = Some(if controller_mode == ControllerMode::XInput { + "NaxGCC (XInput Mode)" + } else { + match input_consistency_mode { + InputConsistencyMode::Original => "NaxGCC (OG Mode)", + InputConsistencyMode::ConsistencyHack => "NaxGCC (Consistency Mode)", + InputConsistencyMode::SuperHack => "NaxGCC (SuperHack Mode)", + InputConsistencyMode::PC => "NaxGCC (PC Mode)", + } }); usb_config.serial_number = Some(serial); usb_config.max_power = 200; usb_config.max_packet_size_0 = 64; - usb_config.device_class = 0; + usb_config.device_class = if controller_mode == ControllerMode::XInput { + 0xff + } else { + 0 + }; usb_config.device_protocol = 0; usb_config.self_powered = false; - usb_config.device_sub_class = 0; + usb_config.device_sub_class = if controller_mode == ControllerMode::XInput { + 0xff + } else { + 0 + }; usb_config.supports_remote_wakeup = true; + usb_config.device_release = if controller_mode == ControllerMode::XInput { + 0x0572 + } else { + 0x0010 + }; let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; @@ -313,8 +364,6 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'stati let mut device_handler = MyDeviceHandler::new(); - let mut state = State::new(); - let mut builder = Builder::new( driver, usb_config, @@ -337,29 +386,55 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'stati match controller_mode { ControllerMode::GcAdapter => { + let mut state = State::new(); + let mut request_handler = GccRequestHandler; - let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 5, 37>( + let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures( input_consistency_mode, + controller_mode, &config, &mut request_handler, builder, - &mut state, GcReportBuilder::default(), + &mut state, + HidReaderWriter::<_, 5, 37>::new, ); join(usb_fut_wrapped, join(in_fut, out_fut)).await; } ControllerMode::Procon => { + let mut state = State::new(); + let mut request_handler = ProconRequestHandler; - let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 64, 64>( + let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures( input_consistency_mode, + controller_mode, &config, &mut request_handler, builder, - &mut state, ProconReportBuilder::default(), + &mut state, + HidReaderWriter::<_, 64, 64>::new, + ); + + join(usb_fut_wrapped, join(in_fut, out_fut)).await; + } + ControllerMode::XInput => { + let mut state = XInputState::new(); + + let mut request_handler = XInputRequestHandler; + + let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures( + input_consistency_mode, + controller_mode, + &config, + &mut request_handler, + builder, + XInputReportBuilder, + &mut state, + XInputReaderWriter::<_, 32, 32>::new, ); join(usb_fut_wrapped, join(in_fut, out_fut)).await; -- 2.51.2 From 0a0754c2483e733ad3b1fb3430868628cd30ec37 Mon Sep 17 00:00:00 2001 From: Naxdy Date: Sat, 28 Dec 2024 03:50:33 +0000 Subject: [PATCH 4/5] feat: introduce `ControllerState` + add functionality for 2nd Z button (#29) Reviewed-on: https://git.naxdy.org/NaxdyOrg/NaxGCC-FW/pulls/29 --- src/config.rs | 95 +++++++++++++++----------- src/hid/gcc.rs | 41 ++++++++++- src/hid/procon.rs | 46 ++++++------- src/hid/xinput.rs | 44 ++++++------ src/input.rs | 163 +++++++++++++++++++++++++++++--------------- src/input_filter.rs | 96 +++++++++++++------------- src/main.rs | 1 + src/usb_comms.rs | 8 +-- 8 files changed, 299 insertions(+), 195 deletions(-) diff --git a/src/config.rs b/src/config.rs index 343e429..c290797 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,7 +19,8 @@ use crate::{ helpers::{PackedFloat, ToPackedFloatArray, ToRegularArray, XyValuePair}, hid::gcc::{GcButtons1, GcButtons2, GcState}, input::{ - read_ext_adc, Stick, StickAxis, FLOAT_ORIGIN, SPI_ACS_SHARED, SPI_CCS_SHARED, SPI_SHARED, + read_ext_adc, ControllerState, Stick, StickAxis, FLOAT_ORIGIN, SPI_ACS_SHARED, + SPI_CCS_SHARED, SPI_SHARED, }, stick::{ calc_stick_values, legalize_notches, AppliedCalibration, CleanedCalibrationPoints, @@ -37,7 +38,7 @@ use embassy_sync::{ }; use embassy_time::Timer; -use crate::input::CHANNEL_GCC_STATE; +use crate::input::CHANNEL_CONTROLLER_STATE; /// Whether we are currently calibrating the sticks. Updates are dispatched when the status changes. /// Initial status is assumed to be false. @@ -53,8 +54,10 @@ pub static SIGNAL_OVERRIDE_STICK_STATE: Signal< > = Signal::new(); /// Dispatched when we want to override the GCC state for a short amount of time. -pub static SIGNAL_OVERRIDE_GCC_STATE: Signal = - Signal::new(); +pub static SIGNAL_OVERRIDE_CONTROLLER_STATE: Signal< + CriticalSectionRawMutex, + OverrideGcReportInstruction, +> = Signal::new(); /// Dispatched when we want to enter config mode, sent from core1 so config mode /// doesn't need to watch for the entire button combo every time. @@ -79,7 +82,7 @@ const MAX_ANALOG_SCALER: u8 = 125; /// a certain mode. #[derive(Default, Debug, Clone, Format)] pub struct OverrideGcReportInstruction { - pub report: GcState, + pub report: ControllerState, pub duration_ms: u64, } @@ -684,7 +687,7 @@ trait ButtonPressProvider { } impl<'a, T: RawMutex, const I: usize, const J: usize, const K: usize> ButtonPressProvider - for Subscriber<'a, T, GcState, I, J, K> + for Subscriber<'a, T, ControllerState, I, J, K> { async fn wait_for_button_press(&mut self, button_to_wait_for: &AwaitableButtons) { loop { @@ -784,25 +787,25 @@ impl<'a, T: RawMutex, const I: usize, const J: usize, const K: usize> ButtonPres } pub fn is_awaitable_button_pressed( - report: &GcState, + report: &ControllerState, button_to_wait_for: &AwaitableButtons, ) -> bool { match button_to_wait_for { - AwaitableButtons::A => report.buttons_1.button_a, - AwaitableButtons::B => report.buttons_1.button_b, - AwaitableButtons::X => report.buttons_1.button_x, - AwaitableButtons::Y => report.buttons_1.button_y, - AwaitableButtons::Up => report.buttons_1.dpad_up, - AwaitableButtons::Down => report.buttons_1.dpad_down, - AwaitableButtons::Left => report.buttons_1.dpad_left, - AwaitableButtons::Right => report.buttons_1.dpad_right, - AwaitableButtons::Start => report.buttons_2.button_start, - AwaitableButtons::L => report.buttons_2.button_l || report.trigger_l > 10, - AwaitableButtons::R => report.buttons_2.button_r || report.trigger_r > 10, - AwaitableButtons::Z => report.buttons_2.button_z, + AwaitableButtons::A => report.button_a, + AwaitableButtons::B => report.button_b, + AwaitableButtons::X => report.button_x, + AwaitableButtons::Y => report.button_y, + AwaitableButtons::Up => report.dpad_up, + AwaitableButtons::Down => report.dpad_down, + AwaitableButtons::Left => report.dpad_left, + AwaitableButtons::Right => report.dpad_right, + AwaitableButtons::Start => report.button_start, + AwaitableButtons::L => report.trigger_l, + AwaitableButtons::R => report.trigger_r, + AwaitableButtons::Z => report.trigger_zr, AwaitableButtons::Wildcard => true, AwaitableButtons::Impossible => false, - AwaitableButtons::NotZ => !report.buttons_2.button_z, + AwaitableButtons::NotZ => !report.trigger_zr, } } @@ -1023,7 +1026,7 @@ impl<'a> StickCalibrationProcess<'a> { pub async fn calibrate_stick(&mut self) { info!("Beginning stick calibration for {}", self.which_stick); - let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap(); + let mut gcc_subscriber = CHANNEL_CONTROLLER_STATE.subscriber().unwrap(); SIGNAL_IS_CALIBRATING.signal(true); let mut done = false; @@ -1137,7 +1140,7 @@ fn get_stick_display_coords(current_step: usize) -> (f32, f32) { } pub async fn override_gcc_state_and_wait(state: &OverrideGcReportInstruction) { - SIGNAL_OVERRIDE_GCC_STATE.signal(state.clone()); + SIGNAL_OVERRIDE_CONTROLLER_STATE.signal(state.clone()); Timer::after_millis(state.duration_ms).await; } @@ -1150,7 +1153,7 @@ async fn configuration_main_loop< >( current_config: &ControllerConfig, flash: &mut Flash<'static, FLASH, Async, FLASH_SIZE>, - gcc_subscriber: &mut Subscriber<'a, M, GcState, C, S, P>, + gcc_subscriber: &mut Subscriber<'a, M, ControllerState, C, S, P>, ) -> ControllerConfig { let mut final_config = current_config.clone(); let config_options = [ @@ -1226,7 +1229,8 @@ async fn configuration_main_loop< stick_y: 127, cstick_x: 127, cstick_y: 127, - }, + } + .into(), duration_ms: 1000, }) .await; @@ -1254,7 +1258,8 @@ async fn configuration_main_loop< stick_y: 255, cstick_x: 127, cstick_y: 127, - }, + } + .into(), duration_ms: 1000, }) .await; @@ -1283,7 +1288,8 @@ async fn configuration_main_loop< stick_y: 127, cstick_x: 255, cstick_y: 255, - }, + } + .into(), duration_ms: 1000, }) .await; @@ -1370,7 +1376,8 @@ async fn configuration_main_loop< StickAxis::YAxis => *to_adjust, }, }) as u8, - }, + } + .into(), duration_ms: 750, }) .await; @@ -1456,7 +1463,8 @@ async fn configuration_main_loop< StickAxis::YAxis => *to_adjust, }, }) as u8, - }, + } + .into(), duration_ms: 750, }) .await; @@ -1542,7 +1550,8 @@ async fn configuration_main_loop< StickAxis::YAxis => *to_adjust, }, }) as u8, - }, + } + .into(), duration_ms: 750, }) .await; @@ -1597,7 +1606,8 @@ async fn configuration_main_loop< Stick::ControlStick => 0, Stick::CStick => *to_adjust, }) as u8, - }, + } + .into(), duration_ms: 750, }) .await; @@ -1653,7 +1663,8 @@ async fn configuration_main_loop< Stick::ControlStick => 0, Stick::CStick => *to_adjust, }) as u8, - }, + } + .into(), duration_ms: 750, }) .await; @@ -1694,7 +1705,8 @@ async fn configuration_main_loop< stick_y: 127 + *to_adjust, cstick_x: 127, cstick_y: 127, - }, + } + .into(), duration_ms: 750, }) .await; @@ -1735,7 +1747,8 @@ async fn configuration_main_loop< }) as u8, cstick_x: 127, cstick_y: 127, - }, + } + .into(), duration_ms: 750, }) .await; @@ -1763,7 +1776,8 @@ async fn configuration_main_loop< stick_y: 127 + final_config.astick_config.y_waveshaping, cstick_x: 127 + final_config.cstick_config.x_waveshaping, cstick_y: 127 + final_config.cstick_config.y_waveshaping, - }, + } + .into(), duration_ms: 1000, }) .await; @@ -1789,7 +1803,8 @@ async fn configuration_main_loop< stick_y: 127 + final_config.astick_config.y_smoothing, cstick_x: 127 + final_config.cstick_config.x_smoothing, cstick_y: 127 + final_config.cstick_config.y_smoothing, - }, + } + .into(), duration_ms: 1000, }) .await; @@ -1815,7 +1830,8 @@ async fn configuration_main_loop< stick_y: (127 + final_config.astick_config.y_snapback) as u8, cstick_x: (127 + final_config.cstick_config.x_snapback) as u8, cstick_y: (127 + final_config.cstick_config.y_snapback) as u8, - }, + } + .into(), duration_ms: 1000, }) .await; @@ -1836,7 +1852,7 @@ async fn configuration_main_loop< #[embassy_executor::task] pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) { - let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap(); + let mut gcc_subscriber = CHANNEL_CONTROLLER_STATE.subscriber().unwrap(); info!("Config task is running."); @@ -1886,7 +1902,8 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) { stick_y: 127, cstick_x: 127, cstick_y: 127, - }, + } + .into(), duration_ms: 1000, }) .await; @@ -1902,7 +1919,7 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) { #[embassy_executor::task] pub async fn enter_config_mode_task() { - let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap(); + let mut gcc_subscriber = CHANNEL_CONTROLLER_STATE.subscriber().unwrap(); info!("Enter config mode task is running."); diff --git a/src/hid/gcc.rs b/src/hid/gcc.rs index dddc8ef..c8cd030 100644 --- a/src/hid/gcc.rs +++ b/src/hid/gcc.rs @@ -4,7 +4,10 @@ use embassy_usb::{ control::OutResponse, }; -use crate::usb_comms::{HidReportBuilder, SIGNAL_RUMBLE}; +use crate::{ + input::ControllerState, + usb_comms::{HidReportBuilder, SIGNAL_RUMBLE}, +}; use packed_struct::{derive::PackedStruct, PackedStruct}; #[rustfmt::skip] @@ -125,6 +128,36 @@ pub struct GcState { pub trigger_r: u8, } +impl From for GcState { + fn from(value: ControllerState) -> Self { + Self { + buttons_1: GcButtons1 { + button_a: value.button_a, + button_b: value.button_b, + button_x: value.button_x, + button_y: value.button_y, + dpad_left: value.dpad_left, + dpad_right: value.dpad_right, + dpad_down: value.dpad_down, + dpad_up: value.dpad_up, + }, + buttons_2: GcButtons2 { + button_start: value.button_start, + button_z: value.trigger_zr, + button_r: value.trigger_r, + button_l: value.trigger_l, + blank1: 0, + }, + stick_x: value.stick_state.ax, + stick_y: value.stick_state.ay, + cstick_x: value.stick_state.cx, + cstick_y: value.stick_state.cy, + trigger_l: if value.trigger_l { 255 } else { 0 }, + trigger_r: if value.trigger_r { 255 } else { 0 }, + } + } +} + impl Default for GcState { fn default() -> Self { Self { @@ -146,13 +179,15 @@ pub struct GcReportBuilder { } impl HidReportBuilder<37> for GcReportBuilder { - async fn get_hid_report(&mut self, state: &GcState) -> [u8; 37] { + async fn get_hid_report(&mut self, state: &ControllerState) -> [u8; 37] { let mut buffer = [0u8; 37]; buffer[0] = 0x21; buffer[1] |= 0x14; - let data = state.pack().expect("Failed to pack GC input data"); + let gcc_state: GcState = (*state).into(); + + let data = gcc_state.pack().expect("Failed to pack GC input data"); if !self.gc_first { buffer[1] |= 0x04; diff --git a/src/hid/procon.rs b/src/hid/procon.rs index 474cfdf..1ed2b5a 100644 --- a/src/hid/procon.rs +++ b/src/hid/procon.rs @@ -18,9 +18,7 @@ use embassy_usb::{ use packed_struct::{derive::PackedStruct, PackedStruct}; use rand::RngCore; -use crate::usb_comms::HidReportBuilder; - -use super::gcc::GcState; +use crate::{input::ControllerState, usb_comms::HidReportBuilder}; const SW_INFO_SET_MAC: u8 = 0x01; @@ -486,36 +484,36 @@ impl Default for BatteryStatus { } } -impl From<&GcState> for ProconState { - fn from(value: &GcState) -> Self { +impl From<&ControllerState> for ProconState { + fn from(value: &ControllerState) -> Self { Self { buttons_left: ProconButtonsLeft { - dpad_down: value.buttons_1.dpad_down, - dpad_right: value.buttons_1.dpad_right, - dpad_up: value.buttons_1.dpad_up, - dped_left: value.buttons_1.dpad_left, - trigger_l: value.buttons_2.button_l, - trigger_zl: value.buttons_2.button_l, + dpad_down: value.dpad_down, + dpad_right: value.dpad_right, + dpad_up: value.dpad_up, + dped_left: value.dpad_left, + trigger_l: value.trigger_zl, + trigger_zl: value.trigger_l, ..Default::default() }, buttons_right: ProconButtonsRight { - button_a: value.buttons_1.button_a, - button_b: value.buttons_1.button_b, - button_x: value.buttons_1.button_x, - button_y: value.buttons_1.button_y, - trigger_r: value.buttons_2.button_z, - trigger_zr: value.buttons_2.button_r, + button_a: value.button_a, + button_b: value.button_b, + button_x: value.button_x, + button_y: value.button_y, + trigger_r: value.trigger_zr, + trigger_zr: value.trigger_r, ..Default::default() }, buttons_shared: ProconButtonsShared { - button_plus: value.buttons_2.button_start && !value.buttons_2.button_z, - button_home: value.buttons_2.button_start && value.buttons_2.button_z, + button_plus: value.button_start && !value.trigger_zr, + button_home: value.button_start && value.trigger_zr, ..Default::default() }, - lstick_x: value.stick_x as u16 * 16, - lstick_y: value.stick_y, - rstick_x: value.cstick_x as u16 * 16, - rstick_y: value.cstick_y, + lstick_x: value.stick_state.ax as u16 * 16, + lstick_y: value.stick_state.ay, + rstick_x: value.stick_state.cx as u16 * 16, + rstick_y: value.stick_state.cy, } } } @@ -694,7 +692,7 @@ impl ProconReportBuilder { } impl HidReportBuilder<64> for ProconReportBuilder { - async fn get_hid_report(&mut self, state: &GcState) -> [u8; 64] { + async fn get_hid_report(&mut self, state: &ControllerState) -> [u8; 64] { let current_report_info = if self.switch_reporting_mode == RM_SEND_STATE { SIGNAL_PROCON_REQUEST.try_take() } else { diff --git a/src/hid/xinput.rs b/src/hid/xinput.rs index ddd8c20..23138f5 100644 --- a/src/hid/xinput.rs +++ b/src/hid/xinput.rs @@ -22,9 +22,9 @@ use embassy_usb::{ }; use packed_struct::{derive::PackedStruct, PackedStruct}; -use crate::usb_comms::HidReportBuilder; +use crate::{input::ControllerState, usb_comms::HidReportBuilder}; -use super::{gcc::GcState, HidReaderWriterSplit, UsbReader, UsbWriter}; +use super::{HidReaderWriterSplit, UsbReader, UsbWriter}; /// lol pub const XINPUT_REPORT_DESCRIPTOR: &[u8] = &[]; @@ -111,37 +111,37 @@ pub struct XInputReport { pub reserved: [u8; 18], } -impl From<&GcState> for XInputReport { - fn from(value: &GcState) -> Self { +impl From<&ControllerState> for XInputReport { + fn from(value: &ControllerState) -> Self { Self { report_id: 0, report_size: 20, buttons_1: XInputButtons1 { - dpad_up: value.buttons_1.dpad_up, - dpad_down: value.buttons_1.dpad_down, - dpad_right: value.buttons_1.dpad_right, - dpad_left: value.buttons_1.dpad_left, - button_menu: value.buttons_2.button_start, + dpad_up: value.dpad_up, + dpad_down: value.dpad_down, + dpad_right: value.dpad_right, + dpad_left: value.dpad_left, + button_menu: value.button_start, button_back: false, button_stick_l: false, button_stick_r: false, }, buttons_2: XInputButtons2 { blank_1: false, - bumper_l: false, - bumper_r: value.buttons_2.button_z, - button_a: value.buttons_1.button_a, - button_b: value.buttons_1.button_b, - button_x: value.buttons_1.button_x, - button_y: value.buttons_1.button_y, + bumper_l: value.trigger_zl, + bumper_r: value.trigger_zr, + button_a: value.button_a, + button_b: value.button_b, + button_x: value.button_x, + button_y: value.button_y, button_guide: false, }, - analog_trigger_l: value.trigger_l, - analog_trigger_r: value.trigger_r, - stick_left_x: (value.stick_x as i16 - 127).clamp(-127, 127) * 257, - stick_left_y: (value.stick_y as i16 - 127).clamp(-127, 127) * 257, - stick_right_x: (value.cstick_x as i16 - 127).clamp(-127, 127) * 257, - stick_right_y: (value.cstick_y as i16 - 127).clamp(-127, 127) * 257, + analog_trigger_l: if value.trigger_l { 255 } else { 0 }, + analog_trigger_r: if value.trigger_r { 255 } else { 0 }, + stick_left_x: (value.stick_state.ax as i16 - 127).clamp(-127, 127) * 257, + stick_left_y: (value.stick_state.ay as i16 - 127).clamp(-127, 127) * 257, + stick_right_x: (value.stick_state.cx as i16 - 127).clamp(-127, 127) * 257, + stick_right_y: (value.stick_state.cy as i16 - 127).clamp(-127, 127) * 257, reserved: [0u8; 18], } } @@ -153,7 +153,7 @@ impl From<&GcState> for XInputReport { pub struct XInputReportBuilder; impl HidReportBuilder<32> for XInputReportBuilder { - async fn get_hid_report(&mut self, state: &super::gcc::GcState) -> [u8; 32] { + async fn get_hid_report(&mut self, state: &ControllerState) -> [u8; 32] { XInputReport::from(state) .pack() .expect("Failed to pack XInput State") diff --git a/src/input.rs b/src/input.rs index 3738ae0..cd28a00 100644 --- a/src/input.rs +++ b/src/input.rs @@ -17,8 +17,8 @@ use libm::{fmaxf, fminf}; use crate::{ config::{ ControllerConfig, ControllerMode, InputConsistencyMode, OverrideGcReportInstruction, - OverrideStickState, SIGNAL_CONFIG_CHANGE, SIGNAL_IS_CALIBRATING, SIGNAL_OVERRIDE_GCC_STATE, - SIGNAL_OVERRIDE_STICK_STATE, + OverrideStickState, SIGNAL_CONFIG_CHANGE, SIGNAL_IS_CALIBRATING, + SIGNAL_OVERRIDE_CONTROLLER_STATE, SIGNAL_OVERRIDE_STICK_STATE, }, filter::{run_waveshaping, FilterGains, KalmanState, WaveshapingValues, FILTER_GAINS}, helpers::XyValuePair, @@ -29,8 +29,13 @@ use crate::{ }; /// Used to send the button state to the usb task and the calibration task -pub static CHANNEL_GCC_STATE: PubSubChannel = - PubSubChannel::new(); +pub static CHANNEL_CONTROLLER_STATE: PubSubChannel< + CriticalSectionRawMutex, + ControllerState, + 1, + 4, + 1, +> = PubSubChannel::new(); /// Used to send the stick state from the stick task to the main input task static SIGNAL_STICK_STATE: Signal = Signal::new(); @@ -43,7 +48,7 @@ pub static SPI_CCS_SHARED: Mutex>> = const STICK_HYST_VAL: f32 = 0.3; pub const FLOAT_ORIGIN: f32 = 127.5; -#[derive(Clone, Debug, Default, Format)] +#[derive(Clone, Copy, Debug, Format, PartialEq, Eq)] pub struct StickState { pub ax: u8, pub ay: u8, @@ -51,6 +56,17 @@ pub struct StickState { pub cy: u8, } +impl Default for StickState { + fn default() -> Self { + Self { + ax: 127, + ay: 127, + cx: 127, + cy: 127, + } + } +} + #[derive(Clone, Debug, Default)] struct StickPositions { x: f32, @@ -69,6 +85,51 @@ struct RawStickValues { c_unfiltered: XyValuePair, } +#[derive(Clone, Copy, Debug, Default, Format, PartialEq, Eq)] +pub struct ControllerState { + pub button_a: bool, + pub button_b: bool, + pub button_x: bool, + pub button_y: bool, + pub trigger_zr: bool, + pub trigger_zl: bool, + pub trigger_l: bool, + pub trigger_r: bool, + pub button_start: bool, + pub dpad_up: bool, + pub dpad_down: bool, + pub dpad_left: bool, + pub dpad_right: bool, + pub stick_state: StickState, +} + +/// This is only implemented for backwards-compatibility purposes +impl From for ControllerState { + fn from(value: GcState) -> Self { + Self { + button_a: value.buttons_1.button_a, + button_b: value.buttons_1.button_b, + button_x: value.buttons_1.button_x, + button_y: value.buttons_1.button_y, + trigger_zr: value.buttons_2.button_z, + trigger_zl: false, + trigger_l: value.trigger_l > 170, + trigger_r: value.trigger_r > 170, + button_start: value.buttons_2.button_start, + dpad_up: value.buttons_1.dpad_up, + dpad_down: value.buttons_1.dpad_down, + dpad_left: value.buttons_1.dpad_left, + dpad_right: value.buttons_1.dpad_right, + stick_state: StickState { + ax: value.stick_x, + ay: value.stick_y, + cx: value.stick_x, + cy: value.stick_y, + }, + } + } +} + #[derive(PartialEq, Eq, Debug, Clone, Format, Copy)] pub enum Stick { ControlStick, @@ -312,7 +373,7 @@ async fn update_stick_states( raw_stick_values.c_unfiltered.x = fminf(125., fmaxf(-125., remapped_c_unfiltered.x)); raw_stick_values.c_unfiltered.y = fminf(125., fmaxf(-125., remapped_c_unfiltered.y)); - let mut out_stick_state = current_stick_state.clone(); + let mut out_stick_state = *current_stick_state; let diff_x = (remapped.x + FLOAT_ORIGIN) - current_stick_state.ax as f32; if !(-STICK_HYST_VAL..=(1.0 + STICK_HYST_VAL)).contains(&diff_x) { @@ -345,7 +406,7 @@ async fn update_stick_states( #[allow(clippy::too_many_arguments)] fn update_button_states( - gcc_state: &mut GcState, + controller_state: &mut ControllerState, btn_a: &Input<'_>, btn_b: &Input<'_>, btn_x: &Input<'_>, @@ -353,42 +414,37 @@ fn update_button_states( btn_start: &Input<'_>, btn_l: &Input<'_>, btn_r: &Input<'_>, - btn_z: &Input<'_>, + btn_zr: &Input<'_>, + btn_zl: &Input<'_>, btn_dleft: &Input<'_>, btn_dright: &Input<'_>, btn_dup: &Input<'_>, btn_ddown: &Input<'_>, ) { - gcc_state.buttons_1.button_a = btn_a.is_low(); - gcc_state.buttons_1.button_b = btn_b.is_low(); - gcc_state.buttons_1.button_x = btn_x.is_low(); - gcc_state.buttons_1.button_y = btn_y.is_low(); - gcc_state.buttons_2.button_z = btn_z.is_low(); - gcc_state.buttons_2.button_start = btn_start.is_low(); - gcc_state.buttons_2.button_l = btn_l.is_low(); - gcc_state.buttons_2.button_r = btn_r.is_low(); - gcc_state.buttons_1.dpad_left = btn_dleft.is_low(); - gcc_state.buttons_1.dpad_right = btn_dright.is_low(); - gcc_state.buttons_1.dpad_up = btn_dup.is_low(); - gcc_state.buttons_1.dpad_down = btn_ddown.is_low(); - gcc_state.trigger_l = match gcc_state.buttons_2.button_l { - true => 255, - false => 0, - }; - gcc_state.trigger_r = match gcc_state.buttons_2.button_r { - true => 255, - false => 0, - }; + controller_state.button_a = btn_a.is_low(); + controller_state.button_b = btn_b.is_low(); + controller_state.button_x = btn_x.is_low(); + controller_state.button_y = btn_y.is_low(); + controller_state.trigger_zr = btn_zr.is_low(); + controller_state.trigger_zl = btn_zl.is_low(); + controller_state.button_start = btn_start.is_low(); + controller_state.trigger_l = btn_l.is_low(); + controller_state.trigger_r = btn_r.is_low(); + controller_state.dpad_left = btn_dleft.is_low(); + controller_state.dpad_right = btn_dright.is_low(); + controller_state.dpad_up = btn_dup.is_low(); + controller_state.dpad_down = btn_ddown.is_low(); } #[embassy_executor::task] pub async fn input_integrity_benchmark() { loop { - SIGNAL_OVERRIDE_GCC_STATE.signal(OverrideGcReportInstruction { + SIGNAL_OVERRIDE_CONTROLLER_STATE.signal(OverrideGcReportInstruction { report: { - let mut report = GcState::default(); - report.buttons_1.dpad_up = true; - report + ControllerState { + dpad_up: true, + ..Default::default() + } }, duration_ms: 100, }); @@ -402,7 +458,8 @@ pub async fn input_integrity_benchmark() { #[allow(clippy::too_many_arguments)] #[embassy_executor::task] pub async fn update_button_state_task( - btn_z: Input<'static>, + btn_zr: Input<'static>, + btn_zl: Input<'static>, btn_a: Input<'static>, btn_b: Input<'static>, btn_dright: Input<'static>, @@ -442,11 +499,11 @@ pub async fn update_button_state_task( MUTEX_INPUT_CONSISTENCY_MODE.lock().await.unwrap() }; - let mut previous_state = GcState::default(); + let mut previous_state = ControllerState::default(); - let mut gcc_state = GcState::default(); + let mut controller_state = ControllerState::default(); - let gcc_publisher = CHANNEL_GCC_STATE.publisher().unwrap(); + let gcc_publisher = CHANNEL_CONTROLLER_STATE.publisher().unwrap(); let mut override_stick_state: Option = None; @@ -459,7 +516,7 @@ pub async fn update_button_state_task( loop { update_button_states( - &mut gcc_state, + &mut controller_state, &btn_a, &btn_b, &btn_x, @@ -467,7 +524,8 @@ pub async fn update_button_state_task( &btn_start, &btn_l, &btn_r, - &btn_z, + &btn_zr, + &btn_zl, &btn_dleft, &btn_dright, &btn_dup, @@ -476,10 +534,7 @@ pub async fn update_button_state_task( // not every loop pass is going to update the stick state if let Some(stick_state) = SIGNAL_STICK_STATE.try_take() { - gcc_state.stick_x = stick_state.ax; - gcc_state.stick_y = stick_state.ay; - gcc_state.cstick_x = stick_state.cx; - gcc_state.cstick_y = stick_state.cy; + controller_state.stick_state = stick_state } if let Some(override_stick_state_opt) = SIGNAL_OVERRIDE_STICK_STATE.try_take() { @@ -488,7 +543,7 @@ pub async fn update_button_state_task( } // check for a gcc state override (usually coming from the config task) - if let Some(override_gcc_state) = SIGNAL_OVERRIDE_GCC_STATE.try_take() { + if let Some(override_gcc_state) = SIGNAL_OVERRIDE_CONTROLLER_STATE.try_take() { trace!("Overridden gcc state: {:?}", override_gcc_state.report); let end_time = Instant::now() + Duration::from_millis(override_gcc_state.duration_ms); while Instant::now() < end_time { @@ -506,20 +561,20 @@ pub async fn update_button_state_task( }; if let Some(override_state) = &override_stick_state { - let mut overriden_gcc_state = gcc_state; + let mut overriden_gcc_state = controller_state; match override_state.which_stick { Stick::ControlStick => { - overriden_gcc_state.stick_x = override_state.x; - overriden_gcc_state.stick_y = override_state.y; + overriden_gcc_state.stick_state.ax = override_state.x; + overriden_gcc_state.stick_state.ay = override_state.y; } Stick::CStick => { - overriden_gcc_state.cstick_x = override_state.x; - overriden_gcc_state.cstick_y = override_state.y; + overriden_gcc_state.stick_state.cx = override_state.x; + overriden_gcc_state.stick_state.cy = override_state.y; } } gcc_publisher.publish_immediate(overriden_gcc_state); } else { - input_filter.apply_filter(&mut gcc_state); + input_filter.apply_filter(&mut controller_state); if input_consistency_mode == InputConsistencyMode::SuperHack { // transmit state always for the first 5 seconds to give the console time to initialize the controller if initializing && Instant::now().duration_since(init_time) > Duration::from_secs(5) @@ -527,12 +582,12 @@ pub async fn update_button_state_task( initializing = false; } - if gcc_state != previous_state || initializing { - gcc_publisher.publish_immediate(gcc_state); - previous_state = gcc_state; + if controller_state != previous_state || initializing { + gcc_publisher.publish_immediate(controller_state); + previous_state = controller_state; } } else { - gcc_publisher.publish_immediate(gcc_state); + gcc_publisher.publish_immediate(controller_state); } } @@ -621,7 +676,7 @@ pub async fn update_stick_states_task( last_loop_time = n; }; - SIGNAL_STICK_STATE.signal(current_stick_state.clone()); + SIGNAL_STICK_STATE.signal(current_stick_state); yield_now().await; ticker.next().await; diff --git a/src/input_filter.rs b/src/input_filter.rs index 3058a94..a3be235 100644 --- a/src/input_filter.rs +++ b/src/input_filter.rs @@ -2,7 +2,7 @@ use defmt::warn; use crate::{ config::{is_awaitable_button_pressed, AwaitableButtons}, - hid::gcc::GcState, + input::ControllerState, }; /** @@ -14,7 +14,7 @@ use crate::{ */ pub trait InputFilter: Sized { - fn apply_filter(&mut self, gcc_state: &mut GcState); + fn apply_filter(&mut self, controller_state: &mut ControllerState); } /// Presses a single button if another button is pressed. @@ -26,46 +26,44 @@ pub struct SingleButtonMacroFilter { } impl InputFilter for SingleButtonMacroFilter { - fn apply_filter(&mut self, gcc_state: &mut GcState) { - if is_awaitable_button_pressed(gcc_state, &self.btn_instigator) { + fn apply_filter(&mut self, controller_state: &mut ControllerState) { + if is_awaitable_button_pressed(controller_state, &self.btn_instigator) { match self.btn_to_press { AwaitableButtons::A => { - gcc_state.buttons_1.button_a = true; + controller_state.button_a = true; } AwaitableButtons::B => { - gcc_state.buttons_1.button_b = true; + controller_state.button_b = true; } AwaitableButtons::X => { - gcc_state.buttons_1.button_x = true; + controller_state.button_x = true; } AwaitableButtons::Y => { - gcc_state.buttons_1.button_y = true; + controller_state.button_y = true; } AwaitableButtons::L => { - gcc_state.trigger_l = 255; - gcc_state.buttons_2.button_l = true; + controller_state.trigger_l = true; } AwaitableButtons::R => { - gcc_state.trigger_r = 255; - gcc_state.buttons_2.button_r = true; + controller_state.trigger_r = true; } AwaitableButtons::Z => { - gcc_state.buttons_2.button_z = true; + controller_state.trigger_zr = true; } AwaitableButtons::Start => { - gcc_state.buttons_2.button_start = true; + controller_state.button_start = true; } AwaitableButtons::Up => { - gcc_state.buttons_1.dpad_up = true; + controller_state.dpad_up = true; } AwaitableButtons::Down => { - gcc_state.buttons_1.dpad_down = true; + controller_state.dpad_down = true; } AwaitableButtons::Left => { - gcc_state.buttons_1.dpad_left = true; + controller_state.dpad_left = true; } AwaitableButtons::Right => { - gcc_state.buttons_1.dpad_right = true; + controller_state.dpad_right = true; } b => { warn!( @@ -83,22 +81,22 @@ impl InputFilter for SingleButtonMacroFilter { pub struct CStickUpTiltFilter; impl InputFilter for CStickUpTiltFilter { - fn apply_filter(&mut self, gcc_state: &mut GcState) { - if gcc_state.cstick_y > 157 { - if (137..=201).contains(&gcc_state.cstick_x) { - gcc_state.cstick_x = 201; - gcc_state.cstick_y = 255; - } else if (53..=117).contains(&gcc_state.cstick_x) { - gcc_state.cstick_x = 53; - gcc_state.cstick_y = 255; + fn apply_filter(&mut self, controller_state: &mut ControllerState) { + if controller_state.stick_state.cy > 157 { + if (137..=201).contains(&controller_state.stick_state.cx) { + controller_state.stick_state.cx = 201; + controller_state.stick_state.cy = 255; + } else if (53..=117).contains(&controller_state.stick_state.cx) { + controller_state.stick_state.cx = 53; + controller_state.stick_state.cy = 255; } - } else if gcc_state.cstick_y < 97 { - if (137..=221).contains(&gcc_state.cstick_x) { - gcc_state.cstick_x = 221; - gcc_state.cstick_y = 0; - } else if (53..=117).contains(&gcc_state.cstick_x) { - gcc_state.cstick_x = 33; - gcc_state.cstick_y = 0; + } else if controller_state.stick_state.cy < 97 { + if (137..=221).contains(&controller_state.stick_state.cx) { + controller_state.stick_state.cx = 221; + controller_state.stick_state.cy = 0; + } else if (53..=117).contains(&controller_state.stick_state.cx) { + controller_state.stick_state.cx = 33; + controller_state.stick_state.cy = 0; } } } @@ -110,22 +108,22 @@ impl InputFilter for CStickUpTiltFilter { pub struct CStickAngledFTiltFilter; impl InputFilter for CStickAngledFTiltFilter { - fn apply_filter(&mut self, gcc_state: &mut GcState) { - if gcc_state.cstick_y > 147 { - if (147..=225).contains(&gcc_state.cstick_x) { - gcc_state.cstick_x = 205; - gcc_state.cstick_y = 205; - } else if (30..=107).contains(&gcc_state.cstick_x) { - gcc_state.cstick_x = 50; - gcc_state.cstick_y = 205; + fn apply_filter(&mut self, controller_state: &mut ControllerState) { + if controller_state.stick_state.cy > 147 { + if (147..=225).contains(&controller_state.stick_state.cx) { + controller_state.stick_state.cx = 205; + controller_state.stick_state.cy = 205; + } else if (30..=107).contains(&controller_state.stick_state.cx) { + controller_state.stick_state.cx = 50; + controller_state.stick_state.cy = 205; } - } else if gcc_state.cstick_y < 107 { - if (147..=225).contains(&gcc_state.cstick_x) { - gcc_state.cstick_x = 205; - gcc_state.cstick_y = 50; - } else if (30..=107).contains(&gcc_state.cstick_x) { - gcc_state.cstick_x = 50; - gcc_state.cstick_y = 50; + } else if controller_state.stick_state.cy < 107 { + if (147..=225).contains(&controller_state.stick_state.cx) { + controller_state.stick_state.cx = 205; + controller_state.stick_state.cy = 50; + } else if (30..=107).contains(&controller_state.stick_state.cx) { + controller_state.stick_state.cx = 50; + controller_state.stick_state.cy = 50; } } } @@ -136,5 +134,5 @@ impl InputFilter for CStickAngledFTiltFilter { pub struct DummyFilter; impl InputFilter for DummyFilter { - fn apply_filter(&mut self, _gcc_state: &mut GcState) {} + fn apply_filter(&mut self, _gcc_state: &mut ControllerState) {} } diff --git a/src/main.rs b/src/main.rs index dc97008..6abcc03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,6 +89,7 @@ fn main() -> ! { spawner .spawn(update_button_state_task( Input::new(AnyPin::from(p.PIN_20), gpio::Pull::Up), + Input::new(AnyPin::from(p.PIN_15), gpio::Pull::Up), Input::new(AnyPin::from(p.PIN_17), gpio::Pull::Up), Input::new(AnyPin::from(p.PIN_16), gpio::Pull::Up), Input::new(AnyPin::from(p.PIN_11), gpio::Pull::Up), diff --git a/src/usb_comms.rs b/src/usb_comms.rs index e89692d..7a76df1 100644 --- a/src/usb_comms.rs +++ b/src/usb_comms.rs @@ -25,7 +25,7 @@ use libm::powf; use crate::{ config::{ControllerMode, InputConsistencyMode}, hid::{ - gcc::{GcReportBuilder, GcState, GccRequestHandler, GCC_REPORT_DESCRIPTOR}, + gcc::{GcReportBuilder, GccRequestHandler, GCC_REPORT_DESCRIPTOR}, procon::{ProconReportBuilder, ProconRequestHandler, PROCON_REPORT_DESCRIPTOR}, xinput::{ XInputReaderWriter, XInputReportBuilder, XInputRequestHandler, XInputState, @@ -33,7 +33,7 @@ use crate::{ }, HidReaderWriterSplit, UsbReader, UsbWriter, }, - input::CHANNEL_GCC_STATE, + input::{ControllerState, CHANNEL_CONTROLLER_STATE}, }; pub static SIGNAL_RUMBLE: Signal = Signal::new(); @@ -56,7 +56,7 @@ pub static MUTEX_CONTROLLER_MODE: Mutex { - async fn get_hid_report(&mut self, state: &GcState) -> [u8; LEN]; + async fn get_hid_report(&mut self, state: &ControllerState) -> [u8; LEN]; } struct UsbConfig { @@ -216,7 +216,7 @@ where }; let in_fut = async move { - let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap(); + let mut gcc_subscriber = CHANNEL_CONTROLLER_STATE.subscriber().unwrap(); let mut last_report_time = Instant::now(); let mut rate_limit_end_time = Instant::now(); -- 2.51.2 From 16a69723a8549afe05b79da9b92444ceb697d020 Mon Sep 17 00:00:00 2001 From: Naxdy Date: Wed, 5 Feb 2025 17:46:31 +0000 Subject: [PATCH 5/5] perf: force min adc read count for stick coords (#30) Reviewed-on: https://git.naxdy.org/NaxdyOrg/NaxGCC-FW/pulls/30 --- src/input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input.rs b/src/input.rs index cd28a00..6e36e31 100644 --- a/src/input.rs +++ b/src/input.rs @@ -224,7 +224,7 @@ async fn update_stick_states( let loop_end = Instant::now(); - done = loop_end >= end_time - (loop_end - loop_start); + done = loop_end >= end_time - (loop_end - loop_start) && adc_count >= 4; } trace!("ADC Count: {}", adc_count); -- 2.51.2