Automation

Best Air Quality Monitors for Home Assistant (Local CO2, PM2.5)

This post contains affiliate links. If you buy through them, we may earn a small commission at no extra cost to you. Learn more.

Most cheap “CO2” monitors never measure carbon dioxide. They carry a metal-oxide gas sensor that reacts to VOCs and hydrogen, then print an estimate called eCO2 that follows cooking smells better than it follows a stale bedroom. So the best air quality monitor for Home Assistant has to carry a real CO2 sensor, ideally NDIR or photoacoustic, and it has to deliver its readings over a local path.

Original content from computingforgeeks.com - post 178790

We ranked six monitors by the CO2 sensor inside and by how each one reaches Home Assistant: ESPHome and the AirGradient local API over Wi-Fi, Matter over Thread, and Bluetooth. We did not have the hardware on a bench, so sensor claims come from maker datasheets, the integration manifests and live US listings. The integration behaviour described here was checked against Home Assistant 2026.10.4 in October 2026 on an Ubuntu 26.04 lab VM. An ESPHome node fed it CO2 values so we could watch a ventilation automation fire.

Which air quality monitor fits Home Assistant best

Read the CO2 column before the price, because it matters most. Five of the six measure CO2 optically, the method you want behind a fan automation; the cheap IKEA sensor has a real CO2 sensor whose principle nobody has published. The other columns tell you what else it watches and which radio Home Assistant needs to hear it. Prices are US bands from late October 2026 and they move, so check the live price before you order.

JobMonitorCO2 sensorAlso measuresPath into Home AssistantPrice band
Best overallAirGradient ONESenseAir S8-series NDIRPM2.5, VOC and NOx index, temp, RHWi-Fi, local API (polling)about $230 direct, about $295 on Amazon
Best for ESPHomeApollo AIR-1Sensirion SCD40 (optional)PM1 to PM10, VOC and NOx index, pressureWi-Fi, ESPHome (push)about $110 to $200 direct
Cheapest CO2 on MatterIKEA ALPSTUGASensirion SEN63C (teardown), principle not statedPM2.5, temp, RHMatter over Thread (push)about $30 at IKEA
Portable CO2 referenceAranet4 HOMENDIR, ±(30 ppm + 3%)temp, RH, pressureBluetooth (push)about $190
Budget NDIRSwitchBot Meter Pro CO2NDIR, ±(50 ppm + 5%)temp, RHBluetooth (push)about $60
Radon plus CO2Airthings Wave PlusNDIR, ±(30 ppm + 3%)radon, VOC, temp, RH, pressureBluetooth (polling)about $250

Why eCO2 is not a CO2 reading

A metal-oxide (MOX) sensor such as the Sensirion SGP30 measures how much reducing gas lands on a heated film. The firmware then converts that signal into a CO2 equivalent, on the assumption that people are the main source of both. When you are alone in a closed room that assumption roughly holds, but spray a cleaner or fry some onions and the eCO2 figure climbs while the real CO2 hasn’t moved. The reverse fails too: people asleep in a closed bedroom raise CO2 steadily all night while giving off little VOC, so the MOX sensor has little to react to.

An optical sensor counts the molecules directly, and it works in order:

  • Light source: an infrared emitter shines through a small chamber at a band of infrared light that CO2 absorbs strongly.
  • Absorption: the more CO2 in the chamber, the less light reaches the detector, so the drop maps to a concentration in ppm.
  • Photoacoustic variant: the Sensirion SCD4x pulses the light and listens for the pressure wave the absorbing gas makes, which lets the module shrink to a few millimetres. Sensirion’s SCD4x datasheet calls it photoacoustic NDIR.
  • Baseline correction: the firmware assumes the lowest reading it sees over a week or so is outdoor air and pins it to about 400 ppm. That step is where most bad readings come from, which the last section explains.

Five of the six picks use one of these optical methods, and the ALPSTUGA section explains the exception. A product page that lists “eCO2”, “CO2 equivalent” or a CCS811, ENS160 or SGP30 sensor without an NDIR part is a VOC monitor with a CO2-shaped number. It does not belong behind a ventilation automation.

How a reading reaches Home Assistant

