Setting up the MH-Z14A CO2 sensor with Raspberry Pi

After feeling tired for days (yay for COVID-19 and being at home all day every day), I wanted to see if the indoor air was at fault. So I bought an an MH-Z14A sensor to get CO2-levels at home, by attaching it to a Raspberry Pi 3.

Indoor CO2-levels have pretty large impact on sleep and concentration. Usually measured in ppm (parts per million), the desired values are below 1000ppm.

CO2 levels and effects

source.
PPM Effects
250-400 normal outdoor air level
400 - 1000 indoor, good airflow
1000-2000 drowsiness, poor air
2000-5000 headaches, sleepiness, stale air
>5000 unusual air conditions, other gases
>40000 harmful due to oxygen deprivation
You'll need a CO2-sensor, a Raspberry Pi and some jumper cables.
Log into your Pi and run sudo raspi-config.
Go to 5 Interfacing Options, then P6 Serial. Select No on the login shell and Yes on the serial port hardware.

Then install pyserial for the sensor code.
sudo apt install python3-pip
pip3 install pyserial

Sensor setup

The sensor has three interfaces, analog output, PWM output and a 9600 baud 3V/5V TTL UART interface. You basically send a request and the sensor responds.
I manually rewired the cable that came with the sensor, to have female jumper DuPont wires to attach to the Pi's GPIO pins. You can also use F-M jumper wires and use the PWM interface instead.

The sensor pins are:
PWM (yellow) TXD (green) RXD (blue) VCC (red) GND (black) | AnalogOutput (white) HD (brown) and need to be attached to the Pi's pins like this:
Wiring diagram

Python code

Here's a simplified version of the code, for the full version check here.
#!/usr/bin/env python3
import serial
import time

class CO2Sensor():
  request = [0xff, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79]

  def __init__(self, port='/dev/ttyS0'):
    self.serial = serial.Serial(
        port = port,
        timeout = 1
    )

  def get(self):
    self.serial.write(bytearray(self.request))
    response = self.serial.read(9)
    if len(response) == 9:
      current_time = time.strftime('%H:%M:%S', time.localtime())
      return {"time": current_time, "ppa": (response[2] << 8) | response[3], "temp": response[4]}
    return -1

def main():
  # other Pi versions might need CO2Sensor('/dev/ttyAMA0')
  sensor = CO2Sensor()
  print(sensor.get())

if __name__ == '__main__':
  main()
Now if you run python3 filename.py, you'll get the time, the current ppm value and the current temperature.
Finished! :)
Pi and sensor