Browse Source

More ir:USER service/example tidying and documentation

pull/129/head
AzureMarker 2 years ago committed by Mark Drobnak
parent
commit
8af2c96360
No known key found for this signature in database
GPG Key ID: 47A133F3BF9D03D3
  1. 18
      ctru-rs/examples/ir-user.rs
  2. 86
      ctru-rs/src/services/ir_user.rs

18
ctru-rs/examples/ir-user.rs

@ -1,3 +1,5 @@
//! A demo of using the ir:USER service to connect to the Circle Pad Pro.
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser}; use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser};
use std::io::Write; use std::io::Write;
@ -37,6 +39,7 @@ fn main() {
bottom_console.select(); bottom_console.select();
}; };
// Get event handles
let conn_status_event = ir_user let conn_status_event = ir_user
.get_connection_status_event() .get_connection_status_event()
.expect("Couldn't get ir:USER connection status event"); .expect("Couldn't get ir:USER connection status event");
@ -49,6 +52,7 @@ fn main() {
'main_loop: while apt.main_loop() { 'main_loop: while apt.main_loop() {
hid.scan_input(); hid.scan_input();
// Check if we need to exit
if hid.keys_held().contains(KeyPad::KEY_START) { if hid.keys_held().contains(KeyPad::KEY_START) {
break; break;
} }
@ -59,6 +63,7 @@ fn main() {
handle_packet(&ir_user, &top_console, &bottom_console); handle_packet(&ir_user, &top_console, &bottom_console);
} }
// Check if we should start the connection
if hid.keys_down().contains(KeyPad::KEY_A) && !is_connected { if hid.keys_down().contains(KeyPad::KEY_A) && !is_connected {
println!("Attempting to connect to the CPP"); println!("Attempting to connect to the CPP");
@ -69,6 +74,7 @@ fn main() {
break 'main_loop; break 'main_loop;
} }
// Start the connection process
ir_user ir_user
.require_connection(IrDeviceId::CirclePadPro) .require_connection(IrDeviceId::CirclePadPro)
.expect("Couldn't initialize circle pad pro connection"); .expect("Couldn't initialize circle pad pro connection");
@ -83,12 +89,12 @@ fn main() {
} }
print_status_info(); print_status_info();
let status_info = ir_user.get_status_info(); if ir_user.get_status_info().connection_status == 2 {
if status_info.connection_status == 2 {
println!("Connected!"); println!("Connected!");
break; break;
} }
// If not connected (ex. timeout), disconnect so we can retry
ir_user ir_user
.disconnect() .disconnect()
.expect("Failed to disconnect circle pad pro connection"); .expect("Failed to disconnect circle pad pro connection");
@ -110,11 +116,13 @@ fn main() {
break 'main_loop; break 'main_loop;
} }
if let Err(e) = ir_user.start_polling_input(CPP_CONNECTION_POLLING_PERIOD_MS) { // Send a request for input to the CPP
if let Err(e) = ir_user.request_input_polling(CPP_CONNECTION_POLLING_PERIOD_MS) {
println!("Error: {e:?}"); println!("Error: {e:?}");
} }
print_status_info(); print_status_info();
// Wait for the response
let recv_event_result = let recv_event_result =
IrUser::wait_for_event(recv_event, Duration::from_millis(100)); IrUser::wait_for_event(recv_event, Duration::from_millis(100));
print_status_info(); print_status_info();
@ -124,6 +132,8 @@ fn main() {
handle_packet(&ir_user, &top_console, &bottom_console); handle_packet(&ir_user, &top_console, &bottom_console);
break; break;
} }
// We didn't get a response in time, so loop and retry
} }
is_connected = true; is_connected = true;
@ -172,7 +182,7 @@ fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Conso
.expect("Failed to release ir:USER packet"); .expect("Failed to release ir:USER packet");
// Remind the CPP that we're still listening // Remind the CPP that we're still listening
if let Err(e) = ir_user.start_polling_input(CPP_POLLING_PERIOD_MS) { if let Err(e) = ir_user.request_input_polling(CPP_POLLING_PERIOD_MS) {
println!("Error: {e:?}"); println!("Error: {e:?}");
} }
} }

86
ctru-rs/src/services/ir_user.rs