Each monitor reaches Home Assistant through one of three radios, and the radio decides what else you need in the house. Wi-Fi monitors talk straight to the server. Matter over Thread monitors need a Thread border router to bridge them onto the LAN. Bluetooth monitors need an adapter on the Home Assistant host or an ESPHome Bluetooth proxy within a room or two of the sensor, because BLE range through walls is short.

Diagram of Bluetooth, Matter over Thread and Wi-Fi air quality monitors reaching Home Assistant locally and driving a fan automation
Every path ends at Home Assistant inside the house, and the CO2 entity then drives the fan or purifier.

Home Assistant labels each integration with an IoT class that says whether data stays local and whether the device pushes or gets polled. We read the labels straight from the integration manifests inside the 2026.10.4 container in our lab:

airgradient      iot_class= local_polling | requirements= ['airgradient==0.10.0']
esphome          iot_class= local_push | requirements= ['aioesphomeapi==46.6.0', 'esphome-dashboard-api==1.4.0', 'bleak-esphome==4.0.0']
zha              iot_class= local_polling | requirements= ['zha==2.3.1', 'zha-quirks==2.3.0']
matter           iot_class= local_push | requirements= ['matter-python-client==1.4.0', 'matter-ble-proxy==0.7.1']
mqtt             iot_class= local_push | requirements= ['paho-mqtt==2.1.0']
aranet           iot_class= local_push | requirements= ['aranet4==2.6.0']
airthings_ble    iot_class= local_polling | requirements= ['airthings-ble==1.3.1']
airthings        iot_class= cloud_polling | requirements= ['airthings-cloud==0.2.0']
switchbot        iot_class= local_push | requirements= ['PySwitchbot==2.9.0']
awair            iot_class= local_polling | requirements= ['python-awair==0.2.5']
qingping         iot_class= local_push | requirements= ['qingping-ble==1.1.6']
sensirion_ble    iot_class= local_push | requirements= ['sensirion-ble==0.1.1']

Two lines decide a purchase here. airthings_ble is local, but airthings is the cloud integration, which is why the Wi-Fi Airthings View Plus is left out of this list and the Bluetooth Wave Plus is in it. The airgradient integration polls the local HTTP API of the monitor. ESPHome, Matter and the Bluetooth integrations get a new value pushed whenever the device sends one. Polling adds a short lag, which does not matter for CO2 because it changes over minutes.

How we picked and what we tested

A monitor made this list only if it measures real CO2 with a dedicated CO2 sensor, optical on five of the six, and Home Assistant can read it without a vendor cloud in the path. That ruled out eCO2 and VOC-only devices and the Wi-Fi models that only speak to their maker’s servers. For each pick we checked the sensor part and its accuracy against the spec page or datasheet of the maker. We checked the integration and its IoT class against the Home Assistant docs and the manifest in our container, and price, seller and stock against the live US listing.

Our hands-on part ran on Home Assistant 2026.10.4 in Docker 29.1.3 on Ubuntu 26.04. An ESPHome 2026.10.1 node built for the host platform stood in for a CO2 monitor. It pushed CO2, PM2.5 and VOC values over the same native API an Apollo AIR-1 uses, so we could time the automation that reacts to them. We also validated the AIR-1 firmware YAML that Apollo publishes with esphome config to read how it drives its sensors. None of this tests the accuracy of a real sensor, and the article says so wherever it matters.

Why the AirGradient ONE is the best overall

The AirGradient ONE is the monitor we would put in a living room first, because it measures everything that matters on one board and gives Home Assistant a documented local API. Inside are a SenseAir S8-series NDIR CO2 sensor (AirGradient lists the S8 or the S88), a Plantower PMS5003 for particles and a Sensirion SGP41 for the VOC and NOx index. A Sensirion SHT40 handles temperature and humidity, all on an ESP32-C3 with an OLED and an 11-LED bar on the front.

AirGradient ONE indoor air quality monitor with NDIR CO2, PM2.5 and VOC readings for Home Assistant
AirGradient ONE: SenseAir S8 NDIR CO2, PMS5003 particles and SGP41 VOC/NOx on one ESP32-C3 board. Image: AirGradient.

