Hello friends, I hope you all are doing great. Today, I am going to share the Raspberry Pi 5 Air Quality Monitor - Proteus Simulation, the tenth project of our IoT Projects series. If you have searched for a Proteus simulation of an air quality monitor with a Raspberry Pi, here is a complete one. A Raspberry Pi 5 reads a PMS5003 laser particle sensor and a CCS811 gas sensor, works out the US Air Quality Index, stamps every reading with the time of a DS3231 real-time clock, shows the air's health on an RGB light and serves a live web dashboard with a downloadable log for your laptop and your phone.

We spend most of our time indoors, and the air in a kitchen, a classroom or an office can be much worse than the air outside: smoke and fine dust from cooking, carbon dioxide from the people in a closed room, and volatile organic compounds from paint, cleaners and furniture. In this tutorial, we will go through the circuit, learn how each sensor measures, turn particle readings into an AQI number, walk through the Python program, and then run the simulation: clean air, a smoking pan, an air purifier, fresh paint and an open window. At the end, you can download the project with the program, every library file and the Python runtime the Pi needs.

NOTICE: Our team has put a lot of effort into this project: the circuit, the code and every part in the simulation are our own work, and you get them all free. Your feedback is the fuel that keeps us going, so please tell us what you think in the comments below. And if our free projects help you, you can buy us a coffee. So, let's get started with the Raspberry Pi 5 Air Quality Monitor - Proteus Simulation:

Figure: The Raspberry Pi 5 air quality monitor in a Proteus simulation.

What Does This Air Quality Monitor Do?

The monitor watches the two kinds of indoor pollution that matter most, the way commercial air monitors do.

  • Particles: a PMS5003 counts the fine dust in the air and reports PM1.0, PM2.5 and PM10 in micrograms per cubic metre.
  • An AQI number: the program turns PM2.5 and PM10 into the US EPA Air Quality Index, from 0 (good) to 500 (hazardous).
  • Gases: a CCS811 reports eCO2, a measure of how stale the air is, and TVOC, the total of volatile organic compounds.
  • Time stamps: a DS3231 real-time clock dates every reading and every event, even without a network.
  • A colour light: an RGB LED shows the air's health at a glance: green, yellow, red or purple, white while warming up.
  • A dashboard: a web page with the readings, the peaks of the run, an event log, a CSV log and a JSON view.

It is a great project for anyone learning the Raspberry Pi with UART and I2C sensors, and a popular final-year project for smart homes, smart classrooms and environmental monitoring. In the simulation, the air changes with one click on a sensor's pop-up.

Components Used in the Proteus Simulation

All the parts come from our Proteus libraries, each with its own tutorial. The project zip at the end includes every LIB and DLL file.

The parts of the air quality monitor and their library posts
PartProteus deviceLibrary postConnection
Raspberry Pi 5RASPBERRY PI 5Raspberry Pi 5 Library for ProteusPI1, Ethernet 192.168.1.51
PMS5003 particle sensorPMS5003ADVTEPPMS5003 Particle Sensor Library for ProteusU1, UART (GPIO14 / GPIO15), 5 V
CCS811 gas sensorCCS811ADVTEPCCS811 Air Quality Sensor Library for ProteusU2, I2C 0x5A, 3.3 V
DS3231 real-time clockDS3231ADVTEPDS3231 RTC Module Library for ProteusU3, I2C 0x68, 3.3 V
RGB LED, common cathodeLEDRGBCCTEPRGB LED Library for ProteusD1, GPIO17 / GPIO27 / GPIO22
Resistors 100, 47, 47 ohmRESISTOR4BANDTEPResistor Library for ProteusR1 - R3, one for each colour
Laptop WiFi MonitorLAPTOP WIFI MONITORLaptop WiFi MonitorPC1
Mobile WiFi MonitorMOBILE WIFI MONITORMobile WiFi MonitorMOB1

