Using a PMS5003 Sensor to detect particle sizes of 1um, 2.5um and 10um, implemented via ESP Home on Home Assistant:
PMS5003 wiring
The PMS5003 uses 5V power and 3.3V for control.
- Connect pin one to the 5v VIN pin on the ESP8266
- Connect Pin 2 to any ground pin.
- Connect Pin 3 to D3
- Connect Pin 4 to D6
- Connect Pin 5 to D7


esphome:
name: aqspms5003
esp8266:
board: d1_mini
# Enable logging
logger:
# Enable Home Assistant API
api:
ota:
password: "db13249d8b5dff2bf079cbe585af974d"
wifi:
ssid: "your Wi-Fi SSID"
password: "your Wi-Fi PASSWORD"
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Aqs Fallback Hotspot"
password: "uOrOJjJeYvqg"
captive_portal:
uart:
rx_pin: D7
tx_pin: D6
baud_rate: 9600
id: pms5003_uart
sensor:
- platform: pmsx003
type: PMSX003
pm_1_0:
name: "PMS5003 <1.0µm Concentration"
accuracy_decimals: 1
pm_2_5:
name: "PMS5003 <2.5µm Concentration"
accuracy_decimals: 1
pm_10_0:
name: "PMS5003 <10.0µm Concentration"
accuracy_decimals: 1
update_interval: 120s

Arduino Serial Interface
#include <SoftwareSerial.h>
SoftwareSerial pmsSerial(2, 3);
void setup() {
// our debugging output
Serial.begin(115200);
// sensor baud rate is 9600
pmsSerial.begin(9600);
}
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() {
if (readPMSdata(&pmsSerial)) {
// reading data was successful!
Serial.println();
Serial.println("---------------------------------------");
Serial.println("Concentration Units (standard)");
Serial.print("PM 1.0: "); Serial.print(data.pm10_standard);
Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard);
Serial.print("\t\tPM 10: "); Serial.println(data.pm100_standard);
Serial.println("---------------------------------------");
Serial.println("Concentration Units (environmental)");
Serial.print("PM 1.0: "); Serial.print(data.pm10_env);
Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_env);
Serial.print("\t\tPM 10: "); Serial.println(data.pm100_env);
Serial.println("---------------------------------------");
Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(data.particles_03um);
Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(data.particles_05um);
Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(data.particles_10um);
Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(data.particles_25um);
Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(data.particles_50um);
Serial.print("Particles > 10.0 um / 0.1L air:"); Serial.println(data.particles_100um);
Serial.println("---------------------------------------");
}
}
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;
}