Maker.io main logo

Automating Indoor Air Quality with Adafruit FunHouse CO2 Monitoring

2021-05-24 | By Nate_Larson

License: See Original Project

We’ve all been spending a lot more time in our homes over the past year, and with a heightened awareness of health, it is only a matter of time before one ponders the air quality to which we are subjecting ourselves. Good air quality is imperative to overall health. Maintaining good air quality involves monitoring the indoor carbon dioxide (CO2) level, which provides a good indication of air quality as stale indoor air is replaced with fresh air from outside.

In this project, I will demonstrate how one can monitor indoor air quality, and then automate ventilation equipment to ensure optimal indoor air quality.

Project Overview

In this project, we will monitor the indoor CO2 level to determine air quality. To monitor the CO2 level, we will use the Adafruit SCD-30 breakout board, which features the Sensirion SCD30 NDIR CO2 sensor module with integrated temperature and humidity sensing for greater accuracy.

The sensor board communicates with the Adafruit FunHouse board over I2C using the solder-free STEMMA QT / Qwiic connector interface. The FunHouse board is powered by an Espressif ESP32-S2-WROVER Wi-Fi MCU module, which polls the sensor for updated data. Then it displays the sensor readings using the onboard screen and publishes these readings to an MQTT server within an instance of Home Assistant running on a Raspberry Pi 4 Model B with 2GB of SDRAM.

The Raspberry Pi logs and monitors these readings, then when the sensor values surpass a threshold, it sends a signal via API to an Adafruit ESP32 Feather Huzzah board. This board then sets a pin, closing the relays on two attached FeatherWing boards, one of which closes the circuit to turn on an energy recovery ventilation unit (ERV) to exchange indoor air with fresh air from outside, while the second relay activates the HVAC fan to circulate the fresh air throughout the house. Once air quality improves and sensor readings drop to a predefined value, the Raspberry Pi turns off the ERV and HVAC fan by signaling to open the relays.

Materials

Prerequisites

This project assumes you already have Home Assistant with the ESPHome add-on setup and running on your network. If you need information on how to do so, please see Smart Home Automation, Iteration 2: Home Assistant and ESPHome for information on how to do this. You must also have your FunHouse board already set up and communicating with your Home Assistant server via MQTT as per the learn guide Using the Adafruit FunHouse with Home Assistant.

Hardware Prep

Begin by soldering the male headers to the Feather Huzzah board. Place the headers into a breadboard to keep them aligned while they are soldered. Then place the board on top of the headers, solder the headers to the board, and remove the assembly from the breadboard.

Adafruit FunHouse CO2 Monitoring

Place a spare set of male headers into the breadboard with the same spacing as the outer relay FeatherWing board.

Adafruit FunHouse CO2 Monitoring

Place a set of the female headers upside-down onto these headers and place the relay FeatherWing upside-down on the headers. Again, this keeps the headers aligned while the board is being soldered. Now solder the headers on this board and then place and solder the terminal blocks to the relay board. Since we are stacking two of these relay boards, it is best to trim the excess leads from the terminal block after they are soldered to ensure the top board doesn’t short to the board below. Repeat this process for the second Relay FeatherWing.

Adafruit FunHouse CO2 Monitoring

Finally, for each relay board, solder a short jumper wire from the signal pin to one of the GPIO pins. In this example, we will use pin 4. Remember, we need these relays to always switch together, so ensure you select the same GPIO pin for both relay FeatherWing boards.

Hardware setup for the FunHouse board is extremely easy. Simply use a STEMMA QT / Qwiic cable to connect the CO2 sensor board to the mainboard via the STEMMA QT / Qwiic connectors on each of these boards.

Adafruit FunHouse CO2 Monitoring

CircuitPython Code

You will find the code is very similar to that of the Using the Adafruit FunHouse with Home Assistant project, but with a few additions for the CO2 sensor. To interface with the CO2 sensor, you will need to download the CircuitPython library bundle and add the adafruit_scd30 file to the lib folder on your board along with all the library files for the aforementioned project.

Adafruit FunHouse CO2 Monitoring

Below is the CircuitPython code for the Adafruit FunHouse for our project.

Copy Code
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2021 Melissa LeBlanc-Williams for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time
import board
import adafruit_scd30
import json
from adafruit_display_shapes.circle import Circle
from adafruit_funhouse import FunHouse

