{"id":30684162,"url":"https://github.com/matoushybl/air-force-one","last_synced_at":"2025-09-01T20:14:14.686Z","repository":{"id":150970977,"uuid":"413033958","full_name":"matoushybl/air-force-one","owner":"matoushybl","description":"A simple air quality measurement system","archived":false,"fork":false,"pushed_at":"2024-02-11T18:54:04.000Z","size":2623,"stargazers_count":22,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-31T21:53:39.801Z","etag":null,"topics":["nrf52","rust-embedded"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/matoushybl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2021-10-03T09:37:12.000Z","updated_at":"2025-07-25T09:43:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"c8167fe6-2a89-4b42-8e5d-eec8eae7d490","html_url":"https://github.com/matoushybl/air-force-one","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/matoushybl/air-force-one","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matoushybl%2Fair-force-one","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matoushybl%2Fair-force-one/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matoushybl%2Fair-force-one/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matoushybl%2Fair-force-one/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/matoushybl","download_url":"https://codeload.github.com/matoushybl/air-force-one/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matoushybl%2Fair-force-one/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273183227,"owners_count":25059812,"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-09-01T02:00:09.058Z","response_time":120,"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":["nrf52","rust-embedded"],"created_at":"2025-09-01T20:14:13.095Z","updated_at":"2025-09-01T20:14:14.677Z","avatar_url":"https://github.com/matoushybl.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Air Force One - a simple air quality measurement system\nAir Force One is a simple air quality measurement systems primarily designed to measure CO2 concentration in multiple rooms of a small apartment. \nIt consists of nodes responsible for measuring the data and broadcasting them using BLE advertisements and a bridge which periodically receives the data and publishes them over MQTT for further processing - in this case displaying them in dashboards of Home Assistant.\nThere are two types of nodes which differ in used hardware and therefore communication protocol. The project was originally developed with the Bluetooth ones in mind, while the ESP32/WiFi one was added only as an experiment.\n\nAn overview of the system can be seen in the following image.\n![overview](img/air-force-one-overview.png)\n\n## Project structure\n* `node-fw` - firmware for the Bluetooth node\n* `node-c3` - firmware for the ESP32/WiFi node\n* `bridge-fw` - firmware for the Bluetooth bridge\n* `shared` - shared library for node and bridge firmware\n* `pcb` - design files for the electronics\n* `case` - design files for the case\n\n## Hardware\nThe hardware for this project is based on Nordic's nRF52840 bluetooth enabled MCU. CO2 concentration, temperature and humidity are measured by a Sensirion SCD41 sensor.\nThe hardware was developed to allow for more features in the future so apart from the SCD41, there are more on-board parts:\n* QSPI flash for measurements logging\n* 4 buttons\n* a connector and a hardware switch for connecting external battery (the circuitry however was not optimized for low-power even though it is well supported with embassy on nRF52)\n* SHT4x sensor for more precise temperature and humidity measurements (the sensors were either badly wired or damaged on prototype boards, so no support for them was added)\n* broken out pins allowing for some extensibility\n\nThe nodes and the bridge do not differ hardware wise, but the prototype bridge didn't have assembled anything other than the nRF52 module, LDO and USB circuitry to save parts, so no measurement support is available in the bridge firmware. The difference between them can be seen in the following image.\n![hardware](img/hardware.jpeg)\n\nFor firmware flashing a SWD probe is required. The programming connector on-board is the 10-pin Cortex-M debug connector with 1.27 mm pitch. An example of a usable probe with the same connector is the [hs-probe](https://github.com/probe-rs/hs-probe). \n\nThere is also a case available fitting the PCB shape.\n![case](img/case.jpg)\n\n## Firmware\nThe firmware for both the node and the bridge is written in Rust and utilizes [embassy](https://embassy.dev) async framework as well as [nrf-softdevice](https://github.com/embassy-rs/nrf-softdevice) for bluetooth support.\nAs such it is assumed that you have standard embedded Rust development tools installed - such as [probe-rs](https://probe.rs/).\n\nTo use the firmware, Nordic's softdevice must first be flashed to the MCU.\n\n1. First download the Softdevice from [here](https://www.nordicsemi.com/Software-and-tools/Software/S140/Download)\n2. Mass erase the MCU `probe-rs erase --chip nrf52840_xxAA`\n3. Flash the softdevice `probe-rs download --verify --format hex --chip nRF52840_xxAA s140_nrf52_7.X.X_softdevice.hex`\n\n\u003e Mass erasing the MCU has an unfortunate side effect of disabling reset pin. The firmwares in this repository reenable this functionality on startup.\n\nBoth of the firmware have their panic behavior configured to automatic reset, for development purposes however, it is better to switch to `panic-probe` panic handler using the 'dev' feature that can be passed to cargo.\n\n### Node Firmware\nFirmware for the node periodically reads data from the SCD41 sensor and updates Manufacturer Specific Data that are part of BLE advertisement.\nFor accessing the SCD41 sensor, the firmware uses the [sensirion-async](https://github.com/matoushybl/sensirion-async) library.\n\nFlashing the firmware can be done in the `node-fw` directory by running `cargo run --release`.\n\n### Bridge Firmware\nFirmware for the bridge scans for available nodes and reads the Manufacturer Specific data part of the node's advertisement. A CDC-NCM bridge is utilized to establish a connection to a MQTT broker. Data read from the scans are then published via the broker. Static IP addresses are utilized in this configuration.\n\nFlashing the firmware can be done in the `bridge-fw` directory by running `cargo run --release`.\n\n## Experimental ESP32/WiFi node\nAs an experiment a node based on ESP32-C3 was developed. The firmware for this node is written using a similar technological stack as the nRf52 firmware - Rust and embassy. The firmware is located in the `node-c3` directory. A defining difference is that this node directly accesses the MQTT broker, making it much more simpler and hopefully more reliable.\n\nThis node also utilizes off-the-shelf hardware, so no custom PCB is requried. In this case [this ESP32-C3 board](https://www.laskakit.cz/laskkit-esp-12-board/?variantId=10482) [this SCD41 breakout board](https://www.laskakit.cz/laskakit-scd41-senzor-co2--teploty-a-vlhkosti-vzduchu/) were used, but any development kits should work the same (apart from assigning pins for the I2C).\n\nWhen building the firmware SSID, password and MQTT broker address must be set as environent variables. Refer to the respective `main.rs` for environment variable names.\n\n\u003e As of time of publishing, the firmware uses unreleased esp-rs crates - `esp-backtrace` and `esp-println` alongside with `espflash` tool. These were used to add support for defmt.\n\n## Accessing the measured data\n\n### Configuring the network interface\nThe network interface can either be configured one-time using the `ip` command or using netplan on Ubuntu using a similar config to the one below. Using netplan has an advantage of the configuration being persistent over reboots and bridge reconnects.\n\nThe following config sets a static IP on the USB CDC-NCM interface.\n\n```\nnetwork:\n  version: 2\n  renderer: networkd\n  ethernets:\n      enx888888888888:\n          optional: true\n          dhcp4: no\n          addresses: [10.42.0.1/24]\n```\n\n### MQTT\nThe bridge firmware expects an MQTT broker running on the computer it is connected to. A simple way to spin up the broker is to use docker.\n```\ndocker run -it -p 1883:1883 -v $(pwd)/mosquitto.conf:/mosquitto/config/mosquitto.conf eclipse-mosquitto\n```\n\nThe bridge expects the broker to run without authentication, which can be achieved by the following `mosquitto.conf`:\n```\nlistener 1883\nallow_anonymous true\n```\n\n### Home Assistant\nOnce the measured data are published to the broker, MQTT integration in Home Assistant can be used to access the measured data. A sample Home Assistant configuration can be found below.\n\n```yaml\nmqtt:\n  sensor:\n    - name: \"Bedroom - co2\"\n      state_topic: \"afo-1\"\n      unit_of_measurement: \"PPM\"\n      value_template: \"{{ value_json.co2 }}\"\n      device_class: \"carbon_dioxide\"\n    - name: \"Bedroom - humidity\"\n      state_topic: \"afo-1\"\n      unit_of_measurement: \"%\"\n      value_template: \"{{ value_json.humidity }}\"\n      device_class: \"humidity\"\n    - name: \"Bedroom - temperature\"\n      state_topic: \"afo-1\"\n      unit_of_measurement: \"°C\"\n      suggested_display_precision: 1\n      value_template: \"{{ value_json.temperature }}\"\n      device_class: \"temperature\"\n    - name: \"Living room - co2\"\n      state_topic: \"afo-0\"\n      unit_of_measurement: \"PPM\"\n      value_template: \"{{ value_json.co2 }}\"\n      device_class: \"carbon_dioxide\"\n    - name: \"Living room - humidity\"\n      state_topic: \"afo-0\"\n      unit_of_measurement: \"%\"\n      value_template: \"{{ value_json.humidity }}\"\n      device_class: \"humidity\"\n    - name: \"Living room - temperature\"\n      state_topic: \"afo-0\"\n      unit_of_measurement: \"°C\"\n      suggested_display_precision: 1\n      value_template: \"{{ value_json.temperature }}\"\n      device_class: \"temperature\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatoushybl%2Fair-force-one","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatoushybl%2Fair-force-one","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatoushybl%2Fair-force-one/lists"}