Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an ir:USER service wrapper and Circle Pad Pro example #86

Merged
merged 46 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
87bbc04
Add a rough ir:USER service wrapper and example
AzureMarker Dec 29, 2022
6c2b184
Got connection working and packets flowing
AzureMarker Jan 2, 2023
ebcb260
Improve packet handling and parse packets
AzureMarker Jan 2, 2023
d1deb60
Fix some result codes not getting recognized as errors
AzureMarker Jan 3, 2023
d798f56
Clean up some of the code and warnings
AzureMarker Jan 3, 2023
5a357dc
Implement ir:USER service shutdown, and do more code cleanup
AzureMarker Jan 3, 2023
39b6bc9
Tidy up the ir:USER code/example and fix a bug in the service code
AzureMarker Jan 3, 2023
8af2c96
More ir:USER service/example tidying and documentation
AzureMarker Jan 5, 2023
9aa866d
Fix some type issues in ir_user service
AzureMarker Jan 14, 2023
d859308
Refactor the ir-user example
AzureMarker Jan 14, 2023
da348b2
Update "known good" nightly version in CI
AzureMarker Jan 14, 2023
6229905
Add a srv module and move wait_for_event to it
AzureMarker Jan 28, 2023
a832cb9
Merge branch 'master' into feature/ir-user
AzureMarker Jan 28, 2023
7f63384
Initialize HID after ir:USER to try and fix New 3DS case
AzureMarker Jan 29, 2023
eb8628d
Make wait_for_event a method on Handle
AzureMarker Jan 29, 2023
86b1820
Derive Default for CirclePadProInputResponse
AzureMarker Jan 29, 2023
c55244d
Merge branch 'master' into feature/ir-user
AzureMarker May 5, 2023
0659df3
Update ir-user example after merge
AzureMarker May 5, 2023
6899f70
Merge branch 'master' of https://github.com/rust3ds/ctru-rs into feat…
Meziu Nov 22, 2023
7e99463
Remove panic handler
Meziu Nov 22, 2023
d5ae641
Fmt
Meziu Nov 22, 2023
47f1f78
Update PR to build with the latest versions
Meziu Nov 22, 2023
44a061a
Merge branch 'master' into feature/ir-user
AzureMarker Dec 23, 2023
e09ad16
Clarify a comment
AzureMarker Dec 23, 2023
87f33f1
Improve some service start/stop error handling
AzureMarker Dec 23, 2023
b4fa7bd
Improve packet parser error handling
AzureMarker Dec 23, 2023
dd9f84f
Improve safety of send_service_request
AzureMarker Dec 23, 2023
051ab26
Add enum for connection status
AzureMarker Dec 23, 2023
09361e5
Try using double buffering to reduce flickering (actually increased :/)
AzureMarker Dec 24, 2023
08b4c37
Fixed file-explorer example (Console usage)
AzureMarker Dec 24, 2023
02126e9
Add missing documentation to ir_user.rs
AzureMarker Dec 24, 2023
ef1c913
Rename ir-user example to ir-user-circle-pad-pro
AzureMarker Dec 24, 2023
1f45da1
Fix double buffering in example via impl Swap and Flush for Console
AzureMarker Dec 24, 2023
964a3d9
Remove Console::with_screen
AzureMarker Dec 26, 2023
323538b
Handle timeout int conversion better
AzureMarker Dec 26, 2023
daf6134
Add missing docs to Error::is_timeout
AzureMarker Dec 26, 2023
c6d8d88
Remove Console S generic by making ConsoleScreen trait alias
AzureMarker Dec 26, 2023
208a917
Add srv::make_ipc_header and use in ir_user
AzureMarker Dec 26, 2023
86c59ee
Simplify some of the syscall code and fix &mut unsafety
AzureMarker Dec 26, 2023
df50702
Rename srv module to svc and add Handle::send_service_request
AzureMarker Dec 26, 2023
979fd50
Simplify some of the packet parsing
AzureMarker Dec 26, 2023
fe0bb51
Update example file documentation
AzureMarker Jan 2, 2024
de1604b
Add ir:rst disable in CPP demo to improve New 3DS compatibility
AzureMarker Jan 2, 2024
1496d47
Move Sealed to separate file
AzureMarker Jan 2, 2024
5233237
Make more IrUser functions take &mut self
AzureMarker Jan 2, 2024
06d26fc
Revert one &mut self
AzureMarker Jan 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
matrix:
toolchain:
# Run against a "known good" nightly
- nightly-2022-07-18
- nightly-2023-01-13
# Check for breakage on latest nightly
- nightly
# But if latest nightly fails, allow the workflow to continue
Expand Down
243 changes: 243 additions & 0 deletions ctru-rs/examples/ir-user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
//! A demo of using the ir:USER service to connect to the Circle Pad Pro.

