#[cfg(target_os = "none")]
use core::sync::atomic::{AtomicBool, Ordering};
#[cfg(target_os = "none")]
use device_envoy_core::button::{__ButtonMonitor, Button};
#[cfg(target_os = "none")]
use embassy_executor::Spawner;
#[cfg(target_os = "none")]
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
#[cfg(target_os = "none")]
use embassy_time::Timer;
#[cfg(target_os = "none")]
use super::{PressDuration, PressedTo};
#[cfg(target_os = "none")]
use crate::{Error, Result};
#[cfg(target_os = "none")]
#[doc(hidden)]
pub struct ButtonWatchStaticEsp {
signal: Signal<CriticalSectionRawMutex, PressDuration>,
state_signal: Signal<CriticalSectionRawMutex, bool>,
state_changed_signal: Signal<CriticalSectionRawMutex, ()>,
initialized_signal: Signal<CriticalSectionRawMutex, ()>,
is_pressed: AtomicBool,
initialized: AtomicBool,
}
#[cfg(target_os = "none")]
impl ButtonWatchStaticEsp {
const fn new() -> Self {
Self {
signal: Signal::new(),
state_signal: Signal::new(),
state_changed_signal: Signal::new(),
initialized_signal: Signal::new(),
is_pressed: AtomicBool::new(false),
initialized: AtomicBool::new(false),
}
}
}
#[cfg(target_os = "none")]
#[doc(hidden)]
pub struct ButtonWatchEsp<'a> {
signal: &'a Signal<CriticalSectionRawMutex, PressDuration>,
state_signal: &'a Signal<CriticalSectionRawMutex, bool>,
state_changed_signal: &'a Signal<CriticalSectionRawMutex, ()>,
initialized_signal: &'a Signal<CriticalSectionRawMutex, ()>,
is_pressed: &'a AtomicBool,
initialized: &'a AtomicBool,
}
#[cfg(target_os = "none")]
impl ButtonWatchEsp<'_> {
#[must_use]
pub const fn new_static() -> ButtonWatchStaticEsp {
ButtonWatchStaticEsp::new()
}
#[must_use = "Must be used to manage the spawned task"]
pub async fn new_from_pin(
button_watch_static: &'static ButtonWatchStaticEsp,
button_pin: impl esp_hal::gpio::InputPin + 'static,
pressed_to: PressedTo,
spawner: Spawner,
) -> Result<Self> {
let pull = match pressed_to {
PressedTo::Voltage => esp_hal::gpio::Pull::Down,
PressedTo::Ground => esp_hal::gpio::Pull::Up,
};
let input = esp_hal::gpio::Input::new(
button_pin,
esp_hal::gpio::InputConfig::default().with_pull(pull),
);
let token = button_watch_task_from_pin(
input,
pressed_to,
&button_watch_static.signal,
&button_watch_static.state_signal,
&button_watch_static.state_changed_signal,
&button_watch_static.initialized_signal,
&button_watch_static.is_pressed,
&button_watch_static.initialized,
);
spawner.spawn(token.map_err(Error::TaskSpawn)?);
let button_watch = Self {
signal: &button_watch_static.signal,
state_signal: &button_watch_static.state_signal,
state_changed_signal: &button_watch_static.state_changed_signal,
initialized_signal: &button_watch_static.initialized_signal,
is_pressed: &button_watch_static.is_pressed,
initialized: &button_watch_static.initialized,
};
button_watch.wait_until_initialized().await;
Ok(button_watch)
}
pub async fn wait_until_initialized(&self) {
if self.initialized.load(Ordering::Acquire) {
return;
}
self.initialized_signal.wait().await;
}
pub async fn wait_for_state_change(&self) {
self.state_changed_signal.wait().await;
}
}
#[cfg(target_os = "none")]
impl __ButtonMonitor for ButtonWatchEsp<'_> {
fn is_pressed_raw(&self) -> bool {
self.is_pressed.load(Ordering::Relaxed)
}
async fn wait_until_pressed_state(&mut self, pressed: bool) {
if self.is_pressed.load(Ordering::Relaxed) == pressed {
return;
}
loop {
let state = self.state_signal.wait().await;
if state == pressed {
return;
}
}
}
}
#[cfg(target_os = "none")]
impl Button for ButtonWatchEsp<'_> {
async fn wait_for_press_duration(&mut self) -> PressDuration {
self.signal.wait().await
}
}
#[embassy_executor::task]
#[cfg(target_os = "none")]
async fn button_watch_task_from_pin(
mut input: esp_hal::gpio::Input<'static>,
pressed_to: PressedTo,
signal: &'static Signal<CriticalSectionRawMutex, PressDuration>,
state_signal: &'static Signal<CriticalSectionRawMutex, bool>,
state_changed_signal: &'static Signal<CriticalSectionRawMutex, ()>,
initialized_signal: &'static Signal<CriticalSectionRawMutex, ()>,
is_pressed: &'static AtomicBool,
initialized: &'static AtomicBool,
) -> ! {
let mut input_button = InputButton {
input: &mut input,
pressed_to,
};
signal_press_durations(
&mut input_button,
signal,
state_signal,
state_changed_signal,
initialized_signal,
is_pressed,
initialized,
)
.await
}
#[cfg(target_os = "none")]
struct InputButton<'a> {
input: &'a mut esp_hal::gpio::Input<'static>,
pressed_to: PressedTo,
}
#[cfg(target_os = "none")]
impl __ButtonMonitor for InputButton<'_> {
fn is_pressed_raw(&self) -> bool {
self.pressed_to.is_pressed(self.input.is_high())
}
async fn wait_until_pressed_state(&mut self, pressed: bool) {
match (pressed, self.pressed_to) {
(true, PressedTo::Voltage) | (false, PressedTo::Ground) => {
self.input.wait_for_high().await;
}
(true, PressedTo::Ground) | (false, PressedTo::Voltage) => {
self.input.wait_for_low().await;
}
}
}
}
#[cfg(target_os = "none")]
async fn signal_press_durations<B: __ButtonMonitor>(
button: &mut B,
signal: &'static Signal<CriticalSectionRawMutex, PressDuration>,
state_signal: &'static Signal<CriticalSectionRawMutex, bool>,
state_changed_signal: &'static Signal<CriticalSectionRawMutex, ()>,
initialized_signal: &'static Signal<CriticalSectionRawMutex, ()>,
is_pressed: &'static AtomicBool,
initialized: &'static AtomicBool,
) -> ! {
let initial_pressed = <B as __ButtonMonitor>::is_pressed_raw(button);
is_pressed.store(initial_pressed, Ordering::Relaxed);
state_signal.signal(initial_pressed);
initialized.store(true, Ordering::Release);
initialized_signal.signal(());
loop {
<B as __ButtonMonitor>::wait_until_pressed_state(button, false).await;
<B as __ButtonMonitor>::wait_until_pressed_state(button, true).await;
is_pressed.store(true, Ordering::Relaxed);
state_signal.signal(true);
state_changed_signal.signal(());
Timer::after(device_envoy_core::button::BUTTON_DEBOUNCE_DELAY).await;
if !<B as __ButtonMonitor>::is_pressed_raw(button) {
is_pressed.store(false, Ordering::Relaxed);
state_signal.signal(false);
state_changed_signal.signal(());
continue;
}
let press_duration = embassy_futures::select::select(
<B as __ButtonMonitor>::wait_until_pressed_state(button, false),
Timer::after(device_envoy_core::button::LONG_PRESS_DURATION),
)
.await;
match press_duration {
embassy_futures::select::Either::First(()) => {
is_pressed.store(false, Ordering::Relaxed);
state_signal.signal(false);
state_changed_signal.signal(());
signal.signal(PressDuration::Short);
}
embassy_futures::select::Either::Second(()) => {
signal.signal(PressDuration::Long);
<B as __ButtonMonitor>::wait_until_pressed_state(button, false).await;
is_pressed.store(false, Ordering::Relaxed);
state_signal.signal(false);
state_changed_signal.signal(());
}
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! button_watch {
($($tt:tt)*) => { $crate::__button_watch_impl! { $($tt)* } };
}
#[doc(hidden)]
#[macro_export]
macro_rules! __button_watch_impl {
(
$(#[$meta:meta])*
$vis:vis $name:ident {
pin: $pin:ident,
}
) => {
$crate::__button_watch_impl! {
@impl
meta: [$(#[$meta])*],
vis: $vis,
name: $name,
pin: $pin
}
};
(
$(#[$meta:meta])*
$name:ident {
pin: $pin:ident,
}
) => {
$crate::__button_watch_impl! {
@impl
meta: [$(#[$meta])*],
vis: ,
name: $name,
pin: $pin
}
};
(
@impl
meta: [$(#[$meta:meta])*],
vis: $vis:vis,
name: $name:ident,
pin: $pin:ident
) => {
::paste::paste! {
$(#[$meta])*
#[doc = concat!(
"Button monitor generated by [`button_watch!`].\n\n",
"Monitors button presses in a background task. ",
"See the [button module documentation](mod@$crate::button) for usage."
)]
$vis struct $name {
button_watch: $crate::button::ButtonWatchEsp<'static>,
}
impl $name {
pub async fn new(
button_pin: $crate::esp_hal::peripherals::$pin<'static>,
pressed_to: $crate::button::PressedTo,
spawner: ::embassy_executor::Spawner,
) -> $crate::Result<&'static mut Self> {
static BUTTON_WATCH_STATIC: $crate::button::ButtonWatchStaticEsp =
$crate::button::ButtonWatchEsp::new_static();
static BUTTON_WATCH_CELL: ::static_cell::StaticCell<$name> =
::static_cell::StaticCell::new();
let button_watch = $crate::button::ButtonWatchEsp::new_from_pin(
&BUTTON_WATCH_STATIC,
button_pin,
pressed_to,
spawner,
)
.await?;
let instance = BUTTON_WATCH_CELL.init($name { button_watch });
Ok(instance)
}
}
impl ::core::ops::Deref for $name {
type Target = $crate::button::ButtonWatchEsp<'static>;
fn deref(&self) -> &Self::Target {
&self.button_watch
}
}
impl $crate::button::__ButtonMonitor for $name {
fn is_pressed_raw(&self) -> bool {
<$crate::button::ButtonWatchEsp<'static> as $crate::button::Button>::is_pressed(
&self.button_watch,
)
}
async fn wait_until_pressed_state(&mut self, pressed: bool) {
<$crate::button::ButtonWatchEsp<'static> as $crate::button::__ButtonMonitor>::wait_until_pressed_state(
&mut self.button_watch,
pressed,
)
.await
}
}
impl $crate::button::Button for $name {
async fn wait_for_press_duration(&mut self) -> $crate::button::PressDuration {
<$crate::button::ButtonWatchEsp<'static> as $crate::button::Button>::wait_for_press_duration(
&mut self.button_watch,
)
.await
}
}
}
};
}