avr-oxide 0.2.1

An extremely simple Rusty operating system for AVR microcontrollers
/* oxide.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Simple supervisor implementation for the AVRoxide runtime.
//!
//! The supervisor acts as an event sink for the device drivers' interrupt
//! service routines.  These service routines send events to the supervisor
//! which queues them for processing.
//!
//! The queued events are then dispatched in the *userland* context to
//! trigger the callback routines you provide via the device drivers.
//!
//! # Usage
//! Obtain a reference to the supervisor using the `avr_oxide::oxide::instance()` method.
//!
//! Create your device driver instances and register any callbacks.
//!
//! To receive events from a device driver, you must tell the supervisor
//! to listen to that device with the `listen()` method.
//!
//! Finally, call the `run()` method on the supervisor to enter the main
//! event handling loop (never returns.)
//!
//! ```rust,no_run
//! #![no_std]
//! #![no_main]
//!
//! use avr_oxide::hal::atmega4809::hardware;
//! use avr_oxide::alloc::boxed::Box;
//! use avr_oxide::devices::UsesPin;
//! use avr_oxide::devices::debouncer::Debouncer;
//! use avr_oxide::devices::{ Handle, OxideLed, OxideButton, OxideSerialPort };
//! use avr_oxide::arduino;
//!
//!
//! #[avr_oxide::main(chip="atmega4809")]
//! pub fn main() {
//!   let supervisor = avr_oxide::oxide::instance();
//!   let hardware = hardware::instance();
//!   let arduino = arduino::nanoevery::Arduino::from(hardware);
//!
//!   let mut green_button = Handle::new(OxideButton::using(Debouncer::with_pin(arduino.a2)));
//!   green_button.on_click(Box::new(move |_pinid, _state|{
//!     // Do some processing when the button is pressed
//!   }));
//!
//!   // Tell the supervisor which devices to listen to
//!   supervisor.listen_handle(green_button);
//!
//!   // Now enter the event loop
//!   supervisor.run();
//! }
//! ```
//!
//! ## Using a custom pre-event handler
//! You can pass a custom pre-handler closure to the `run_with_prehandler()`
//! method.  This will be executed *before* the default event callback process
//! is dispatched, and returns a `bool` indicating if the default event
//! handling process should still be followed.  If this closure returns `false`,
//! the event will be discarded without the event being passed to the device
//! driver.
//!
//! This can be useful if you are using a Watchdog device as a way to kick
//! the watchdog when any event is processed, regardless of source or
//! destination.
//!
//! ```rust,no_run
//! #![no_std]
//! #![no_main]
//!
//! use avr_oxide::hal::atmega4809::hardware;
//! use avr_oxide::alloc::boxed::Box;
//! use avr_oxide::devices::UsesPin;
//! use avr_oxide::devices::debouncer::Debouncer;
//! use avr_oxide::devices::{ Handle, OxideLed, OxideButton, OxideSerialPort };
//! use avr_oxide::arduino;
//! use avr_oxide::event::OxideEvent;
//!
//! #[avr_oxide::main(chip="atmega4809")]
//! pub fn main() {
//!   let supervisor = avr_oxide::oxide::instance();
//!   let hardware = hardware::instance();
//!   let arduino = arduino::nanoevery::Arduino::from(hardware);
//!
//!   let mut green_button = Handle::new(OxideButton::using(Debouncer::with_pin(arduino.a2)));
//!   green_button.on_click(Box::new(move |_pinid, _state|{
//!     // Do some processing when the button is pressed
//!   }));
//!
//!   // Tell the supervisor which devices to listen to
//!   supervisor.listen_handle(green_button);
//!
//!   // Now enter the event loop
//!   supervisor.run_with_prehandler(|event|{
//!     match event {
//!       OxideEvent::Initialise => {
//!         // Do some custom initialisation
//!         true
//!       },
//!       OxideEvent::ClockTick(_timer,_ticks) => {
//!         // Ignore all clocktick events
//!         false
//!       },
//!       _ => {
//!         // Everything else should follow default processing
//!         true
//!       }
//!     }
//!   });
//! }
//! ```

// Imports ===================================================================
use avr_oxide::devices::internal::StaticShareable;
use avr_oxide::devices::Handle;
use avr_oxide::private::ringq::RingQ;
use avr_oxide::event::{OxideEvent, EventSink, EventSource, OxideEventEnvelope};
use avr_oxide::hal::concurrency::interrupt;

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

pub struct OxideSupervisor<'e>
{
  event_q: RingQ<OxideEventEnvelope<'e>, 16>,
}

static mut GLOBAL_INSTANCE : Option<&mut OxideSupervisor> = None;

// Code ======================================================================
pub(crate) fn create_supervisor<'a>() -> OxideSupervisor<'a> {
  OxideSupervisor {
    event_q: RingQ::new_with(OxideEventEnvelope::anon(OxideEvent::Initialise)),
  }
}

pub(crate) unsafe fn set_global_supervisor(global: &mut OxideSupervisor) {
  GLOBAL_INSTANCE.replace(core::mem::transmute(global));
}

pub fn instance() -> &'static mut OxideSupervisor<'static> {
  unsafe {
    match &mut GLOBAL_INSTANCE {
      Some(instance) => {
        *instance
      },
      None => panic!()
    }
  }
}

impl OxideSupervisor<'_> {
  /**
   * Called to have the supervisor listen for events from this device.
   */
  #[cfg(feature="alloc_ftr")]
  pub fn listen_handle<EP: 'static + EventSource + StaticShareable>(&mut self, source: Handle<EP>) {
    Handle::static_deref(&source).listen();
  }

  /**
   * Called to have the supervisor listen for events from this device.
   */
  pub fn listen<EP: 'static + EventSource>(&mut self, source: &'static EP) {
    source.listen();
  }

  /**
   * Enter the event loop - and never return (*evil cackle*).  A pre-handler
   * closure is provided that will be called with the event before the
   * default handling method (a callback to the originator) is executed.  If
   * this prehandler returns `false`, then the standard event handling will
   * not be executed for this event and it will be discarded immediately
   * after the prehandler.
   */
  pub fn run_with_prehandler<F: FnMut(OxideEvent) -> bool>(&mut self, mut pre_handler: F) -> ! {
    loop {
      interrupt::wait();

      // We use the no-locking event Q.  This is safe as long as I am
      // the only consumer, and any producers are running in an interrupt-secure
      // context.
      while let Some(event) = self.event_q.consume_nolock() {
        if pre_handler(event.open_event()) {
          event.invoke_recipient();
        }
      }
    }
  }

  /**
   * Enter the event loop and never return.
   */
  pub fn run(&mut self) -> ! {
    self.run_with_prehandler(|_|{true});
  }
}

impl EventSink for OxideSupervisor<'_> {
  fn event(event: OxideEventEnvelope) {
    unsafe {
      match &mut GLOBAL_INSTANCE {
        None => {}
        Some(supervisor) => {
          let _ = (*supervisor).event_q.append_nolock(core::mem::transmute(event));
        }
      }
    }
  }
}


// Tests =====================================================================