ESP32 Indoor Air Quality Station: SCD41 + SGP40 + PMS5003 + SHT45

Quick Summary (TL;DR):
A serious Home Assistant indoor-air-quality station should not try to turn one cheap “air quality” sensor into everything. Use SCD41 for true CO₂, SGP40 for VOC Index, PMS5003 for PM1/PM2.5/PM10, and SHT45 for the primary room temperature and humidity. These four sensors measure different physical problems: CO₂ tells you about ventilation/occupancy, VOC Index reacts to chemical/gaseous pollution, PMS5003 detects airborne particles such as cooking smoke and dust, and SHT45 gives accurate environmental temperature/humidity while also improving the SGP40 gas compensation. Current ESPHome supports all four directly. SCD41 uses scd4x at I²C address 0x62; SHT45 uses sht4x at 0x44; SGP40 uses sgp4x and can take the SHT45 temperature/humidity entities as compensation sources; PMS5003 uses pmsx003 on a 9600-baud UART. Current ESPHome also renamed SGP4x outputs from voc/nox to voc_index/nox_index; the old names are deprecated and scheduled for removal in ESPHome 2027.2.0. The SGP40 VOC algorithm must be driven internally at 1 Hz, which ESPHome handles automatically even if Home Assistant only receives an updated VOC Index every 30–60 seconds. For PMS5003, if you connect both UART RX and TX and choose an update interval above 30 seconds, current ESPHome can sleep/wake the sensor automatically; 120 seconds or longer is sensible for a permanent monitor when you want to reduce fan/laser wear. The biggest challenge is not YAML—it is enclosure airflow and thermal layout. Keep SCD41 and SHT45 away from the ESP32, voltage regulator and PMS5003 exhaust; give every sensor access to real room air; do not seal them in one stagnant plastic box. For automation, treat each pollutant separately rather than producing one fake “IAQ percentage”: CO₂ can trigger ventilation, PM2.5 can boost extraction or filtration, VOC Index can flag solvents/cooking/cleaners, and humidity can control dehumidification. This gives Home Assistant a real multi-pollutant view of the room instead of one ambiguous gas-resistance number.

Materials You’ll Need

ItemRole
ESP32 development boardRuns ESPHome, I²C and UART
SCD41True CO₂ measurement using photoacoustic sensing
SGP40VOC Index / chemical-air-quality trend
PMS5003PM1.0, PM2.5 and PM10 particulate matter
SHT45Accurate room temperature and relative humidity
Stable 5 V USB supplyPowers ESP32 and PMS5003
3.3 V railTypical safe supply for SHT45/SGP40 breakout logic
Ventilated enclosureAllows representative room air to reach sensors
Home AssistantDashboards, history, alerts and ventilation automation

Why Four Sensors Instead of One

Pollutant / variableSensorWhat it tells you
CO₂SCD41Ventilation effectiveness and occupancy-related CO₂ buildup
VOC trendSGP40Changes in volatile organic gas mixture
PM1/PM2.5/PM10PMS5003Airborne particulate / smoke / dust / aerosols
Temperature + RHSHT45Thermal comfort, humidity and SGP40 compensation

These measurements are complementary. High CO₂ does not imply high PM2.5. A room can have excellent CO₂ but terrible cooking particles, or low particles but strong VOCs from cleaning products.

Do Not Create One Fake IAQ Percentage

Combining CO₂, PM2.5, VOC Index and humidity into one arbitrary 0–100 score hides useful information. Home Assistant should expose the actual measurements and let automations react to the relevant pollutant.

CO2 high
→ ventilation problem

PM2.5 high
→ particles / smoke / aerosol problem

VOC Index high
→ gaseous/chemical event

Humidity high
→ moisture / condensation risk

Why SCD41 for CO₂

SCD41 is a true optical/photoacoustic CO₂ sensor, not an “eCO₂” estimate derived from VOC resistance. Sensirion specifies its main accuracy range from approximately 400 to 5000 ppm, with a typical response time around 60 seconds.

