AirBox V2 Design Document

AirBox is an open educational project in which participants build their own solar-powered air quality monitoring station from scratch. The project comes in two variants:

Both variants include temperature and humidity sensing, which serve both as standalone comfort metrics and as compensation inputs for the gas and particle sensors.

Each station connects over Wi-Fi and publishes its readings to a shared MQTT broker, making the data available in near-real-time to anyone. On the frontend, a Grafana dashboard presents the data on an interactive geographical map — every deployed AirBox appears as a data point, allowing participants and the public to explore hyperlocal air quality patterns across an entire city or campus.

Because all harvested data is public by design, AirBox doubles as a citizen-science platform: the more stations that are built and deployed, the denser and more useful the shared dataset becomes.

On the hardware side, AirBox is designed to run indefinitely off a small 10V solar panel backed by a single-cell Li-ion battery, with USB available as a secondary or indoor-only power source.

Connection instructions

You can find instructions for connecting your AirBox V2 to WiFi here

MCU Options

The following table provides information on a few MCU setups that were evaluated for AirBox. For our application, we are using for a deep-sleep capable microprocessor that is able to wake on an internal RTC clock. Wireless connectivity is vital for reporting data to AirBox servers.
Since AirBox is an engineering-focused course initially aimed at atudents, ease of programming is a big plus.

Spec nRF5340 + nRF7002 RP2040 + CYW43439 (Pico W) ESP32-S2
(Winner)
ESP32-S3
CPU 2× ARM Cortex-M33: app core @ 128 MHz + net core @ 64 MHz; no ULP 2× ARM Cortex-M0+ @ 133 MHz; no ULP 1× Xtensa LX7 @ 240 MHz + RISC-V ULP coprocessor 2× Xtensa LX7 @ 240 MHz + RISC-V ULP coprocessor
Wi-Fi 802.11ax (Wi-Fi 6), 2.4 + 5 GHz 802.11 b/g/n (Wi-Fi 4), 2.4 GHz 802.11 b/g/n, 2.4 GHz 802.11 b/g/n, 2.4 GHz
Bluetooth BLE 5.3 (via nRF5340 radio) BLE 5.2 (via CYW43439) No BLE 5.0
Other protocols Thread, Zigbee, NFC, 802.15.4, ANT — — —
USB USB 2.0 full-speed USB 1.1 (host & device) USB OTG (full-speed) USB OTG (full-speed)
SDK Zephyr Arduino IDE Arduino IDE Arduino IDE
Running current (CPU active) 5 mA + ~70 mA (nRF7002 Wi-Fi TX) 25–30 mA, up to 300 mA (Wi-Fi TX) 20–31 mA, up to 240 mA (Wi-Fi TX) 20–68 mA, up to 240 mA (Wi-Fi TX)
RTC Sleep 1.7 µA (System ON + net core RTC + 64 KB RAM) 1.3 mA (Pico W board-level) 20 µA (deep-sleep, RTC timer) 8 µA (deep-sleep, RTC timer)

The nRF5340 + nRF7002 combo is a very professional option with many IoT-specific communication protocols, allowing advanced integration with sensor networks and automation tooling.

For the initial phase of AirBox V2, ESP32-S2 looks like the best option that checks all the required boxes. We will use ESP32-S2 for the development of AirBox, more specifically the Wemos S2 Mini board to allow students to easily solder it to the board.

esp32-s2-mini-wemos-clone-audio-output-heeelp-v0-qn1pjs56puvb1.png

Sensors

The parameters that matter most depend on whether the station lives indoors or outdoors:

Spec SCD30 SEN54 SEN55 SEN66
Product family SCD (CO₂ module) SEN5x (environmental node) SEN5x (environmental node) SEN6x (environmental node)
CO2 ✅ NDIR dual-channel, 400–10 000 ppm ±(30 ppm + 3 % m.v.) ❌ ❌ ✅ PASens (photoacoustic), 400–10 000 ppm ±(30 ppm + 3 % m.v.)
PM1 / PM2.5 / PM4 / PM10 ❌ ✅ (laser scattering) ✅ (laser scattering) ✅ (MEMS-based SPS6x)
PM2.5 accuracy — ±5 µg/m³ (0–100) / ±10 % (100–1000) ±5 µg/m³ (0–100) / ±10 % (100–1000) ±5 µg/m³ (0–100) / ±10 % (100–1000)
VOC Index ❌ ✅ (SGP40-based) ✅ (SGP40-based) ✅
NOx Index ❌ ❌ ✅ ✅
Temperature ✅ ✅ (SHT4x-based) ✅ (SHT4x-based) ✅
Relative humidity ✅ ✅ (SHT4x-based) ✅ (SHT4x-based) ✅
Interface I2C / Modbus / PWM I2C / UART I2C / UART I2C
Supply voltage 3.3–5.5 V 5 V 5 V 3.3 V or 5 V
Current (measuring) 19 mA 63 mA (fan + laser active) 63 mA (fan + laser active) 55 mA (all sensors active)
Current (idle) 5.6mA 2.6mA 2.6mA 3mA
Module size 35 × 23 × 7 mm 41 × 41 × 12 mm 41 × 41 × 12 mm 41 × 41 × 11 mm
Design lifetime >15 years (CO₂ cell) >10 years >10 years >10 years

