Particles Detector for Air Quality

Particles Detector for Air Quality © LGPL

In this project I show how to build a particles detector with data display, data backup on SD card and IoT with Android application.

  • 5,073 views
  • 4 comments
  • 27 respects

Components and supplies

Necessary tools and machines

Apps and online services

About this project

INTRO:

In this project I show how to build a particle detector with data display, data backup on SD card and IoT. Visually a NeoPixel ring display indicates the air quality.

Air quality is an increasingly important concern today. there are systems to measure the dust rate but they are very expensive.

There are low-cost, high-quality particle detectors on the market, as shown by some studies.

https://www.atmos-meas-tech.net/11/4823/2018/amt-11-4823-2018.pdf

I therefore decided to build a device capable of measuring the number of particles by size classes (0.5µm to 10 µm), visually with a simple display of the result (NeoPixel ring), a more detailed display on a TFT screen, and a time-stamped backup on an SD card..

In addition I have added a bluetooth communication module to be able to communicate with an android application and thus publish the results on an IoT server.

The overall cost of the whole does not exceed 60€.

Result:

You can see how the system works in the video below.

I hope that you like this project!

The result

I developed a new software with display of the curves in real time for each class of particles whose here is the result.

Code

PMS 5003 with ST7735 and neopixel ring bluetoothArduino
arduino code
#include <SoftwareSerial.h>
#include <Wire.h>    // Bibliothque pour l'I2C
#include "RTClib.h"  // Bibliothque pour le module RTC
RTC_DS1307 RTC; 
#include <Adafruit_NeoPixel.h>

// Which pin on the Arduino is connected to the NeoPixels?
#define PIN        6 // On Trinket or Gemma, suggest changing this to 1

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 24 // Popular NeoPixel ring size
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
uint32_t vert = pixels.Color(0, 250, 0);
uint32_t orange = pixels.Color(250, 250, 0);
uint32_t rouge = pixels.Color(255, 0, 0);


SoftwareSerial pmsSerial(2, 3);
#define cs   10
#define dc   9
#define rst  8  // you can also connect this to the Arduino reset

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>
#include<SD.h>
const int cs_sd=4;
int temps;                // temps d'acquisition
double tempsInit;         // initialisation du timer au dmarrage du loop()


#if defined(__SAM3X8E__)
    #undef __FlashStringHelper::F(string_literal)
    #define F(string_literal) string_literal
#endif

// Option 1: use any pins but a little slower
//Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, mosi, sclk, rst);

// Option 2: must use the hardware SPI pins
// (for UNO thats sclk = 13 and sid = 11) and pin 10 must be
// an output. This is much faster - also required if you want
// to use the microSD card (see the image drawing example)
Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst);
 float nombre_leds=0;
void setup() {
Serial.begin(9600);
 // Initialise la liaison I2C  
  Wire.begin();
 
  // Initialise le module RTC
  RTC.begin();
  
Serial.print("init SD");
  delay(1000);
  if(!SD.begin(cs_sd))    //Condition vrifiant si la carte SD est prsente dans l'appareil
  {
   Serial.print("Defaut SD");
    return;
  }
 Serial.print("Carte SD OK");

 File data = SD.open("donnees.txt",FILE_WRITE);              // Ouvre le fichier "donnees.txt"
  data.println(""); data.println("Dmarrage acquisition");    // Ecrit dans ce fichier
  data.close();
  
  tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab
  Serial.println("init");
  // our debugging output
  
  
tft.fillScreen(ST7735_BLACK);
  // sensor baud rate is 9600
  pmsSerial.begin(9600);

  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.setBrightness(2);

  
}

struct pms5003data {
  uint16_t framelen;
  uint16_t pm10_standard, pm25_standard, pm100_standard;
  uint16_t pm10_env, pm25_env, pm100_env;
  uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
  uint16_t unused;
  uint16_t checksum;
};

  
  
struct pms5003data data;
    
