Earlier we've seen examples of drogue device using LoRaWAN using an STM32 discovery board. Since then, the hardware support in the community has improved even more.

There are multiple efforts within Rust Embedded to improve LoRaWAN support. This post will walk through the state of the ecosystem and introduce some of the crates being worked on, and some updates from Embassy and Drogue Device.

Improved hardware support

The LoRaWAN capable hardware is dominated by Semtech-based radios. The Sx127x family of radios have been the most common in the development kits, with an SPI interface for accessing the radio state. This radio is well supported in Rust, and there are several crates for accessing it, such as sx127x_lora and radio-sx127x. Drogue uses a variant of the first for its async sx127x driver.

The Sx126x family of radios, which is a more power efficient and has a smaller footprint, seems to be used in newer designs. This is also used in the STM32WL family of chips in a System on Chip (SoC) fashion, using a special SPI peripheral for accessing it. Recently, there have been efforts to add support for these chips in the stm32wl-hal crate, from which the implementation in Embassy is based. The STM32WL chip is also used in the new Generic Node sensor from The Things Industries (TTI). Since Drogue Device is based on Embassy, the radio peripheral can be used with this chip there as well.

There are also standalone Sx126x radios on several development kits, and the sx126x crate supports these standalone radios.

The link layer

Although having radio support in the various HALs are great, that will only get you as far as sending radio packets. To operate in a LoRaWAN network, an implementation of the medium access control (MAC) layer is needed. The rust-lorawan crate implements a LoRaWAN compliant MAC layer, and allows plugging in any radio peripheral. Drogue Device have been using this crate for a long time, with an out of the box integration for sx127x based radios, demonstrated by the STM32 LoRa Discovery Board. The crate(s) are maintained by Ivaylo Petrov and Louis Thiery.

For the new STM32WL-based chips, there are different examples depending on if you use async or not. With the new stm32wl-hal, the lorawan-wl example was created by Jorge Iglesias Garcia, and it integrates the STM32WL radio with rust-lorawan. Thanks to this, with debugging help from Jorge, Drogue Device now also has an example based on the Embassy HAL.

New developments in the link layer is defmt support from Jorge, performance enhancements12 from yours truly, and initial discusisons on introducing an async radio interface, allowing async-await to be used all the way.

Embassy

Embassy aims to be an async runtime for embedded Rust, which we've covered in an earlier post. Since then, the community set out to build a new PAC (Peripheral Access Crate) for STM32, named stm32-metapac, that can be used both by Embassy and other HALs. The PAC relies on generating code for specific chip variants at compile time using cargo features. The code is generated based on a chip definition, which is in turn derived from the STM32 CubeDB XML, C header files and SVD files. Adding support for new chips can be as easy as adding it to a list of chip families that it should support, and if you're lucky, register definitions for the peripherals already exists.

On top of stm32-metapac lies embassy-stm32, which is an async HAL using the generated metapac underneath. This is a "unified" HAL that aims to work with any STM32 chip that can be generated by stm32-metapac. The existing HALsfor STM32 are written per chip family, which cause them to suffer from duplication and a corresponding higher maintenance load. The way the unified approach works is that STM32 peripherals are versioned (SPI v1, SPI v2, GPIO v1 etc.), and reused in different chip families. The HAL therefore only needs to implement access to peripherals of a given version, and will automatically work with multiple chip families that are using that version of the peripheral. This reduces the amount of duplication since features are gated by peripheral versions rather than chip family.

Another enhancement that landed in Embassy is an async MPSC (Multiple Producer Single Consumer) implementation from Christopher Hunt, which has now replaced the channel implementation in Drogue Device as well. This makes for a great communication channel between Embassy tasks.

Drogue Device

Drogue Device have changed slightly to simplify it's Actor model even further. Instead of requiring an Actor to implement both an on_mount, on_start and on_message methods, now only an on_mount method, which is handed an Inbox to pull messages from. This greatly simplifies actor implementations. Lets say you have a shared counter that you want to make an actor for:

pub struct Counter {
    count: u32,
}
pub struct Increment;

In earlier versions you would implement an Actor this way:

impl Actor for Counter {
    type Message<'a> = Increment;
    fn on_mount(&mut self, _: Self::Configuration, _: Address<'static, Self>) { }

    type OnStartFuture<'m> = impl core::future::Future<Output = ()> + 'm;
    fn on_start<'m>(&'m mut self) -> Self::OnStartFuture<'m> {
        async move {
            self.count = 0;
        }
    }

    type OnMessageFuture<'m> = impl core::future::Future<Output = ()> + 'm;
    fn on_message<'m>(&'m mut self, message: Self::Message<'m>) -> Self::OnMessageFuture<'m> {
        async move {
            self.count += 0;
        }
    }
}

After the restructuring, the API is now changed to this:

impl Actor for Counter {
    type Message<'a> = Increment;

    type OnMountFuture<'a> = impl core::future::Future<Output = ()> + 'a;

    fn on_mount<'m, M>(
        &'m mut self,
        _: Self::Configuration,
        _: Address<'static, Self>,
        inbox: &'m mut M,
    ) -> Self::OnMountFuture<'m, M>
    where
        M: Inbox<'m, Self> + 'm;
    {
        async move {
            self.count = 0;
            loop {
                if let Some(m) = inbox.next().await {
                    self.count += 1;
                }
            }
        }
    }
}

This reduces the number of types and methods an actor has to implement, and allows for another use case that wasn't possible before: selecting an action based on awaiting the arrival of a new message or some timeout, which removes the need for using a Timer or Ticker actor for this type of behavior.

New LoRaWAN examples

As mentioned earlier, the set of examples for LoRaWAN have now also expanded:

  • RAK811 - STM32 L1 + Semtech Sx1276 radio
  • STM32WL - STM32WL with builtin Semtech Sx126x radio

These in combination of the existing examples, cover some of the most popular devices used for LoRaWAN sensors.

Future work

Is LoRaWAN support done yet? No :) In an evolving world there are always new hardware and new standards to support. However, it is also time to take a step back and ensure other parts of Drogue Device is also improving.

A LoRaWAN improvement that's in the works is to introduce an async version of the MAC implementation in rust-lorawan, which in turn can define async radio traits that fully make use of the async capabilities in the Embassy HAL.

A WiFi-based workshop is also in the works, which will make use of the STM32L4 IoT Discovery Kit, thereby adding support for the eS-WiFi driver.

Finally, we want to provide more examples using Bluetooth Low Energy (BLE), which is already well supported in Embassy.

Summary

We've seen how the different LoRa radios are improving their support in the Rust ecosystem. Moreover, Embassy and Drogue Device are taking advantage of this and providing new examples.