use ctru::prelude::*;
use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser};
use ctru::services::srv;
use ctru_sys::Handle;
use std::io::Write;
use std::time::Duration;

const PACKET_INFO_SIZE: usize = 8;
const MAX_PACKET_SIZE: usize = 32;
const PACKET_COUNT: usize = 1;
const PACKET_BUFFER_SIZE: usize = PACKET_COUNT * (PACKET_INFO_SIZE + MAX_PACKET_SIZE);
const CPP_CONNECTION_POLLING_PERIOD_MS: u8 = 0x08;
const CPP_POLLING_PERIOD_MS: u8 = 0x32;

fn main() {
ctru::init();
let apt = Apt::init().unwrap();
let hid = Hid::init().unwrap();
let gfx = Gfx::init().unwrap();
let top_console = Console::init(gfx.top_screen.borrow_mut());
let bottom_console = Console::init(gfx.bottom_screen.borrow_mut());

let demo = CirclePadProDemo::new(top_console, bottom_console);
demo.print_status_info();

println!("Press A to connect to the CPP");

let mut is_connected = false;
while apt.main_loop() {
hid.scan_input();

// Check if we need to exit
if hid.keys_held().contains(KeyPad::KEY_START) {
break;
}

// Check if we've received a packet from the circle pad pro
let packet_received =
srv::wait_for_event(demo.receive_packet_event, Duration::ZERO).is_ok();
if packet_received {
demo.handle_packets();
}

// Check if we should start the connection
if hid.keys_down().contains(KeyPad::KEY_A) && !is_connected {
println!("Attempting to connect to the CPP");

match demo.connect_to_cpp(&hid) {
ConnectionResult::Connected => is_connected = true,
ConnectionResult::Canceled => break,
}
}

gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank();
}
}

struct CirclePadProDemo<'screen> {
top_console: Console<'screen>,
bottom_console: Console<'screen>,
ir_user: IrUser,
connection_status_event: Handle,
receive_packet_event: Handle,
}

enum ConnectionResult {
Connected,
Canceled,
}