PUBLISH_DELAY = 60
ENVIRONMENT_CHECK_DELAY = 5
ENABLE_PIR = False
ENABLE_PIR = False
MQTT_TOPIC = "funhouse/state"
LIGHT_STATE_TOPIC = "funhouse/light/state"
LIGHT_COMMAND_TOPIC = "funhouse/light/set"
INITIAL_LIGHT_COLOR = 0x008000
USE_FAHRENHEIT = True

try:
from secrets import secrets
except ImportError:
print("WiFi secrets are kept in secrets.py, please add them there!")
raise

i2c = board.I2C() # uses board.SCL and board.SDA
scd = adafruit_scd30.SCD30(board.I2C())

funhouse = FunHouse(default_bg=0x0F0F00)
funhouse.peripherals.dotstars.fill(INITIAL_LIGHT_COLOR)

funhouse.display.show(None)
funhouse.add_text(
text="Temperature:",
text_position=(20, 10),
text_color=0xFF8888,
text_font="fonts/Arial-18.pcf",
)
temp_label = funhouse.add_text(
text_position=(120, 40),
text_anchor_point=(0.5, 0.5),
text_color=0xFFFF00,
text_font="fonts/Arial-18.pcf",
)
funhouse.add_text(
text="Humidity:",
text_position=(20, 70),
text_color=0x8888FF,
text_font="fonts/Arial-18.pcf",
)
hum_label = funhouse.add_text(
text_position=(120, 100),
text_anchor_point=(0.5, 0.5),
text_color=0xFFFF00,
text_font="fonts/Arial-18.pcf",
)
funhouse.add_text(
text="Pressure:",
text_position=(20, 130),
text_color=0xFF88FF,
text_font="fonts/Arial-18.pcf",
)
pres_label = funhouse.add_text(
text_position=(120, 160),
text_anchor_point=(0.5, 0.5),
text_color=0xFFFF00,
text_font="fonts/Arial-18.pcf",
)
funhouse.add_text(
text="CO2:",
text_position=(20, 190),
text_color=0xFF88FF,
text_font="fonts/Arial-18.pcf",
)
co2_label = funhouse.add_text(
text_position=(120, 220),
text_anchor_point=(0.5, 0.5),
text_color=0xFFFF00,
text_font="fonts/Arial-18.pcf",
)
funhouse.display.show(funhouse.splash)

status = Circle(229, 10, 10, fill=0xFF0000, outline=0x880000)
funhouse.splash.append(status)


def update_enviro():
global environment

if scd.data_available:

temp = scd.temperature
unit = "C"
if USE_FAHRENHEIT:
temp = temp * (9 / 5) + 32
unit = "F"

environment["temperature"] = temp
environment["pressure"] = funhouse.peripherals.pressure
environment["humidity"] = scd.relative_humidity
environment["co2"] = scd.CO2
environment["light"] = funhouse.peripherals.light

funhouse.set_text("{:.1f}{}".format(environment["temperature"], unit), temp_label)
funhouse.set_text("{:.1f}%".format(environment["humidity"]), hum_label)
funhouse.set_text("{}hPa".format(environment["pressure"]), pres_label)
funhouse.set_text("{:.0f}PPM".format(environment["co2"]), co2_label)

def connected(client, userdata, result, payload):
status.fill = 0x00FF00
status.outline = 0x008800
print("Connected to MQTT! Subscribing...")
client.subscribe(LIGHT_COMMAND_TOPIC)


def disconnected(client):
status.fill = 0xFF0000
status.outline = 0x880000


def message(client, topic, payload):
print("Topic {0} received new value: {1}".format(topic, payload))
if topic == LIGHT_COMMAND_TOPIC:
settings = json.loads(payload)
if settings["state"] == "on":
if "brightness" in settings:
funhouse.peripherals.dotstars.brightness = settings["brightness"] / 255
else:
funhouse.peripherals.dotstars.brightness = 0.3
if "color" in settings:
funhouse.peripherals.dotstars.fill(settings["color"])
else:
funhouse.peripherals.dotstars.brightness = 0
publish_light_state()


def publish_light_state():
funhouse.peripherals.led = True
output = {
"brightness": round(funhouse.peripherals.dotstars.brightness * 255),
"state": "on" if funhouse.peripherals.dotstars.brightness > 0 else "off",
"color": funhouse.peripherals.dotstars[0],
}
# Publish the Dotstar State
print("Publishing to {}".format(LIGHT_STATE_TOPIC))
funhouse.network.mqtt_publish(LIGHT_STATE_TOPIC, json.dumps(output))
funhouse.peripherals.led = False