For a room monitor, this is exactly what you want: a real CO₂ concentration in ppm that can be used directly for ventilation decisions.

SCD41 vs eCO₂

True SCD41 CO₂VOC-derived eCO₂
Measures CO₂ physicallyEstimates CO₂ from gas/VOC behaviour
Outputs ppm based on CO₂ sensingCan be fooled by cleaners/perfume/cooking
Useful for ventilationUseful only as a rough proxy

Do not substitute an SGP40 VOC Index for real CO₂. The sensors answer different questions.

SCD41 ESPHome Basics

sensor:
  - platform: scd4x
    id: scd41_sensor
    co2:
      name: "Indoor CO2"
      id: indoor_co2
    update_interval: 30s

Current ESPHome defaults the SCD4x I²C address to 0x62 and the reporting interval to 60 seconds.

SCD41 Measurement Modes

ModeBehaviourUse
periodicNew measurement every ~5 sFast normal monitoring
low_power_periodicNew measurement every ~30 sLower-power fixed node
single_shotSCD41 only; one measurement per intervalLow-power specialised use
single_shot_rht_onlyTemperature/RH only; CO₂ reports 0Not useful for normal IAQ CO₂ monitoring

For a mains-powered Home Assistant IAQ station, periodic or low_power_periodic is simpler than single-shot operation.

Automatic Self-Calibration

ESPHome enables SCD4x automatic self-calibration by default. For a normal occupied building that periodically receives fresh-air CO₂ conditions, this is usually the right starting point.

Do not repeatedly force-calibrate because two cheap sensors disagree by 50 ppm. Forced calibration should be done only against a known reference condition after the sensor has stabilised in the same operating mode.

SCD41 Temperature Is Not My Primary Room Temperature

SCD41 contains internal temperature/humidity sensing because those values are needed for its own compensation and are also available as outputs. However, the SCD4x package and nearby electronics can create a temperature offset. Current ESPHome even defaults the SCD4x temperature offset setting to 4°C.

For this multi-sensor station, use the SHT45 as the primary exposed room temperature/humidity sensor. Keep SCD41 temperature/humidity diagnostic or omit them from the UI if you do not need them.

Why SHT45 Is the Better Environmental Reference

Sensirion specifies SHT45 around ±0.1°C typical temperature accuracy and ±1% RH typical humidity accuracy. That makes it a much stronger standalone room sensor than using SCD41’s thermal reading from inside a busy electronics enclosure.

Primary room temperature → SHT45
Primary room humidity    → SHT45
CO2                      → SCD41
VOC compensation         → SHT45 T + RH

SHT45 ESPHome Configuration

sensor:
  - platform: sht4x
    id: sht45_sensor
    address: 0x44
    precision: High
    temperature:
      name: "Indoor Temperature"
      id: indoor_temperature
    humidity:
      name: "Indoor Humidity"
      id: indoor_humidity
    update_interval: 10s

Current ESPHome defaults SHT4x to address 0x44, High precision and a 60-second update interval. Ten to thirty seconds is convenient when the values also compensate SGP40.

SHT45 Heater

Current ESPHome supports the SHT4x on-chip heater with a maximum automatic duty cycle of 5%. It is intended mainly for condensation recovery/high-humidity situations.

Leave the heater off by default in a normal indoor station. Heating the humidity sensor continuously would distort the local temperature/humidity environment you are trying to measure.

Why SGP40 Needs Temperature and Humidity Compensation

The SGP40 raw gas signal changes with ambient temperature and humidity. ESPHome can feed external temperature and humidity sensors into the SGP4x component for compensation.

Since this project already has an accurate SHT45, use it.

Current SGP40 ESPHome Syntax

sensor:
  - platform: sgp4x
    id: sgp40_sensor

    voc_index:
      name: "Indoor VOC Index"
      id: indoor_voc_index

    compensation:
      temperature_source: indoor_temperature
      humidity_source: indoor_humidity

    update_interval: 30s