impl<'screen> CirclePadProDemo<'screen> {
fn new(top_console: Console<'screen>, bottom_console: Console<'screen>) -> Self {
bottom_console.select();
println!("Welcome to the ir:USER / Circle Pad Pro Demo");

println!("Starting up ir:USER service");
let ir_user = IrUser::init(
PACKET_BUFFER_SIZE,
PACKET_COUNT,
PACKET_BUFFER_SIZE,
PACKET_COUNT,
)
.expect("Couldn't initialize ir:USER service");
println!("ir:USER service initialized");

// Get event handles
let connection_status_event = ir_user
.get_connection_status_event()
.expect("Couldn't get ir:USER connection status event");
let receive_packet_event = ir_user
.get_recv_event()
.expect("Couldn't get ir:USER recv event");

Self {
top_console,
bottom_console,
ir_user,
connection_status_event,
receive_packet_event,
}
}

fn print_status_info(&self) {
self.top_console.select();
self.top_console.clear();
println!("{:#x?}", self.ir_user.get_status_info());
self.bottom_console.select();
}

fn connect_to_cpp(&self, hid: &Hid) -> ConnectionResult {
// Connection loop
loop {
hid.scan_input();
if hid.keys_held().contains(KeyPad::KEY_START) {
return ConnectionResult::Canceled;
}

// Start the connection process
self.ir_user
.require_connection(IrDeviceId::CirclePadPro)
.expect("Couldn't initialize circle pad pro connection");

// Wait for the connection to establish
if let Err(e) =
srv::wait_for_event(self.connection_status_event, Duration::from_millis(100))
{
if !e.is_timeout() {
panic!("Couldn't initialize circle pad pro connection: {e}");
}
}

self.print_status_info();
if self.ir_user.get_status_info().connection_status == 2 {
AzureMarker marked this conversation as resolved.
Show resolved Hide resolved
println!("Connected!");
break;
}

// If not connected (ex. timeout), disconnect so we can retry
self.ir_user
.disconnect()
.expect("Failed to disconnect circle pad pro connection");

// Wait for the disconnect to go through
if let Err(e) =
srv::wait_for_event(self.connection_status_event, Duration::from_millis(100))
{
if !e.is_timeout() {
panic!("Couldn't initialize circle pad pro connection: {e}");
}
}
}

// Sending first packet retry loop
loop {
hid.scan_input();
if hid.keys_held().contains(KeyPad::KEY_START) {
return ConnectionResult::Canceled;
}

// Send a request for input to the CPP
if let Err(e) = self
.ir_user
.request_input_polling(CPP_CONNECTION_POLLING_PERIOD_MS)
AzureMarker marked this conversation as resolved.
Show resolved Hide resolved
{
println!("Error: {e:?}");
}
self.print_status_info();

// Wait for the response
let recv_event_result =
srv::wait_for_event(self.receive_packet_event, Duration::from_millis(100));
self.print_status_info();

if recv_event_result.is_ok() {
println!("Got first packet from CPP");
self.handle_packets();
break;
}

// We didn't get a response in time, so loop and retry
}

ConnectionResult::Connected
}

fn handle_packets(&self) {
let packets = self.ir_user.get_packets();
let packet_count = packets.len();
let Some(last_packet) = packets.last() else { return };

// Use a buffer to avoid flickering the screen (write all output at once)
AzureMarker marked this conversation as resolved.
Show resolved Hide resolved
let mut output_buffer = Vec::with_capacity(0x1000);

writeln!(&mut output_buffer, "{:x?}", self.ir_user.get_status_info()).unwrap();

self.ir_user.process_shared_memory(|ir_mem| {
writeln!(&mut output_buffer, "\nReceiveBufferInfo:").unwrap();
write_buffer_as_hex(&ir_mem[0x10..0x20], &mut output_buffer);

writeln!(&mut output_buffer, "\nReceiveBuffer:").unwrap();
write_buffer_as_hex(&ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE], &mut output_buffer);
writeln!(&mut output_buffer).unwrap();
});

writeln!(&mut output_buffer, "\nPacket count: {packet_count}").unwrap();
writeln!(&mut output_buffer, "{last_packet:02x?}").unwrap();

let cpp_response = CirclePadProInputResponse::try_from(last_packet)
.expect("Failed to parse CPP response from IR packet");
writeln!(&mut output_buffer, "\n{cpp_response:#02x?}").unwrap();

// Write output to top screen
self.top_console.select();
self.top_console.clear();
std::io::stdout().write_all(&output_buffer).unwrap();
self.bottom_console.select();

// Done handling the packets, release them
self.ir_user
.release_received_data(packet_count as u32)
.expect("Failed to release ir:USER packet");

// Remind the CPP that we're still listening
if let Err(e) = self.ir_user.request_input_polling(CPP_POLLING_PERIOD_MS) {
println!("Error: {e:?}");
}
}
}

fn write_buffer_as_hex(buffer: &[u8], output: &mut Vec<u8>) {
let mut counter = 0;
for byte in buffer {
write!(output, "{byte:02x} ").unwrap();
counter += 1;
if counter % 16 == 0 {
writeln!(output).unwrap();
}
}
}
9 changes: 8 additions & 1 deletion ctru-rs/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl Try for ResultCode {
}

fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
if self.0 < 0 {
if self.0 != 0 {
ControlFlow::Break(self.into())
} else {
ControlFlow::Continue(())
Expand Down Expand Up @@ -69,6 +69,13 @@ impl Error {
// Copy out of the error string, since it may be changed by other libc calls later
Self::Libc(error_str.to_string_lossy().into())
}

pub fn is_timeout(&self) -> bool {
match *self {
Error::Os(code) => R_DESCRIPTION(code) == ctru_sys::RD_TIMEOUT as ctru_sys::Result,
_ => false,
}
}
}

impl From<ctru_sys::Result> for Error {
Expand Down
Loading