From d6575c3585f5ac7010220106ddcb0f75866d51fc Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Wed, 4 Sep 2024 14:07:57 +0200 Subject: [PATCH 1/5] WIP PTM service --- ctru-rs/src/services/mod.rs | 1 + ctru-rs/src/services/ptm/mod.rs | 10 +++ ctru-rs/src/services/ptm/user.rs | 114 +++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 ctru-rs/src/services/ptm/mod.rs create mode 100644 ctru-rs/src/services/ptm/user.rs diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index e4ddb449..5638256c 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -22,6 +22,7 @@ pub mod hid; pub mod ir_user; pub mod ndsp; pub mod ps; +pub mod ptm; mod reference; pub mod soc; pub mod sslc; diff --git a/ctru-rs/src/services/ptm/mod.rs b/ctru-rs/src/services/ptm/mod.rs new file mode 100644 index 00000000..82d604c6 --- /dev/null +++ b/ctru-rs/src/services/ptm/mod.rs @@ -0,0 +1,10 @@ +//! Power-Time service. +//! +//! This service manages user information such as registered playtime, step count (using the pedometer) and control to various +//! hardware and features to notify the user during play (such as the Notification/Info LED). +#![doc(alias = "led")] +#![doc(alias = "playtime")] +#![doc(alias = "step")] +#![doc(alias = "power")] + +pub mod user; diff --git a/ctru-rs/src/services/ptm/user.rs b/ctru-rs/src/services/ptm/user.rs new file mode 100644 index 00000000..60d3c6a7 --- /dev/null +++ b/ctru-rs/src/services/ptm/user.rs @@ -0,0 +1,114 @@ +//! PTM User service. +//! +//! This sub-service of the Power-Time service family icludes getters for various hardware/console states, such as whether +//! the console shell is open or the current battery level. + +use std::sync::Mutex; + +use crate::error::ResultCode; +use crate::services::ServiceReference; + +static PTMU_ACTIVE: Mutex<()> = Mutex::new(()); + +/// Whether the console's shell is open or closed. +#[repr(u8)] +pub enum ShellState { + Closed = 0, + Open = 1, +} + +/// Representation of the console's battery charge level. +/// +/// These values correspond to the various states the battery is shown to be in the Home Menu UI. +#[repr(u8)] +pub enum BatteryLevel { + // Battery charge at 0%. System shutdown is imminent. + Drained = 0, + // Battery charge between 5-1% + Critical = 1, + // Battery charge between 10-6% + VeryLow = 2, + // Battery charge between 30-11% + Low = 3, + // Battery charge between 60-31% + Medium = 4, + // Battery charge between 100-61% + High = 5, +} + +/// Handle to the PTM:User service. +pub struct PTMUser { + _service_handler: ServiceReference, +} + +impl PTMUser { + /// Initialize a new service handle. + /// + /// # Errors + /// + /// This function will return an error if the service was unable to be initialized. + /// Since this service requires no special or elevated permissions, errors are rare in practice. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ptm::PTMUser; + /// + /// let ptmu = PTMUser::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ptmuInit")] + pub fn new() -> crate::Result { + let handler = ServiceReference::new( + &PTMU_ACTIVE, + || { + ResultCode(unsafe { ctru_sys::ptmuInit() })?; + + Ok(()) + }, + || unsafe { + ctru_sys::ptmuExit(); + }, + )?; + + Ok(Self { + _service_handler: handler, + }) + } + + /// Returns whether the console's clamshell is closed or open. + pub fn shell_state() -> crate::Result { + let mut state: u8 = 0; + + ResultCode(unsafe { ctru_sys::PTMU_GetShellState(&mut state) })?; + + Ok(state.into()) + } + + /// Returns the console's current battery charge level. + pub fn battery_level() -> crate::Result { + let mut level: u8 = 0; + + ResultCode(unsafe { ctru_sys::PTMU_GetBatteryLevel(&mut level) })?; + + Ok(level.into()) + } + + /// Returns whether the console is currently charging its battery. + pub fn is_charging() -> crate::Result { + let mut charging: u8 = 0; + + ResultCode(unsafe { ctru_sys::PTMU_GetBatteryChargeState(&mut charging) })?; + + Ok(charging) + } +} + +from_impl!(ShellState, u8); +from_impl!(BatteryLevel, u8); From 1b356d4a830dc28d3b39f1b042dddb6546e7f2b5 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Fri, 13 Sep 2024 18:25:04 +0200 Subject: [PATCH 2/5] Complete PTM:User implementation --- ctru-rs/examples/ptm-user.rs | 46 ++++++++ ctru-rs/src/services/ptm/user.rs | 173 +++++++++++++++++++++++++++---- 2 files changed, 201 insertions(+), 18 deletions(-) create mode 100644 ctru-rs/examples/ptm-user.rs diff --git a/ctru-rs/examples/ptm-user.rs b/ctru-rs/examples/ptm-user.rs new file mode 100644 index 00000000..00c2443a --- /dev/null +++ b/ctru-rs/examples/ptm-user.rs @@ -0,0 +1,46 @@ +//! Power-Time Services example. +//! +//! This example shows off common functionality found in the PTM family of system services, like pedometer steps count, battery state reading +//! and some light shows with the notification LED. + +use ctru::prelude::*; +use ctru::services::ptm::user::{BatteryLevel, PTMUser}; + +fn main() { + let apt = Apt::new().unwrap(); + let mut hid = Hid::new().unwrap(); + let gfx = Gfx::new().unwrap(); + let _top_screen = Console::new(gfx.top_screen.borrow_mut()); + + let ptm_user = PTMUser::new().unwrap(); + + // Let's gather some simple data with PTM:User + let battery_level = ptm_user.battery_level().unwrap(); + let charging = ptm_user.is_charging().unwrap(); + let steps = ptm_user.step_count().unwrap(); + + if battery_level >= BatteryLevel::Low { + println!("The battery level is sufficient to play a while.") + } else { + println!("The battery level is low.") + } + + if charging { + println!("The battery is currently charging.") + } else { + println!("The battery is discharging.") + } + + println!("You accumulated a total of {steps} steps."); + + println!("\x1b[29;16HPress Start to exit"); + + while apt.main_loop() { + gfx.wait_for_vblank(); + + hid.scan_input(); + if hid.keys_down().contains(KeyPad::START) { + break; + } + } +} diff --git a/ctru-rs/src/services/ptm/user.rs b/ctru-rs/src/services/ptm/user.rs index 60d3c6a7..890b5fa3 100644 --- a/ctru-rs/src/services/ptm/user.rs +++ b/ctru-rs/src/services/ptm/user.rs @@ -1,19 +1,23 @@ //! PTM User service. //! -//! This sub-service of the Power-Time service family icludes getters for various hardware/console states, such as whether -//! the console shell is open or the current battery level. - +//! This sub-service of the Power-Time service family includes getters for various hardware/console states, such as whether +//! the console [shell is open](PTMUser::shell_state) or the current [battery level](PTMUser::battery_level). +#[doc(alias = "battery")] +#[doc(alias = "shell")] use std::sync::Mutex; -use crate::error::ResultCode; +use crate::error::{Error, Result, ResultCode}; use crate::services::ServiceReference; static PTMU_ACTIVE: Mutex<()> = Mutex::new(()); /// Whether the console's shell is open or closed. #[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum ShellState { + /// Clam shell is currently closed. Closed = 0, + /// Clam shell is currently open. Open = 1, } @@ -21,18 +25,19 @@ pub enum ShellState { /// /// These values correspond to the various states the battery is shown to be in the Home Menu UI. #[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum BatteryLevel { - // Battery charge at 0%. System shutdown is imminent. + /// Battery charge at 0%. System shutdown is imminent. Drained = 0, - // Battery charge between 5-1% + /// Battery charge between 5-1%. Critical = 1, - // Battery charge between 10-6% + /// Battery charge between 10-6%. VeryLow = 2, - // Battery charge between 30-11% + /// Battery charge between 30-11%. Low = 3, - // Battery charge between 60-31% + /// Battery charge between 60-31%. Medium = 4, - // Battery charge between 100-61% + /// Battery charge between 100-61%. High = 5, } @@ -56,7 +61,7 @@ impl PTMUser { /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::ptm::PTMUser; + /// use ctru::services::ptm::user::PTMUser; /// /// let ptmu = PTMUser::new()?; /// # @@ -64,7 +69,7 @@ impl PTMUser { /// # } /// ``` #[doc(alias = "ptmuInit")] - pub fn new() -> crate::Result { + pub fn new() -> Result { let handler = ServiceReference::new( &PTMU_ACTIVE, || { @@ -83,30 +88,162 @@ impl PTMUser { } /// Returns whether the console's clamshell is closed or open. - pub fn shell_state() -> crate::Result { + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ptm::user::{PTMUser, ShellState}; + /// + /// let ptmu = PTMUser::new()?; + /// + /// let state = ptmu.shell_state()?; + /// + /// match state { + /// ShellState::Closed => println!("The shell is closed! How are you able to read this?"), + /// ShellState::Open => println!("The shell is open! That might seem obvious to you."), + /// } + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "PTMU_GetShellState")] + pub fn shell_state(&self) -> Result { let mut state: u8 = 0; ResultCode(unsafe { ctru_sys::PTMU_GetShellState(&mut state) })?; - Ok(state.into()) + state.try_into() } /// Returns the console's current battery charge level. - pub fn battery_level() -> crate::Result { + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ptm::user::{PTMUser, BatteryLevel}; + /// + /// let ptmu = PTMUser::new()?; + /// + /// let charge = ptmu.battery_level()?; + /// + /// if charge <= BatteryLevel::Low { + /// println!("You should put the console to charge!"); + /// } + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "PTMU_GetBatteryLevel")] + pub fn battery_level(&self) -> Result { let mut level: u8 = 0; ResultCode(unsafe { ctru_sys::PTMU_GetBatteryLevel(&mut level) })?; - Ok(level.into()) + level.try_into() } /// Returns whether the console is currently charging its battery. - pub fn is_charging() -> crate::Result { + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ptm::user::PTMUser; + /// + /// let ptmu = PTMUser::new()?; + /// + /// let is_charging = ptmu.is_charging()?; + /// + /// if is_charging { + /// println!("That is one juicy power line."); + /// } + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "PTMU_GetBatteryChargeState")] + pub fn is_charging(&self) -> Result { let mut charging: u8 = 0; ResultCode(unsafe { ctru_sys::PTMU_GetBatteryChargeState(&mut charging) })?; - Ok(charging) + match charging { + 0 => Ok(false), + 1 => Ok(true), + v => Err(Error::Other(format!( + "unexpected charging state value: {v}", + ))), + } + } + + /// Returns the console's total step count. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ptm::user::PTMUser; + /// + /// let ptmu = PTMUser::new()?; + /// + /// let steps = ptmu.step_count()?; + /// + /// println!("You accumulated {steps} steps. Don't stop moving!"); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "PTMU_GetTotalStepCount")] + pub fn step_count(&self) -> Result { + let mut steps: u32 = 0; + + ResultCode(unsafe { ctru_sys::PTMU_GetTotalStepCount(&mut steps) })?; + + Ok(steps) + } +} + +impl TryFrom for ShellState { + type Error = Error; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Closed), + 1 => Ok(Self::Open), + v => Err(Error::Other(format!("unexpected shell state value: {v}",))), + } + } +} + +impl TryFrom for BatteryLevel { + type Error = Error; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Drained), + 1 => Ok(Self::Critical), + 2 => Ok(Self::VeryLow), + 3 => Ok(Self::Low), + 4 => Ok(Self::Medium), + 5 => Ok(Self::High), + v => Err(Error::Other( + format!("unexpected battery level value: {v}",), + )), + } } } From 6e589f3d08fb04b6ff5fbebb50d12f2ea12adb35 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Fri, 13 Sep 2024 18:25:50 +0200 Subject: [PATCH 3/5] Add PTM:SysM implementation --- ctru-rs/src/services/ptm/mod.rs | 1 + ctru-rs/src/services/ptm/sysm.rs | 150 +++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 ctru-rs/src/services/ptm/sysm.rs diff --git a/ctru-rs/src/services/ptm/mod.rs b/ctru-rs/src/services/ptm/mod.rs index 82d604c6..6d561055 100644 --- a/ctru-rs/src/services/ptm/mod.rs +++ b/ctru-rs/src/services/ptm/mod.rs @@ -7,4 +7,5 @@ #![doc(alias = "step")] #![doc(alias = "power")] +pub mod sysm; pub mod user; diff --git a/ctru-rs/src/services/ptm/sysm.rs b/ctru-rs/src/services/ptm/sysm.rs new file mode 100644 index 00000000..71275f25 --- /dev/null +++ b/ctru-rs/src/services/ptm/sysm.rs @@ -0,0 +1,150 @@ +//! PTM SystemMenu service. +//! +//! This sub-service of the Power-Time service family is able to control shutdown/sleep functionality and how those states are +//! communicated to the user (such as via the notification/battery LED). +#[doc(alias = "sleep")] +#[doc(alias = "shutdown")] +#[doc(alias = "led")] +use std::sync::Mutex; +use std::time::Duration; + +use crate::error::{Result, ResultCode}; +use crate::services::ServiceReference; + +static PTMSYSM_ACTIVE: Mutex<()> = Mutex::new(()); + +/// Handle to the PTM:SysM service. +pub struct PTMSysM { + _service_handler: ServiceReference, +} + +impl PTMSysM { + /// Initialize a new service handle. + /// + /// # Errors + /// + /// This function will return an error if the service was unable to be initialized. + /// Since this service requires no special or elevated permissions, errors are rare in practice. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ptm::sysm::PTMSysM; + /// + /// let ptm_sysm = PTMSysM::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ptmSysmInit")] + pub fn new() -> Result { + let handler = ServiceReference::new( + &PTMSYSM_ACTIVE, + || { + ResultCode(unsafe { ctru_sys::ptmSysmInit() })?; + + Ok(()) + }, + || unsafe { + ctru_sys::ptmSysmExit(); + }, + )?; + + Ok(Self { + _service_handler: handler, + }) + } + + /// Try putting the console in sleep mode. + /// + /// # Notes + /// + /// This request can be denied for various reasons. This does not "force" the console to sleep. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ptm::sysm::PTMSysM; + /// use std::time::Duration; + /// + /// let ptm_sysm = PTMSysM::new()?; + /// + /// // Request the activation of sleep mode. + /// ptm_sysm.request_sleep(Duration::from_nanos(0)).unwrap(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "PTMSYSM_RequestSleep")] + pub fn request_sleep(&self) -> Result<()> { + ResultCode(unsafe { ctru_sys::PTMSYSM_RequestSleep() })?; + + Ok(()) + } + + /// Request a system shutdown within the given timeout. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ptm::sysm::PTMSysM; + /// use std::time::Duration; + /// + /// let ptm_sysm = PTMSysM::new()?; + /// + /// // Shutdown the system (usually the request succeeds immediately). + /// ptm_sysm.request_shutdown(Duration::from_nanos(0)).unwrap(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "PTMSYSM_ShutdownAsync")] + pub fn request_shutdown(&self, timeout: Duration) -> Result<()> { + let timeout = timeout.as_nanos() as u64; + + ResultCode(unsafe { ctru_sys::PTMSYSM_ShutdownAsync(timeout) })?; + + Ok(()) + } + + /// Request a system reboot within the given timeout. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ptm::sysm::PTMSysM; + /// use std::time::Duration; + /// + /// let ptm_sysm = PTMSysM::new()?; + /// + /// // Reboot the system. + /// ptm_sysm.request_reboot(Duration::from_nanos(0)).unwrap(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "PTMSYSM_RebootAsync")] + pub fn request_reboot(&self, timeout: Duration) -> Result<()> { + let timeout = timeout.as_nanos() as u64; + + ResultCode(unsafe { ctru_sys::PTMSYSM_RebootAsync(timeout) })?; + + Ok(()) + } +} From 4b81412bda6795e4d11f0d4687caf97d3710023d Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 17 Nov 2024 21:02:13 +0100 Subject: [PATCH 4/5] Fix typo in sysm example --- ctru-rs/src/services/ptm/sysm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/services/ptm/sysm.rs b/ctru-rs/src/services/ptm/sysm.rs index 71275f25..e437c763 100644 --- a/ctru-rs/src/services/ptm/sysm.rs +++ b/ctru-rs/src/services/ptm/sysm.rs @@ -78,7 +78,7 @@ impl PTMSysM { /// let ptm_sysm = PTMSysM::new()?; /// /// // Request the activation of sleep mode. - /// ptm_sysm.request_sleep(Duration::from_nanos(0)).unwrap(); + /// ptm_sysm.request_sleep().unwrap(); /// # /// # Ok(()) /// # } From c40a567b262e6066e59604e2c66aca0b72cbdeae Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Thu, 21 Nov 2024 08:50:33 +0100 Subject: [PATCH 5/5] Bump rustdoc toolchain --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1f39fdab..f14a88b4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,7 +22,7 @@ jobs: - uses: rust3ds/test-runner/setup@v1 with: - toolchain: nightly-2024-02-18 + toolchain: nightly-2024-03-10 - name: Build workspace docs run: cargo 3ds --verbose doc --verbose --no-deps --workspace