| Sydney bushfires 2019 - is the air indoors safe?

| A look at PM1, PM2.5 and PM10 indoors

| Date: 21 December 2019
| Tags: embedded


Particulate matter (PM) concentrations in Sydney have been extremely high for many days over the past several weeks due to bushfire smoke. To deal with this I have been wearing P2 masks while outside on bad days and have a HEPA air purifier at home, but unfortunately I find the P2 masks to be too uncomfortable to wear for extended periods while at work. My office is airconditioned but typically these filters aren’t useful for filtering PM1, PM2.5 or PM10. I was wondering how bad the air is inside the office over the day so I threw together a portable PM sensor from parts of another project and measured.


Here is a chart of the particulate concentration at my desk at work over the day of 19/12/19. Note that PM1 is particles with a diameter <= 1.0um, PM2.5 is <= 2.5um and PM10 is <= 10um, so the sensor showing PM1=100 and PM2.5=100 indicates that there are 100 ug/m3 of particles < 1.0um and ~0 ug/m3 of particles between 1.0um and 2.5um.

At 12:25 I measured the particulate concentrations (in ug/m3) outside as PM1: 366, PM2.5: 369, PM10: 371 and outside at 18:40 it was PM1: 8, PM2.5: 8, PM10: 9. From the graph only peaking at around 200 ug/m3 its clear that being inside is providing some insulation against the higher outside PM concentration. Notably in the graph the rate at which the PM concentration decreases drops at exactly 17:00 which is when the office air conditioning turns off, however I’m not sure if this decrease is due to filtering, reduced mixing of air or some other unknown effect.

Either way, the results are clear that my office provides some defence against outside PM, but not a lot and the office air still had high PM concentrations. Also as this was only a few hours of raised outside PM I will need to do more testing for when high PM concentrations extend over multiple days which will also help identify the role the AC is playing.

I found it interesting that the particles sensed were so small, with the sensor showing the vast majority were less than 1um. I didn’t realise bushfire smoke products were so small and first thought it was an issue with the firmware or sensor, but it turns out this is the case and most products are 200nm or smaller. A relevant paper on this is Particle size distributions from laboratory-scale biomass fires using fast response instruments by S. Hosseini et al.


The sensor used seems to be identical to the Plantower PMS7003M both physically and in the protocol used to interface with it. I purchased the SM-UART-04L from digikey for about $50AUD, while the PMS7003M is about $28AUD from Aliexpress. I’m not sure if there is a quality difference between them (update: some helpful researchers have looked into this). The connector for the particulate sensor is a 0.05” 2x5 male header, I used an Amphenol 20021311-00010T4LF female header (on a PCB) to connect to it, however you can get adaptor boards to 0.1” on aliexpress if you need it (listed as “G7A to G135”).

I also used a PCB I designed for an air quality monitor project I am working on. The relevant features of this board for this project are that it has a 5V -> 3.3V DC-DC, a USB connector, a fair amount of decoupling caps and a connector for the particulate sensor. This PCB was designed for a different microcontroller board so to connect the ESP32 I used dupont cables to the particulate sensor UART pins and power. For 5V power I used a USB powerbank, this can power the board for more than a day as the current draw is around 110mA while running.


For this project I used Arduino because there was a graphics library for the ST7789 from Adafruit and a PMS7003M library from Alvaro Valdebenito. I don’t like arduino for my projects, but in this case the project is quite simple and it was a lot easier than writing or porting a graphics library. The glue code between these is included in the following snippet:

  This is a library for several Adafruit displays based on ST77* drivers.

  Works with the Adafruit 1.8" TFT Breakout w/SD card
    ----> http://www.adafruit.com/products/358
  The 1.8" TFT shield
    ----> https://www.adafruit.com/product/802
  The 1.44" TFT breakout
    ----> https://www.adafruit.com/product/2088
  The 1.14" TFT breakout
  ----> https://www.adafruit.com/product/4383
  The 1.3" TFT breakout
  ----> https://www.adafruit.com/product/4313
  The 1.54" TFT breakout
    ----> https://www.adafruit.com/product/3787
  The 2.0" TFT breakout
    ----> https://www.adafruit.com/product/4311
  as well as Adafruit raw 1.8" TFT display
    ----> http://www.adafruit.com/products/618

  Check out the links above for our tutorials and wiring diagrams.
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional).

  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>
#include <PMserial.h>  // Arduino library for PM sensors with serial interface

#define TFT_CS         5
#define TFT_RST        16 // Or set to -1 and connect to Arduino RESET pin
#define TFT_DC         17

// For 1.14", 1.3", 1.54", and 2.0" TFT with ST7789:
Adafruit_ST7789 tft = Adafruit_ST7789(&SPI, TFT_CS, TFT_DC, TFT_RST);

SerialPM pms { PMS7003, Serial };

void set_color_with_limits(uint16_t value, uint16_t low, uint16_t medium);
void write_to_screen(uint16_t pm1, uint16_t pm2_5, uint16_t pm10);

void setup(void) {
  tft.init(240, 240, SPI_MODE2);           // Init ST7789 240x240

void loop() {
    write_to_screen(pms.pm01, pms.pm25, pms.pm10);

void write_to_screen(uint16_t pm1, uint16_t pm2_5, uint16_t pm10)
  tft.setCursor(0, 0);

  tft.print("PM1:   ");
  set_color_with_limits(pm1, 40, 80);
  tft.print(pm1, DEC);

  tft.print("PM2.5: ");
  set_color_with_limits(pm2_5, 40, 80);
  tft.print(pm2_5, DEC);

  tft.print("PM10:  ");
  set_color_with_limits(pm10, 40, 80);
  tft.print(pm10, DEC);

void set_color_with_limits(uint16_t value, uint16_t low, uint16_t medium)
  if(value <= low)
  else if(value <= medium)