void loop()
{
    
  pixels.clear(); // Set all pixel colors to 'off'
  
   DateTime now=RTC.now(); //Rcupre l'heure et le date courante
  
 //affiche_date_heure(now);

  
    temps = ((millis() - tempsInit))/1000 ;  // Dmarrage du chrono 



    
  if (readPMSdata(&pmsSerial)) {
    
 tft.fillScreen(ST7735_BLACK);
    
     tft.setCursor(10, 5);
      tft.setTextColor(ST7735_WHITE);
     tft.println(" nbre parts/ 0.1 l");

     
    tft.setCursor(10, 17);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("0.3 um  ");tft.print(data.particles_03um);


tft.setCursor(10, 29);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("0.5 um  ");tft.print(data.particles_05um);

tft.setCursor(10, 41);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("1.0 um  ");tft.print(data.particles_10um);


tft.setCursor(10, 53);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("2.5 um  ");tft.print(data.particles_25um);

tft.setCursor(10, 65);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("5.0 um  ");tft.print(data.particles_50um);

tft.setCursor(10, 77);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("10 um  ");tft.print(data.particles_100um);

tft.setCursor(2, 89);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("PM 1.0 ");tft.setTextColor(ST7735_YELLOW);tft.print(data.pm10_standard);tft.setTextColor(ST7735_GREEN);tft.print(" microg/m3");

  tft.setCursor(2, 100);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("PM 2.5 ");tft.setTextColor(ST7735_YELLOW);tft.print(data.pm25_standard);tft.setTextColor(ST7735_GREEN);tft.print(" microg/m3");


  tft.setCursor(2, 110);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("PM 10 ");tft.setTextColor(ST7735_YELLOW);tft.print(data.pm100_standard);tft.setTextColor(ST7735_GREEN);tft.print(" microg/m3");
  


  tft.setCursor(10, 5);
      tft.setTextColor(ST7735_WHITE);
      tft.setTextSize(1);
     tft.println(" nbre parts/ 0.1 l");

    // Serial.print(temps);
 //   Serial.print (" ");
  Serial.print ("#");
   Serial.print ("03m ");
   Serial.print(data.particles_03um);
   Serial.print (" ");
    Serial.print ("05m ");
    Serial.print(data.particles_05um);
    Serial.print (" ");
    Serial.print ("1m  ");
    Serial.print(data.particles_10um);
    Serial.print (" ");
    Serial.print ("25m ");
    Serial.print(data.particles_25um);
    Serial.print (" ");
    Serial.print ("50m ");
    Serial.print(data.particles_50um);
    Serial.print (" ");
    Serial.print ("100m  ");
    Serial.print(data.particles_100um);
    Serial.println (" ");
    
nombre_leds =int (((float (data.particles_03um)/65535)*24));
//nombre_leds =(8);
Serial.println (nombre_leds);

     if ((nombre_leds<=8) and (nombre_leds>=1)){
  pixels.fill(vert , 0, nombre_leds);
    
    }
    
    else if ((nombre_leds<=16) and (nombre_leds>=8)) {
      pixels.fill(vert , 0, 8);
      pixels.fill(orange , 8, ((nombre_leds)-8));
      
    }
    else if  (nombre_leds>16) {

      
      pixels.fill(vert , 0, 8);
      pixels.fill(orange , 8, 8);
      pixels.fill(rouge , 16, ((nombre_leds)-16));
    }
    else  if (nombre_leds<=1) {
      pixels.fill(vert , 0, 1);
    }
    pixels.show();   // Send the updated pixel colors to the hardware.

    
    
  // Dfinition donnes
  String PM03=String(data.particles_03um);
  String PM05=String(data.particles_05um);
  String PM10=String(data.particles_10um);
  String PM25=String(data.particles_25um);
  String PM50=String(data.particles_50um);
  String PM100=String(data.particles_100um);
  String PMS10=String(data.pm10_standard);
  String PMS25=String(data.pm25_standard);
  String PMS100=String(data.pm100_standard);
 
  
  
  String Temps=String(temps);

   //Ecriture des donnes dans le fichier texte
  File data=SD.open("donnees.txt",FILE_WRITE);
  data.println( Temps + " " + PM03+ " " + PM05 +" " +PM10+" " +PM25+" "+PM50+" " +PM100+" "+PMS10+" "+PMS25+" "+PMS100+" "); 
  data.close();
  
  }

  
}

