Flash an AirGradient ONE from the Command Line
I’ve purchased two AirGradient ONE indoor quality monitors to measure air quality in my home. AirGradient devices are open-source, so you can flash your own custom firmware and collect your air data locally rather than sending it to AirGradient’s proprietary cloud dashboard.

I keep an AirGradient ONE air quality monitor in my office to measure CO2 and pollution.
The existing documentation for flashing firmware requires you to use the Arduino IDE, a clunky GUI program:

Existing instructions for flashing AirGradient ONE rely on the Arduino IDE, a clunky GUI program.
I couldn’t find instructions for flashing AirGradient devices using the command-line, and it took me several hours to figure out, so I’ve included the steps below.
Aside: I don’t get the hype about AirGradient ๐︎
Every time I see AirGradient come up on forum discussions, everyone sounds excited about their products. I’ve found my AirGradient ONE to be mediocre. The software is extremely buggy and the documentation is sparse. But they’re the only company I’ve found that sells pre-made air quality monitors that are open-source, so I bought a second AirGradient monitor.
For years, AirGradient never bothered to publish instructions for flashing software onto the AirGradient ONE. I learned how to do it from these blog posts:
- “Monitoring my home’s air quality (CO2, PM2.5, Temp/Humidity) with AirGradient’s DIY sensor” by Jeff Geerling
- “AirGradient ONE Kit Review โ An open-source indoor air quality monitor” by CNX Software
This year finally, AirGradient published official flashing instructions, but they’re still a bit hidden.
Environment ๐︎
I tested these steps on Debian 13.0, but they should work on any Debian/Ubuntu-like system.
Install packages ๐︎
First, I install the base packages I need:
sudo apt update && \
sudo apt install -y \
git \
curl \
python3 \
python3-serial
Install arduino-cli ๐︎
Next, I install the Arduino CLI tool:
ARDUINO_CLI_VERSION='1.2.2'
ARDUINO_BIN_DIR="${HOME}/.local/arduino-cli"
mkdir -p "${ARDUINO_BIN_DIR}" && \
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh \
| BINDIR="${ARDUINO_BIN_DIR}" sh -s "${ARDUINO_CLI_VERSION}"
export PATH="${PATH}:${ARDUINO_BIN_DIR}"
To verify the install was successful, I print out the version string for arduino-cli:
$ arduino-cli version
arduino-cli Version: 1.2.2 Commit: c11b9dd5 Date: 2025-04-22T13:51:01Z
Download ESP32 libraries ๐︎
AirGradient ONE depends on the ESP32 Arduino libraries. As of this writing, AirGradient is not yet compatible with the 3.x versions of Arduino, so I have to use the latest stable 2.x version.
ARDUINO_ESP32_VERSION='2.0.17'
arduino-cli config init \
--additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json && \
arduino-cli core install "esp32:esp32@${ARDUINO_ESP32_VERSION}"
Find the path to my device ๐︎
Next, I need the device path to my AirGradient ONE. The simplest way to find the device path is:
- Run
dmesg --follow - Plug my AirGradient ONE into my system via USB
- Look for the device path to appear in the
dmesgoutput
Here’s what it looks like on my system:
$ sudo dmesg --follow
[517021.978880] usb 1-4: New USB device found, idVendor=303a, idProduct=1001, bcdDevice= 1.01
[517021.978884] usb 1-4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[517021.978894] usb 1-4: Product: USB JTAG/serial debug unit
[517021.978896] usb 1-4: Manufacturer: Espressif
[517021.978898] usb 1-4: SerialNumber: D8:3B:DA:1A:EE:C4
[517022.017678] cdc_acm 1-4:1.0: ttyACM0: USB ACM device
^^^^^^^
Path name
Given this output, the path on my system to my AirGradient ONE is /dev/ttyACM0:
AIRGRADIENT_PATH='/dev/ttyACM0'
Make device path writeable ๐︎
Next, I ensure that I can write to the AirGradient file path:
sudo chmod a+rw "${AIRGRADIENT_PATH}"
The AirGradient path should now have these permissions:
$ ls -l "${AIRGRADIENT_PATH}"
crw-rw-rw- 1 root dialout 166, 0 Aug 10 10:34 /dev/ttyACM0
^^^^^^^^
I also add myself to the dialout group so I can write to the path:
sudo adduser "$(whoami)" dialout
Get AirGradient source ๐︎
Next, I check the AirGradient factory flashing page to find out the latest production release.
# Current production release, as of this writing.
AIRGRADIENT_RELEASE='3.3.8'
With the version number in hand, I grab the AirGradient source code from AirGradient’s GitHub repo:
git clone --recurse-submodules \
--branch "${AIRGRADIENT_RELEASE}" \
--depth 1 \
https://github.com/airgradienthq/arduino.git \
~/airgradient-one
Flash firmware onto AirGradient ONE device ๐︎
Finally, it’s time to flash the software to my device:
cd ~/airgradient-one && \
arduino-cli compile \
--verbose \
--fqbn esp32:esp32:esp32c3:CDCOnBoot=cdc,PartitionScheme=min_spiffs,DebugLevel=info \
--library . \
--port "${AIRGRADIENT_PATH}" \
--verify \
--upload \
examples/OneOpenAir/OneOpenAir.ino
,EraseFlash=all to the end of the --fqbn flag.If flashing was successful, I see my device reboot and this output at the end of the process:
Wrote 1753792 bytes (967231 compressed) at 0x00010000 in 14.7 seconds (effective 952.3 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
Optional: View serial log output ๐︎
While my AirGradient is connected to my computer, I can view its log output through the serial port by using the arduino-cli monitor command:
$ arduino-cli monitor --port "${AIRGRADIENT_PATH}"
Using default monitor configuration for board: esp32:esp32:heltec_wifi_kit_32_V3
Monitor port settings:
baudrate=9600
bits=8
dtr=on
parity=none
rts=on
stop_bits=1
Connecting to /dev/ttyACM0. Press CTRL-C to exit.
[1] Standard Particle PM 2.5 = 7.00 ug/m3
[1] Particle Count 0.3 = 1298.5
[1] Particle Count 0.5 = 383.5
[1] Particle Count 1.0 = 39.7
[1] Particle Count 2.5 = 2.0
[1] Particle Count 5.0 = 2.0
[1] Particle Count 10 = 0.0
Alternative: Nix flake ๐︎
If you’re a Nix nerd, you might want to do this the Nix way. I’ve created a Nix flake to automate all the above steps:
When I want to flash my repo, I just run:
nix run .#flash
And when I want to view serial output, I run:
nix run .#monitor
I’m not sure how well my Nix flake works across systems, so you’ll probably have to tinker a little bit to get it to work for your system.
Conclusion ๐︎
I hope these instructions make it easier for you to flash your AirGradient ONE device and customize your firmware as you see fit.
Read My Book

I'm writing a book of simple techniques to help developers improve their writing.
My book will teach you how to:
- Create clear and pleasant software tutorials
- Attract readers and customers through blogging
- Write effective emails
- Minimize pain in writing design documents
Be the first to know when I post cool stuff
Subscribe to get my latest posts by email.