@ -1,5 +1,6 @@
use crate::error::ResultCode; use crate::error::ResultCode;
use crate::services::ServiceReference; use crate::services::ServiceReference;
use crate::Error;
use ctru_sys::{Handle, MEMPERM_READ, MEMPERM_READWRITE}; use ctru_sys::{Handle, MEMPERM_READ, MEMPERM_READWRITE};
use std::alloc::Layout; use std::alloc::Layout;
use std::cmp::max; use std::cmp::max;
@ -11,10 +12,13 @@ use std::time::Duration;
static IR_USER_ACTIVE: Mutex<usize> = Mutex::new(0); static IR_USER_ACTIVE: Mutex<usize> = Mutex::new(0);
static IR_USER_STATE: Mutex<Option<IrUserState>> = Mutex::new(None); static IR_USER_STATE: Mutex<Option<IrUserState>> = Mutex::new(None);
/// The "ir:USER" service. This service is used to talk to IR devices such as
/// the Circle Pad Pro.
pub struct IrUser { pub struct IrUser {
_service_reference: ServiceReference, _service_reference: ServiceReference,
} }
// We need to hold on to some extra service state, hence this struct.
struct IrUserState { struct IrUserState {
service_handle: Handle, service_handle: Handle,
shared_memory_handle: Handle, shared_memory_handle: Handle,
@ -24,6 +28,7 @@ struct IrUserState {
recv_packet_count: usize, recv_packet_count: usize,
} }
// ir:USER syscall command headers
const REQUIRE_CONNECTION_COMMAND_HEADER: u32 = 0x00060040; const REQUIRE_CONNECTION_COMMAND_HEADER: u32 = 0x00060040;
const DISCONNECT_COMMAND_HEADER: u32 = 0x00090000; const DISCONNECT_COMMAND_HEADER: u32 = 0x00090000;
const GET_RECEIVE_EVENT_COMMAND_HEADER: u32 = 0x000A0000; const GET_RECEIVE_EVENT_COMMAND_HEADER: u32 = 0x000A0000;
@ -32,7 +37,18 @@ const SEND_IR_NOP_COMMAND_HEADER: u32 = 0x000D0042;
const INITIALIZE_IRNOP_SHARED_COMMAND_HEADER: u32 = 0x00180182; const INITIALIZE_IRNOP_SHARED_COMMAND_HEADER: u32 = 0x00180182;
const RELEASE_RECEIVED_DATA_COMMAND_HEADER: u32 = 0x00190040; const RELEASE_RECEIVED_DATA_COMMAND_HEADER: u32 = 0x00190040;
// Misc constants
const SHARED_MEM_INFO_SECTIONS_SIZE: usize = 0x30;
const SHARED_MEM_RECV_BUFFER_OFFSET: usize = 0x20;
const PAGE_SIZE: usize = 0x1000;
const IR_BITRATE: u32 = 4;
const PACKET_INFO_SIZE: u32 = 8;
const CIRCLE_PAD_PRO_INPUT_RESPONSE_PACKET_ID: u8 = 0x10;
impl IrUser { impl IrUser {
/// Initialize the ir:USER service. The provided buffer sizes and packet
/// counts are used to calculate the size of shared memory used by the
/// service.
pub fn init( pub fn init(
recv_buffer_size: usize, recv_buffer_size: usize,
recv_packet_count: usize, recv_packet_count: usize,
@ -53,19 +69,13 @@ impl IrUser {
// Calculate the shared memory size. // Calculate the shared memory size.
// Shared memory length must be page aligned. // Shared memory length must be page aligned.
let info_sections_size = 0x30;
let minimum_shared_memory_len = let minimum_shared_memory_len =
info_sections_size + recv_buffer_size + send_buffer_size; SHARED_MEM_INFO_SECTIONS_SIZE + recv_buffer_size + send_buffer_size;
let shared_memory_len = if minimum_shared_memory_len % 0x1000 != 0 { let shared_memory_len = round_up(minimum_shared_memory_len, PAGE_SIZE);
(minimum_shared_memory_len / 0x1000) * 0x1000 + 0x1000
} else {
(minimum_shared_memory_len / 0x1000) * 0x1000
};
assert_eq!(shared_memory_len % 0x1000, 0);
// Allocate the shared memory // Allocate the shared memory
let shared_memory_layout = let shared_memory_layout =
Layout::from_size_align(shared_memory_len, 0x1000).unwrap(); Layout::from_size_align(shared_memory_len, PAGE_SIZE).unwrap();
let shared_memory_ptr = std::alloc::alloc_zeroed(shared_memory_layout); let shared_memory_ptr = std::alloc::alloc_zeroed(shared_memory_layout);
let shared_memory = &*slice_from_raw_parts(shared_memory_ptr, shared_memory_len); let shared_memory = &*slice_from_raw_parts(shared_memory_ptr, shared_memory_len);
@ -87,7 +97,7 @@ impl IrUser {
recv_packet_count: recv_packet_count as u32, recv_packet_count: recv_packet_count as u32,
send_packet_buffer_len: send_buffer_size as u32, send_packet_buffer_len: send_buffer_size as u32,
send_packet_count: send_packet_count as u32, send_packet_count: send_packet_count as u32,
bit_rate: 4, bit_rate: IR_BITRATE,
shared_memory_handle, shared_memory_handle,
})?; })?;
@ -131,6 +141,7 @@ impl IrUser {
}) })
} }
/// Try to connect to the device with the provided ID.
pub fn require_connection(&self, device_id: IrDeviceId) -> crate::Result<()> { pub fn require_connection(&self, device_id: IrDeviceId) -> crate::Result<()> {
self.send_service_request( self.send_service_request(
vec![REQUIRE_CONNECTION_COMMAND_HEADER, device_id.get_id()], vec![REQUIRE_CONNECTION_COMMAND_HEADER, device_id.get_id()],
@ -139,11 +150,13 @@ impl IrUser {
Ok(()) Ok(())
} }
/// Close the current IR connection.
pub fn disconnect(&self) -> crate::Result<()> { pub fn disconnect(&self) -> crate::Result<()> {
self.send_service_request(vec![DISCONNECT_COMMAND_HEADER], 2)?; self.send_service_request(vec![DISCONNECT_COMMAND_HEADER], 2)?;
Ok(()) Ok(())
} }
/// Get an event handle that activates on connection status changes.
pub fn get_connection_status_event(&self) -> crate::Result<Handle> { pub fn get_connection_status_event(&self) -> crate::Result<Handle> {
let response = let response =
self.send_service_request(vec![GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER], 4)?; self.send_service_request(vec![GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER], 4)?;
@ -152,6 +165,7 @@ impl IrUser {
Ok(status_event) Ok(status_event)
} }
/// Get an event handle that activates when a packet is received.
pub fn get_recv_event(&self) -> crate::Result<Handle> { pub fn get_recv_event(&self) -> crate::Result<Handle> {
let response = self.send_service_request(vec![GET_RECEIVE_EVENT_COMMAND_HEADER], 4)?; let response = self.send_service_request(vec![GET_RECEIVE_EVENT_COMMAND_HEADER], 4)?;
let recv_event = response[3] as Handle; let recv_event = response[3] as Handle;
@ -159,6 +173,8 @@ impl IrUser {
Ok(recv_event) Ok(recv_event)
} }
/// Wait for an event to fire. If the timeout is reached, an error is returned. You can use
/// [`Error::is_timeout`] to check if the error is due to a timeout.
pub fn wait_for_event(event: Handle, timeout: Duration) -> crate::Result<()> { pub fn wait_for_event(event: Handle, timeout: Duration) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::svcWaitSynchronization( ResultCode(ctru_sys::svcWaitSynchronization(
@ -169,7 +185,11 @@ impl IrUser {
Ok(()) Ok(())
} }
pub fn start_polling_input(&self, period_ms: u8) -> crate::Result<()> { /// Circle Pad Pro specific request.
///
/// This will send a packet to the CPP requesting it to send back packets
/// with the current device input values.
pub fn request_input_polling(&self, period_ms: u8) -> crate::Result<()> {
let ir_request: [u8; 3] = [1, period_ms, (period_ms + 2) << 2]; let ir_request: [u8; 3] = [1, period_ms, (period_ms + 2) << 2];
self.send_service_request( self.send_service_request(
vec![ vec![
@ -184,11 +204,14 @@ impl IrUser {
Ok(()) Ok(())
} }
/// Mark the last `packet_count` packets as processed, so their memory in
/// the receive buffer can be reused.
pub fn release_received_data(&self, packet_count: u32) -> crate::Result<()> { pub fn release_received_data(&self, packet_count: u32) -> crate::Result<()> {
self.send_service_request(vec![RELEASE_RECEIVED_DATA_COMMAND_HEADER, packet_count], 2)?; self.send_service_request(vec![RELEASE_RECEIVED_DATA_COMMAND_HEADER, packet_count], 2)?;
Ok(()) Ok(())
} }
/// This will let you directly read the ir:USER shared memory via a callback.
pub fn process_shared_memory(&self, process_fn: impl FnOnce(&[u8])) { pub fn process_shared_memory(&self, process_fn: impl FnOnce(&[u8])) {
let shared_mem_guard = IR_USER_STATE.lock().unwrap(); let shared_mem_guard = IR_USER_STATE.lock().unwrap();
let shared_mem = shared_mem_guard.as_ref().unwrap(); let shared_mem = shared_mem_guard.as_ref().unwrap();
@ -196,6 +219,7 @@ impl IrUser {
process_fn(shared_mem.shared_memory); process_fn(shared_mem.shared_memory);
} }
/// Read and parse the ir:USER service status data from shared memory.
pub fn get_status_info(&self) -> IrUserStatusInfo { pub fn get_status_info(&self) -> IrUserStatusInfo {
let shared_mem_guard = IR_USER_STATE.lock().unwrap(); let shared_mem_guard = IR_USER_STATE.lock().unwrap();
let shared_mem = shared_mem_guard.as_ref().unwrap().shared_memory; let shared_mem = shared_mem_guard.as_ref().unwrap().shared_memory;
@ -224,11 +248,13 @@ impl IrUser {
} }
} }
/// Read and parse the current packets received from the IR device.
pub fn get_packets(&self) -> Vec<IrUserPacket> { pub fn get_packets(&self) -> Vec<IrUserPacket> {
let shared_mem_guard = IR_USER_STATE.lock().unwrap(); let shared_mem_guard = IR_USER_STATE.lock().unwrap();
let user_state = shared_mem_guard.as_ref().unwrap(); let user_state = shared_mem_guard.as_ref().unwrap();
let shared_mem = user_state.shared_memory; let shared_mem = user_state.shared_memory;
// Find where the packets are, and how many
let start_index = u32::from_ne_bytes([ let start_index = u32::from_ne_bytes([
shared_mem[0x10], shared_mem[0x10],
shared_mem[0x11], shared_mem[0x11],
@ -242,11 +268,15 @@ impl IrUser {
shared_mem[0x1b], shared_mem[0x1b],
]); ]);
// Parse the packets
(0..valid_packet_count) (0..valid_packet_count)
.map(|i| { .map(|i| {
// Get the packet info
let packet_index = (i + start_index) % user_state.recv_packet_count as u32; let packet_index = (i + start_index) % user_state.recv_packet_count as u32;
let packet_info_offset = 0x20 + (packet_index * 8) as usize; let packet_info_offset =
let packet_info = &shared_mem[packet_info_offset..packet_info_offset + 8]; SHARED_MEM_RECV_BUFFER_OFFSET + (packet_index * PACKET_INFO_SIZE) as usize;
let packet_info =
&shared_mem[packet_info_offset..packet_info_offset + PACKET_INFO_SIZE];
let offset_to_data_buffer = u32::from_ne_bytes([ let offset_to_data_buffer = u32::from_ne_bytes([
packet_info[0], packet_info[0],
@ -261,14 +291,17 @@ impl IrUser {
packet_info[7], packet_info[7],
]) as usize; ]) as usize;
let packet_info_section_size = user_state.recv_packet_count * 8; // Find the packet data. The packet data may wrap around the buffer end, so
// `packet_data` is a function from packet byte offset to value.
let packet_info_section_size = user_state.recv_packet_count * PACKET_INFO_SIZE;
let header_size = SHARED_MEM_RECV_BUFFER_OFFSET + packet_info_section_size;
let data_buffer_size = user_state.recv_buffer_size - packet_info_section_size;
let packet_data = |idx| -> u8 { let packet_data = |idx| -> u8 {
let header_size = 0x20 + packet_info_section_size;
let data_buffer_offset = offset_to_data_buffer + idx; let data_buffer_offset = offset_to_data_buffer + idx;
let data_buffer_size = user_state.recv_buffer_size - packet_info_section_size;
shared_mem[header_size + data_buffer_offset % data_buffer_size] shared_mem[header_size + data_buffer_offset % data_buffer_size]
}; };
// Find out how long the payload is (payload length is variable-length encoded)
let (payload_length, payload_offset) = if packet_data(2) & 0x40 != 0 { let (payload_length, payload_offset) = if packet_data(2) & 0x40 != 0 {
// Big payload // Big payload
( (
@ -280,8 +313,10 @@ impl IrUser {
((packet_data(2) & 0x3F) as usize, 3) ((packet_data(2) & 0x3F) as usize, 3)
}; };
// Check our payload length math against what the packet info contains
assert_eq!(data_length, payload_offset + payload_length + 1); assert_eq!(data_length, payload_offset + payload_length + 1);
// IR packets start with a magic number, so double check it
let magic_number = packet_data(0); let magic_number = packet_data(0);
assert_eq!(magic_number, 0xA5); assert_eq!(magic_number, 0xA5);
@ -298,6 +333,7 @@ impl IrUser {
.collect() .collect()
} }
/// Internal helper for calling ir:USER service methods.
fn send_service_request( fn send_service_request(
&self, &self,
mut request: Vec<u32>, mut request: Vec<u32>,
@ -331,6 +367,15 @@ impl IrUser {
} }
} }
// Internal helper for rounding up a value to a multiple of another value.
fn round_up(value: usize, multiple: usize) -> usize {
if value % multiple != 0 {
(value / multiple) * multiple + multiple
} else {
(value / multiple) * multiple
}
}
struct InitializeIrnopSharedParams { struct InitializeIrnopSharedParams {
ir_user_handle: Handle, ir_user_handle: Handle,
shared_memory_len: u32, shared_memory_len: u32,
@ -342,6 +387,7 @@ struct InitializeIrnopSharedParams {
shared_memory_handle: Handle, shared_memory_handle: Handle,
} }
/// Internal helper for initializing the ir:USER service
unsafe fn initialize_irnop_shared(params: InitializeIrnopSharedParams) -> crate::Result<()> { unsafe fn initialize_irnop_shared(params: InitializeIrnopSharedParams) -> crate::Result<()> {
let cmd_buffer = &mut *(slice_from_raw_parts_mut(ctru_sys::getThreadCommandBuffer(), 9)); let cmd_buffer = &mut *(slice_from_raw_parts_mut(ctru_sys::getThreadCommandBuffer(), 9));
cmd_buffer[0] = INITIALIZE_IRNOP_SHARED_COMMAND_HEADER; cmd_buffer[0] = INITIALIZE_IRNOP_SHARED_COMMAND_HEADER;
@ -363,6 +409,8 @@ unsafe fn initialize_irnop_shared(params: InitializeIrnopSharedParams) -> crate:
Ok(()) Ok(())
} }
/// An enum which represents the different IR devices the 3DS can connect to via
/// the ir:USER service.
pub enum IrDeviceId { pub enum IrDeviceId {
CirclePadPro, CirclePadPro,
// Pretty sure no other IDs are recognized, but just in case // Pretty sure no other IDs are recognized, but just in case
@ -370,6 +418,7 @@ pub enum IrDeviceId {
} }
impl IrDeviceId { impl IrDeviceId {
/// Get the ID of the device.
pub fn get_id(&self) -> u32 { pub fn get_id(&self) -> u32 {
match *self { match *self {
IrDeviceId::CirclePadPro => 1, IrDeviceId::CirclePadPro => 1,
@ -378,6 +427,7 @@ impl IrDeviceId {
} }
} }
/// This struct holds a parsed copy of the ir:USER service status (from shared memory).
#[derive(Debug)] #[derive(Debug)]
pub struct IrUserStatusInfo { pub struct IrUserStatusInfo {
pub recv_err_result: ctru_sys::Result, pub recv_err_result: ctru_sys::Result,
@ -392,6 +442,7 @@ pub struct IrUserStatusInfo {
pub unknown_field_3: u8, pub unknown_field_3: u8,
} }
/// A packet of data sent/received to/from the IR device.
#[derive(Debug)] #[derive(Debug)]
pub struct IrUserPacket { pub struct IrUserPacket {
pub magic_number: u8, pub magic_number: u8,
@ -401,6 +452,7 @@ pub struct IrUserPacket {
pub checksum: u8, pub checksum: u8,
} }
/// Circle Pad Pro response packet holding the current device input signals and status.
#[derive(Debug)] #[derive(Debug)]
pub struct CirclePadProInputResponse { pub struct CirclePadProInputResponse {
pub c_stick_x: u16, pub c_stick_x: u16,
@ -424,7 +476,7 @@ impl TryFrom<&IrUserPacket> for CirclePadProInputResponse {
} }
let response_id = packet.payload[0]; let response_id = packet.payload[0];
if response_id != 0x10 { if response_id != CIRCLE_PAD_PRO_INPUT_RESPONSE_PACKET_ID {
return Err(format!( return Err(format!(
"Invalid response ID (expected 0x10, got {:#x}", "Invalid response ID (expected 0x10, got {:#x}",
packet.payload[0] packet.payload[0]

Loading…
Cancel
Save