use core::sync::atomic::{AtomicBool, Ordering};
use embassy_futures::select::{Either, select};
use embassy_sync::signal::Signal;
use embassy_time::{Duration, Timer};
use crate::SPLIT_CENTRAL_SLEEP_TIMEOUT_SECONDS;
use crate::event::{SleepStateEvent, publish_event};
pub(crate) static SLEEPING_STATE: AtomicBool = AtomicBool::new(false);
static SLEEP_INPUT: Signal<crate::RawMutex, bool> = Signal::new();
pub(crate) fn report_activity() {
SLEEP_INPUT.signal(false);
}
pub(crate) fn request_sleep() {
SLEEP_INPUT.signal(true);
}
pub(crate) async fn run_sleep_manager() {
if SPLIT_CENTRAL_SLEEP_TIMEOUT_SECONDS == 0 {
info!("Sleep management disabled (timeout = 0)");
core::future::pending::<()>().await;
return;
}
info!(
"Sleep manager started with {}s timeout",
SPLIT_CENTRAL_SLEEP_TIMEOUT_SECONDS
);
manage_sleep_state(Duration::from_secs(SPLIT_CENTRAL_SLEEP_TIMEOUT_SECONDS.into())).await
}
async fn manage_sleep_state(idle_timeout: Duration) -> ! {
loop {
loop {
match select(SLEEP_INPUT.wait(), Timer::after(idle_timeout)).await {
Either::First(true) | Either::Second(_) => break,
Either::First(false) => debug!("Activity detected, resetting sleep timeout"),
}
}
info!("Entering sleep mode");
SLEEPING_STATE.store(true, Ordering::Release);
publish_event(SleepStateEvent::new(true));
while SLEEP_INPUT.wait().await {}
info!("Waking up from sleep mode due to activity");
SLEEPING_STATE.store(false, Ordering::Release);
publish_event(SleepStateEvent::new(false));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::test_block_on as block_on;
fn with_sleep_manager(script: impl core::future::Future<Output = ()>) {
block_on(async {
select(manage_sleep_state(Duration::from_secs(1)), script).await;
});
}
fn sleeping() -> bool {
SLEEPING_STATE.load(Ordering::Acquire)
}
#[test]
fn sleeps_when_idle_and_wakes_on_activity() {
with_sleep_manager(async {
Timer::after_millis(900).await;
assert!(!sleeping(), "still inside the idle timeout");
Timer::after_millis(200).await;
assert!(sleeping(), "idle timeout elapsed");
report_activity();
Timer::after_millis(10).await;
assert!(!sleeping(), "activity wakes the keyboard");
});
}
#[test]
fn activity_restarts_the_idle_timeout() {
with_sleep_manager(async {
for _ in 0..3 {
Timer::after_millis(600).await;
assert!(!sleeping(), "activity must restart the idle timeout");
report_activity();
}
});
}
#[test]
fn sleep_request_skips_the_idle_timeout() {
with_sleep_manager(async {
request_sleep();
Timer::after_millis(10).await;
assert!(sleeping(), "a sleep request doesn't wait for the timeout");
request_sleep();
Timer::after_millis(10).await;
assert!(sleeping(), "a second request while asleep changes nothing");
report_activity();
Timer::after_millis(10).await;
assert!(!sleeping());
});
}
}