Home Assistant finds it on the network and the AirGradient integration polls its local API, so firmware 3.1.1 or newer is the only prerequisite. You can switch off the AirGradient cloud connection in the local settings and the readings keep flowing. The firmware is open source on GitHub if you want to see exactly what it sends. AirGradient states the S8’s automatic baseline correction runs every seven days and can be changed, which matters for a bedroom.

  • Who it’s for: anyone who wants CO2, PM2.5 and VOC from one box with a screen.
  • Skip it if: you need battery power. It needs a USB-C supply (not included) and 2.4 GHz Wi-Fi.
  • Price note: about $230 assembled or $140 as a kit from AirGradient, about $295 from a third-party seller on Amazon.

What the Apollo AIR-1 does for ESPHome users

The Apollo AIR-1 is the pick if your house already runs on ESPHome, because it ships with ESPHome firmware and arrives in Home Assistant as a native-API device that pushes values. The base board carries a Sensirion SEN55 for PM1 to PM10, VOC index, NOx index, temperature and humidity, plus a DPS310 pressure sensor. CO2 is an option: order the variant with the Sensirion SCD40, or you’ll get an excellent particle monitor that can’t tell you when to open a window.

Apollo AIR-1 ESPHome air quality sensor with SCD40 CO2 and SEN55 particulate sensor for Home Assistant
Apollo AIR-1: ESP32-C3 with a Sensirion SEN55 and an optional SCD40 CO2 sensor. Image: Apollo Automation.

We validated Apollo’s published firmware from the AIR-1 repository with ESPHome 2026.10.1 and read how it configures the CO2 sensor. The SCD40 reads every 60 seconds with self-calibration on and takes pressure compensation from the DPS310, which is better practice than a fixed altitude:

  - platform: scd4x
    id: scd40
    co2:
      name: "CO2"
      id: "co2"
      on_value:
        - if:
            condition:
              lambda: 'return id(air_quality_led_source).current_option() == "CO2";'
            then:
              - script.execute: update_air_quality_led
    automatic_self_calibration: true
    update_interval: 60s
    measurement_mode: "periodic"
    i2c_id: bus_a
    ambient_pressure_compensation_source: dps310pressure

In the same file, a calibrate_co2_value action runs a forced calibration of the SCD40 with a ppm value you pass in. Forced calibration is a one-time correction, though, and self-calibration keeps running afterwards. For a room that never airs out, call the action outdoors and also turn off the firmware’s “CO2 Auto Calibration” config switch, which writes the setting to the SCD40. Apollo also ships a temperature offset of a few degrees because the ESP32-C3 warms the case, and you will need to tune that against a reference thermometer.

  • Who it’s for: ESPHome users who want a small sensor with full particle data.
  • Skip it if: you want a screen. It has no display and sells only from the Apollo store.
  • Price note: about $130 with CO2, about $200 with the extra gas sensor.

Why the IKEA ALPSTUGA is the cheapest Matter CO2 sensor

The IKEA ALPSTUGA brings CO2 and PM2.5 to Matter over Thread for about $30, which is a fraction of anything else on this list. IKEA’s product page lists CO2, PM2.5, temperature and humidity on a dot-matrix display that doubles as a clock, and Sensirion’s press release confirms a Sensirion sensor inside. A published teardown identifies it as a SEN63C, though IKEA itself doesn’t name the part.

IKEA ALPSTUGA Matter over Thread air quality sensor showing a CO2 reading for Home Assistant
IKEA ALPSTUGA: CO2, PM2.5, temperature and humidity over Matter over Thread. Image: Sensirion / IKEA.

In Home Assistant, the Matter integration maps the Matter CO2, PM2.5 and PM10 concentration clusters to sensor entities, which we confirmed in matter/sensor.py in the 2026.10.4 container. The ALPSTUGA therefore lands as plain CO2 and PM2.5 entities with no custom code. The catch is the radio: it needs a Thread border router, and if you don’t have one, the cheap sensor brings a second purchase with it.