As of current ESPHome, use voc_index. The older voc key still works temporarily but is deprecated and scheduled for removal in ESPHome 2027.2.0.

SGP40 Is Read Internally Every Second

Current ESPHome requires the SGP4x gas algorithm to be driven at 1 Hz. ESPHome therefore reads the device locally every second regardless of a slower frontend update_interval. The update interval only controls how often the state is reported to the rest of ESPHome/Home Assistant.

SGP40 sampling on ESP32
→ every 1 second

Home Assistant update_interval: 30s
→ network/UI gets a value every 30s

Algorithm still receives required 1Hz samples

What VOC Index Means

Sensirion’s VOC Index is unitless. Its algorithm adapts to the local indoor background: around 100 represents the recent typical indoor gas composition, values above 100 indicate a deterioration relative to that background, and values below 100 indicate improvement.

That means VOC Index is excellent for detecting events but is not a direct ppm concentration of “all VOCs”.

VOC Index Is Relative, Not an Absolute Pollution Concentration

VOC Index ~100
→ typical recent indoor gas background

VOC Index rises strongly
→ current gas mixture is worse/more intense than learned background

VOC Index falls below 100
→ cleaner than recent background

Do not write automations that claim VOC Index 200 means “200 ppm VOC”. It does not.

SGP40 Startup and Learning

The VOC algorithm learns the room’s background over time. Expect startup adaptation rather than judging the sensor from its first few minutes after boot.

Current ESPHome stores baseline/algorithm state by default, which helps the sensor resume more quickly after normal restarts.

Why PMS5003 Is Needed Even with SGP40

SGP40 cannot measure particles. PMS5003 cannot measure gaseous VOCs. Cooking can create both, but many events affect one far more strongly than the other.

EventLikely VOCLikely PM
Perfume / solvent / cleanerHigh responseMay be small
Frying / searingOften highOften very high PM2.5
Dusting / vacuumingVariablePM10/PM2.5 can rise
Human occupancy without cookingLimited VOC change possibleUsually low particles

PMS5003 UART Configuration

uart:
  id: pms_uart
  rx_pin: GPIO16
  tx_pin: GPIO17
  baud_rate: 9600
  parity: NONE
  stop_bits: 1

Current ESPHome PMSX003 support uses 9600 baud. Sensor TX connects to ESP32 RX. ESP32 TX back to the PMS receiver is required if you want ESPHome-controlled passive sleep/wake operation.

PMS5003 ESPHome Configuration

sensor:
  - platform: pmsx003
    type: PMSX003
    uart_id: pms_uart
    update_interval: 120s

    pm_1_0:
      name: "Indoor PM1.0"
      id: indoor_pm1

    pm_2_5:
      name: "Indoor PM2.5"
      id: indoor_pm25

    pm_10_0:
      name: "Indoor PM10"
      id: indoor_pm10

For the standard PMS5003, use type: PMSX003. These PM entities are the atmospheric values suited to normal environmental monitoring.

Why 120 Seconds Is a Good Permanent PMS5003 Interval

Current ESPHome automatically changes behaviour when PMSX003 update_interval is above 30 seconds and bidirectional UART control is available. It can sleep the sensor, wake it roughly 30 seconds before a reading, allow stabilisation, request the sample and return to the long cycle.

This reduces fan/laser operating time. ESPHome’s documentation recommends 120 seconds or longer when extending sensor life is the objective.

Continuous PMS5003 Can Still Make Sense

If the monitor’s main job is catching rapid cooking/smoke events, continuous operation gives faster particle response. For a general living-room/bedroom station, two-minute sampling is usually enough.

I²C Address Map

SensorInterfaceAddress / speed
SHT45I²C0x44
SGP40I²CFixed SGP4x bus device; no conflict with SHT45/SCD41
SCD41I²C0x62
PMS5003UART9600 baud

The three Sensirion devices can share one ESP32 I²C bus without address conflicts in this design.

