Posted on 39 Comments

#39: DIY air quality sensor, part 2 – “Display” version

It’s surprisingly easy to make your own simple air quality sensor. All you need is a cheap laser-scattering particulate matter sensor, a Wemos D1 Mini, a soldering iron, and Tasmota.

Part 1 showed how to make the simplest possible air quality sensor. Make sure you’ve seen that first, because Part 2 continues from Part 1 to add a 128×32 pixel OLED display and a mode button. We’re also going to install custom firmware to make the sensor last longer.


3D print the case

Printing a case is totally optional, of course. You can use whatever enclosure you like. My case has been designed to be a press-fit over the PMS5003, which holds the two halves together by friction. See the link above to download the STLs if you want to print it yourself, or you can buy a case from me if you don’t have access to a printer. I’ll include a 6x6x9mm tact switch with the “Display” version of the case:

Put switch into case

The space for the tact switch is very tight.

To fit it into the slot, trim off the pins from the right side and trim the pins on the left side so they are a couple of millimeters long. Bend those leads out at a 45 degree angle, and solder a pair of jumper wires to them.

Push the switch into the cavity in the case as shown above, and guide the wires around the slot to the left and then down.

The button connects between GND and I/O pin D3:

Connect 128×32 OLED to D1 Mini

The 0.91″ 128×32 OLED module already has I2C pull-ups included, so there’s no need to add them. All we need to do is connect power and I2C:

The connections are:

  • OLED GND to D1 Mini GND
  • OLED VCC to D1 Mini 3.3V
  • OLED SCK to D1 Mini pin D1
  • OLED SDA to D1 Mini pin D2

Connect PMS5003 to D1 Mini

In the “Basic” version of this project, we only needed to connect to the 5V, GND, and Tx pins of the PMS5003. Now we also need to connect I/O pin D6 to the sensor’s Rx pin, to allow commands to be sent to the sensor:

The connections are:

  • PMS5003 pin 1 (5V) to D1 Mini 5V
  • PMS5003 pin 2 (GND) to D1 Mini GND
  • PMS5003 pin 4 (Rx) to D1 Mini pin D6
  • PMS5003 pin 5 (Tx) to D1 Mini pin D4

Close the case

This can be more tricky than it sounds! Be gentle so you don’t damage anything.

With the electronics sitting neatly in the “back” (or “right”) half of the case, make sure all the wiring is neatly tucked into the slots. It should look something like this:

Slide the other half of the case over the modules, checking that no wires are pinched between the halves. The case should close completely.

Set up Arduino IDE for ESP8266

If you don’t already have it, download and install the Arduino IDE:

By default, the Arduino IDE does not support the ESP8266 processor on the D1 Mini. To add this support, go to the following URL and follow the instructions. The easiest method is to use the Boards Manager:

Install libraries

The sketch uses 4 libraries.

The “PMS” library is directly embedded within the program, so you don’t need to install it. This is done to force a specific version of the library, which was forked from the official version by GitHub user SwapBap to add extra features. There’s nothing you need to do because it’s part of the project.

In the Arduino IDE, go into “Sketch -> Include Library -> Manage Libraries…

Then search for each of these libraries, and click “Install”:

  • GFX library by Adafruit
  • SSD1306 library by Adafruit
  • PubSubClient library by Nick O’Leary

If you need more help, check out this tutorial:

Download “AirQualitySensorD1Mini” sketch

Download the AirQualitySensorD1Mini sketch from:

The easiest way is to click the green “Clone or download” button near the top right of the page and select “Download ZIP”. Once you’ve downloaded it, extract the folder and put it in your sketchbook directory. You can find the correct location for this by looking in the Arduino IDE preferences.

Configure and compile sketch

Open the Arduino IDE, open the sketch, and have a look at the 4 tabs across the top. You can ignore the first three. Open the “config.h” tab, and modify the settings for WiFi and your MQTT broker:

Plug in the Air Quality Sensor using a USB cable.

Select “Tools -> Board:” and set it to “LOLIN(WEMOS) D1 R2 & mini”. Select the port, and upload the sketch:

After the sketch has uploaded the Air Quality Sensor will reboot, connect to your WiFi and MQTT broker, and show values on the display once it has successfully received data from the PMS5003.

Stay tuned for part 3, which will explain how the sketch works and how to read the data from it.