It is also the one pick whose CO2 method is not confirmed as optical. Neither IKEA nor Sensirion states how the SEN63C senses CO2; Sensirion’s SEN63C page lists ±100 ppm with no principle. The ±(100 ppm + 10%) figure the teardown quotes is the same accuracy Sensirion publishes for its STCC4, a thermal-conductivity CO2 sensor rather than an NDIR one. That makes it a real CO2 reading, not eCO2, but a looser one than the Aranet’s, and buyers also report the temperature reads high. Treat it as a cheap per-room trend sensor and keep an NDIR monitor as your reference.

  • Who it’s for: households already on Thread that want a cheap CO2 reading per bedroom.
  • Skip it if: you have no border router or you need NDIR-grade CO2 accuracy.
  • Price note: about $30 at IKEA, USB-C power supply not included. The US page blocked our checks, so confirm stock at IKEA US.

What the Aranet4 HOME gets right

The Aranet4 HOME shares the tightest CO2 specification on the list with the Wave Plus: ±(30 ppm + 3%) from a true NDIR sensor. Aranet rates its battery for up to four years with the e-ink screen on. That makes it the monitor to carry between rooms, leave in a child’s bedroom without a cable, or use as the reference you check the cheaper sensors against.

Aranet4 HOME battery NDIR CO2 monitor with e-ink display that reports to Home Assistant over Bluetooth
Aranet4 HOME: NDIR CO2 at ±(30 ppm + 3%), temperature, humidity and pressure on batteries. Image: Aranet.

Home Assistant’s Aranet integration picks up its Bluetooth broadcasts and is local_push, but it needs firmware 1.2.0 or newer and the “Smart Home integration” switch turned on in the Aranet Home app. Leave that switch off and Home Assistant finds nothing, which is the setup step most people miss. Put the monitor within range of the host’s Bluetooth adapter or an ESPHome Bluetooth proxy, or the readings will drop out.

  • Who it’s for: trustworthy CO2 without wiring, or a reference unit for the house.
  • Skip it if: you care about particles or VOCs. It measures nothing else.
  • Price note: about $190 on Amazon, sold by Aranet’s own US store.

Why the SwitchBot Meter Pro CO2 is the budget NDIR pick

The SwitchBot Meter Pro (CO2 Monitor) puts a real NDIR sensor behind a large LCD for about $60, which is where true CO2 measurement starts on price. SwitchBot specifies ±(50 ppm + 5%) between 400 and 9,999 ppm, and it runs on two AA cells for about a year or from USB-C, which also speeds its refresh up.

SwitchBot Meter Pro CO2 monitor with NDIR sensor and LCD for Home Assistant over Bluetooth
SwitchBot Meter Pro (CO2 Monitor): NDIR CO2, temperature and humidity on an LCD. Image: SwitchBot.

Home Assistant reads it over Bluetooth through the SwitchBot integration, with no SwitchBot hub or account and creates temperature, humidity, CO2 and battery entities, plus a button that syncs the on-screen clock. That last one exists for a reason: the clock drifts, and an air quality monitor with the wrong time on its face looks broken even when the CO2 figure is fine. Buy the plain listing; the Alexa-enabled sibling costs about $20 more and adds nothing for Home Assistant.

  • Who it’s for: a first CO2 monitor, or one per bedroom on a budget.
  • Skip it if: you want particles or VOC, or you would rather not depend on Bluetooth range.
  • Price note: about $60 on Amazon, sold by SwitchBot.

When the Airthings Wave Plus is worth the premium

The Airthings Wave Plus is the only pick that measures radon, and that is the reason to pay about $250 for it. Radon is a radioactive gas that seeps up from the ground and collects in basements and ground floors, and no other sensor here can detect it. Alongside radon it carries an NDIR CO2 sensor rated ±(30 ppm + 3%), plus VOC, temperature, humidity and pressure, on two AA cells for about a year and a half.

Airthings Wave Plus radon, CO2 and VOC monitor that connects to Home Assistant over Bluetooth
Airthings Wave Plus: radon, NDIR CO2, VOC, temperature, humidity and pressure. Image: Airthings.

Home Assistant reads it through Airthings BLE, which polls the device on an interval rather than receiving pushes. If you already paired it to an Airthings hub with SmartLink, switch it back to Bluetooth mode first, or Home Assistant will not see it. There is no PM2.5 sensor and no screen, so this is a monitor for the floor where radon collects, not a living-room dashboard.

  • Who it’s for: basements and ground-floor bedrooms in a radon area.
  • Skip it if: radon is not a concern. The AirGradient ONE or Aranet4 covers CO2 for less.
  • Price note: about $250 on Amazon, sold by Amazon.

