Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/jonlamb-gh/air-gradient-pro-rs

Rust bootloader, firmware and CLI tools for the AirGradient PRO
https://github.com/jonlamb-gh/air-gradient-pro-rs

air-gradient bootloader emulation environment-monitoring firmware fota gas-index no-std nox-representation renode renode-run robot-framework rtic rust smoltcp voc-representation

Last synced: about 1 month ago
JSON representation

Rust bootloader, firmware and CLI tools for the AirGradient PRO

Lists

README

        

# air-gradient-pro-rs

Firmware and tools for the [AirGradient PRO](https://www.airgradient.com/open-airgradient/kits/) kit
with some modifications.

The firmware is written in Rust and uses the [RTIC](https://rtic.rs/1/book/en/) framework.

![dashboard](resources/dashboard.png)
![startup](resources/startup.jpg)
![display.jpg](resources/display.jpg)
![prototype.jpg](resources/prototype.jpg)
![agp_renode.png](resources/agp_renode.png)

## Overview

I've modified an AirGradient PRO kit ([PCB Version 3.7](https://www.airgradient.com/open-airgradient/instructions/diy-pro-v37/)) mainly so I can have a wired ethernet connection.

Significant differences from stock:
* Replace the Wemos D1 Mini v4 with an stm32f411 "black pill" board
* Add a ENC28J60 Ethernet board
* Custom bootloader and firmware, written in Rust

## Features

* Bootloader with firmware update and failover rollback mechanisms
- update manager task handles the update protocol in-application
- see the [bootloader README](bootloader/README.md)
* TCP/IP stack ([smoltcp](https://github.com/smoltcp-rs/smoltcp)), comes with these protocols:
- a lightweight [broadcast protocol](libraries/wire-protocols/src/broadcast.rs) for influx/etc integration
- a [device protocol](libraries/wire-protocols/src/device.rs) for FOTA updates, device info, and device control
* CLI with command-line tools and InfluxDB relaying, see the [air-gradient-cli README](host_tools/air-gradient-cli/README.md)
* Configuration for network and device settings
* OLED display
* Emulation support via [Renode](https://renode.io/)

## Configuration

The [build.rs](./build.rs) file handles generating build-time configuration values based
on the github repository and host environment variables.

The following environment variables can be set:
* `AIR_GRADIENT_IP_ADDRESS` : The device's IP address, default is `192.168.1.38`
* `AIR_GRADIENT_MAC_ADDRESS` : The device's MAC address, default is `02:00:04:03:07:02`
* `AIR_GRADIENT_DEVICE_ID` : An arbitrary 16-bit identifier, default is `0xFFFF` (`DeviceId::DEFAULT`)
* `AIR_GRADIENT_BROADCAST_PORT` : The port number to send the broadcast protocol data on, default is `32100`
* `AIR_GRADIENT_BROADCAST_ADDRESS` : The IP address to send the broadcast protocol data to, default is `255.255.255.255`
* `AIR_GRADIENT_DEVICE_PORT` : The port number the device protocol socket listens on, default is `32101`
* `AIR_GRADIENT_LOG` : The max log level filter to use, default is `INFO`

## FOTA Updating

Update files (`agp_images.cpio`) are generated by a custom linker (see its [README](host_tools/agp-linker/README.md))
as part of the build process (`cargo build --release`).

* See the [device update](host_tools/air-gradient-cli/README.md#device-update) section of the
CLI for more information on using the CLI.
* See the [Firmware Update Sequence](bootloader/README.md#update-sequence) section of the
bootloader for more information on performing FOTA updates.
* See the [Design](bootloader/README.md#design) section of the bootloader for more
information on how the update protocol and failover mechanism works.
* See the [Example Update Log](bootloader/README.md#example-update-log) section of the bootloader
for example output from the bootloader and firmware throughout the update process.

```bash
air-gradient device update --address 192.168.1.38 path/to/agp_images.cpio
```

## Flashing

Initial flashing of the bootloader and firmware onto the board is currently done via SWD and an st-link.

The default [memory.x](memory.x) file is setup to use firmware slot 0 in flash, which is also
the default slot picked by the bootloader on initial setup.

You can use the [Development Artifacts](https://github.com/jonlamb-gh/air-gradient-pro-rs/actions/workflows/dev_artifacts.yml)
github action to build a custom-configured bootloader and firmware image in CI (click "Run workflow" and set the configuration fields)
or grab the latest release with the default configuration from the [Releases page](https://github.com/jonlamb-gh/air-gradient-pro-rs/releases).

### Using a github release artifact

1. Install [probe-rs-cli](https://crates.io/crates/probe-rs-cli)
```bash
cargo install probe-rs-cli
```
2. Flash the target using the ELF files
```bash
probe-rs-cli run --chip STM32F411CEUx --protocol swd path/to/bootloader
probe-rs-cli run --chip STM32F411CEUx --protocol swd path/to/air-gradient-pro
```

### Building from source

1. Install [cargo-embed](https://crates.io/crates/cargo-embed) and [flip-link](https://crates.io/crates/flip-link)
```bash
cargo install cargo-embed flip-link
```
2. Build the bootloader and flash the target
```bash
cd bootloader/
cargo embed --release
```
2. Build the firmware and flash the target
```bash
cd firmware/
cargo embed --release
```

Log messages are available on pin PA11 (USART6 Tx), you should see output like the following:

```
************************************************************
agp-bootloader 0.1.0 (release)
Build date: Mon, 24 Apr 2023 14:28:38 +0000
Compiler: rustc 1.69.0 (84c898d65 2023-04-16)
Commit: 3023a001f2ab011406a3e58dd8e328cb4502737a
Reset reason: Software reset
Boot config slot: SLOT0
Update pending: false
Update valid: false
************************************************************
############################################################
air-gradient-pro-rs 0.2.0 (release)
Build date: Mon, 24 Apr 2023 14:35:08 +0000
Compiler: rustc 1.69.0 (84c898d65 2023-04-16)
Commit: 3023a001f2ab011406a3e58dd8e328cb4502737a
Serial number: 303233313036517042018
Device ID: 0x1 (1)
IP address: 192.168.1.38
MAC address: 02-00-04-03-07-02
Broadcast protocol port: 32100
Broadcast protocol address: 255.255.255.255
Device protocol port: 32101
Reset reason: Software reset
Update pending: false
############################################################
Setup: startup delay 5 seconds
Setup: boot config
Setup: S8 LP
Setup: PMS5003
Setup: I2C2
Setup: SH1106
Setup: SHT31
Setup: SGP41
Setup: ETH
Setup: TCP/IP
Setup: net clock timer
Setup: net poll timer
>>> Initialized <<<
```

## Renode Emulation

The default Cargo runner for the bootloader and firmware is set to
[`renode-run`](https://crates.io/crates/renode-run).

1. Install [Renode](https://renode.io/) (currently requires nightly, tested against `1.14.0+20231008gitebcb1b6b`)
2. Install [renode-run](https://crates.io/crates/renode-run)
```bash
cargo install renode-run
```
3. Setup TAP device and networking on the host (requires root)
```bash
sudo ./renode/setup-network.sh
```
4. Configure the environment per the [configuration section](#configuration).
`AIR_GRADIENT_MAC_ADDRESS` is required to match
the renode-run configuration in [Cargo.toml](firmware/Cargo.toml)
```bash
export AIR_GRADIENT_MAC_ADDRESS=02:00:04:03:07:04
export AIR_GRADIENT_IP_ADDRESS=192.0.2.80
```
5. Run the bootloader or the firmware
```bash
cd firmware/
cargo run --release
```
6. Use the [air-gradient CLI](host_tools/air-gradient-cli/README.md) to interact with the system

Alternatively, there's also a [agp.resc](renode/agp.resc) script that can be used to emulate the full system
with the bootloader and the firmware, which can be used to test the firmware update procedure.

```bash
# Assumes the bootloader and firmware ELF files have already been built
renode renode/agp.resc

# Run the 'start' command in the renode console
```

## Robot Framework Tests

The system-level integration tests are written in [Robot Framework](https://robotframework.org) and
use [Renode's integration](https://renode.readthedocs.io/en/latest/introduction/testing.html) to drive it.

1. Install [Renode](https://renode.io/) (currently requires nightly, tested against `1.14.0+20231008gitebcb1b6b`)
2. Install [Robot Framework](https://robotframework.org)
```bash
pip install robotframework==6.0.2
```
3. Setup TAP device and networking on the host (requires root)
```bash
sudo ./renode/setup-network.sh
```
4. Run the tests
```bash
renode-test agp.robot
```

## System Architecture

![system_architecture.png](doc/system_architecture.png)

## RTIC Task and System Timings

NOTE: some of the measurement periods are inaccurate (like the S8 LP), need to read the docs again.
Also, the relative start times are not accurate.

![timing.png](doc/timing.png)

![pms_timing.png](doc/pms_timing.png)

## Hardware

* STM32F411 "black pill"
- [WeActStudio github](https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x1#stm32f411ceu6-core-board)
- [pinout diagram](https://raw.githubusercontent.com/WeActStudio/WeActStudio.MiniSTM32F4x1/master/images/STM32F4x1_PinoutDiagram_RichardBalint.png)
- [refman](https://www.st.com/resource/en/reference_manual/dm00119316-stm32f411xc-e-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf)
- [datasheet](https://www.st.com/resource/en/datasheet/stm32f411ce.pdf)
* ENC28J60 Ethernet board
- [Waveshare wiki](https://www.waveshare.com/wiki/ENC28J60_Ethernet_Board)
- [datasheet](https://www.waveshare.com/w/upload/7/7f/ENC28J60.pdf)
- [errata](https://ww1.microchip.com/downloads/en/DeviceDoc/80349b.pdf)
* AirGradient PRO V3.7 kit
- [kit page](https://www.airgradient.com/open-airgradient/instructions/diy-pro-v37/)
- [schematic](https://www.airgradient.com/images/diy/schematicpro37.png)
- [Arduino code github](https://github.com/airgradienthq/arduino)
- [Factory firmware](https://github.com/airgradienthq/arduino/blob/master/examples/DIY_PRO_V3_7/DIY_PRO_V3_7.ino)
* SH1106 OLED
- [datasheet](https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf)
* Sensirion SHT31 (temperature/humidity sensor)
- [datasheet](https://www.mouser.com/datasheet/2/682/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital-971521.pdf)
* Senseair S8 LP (CO2 sensor)
- [product page](https://senseair.com/products/size-counts/s8-lp/)
- [doc](https://rmtplusstoragesenseair.blob.core.windows.net/docs/publicerat/PSP126.pdf)
- [doc](https://rmtplusstoragesenseair.blob.core.windows.net/docs/Dev/publicerat/TDE2067.pdf)
* PMS5003 (particle concentration sensor)
- [manual](https://www.aqmd.gov/docs/default-source/aq-spec/resources-page/plantower-pms5003-manual_v2-3.pdf)
* Sensirion SGP41 (TVOC/NOx sensor)
- [product page](https://sensirion.com/products/catalog/SGP41/)
- [datasheet](https://www.mouser.com/datasheet/2/682/Sensirion_Gas_Sensors_Datasheet_SGP41-2604356.pdf)
* AMS1117-3.3 regulator
- [Amazon link](https://www.amazon.com/gp/product/B07CP4P5XJ/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1)

### Pins

| Pin | Peripheral | Board D1 Mini Header Pin | Description |
| :--- | :--- | :--- | :--- |
| PA11 | USART6 Tx | TX | Console/logger/panic-handler output |
| PA12 | USART6 Rx | RX | Console input (not used currenlty) |
| PA9 | USART1 Tx | D3 | senseAir S8 Rx |
| PA10 | USART1 Rx | D4 | senseAir S8 Tx |
| PA2 | USART2 Tx | D6 | PMS5003 Rx |
| PA3 | USART2 Rx | D5 | PMS5003 Tx |
| PB3 | I2C2 SDA | D1 | Shared I2C SCL : SH1106, SHT31, SGP41 |
| PB10 | I2C2 SCL | D2 | Shared I2C SDA : SH1106, SHT31, SGP41 |
| PB13 | SPI2 SCK | NC | ENC28J60 Eth SCK |
| PB14 | SPI2 MISO | NC | ENC28J60 Eth MISO |
| PB15 | SPI2 MOSI | NC | ENC28J60 Eth MOSI |
| PB12 | GPIO Output | NC | ENC28J60 Eth CS |
| PA8 | GPIO Input | NC | ENC28J60 Eth INT |
| PB1 | GPIO Output | NC | ENC28J60 Eth RESET |
| PC13 | GPIO Output | NC | On-board LED |

## License

Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.