Blinker
This example demonstrates how to build a simple application that controls the blinking of 4 LEDs and 4 buttons. The user can change the pattern and speed of the blinking.
- B1: changes the pattern,
- B2: changes the speed,
- B3: lights up all the leds while pressed/ regardless of the current state,
- B4: pauses the execution.
Application modeling
Basics
#![allow(unused)] fn main() { pub struct Config { pub pattern: BlinkingPattern, pub speed: BlinkingSpeed, } pub struct State { ticks: usize, /// Tells if we are currently in override where all the leds are lit up all_led_override: bool, is_paused: bool, } pub enum BlinkingPattern { Off, /// Led 1-2-3-4 LeftToRight, /// Led 1-2-4-3 Round, /// 1/3 - 2/4 Column, Cross, } pub enum BlinkingSpeed { Slow, Standard, Fast, } pub struct App { pub config: Config, pub state: State, } }
What is noticeable in this example:
- We use plain Rust struct to define our configuration and state,
- We don't define the actual blinking speed of the leds, only high-level values.
- There are no references to any specific implementation of the hardware.
- An application is the composition of the configuration and the state
Handling Events
There are only two kind of events in this example: button presses and clock ticks. We treat clock ticks as events so we can manipulate time explicitly. That might not suit all designs but in this case it fits perfectly.
Button presses are forwarded to the application through events. Again, we use plain rust facilities to define them and we don't tie them to actual buttons yet.
As described in the principles and concepts
section, this direction is handled by the System trait.
pub enum Event {
ClockTick,
CyclePatternRequest,
CycleSpeedRequest,
/// Lights up all the leds, regardless of the current state and config
OverrideChange(bool),
TogglePause,
}
impl System for App {
type Event = Event;
async fn process_event(&mut self, event: Event) {
match event {
Event::ClockTick => {
if !self.state.is_paused {
self.state.ticks += 1;
}
// perform what is needed at each tick
}
Event::CyclePatternRequest => {
// take the next pattern in the list
}
Event::CycleSpeedRequest => {
// ...
}
Event::OverrideAllLeds(active) => {
// ...
}
Event::TogglePause => {
// ...
}
}
}
}
The actual implementation of each event handling will be described later.
Acting on the hardware
The Controller trait is the inverse of the System trait: it lets the
application acts on the outside world through Action.
A controller is not actually tied to the hardware, it is an entity that can receive orders from the application. This is ideal to write a simulator or an actual hardware implementation.
The blinker app can only do one action: light up the leds in a given pattern.
pub enum BlinkerAction {
LitUpLED(LitPattern),
}
/// Implementation for a hardware board running on a nRF52.
impl<'a> Controller for NRFController<'a> {
type Action = BlinkerAction;
async fn perform(&self, action: Self::Action) {
match action {
core::Action::LitUpLED(pattern) => self.led_control.send(pattern).await,
}
}
}