What VINDSTYRKA and the Aqara TVOC monitor actually measure

Neither of the two most common Zigbee air monitors measures CO2, and that is why neither is a pick. IKEA’s VINDSTYRKA reports PM2.5, temperature, humidity and a VOC index; the Zigbee2MQTT device page lists exactly those four. The Aqara TVOC Air Quality Monitor reports TVOC, temperature and humidity, and we couldn’t find it sold new on Amazon US. Both pair fine with a Zigbee coordinator on ZHA or Zigbee2MQTT, and our ZHA vs Zigbee2MQTT comparison covers which stack to run.

Inside the 2026.10.4 container, ZHA handles both through quirks. The VINDSTYRKA quirk replaces IKEA’s manufacturer cluster 0xFC7E with a VOC index cluster, and ZHA exposes it as a VOC index sensor. These are the matching lines of zhaquirks/ikea/vindstyrka.py:

/usr/local/lib/python3.14/site-packages/zhaquirks/ikea/vindstyrka.py
1:"""IKEA VINDSTYRKA device."""
14:from zhaquirks.clusters import CustomCluster
18:class VOCIndex(CustomCluster):
19:    """IKEA VOC index cluster."""
21:    cluster_id: t.uint16_t = 0xFC7E
22:    name: str = "IKEA VOC Index"
23:    ep_attribute: str = "voc_index"
25:    class AttributeDefs(BaseAttributeDefs):
29:            id=0x0000, type=t.Single, access="rp", is_manufacturer_specific=True
32:            id=0x0001, type=t.Single, access="r", is_manufacturer_specific=True
35:            id=0x0002, type=t.Single, access="r", is_manufacturer_specific=True
40:    QuirkBuilder(IKEA, "VINDSTYRKA")
41:    .replaces(VOCIndex)
43:        VOCIndex.AttributeDefs.measured_value.name,
44:        VOCIndex.cluster_id,
50:        translation_key="voc_index",
51:        fallback_name="VOC index",

The Aqara monitor gets a quirk of its own, zhaquirks/xiaomi/aqara/tvoc.py, which relays its readings into a VOC level cluster.

A VOC index is not a concentration. The Sensirion algorithm reports how the current VOC level compares with what the sensor has learned is normal for that room, centred on 100. The AIR-1 firmware, for example, sets index_offset: 100 with a 72-hour learning time. A fresh coat of paint reads high, and the same paint a week later reads normal. That is useful for a range-hood or purifier automation, but it can’t tell you when a room needs fresh air, and that’s the job CO2 does. If you already own a VINDSTYRKA, keep it for particles and add an NDIR monitor beside it.

How the CO2 automation behaves in Home Assistant

A CO2 automation should switch ventilation on when the level stays high, not on every brief spike, so the trigger needs a duration. We built the test around an ESPHome node on the host platform, with a template number standing in for the CO2 reading. That gave us the same native API an Apollo AIR-1 uses, without the sensor. This is the node’s config, with the encryption key removed:

esphome:
  name: air-lab

host:

logger:

api:
  encryption:
    key: "REDACTED-base64-32-byte-key"

number:
  - platform: template
    name: "CO2 Simulated"
    id: co2_sim
    min_value: 400
    max_value: 3000
    step: 10
    initial_value: 600
    optimistic: true
    mode: box

sensor:
  - platform: template
    name: "CO2"
    id: co2
    unit_of_measurement: "ppm"
    device_class: carbon_dioxide
    state_class: measurement
    accuracy_decimals: 0
    update_interval: 10s
    lambda: |-
      return id(co2_sim).state;
  - platform: template
    name: "PM2.5"
    unit_of_measurement: "µg/m³"
    device_class: pm25
    state_class: measurement
    accuracy_decimals: 1
    update_interval: 10s
    lambda: |-
      return 8.0 + (random_uint32() % 30) / 10.0;
  - platform: template
    name: "VOC Index"
    state_class: measurement
    accuracy_decimals: 0
    update_interval: 10s
    lambda: |-
      return 100 + (random_uint32() % 20);