I²C Wiring

ESP32 3.3V ───── SHT45 VCC
ESP32 3.3V ───── SGP40 VCC/breakout logic
SCD41 supply ─── according to breakout/module design
ESP32 GND ────── all sensor grounds

ESP32 SDA ────── SHT45 + SGP40 + SCD41 SDA
ESP32 SCL ────── SHT45 + SGP40 + SCD41 SCL

Bare sensor IC voltage limits differ from breakout-board input limits. Check the exact boards you buy instead of assuming every “SCD41 module” or “SGP40 board” has the same regulator/level shifting.

ESPHome I²C Setup

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true

Keep scan: true during bring-up so ESPHome logs confirm the expected I²C devices. Once stable, leaving the scan enabled is normally harmless.

The Enclosure Is More Important Than the YAML

A single plastic project box can destroy the quality of otherwise excellent sensors. The ESP32, voltage regulator and PMS5003 fan/laser all create heat or airflow. If SCD41 and SHT45 sit beside those components in stagnant air, Home Assistant will measure the electronics enclosure instead of the room.

Sensirion’s SCD4x Placement Guidance

Sensirion recommends strong coupling to ambient air: place the SCD4x close to an enclosure opening, minimise trapped dead air, and separate it from heat sources such as CPUs, displays, Wi-Fi modules, regulators and batteries.

This is directly relevant to an ESP32 IAQ station because the ESP32 Wi-Fi radio and regulator are both local heat sources.

Recommended Internal Layout

Room-air vent
   ↓
[ SHT45 ]   [ SCD41 ]   [ SGP40 ]
   ↑ open ambient-air region

---------------- thermal / airflow separation ----------------

[ ESP32 + regulator ]

[ PMS5003 ] → dedicated inlet → fan chamber → dedicated exhaust

Do not aim the PMS5003 exhaust directly across SHT45 or SCD41. That airflow has been warmed by the particle sensor and can distort temperature/humidity response.

Give PMS5003 Its Own Airflow Path

PMS5003 has an internal fan. The enclosure should allow room air into its inlet and allow exhaust back out without recirculating the same trapped air.

Good:
room → PMS inlet → optical chamber → exhaust → room

Bad:
sealed box → PMS exhaust loops back into inlet

Keep SGP40 Away from Contaminating Materials

VOC sensors respond to chemicals from the environment—including the enclosure itself. Fresh paint, silicone, adhesives, solvent-cleaned plastics, some foams and freshly printed materials can produce VOC events and long adaptation periods.

  • Allow a newly built enclosure to air out before judging baseline behaviour.
  • Avoid placing the SGP40 directly beside adhesive/solvent sources.
  • Do not clean the assembled gas-sensor chamber with strong solvents and immediately expect normal VOC Index.

Do Not Mount SHT45 Above the ESP32

Warm air from the regulator/ESP32 can rise toward the humidity sensor and make temperature read high while relative humidity reads artificially low. Place SHT45 on the cool, ventilated edge of the enclosure.

SCD41 Membrane

ESPHome documentation warns not to remove or tamper with the white SCD4x membrane. It is part of the sensor design.

Full ESPHome Configuration

esphome:
  name: indoor-air-quality-station
  friendly_name: Indoor Air Quality Station

esp32:
  board: esp32dev
  framework:
    type: esp-idf

logger:

api:
  encryption:
    key: !secret iaq_api_key

ota:
  - platform: esphome
    password: !secret ota_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true

uart:
  id: pms_uart
  rx_pin: GPIO16
  tx_pin: GPIO17
  baud_rate: 9600
  parity: NONE
  stop_bits: 1