# Initialize a new MQTT Client object
funhouse.network.init_mqtt(
secrets["mqtt_broker"],
secrets["mqtt_port"],
secrets["mqtt_username"],
secrets["mqtt_password"],
)
funhouse.network.on_mqtt_connect = connected
funhouse.network.on_mqtt_disconnect = disconnected
funhouse.network.on_mqtt_message = message

print("Attempting to connect to {}".format(secrets["mqtt_broker"]))
funhouse.network.mqtt_connect()

last_publish_timestamp = None

last_peripheral_state = {
"button_up": funhouse.peripherals.button_up,
"button_down": funhouse.peripherals.button_down,
"button_sel": funhouse.peripherals.button_sel,
"captouch6": funhouse.peripherals.captouch6,
"captouch7": funhouse.peripherals.captouch7,
"captouch8": funhouse.peripherals.captouch8,
}

if ENABLE_PIR:
last_peripheral_state["pir_sensor"] = funhouse.peripherals.pir_sensor

environment = {}
update_enviro()
last_environment_timestamp = time.monotonic()

# Provide Initial light state
publish_light_state()

while True:
if not environment or (
time.monotonic() - last_environment_timestamp > ENVIRONMENT_CHECK_DELAY
):
update_enviro()
last_environment_timestamp = time.monotonic()
output = environment

peripheral_state_changed = False
for peripheral in last_peripheral_state:
current_item_state = getattr(funhouse.peripherals, peripheral)
output[peripheral] = "on" if current_item_state else "off"
if last_peripheral_state[peripheral] != current_item_state:
peripheral_state_changed = True
last_peripheral_state[peripheral] = current_item_state

if funhouse.peripherals.slider is not None:
output["slider"] = funhouse.peripherals.slider
peripheral_state_changed = True

# Every PUBLISH_DELAY, write temp/hum/press/light or if a peripheral changed
if (
last_publish_timestamp is None
or peripheral_state_changed
or (time.monotonic() - last_publish_timestamp) > PUBLISH_DELAY
):
funhouse.peripherals.led = True
print("Publishing to {}".format(MQTT_TOPIC))
funhouse.network.mqtt_publish(MQTT_TOPIC, json.dumps(output))
funhouse.peripherals.led = False
last_publish_timestamp = time.monotonic()

# Check any topics we are subscribed to
funhouse.network.mqtt_loop(0.5)

Changes to the code from the base Adafruit project include:

  • Inclusion of CO2 sensor library
  • Reducing label and sensor value text sizes displayed on the screen to make room for the additional CO2 sensor value
  • Added display labels and variable for CO2 sensor value
  • Temperature and humidity were changed to use the values reported by the SCD30 sensor, as they seemed more accurate than those reported by the FunHouse onboard sensor
  • SCD sensor values only update when the sensor indicates it has data available

Don’t forget to add the CO2 sensor to your Home Assistant configuration file so this sensor data can be logged and displayed by the server.

Adafruit FunHouse CO2 Monitoring

Setting up the ESP32 Feather Huzzah

The ESP32 Feather Huzzah is programmed using the ESPHome add-on in Home Assistant. If you need information on how to set this up, or how to create a new node using ESPHome, please see the Smart Home Automation, Iteration 2: Home Assistant and ESPHome project. Once your new node is created, we need to add the code to identify this node as having a device class of “switch”, name that switch, and finally designate which GPIO the relay is attached to. You can see the YAML code for this below.

Copy Code
 esphome:
name: erv_controller
platform: ESP32
board: featheresp32

wifi:
ssid: "WiFi Network Name"
password: "WiFi Password"

# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Erv Controller Fallback Hotspot"
password: "Password"

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:
password: "API Password"

ota:
password: "OTA Password"

switch:
- platform: gpio
name: "Relay"
pin: 4

Once this code is flashed to the Feather, you can begin wiring the relay boards into your existing system. My current ERV controller is a simple timer switch mounted near the ERV in the mechanical room.

Adafruit FunHouse CO2 Monitoring

The timer switch is powered by 24VAC provided by the HVAC system and has the below wire connections.

Adafruit FunHouse CO2 Monitoring

The new controller does not require the Common wire, so this was disconnected and insulated. The first relay board will control the HVAC fan, so the HVAC control board fan wire is connected to the common (COM) pin of the relay terminal block, with the HVAC R wire connected to the normally open (NO) relay connection and the thermostat fan wire connected to the normally closed (NC) position of the terminal block. This allows the thermostat to continue to control the HVAC fan whenever the relay is not activated; meanwhile, if the relay is closed, the HVAC fan will turn on.

