Digital Biology: Visualizing Air Quality Through Artificial Life

by Arpan Mondal in Circuits > Microcontrollers

254 Views, 10 Favorites, 0 Comments

Digital Biology: Visualizing Air Quality Through Artificial Life

MILKYWAY COMPASS(1).png
VID_20251130_113600565.gif
IMG_20251130_110717810.jpg

We are surrounded by data that we cannot see. The air quality in our cities fluctuate constantly. But usually, the only way we see this information is by reading a number on a screen like "AQI 150." But our brains aren't designed to care about numbers, we are designed to care about life.

I recently became fascinated by Conway's Game of Life. It is a famous computer simulation where "digital cells" live, die, and reproduce based on simple rules. It is mesmerizing to watch, but it usually exists in a digital void, completely disconnected from the real world.

This gave me an idea. What if this digital life had to breathe the same air I do?

I decided to build this miniature, retro-styled TV that visualizes Air Quality not as a number, but as an ecosystem. I modified the rules of the Conway's Game of Life so that the "digital biology" is directly influenced by real-world surroundings.

When the air is clean (Good AQI): The colony thrives, forming complex, beautiful structures and that dance on the screen.

When the air is toxic (Poor AQI): The environment becomes "poisonous". Random cell death is introduced, causing the colony to struggle, fracture, and disintegrate.

Just the idea is fascinating to me. We are creating life to visualize life!

Supplies

Esp32 development board Buy

1.12 inch OLED display Buy

Hookup wires Buy

USB-C cable

Soldering iron and solder

Rubber adhesive

Acrylic/spray paint

Double-sided tape

What Is Conway's Game of Life

1_simple_colony-ezgif.com-added-text.gif
2_glider-ezgif.com-added-text.gif
3_oscillator-ezgif.com-added-text.gif
4_spaceship-ezgif.com-added-text.gif

Conway's Game of Life is not a game you play. It is a game you watch. It was created by mathematician John Conway in 1970. You simply create an initial state (a pattern of pixels), and then the game plays itself forever based on 4 simple rules.

Imagine a grid of cells (pixels). Each cell is either Alive (lit up) or Dead (dark). Every single frame of the animation, each cell looks at its 8 neighbors to decide its fate for the next frame:

  1. Loneliness: If a cell has fewer than 2 neighbors, it dies.
  2. Overcrowding: If a cell has more than 3 neighbors, it dies.
  3. Survival: If a cell has exactly 2 or 3 neighbors, it stays alive.
  4. Birth: If a dead cell has exactly 3 neighbors, it miraculously comes to life!

Even though the rules are incredibly simple, they create complex, "living" behaviors that look like biology.

  1. Gliders: Simple patterns that "walk" across the screen like little insects.
  2. Oscillators: Patterns that blink or spin in a perfect, infinite loop.
  3. Spaceships: Complex structures that leave a trail of debris behind them.

Modification: "Breathing" Real Air

The standard Game of Life happens in a perfect, closed digital universe. Nothing changes unless the rules say so. To turn this into an environmental monitor, I hacked the simulation to make the digital world react to the real world.

I introduced a modification to the classic rules. I connected the simulation to the real world Air Quality Index (AQI). This number acts as a "poison" variable in the simulation.

  1. AQI 0-50 (Good Air): The simulation runs with standard rules. Life is robust, stable, and forms large, predictable colonies.
  2. AQI 100+ (Unhealthy): I introduced a "random decay" probability. Even if a cell should survive according to Conway's rules, there is now a 2% chance it will randomly die.
  3. AQI 150+ (Hazardous): The death chance rises to 5%. Visually, this is striking—large, stable clusters begin to rot from the inside. The colony looks "sick" and empty, giving you a visual sense of the pollution outside your window.


The "Gardener" (Entropy Injection)

A common problem with small Game of Life grids is "Heat Death"—eventually, everything dies out or gets stuck in a boring, infinite loop. To fix this, I added a Gardener Algorithm.

The code constantly monitors the population. If it notices the colony has died out or hasn't changed its population size for a few seconds (stagnation), it automatically "reseeds" the grid. It drops 5-6 new random clusters of cells into the mix, sparking a fresh cycle of chaotic evolution. This ensures the art piece is never static. It is always evolving, dying, and being reborn.

Designing the Housing

Screenshot 2025-12-30 112452.png
Screenshot 2025-12-30 112514.png

I originally planned to model a housing in Fusion 360, but my laptop died midway through the project! I had to borrow a friend's laptop, which wasn't powerful enough for heavy CAD software.

I realized this was actually a perfect opportunity to use Tinkercad, a free, web-based 3D modeling tool that runs on almost any computer. It turned out to be the perfect tool for this retro aesthetic. I designed a simple, cute CRT-style TV case.

Download the Files:

I have attached the .STL files below. They are designed to be printed without supports.

AQI_TV_FULL.STL - Contains all parts in one file

Others - Each part separated into individual models

Even if you are a CAD pro, give Tinkercad a try for quick projects like this. It saved the day!

3D Printing and Painting

IMG_20251129_122705277.jpg
IMG_20251129_122651501.jpg
IMG_20251129_133457825.jpg
IMG_20251129_130635902.jpg

I sent the files to a local 3D printing vendor. The parts came out great in standard PLA.

You might notice that the front bezel is a separate piece from the main body. I could have printed them as one solid unit, but I intentionally split them because if you want to experiment with different screen sizes or bezel styles later (circular, squared, decorative dials), you can swap just the front piece without reprinting the entire housing.

This bezel simply needs to be glued into place over the front panel. I used rubber adhesive for this.

Paint!

IMG_20251129_150521791.jpg
IMG_20251129_150524838.jpg
IMG_20251129_152656374.jpg

The raw white PLA looked clean, it looked more like a computer monitor than a vintage TV set. To nail that nostalgic, 1980s consumer electronics vibe, I needed to change the color.

I went with a blueish gray acrylic paint (similar to the color of old Sony or Panasonic TVs from the 80s). This subtle, cool-toned gray.

Painting Process:

  1. Prep: I lightly sanded the PLA with 220-grit sandpaper to help the paint adhere.
  2. Base Coat: Applied 2 thin coats of acrylic paint with a paint brush (letting each coat dry for 20 minutes).\


I also glued two thin GI wires for the antennae.

Circuit Connections

Copy of ledstrip.png
FL4KA3DM3VM0RU2.jpg
IMG_20251129_122400674.jpg
F9IQXIIM8HJXA2S.png
IMG_20251129_122338625.jpg
IMG_20251129_122323793.jpg

While the paint is drying, we can shift our focus to the electronics. Since we are using the XIAO ESP32C3, the wiring is incredibly simple because the microcontroller and the OLED display both run on 3.3V logic. We don't need any resistors, capacitors, or level shifters.

The Pinout (Xiao > OLED)

3V3 > VCC

GND > GND

D4 > SDA

D5 > SCL

Keep your wires short (under 10cm if possible). I2C signals can get unstable over long wires without pull-up resistors.

Note: On the XIAO ESP32C3, the I2C pins are physically labeled D4 and D5. On other ESP32 boards, these are often D21/D22. Check the pinout of your specific board!

The Code

Here, I'm summarizing the code and usage. I'll do a detailed code walkthrough in the end.

  1. The Simulation: It runs Conway's "Game of Life" on a 32x32 grid.
  2. The Data Fetch: Every hour, it wakes up the Wi-Fi, connects to the WAQI (World Air Quality Index) API, and downloads the pollution level for your specific city.
  3. The Corruption: It uses the AQI to sabotage the game. If the air is toxic, the cells start dying randomly.

Prerequisites:

Before uploading, you need to install these libraries in the Arduino IDE (Sketch > Include Library > Manage Libraries):

  1. U8g2
  2. ArduinoJson

Configuration:

I have designed the code to be "plug-and-play," but you need to edit two lines at the top:

// 1. YOUR WI-FI CREDENTIALS
const char* SSID_NAME = "Your_WiFi_Name";
const char* WIFI_PASS = "Your_WiFi_Password";

// 2. YOUR LOCATION (Latitude & Longitude)
// Replace "LAT;LON" after "geo:" with your city's coordinates.
String AQI_URL = "https://api.waqi.info/feed/geo:LAT;LON/?token=demo";
// Note: The 'demo' token works for testing, but you need to get a free API key from aqicn.org for stability.

Find the code attached below.

Downloads

Testing

VID_20251129_120650835-ezgif.com-optimize.gif
VID_20251129_120804123-ezgif.com-optimize.gif
VID_20251129_120859811-ezgif.com-optimize.gif

Once I uploaded the code to the XIAO, it was time to test how the "digital colony" would respond to different air quality levels (sounds like I'm a biologist, doing a test on microorganisms' haha).

I temporarily hardcoded three different AQI values into the firmware to simulate real-world conditions:

  1. AQI 50 (Good Air): The colony thrived, forming complex patterns.
  2. AQI 100 (Moderate): The colony looked visibly stressed, with frequent die-offs.
  3. AQI 150+ (Hazardous): Mass extinction. Within seconds, the entire population collapsed into blackness.


The Problem:

While the "dying" effect was dramatic and visually effective, I noticed two critical issues:

  1. Total Extinction: When air quality was bad, all the cells would eventually die, leaving the screen completely black. The display looked "dead," not "struggling."
  2. Infinite Loops (Stagnation): Sometimes the simulation would get stuck with only "Oscillators" (patterns that blink forever) or "Still Life" (static blocks). The animation froze into a boring loop.


