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(()) }
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.