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 either controlled by typing button on a line on stdin (the default) or by clicking the button in the Web UI (with --interface=web). With --interface=stdin (the default), one can also type press and release to control those events independently. Eventually, the number of buttons will be configurable.

The input and output of a host platform with --interface=stdio could look like this (using <- for input and -> for output):

-> Applet running.
<- button
-> 0.580263: Button 0 has been Pressed.
-> 0.580633: Button 0 has been Released.
<- button
-> 3.308765: Button 0 has been Pressed.
-> 3.309006: Button 0 has been Released.
<- press
-> 4.380741: Button 0 has been Pressed.
<- release
-> 5.436308: Button 0 has been Released.

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 LEDs, we need some state to store this information. For each button, we'll store the index of the LED to which it maps to in its handler. We have to use interior mutability (in this case Cell) because the handler is called as &self not &mut self1.

        // We create the state containing the LED to which this button maps to.
        let led_state = 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_state.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_state.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 goes 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 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_state = 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_state.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_state.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 and thus the handler may be reentered. This would essentially copy a mutable reference which is unsound.