Monitoring CO2 with ESPHome
I have done several posts about my smart home, and in particular about my instance of Home Assistant. In an effort to continuously improve this system, I have recently added CO2 level monitoring with a Pi Pico and an SCD30 CO2 sensor, which also monitors temperature and humidity.
I've been keen to see how the levels of CO2 affect my concentration and the stuffiness of the room, and with a big gauge in Home Assistant, my previously uncaring self can slowly start to become obsessed with the air quality in the office.
The Sensor
The SCD30 is an I2C sensor, with a built in temperature and humidity monitor, with self-calibration. The sensor comes in a small package and some small headers, which have to be soldered on. I've also got a bit of a problem with the damp at home, so having built in temperature and humidity monitoring was a plus, even if it isn't that precise.
The Firmware
I've always been a big believer in ESPHome and this project was no exception. It integrates well with the Pi Pico W and makes sensor configuration headaches a thing of the past. It's a declarative config file, where you specify the sensor you need, how to find it, what you want it to show Home Assistant, and you're off to the races.
My YAML config file
esphome:
name: breathing-plant
friendly_name: "Breathing Plant"
rp2040:
board: rpipicow
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "[snip]"
ota:
- platform: esphome
password: "[snip]"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
domain: .iot.chza.me
fast_connect: True
# Enable fallback hotspot in case wifi connection fails
ap:
ssid: "Chaziot Fallback Hotspot"
password: "[snip]"
i2c:
sda: GPIO8
scl: GPIO9
sensor:
- platform: scd30
co2:
name: "CO2"
accuracy_decimals: 1
temperature:
name: "Temperature"
accuracy_decimals: 2
humidity:
name: "Humidity"
accuracy_decimals: 1
address: 0x61
update_interval: 5s
light:
- platform: rp2040_pio_led_strip
name: led_strip
id: led_strip
pin: GPIO28
num_leds: 1
pio: 0
rgb_order: GRB
chipset: WS2812B
For this project, I simply needed to specify the SDA
and SCL
lines, connect VBUS
and GND
, then I could be on my way. For prototyping, I was using a Cytron Maker Pi Pico, which includes a WS2812 (more commonly known as NeoPixel) LED. As it was on the board and pre-connected to a GPIO PIN, why not expose it?
Running and Flashing with ESPHome
I run Home Assistant as a Docker container on running-lion
, so I can't install ESPHome through the add-on store. To install, I simply amended the docker-compose.yaml
file already in place for Home Assistant, to create a container for ESPHome.
docker-compose.yaml
file
version: '3'
services:
homeassistant:
container_name: homeassistant
image: "ghcr.io/home-assistant/home-assistant:stable"
volumes:
- ./config:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
ports:
- "127.0.0.1:8180:8180"
- "127.0.0.1:8123:8123"
restart: unless-stopped
privileged: true
#network_mode: host
networks:
- hass
# Automatic Updates
watchtower:
image: containrrr/watchtower
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: homeassistant esphome --interval 30
# ESPHome Web UI
# TODO: setup TLS/DNS record
esphome:
container_name: esphome
image: ghcr.io/esphome/esphome
volumes:
- ./esphomeconfig:/config
- /etc/localtime:/etc/localtime:ro
restart: always
privileged: true
ports:
- 0.0.0.0:6052:6052
environment:
- USERNAME=charlie
- PASSWORD=[snip]
networks:
hass:
driver: bridge
ipam:
config:
- subnet: 10.5.0.0/24
gateway: 10.5.0.1
Theoretically, I could then flash my Pi Pico W through the web interface, but even generating the file to manually copy across was proving difficult. Eventually, I discovered the problem was that the underlying PlatformIO couldn't work properly with an NFS-backed filesystem that used locks. They did point this out to me on the installation page, but as always, I ignored this and ploughed on anyway, just to find that indeed, this did apply to me.
A quick edit to /etc/fstab
to add nolock
to the mount, and I was off to the races. From there, the web interface took care of compilation, and as if by magic a couple of seconds after copying across, the Pi came to life on the network.
Home Assistant
In the dashboard, it is then as easy as making a couple of cards. I track the current concentration as a gauge, then have a graph of the historical concentration and temperature.
I toyed around with setting the NeoPixel LED to red, yellow, or green, depending on the gauge colour, but to get a more representative reading, I settled on leaving it in the other corner of the room, where I wouldn't be able to see the LED anyway.
The Data
It was a bit scary the first couple of days, seeing levels spike way above the maximum safe threshold. A typical office should be at between 400-1000ppm, with anything over 1000ppm being considered detrimental to productivity and causing drowsiness. At 2000ppm, you start to get headaches, sleepiness, and stale air.
Overnight, the room spiked to 3200ppm, but that was with the door shut and window closed. Leaving the room empty for an hour or so brought the value down to 1700ppm, then opening the window whilst working brought it down to around 580ppm. Outdoor air should have roughtly 400ppm, so a well ventilated room shouldn't be too far from this.
I can also use this data to see whether the damp is going to be an issue, as somewhere around 60-70% RH mould can start to form, so I can at least monitor and reduce this with a dehumidifier and opening the window where possible.
Conclusion
For a project that all in could probably be done for less than £20, this seems like an easy thing to get out of the way and start fleshing out your Home Assistant dashboard with. It might be more expensive than a standalone humidity monitor with a screen, but at least it collects the data so you can start to pick up the long term trends, and, unlike most humidity sensors it also tells you how much CO2 is in the air around you.