The SCD30 has no true idle mode — the IR detector draws current continuously. Increasing the sampling interval from 2s to 30s reduces average current to ~5.6 mA at the cost of slower response time (~135 s vs. ~20 s).

SCD30 provides the vital CO2 metric for indoor air quality while being affordable. SEN66 sensor provides all the important metrics, and is suitable for both indoor and outdoor sensing.

We will build connectors for both sensors on the pcb:

AirBox V2 EduKit AirBox V2
SCD30 is normally pcb-mounted using pin headers, but for our project it is best to be able to mount the sensor flexibly inside the enclosure. We will attach JST-XH headers to the sensor and connect using a detachable cable. SEN66 connects using its built-in connector (crimped wires will be done by hand) to the PCB. Case design should respect recommandations from Sensirion's Mechanical Design Guidelines.
4b4053f1-f7ef-43d2-bfe0-b2f00cfe34ba.png d2b23157-ec1c-40f7-b89f-e38ad4d4a40f.png

The sensors talk to the Airbox over I²C. There are plenty of nice resources on the internet to understand it, but we highly recommend checking out these slides made by Alexandru Radovici for the PM Course at UPB to understand the structure of the transactions.
https://embedded-rust-101.wyliodrin.com/docs/fils_en/lecture/06

Power Management

Between measurement cycles the MCU enters deep sleep and relies on its internal RTC to wake at the next scheduled interval.

The station accepts power from two sources - a USB connector for bench or indoor use and a 10V solar panel for outdoor deployments - with each input protected by a Schottky diode so that neither source can back-feed into the other.
Behind the diodes sits a TI BQ25185, a single-cell Li-Ion charge controller with an integrated power-path that seamlessly routes current between the solar panel, the USB supply, and the battery depending on what is available at any given moment.

The BQ25185 comes in a 2.5 × 2.5 mm QFN package, which is too fine-pitch for hand soldering; it will therefore be pre-soldered to the AirBox PCB before participants receive their kits, so that the power stage is guaranteed to work.

The ESP32 must be able to measure the state of charge of the battery. This will be done by measuring the battery voltage trough a 2x 100kOhm resistive divider. There are a few factors affecting this reading:

Voltage at the battery level is mapped to charge state using the following interpolation table:

100% 85% 65% 50% 30% 15% 0%
4.2V 4.05V 3.9V 3.8V 3.7V 3.6V 3.3V

When the temporary charge turnoff event happens, the ESP will also measure the voltage at solar panels to be able to write the sun field. It will do so using a resistive divider (100kOhm and 330kOhm) where a solar line voltage of 4.5V indicates presence of sunlight.

Voltage readings should be done after a short settling delay (50ms) after charge turnoff to help stability.

The sensor bank must include a high-side mosfet that will power cycle the 3.3V rail briefly (500-2000ms) if the one of sensors need to be reset (due to operation outside design conditions).

Construction

Passive components (resistors, capacitors) will be 2010 or 1812 package to be easy to solder by hand.

All board interfaces will be JST-PH connectors, including sensors, battery and solar panel connections.
Sensors wil be mounted off-board to facilitate positioning as specified in the reccomended engineering guides supplied by Sensirion.

Complete Schematic

airbox-full-schematic.svg

Power section Schematic

airbox-power-schematic.svg

Sensor switching Schematic

airbox-sensors-section.svg

MCU Schematic

airbox-mcu-schematic.svg

Assembly instructions

Resistors

Start with the bare PCB. U2, U3 and L1 are alrady pre-soldered for you.

Step 0: Bare PCB

1 MΩ Resistor (total 2 resistors)

Step 1: R5 R6

330 kΩ Resistor (total 1 resistor)

Step 2: R18

180 kΩ Resistor (total 1 resistor)