sensor:
  - platform: sht4x
    id: sht45_sensor
    address: 0x44
    precision: High
    temperature:
      name: "Indoor Temperature"
      id: indoor_temperature
    humidity:
      name: "Indoor Humidity"
      id: indoor_humidity
    update_interval: 10s

  - platform: sgp4x
    id: sgp40_sensor
    voc_index:
      name: "Indoor VOC Index"
      id: indoor_voc_index
    compensation:
      temperature_source: indoor_temperature
      humidity_source: indoor_humidity
    update_interval: 30s

  - platform: scd4x
    id: scd41_sensor
    measurement_mode: periodic
    automatic_self_calibration: true
    co2:
      name: "Indoor CO2"
      id: indoor_co2
    update_interval: 30s

  - platform: pmsx003
    type: PMSX003
    uart_id: pms_uart
    update_interval: 120s

    pm_1_0:
      name: "Indoor PM1.0"
      id: indoor_pm1

    pm_2_5:
      name: "Indoor PM2.5"
      id: indoor_pm25

    pm_10_0:
      name: "Indoor PM10"
      id: indoor_pm10

This configuration intentionally exposes SHT45 temperature/humidity as the room values and only SCD41 CO₂ from the SCD4x component. You can add SCD41 temperature/humidity diagnostic entities if you want to compare enclosure thermal offsets.

Should All Sensors Update at the Same Rate?

No. Their physical response and useful automation timescales differ.

SensorPractical interval
SHT4510–30 s
SGP40 frontend report30–60 s; ESPHome still samples internally at 1 Hz
SCD41 CO₂30–60 s is plenty for rooms
PMS5003120 s for low-wear general monitoring, faster/continuous for rapid PM events

Do not force every sensor to publish every second just because the ESP32 can handle it.

Home Assistant Dashboard Layout

  • CO₂ ppm current value + 24-hour graph
  • PM2.5 µg/m³ current value + spike graph
  • VOC Index current value + trend
  • Temperature
  • Relative humidity
  • PM1/PM10 secondary values
  • Ventilation / extractor / purifier state

Put CO₂, PM2.5 and VOC Index on separate graph scales. Their units and behaviour are completely different.

CO₂ Automation

Home Assistant’s current air-quality guidance uses a practical ventilation example that turns ventilation on when CO₂ rises above 1000 ppm and off again below 800 ppm.

alias: Ventilate room when CO2 is high
triggers:
  - trigger: numeric_state
    entity_id: sensor.indoor_co2
    above: 1000

actions:
  - action: fan.turn_on
    target:
      entity_id: fan.ventilation

Use hysteresis for the OFF action so the fan does not chatter around one threshold.

CO₂ Is Primarily a Ventilation Metric

In ordinary occupied rooms, CO₂ rises mainly because people exhale it. That makes it a very useful signal for whether ventilation is keeping up with occupancy.

It does not directly tell you whether the room contains cooking smoke, VOCs or humidity problems.

PM2.5 Automation

PM2.5 sustained high
→ kitchen extraction / air purifier boost

PM2.5 returns low
→ normal fan speed

Cooking can create genuine short-lived particle spikes. Do not filter them away just because the graph looks dramatic.

VOC Automation

VOC Index is best used as an event/trend signal. Large increases can come from cleaners, perfume, solvents, cooking or other gas-phase emissions.

VOC Index rises far above recent baseline
→ investigate source
→ boost ventilation if appropriate

Because the index adapts to recent indoor background, avoid treating one absolute VOC Index threshold as a certified pollutant concentration limit.

Humidity Automation

Humidity high for sustained period
→ bathroom/dehumidifier ventilation

Humidity normalises
→ return to normal

SHT45 gives you a clean humidity signal that is independent of the SCD41 enclosure temperature offset.

Use Pollutant-Specific Hysteresis

MetricExample ONExample OFF
CO₂ ventilationAbove 1000 ppmBelow 800 ppm
PM2.5 purifierAbove chosen sustained PM thresholdBelow a lower threshold
Humidity extractionAbove chosen RH thresholdBelow a lower RH threshold

The exact PM/humidity thresholds depend on your policy, room and equipment. The architectural rule is simple: use separate ON/OFF thresholds.

A ‘Ventilation Needed’ Composite Sensor