boolean readPMSdata(Stream *s) {
  if (! s->available()) {
    return false;
  }
  
  // Read a byte at a time until we get to the special '0x42' start-byte
  if (s->peek() != 0x42) {
    s->read();
    return false;
  }

  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;
  }
    
  uint8_t buffer[32];    
  uint16_t sum = 0;
  s->readBytes(buffer, 32);

  // get checksum ready
  for (uint8_t i=0; i<30; i++) {
    sum += buffer[i];
  }

  /* debugging
  for (uint8_t i=2; i<32; i++) {
    Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", ");
  }
  Serial.println();
  */
  
  // The data comes in endian'd, this solves it so it works on all platforms
  uint16_t buffer_u16[15];
  for (uint8_t i=0; i<15; i++) {
    buffer_u16[i] = buffer[2 + i*2 + 1];
    buffer_u16[i] += (buffer[2 + i*2] << 8);
  }

  // put it into a nice struct :)
  memcpy((void *)&data, (void *)buffer_u16, 30);

  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
    
  }
  // success!
  return true;
   
}

//Converti le numro de jour en jour /!\ la semaine commence un dimanche
String donne_jour_semaine(uint8_t j){ 
  switch(j){
   case 0: return "DIM";
   case 1: return "LUN";
   case 2: return "MAR";
   case 3: return "MER";
   case 4: return "JEU";
   case 5: return "VEN";
   case 6: return "SAM";
   default: return "   ";
  }
}

// affiche la date et l'heure sur l'cran
void affiche_date_heure(DateTime datetime){
  
  // Date 
  String jour = donne_jour_semaine(datetime.dayOfTheWeek()) + " " + 
                Vers2Chiffres(datetime.day())+ "/" + 
                Vers2Chiffres(datetime.month())+ "/" + 
                String(datetime.year(),DEC);
  
  // heure
  String heure = "";
  heure  = Vers2Chiffres(datetime.hour())+ ":" + 
           Vers2Chiffres(datetime.minute())+ ":" + 
           Vers2Chiffres(datetime.second());

           

  
 // Serial.print(jour);
//  Serial.print("  ");
 // Serial.print(heure);
  //Serial.print("  ");
  File data=SD.open("donnees.txt",FILE_WRITE);
  data.print(jour + " " + heure+" " ); 
  data.close();

  tft.setCursor(2, 120);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("date ");tft.setTextColor(ST7735_YELLOW);tft.print(jour);tft.setTextColor(ST7735_GREEN);tft.setCursor(2, 130);tft.print(" heure");tft.setTextColor(ST7735_YELLOW);tft.print(heure);

 delay(500);
  
}

//permet d'afficher les nombres sur deux chiffres
String Vers2Chiffres(byte nombre) {
  String resultat = "";
  if(nombre < 10)
    resultat = "0";
  return resultat += String(nombre,DEC);  
}
monitoring PMS 5003Java
mit app inventor application
No preview (download only).
new software with real time curveArduino
display improvement
#include <SoftwareSerial.h>
#include <Wire.h>    // Bibliothque pour l'I2C
#include "RTClib.h"  // Bibliothque pour le module RTC
RTC_DS1307 RTC; 
#include <Adafruit_NeoPixel.h>

// Which pin on the Arduino is connected to the NeoPixels?
#define PIN        6 // On Trinket or Gemma, suggest changing this to 1

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 24 // Popular NeoPixel ring size
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
uint32_t vert = pixels.Color(0, 250, 0);
uint32_t orange = pixels.Color(250, 250, 0);
uint32_t rouge = pixels.Color(255, 0, 0);


SoftwareSerial pmsSerial(2, 3);
#define cs   10
#define dc   9
#define rst  8  // you can also connect this to the Arduino reset

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library resolution 128X160

#include <SPI.h>
#include<SD.h>
const int cs_sd=4;
int temps;                // temps d'acquisition
double tempsInit;         // initialisation du timer au dmarrage du loop()


#if defined(__SAM3X8E__)
    #undef __FlashStringHelper::F(string_literal)
    #define F(string_literal) string_literal
#endif

// Option 1: use any pins but a little slower
//Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, mosi, sclk, rst);

// Option 2: must use the hardware SPI pins
// (for UNO thats sclk = 13 and sid = 11) and pin 10 must be
// an output. This is much faster - also required if you want
// to use the microSD card (see the image drawing example)
Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst);
 float nombre_leds=0;
 int x=2;
