avr-oxide 0.4.1

An extremely simple Rusty operating system for AVR microcontrollers
/* button.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Simple abstraction of a button attached to a GPIO port.
//!
//! # Usage
//! Create the button using one of the constructors provided by the [`avr_oxide::devices::UsesPin`]
//! trait.
//!
//! The AVR's internal pullup can be enabled/disabled using the
//! `pullup` method.
//!
//! State can be queried using the `is_pressed` or `state` methods, or
//! alternatively a closure can be provided as a callback to be called
//! whenever the button's state changes using the `on_click` method.
//!
//! ```rust,no_run
//! # #![no_std]
//! # #![no_main]
//! #
//! # use avr_oxide::alloc::boxed::Box;
//! # use avr_oxide::devices::UsesPin;
//! # use avr_oxide::devices::debouncer::Debouncer;
//! # use avr_oxide::devices::{ OxideButton, button::ButtonState };
//! # use avr_oxide::boards::board;
//! # use avr_oxide::StaticWrap;
//!
//! #[avr_oxide::main(chip="atmega4809")]
//! pub fn main() {
//!   let supervisor = avr_oxide::oxide::instance();
//!
//!   let mut green_button = StaticWrap::new(OxideButton::using(Debouncer::with_pin(board::pin_a(2))));
//!
//!   green_button.borrow().on_click(Box::new(move |_pinid, state|{
//!     match state {
//!       ButtonState::Released => {
//!         // Do something
//!       }
//!       ButtonState::Pressed => {
//!         // Do something
//!       }
//!       ButtonState::Unknown => {
//!         // Probably don't do anything
//!       }
//!     }
//!   }));
//!
//!   // Tell the supervisor which devices to listen to
//!   supervisor.listen(green_button.borrow());
//!
//!   // Now enter the event loop
//!   supervisor.run();
//! }
//! ```


// Imports ===================================================================
use avr_oxide::hal::generic::port::{Pin, PinMode, InterruptMode, PinIdentity};
use avr_oxide::event::{EventSink, EventSource, OxideEvent, OxideEventEnvelope};
use core::marker::PhantomData;
use core::ops::DerefMut;
use core::cell::{RefCell, UnsafeCell};
use ufmt::derive::uDebug;
use oxide_macros::Persist;
use avr_oxide::hal::generic::callback::IsrCallback;
use avr_oxide::alloc::boxed::Box;
use avr_oxide::devices::UsesPin;
use avr_oxide::panic_if_none;
use avr_oxide::private::binaryringq::BinaryRingQ;
use avr_oxide::util::OwnOrBorrow;

// Declarations ==============================================================

/**
 * Button states passed in Oxide events.
 */
#[derive(PartialEq,Eq,Clone,Copy,uDebug,Persist)]
pub enum ButtonState {
  /// Button was pressed
  Pressed,
  /// Button was released
  Released,
  /// Button is in an unknown state
  Unknown
}

/**
 * Encapsulation of a Button attached to a standard GPIO pin.  Generates Oxide
 * events on button events (press/release.)
 */
pub struct Button<'b, S>
where
  S: EventSink
{
  pin: OwnOrBorrow<'static,dyn Pin>,
  phantom: PhantomData<S>,

  on_click: RefCell<Option<Box<dyn FnMut(PinIdentity,ButtonState)->() + 'b>>>,
  wait_events: UnsafeCell<BinaryRingQ>
}

// Code ======================================================================
impl<'b,S> UsesPin for Button<'b,S>
where
  S: 'static + EventSink
{
  fn using<OP: Into<OwnOrBorrow<'static, dyn Pin>>>(pin: OP) -> Self {
    let pin : OwnOrBorrow<dyn Pin> = pin.into();

    pin.set_mode(PinMode::Input);
    pin.set_interrupt_mode(InterruptMode::BothEdges);
    Button {
      pin,
      phantom: PhantomData::default(),
      on_click: RefCell::new(None),
      wait_events: UnsafeCell::new(BinaryRingQ::new())
    }
  }
}

impl<'b,S> Button<'b,S>
where
  S: 'static + EventSink
{
  /**
   * Have the Oxide supervisor execute the given closure every time an event
   * is generated by this device.
   */
  pub fn on_click(&self, bf: Box<dyn FnMut(PinIdentity, ButtonState) -> () + 'b>) {
    self.on_click.replace(Some(bf));
  }

  /**
   * Enable or disable the internal pullup on this pin
   */
  pub fn pullup(self, enable: bool) -> Self {
    match enable {
      true => self.pin.set_mode(PinMode::InputPullup),
      false => self.pin.set_mode(PinMode::InputFloating)
    }
    self
  }

  /**
   * True if and only if this button is currently pressed
   */
  pub fn is_pressed(&self) -> bool {
    !self.pin.get()
  }

  /**
   * Return the current state of this button.
   */
  pub fn state(&self) -> ButtonState {
    match self.pin.get() {
      true  => ButtonState::Pressed,
      false => ButtonState::Released
    }
  }

  /**
   * Block until the state of this button changes.  The new state is
   * returned.
   */
  pub fn wait_for_change(&self) -> ButtonState {
    match unsafe {
      let wait_events = &mut *self.wait_events.get();

      wait_events.consume_blocking()
    } {
      true  => ButtonState::Pressed,
      false => ButtonState::Released
    }
  }
}

impl<S> EventSource for Button<'_,S>
where
  S: EventSink
{
  fn listen(&'static self) {
    self.pin.listen(IsrCallback::WithData(|isotoken, _source, id, state, udata|{
      // This is executing within the ISR context
      //
      // First, release any threads waiting for an event
      unsafe {
        let myself = panic_if_none!(udata, avr_oxide::oserror::OsError::InternalError) as *const Button<S>;
        let wait_events = &mut *((*myself).wait_events.get());

        let _ = wait_events.append(isotoken, state);

        // Then also send an OxideEvent
        S::event(isotoken, OxideEventEnvelope::to(&*(myself as *const dyn EventSource),
                                                  OxideEvent::ButtonEvent(id,
                                                                          match state {
                                                                            true  => ButtonState::Pressed,
                                                                            false => ButtonState::Released
                                                                          })));
      }
    }, self as *const dyn core::any::Any ))
  }

  fn process_event(&self, evt: OxideEvent) {
    match (self.on_click.borrow_mut().deref_mut(), evt) {
      (Some(f), OxideEvent::ButtonEvent(source, state)) => {
        (*f)(source,state)
      },
      _ => {}
    }
  }
}

unsafe impl <S> Send for Button<'_,S>
where
  S: EventSink
{}

unsafe impl <S> Sync for Button<'_,S>
  where
    S: EventSink
{}