The Solution: The "Gardener" Algorithm

To keep the ecosystem alive and interesting, I introduced a monitoring function that acts like a caretaker for the colony.

  1. Population Tracking: The code constantly counts how many cells are alive.
  2. Stagnation Detection: If the population stays exactly the same for 30+ frames (or drops below 5 cells), the Gardener triggers.
  3. Intervention: It drops 5-6 new random "seed clusters" onto the grid, sparking a fresh cycle of evolution.


This makes sure the display is never static. Even in the worst air quality, life finds a way to restart, just like real ecosystems after a disaster.

Assembly

IMG_20251130_103243099.jpg
IMG_20251129_152805794.jpg
IMG_20251129_152800312.jpg
IMG_20251129_153210108.jpg
IMG_20251130_110717810.jpg

With the code tested and the paint dry, I carefully tucked the XIAO, OLED and the wiring into the back compartment of the TV housing. The beauty of the XIAO is its tiny size. It fits comfortably with room to spare.

I made sure the USB-C port was aligned perfectly with the cutout hole on the back panel. This allows me to power the device (or reprogram it) without disassembling anything.

To make this look like a real miniature TV from the 80s, I added a few small details:

  1. Fake Control Buttons: I glued three black-painted beads to the front bezel (below the screen). They don't do anything, but they instantly sell the "vintage electronics" aesthetic.
  2. Stand Legs: I attached two more beads to the bottom as tiny "feet." This lifts the TV slightly off the desk and gives it a proper standing posture.

The Colony Is Alive!

FTP6P00MJR3IY7N.gif
IMG_20251130_113513472.jpg

Finally, plug the USB-C cable into the back of your Mini TV and watch as your digital colony springs to life.

What surprised me most about this project wasn't the code or the hardware, it was how addictive it is to just watch. The emergence of gliders skating across the screen, spaceships cruising past, oscillators blinking rhythmically... I've found myself staring at this tiny TV for hours without realizing it, the same way people watch fish tanks or crackling fires. Except this aquarium is filled with digital life that breathes my air.

On days when the pollution is high, the colony visibly struggles. The bustling patterns become sparse and chaotic. It's a sobering reminder that the invisible data we ignore on our phones has real consequences, even if only for digital creatures.

If you're intrigued by the idea of turning invisible data into living, breathing art, I encourage you to build your own version. Experiment with different datasets (weather, stock prices, heart rate), and different housing too! Now that I think about it, it would be cool for the housing to look like a Petri Dish, what do you think?

Code Walkthrough

The code might look long, but it is actually built from three simple blocks. Let's break them down.

1. The Configuration Block

At the very top, we set up the connection to the real world.

// USER CONFIGURATION
const char* SSID_NAME = "Your_WiFi_Name";
const char* WIFI_PASS = "Your_WiFi_Password";
String AQI_URL = "https://api.waqi.info/feed/geo:LAT;LON/?token=demo";

What it does: This tells the ESP32 which Wi-Fi network to join and which specific GPS coordinate to check for air pollution.

2. The Game Engine (Conway's Logic)

The function computeNextGen() is the brain of the simulation. It loops through every single cell on the 32x32 grid and decides its fate.


cpp
// The Standard Rules
if (neighbors < 2 || neighbors > 3) newgrid[x][y] = 0; // Dies of loneliness or overcrowding
else newgrid[x][y] = 1; // Survives

This is where I injected the Air pollution logic.


cpp
// The Pollution Hack
if (currentAQI > 150) deathChance = 5; // 5% chance of random death in toxic air
if (deathChance > 0 && random(100) < deathChance) newgrid[x][y] = 0;

This small "if" statement is what connects the digital world to the physical one. It overrides the game rules to force a "die off" when the air is bad.

3. The Gardener (Stagnation Check)

The Game of Life can sometimes get stuck in a loop.


cpp
if (abs(population - prevPopulation) <= 2) stagnationCount++;

if (stagnationCount > 30 || population < 5) {
// ...Reseed the grid...
}
  1. If the simulation becomes a "still life" (frozen) or an "oscillator" (looping forever) for more than 30 frames, the code detects it.
  2. It drops 5-6 new clusters of random cells onto the grid, sparking a new chaotic evolution. This ensures the art piece is never boring to look at.

4. The Data Fetcher

The updateAirQuality() function runs once every hour (3600000 milliseconds).

  1. It connects to the WAQI API.
  2. It parses the JSON response (which looks like {"status":"ok","data":{"aqi":145,...}}).
  3. It extracts just the number 145 and updates the global currentAQI variable.