Adafruit FunHouse CO2 Monitoring

The second relay board will close the switch loop for the ERV. One wire is connected to the common pin on the terminal block while the second wire is connected to the normally open pin.

Once the relay connections are made, the Feather and FeatherWing boards can be stacked together and powered via a USB power supply. I chose to temporarily mount a small breadboard using some 3M VHB tape and plug the assembly into it until I can 3D print a proper enclosure for the assembly. This also provides easy access to the other GPIO on the board so I can experiment with adding additional hardware and features and allows visibility of the relay indicator LEDs to verify proper operation.

Adafruit FunHouse CO2 Monitoring

Creating the Home Assistant Automation

Now that our sensor data is being recorded, and we have enabled Home Assistant control of our ventilation system, we need to automate our indoor air quality to ensure proper ventilation.

Adafruit FunHouse CO2 Monitoring

We will create two separate automation routines to control the ERV using the values of the CO2 sensor. On the automation page within the Home Assistant server web interface, click the “ADD AUTOMATION” button, then select the “START WITH AN EMPTY AUTOMATION” option.

Adafruit FunHouse CO2 Monitoring

The first Automation will turn on the ventilation system, so I named it ERVon. Select “Numeric State” for the trigger type, and then sensor.co2 for the trigger entity. We want the ventilation system to activate when the sensor reads above 1000ppm, so put 1000 as the “Above” numeric value. Finally, since the sensor value fluctuates a bit as ambient conditions change, we only want to activate the ventilation system if the reading has been consistently above 1000ppm for five minutes, so set the “For” parameter to 00:05:00.

Adafruit FunHouse CO2 Monitoring

Now that our trigger is defined, we can define the automation’s action. Set the action type to “Device” and select “erv_controller” as the target device. Finally, set the action to “Turn on ERV” and save the automation.

Adafruit FunHouse CO2 Monitoring

If you prefer to create the automation by editing the yaml code as opposed to using the visual editor above, you can find the automation code below.

Copy Code
alias: ERVon
description: ''
trigger:
- platform: numeric_state
entity_id: sensor.co2
above: '1000'
for: '00:05:00'
condition: []
action:
- type: turn_on
device_id: 5d7fae794cf960b1c53a1a5f803f3af3
entity_id: switch.relay
domain: switch
mode: single

Now that our trigger is defined, we can define the automation’s action. Set the action type to “Device” and select “erv_controller” as the target device. Finally, set the action to “Turn on ERV” and save the automation.

Copy Code
alias: ERVoff
description: ''
trigger:
- platform: numeric_state
entity_id: sensor.co2
below: '700'
for: '00:05:00'
condition: []
action:
- type: turn_off
device_id: 5d7fae794cf960b1c53a1a5f803f3af3
entity_id: switch.relay
domain: switch
mode: single

Going Further

With this complete, we now have automated our ventilation system to assure we have optimal indoor air quality. This project is ripe with possibilities for additional features, so I anticipate adding VOC sensing to this in the future, along with setting the LEDs on the FunHouse board to change color according to CO2 sensor values and whether the ERV is running, among other ideas.

If you would like to learn more about CO2 level sensing and indoor air quality, Make: just published a great discussion about this with Guido Burger where they go into greater detail on the importance of indoor air quality, the differences among CO2 sensors, and various monitoring devices created. You can find this on the Make website here: https://makezine.com/2021/05/17/the-what-how-and-why-of-co-monitoring/

制造商零件编号 4985
ADAFRUIT FUNHOUSE - WIFI HOME AU
Adafruit Industries LLC
制造商零件编号 4867
ADAFRUIT SCD-30 - NDIR CO2 TEMPE
Adafruit Industries LLC
制造商零件编号 3405
HUZZAH32 ESP32 FEATHER LOOSE HDR
Adafruit Industries LLC
制造商零件编号 2895
ADAFRUIT NON-LATCHING MINI RELAY
Adafruit Industries LLC
制造商零件编号 4210
JST SH 4-PIN CABLE - QWIIC COMPA
Adafruit Industries LLC
制造商零件编号 2830
FEATHER STACKING HEADERS FML
Adafruit Industries LLC
制造商零件编号 BB-32650-G
BREADBRD DBL STRIP 70TIE-PTS GRN
Bud Industries
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum