Creating Indicator Modules

Creating an indicator module is really just a matter of creating a regular Rust-based WebAssembly module that adheres to the contract we’ve defined. You’ve seen how to create wasm modules using Rust a number of times throughout this book, so it should be easy to get started.

To start, create a root directory that will hold a battery indicator, an animated indicator, and the host application. I chose to call my directory gims, but you can choose whatever you like. As a convenience, to allow you to run builds and tests on all subdirectories at once, you can create a new Cargo.toml in the gims directory with the following contents:

 [workspace]
 
 members = [
 "animatedindicator"​,
 "batteryindicator"​,
 "pihost"
 ]

Use cargo new --lib to create the batteryindicator and animatedindicator projects, and cargo new --bin to create the pihost project.

Creating the Battery Indicator

The first indicator module we’re going to build is a battery indicator. Its operation is fairly simple: one of the sensor inputs represents the amount of battery remaining as a percentage. In response to that percentage, we’re going to control the color of a group of eight LEDs.

These LED indicators are each capable of lighting up with colors comprised of RGB components ranging from 0 through 255. The actual hardware used will be a Blinkt! module from Pimoroni, and I’ll include all the details later in case you want to go shopping for your own kits.

Its core logic will be to convert a number from 0-100 into an eight-element array with each element containing an RGB color value—think of it like an LED-based progress bar. For this indicator, we’ll divide the percentages among the LEDs and only light them up if the value is >= the base value for that LED. Figuring out the base value for each LED is simple—divide the eight LEDs by 100 and we get 12.5% per LED.

Update your lib.rs with the following code:

 #​[​derive​(PartialEq, Debug, Clone)]
struct​ ​LedColor​(i32, i32, i32);
 
 const​ SENSOR_BATTERY: i32 = 20;
 
 const​ OFF:LedColor = ​LedColor​(0, 0, 0);
 const​ YELLOW: LedColor = ​LedColor​(255, 255, 0);
 const​ GREEN: LedColor = ​LedColor​(0, 255, 0);
 const​ RED: LedColor = ​LedColor​(255, 0, 0);
 const​ PCT_PER_PIXEL: f64 = 12.5_f64;
 
 
 extern​ ​"C"​ {
fn​ ​set_led​(led_index: i32, r: i32, g: i32, b: i32);
 }
 
 
 #[no_mangle]
pub​ ​extern​ ​"C"​ ​fn​ ​sensor_update​(sensor_id: i32, sensor_value: f64) ​->​ f64 {
 if​ sensor_id == SENSOR_BATTERY {
 set_leds​(​get_led_values​(sensor_value));
  }
  sensor_value
 }
 
 #[no_mangle]
 pub​ ​extern​ ​"C"​ ​fn​ ​apply​(_frame: u32) {
 // NO OP, not an animated indicator
 }
 
 
fn​ ​get_led_values​(battery_remaining: f64) ​->​ [LedColor; 8] {
 let​ ​mut​ arr: [LedColor; 8] = [OFF,OFF,OFF,OFF,OFF,OFF,OFF,OFF,];
 let​ lit = (battery_remaining ​/​ PCT_PER_PIXEL)​.ceil​();
 
 // 0 - 20 : Red
 // 21 - <50 : Yellow
 // 51 - 100 : Green
 
 let​ color = ​if​ 0.0 <= battery_remaining &&
  battery_remaining <= 20.0 {
  RED
  } ​else​ ​if​ battery_remaining > 20.0 && battery_remaining < 50.0 {
  YELLOW
  } ​else​ {
  GREEN
  };
 
 for​ idx in 0..lit ​as​ usize {
  arr[idx] = color​.clone​();
  }
 
  arr
 }
 
fn​ ​set_leds​(values: [LedColor; 8]) {
 for​ x in 0..8 {
 let​ ​LedColor​(r, g, b) = values[x];
 unsafe​ {
 set_led​(x ​as​ i32, r,g,b);
  }
  }
 }

Create a tuple-struct to hold the three-color codes

Import the set_led function from our host

Expose the sensor_update and apply functions to the host

Core logic to convert a percentage into a set of eight color codes

Invoke the unsafe import in a loop to set all the LED colors on the host

With this code in place, we’re going to want to test our module before we plug it into real hardware.

Testing the Battery Indicator

Testing hardware and embedded systems is typically one of the hardest aspects of that kind of development. Pure hardware developers might want to just pull out an oscilloscope and take a look at how the current flows through your system, but this doesn’t help us test our business logic (though it could help integration test the host).

Fortunately for us, we don’t need to physically test the LEDs right now. We can assume that the host works and write unit tests for our business logic that determines which LEDs to light up and what colors to display.

