avr-oxide 0.4.0

An extremely simple Rusty operating system for AVR microcontrollers
/* event.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Oxide system events; these are generated by the low-level device driver
//! interrupt handlers and passed to the `OxideSupervisor`, which is
//! responsible for then scheduling the appropriate userland code callbacks
//! to handle the events.
//!
//! You will not normally need to deal directly with such events from within
//! an application, rather use the various `on_event()` style callback
//! methods provided by the device drivers.
//!
//! The exception would be when using a pre_handler with [`avr_oxide::oxide::OxideSupervisor`].
//! See the relevant documentation for examples.

// Imports ===================================================================
use avr_oxide::private::ringq::{Coalesce, QueueError};
use avr_oxide::devices::button::ButtonState;
use avr_oxide::devices::serialport::SerialState;
use avr_oxide::hal::generic::timer::TimerIdentity;
use avr_oxide::hal::generic::port::PinIdentity;
use avr_oxide::hal::generic::serial::SerialPortIdentity;
use ufmt::derive::uDebug;
use avr_oxide::concurrency::Isolated;
use oxide_macros::Persist;
use avr_oxide::OxideResult;
use avr_oxide::OxideResult::{Ok,Err};

// Declarations ==============================================================
/**
 * Events that can cause your application to wake up and need to do something.
 */
#[derive(Clone,Copy,PartialEq,Eq,uDebug,Persist)]
pub enum OxideEvent {
  /**
   * Sent when the system starts up to allow the application to process any
   * initialisation tasks it needs.  This is guaranteed to be the first
   * event received by the application program.
   */
  Initialise,

  /**
   * Sent when a clock timer tick interrupt occurs.  The number
   * of ticks is included.  (This will usually be 1, of course, but if
   * events are coalesced by the queue it may be greater.)
   */
  ClockTick(TimerIdentity, u16),

  /**
   * Sent when a button event occurs.  The state of the button when the
   * event was generated is included.
   */
  ButtonEvent(PinIdentity, ButtonState),

  /**
   * Sent when a serial port event occurs.
   */
  SerialEvent(SerialPortIdentity, SerialState)
}

/**
 * An envelope with a recipient address that we can wrap up Oxide events in.
 */
#[derive(Clone,Copy)]
pub struct OxideEventEnvelope<'e> {
  /**
   * What EventSource should process this event
   */
  receiver: Option<&'e dyn EventSource>,

  /**
   * The event itself
   */
  event: OxideEvent
}

/**
 * An event source is something which generates events for later processing.
 */
pub trait EventSource {
  fn listen(&'static self);

  fn process_event(&self, evt: OxideEvent);
}

/**
 * An event sink is something that devices can call to dump their events into.
 */
pub trait EventSink {
  fn event(isotoken: Isolated, event: OxideEventEnvelope);
}

/**
 * A 'null' event sunk that allows you to instantiate Oxide devices that
 * discard any generated events.
 */
pub struct EventDevNull {
}

// Code ======================================================================
impl<'e> OxideEventEnvelope<'e> {
  /**
   * Wrap an event intended for the given EventSource to process
   */
  pub fn to(receiver: &'e dyn EventSource, event: OxideEvent) -> Self {
    OxideEventEnvelope {
      receiver: Some(receiver),
      event
    }
  }

  /**
   * Wrap an event addressed to nobody in particular
   */
  pub fn anon(event: OxideEvent) -> Self {
    OxideEventEnvelope {
      receiver: None,
      event
    }
  }

  /**
   * Return the contents of the envelope (i.e. the event!)
   */
  pub fn open_event(&self) -> OxideEvent {
    self.event
  }