You can create a convenience binary sensor that becomes ON if any relevant pollutant calls for ventilation, while still retaining the raw measurements.

binary_sensor:
  - platform: template
    name: "Ventilation Needed"
    device_class: problem
    lambda: |-
      return id(indoor_co2).state > 1000.0
          || id(indoor_pm25).state > 35.0
          || id(indoor_voc_index).state > 200.0;
    filters:
      - delayed_on: 2min
      - delayed_off: 5min

This is a convenience automation state, not an official IAQ index. Keep the real CO₂/PM/VOC values visible.

Why the Composite Thresholds Need Care

CO₂ is an absolute ppm measurement, PM2.5 is mass concentration, and VOC Index is an adaptive unitless index. They are not directly comparable. The composite sensor simply says “one of my chosen triggers is elevated”.

Outdoor Air Matters

Ventilation only improves particulate air if outdoor PM is better than indoor PM. Opening windows during wildfire smoke or heavy urban pollution can make PM2.5 worse while lowering CO₂.

Indoor CO2 high
+ outdoor PM low
→ fresh-air ventilation is attractive

Indoor CO2 high
+ outdoor PM extremely high
→ ventilation strategy needs filtration / timing

Kitchen Example

A kitchen event can show a very distinctive signature:

Frying starts
→ VOC Index rises
→ PM2.5 rises sharply
→ CO2 may rise only modestly

Extractor starts
→ PM and VOC should fall

This is why a multi-sensor station can tell you much more than CO₂ alone.

Bedroom Example

Overnight bedroom occupancy often produces a strong CO₂ rise while PM2.5 remains low. That points directly toward insufficient ventilation rather than particle pollution.

Cleaning Product Example

Spray cleaners or solvent products can drive VOC Index sharply upward while PMS5003 remains relatively quiet. The station correctly identifies a gas-phase event rather than inventing a particle problem.

Humidifier Example

An ultrasonic humidifier can raise humidity and may also increase PMS5003 particle readings if it aerosolises minerals from tap water. That is not necessarily a PMS5003 fault—the optical sensor sees real airborne droplets/mineral particles.

Avoid Cross-Contamination from the Device Itself

  • Let fresh 3D-printed/plastic enclosures air out.
  • Avoid silicone/solvent fumes near SGP40.
  • Keep hot regulators away from SHT45/SCD41.
  • Do not vent PMS5003 exhaust directly across the environmental sensors.
  • Do not put the entire station inside an airtight decorative box.

Wi-Fi Heat

ESP32 Wi-Fi causes local self-heating. Sensirion explicitly recommends separating SCD4x from Wi-Fi modules and other heat sources. If you cannot physically separate them, expect to need careful enclosure design and possibly temperature offset compensation for SCD41’s own temperature output.

Use SHT45 Compensation for SGP40, Not an Arbitrary Constant

If no compensation source is configured, ESPHome uses defaults. Since an accurate SHT45 is already present, there is no reason to leave SGP40 compensation at fixed generic temperature/humidity values.

Do Not Feed Filtered/Stale Humidity Too Slowly

SGP40 is internally sampled every second, but compensation can only be as current as the source sensor state. A 10–30 second SHT45 interval is a sensible balance for a fixed indoor station.

What About SGP41 Instead of SGP40?

SGP41 adds a NOx Index channel as well as VOC Index and is automatically detected by ESPHome’s same sgp4x component. If traffic/combustion NOx trends matter and the price difference is acceptable, SGP41 is the richer gas sensor.

If the goal is a broad indoor chemical/VOC trend, SGP40 remains perfectly useful.

What About SCD40 Instead of SCD41?

SCD40 is also a true CO₂ sensor and works with the same ESPHome component. SCD41 offers the wider specified CO₂ accuracy range and single-shot mode. For a mains-powered room station, either can work, but SCD41 is the stronger specification choice.

What About SEN55 / SEN66 Instead of Separate Sensors?

