Overview of the Generic Indicator Module

Let’s assume that we’ve been tasked with designing and building part of an IoT project. This project is to build an autonomous wheeled robot that maneuvers its way through an obstacle course as they do in many robotics competitions.

Since we’re working as part of a team and there are hundreds of individual pieces on this robot, we’ve been tasked with handling the Generic Indicator Module System. Since all hardware projects need acronyms, we’ll call this one GIMS. Bonus points for a four-letter acronym, as that puts us just that tiny bit closer to feeling like NASA.

The robot will process multiple streams of sensor inputs from many different devices. GIMS’s job is to allow the sensory input to be fed into a WebAssembly module that can then determine how the current state of some aspect of the robot should be visualized. We might have access to gauges, multi-colored LEDs, headlights that could turn on when it’s dark—any number of amazing devices.

In some cases, the sensory input might already have been massaged a little bit by the more accurate timing of microcontrollers, while our GIMS will be running on a Raspberry Pi at the very heart of a robot. In this chapter, to keep from writing an entire new book on robotics and WebAssembly, we’ll focus solely on the generic indicator system.

The robotics team leadership has built these types of competition robots before, and they know the pain and true price of the integration cost when they have to go back into their code and fuss with tiny details every time they change a piece of hardware. If you haven’t played with microcontrollers, “maker kits,” or Raspberry Pis, you might assume that LEDs are just LEDs—you control one the same way you control another. The truth is far more annoying.

The reality is that you can go from the simplest LED (apply current, it lights up, magic!) to chains of multicolor LEDs that operate with simple timing sequences to systems that use very specific communications protocols like I2C.[31] Changing your peripherals mid-build can be a “stop the world” event, but we can engineer our way around that with a little help from WebAssembly.

With the GIMS design, we’ll be putting the indicator logic—which translates a series of sensor inputs into a series of hardware manipulation commands—into WebAssembly. This way, the indicator logic remains isolated and loosely coupled from the physical indicator(s). If someone changes an LED from a simple light-and-resistor to a brick of 200 “LED pixels,” they should be able to make a small change to an interface layer and leave our indicator relatively unbothered.

In short, we’re taking the software engineering principles of loose coupling and separation of concerns and, with the power of WebAssembly, bringing them to the world of consumer-grade electronics. The first thing we’re going to need to do in order to make that happen is design the contract between the host and the WebAssembly modules.

Designing the Module Contract

As you saw in the chapter on basic JavaScript integration, the contract between a WebAssembly module and its host is a very basic, low-level contract built from numeric primitives. That contract defines how linear memory is accessed, how parameter values can be passed to functions, how we can invoke functions exported from a module, and within the module, invoke functions imported from the host.

Above these low-level bindings, what we need is an API. We need an API that lets the host invoke functions whenever there are new sensor readings available. This API also needs to let the WebAssembly module control the indicator lights. We could even let WebAssembly modules control more hardware like motors and actuators, but that’s outside the scope of our GIMS project (though it certainly could be a lot of fun to explore).

First let’s think about sensor inputs. I’m sure in real-world circumstances, our sensors would have all different kinds of outputs, and some might have more than one value. But knowing that we’re doing this for the Raspberry Pi, and that other microcontrollers closer to the data might be able to massage it for us, it’s safe to assume that we’ll be able to get a decimal value from each sensor whenever a value changes. So our host is going to want to call a function like the one below to inform our wasm module of a new data point:

 fn​ ​sensor_update​(sensor_id: i32, sensor_value: f64) ​->​ f64;

Let’s say the motor speed is sensor 1, the ambient light detector is sensor 2, the collision detector is sensor 3, the battery of our main laser cannon is 20, etc. We’ll have to maintain the Rust-equivalent of a header file so that we can ensure all our modules are operating on the same list of sensors. If the team disagrees on sensor IDs, we’re basically back at square 1 and haven’t fixed any problems.

Another function we want the host to be able to call is apply. If we need to animate or update our display over time, we could probably attempt some kind of intricate threading scheme to run each module, but it’s far easier to use the “game loop” model and just invoke something like apply n times per second. We might be able to do fancier things when threading becomes a part of a future version of the WebAssembly specification, but this is good enough for our needs today.

To let the modules know about the passage of time, we can, however, invoke the same function at fixed intervals and pass a frame value that increases for each call. We can either agree on a frame rate for updates or write our code so it doesn’t really matter:

 fn​ ​apply​(frame: i64);

For example, an animated indicator might have apply called 20 times per second.

That’s it for the input to our modules. Now we need to give the WebAssembly modules a way to control hardware without tightly coupling them to it. For this, we’ll abstract over the notion of setting the color of an individual LED with a function that takes an LED index and 3 RGB values between 0 and 255, like so:

 fn​ ​set_led​(led_index: i32, r: i32, g: i32, b: i32);

If you think back to the fundamentals chapter, recall that while we’re allowed to use plenty of data types privately within the module code, we can’t import and export higher-level data types like structs. Let’s recap and take a look at the three functions in the GIMS API contract (import and export are from the point of view of the WebAssembly module) as shown in the table.

NameDirectionParamsReturns

apply

export

  • frame

None

sensor_update

export

  • sensor_id
  • sensor_value

Value

set_led

import

  • led_index
  • red
  • green
  • blue

None

Now that we’ve got a preliminary contract defined between our hardware host and the wasm modules, we can create a couple of different indicators.

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

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