Step 3: R7

100 kΩ Resistor (total 3 resistors)

Step 4: R16 R17 R19

13 kΩ Resistor (total 1 resistor)

Step 5: R10

10 kΩ Resistor (total 5 resistors)

Step 6: R8 R12 R14 R15 R20

4.7 kΩ Resistor (total 1 resistor)

Step 7: R21

680 Ω Resistor (charge-set) (total 1 resistor)

Step 8: R9

100 Ω Resistor (total 2 resistors)

Step 9: R11 R13

AO3401A P-MOSFET (total 1 MOSFET)

Step 10: Q1

Status LED (total 1 LED)

LED's are polarized components - It's important to get the orientation right. The LED's circle is assymetric. Mount using the lens upwards, towards the board edge!

Ask Justin, Ilinca or another assistant for help placing this part.

Step 11: D3 step11-led-details.png

SS24 Schottky Diode (total 2 diodes)

Diodes are polarized components - It's important to get the orientation right. You will find three engraved lines on the anode of the diode.

Step 12: D1 D2 2021_0315_180403_025.png

1 µF Capacitor (total 2 capacitors)

Step 13: C8 C9

10 µF Capacitor (total 3 capacitors)

Step 14: C5 C6 C10

100 nF Capacitor (total 1 capacitor)

Step 15: C4

22 µF Capacitor (total 2 capacitors)

Step 16: C3 C7

2-pin JST Connector (Solar + Battery) (total 2 connectors)

It is important to get the polarity of the power connectors right!
Battery connector tab towards the center of the board.
Solar connector tab towards the "SOLAR" writing.

Step 17: J3 J4

6-pin JST Connector (SEN66) (total 1 connector)

Place connector tab towards the center of the board or SEN66 text.

Step 18: J1

3-pin JST Connector (DS18B20) (total 1 connector)

Place connector tab towards the center of the board or SEN66 text.
Identical to the SEN66 connector.

Step 19: J7

2×8 MCU Socket Header (total 2 headers)

Step 20: J6 J2

Insert MCU Header Pins (total 2 headers)

The pin headers have a long and short side. Insert the long side into the sockets, and the short side should stick up.
We will later solder the MCU to the short side.

Step 21: J6 J2

Solder WeMos S2 Mini Module (total 1 module)

Step 22: U1

Software

Firmware

Key considerations:

Infrastructure & Data Flow

flowchart LR
    A["Airbox sensor<br/>builds JSON payload"]
    A -->|"HTTP POST"| B{"Authorization<br/>ApiKey"}
    B -->|"reject"| R["401 Unauthorized<br/>drop silently"]
    B -->|"pass"| J{"Validation<br>Schema & Values"}
    J -->|"fail"| K["400 Bad Request<br/>log and drop"]
    J -->|"pass"| C["Enrichment<br/>resolve device, installation<br/>add server timestamp"]
    C --> P["Persistence<br/>hypertable AIRBOX_READINGS"]

Firmware sends data to a HTTPS server under the following format using POST method:

Location: https://ingest.airbox.alacrity.ro/api/v2/submit
Authorization: Server must accept API token in any of the following headers:

Submitting a GET request to this page should show a HTML page with a small guide on how to submit data and a link to this documentation page here.

Only Content-Type header accepted must be application/json and the content of the request must conform with the schema:

{
  "geohash": "...",
  "charge": 0, # from 0 to 100 float
  "sun": false,
  "co2": 0
  "pm1": 0,
  "pm25": 0,
  "pm4": 0,
  "pm10": 0,
  "temp": 0,
  "hum": 0,
  "voc_index": 0,
  "nox_index": 0,
  "voc": 0,
  "nox": 0,
}

Failure to verify an ApiKey should not be logged.

String fields must be at most 100 characters long, and no other fields must be present. Any validation failure will drop the data point and log with a WARN severity the following information:

Numeric fields are optional. If they are not provided in the messagem they should be set to null.

The following fields must be enriched in the message with data fetched from the separate AIRBOX_INSTALLATIONS, by joining on the ApiKey received with the request:

Then, the whole bunch of data goes to persist inside the AIRBOX_READINGS hypertable. The time, installation, device and geohash fields are mandatory and must be indexed. The rest of the fields represent concentrations and should be only set where measurements are available (persist null when absent).

When an airbox is not able to measure a specific measurement (like the SEN66-equipped airboxes for non-index VOC and NOX) those fields should be absent or set to undefined. Likewise, SCD30-equipped airboxes will publish only the co2 field (besides the three required fields).