The three sensors are Advance models with pop-ups. The PMS5003's pop-up sets the air: the presets CLEAN, CITY, HAZE, SMOKE and DUST, and the actions COOKING, PURIFIER, FOG and WIND. The CCS811's pop-up has the presets FRESH, OFFICE, MEETING and PAINT, and the actions BREATH, SANITIZER, VENTILATE and HUMID. The DS3231's pop-up shows its registers, its time and its temperature.

Proteus Simulation of the Air Quality Monitor: the Circuit

Figure: The complete circuit of the air quality monitor on an A3 Proteus sheet.

Every signal leaves the Pi's header through a net label. The two I2C chips share one bus, and the particle sensor has the UART to itself:

The Raspberry Pi 5 connections
PartPinRaspberry Pi 5 header
U1 PMS5003VCC / GND5V (pin 2) / GND (pin 6)
U1 PMS5003TX / RXGPIO15 RXD (pin 10) / GPIO14 TXD (pin 8)
U2 CCS811VCC / GND3V3 (pin 1) / GND
U2 CCS811SDA / SCLGPIO2 SDA (pin 3) / GPIO3 SCL (pin 5)
U2 CCS811WAK / ADDGND: always awake, address 0x5A
U3 DS3231VCC / GND / SDA / SCL3V3 / GND / GPIO2 / GPIO3
D1 RGB LEDR / G / BGPIO17 (pin 11) / GPIO27 (pin 13) / GPIO22 (pin 15), each through a resistor
D1 RGB LEDC (cathode)GND

Supplies and Logic Levels

The sensors take their power from the Pi's own header, as on a real build. The PMS5003 needs 5 V for its fan, but its UART runs at 3.3 V logic, so its TX can go straight to the Pi's RXD. The CCS811 and the DS3231 run from the Pi's 3.3 V pin, so their I2C lines are at 3.3 V too; the Raspberry Pi already has pull-up resistors on GPIO2 and GPIO3. The CCS811's WAK pin must be LOW for the chip to listen on I2C, so it goes to ground, and ADD to ground selects the address 0x5A.

Choosing the LED Resistors

Each colour of an RGB LED is its own diode with its own forward voltage: about 2.0 V for red and about 3.0 to 3.1 V for green and blue. A GPIO gives 3.3 V and should not supply more than about 16 mA. For red, 100 ohm gives (3.3 - 2.0) / 100 = 13 mA. Green and blue have only 0.2 to 0.3 V left over, so they need a smaller resistor: with 47 ohm they get about 4 to 6 mA, still bright enough to mix. That is why the three resistors differ.

How the Air Quality Monitor Works

How the PMS5003 Counts Particles

Inside the PMS5003, a small fan pulls air through a chamber where a laser beam crosses it. Every particle scatters a flash of light onto a photodiode, and the size of the flash tells the particle's size. The sensor counts the flashes, sorts them by size and works out the mass of particles per cubic metre: PM1.0 for particles under 1 micrometre, PM2.5 under 2.5 and PM10 under 10. PM2.5 is the most important for health, because such fine particles reach deep into the lungs. The fan needs about 30 seconds to give stable readings after power-up.

The sensor sends its readings by itself over its UART at 9600 baud: a 32-byte frame starting with the bytes 0x42 0x4D, then the length, thirteen 16-bit values and a checksum. Values 4 to 6 are PM1.0, PM2.5 and PM10 under atmospheric conditions, the ones to use for room air. Once stable, a frame comes every 2.3 seconds.

Turning Particles into an AQI Number

A concentration in micrograms means little to most people, so air quality is reported as an index. The US EPA AQI splits each pollutant into bands, and inside a band the index is a straight line between the band's ends. For PM2.5 (the 2024 table): 0 to 9.0 is GOOD (0 - 50), 9.1 to 35.4 MODERATE (51 - 100), 35.5 to 55.4 UNHEALTHY FOR SENSITIVE GROUPS (101 - 150), 55.5 to 125.4 UNHEALTHY (151 - 200), 125.5 to 225.4 VERY UNHEALTHY (201 - 300) and above that HAZARDOUS. PM10 has its own bands, and the AQI is the higher of the two.