Integrated Sensirion environmental modules can reduce wiring and enclosure work, but separate SCD41 + SGP40 + PMS5003 + SHT45 gives you a modular system, independent replacement and easy experimentation.

This article is about maximising transparency and learning rather than minimising component count.

Can One ESP32 Handle All Four?

Yes. Three sensors share low-speed I²C and PMS5003 uses UART. The processing load is modest for a normal ESP32.

The harder problem is physical packaging and stable power, not CPU performance.

ESP32 Board Choice

A classic ESP32 DevKit works well. ESP32-S3 gives extra headroom if you later add a display, but you do not need S3 just to read these four sensors.

Choose the board based on available GPIO, enclosure and future features rather than expecting one ESP32 family to make the sensors more accurate.

Power Budget

PMS5003 is the largest obvious continuous load because it contains a fan and laser. SCD41 also has non-trivial current peaks. Use a good 5 V supply with comfortable margin rather than powering the whole station from a weak USB port/cable.

Power instability can show up as UART checksum errors, I²C dropouts or random ESP32 resets.

Troubleshooting: SCD41 Reads Too Warm

  • sensor too close to ESP32/regulator
  • stagnant enclosure
  • temperature offset not matched to enclosure
  • PMS5003 exhaust warming SCD41

Use SHT45 as the room reference and improve physical layout before applying large arbitrary offsets.

Troubleshooting: SHT45 Reads Too Warm

  • mounted above ESP32 heat plume
  • enclosure ventilation poor
  • sensor touching warm PCB/copper area
  • heater accidentally enabled

SHT45 itself is highly accurate; system-level thermal design usually dominates.

Troubleshooting: VOC Index Stays Near 100

That can be normal in a stable environment because 100 represents the learned typical indoor gas composition. Test whether real household VOC events produce a response before assuming failure.

Troubleshooting: VOC Index Is Wild after Assembly

Fresh adhesives, plastics, solvents or enclosure outgassing can dominate the first hours/days. Let the assembled device air out and allow the VOC algorithm to adapt.

Troubleshooting: SGP40 Compensation Error

Check that the SHT45 temperature/humidity IDs exist before the SGP4x component references them and that both sensors are returning valid states. If SHT45 is unavailable, compensation cannot reflect the true environment.

Troubleshooting: PM2.5 Spikes but VOC Does Not

That can be a genuine dust/particle event with little gas-phase VOC change. Vacuuming or resuspended dust is a good example.

Troubleshooting: VOC Spikes but PM Does Not

That can be a genuine solvent/perfume/cleaner event with little particulate matter. The sensors are supposed to disagree sometimes.

Troubleshooting: Everything Rises When Cooking

That is also plausible. Cooking can emit particles, VOCs, humidity and CO₂ depending on the fuel/process. Multi-pollutant correlation is one of the biggest advantages of this station.

Troubleshooting: PMS5003 Has Checksum Errors

  • check 9600 baud
  • check common ground
  • check PMS TX → ESP RX
  • use stable 5 V supply
  • keep UART away from noisy motor/relay wiring

Do not solve UART corruption with a moving-average filter.

Troubleshooting: I²C Devices Disappear

  • check SDA/SCL pull-ups and wiring
  • shorten breadboard jumpers
  • verify 3.3 V logic compatibility of breakout boards
  • check power supply
  • run ESPHome I²C scan

Three I²C sensors on one short internal bus is straightforward when wiring is clean.

Troubleshooting: CO₂ Never Falls Near Outdoor Conditions

First verify actual ventilation and placement. Do not immediately force-calibrate a room sensor just because it reads higher than an outdoor weather website.

If calibration is genuinely needed, follow SCD4x forced-calibration requirements: stable homogeneous reference CO₂, correct operating mode and adequate stabilisation time.

Troubleshooting: SCD41 Responds Slowly

SCD41 has a physical response time around a minute and enclosure dead volume can make it slower. Large vents close to the sensor improve ambient-air coupling; a tiny decorative pinhole far away from the sensor does not.