39 thoughts on “#39: DIY air quality sensor, part 2 – “Display” version

  1. Hi there
    Really nice project. Keep up the good work
    What is missing on the printed enclosure is some small locking tabs inside, so the enclosure will not need to be glued to keep together

    1. That was my original plan, but when I printed a simple version of the case without any fastenings just to test the fit it was so good that I decided it didn’t need them. If you do a better case though please let me know. I’m a beginner at this sort of thing so I have a lot to learn.

      1. I too am a beginner in Fusion 360 🙂

  2. The case needs a offset around the perimeter like on this design 😉

  3. This is a really cool project and will be a nice addition to my home automation system. I’m very keen to see more smart projects like this that use generic MQTT.
    Great stuff!

  4. Great work buddy, I’m following you from a long time you are a real expert I really like how you explain things on their basics and sometimes you deep dive into HW and SW , so there is something for everyone.

  5. I created a PR for tasmota (that just got merged) that added support for SLEEP/WAKE for the PMS5003 sensors in Tasmota:

    1. This is fantastic, great work!

  6. Hello is there a reason you use the PMS5003? better support or?
    I saw that this is an older sensor and there are several tiers of newer versions, even on your aliexpress link it has a few of the newer ones.
    I was just curious if there’s actually differences or anything?

    1. I partly explained this in part 1. It’s because the 5003 comes with a cable that can be used to easily connect it to a board like a D1 Mini simply by cutting the cable and soldering it on. Later sensors such as the PMS7003 have a 1.27mm pitch IDC connector, which requires the use of an adapter board to make easy connections to it. The “Pro” version of the project (which will probably be part 5) uses the 7003 on a fully custom PCB with an ESP32, a BME680, and USB-C.

      1. Oh ok thank you, I saw that video I just thought I missed the follow up but it isn’t out yet lol. BME680 that’ll be cool I have several BMP280 and BME280 that I use, but none do gas sensing.
        I saw on their site this page: (their site is not completed it seems, these aren’t listed under the products tab) the ‘L” versions have i2c as well for the PMS7003 and PMSA003 but I couldn’t find those existing anywhere other than this page LOL!
        I also saw this which seems to be a great post with great information.. but no comments / viewers so idk if it works or not. I’ll have to test it! From looking the A003 seems the same just a smaller frame than the 7003
        Thanks for the info & looking forward to the next parts! Have a good one!

        1. Oh wait I lied it’s an i not an L lmao

          I think this is it, it says i2c. Idk if anyone cares really but maybe they have a specific chain of i2c devices and want to use all i2c stuff idk.

      2. Are the newer sensors (PMS7003 or PMSA003) any more accurate/stable than the older PMS5003? I know the newer sensors are smaller.

        The 0.3u particle count for the 5003 tends to bounce around a lot.. Just wondering if the newer sensors are better in that regard.

        1. Possibly with them changing the sizes and the air pathways it is to help efficiency. The accuracy in the datasheet hasn’t changed in any of them. But I would (assume/hope (possibly that is dangerous to assume)) that their newer models have perfected the air flow of the device and helped with the stability of the readings etc. You can see my posts above maybe the links will give you some hints. The fans and overall sizes changed greatly.

        2. Hi, you can see a geat comparison and how is constructed also here

  7. Hi Jono,
    Is there a place/requirement to enter the user id and password of the MQTT broker?

    Great project and thanks for the case 🙂


    1. Hi!

      This works for me. On config.h add two lines after mqtt_broker:

      /* MQTT */
      const char* mqtt_broker = “”; // IP address of your MQTT broker
      const char* mqtt_username = “your_username”; // MQTT username
      const char* mqtt_password = “your_password”; // MQTT password

      1. Thanks for this. I finally had another chance to try. Unfortunately it did not work for me. However, when I allowed anonymous connections it connected.

      2. Thanks Tomi,

        I added the two lines you suggested and modified line 645 to include the two variable

        if (client.connect(mqtt_client_id, mqtt_username, mqtt_password))

        It now work with my credentials.

        THank you

        1. Thanks Tomi & Martin. I’m now connected.

  8. Hello,
    First, thank you Jonathan for this great project and all the documentation for it.
    i’m not sure if this is the place to ask for help, but i’ll give it a try. I have built this project with the oled screen and the tact switch. When i power it up, the screen show the information with the superhouse tv url, the device id…etc.. but when i click the button, nothing change… I think i can feel the fan blowing from the sensor and i verified all my connections.

    Looking into the code i realized that my mqtt server required a username and password… When i configure tasmota devices, i need to enter these into the mqtt configuration.

    I tried to add the user name and password to the connect function, but i was not successful 🙁

    Has anyone had the same problem and was able to change the code to support a MQTT account?

    Or maybe i have another problem, but so far my troubleshooting skills have brought me to this point.


    1. Update… I enable anonymous access to the MQTT brocker and the device properly run showing me results on the oled screen has expected… i will still try to figure out how i can add passing the user and password into the connect function.

      looking forward the next episode with logging this data into some database.. influx db maybe?

  9. Just finished putting together my AQS with display. Thank You Johnathon for the very detailed and concise instructions. It only around 20min after printing the case.
    Just need to learn a bit more about the results from the sensor, Appreciate if anyone has something to scale against. Looking to display on a home automation screen like a traffic light signal.

  10. Should the PMS5003 pin 4 (Rx) connect to D1 Mini pin D6 or D8? The above instructions say D6. But the config.h file points to D8.

    1. Well spotted, you are right (I had trouble with putting it to sleep). Just tested it and it works. Also noticed that after flashing had to add wire to RST >> D0.

    2. That’s indeed a wrong pin in config.h, as this way the PMS is continues running when not put into passive mode.

      I just wondered a few month why the heck the LED always blinks (connected to the RX pin on ESP). So now i had time to check and found this pin assign issue.

      Also, i added a
      into the code as the sensor always opens an AP and that’s not needed.

  11. Thank you for your work, its great. One question, I’ve noticed that my sensor/fan keeps running, any idea how to make sure that its in sleep? I’ve changed report interval to 900. I’m not a developer and struggling here a bit.

    1. Hi, I had same problem with no sleeping with one my PMS. Other piece from another manufacturer had no problems.
      Try to move “pms.sleep()” command after reports (“reportToSerial()”) or insert some delay between reading and “pms.sleep()” command.

  12. Just noticed. Diagram on YT video(and here) is missing D0 >> RST connection. I’ve added it, but still no luck with sleep.

  13. The software crashes out on my after two calls to OLED.

    I looked up the SSD1306 declaration and noticed that it is being declared in a deprecated manner. I wonder if this is something to do with it?

    This is what I get…

    Air Quality Sensor starting up, v2.4
    Device ID: E7B8A6

    Soft WDT reset


    ctx: cont
    sp: 3ffffd10 end: 3fffffc0 offset: 01a0
    3ffffeb0: 3ffeeedc 00000001 3ffeeedc 40207afc
    3ffffec0: 3ffeeedc 000000a0 00000005 40207bc0
    3ffffed0: 00000001 3ffeec4c 3ffeeedc 40207c7a
    3ffffee0: 3ffeec2f 0000003c 3ffeec2d 40207e2c
    3ffffef0: 007a1200 1443e7b8 3ffee800 00000001
    3fffff00: 00001054 3fff0fd1 3ffeec2d 40207e2c
    3fffff10: 3ffeec1c 4023f487 3ffee82c 40204444
    3fffff20: 00000000 3ffeed4c 3ffee82c 4020446c
    3fffff30: 00000000 3ffeed4c 3ffee82c 40203c94
    3fffff40: 00000000 00000008 3ffee82c 402068a8
    3fffff50: 00000000 3ffeed4c 3ffeed6c 3ffee82c
    3fffff60: 00000000 3ffeed4c 3ffeed6c 40201bef
    3fffff70: 00000040 00000000 feefeffe feefeffe
    3fffff80: feefeffe feefeffe feefeffe feefeffe
    3fffff90: feefeffe feefeffe feefeffe 3ffeee98
    3fffffa0: 3fffdad0 00000000 3ffeee58 40207464
    3fffffb0: feefeffe feefeffe 3ffe850c 40101505

    ets Jan 8 2013,rst cause:2, boot mode:(3,6)

    load 0x4010f000, len 3456, room 16
    tail 0
    chksum 0x84
    csum 0x84

    Any assistance will be greatly appreciated!

  14. Hi,
    Thanks for the project with the display. I will want to order a case once international post is possible, to Spain, thanks.
    So how did it go?
    Well apart from a couple of “I know what I am doing so I did not pay full attention” mistakes it is working.
    I told the IDE the wrong board, yes you had it correct and when I did it had the correct pins to talk to the sensor!
    I also did my own thing when it came to the PMS files which was fun and time consuming, much simpler downloading the zip!

    Your 3D printer and mine sort of disagree with the exact sizing or the board and so mine was not quite so good, the sensor fitted tight, the switch and board needed some Dremel mods and now they fit (ish). My bigger problem was the display and despite your warnings to be cautious, I damaged my first one and out comes the Dremel to allow the second one to work.
    My MQTT server has user and password so I looked into that and added the data in the main file. (Now I have viewed the 3rd video I am feeling a little guilty, happy it worked but oops!)
    I used some very thin wire so was able to add the earth wires all to the single earth pad on the ESP board and of course, being thin they feed into the groves in the case really well.
    Thank you, again, for a great little project and insight as to how you put it all together.

  15. Hello,
    thanks for this great project and the files supplied in the GitHub. Is it possible to get the F360-files of the case? The reason is, all parts fit fine in my printed case except the Wemos, it’s PCB is to thick fitting in the slots in the case.
    My plan is to redesign or modify the case for my needs.

    Thanks from Germany, MAX.

  16. Trap for experienced players who didn’t read the instructions properly check your board profile I had it set to ‘WEMOS D1 R1’ rather than ‘LOLIN(WEMOS) D1 R2 & mini’.

  17. Hi, i finally got this to work and the device itself works like a charm. It also sends out the data to my MQTT broker successfully. But as i am quite a noob in al this i was wondering if there is someone out there who can help me with a flow for node-red to inject al this info into my domoticz configuration……


  18. Excellent and useful post! I really appreciate the time we live in, where a total stranger from another part of the globe can help you and make a difference in your community. In Serbia the air quality is degrading in the winter times due to coal and wood burning for heat, so your post showed there is an easy and affordable air quality monitoring solution.
    I am a total noob, but I followed your tutorial word for word, and PMS is working without a hitch. Got the parts from Banggood for about 25 EUR (delivered in Serbia in 20 days). I used some Dupont wires (locally sourced) and printed the case with a shop that does 3D printing (about 7EUR).
    I noticed the readings that come off of my PMS are a bit higher than my home air purifier (Xiao Mi 3H), by about 5-10 gr/m3 (at first it was a lot bigger difference). I altered the code to not put the sensor to sleep, but to run continuously, and after a weekend of running it came close to the purifier values, but still differs (purifier says it is 4, but PMS says it is 10-11). I will mess with the code some more to find optimal measurement and warm-up times.

  19. Hi Jonathan! Thank you for this great project, detailed tutorial and the STL’s for the case. I created my version of AQS. It connects to WiFi and to my Home Assistant MQTT broker. If I subscribe to any topic I can see the data coming in, but for some unknown reason my MQTT integration didn’t discovered the sensor in Home Assistant. What setting should I change to have these sensor values available in Home Assistant?

  20. Hi Jonathon,
    Thanks for the work that you put into this project, it is a great resource and contains lots of information to help us get started in the world of air quality sensors. Your work is very much appreciated.
    BLUF: Clear the serial port read buffer prior to reading data.
    While I know that this is an old project and that you have probably moved on but I have just come across an issue with the D1 mini version of this project. The issue is that readings obtained are almost always sampled during the 30 second wakeup stabilization time and may in fact be up to 2 sample periods old. The issue arises because of two things. Firstly the Software Serial library used in the project has an input buffer of 64 bytes, secondly the Plantower module starts sending data some 4-5 seconds after being woken up, essentially going into active mode (I appreciate that other modules may have different behavior but that is what happens to the two PMSA003 modules I have). As the Software Serial buffer is not cleared before a read the data being read are the first 64 bytes seen which are those that arrived during the wakeup stabilization time. A simple fix is to clear the pms sensor serial port buffer prior to issuing a read. This ensures that the data being read is the freshest available. I hope that this is a helpful addition to this fine resource.

  21. Hi Jonathon,
    Thanks for the solid effort mate. I was wondering if the PMS sensor can be easily swapped out for the sensirion sps30? If so how would you go about it?

Leave a Reply

Your email address will not be published. Required fields are marked *