void setup() {
Serial.begin(9600);
 // Initialise la liaison I2C  
  Wire.begin();
 
  // Initialise le module RTC
  RTC.begin();
  
Serial.print("init SD");
  delay(1000);
  if(!SD.begin(cs_sd))    //Condition vrifiant si la carte SD est prsente dans l'appareil
  {
   Serial.print("Defaut SD");
    return;
  }
 Serial.print("Carte SD OK");

 File data = SD.open("donnees.txt",FILE_WRITE);              // Ouvre le fichier "donnees.txt"
  data.println(""); data.println("Dmarrage acquisition");    // Ecrit dans ce fichier
  data.close();
  
  tft.initR(INITR_GREENTAB);   // initialize a ST7735S chip, black tab
  Serial.println("init");
  // our debugging output
  
  
tft.fillScreen(ST7735_BLACK);

  // sensor baud rate is 9600
  pmsSerial.begin(9600);

  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.setBrightness(2);

  
}

struct pms5003data {
  uint16_t framelen;
  uint16_t pm10_standard, pm25_standard, pm100_standard;
  uint16_t pm10_env, pm25_env, pm100_env;
  uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
  uint16_t unused;
  uint16_t checksum;
};

  
  
struct pms5003data data;
    
void loop()
{
    
  pixels.clear(); // Set all pixel colors to 'off'
  
   DateTime now=RTC.now(); //Rcupre l'heure et le date courante
  
 affiche_date_heure(now);

  
    temps = ((millis() - tempsInit))/1000 ;  // Dmarrage du chrono 



    
  if (readPMSdata(&pmsSerial)) {
     
     
    tft.setCursor(10, 17);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.setCursor(10, 29);
  tft.print("0.3 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_03um);
  courbe();

tft.setCursor(10, 41);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("0.5 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_05um);

tft.setCursor(10, 53);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("1.0 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_10um);


tft.setCursor(10, 65);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("2.5 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_25um);

tft.setCursor(10, 77);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("5.0 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_50um);

tft.setCursor(10, 89);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("10.0 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_100um);




  tft.setCursor(10, 101);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("PM 10 ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.pm100_standard);tft.setTextColor(ST7735_GREEN,ST7735_BLACK);tft.print(" mic/m3 ");
  
  tft.drawLine(0,113,0,158,ST7735_RED);
  tft.drawLine(0,158,128,158,ST7735_RED);
 
  
    
nombre_leds =int (((float (data.particles_03um)/65535)*24));
//nombre_leds =(8);
Serial.println (nombre_leds);

     if ((nombre_leds<=8) and (nombre_leds>=1)){
  pixels.fill(vert , 0, nombre_leds);
    
    }
    
    else if ((nombre_leds<=16) and (nombre_leds>=8)) {
      pixels.fill(vert , 0, 8);
      pixels.fill(orange , 8, ((nombre_leds)-8));
      
    }
    else if  (nombre_leds>16) {

      
      pixels.fill(vert , 0, 8);
      pixels.fill(orange , 8, 8);
      pixels.fill(rouge , 16, ((nombre_leds)-16));
    }
    else  if (nombre_leds<=1) {
      pixels.fill(vert , 0, 1);
    }
    pixels.show();   // Send the updated pixel colors to the hardware.

    
    
  // Dfinition donnes
  String PM03=String(data.particles_03um);
  String PM05=String(data.particles_05um);
  String PM10=String(data.particles_10um);
  String PM25=String(data.particles_25um);
  String PM50=String(data.particles_50um);
  String PM100=String(data.particles_100um);
  String PMS10=String(data.pm10_standard);
  String PMS25=String(data.pm25_standard);
  String PMS100=String(data.pm100_standard);
 
  
  
  String Temps=String(temps);

   //Ecriture des donnes dans le fichier texte
  File data=SD.open("donnees.txt",FILE_WRITE);
  data.println( Temps + " " + PM03+ " " + PM05 +" " +PM10+" " +PM25+" "+PM50+" " +PM100+" "+PMS10+" "+PMS25+" "+PMS100+" "); 
  data.close();
  
  }

  
}

boolean readPMSdata(Stream *s) {
  if (! s->available()) {
    return false;
  }
  
  // Read a byte at a time until we get to the special '0x42' start-byte
  if (s->peek() != 0x42) {
    s->read();
    return false;
  }

  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;
  }
    
  uint8_t buffer[32];    
  uint16_t sum = 0;
  s->readBytes(buffer, 32);

  // get checksum ready
  for (uint8_t i=0; i<30; i++) {
    sum += buffer[i];
  }

  
  
  // The data comes in endian'd, this solves it so it works on all platforms
  uint16_t buffer_u16[15];
  for (uint8_t i=0; i<15; i++) {
    buffer_u16[i] = buffer[2 + i*2 + 1];
    buffer_u16[i] += (buffer[2 + i*2] << 8);
  }

  // put it into a nice struct :)
  memcpy((void *)&data, (void *)buffer_u16, 30);

  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
    
  }
  // success!
  return true;
   
}

//Converti le numro de jour en jour /!\ la semaine commence un dimanche
String donne_jour_semaine(uint8_t j){ 
  switch(j){
   case 0: return "DIM";
   case 1: return "LUN";
   case 2: return "MAR";
   case 3: return "MER";
   case 4: return "JEU";
   case 5: return "VEN";
   case 6: return "SAM";
   default: return "   ";
  }
}

// affiche la date et l'heure sur l'cran
void affiche_date_heure(DateTime datetime){
  
  // Date 
  String jour = donne_jour_semaine(datetime.dayOfTheWeek()) + " " + 
                Vers2Chiffres(datetime.day())+ "/" + 
                Vers2Chiffres(datetime.month())+ "/" + 
                String(datetime.year(),DEC);
  
  // heure
  String heure = "";
  heure  = Vers2Chiffres(datetime.hour())+ ":" + 
           Vers2Chiffres(datetime.minute())+ ":" + 
           Vers2Chiffres(datetime.second());

           

  
  
  File data=SD.open("donnees.txt",FILE_WRITE);
  data.print(jour + " " + heure+" " ); 
  data.close();

  tft.setCursor(18, 5);
 tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
 tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(jour);
  tft.setCursor(30, 17);tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(heure);

  
}

//permet d'afficher les nombres sur deux chiffres
String Vers2Chiffres(byte nombre) {
  String resultat = "";
  if(nombre < 10)
    resultat = "0";
  return resultat += String(nombre,DEC);  
}

void courbe() {


  int nouvelleValeur03;
  int nouvelleValeur05;
  int nouvelleValeur10;
  int nouvelleValeur25;
  int nouvelleValeur50;
  
  
  nouvelleValeur03 = map((data.particles_03um), 2, 65535, 158, 113); 
  nouvelleValeur05 = map((data.particles_05um), 2, 65535, 158, 113);
  nouvelleValeur10 = map((data.particles_10um), 2, 65535, 158, 113);
  nouvelleValeur25 = map((data.particles_25um), 2, 65535, 158, 113);
  nouvelleValeur50 = map((data.particles_50um), 2, 65535, 158, 113);
  
  x++;
  
  tft.drawPixel(x,nouvelleValeur03,ST7735_CYAN);
  tft.drawPixel(x,nouvelleValeur05,ST7735_YELLOW);
  tft.drawPixel(x,nouvelleValeur10,ST7735_RED);
    tft.drawPixel(x,nouvelleValeur25,ST7735_WHITE);
    tft.drawPixel(x,nouvelleValeur50,ST7735_BLUE);
  
  if (x>123) {
    x=2;
    tft.fillRect(2,113,126,45,ST7735_BLACK);
    
  }
}
SD card library
library for SD card
neopixel ring library
library for use neopixel ring

Schematics

TFT connecting
SainSmart 1.8″ TFT Arduino Display connecting
Tft display arduino with sd card qaa9dlgrm0
PMS 5003
connecting PMS 5003
Tft display arduino with sd card connect eiofxnzvxb
complete schematic
the full connecting
Schema pms5003 jvipgdnczn

Comments

Similar projects you might like

Air Quality Monitor

Project showcase by Mustafa Hesham

  • 10,227 views
  • 9 comments
  • 41 respects

Wooden Chest Air Quality Monitor

Project tutorial by Fillbee

  • 2,982 views
  • 2 comments
  • 11 respects

Arduino Air Quality Monitor with DSM501A Sensor

Project tutorial by Mirko Pavleski

  • 10,654 views
  • 1 comment
  • 23 respects

DIY Air Quality Monitor with Sharp GP2Y1010AU0F Sensor

Project tutorial by Mirko Pavleski

  • 1,475 views
  • 0 comments
  • 6 respects

PPD42NS Arduino Air Quality Monitor in 3D Printed Enclosure

Project tutorial by Mirko Pavleski

  • 1,369 views
  • 0 comments
  • 7 respects
Add projectSign up / Login