This is where the pluggable modularity of WebAssembly modules starts to truly shine in the embedded and IoT space—finally giving us software developers a way to write unit tests for hardware-bound code without having to rig up elaborate Rube Goldberg machinery to our developer workstations.

This test code (at the bottom of lib.rs) just invokes the get_led_values function for some known percentages and ensures that we get the right color array in response:

 #​[​cfg​(test)]
 mod​ tests {
 
 use​ {OFF, YELLOW, RED, GREEN, get_led_values};
 
  #[test]
 fn​ ​test_0_pct​() {
  assert_eq!(​get_led_values​(0.0),
  [OFF,OFF,OFF,OFF,OFF,OFF,OFF,OFF,]);
  }
 
  #[test]
 fn​ ​test_15_pct​() {
  assert_eq!(​get_led_values​(15.0),
  [RED, RED, OFF, OFF, OFF, OFF, OFF, OFF]);
  }
 
  #[test]
 fn​ ​test_49_pct​() {
  assert_eq!(​get_led_values​(49.0),
  [YELLOW, YELLOW, YELLOW, YELLOW, OFF, OFF, OFF, OFF]);
  }
 
  #[test]
 fn​ ​test_75_pct​() {
  assert_eq!(​get_led_values​(75.0),
  [GREEN,GREEN,GREEN,GREEN,GREEN,GREEN,OFF,OFF,]);
  }
 
  #[test]
 fn​ ​test_100_pct​() {
  assert_eq!(​get_led_values​(100.0),
  [GREEN,GREEN,GREEN,GREEN,GREEN,GREEN,GREEN,GREEN,]);
  }
 }

You should be able to run cargo test from the root gims directory, and your tests should be invoked, showing output that looks like the following:

 running 5 tests
 test tests::test_0_pct ... ok
 test tests::test_100_pct ... ok
 test tests::test_49_pct ... ok
 test tests::test_15_pct ... ok
 test tests::test_75_pct ... ok
 
 test result: ok. 5 passed;​ 0 failed; 0 ignored; 0 measured; 0 filtered out

That’s it for the battery indicator, now let’s move on to an animated indicator.

Creating the Pulsing Indicator

We have plenty of options available for building an animated indicator. Given that our API is going to be calling apply at a fixed frame rate, it makes sense for us to use a key frame[32] animation. Key frame animations are where you define (or interpolate) the values for an animation at given key points or frames.

Depending on your age, you might remember the pulsing light built into the front of the K.I.T.T. Pontiac from the old TV series Knight Rider. If not, then perhaps the pulsing animated visor in the heads of Cylons from Battlestar Galactica is more familiar. Using key frames, this is an incredibly easy animation to create.

Such a “pulser” could represent waiting for a command, an analysis in progress, a pending request to a remote system, or perhaps just to intimidate other robot operators in the competition.

The first thing we want to do is define the key frames. If we think of our eight-light LED strip as an array, then the key frames are actually just the index of the currently lit LED. With each successive frame, the “lit index” will move from left to right, take an additional pause on the far right, and then move back to the left again—reproducing the iconic “pulse” animation from Knight Rider and/or Battlestar Galactica.

You might be shocked by just how little code there is to write:

const​ KEYFRAMES: [i32; 16] = [0,1,2,3,4,5,6,7, 7,6,5,4,3,2,1,0];
 
extern​ ​"C"​ {
 fn​ ​set_led​(led_index: i32, r: i32, g: i32, b: i32);
 }
 
 #[no_mangle]
 pub​ ​extern​ ​"C"​ ​fn​ ​sensor_update​(_sensor_id: i32, _sensor_value: f64) ​->​ f64 {
 // NO-OP, don't really care about sensor values
  0.0
 }
 
 #[no_mangle]
 pub​ ​extern​ ​"C"​ ​fn​ ​apply​(frame: i32) {
 let​ idx = frame % 16;
 
for​ x in 0..8 {
 unsafe​ {
 set_led​(x, 0, 0, 0);
  }
  }
 unsafe​ {
set_led​(KEYFRAMES[idx ​as​ usize], 255, 0, 0);
  }
 }

Define the “lit index” for each of the 16 frames

The exact same imports and exports as the previous module

Ensure that all eight LEDs are dark before we light up the key frame

Call set_led to turn on the light for the key frame

Since we’re guaranteed by the host contract that the frame value will monotonically increase, we just need to grab the modulo 16 of the frame counter to figure out which frame index is lit. We’ll also have to assume that the host will reset the frame counter before doing an overflow. Further, the host can change the speed of the pulser by modifying the frequency of apply calls without us having to modify the WebAssembly module at all.

With our two indicator modules in hand, let’s move on to building the Raspberry Pi host.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.162.51