Adding it through the ESPHome integration asked for the host, port 6053 and the Noise encryption key, then created these entities. The device_class and state_class values matter more than they look. carbon_dioxide with measurement is what makes Home Assistant keep long-term statistics. The VOC index arrives with no unit and no device class, because Home Assistant has no class for an index:

number.air_lab_co2_simulated       600.0    unit=None device_class=None state_class=None
sensor.air_lab_co2                 600.0    unit=ppm device_class=carbon_dioxide state_class=measurement
sensor.air_lab_pm2_5               10.1000003814697 unit=μg/m³ device_class=pm25 state_class=measurement
sensor.air_lab_voc_index           118.0    unit=None device_class=None state_class=measurement

On the device page the node looks like any ESPHome air sensor: the three readings under Sensors, the simulated CO2 control, and the two automations listed as related once they reference the CO2 entity.

Home Assistant device page for an ESPHome air quality node showing CO2, PM2.5 and VOC Index sensors
The ESPHome node in Home Assistant 2026.10.4 with its CO2, PM2.5 and VOC Index sensors and the two ventilation automations.

The two automations use different thresholds on purpose. Ventilation turns on above 1,000 ppm, the level the UK Health and Safety Executive ties to roughly 10 litres per second of fresh air per person, and turns off below 800 ppm. That gap stops a reading that hovers near 1,000 from flicking the fan on and off every few minutes:

- id: ventilation_on_co2_high
  alias: Ventilation on when CO2 above 1000 ppm
  description: ""
  triggers:
    - trigger: numeric_state
      entity_id: sensor.air_lab_co2
      above: 1000
      for: "00:02:00"
  conditions: []
  actions:
    - action: input_boolean.turn_on
      target:
        entity_id: input_boolean.ventilation
  mode: single
- id: ventilation_off_co2_low
  alias: Ventilation off when CO2 below 800 ppm
  description: ""
  triggers:
    - trigger: numeric_state
      entity_id: sensor.air_lab_co2
      below: 800
      for: "00:02:00"
  conditions: []
  actions:
    - action: input_boolean.turn_off
      target:
        entity_id: input_boolean.ventilation
  mode: single

In the lab the two-minute hold is short so the test finishes quickly; in a real room use ten or fifteen minutes. We set CO2 to 1,250 ppm, then to 650, then ran a spike that rose to 1,250 and dropped back to 950 after only 60 seconds. The poll log below is trimmed to the set commands and the polls where ventilation changed state (times UTC):

04:00:19 SET CO2 sim -> 1250
04:02:20 co2=1250.0 | ventilation=off 03:57:37
04:02:40 co2=1250.0 | ventilation=on 04:02:27
04:03:00 SET CO2 sim -> 650
04:05:01 co2=650.0 | ventilation=on 04:02:27
04:05:21 co2=650.0 | ventilation=off 04:05:07
04:05:41 SET CO2 sim -> 1250
04:06:41 SET CO2 sim -> 950
04:09:22 co2=950.0 | ventilation=off 04:05:07

Ventilation came on at 04:02:27, 2 minutes 8 seconds after the value changed, and that extra 8 seconds is the whole lesson. The for timer starts when Home Assistant receives the first reading above 1,000, and the node only reported every 10 seconds, so its first high value arrived at 04:00:27. A battery monitor that reports every few minutes adds that interval to every automation built on it. The 60-second spike never triggered anything: the for clause reset when CO2 fell to 950, which is exactly the behaviour you want from a door left open for a minute.

Home Assistant automation trace for ventilation on when CO2 stays above 1000 ppm for two minutes
The automation trace records the numeric_state trigger crossing 1,000 ppm and holding for two minutes before the action ran.

Because the CO2 sensor has state_class: measurement, the recorder also kept 5-minute statistics with mean, min and max. The more-info dialog draws them as a band, which is how you’ll spot a bedroom that climbs past 1,000 ppm every night without reading raw history.

Home Assistant CO2 sensor history graph with 5-minute mean, min and max statistics
Five-minute aggregated CO2 statistics in the more-info dialog, with the min and max band around the mean.

