Buttons

In this section, we will walk through 2 Rust examples:

  • The button example illustrates stateless usage but lets us introduce how to handle events with callbacks.
  • The led example illustrates stateful usage and thus how to access state in callbacks.

Stateless usage

This example prints to the debug output the new state of a button each time that button changed state and so for all buttons.

Similarly to LEDs, there is a button::count() function to discover the number of buttons available on the board:

    // Make sure there is at least one button.
    let count = button::count();
    assert!(count > 0, "Board has no buttons.");

We want to listen on events for all available buttons, so we loop over all button indices (starting at 0):

    // For each button on the board.
    for index in 0 .. count {

For each button, we define a handler that prints the new button state to the debug output. The handler takes the new button state as argument. Since button::State implements Debug, we simply use {state:?} to print the new state.

        // We define a button handler printing the new state.
        let handler = move |state| debug!("Button {index} has been {state:?}.");

We can now start listening for events. This is done by creating a button::Listener which will call the provided handler each time the button changes state. We specify the button we want to listen to by its index and the handler as a closure.

        // We start listening for state changes with the handler.
        let listener = button::Listener::new(index, handler)?;

A listener continues to listen for events until it is dropped. Since we want to indefinitely listen, we must not drop the listener. A simple way to do that is to leak it. This is equivalent to calling core::mem::forget().

        // We leak the listener to continue listening for events.
        listener.leak();

Finally, we just endlessly wait for callbacks. This step is optional: waiting for callbacks indefinitely is the implicit behavior when main exits. In some way, main can be seen as a callback setup procedure. The scheduling::wait_for_callback() function puts the applet to sleep until a callback is scheduled and scheduled::wait_indefinitely() is just an infinite loop around wait_for_callback().

    // We indefinitely wait for callbacks.
    scheduling::wait_indefinitely();

The final code looks like this:

#![no_std]
wasefire::applet!();

fn main() -> Result<(), Error> {
    // Make sure there is at least one button.
    let count = button::count();
    assert!(count > 0, "Board has no buttons.");

    // For each button on the board.
    for index in 0 .. count {
        // We define a button handler printing the new state.
        let handler = move |state| debug!("Button {index} has been {state:?}.");

        // We start listening for state changes with the handler.
        let listener = button::Listener::new(index, handler)?;

        // We leak the listener to continue listening for events.
        listener.leak();
    }

    // We indefinitely wait for callbacks.
    scheduling::wait_indefinitely();
}

Testing

The host runner currently has 1 button and is controlled by typing button on a line on stdin. Eventually, the number of buttons will be configurable and how they are controlled will be improved (for example through some graphical interface).

Stateful usage

This example combines all the LEDs and buttons available on the board by maintaining a dynamic mapping between them. Initially, all buttons map to the first LED. Each time a button is pressed or released, the LED it is mapped to is toggled. And when a button is released, it maps to the next LED (or the first one if it was mapping to the last one).

In particular:

  • A single button can toggle all LEDs.
  • Multiple buttons can toggle the same LED.
  • A button may stay pressed while another button is pressed.
  • All buttons eventually toggle all LEDs.

We skip over the setup which doesn't illustrate anything new:

    // Make sure there is at least one button.
    let num_buttons = button::count();
    assert!(num_buttons > 0, "Board has no buttons.");

    // Make sure there is at least one LED.
    let num_leds = led::count();
    assert!(num_leds > 0, "Board has no LEDs.");

    // For each button on the board.
    for button_index in 0 .. num_buttons {

Because buttons dynamically map to a LED, we need a state to store this information. We create this state on the heap because we will eventually leak the listener and exit the main function to indefinitely listen for button events. This state simply contains the index of the LED to which this buttons maps to. We have to use Cell because the handler is called as &self (and thus closures must be Fn not FnMut)1.

        // We create the state containing the LED to which this button maps to.
        let led_pointer = Box::new(Cell::new(0));

When defining the button handler, we must move (and thus transfer ownership of) the state we just created to the handler, such that the handler can read and write the state when called.

        // We define the button handler and move the state to there.
        let handler = move |button_state| {

When the handler is called, we first toggle the associated LED:

            // We toggle the LED.
            let led_index = led_pointer.get();
            led::set(led_index, !led::get(led_index));

And then if the button is released, we update the dynamic mapping to point to the next LED (wrapping if needed):

            // If the button is released, we point it to the next LED.
            if matches!(button_state, button::Released) {
                led_pointer.set((led_index + 1) % num_leds);
            }

Finally, we create a button listener with the defined handler. And we leak it to continue listening after it going out of scope and in particular after main returns.

        // We indefinitely listen by creating and leaking a listener.
        button::Listener::new(button_index, handler)?.leak();

The final code looks like this:

#![no_std]
wasefire::applet!();

use alloc::boxed::Box;
use core::cell::Cell;

fn main() -> Result<(), Error> {
    // Make sure there is at least one button.
    let num_buttons = button::count();
    assert!(num_buttons > 0, "Board has no buttons.");

    // Make sure there is at least one LED.
    let num_leds = led::count();
    assert!(num_leds > 0, "Board has no LEDs.");

    // For each button on the board.
    for button_index in 0 .. num_buttons {
        // We create the state containing the LED to which this button maps to.
        let led_pointer = Box::new(Cell::new(0));

        // We define the button handler and move the state to there.
        let handler = move |button_state| {
            // We toggle the LED.
            let led_index = led_pointer.get();
            led::set(led_index, !led::get(led_index));

            // If the button is released, we point it to the next LED.
            if matches!(button_state, button::Released) {
                led_pointer.set((led_index + 1) % num_leds);
            }
        };

        // We indefinitely listen by creating and leaking a listener.
        button::Listener::new(button_index, handler)?.leak();
    }

    Ok(())
}
1

This is because the handler could wait for callbacks itself (which the prelude has no way to know, or is there?) and thus the handler may be reentered. This would essentially copy a mutable reference which is unsound.