Troubleshooting: Home Assistant Graph Is Too Noisy

Use light per-sensor filtering appropriate to the physical variable. Avoid applying one huge moving average to everything.

SensorFiltering idea
SCD41 CO₂30–60 s reporting already smooth for rooms
SHT45Small moving average if needed
SGP40 VOC IndexLet gas algorithm do its job; avoid excessive extra smoothing
PMS5003Median for occasional outliers; preserve real spikes

Do Not Over-Filter IAQ Events

The reason to install these sensors is to see changes. A 30-minute moving average can make the dashboard beautiful while hiding the exact cooking/cleaning event you wanted to detect.

Automation Philosophy

CO2 → ventilation
PM2.5 → filtration / extraction
VOC Index → source investigation / ventilation
Humidity → moisture control
Temperature → comfort / HVAC

Each sensor should drive the equipment capable of fixing that particular problem.

Recommended Home Assistant History

  • CO₂ for at least 7–30 days
  • PM2.5 high-resolution history around cooking events
  • VOC Index trend
  • temperature/humidity trend
  • fan/extractor/air-purifier state on the same timeline

Correlating pollutant graphs with ventilation state turns the device from a sensor box into a commissioning tool.

Best Use Cases

RoomMost useful signals
BedroomCO₂ + temperature/humidity
KitchenPM2.5 + VOC + humidity
Living roomCO₂ + VOC + PM2.5
WorkshopVOC + PM + CO₂ depending process
NurseryCO₂ + humidity + temperature + PM
Home officeCO₂ + temperature + VOC

You can expose all channels everywhere, but these combinations explain which ones usually become operationally important.

My Recommended Physical Build

Front/top ambient vents
├─ SHT45 near cool outer vent
├─ SCD41 near large ambient opening
└─ SGP40 exposed to room air

Separate lower/rear section
├─ ESP32
└─ regulator/power electronics

Dedicated side channel
└─ PMS5003 inlet → sensor → exhaust

If you only remember one thing from this guide, remember this layout. Sensor placement can matter more than changing any YAML option.

My Recommended ESPHome Settings

ComponentRecommendation
SCD41periodic or low_power_periodic; ASC enabled initially; 30–60 s report
SHT45High precision; heater off normally; 10–30 s update
SGP40voc_index; SHT45 compensation; baseline storage on
PMS5003120 s sleep-cycle for general monitor or continuous for fast PM detection

These are sensible defaults for a mains-powered Home Assistant room station rather than universal laboratory settings.

Final Recommendation

If you want one ESP32 node that actually explains indoor air rather than producing a vague “air quality” number, SCD41 + SGP40 + PMS5003 + SHT45 is an excellent combination.

Use SCD41 for true CO₂, SHT45 as the primary environmental temperature/humidity reference, feed those SHT45 values into SGP40 compensation, and use PMS5003 for real particulate matter. Do not substitute VOC-derived eCO₂ for SCD41 or assume VOC Index is a ppm concentration.

Keep the ESPHome syntax current: use voc_index for SGP40, not the deprecated voc key; use PMS5003 at 9600 baud; and let ESPHome handle the SGP40 1 Hz gas-algorithm sampling and PMS5003 sleep/wake cycle.

Most importantly, design the enclosure around airflow and heat. Put SCD41 and SHT45 near real ambient openings and away from the ESP32/regulator, isolate the PMS5003 airflow path, and let a new enclosure air out before judging VOC behaviour.

In Home Assistant, keep the pollutants separate. CO₂ tells you whether the room needs fresh-air ventilation; PM2.5 reveals smoke/aerosol events; VOC Index highlights gaseous chemical changes; humidity tracks moisture. That separation makes the station far more useful for real automation and troubleshooting than any single composite IAQ score.

Related ESP32 Guides

Datasheets & External Resources

All external manufacturer/framework references are collected here so the main article keeps readers inside esp32.co.uk.

Share your love