The 04:00 bucket in that graph averaged 970 ppm with a 650 minimum and a 1,250 maximum, straight from the recorder’s 5-minute table. A statistics card on a dashboard reads the same table, so a week of bedroom data costs you nothing extra to keep.

What to check before you buy

Six specs separate a monitor that works with Home Assistant from one that only looks like it does. Read them in this order, because the first two decide whether the CO2 number is worth automating on at all:

  • CO2 sensor type: look for NDIR or photoacoustic NDIR and a part number (SenseAir S8, Sensirion SCD4x). “eCO2” or a MOX part alone means an estimate.
  • Calibration control: automatic baseline correction is fine in rooms that air out weekly. For a sealed bedroom, pick a monitor that lets you turn it off or force-calibrate outdoors.
  • Radio you already run: Wi-Fi needs nothing extra, Thread needs a border router, and Bluetooth needs an adapter or proxy near the sensor.
  • IoT class: local_push or local_polling keeps working with the internet down; anything cloud_ stops when your connection does.
  • Power: USB-powered monitors report more often; battery units report less often to save power, which delays automations.
  • Particles and VOC: PM2.5 is a real concentration you can automate a purifier on. VOC from these sensors is an index, useful for trends and not for thresholds.

Run Home Assistant on something that stays up, because a CO2 automation is only as reliable as the box running it. Our guides to installing Home Assistant on Ubuntu 26.04 and running it in Docker cover the server side. The same CO2 entity pairs well with a local thermostat, or with an mmWave presence sensor so ventilation only runs when the room is occupied. If you would rather build your own ESPHome monitor, our ESP32 board guide covers the boards these sensors attach to.

Where a CO2 reading misleads you

A good optical sensor still gives you a wrong number under four conditions, and each one has a fix you can apply in Home Assistant or on the device.

A sealed bedroom drifts low

Self-calibration assumes the room reaches outdoor air at some point every week. Sensirion’s datasheet gives the SCD4x a 156-hour standard calibration period that needs that exposure, and AirGradient sets the S8’s correction to seven days. A bedroom with the window shut and someone in it every night never reaches outdoor air. The sensor slowly decides that its overnight low is the new baseline, and every reading after that comes out too low. Turn self-calibration off for that room and force-calibrate the monitor outdoors instead; on the AIR-1 that means the “CO2 Auto Calibration” switch plus the calibrate_co2_value action.

Outdoor air isn’t 400 ppm any more

NOAA’s Mauna Loa record put the September 2026 monthly mean at 426.07 ppm, and city air often runs higher. A sensor that pins outdoor air to 400 ppm reads about 25 ppm low across the whole range. That matters little at 1,000 ppm, but it explains why a freshly aired room never quite shows 400.

The case warms the sensor

Every mains-powered monitor here has a microcontroller in the same box as its temperature sensor, and it adds a few degrees. Apollo ships an adjustable offset for that reason, and buyers report the ALPSTUGA reads warm too. CO2 sensors compensate for temperature internally, so the CO2 figure is fine, but do not drive your heating from the temperature entity of an air quality monitor.

CO2 is not a safety alarm

None of these monitors detects carbon monoxide, the gas a faulty boiler or generator produces, and Apollo says plainly that the AIR-1 is not for safety monitoring. A CO2 reading tells you the room needs fresh air. Keep a certified CO alarm near fuel-burning appliances, separate from anything you automate.

Keep reading

OpenCode CLI Cheat Sheet: Commands, Sessions and Fixes (PDF) AI OpenCode CLI Cheat Sheet: Commands, Sessions and Fixes (PDF) Best Mini PC for Home Assistant: 4 Picks for HAOS and Proxmox Automation Best Mini PC for Home Assistant: 4 Picks for HAOS and Proxmox Ansible + Proxmox: Automated VM Management Ansible Ansible + Proxmox: Automated VM Management Best Weather Stations for Home Assistant: Ecowitt, Tempest, Davis Automation Best Weather Stations for Home Assistant: Ecowitt, Tempest, Davis Best Water Leak Sensors and Auto Shutoff Valves for Home Assistant Automation Best Water Leak Sensors and Auto Shutoff Valves for Home Assistant Best Ansible Books to Master Automation in 2026 Ansible Best Ansible Books to Master Automation in 2026

Leave a Comment

Press ESC to close