{"id":13423333,"url":"https://github.com/jonlamb-gh/air-gradient-pro-rs","last_synced_at":"2025-10-23T22:31:07.313Z","repository":{"id":105862738,"uuid":"598155143","full_name":"jonlamb-gh/air-gradient-pro-rs","owner":"jonlamb-gh","description":"Rust bootloader, firmware and CLI tools for the AirGradient PRO","archived":false,"fork":false,"pushed_at":"2023-10-22T11:07:03.000Z","size":479,"stargazers_count":9,"open_issues_count":13,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-06T20:11:21.956Z","etag":null,"topics":["air-gradient","bootloader","emulation","environment-monitoring","firmware","fota","gas-index","no-std","nox-representation","renode","renode-run","robot-framework","rtic","rust","smoltcp","voc-representation"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonlamb-gh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-02-06T14:18:45.000Z","updated_at":"2024-10-15T01:55:00.000Z","dependencies_parsed_at":"2024-01-12T21:20:05.153Z","dependency_job_id":null,"html_url":"https://github.com/jonlamb-gh/air-gradient-pro-rs","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/jonlamb-gh/air-gradient-pro-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonlamb-gh%2Fair-gradient-pro-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonlamb-gh%2Fair-gradient-pro-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonlamb-gh%2Fair-gradient-pro-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonlamb-gh%2Fair-gradient-pro-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonlamb-gh","download_url":"https://codeload.github.com/jonlamb-gh/air-gradient-pro-rs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonlamb-gh%2Fair-gradient-pro-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280706334,"owners_count":26376965,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-23T02:00:06.710Z","response_time":142,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["air-gradient","bootloader","emulation","environment-monitoring","firmware","fota","gas-index","no-std","nox-representation","renode","renode-run","robot-framework","rtic","rust","smoltcp","voc-representation"],"created_at":"2024-07-31T00:00:31.152Z","updated_at":"2025-10-23T22:31:02.305Z","avatar_url":"https://github.com/jonlamb-gh.png","language":"Rust","funding_links":[],"categories":["Firmware projects"],"sub_categories":["WIP"],"readme":"# air-gradient-pro-rs\n\nFirmware and tools for the [AirGradient PRO](https://www.airgradient.com/open-airgradient/kits/) kit\nwith some modifications.\n\nThe firmware is written in Rust and uses the [RTIC](https://rtic.rs/1/book/en/) framework.\n\n![dashboard](resources/dashboard.png)\n![startup](resources/startup.jpg)\n![display.jpg](resources/display.jpg)\n![prototype.jpg](resources/prototype.jpg)\n![agp_renode.png](resources/agp_renode.png)\n\n## Overview\n\nI'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.\n\nSignificant differences from stock:\n* Replace the Wemos D1 Mini v4 with an stm32f411 \"black pill\" board\n* Add a ENC28J60 Ethernet board\n* Custom bootloader and firmware, written in Rust\n\n## Features\n\n* Bootloader with firmware update and failover rollback mechanisms\n  - update manager task handles the update protocol in-application\n  - see the [bootloader README](bootloader/README.md)\n* TCP/IP stack ([smoltcp](https://github.com/smoltcp-rs/smoltcp)), comes with these protocols:\n  - a lightweight [broadcast protocol](libraries/wire-protocols/src/broadcast.rs) for influx/etc integration\n  - a [device protocol](libraries/wire-protocols/src/device.rs) for FOTA updates, device info, and device control\n* CLI with command-line tools and InfluxDB relaying, see the [air-gradient-cli README](host_tools/air-gradient-cli/README.md)\n* Configuration for network and device settings\n* OLED display\n* Emulation support via [Renode](https://renode.io/)\n\n## Configuration\n\nThe [build.rs](./build.rs) file handles generating build-time configuration values based\non the github repository and host environment variables.\n\nThe following environment variables can be set:\n* `AIR_GRADIENT_IP_ADDRESS` : The device's IP address, default is `192.168.1.38`\n* `AIR_GRADIENT_MAC_ADDRESS` : The device's MAC address, default is `02:00:04:03:07:02`\n* `AIR_GRADIENT_DEVICE_ID` : An arbitrary 16-bit identifier, default is `0xFFFF` (`DeviceId::DEFAULT`)\n* `AIR_GRADIENT_BROADCAST_PORT` : The port number to send the broadcast protocol data on, default is `32100`\n* `AIR_GRADIENT_BROADCAST_ADDRESS` : The IP address to send the broadcast protocol data to, default is `255.255.255.255`\n* `AIR_GRADIENT_DEVICE_PORT` : The port number the device protocol socket listens on, default is `32101`\n* `AIR_GRADIENT_LOG` : The max log level filter to use, default is `INFO`\n\n## FOTA Updating\n\nUpdate files (`agp_images.cpio`) are generated by a custom linker (see its [README](host_tools/agp-linker/README.md))\nas part of the build process (`cargo build --release`).\n\n* See the [device update](host_tools/air-gradient-cli/README.md#device-update) section of the\n  CLI for more information on using the CLI.\n* See the [Firmware Update Sequence](bootloader/README.md#update-sequence) section of the\n  bootloader for more information on performing FOTA updates.\n* See the [Design](bootloader/README.md#design) section of the bootloader for more\n  information on how the update protocol and failover mechanism works.\n* See the [Example Update Log](bootloader/README.md#example-update-log) section of the bootloader\n  for example output from the bootloader and firmware throughout the update process.\n\n```bash\nair-gradient device update --address 192.168.1.38 path/to/agp_images.cpio\n```\n\n## Flashing\n\nInitial flashing of the bootloader and firmware onto the board is currently done via SWD and an st-link.\n\nThe default [memory.x](memory.x) file is setup to use firmware slot 0 in flash, which is also\nthe default slot picked by the bootloader on initial setup.\n\nYou can use the [Development Artifacts](https://github.com/jonlamb-gh/air-gradient-pro-rs/actions/workflows/dev_artifacts.yml)\ngithub action to build a custom-configured bootloader and firmware image in CI (click \"Run workflow\" and set the configuration fields)\nor grab the latest release with the default configuration from the [Releases page](https://github.com/jonlamb-gh/air-gradient-pro-rs/releases).\n\n### Using a github release artifact\n\n1. Install [probe-rs-cli](https://crates.io/crates/probe-rs-cli)\n  ```bash\n  cargo install probe-rs-cli\n  ```\n2. Flash the target using the ELF files\n  ```bash\n  probe-rs-cli run --chip STM32F411CEUx --protocol swd path/to/bootloader\n  probe-rs-cli run --chip STM32F411CEUx --protocol swd path/to/air-gradient-pro\n  ```\n\n### Building from source\n\n1. Install [cargo-embed](https://crates.io/crates/cargo-embed) and [flip-link](https://crates.io/crates/flip-link)\n  ```bash\n  cargo install cargo-embed flip-link\n  ```\n2. Build the bootloader and flash the target\n  ```bash\n  cd bootloader/\n  cargo embed --release\n  ```\n2. Build the firmware and flash the target\n  ```bash\n  cd firmware/\n  cargo embed --release\n  ```\n\nLog messages are available on pin PA11 (USART6 Tx), you should see output like the following:\n\n```\n************************************************************\nagp-bootloader 0.1.0 (release)\nBuild date: Mon, 24 Apr 2023 14:28:38 +0000\nCompiler: rustc 1.69.0 (84c898d65 2023-04-16)\nCommit: 3023a001f2ab011406a3e58dd8e328cb4502737a\nReset reason: Software reset\nBoot config slot: SLOT0\nUpdate pending: false\nUpdate valid: false\n************************************************************\n############################################################\nair-gradient-pro-rs 0.2.0 (release)\nBuild date: Mon, 24 Apr 2023 14:35:08 +0000\nCompiler: rustc 1.69.0 (84c898d65 2023-04-16)\nCommit: 3023a001f2ab011406a3e58dd8e328cb4502737a\nSerial number: 303233313036517042018\nDevice ID: 0x1 (1)\nIP address: 192.168.1.38\nMAC address: 02-00-04-03-07-02\nBroadcast protocol port: 32100\nBroadcast protocol address: 255.255.255.255\nDevice protocol port: 32101\nReset reason: Software reset\nUpdate pending: false\n############################################################\nSetup: startup delay 5 seconds\nSetup: boot config\nSetup: S8 LP\nSetup: PMS5003\nSetup: I2C2\nSetup: SH1106\nSetup: SHT31\nSetup: SGP41\nSetup: ETH\nSetup: TCP/IP\nSetup: net clock timer\nSetup: net poll timer\n\u003e\u003e\u003e Initialized \u003c\u003c\u003c\n```\n\n## Renode Emulation\n\nThe default Cargo runner for the bootloader and firmware is set to\n[`renode-run`](https://crates.io/crates/renode-run).\n\n1. Install [Renode](https://renode.io/) (currently requires nightly, tested against `1.14.0+20231008gitebcb1b6b`)\n2. Install [renode-run](https://crates.io/crates/renode-run)\n  ```bash\n  cargo install renode-run\n  ```\n3. Setup TAP device and networking on the host (requires root)\n  ```bash\n  sudo ./renode/setup-network.sh\n  ```\n4. Configure the environment per the [configuration section](#configuration).\n   `AIR_GRADIENT_MAC_ADDRESS` is required to match\n   the renode-run configuration in [Cargo.toml](firmware/Cargo.toml)\n  ```bash\n  export AIR_GRADIENT_MAC_ADDRESS=02:00:04:03:07:04\n  export AIR_GRADIENT_IP_ADDRESS=192.0.2.80\n  ```\n5. Run the bootloader or the firmware\n  ```bash\n  cd firmware/\n  cargo run --release\n  ```\n6. Use the [air-gradient CLI](host_tools/air-gradient-cli/README.md) to interact with the system\n\nAlternatively, there's also a [agp.resc](renode/agp.resc) script that can be used to emulate the full system\nwith the bootloader and the firmware, which can be used to test the firmware update procedure.\n\n```bash\n# Assumes the bootloader and firmware ELF files have already been built\nrenode renode/agp.resc\n\n# Run the 'start' command in the renode console\n```\n\n## Robot Framework Tests\n\nThe system-level integration tests are written in [Robot Framework](https://robotframework.org) and\nuse [Renode's integration](https://renode.readthedocs.io/en/latest/introduction/testing.html) to drive it.\n\n1. Install [Renode](https://renode.io/) (currently requires nightly, tested against `1.14.0+20231008gitebcb1b6b`)\n2. Install [Robot Framework](https://robotframework.org)\n  ```bash\n  pip install robotframework==6.0.2\n  ```\n3. Setup TAP device and networking on the host (requires root)\n  ```bash\n  sudo ./renode/setup-network.sh\n  ```\n4. Run the tests\n  ```bash\n  renode-test agp.robot\n  ```\n\n## System Architecture\n\n![system_architecture.png](doc/system_architecture.png)\n\n## RTIC Task and System Timings\n\nNOTE: some of the measurement periods are inaccurate (like the S8 LP), need to read the docs again.\nAlso, the relative start times are not accurate.\n\n![timing.png](doc/timing.png)\n\n![pms_timing.png](doc/pms_timing.png)\n\n## Hardware\n\n* STM32F411 \"black pill\"\n  - [WeActStudio github](https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x1#stm32f411ceu6-core-board)\n  - [pinout diagram](https://raw.githubusercontent.com/WeActStudio/WeActStudio.MiniSTM32F4x1/master/images/STM32F4x1_PinoutDiagram_RichardBalint.png)\n  - [refman](https://www.st.com/resource/en/reference_manual/dm00119316-stm32f411xc-e-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf)\n  - [datasheet](https://www.st.com/resource/en/datasheet/stm32f411ce.pdf)\n* ENC28J60 Ethernet board\n  - [Waveshare wiki](https://www.waveshare.com/wiki/ENC28J60_Ethernet_Board)\n  - [datasheet](https://www.waveshare.com/w/upload/7/7f/ENC28J60.pdf)\n  - [errata](https://ww1.microchip.com/downloads/en/DeviceDoc/80349b.pdf)\n* AirGradient PRO V3.7 kit\n  - [kit page](https://www.airgradient.com/open-airgradient/instructions/diy-pro-v37/)\n  - [schematic](https://www.airgradient.com/images/diy/schematicpro37.png)\n  - [Arduino code github](https://github.com/airgradienthq/arduino)\n  - [Factory firmware](https://github.com/airgradienthq/arduino/blob/master/examples/DIY_PRO_V3_7/DIY_PRO_V3_7.ino)\n* SH1106 OLED\n  - [datasheet](https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf)\n* Sensirion SHT31 (temperature/humidity sensor)\n  - [datasheet](https://www.mouser.com/datasheet/2/682/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital-971521.pdf)\n* Senseair S8 LP (CO2 sensor)\n  - [product page](https://senseair.com/products/size-counts/s8-lp/)\n  - [doc](https://rmtplusstoragesenseair.blob.core.windows.net/docs/publicerat/PSP126.pdf)\n  - [doc](https://rmtplusstoragesenseair.blob.core.windows.net/docs/Dev/publicerat/TDE2067.pdf)\n* PMS5003 (particle concentration sensor)\n  - [manual](https://www.aqmd.gov/docs/default-source/aq-spec/resources-page/plantower-pms5003-manual_v2-3.pdf)\n* Sensirion SGP41 (TVOC/NOx sensor)\n  - [product page](https://sensirion.com/products/catalog/SGP41/)\n  - [datasheet](https://www.mouser.com/datasheet/2/682/Sensirion_Gas_Sensors_Datasheet_SGP41-2604356.pdf)\n* AMS1117-3.3 regulator\n  - [Amazon link](https://www.amazon.com/gp/product/B07CP4P5XJ/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8\u0026psc=1)\n\n### Pins\n\n| Pin   | Peripheral    | Board D1 Mini Header Pin | Description |\n| :---  |    :---       |     :---                 |   :---      |\n| PA11  | USART6 Tx     | TX | Console/logger/panic-handler output |\n| PA12  | USART6 Rx     | RX | Console input (not used currenlty) |\n| PA9   | USART1 Tx     | D3 | senseAir S8 Rx |\n| PA10  | USART1 Rx     | D4 | senseAir S8 Tx |\n| PA2   | USART2 Tx     | D6 | PMS5003 Rx |\n| PA3   | USART2 Rx     | D5 | PMS5003 Tx |\n| PB3   | I2C2 SDA      | D1 | Shared I2C SCL : SH1106, SHT31, SGP41 |\n| PB10  | I2C2 SCL      | D2 | Shared I2C SDA : SH1106, SHT31, SGP41 |\n| PB13  | SPI2 SCK      | NC | ENC28J60 Eth SCK |\n| PB14  | SPI2 MISO     | NC | ENC28J60 Eth MISO |\n| PB15  | SPI2 MOSI     | NC | ENC28J60 Eth MOSI |\n| PB12  | GPIO Output   | NC | ENC28J60 Eth CS |\n| PA8   | GPIO Input    | NC | ENC28J60 Eth INT |\n| PB1   | GPIO Output   | NC | ENC28J60 Eth RESET |\n| PC13  | GPIO Output   | NC | On-board LED |\n\n## License\n\nLicensed under either of\n\n- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or\n  http://www.apache.org/licenses/LICENSE-2.0)\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\n\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall be\ndual licensed as above, without any additional terms or conditions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonlamb-gh%2Fair-gradient-pro-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonlamb-gh%2Fair-gradient-pro-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonlamb-gh%2Fair-gradient-pro-rs/lists"}