AIRBOX_INSTALLATIONS Static Table

The table AIRBOX_INSTALLATIONS is static and will be manually updated by the administrators. The middleware should ensure the table exists, and if not, should create it.

CREATE TABLE IF NOT EXISTS AIRBOX_INSTALLATIONS (
    device_id       VARCHAR(100)  PRIMARY KEY,
    apikey          VARCHAR(100)  NOT NULL,
    owner_email     VARCHAR(100)  NOT NULL,
    co_owner1_email VARCHAR(100),
    co_owner2_email VARCHAR(100),
    installation    VARCHAR(100) NOT NULL,
    notes           TEXT,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

In future iterations, we will implement a GUI for editing this table. Fow now, manual edits.

Data presentation

The main user interaction website is airbox.alacrity.ro. Here, we will have a main landing page with a short description of the project. A button or link will lead to the public grafana dashboard that serves the outdoor instances.

Grafana runs on /g/ base url on the aforementioned domain, and exposes a single dashboard for all outdoor stations and one dashboard per indoor station.

Grafana is configued with SSO, connected to our Authentik instance for administration.

indoor dashboard automated provisioning will go unimplemented for now as we are not actively provisioning this variant.

Previous iterations

After initial testing with mock data, we have discovered that Prometheus is not suitable for long-term data retention. We will evaluate wether Mimir or TimescaleDB would fit the purpose better.
Telegraf also cannot be trusted with unsanitized data, therefore we are opting to build a middleware that transfers data from MQTT to PGSQL, while also ensuring data sanitization.

Connecting an AirBox to WiFi

The WiFi name, password and the station location (geohash) are not written in the code. They live in the board's flash memory and are entered through setup mode: the AirBox opens its own WiFi hotspot with a small configuration page. You can change them any time without reflashing.

What you need

The ESP32 only speaks 2.4 GHz WiFi. A network that is 5 GHz only will never be found, no matter how correct the password is. Networks that need a login page (hotel or school portals) do not work either, the station has no browser to log in with.

Step 1: enter setup mode

Press the setup button and keep it held for 15 seconds. This works from any state, even while the station is sleeping: the button wakes it up.

You know you are in setup mode when the LED stays solid on.

A freshly flashed board has no WiFi settings at all, so it boots straight into setup mode by itself. In that case skip the button and go to step 2.

If you release the button too early the station just continues whatever it was doing. Try again and count the full 15 seconds.

Step 2: connect to the AirBox hotspot

On your phone or laptop, join this network:

Your phone will likely warn that this network has no internet. That is normal, stay connected anyway.

Step 3: open the configuration page

On most phones the page pops up on its own a few seconds after connecting (the same way hotel login pages do). If nothing appears, open a browser and go to:

http://192.168.4.1

Step 4: fill in the form

The page has three fields:

Press Save & reboot. The station stores the values and restarts in normal mode.

If you only wanted to look and change nothing, press Leave setup mode instead: the station reboots and keeps its old settings.

Step 5: check that it worked

Be patient here. The station does not connect immediately after the reboot: it first checks the temperature probe, then preheats the air sensor for 5 minutes, and only then connects and uploads. So the first data appears on the dashboard about 6 to 7 minutes after the reboot, and then roughly every 16 minutes.

If you have a USB cable and the serial monitor open (115200 baud), you can watch it happen live: a connected line with an IP address, then a POST line and HTTP 200 (accepted).

When it does not work

Setup mode can be entered as many times as you like, nothing breaks by trying.

Development timeline

PCB Development timeline:

12 April 17 April 18 April 20 April 23 April 3 May 6 May
PCB Development started PCB development finished RCB Review finished & Ordered at JLCPCB + LCSC PCB Fab finished & QFN automated assembly started PCB Shipped PCB Arrived in Bucharest Assembly of the first prototype finished

Course material development timeline:

18 April 30 April 3 May 8 May
Course material development started after PCB design is finished Course deadline, follows review process for refinement Review finished and changes implemented Course is ready for demo

BOM

This bom is aimed at protptyping with pre-assembled PCB for a few pieces. Once testing is positive, we will provide a BOM for manual assembly:

Datasheets

SEN66 SEN55 SCD30 BQ25185

Previous prototypes

In a previous prototype, the nets of R7 and C9 got flipped 180 degrees by mistake, during a schematic-only reorganization. This caused ground planes to form around the wrong pin, shorting to ground the Vin line.

The fix was to:

2021_0315_180617_026.png 2021_0315_180641_027.png