  /**
   * Invoke the recipient's `process_event()` method (or just ignore it
   * if the event is unaddressed.)
   */
  pub(crate) fn invoke_recipient(&self) {
    match self.receiver {
      Some(ev_src) => {
        ev_src.process_event(self.event);
      },
      None => {}
    }
  }
}

impl Coalesce for OxideEvent {
  fn coalesced(&self, with: &Self) -> OxideResult<Self, QueueError> {
    match self {
      // MasterClockEvents we coalesce by adding the ticks together
      OxideEvent::ClockTick(source1, myticks) => {
        match with {
          OxideEvent::ClockTick(source2, ticks) => {
            if (source1 == source2) && (u16::MAX - *myticks) > *ticks {
              Ok(OxideEvent::ClockTick(*source1,*myticks + *ticks))
            } else {
              Err(QueueError::CannotCoalesce)
            }
          },
          _ => Err(QueueError::CannotCoalesce)
        }
      },
      // Serial events we coalesce by just discarding duplicates
      OxideEvent::SerialEvent(source1, state) => {
        match with {
          OxideEvent::SerialEvent(source2, with_state) => {
            if (source1 == source2) && (state == with_state) {
              Ok(self.clone())
            } else {
              Err(QueueError::CannotCoalesce)
            }
          },
          _ => Err(QueueError::CannotCoalesce)
        }
      },
      _ => Err(QueueError::CannotCoalesce)
    }
  }
}

impl Coalesce for OxideEventEnvelope<'_> {
  fn coalesced(&self, with: &Self) -> OxideResult<Self, QueueError> {
    match (self.receiver, with.receiver) {
      (Some(my_receiver),Some(with_receiver)) => {
        if my_receiver as *const _ == with_receiver as *const _ {
          match self.event.coalesced(&with.event) {
            Ok(coalesced_event) => {
              Ok(OxideEventEnvelope {
                receiver: self.receiver,
                event: coalesced_event
              })
            },
            Err(_) => {
              Err(QueueError::CannotCoalesce)
            }
          }
        } else {
          Err(QueueError::CannotCoalesce)
        }
      }
      _=> Err(QueueError::CannotCoalesce)
    }
  }
}

/**
 * Do-nothing implementation of EventSink.
 */
impl EventSink for EventDevNull {
  fn event(_isotoken: Isolated, _event: OxideEventEnvelope) {
  }
}

// Tests =====================================================================
#[cfg(test)]
mod tests {
  use avr_oxide::event::{EventSource, OxideEvent, OxideEventEnvelope};
  use avr_oxide::private::ringq::Coalesce;
  use avr_oxide::event::OxideEvent::{ButtonEvent, ClockTick, SerialEvent};
  use avr_oxide::devices::serialport::SerialState;
  use avr_oxide::devices::button::ButtonState;
  use avr_oxide::hal::generic::timer::TimerIdentity;
  use avr_oxide::hal::generic::serial::SerialPortIdentity;
  use avr_oxide::hal::generic::port::PinIdentity;

  struct DummyEventSource {
  }
  impl EventSource for DummyEventSource {
    fn listen(&'static self) {
      unimplemented!()
    }

    fn process_event(&self, evt: OxideEvent) {
      unimplemented!()
    }
  }

  #[test]
  fn test_event_coalesce_masterclockevent() {
    static DES : DummyEventSource = DummyEventSource {};

    let mut event1 = OxideEventEnvelope::to(&DES, ClockTick(TimerIdentity::Tcb0, 12));
    let event2 = OxideEventEnvelope::to(&DES, ClockTick(TimerIdentity::Tcb0, 14));

    let coalesced = event1.coalesced(&event2).unwrap();

    if let ClockTick(TimerIdentity::Tcb0, ticks) = coalesced.open_event() {
      println!("Coalesced MasterClockEvent (ticks == {})", ticks);
      assert_eq!(ticks, 26);
    }
  }

  #[test]
  fn test_event_coalesce_serialevent_ok() {
    static DES : DummyEventSource = DummyEventSource {};

    let mut event1 = OxideEventEnvelope::to(&DES, SerialEvent(SerialPortIdentity::Usart0, SerialState::ReadAvailable));
    let event2 = OxideEventEnvelope::to(&DES, SerialEvent(SerialPortIdentity::Usart0, SerialState::ReadAvailable));

    event1.coalesced(&event2).unwrap();

    let mut event1 = OxideEventEnvelope::to(&DES, SerialEvent(SerialPortIdentity::Usart0, SerialState::BreakDetected));
    let event2 = OxideEventEnvelope::to(&DES, SerialEvent(SerialPortIdentity::Usart0, SerialState::BreakDetected));

    event1.coalesced(&event2).unwrap();
  }

  #[test]
  #[should_panic]
  fn test_event_coalesce_serialevent_fail() {
    static DES : DummyEventSource = DummyEventSource {};

    let mut event1 = OxideEventEnvelope::to(&DES, SerialEvent(SerialPortIdentity::Usart0, SerialState::ReadAvailable));
    let event2 = OxideEventEnvelope::to(&DES, SerialEvent(SerialPortIdentity::Usart0, SerialState::BreakDetected));

    event1.coalesced(&event2).unwrap();
  }

  #[test]
  #[should_panic]
  fn test_event_coalesce_fail() {
    static DES : DummyEventSource = DummyEventSource {};

    let mut event1 = OxideEventEnvelope::to(&DES, ClockTick(TimerIdentity::Tcb0, 42));
    let event2 = OxideEventEnvelope::to(&DES, ButtonEvent(PinIdentity::PortA(0),ButtonState::Pressed));

   event1.coalesced(&event2).unwrap();
  }
}