A Worked Example

Say the 30-second average of PM2.5 is 72 micrograms per cubic metre. That falls in the band 55.5 to 125.4, which spans the AQI 151 to 200. The index is 151 + (200 - 151) / (125.4 - 55.5) x (72 - 55.5) = 151 + 0.701 x 16.5 = 162.6, so the AQI is 163: UNHEALTHY. An official AQI uses 24-hour averages; a room monitor shows a short average instead, so that it reacts to a smoking pan within a minute.

How the CCS811 Measures Gases

The CCS811 holds a tiny heated metal-oxide sensor whose resistance drops when it meets volatile organic compounds, and a microcontroller that turns the resistance into two numbers. TVOC is the total of those compounds in parts per billion. eCO2 is an equivalent carbon dioxide level in parts per million, worked out from the VOCs that people breathe out, so it rises in a crowded closed room just as real CO2 does. A new sensor needs a run-in of 20 minutes after each start before its readings are trusted; the simulation shortens this to 20 seconds with its RUNIN property. The chip can correct its readings for the room's humidity and temperature, so the program passes it the DS3231's temperature every minute.

The DS3231 Real-Time Clock

A Raspberry Pi has no clock that keeps time while it is switched off; it sets its time from the network. A DS3231 keeps the time on its coin cell for years, accurate to about 2 ppm thanks to its temperature-compensated crystal. It sits on the same I2C bus at address 0x68 and holds the seconds to the year in BCD registers. The program dates every reading and event with it, so a log stays correct even in a room without a network. Its temperature sensor, which it uses for the compensation, gives the program the room's temperature as a bonus.

The Colour Rules

How the air quality monitor chooses its colour
LightWhenMeaning
whitethe first 30 swarming up: the PMS5003's fan and the CCS811's run-in
greenAQI up to 50, eCO2 up to 800 ppm and TVOC up to 220 ppbgood air
yellowAQI 51 - 100, or eCO2 801 - 1500, or TVOC 221 - 660moderate: open a window soon
redAQI 101 - 200, or eCO2 above 1500, or TVOC above 660unhealthy: ventilate now
purpleAQI above 200very unhealthy or hazardous: leave the room or run a purifier

The colour is set by the worst of the three readings: a room can have clean particles and still be stuffy, or fresh air full of smoke.

The Python Code Explained

The whole program is included in the download. It uses pyserial for the particle sensor, smbus2 for the I2C chips, lgpio for the LED and the standard library's http.server for the dashboard. At the top are the pins and the timings:

RGB = (17, 27, 22)                                             # red, green, blue - BCM numbers
CCS_ADDR, RTC_ADDR = 0x5A, 0x68
WARMUP_S = 30                                                  # the PMS5003's fan needs 30 s for stable data (its manual)

Reading a PMS5003 Frame

The program waits for the two start bytes, reads the other 30, checks the sum and picks out the atmospheric values:

            if self.uart.read(1) != b'\x42' or self.uart.read(1) != b'\x4d':
                continue                                       # (find the start of a frame)
            if len(frame) != 32 or sum(frame[:30]) != (frame[30] << 8 | frame[31]):
            words = [frame[4 + 2 * k] << 8 | frame[5 + 2 * k] for k in range(13)]
            return words[3], words[4], words[5]                # PM1.0, PM2.5, PM10 under atmospheric conditions

The AQI Function

Each band is a tuple of the concentration range and the AQI range; the function finds the band and interpolates inside it:

PM25_BP = [(0.0, 9.0, 0, 50), (9.1, 35.4, 51, 100), (35.5, 55.4, 101, 150), (55.5, 125.4, 151, 200), (125.5, 225.4, 201, 300),
    for lo, hi, ilo, ihi in table:
        if c <= hi:
            return round((ihi - ilo) / (hi - lo) * (max(c, lo) - lo) + ilo)

Starting the CCS811

A CCS811 powers up in its boot mode. The program checks its hardware ID, starts the application with a write of the register address 0xF4 alone, and sets a measurement every second:

        hw = bus.read_byte_data(CCS_ADDR, 0x20)                # HW_ID: always 0x81
        bus.write_byte(CCS_ADDR, 0xF4)                         # APP_START: a write of the register address alone
        bus.write_byte_data(CCS_ADDR, 0x01, 0x10)              # MEAS_MODE: drive mode 1, a measurement every second

Then, whenever the STATUS register's DATA_READY bit is set, eight bytes from ALG_RESULT_DATA hold eCO2 and TVOC, high byte first:

        d = self.bus.read_i2c_block_data(CCS_ADDR, 0x02, 8)

The compensation data goes the other way: humidity and temperature in steps of 1/512, the temperature offset by 25 degrees C:

        h, c = round(rh * 512), round((t + 25) * 512)

Reading the Clock

The DS3231 keeps each value in BCD, two decimal digits in one byte, so 0x45 means 45. One block read gets all seven time registers:

    def bcd(v):
        return (v >> 4) * 10 + (v & 0x0F)

The Level and the Light

Once a second, the monitor thread combines the readings into one level, 0 to 3, and lights the matching colour:

    return max(pm, co2, voc)
LEVEL_COLOUR = ['GREEN', 'YELLOW', 'RED', 'PURPLE']

Two threads share the work: one reads the PMS5003 frames as they arrive, and one handles the gases, the clock, the level and the log; the web server runs in the main thread. A lock keeps them from touching the shared readings at the same time. Every 10 seconds, a row goes into the log that /log.csv serves.

How to Run the Proteus Simulation

  1. Download the project zip below and install the library files and the TEPPi folder (the steps are under the download button).
  2. Open Pi-5-Air-Quality-Monitor.pdsprj from the Proteus Simulation folder. Keep air_quality.py and the DLL files next to it.
  3. Press Play. The light is white while the sensors warm up, then green for clean air.
  4. Open the Laptop WiFi Monitor (PC1) or the Mobile WiFi Monitor (MOB1) and press Go.
  5. In the PMS5003's pop-up (U1), press COOKING: smoke fills the room and the light turns purple. Press PURIFIER to clean it.
  6. In the CCS811's pop-up (U2), press PAINT for VOCs, or MEETING for a stuffy room, then VENTILATE.

Proteus Simulation Results

Here is one complete run of the simulation, with the numbers from our final test run. Every time below is the DS3231's, which started at 10:05:00. The Pi's terminal shows the program finding the clock at 0x68 with 25.00 degrees C, the light white while the sensors warm up, and a status line every 5 seconds. At 10:05:30 the warm-up ended: "ready - the light is GREEN", AQI 21 GOOD, with PM2.5 at 6 micrograms, eCO2 at 442 ppm and TVOC at 6 ppb:

Figure: The Pi's terminal: the warm-up and the first readings.

Clean Air: the Sensors and the Dashboard

Figure: The particle sensor in clean air, with its last frame byte by byte.
Figure: The gas sensor in fresh air.
Figure: The clock, read once a second by the program.

The pop-ups show both ends of each link: the PMS5003's last frame starts with 42 4D and its sum matches, the CCS811's eight result bytes are the ones the program reads, and the DS3231's list of I2C transactions shows the program reading the time registers and then the temperature, once a second.

Figure: The dashboard on the laptop.
Figure: The same dashboard on the phone, the links as buttons.
Figure: The air quality light: green.

A Smoking Pan

Now COOKING in the PMS5003's pop-up: a pan of oil smoking. The readings jumped within seconds, while the 30-second average climbed behind them, so the light stepped up one colour at a time: YELLOW at 10:05:59 (AQI 54), RED at 10:06:03 (AQI 138, UNHEALTHY FOR SENSITIVE GROUPS) and PURPLE at 10:06:20 (AQI 208, VERY UNHEALTHY). The particle readings peaked at PM2.5 208 micrograms at 10:06:06, and the AQI at 230 at 10:06:30.

Figure: The particle sensor during the cooking: the smoke curve.
Figure: The laptop while the smoke is at its worst.
Figure: The whole project running in Proteus.
Figure: Red and blue together: purple.
Figure: The phone: AQI 220.

Notice the lag on the laptop: the instant PM2.5 was 151 micrograms, but the 30-second average was 138.7 and gave the AQI 214. That is the average at work. It keeps one smoky second from flashing the light, at the price of reacting a few seconds later.

The Air Purifier

Then PURIFIER in the same pop-up: the air went back to clean with a time constant of 15 seconds. The light walked back down through the colours: RED at 10:06:39 (AQI 197), YELLOW at 10:07:14 (AQI 90) and GREEN at 10:07:41 (AQI 47). The peaks on the page kept the worst moment, PM2.5 208 at 10:06:06.

Figure: The particle sensor after the purifier: the whole smoke episode on its chart.
Figure: The laptop: green again.

Fresh Paint

Next, PAINT in the CCS811's pop-up: paint fumes, a TVOC of 450 ppb, with the room's real CO2 at only 600 ppm. The light went YELLOW at 10:07:47 (eCO2 881 ppm) and RED at 10:07:49 (eCO2 1863 ppm). The readings peaked at eCO2 3338 ppm and TVOC 441 ppb at 10:08:01.

Figure: The gas sensor with paint fumes: eCO2 far above the real CO2.
Figure: The phone: VENTILATE NOW.
Figure: The light: red.

This run shows the CCS811's main limitation clearly. Its eCO2 is worked out from the VOCs, so paint fumes look like a room full of people: 3338 ppm of eCO2 while the real CO2 was 600 ppm. The TVOC alone said moderate. For a classroom that must know its real CO2, use an NDIR CO2 sensor; for spotting fumes and stale air in general, the CCS811 does the job.

Fresh Air Again

Finally, VENTILATE in the CCS811's pop-up: a window opened. The light went YELLOW at 10:08:25 (eCO2 1361 ppm) and GREEN at 10:08:45 (eCO2 788 ppm, TVOC 58 ppb).

Figure: The gas sensor while the room airs out.
Figure: The laptop at the end: the peaks of the whole run.
Figure: LOG on the laptop: the CSV log.

The LOG link shows the CSV log: a row every 10 seconds with the DS3231's date and time, the three particle readings, the AQI, eCO2, TVOC, the temperature and the colour of the light. Copy it into a spreadsheet and the whole smoke episode becomes a chart.

Figure: The clock at the end of the run.
Figure: The Pi's terminal at the end of the run.
The final simulation run in numbers
TimeWhat happenedLight
10:05:00 - 10:05:30warm-up; then AQI 21, eCO2 442 ppmwhite, then green
10:05:59 - 10:06:20COOKING: AQI 54, 138, then 208yellow, red, purple
10:06:06 / 10:06:30the peaks: PM2.5 208 ug/m3, AQI 230purple
10:06:39 - 10:07:41PURIFIER: AQI 197, 90, then 47red, yellow, green
10:07:47 - 10:08:01PAINT: eCO2 881, 1863, peak 3338 ppm; TVOC 441 ppbyellow, red
10:08:25 - 10:08:45VENTILATE: eCO2 1361, then 788 ppmyellow, green

The whole run covered 234 seconds of simulation and took about 17 minutes on our PC, including all the screenshots.

Download the Project Files

Click the button below to download Raspberry-Pi-5-Air-Quality-Monitor-Proteus-Simulation-v1.0.zip (about 14.3 MB, without the source code of the models):

Raspberry Pi 5 Air Quality Monitor - Proteus Simulation V1.0
  • Proteus Simulation: Pi-5-Air-Quality-Monitor.pdsprj, the program air_quality.py and copies of the models.
  • Python Code: air_quality.py, the complete program.
  • Proteus Library Files: the LIB files of the Raspberry Pi 5, the PMS5003, the CCS811, the DS3231, the RGB LED, the resistor and the two WiFi Monitors.
  • Proteus Model Files: the DLL models and the TEPPi folder, the Python 3.11 and the runtime that the Pi board runs.
  • README.txt: the installation steps, the properties, the test results and the links to every library post.

No Proteus yet? Read How to Download and Install Proteus 7 and 8. New to libraries? Follow How to Install a New Library in Proteus.

How to Install the Project Files

  1. Close Proteus and extract the whole zip file, for example to your Desktop.
  2. Copy the files from Proteus Library Files into the LIBRARY folder of Proteus.
  3. Copy everything in Proteus Model Files, the DLL files and the TEPPi folder, into the MODELS folder.
  4. Start Proteus and open the project from the Proteus Simulation folder.

Changing the Code

The program is plain Python 3: edit air_quality.py beside the project and press Play again; the Pi board runs the new file. The same program runs on a real Raspberry Pi 5 with Raspberry Pi OS: enable I2C and the serial port (without the login shell) in raspi-config, and install pyserial and smbus2 if they are missing. On a real CCS811, set CCS_RUNIN_S to 1200 for the datasheet's 20 minutes. For another Raspberry Pi 5 project with a web page, see our Raspberry Pi 5 Smart Parking System.

Ideas to Extend the Project

  • Add a humidity sensor such as an AHT20 or a BME280 and pass the real humidity to the CCS811.
  • Switch a fan or an air purifier with a relay when the AQI passes 100.
  • Keep the log on the SD card as a CSV file for each day, named by the DS3231's date.
  • Compute the NowCast, the EPA's 12-hour weighted average, for an AQI closer to the official one.
  • Send the readings to a cloud dashboard or a phone app through /data.
  • Add a small OLED or LCD so the numbers can be read without a phone.

From Simulation to Real Hardware

  • Give the PMS5003 room to breathe: keep its inlet and outlet clear and away from the floor, where dust settles.
  • Burn in the CCS811: a new sensor needs 48 hours of running, and 20 minutes after each start, before its readings settle.
  • Mind the eCO2: the CCS811 estimates CO2 from VOCs; for a true CO2 reading, use an NDIR sensor such as the SCD40 or the MH-Z19.
  • Free the UART: on a real Pi, disable the serial login shell so the PMS5003 owns /dev/serial0.
  • Keep the GPIO currents low: the three LED channels together stay well under the Pi's limits with these resistors.

Troubleshooting

  • The particle readings stay at zero: the PMS5003's TX is not on GPIO15 RXD, or its 5 V is missing.
  • "CCS811 not found": SDA and SCL are swapped, ADD is high (the chip is at 0x5B), or WAK is not LOW.
  • eCO2 stays at 400 ppm: the CCS811 is still in its run-in; wait for the RUNIN time.
  • The light stays off: the LED's cathode is not on ground, or the colours are not on GPIO17, 27 and 22.
  • The terminal cannot start Python: the TEPPi folder is not in MODELS.

Limitations of the Simulation

  • The air comes from the sensors' pop-ups: a preset sets the air at once, while the readings follow it with the sensors' response times.
  • The CCS811's run-in is 20 seconds instead of 20 minutes, set by its RUNIN property, so the demonstration does not wait.
  • The humidity passed to the CCS811 is a fixed 50 %RH; there is no humidity sensor in this project.
  • The network is simulated: the dashboard travels between the Pi and the two monitors inside Proteus; the times in this tutorial are the DS3231's.
  • Raspberry Pi is a trademark of Raspberry Pi Ltd; PMS5003 (Plantower), CCS811 (ScioSense) and DS3231 (Analog Devices) are names of their owners; this project is not made or endorsed by them or by Labcenter.

So, that was all about the Raspberry Pi 5 Air Quality Monitor - Proteus Simulation. I hope it helps you build your own: a laser particle sensor on the UART, a gas sensor and a clock on I2C, an AQI worked out in Python, a colour light and a web dashboard with a log, all tested in a simulation before a sensor goes on a wall. More IoT projects with the ESP32 and the Raspberry Pi 5 are coming in this series. If you have any questions, ask in the comments and I will help you out. Till the next tutorial, take care and have fun!