Tim's blah blah blah

Measuring CO2/Temp/RH/Pressure with ESP8266 version 2

(Updated: )

After experimenting a bit, I’ve made a new iteration of my ESP8266 sensor board, which now sports a PCB design (thanks all fellow Tweakers on GoT!). Some improvements:

Update 20210207 : made voltage regulator for BME280 requirement more explicit (thanks @ManS-H!)


For this project, I had the same goals as before, and wanted to optimise the design.

This guide assumes you’ve read the previous guide, so this article skips the basics such as installing esphome etc.

Bill of materials

  1. Special-built SensorThing mk2 PCB -- 5 euros with same-day shipping!
  2. ESP8266 WeMos D1 mini (optionally pro)
  3. Winsen MH-Z19B CO2 sensor
  4. Optional: 1.3" I2C OLED screen (for displaying values live)
  5. BME280 module with level converter - N.B. ensure you have a BME280 module that accepts 5V to use my print!
  6. DS18B20 temperature sensor
  7. Male pin headers straight (for modules)
  8. Optional: Micro usb dip adapter/module (for bottom-side power supply, else use Wemos USB port directly)
  9. Optional: Male pin headers 90 degrees (for USB micro module)
  10. Optional: Dupont female-female jumper wires (to increase distance of DS18B20) Since I power everything on 5V and the BME280 only accepts 1.7-3.6V as input voltage, ensure you get a BME280 module with voltage regulator, e.g. the one linked above. It should explicitly note that input voltage can be either 3.3V or 5V.

Bill of process

Software integration

Install esphome as documented here.

Hardware integration

Collect all parts

Solder male header pins onto PCB (1) for D1 mini (2), MH-Z19B (3), and OLED (4). For the MH-Z19B you can save a few headers by only connecting the necessary VCC, GND, RX and TX pins. (You could also only solder used pins for the D1 mini but I chose to connect all so I can optionally connect additional stuff via jumper wires).

For the temperature sensors BME280 (5) and DS18B20 (6), decide what mounting option you want (see above), and either connect male header pins or female header pins to the PCB. In this example I connected BME280 using male pin headers, and the DS18B20 using female pin headers.

To power the board, you can either A] solder male headers in the USB pins, and connect them with jumper wires directly, or B] solder the micro-USB dip connector to the PCB.

For A], simply solder extra male header pins which you can connect jumper wires to later. N.B. GND is left and VCC is the right pin. (B] continues below):

Next, connect the modules (note in these pictures I chose for powering via male header pins, i.e. A])

For B], solder 90 degree headers onto USB micro dip. Ensure that the pins do not protrude through the module pcb, else they might touch the MH-Z19B module. Only the outer pins (VCC and GND) matter.

Then solder the Micro USB dip module onto PCB, which doubles as a stand.

Software configuration

This esphome configuration is very similar to my previous project, with some tweaks:

  name: esp_test_board
  platform: ESP8266
  board: d1_mini_pro

  ssid: "your wifi SSID"
  password: "your wifi password"
  #fast_connect: True # Required to connect to hidden SSIDs and only with one network
  domain: ".lan"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
    ssid: "Esp Living Fallback Hotspot"
    password: "your fallback password"


# Enable logging
  # Disable UART Logging to fix MHZ-19B preamble issue when using hardware UART, see https://github.com/esphome/issues/issues/488
  # Not strictly necessary anymore with 1.14.0 https://github.com/esphome/esphome/releases/tag/v1.14.0
  # baud_rate: 0

# Enable Home Assistant API for logging over wifi.
# Disable reboot loop by setting reboot_timeout to 0s!
# See warning on https://esphome.io/components/mqtt.html
  reboot_timeout: 0s

# Disable webserver, since we push data over mqtt. Can be useful for 
# diagnostics, but takes up quite some memory
# web_server:
#   port: 80

# Optionally use deep sleep loop to reduce power consumption/self-heating.
# deep_sleep:
#   run_duration: 30s
#   sleep_duration: 150s

# Allow OTA updates

# For MH-Z19B CO2 via software serial
  - id: myuart1 
    rx_pin: GPIO12
    tx_pin: GPIO13
    baud_rate: 9600

# For BME280
  sda: GPIO4
  scl: GPIO5
  scan: True

# For Dallas temp sensors connected to pin GPIO0 = D3, enable internal pull-up
# resistor
  id: ds18b20_temp_sensor
    number: GPIO0
    # inverted: True
    mode: INPUT_PULLUP
  update_interval: 30s

# Ensure you get *ttf files from somewhere
  - file: "slkscr.ttf"
    id: my_font1
    size: 8
  - file: "slkscr.ttf"
    id: my_font2
    size: 16
  - file: "Arial.ttf"
    id: my_font3
    size: 16

  # For mobile = WAN : use FQDN, for local (IoT network - no WAN), use home IP.
  broker: ""
  #broker: home.yourhostname.org
  port: 1883
  username: "esp_board_client"
  password: "AQFCg72z5MqFihspHGbkqOj9"

  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 10s
    id: mywifi1
  - platform: wifi_signal
    name: "WiFi Signal2"
    update_interval: 10s
    - exponential_moving_average:
        alpha: 0.001 # We want ~6 hour averaging, at 10s update, that's 6*3600/10 = 2160 points, thus alpha should be 2/2160 ~ 0.001 
        send_every: 1
    id: mywifi2    
  - platform: dallas
    index: 0
    name: "DS18B20 Temperature"
    state_topic: influx/environv3/quantity/T/source/dallas1/board/esp_test_board/location/home/room/living/value/state
    id: ds18b20_temp
  - platform: mhz19
      name: "MH-Z19 CO2"
      state_topic: influx/environv3/quantity/CO2/source/mhz19b/board/esp_test_board/location/home/room/living/value/state
      id: mhz_19_co2
      name: "MH-Z19 Temperature"
      state_topic: influx/environv3/quantity/T/source/mhz19b/board/esp_test_board/location/home/room/living/value/state
      id: mhz_19_T
    update_interval: 20s
    uart_id: myuart1
  - platform: mhz19
      name: "MH-Z19 CO2"
      id: mhz_19_co2_ema
      - exponential_moving_average:
          alpha: 0.001 # We want ~12 hour averaging, at 20s update, that's 12*3600/20 = 2160 points, thus alpha should be 2/2160 ~ 0.001 
          send_every: 1
      name: "MH-Z19 Temperature"
      id: mhz_19_T_ema
      - exponential_moving_average:
          alpha: 0.001 # We want ~12 hour averaging, at 20s update, that's 12*3600/20 = 2160 points, thus alpha should be 2/2160 ~ 0.001 
          send_every: 1
    update_interval: 20s # if changed also update alpha
    uart_id: myuart1
  - platform: bme280
      name: "BME280 Temperature"
      oversampling: 16x
      id: bme_280_temp
      state_topic: influx/environv3/quantity/T/source/bme280/board/esp_test_board/location/home/room/living/value/state
      name: "BME280 Pressure"
      id: bme_280_press
      state_topic: influx/environv3/quantity/P/source/bme280/board/esp_test_board/location/home/room/living/value/state
      name: "BME280 Humidity"
      id: bme_280_rh
      state_topic: influx/environv3/quantity/RH/source/bme280/board/esp_test_board/location/home/room/living/value/state
    address: 0x76
    update_interval: 30s
  - platform: bme280
      name: "BME280 Temperature"
      oversampling: 16x
      id: bme_280_temp_ema
      - exponential_moving_average:
          alpha: 0.001 # We want ~12 hour averaging, at 20s update, that's 12*3600/20 = 2160 points, thus alpha should be 2/2160 ~ 0.001 
          send_every: 1
      name: "BME280 Pressure"
      id: bme_280_press_ema
      - exponential_moving_average:
          alpha: 0.0002 # We want ~48 hour averaging, at 20s update, that's 48*3600/20 = 8640 points, thus alpha should be 2/8640 ~ 0.0002 
          send_every: 1
      name: "BME280 Humidity"
      id: bme_280_rh_ema
      - exponential_moving_average:
          alpha: 0.001 # We want ~12 hour averaging, at 20s update, that's 12*3600/20 = 2160 points, thus alpha should be 2/2160 ~ 0.001 
          send_every: 1
    address: 0x76
    update_interval: 20s # if you change this don't forget to update alpha

  - platform: ssd1306_i2c
    model: "SH1106 128x64"
    address: 0x3C
    lambda: |-
      it.printf(0,  2, id(my_font1), "CO2");
      it.printf(0,  10, id(my_font1), "PPM");
      it.printf(24, 6, id(my_font3), "%d (%d)", int(id(mhz_19_co2).state), int(id(mhz_19_co2_ema).state));
      it.printf(0, 18, id(my_font1), "T");
      it.printf(0,  26, id(my_font1), "C");
      it.printf(24, 22, id(my_font3), "%.1f (%.1f)", id(bme_280_temp).state, id(bme_280_temp_ema).state);
      it.printf(0, 34, id(my_font1), "RH");
      it.printf(0,  42, id(my_font1), "%%");
      it.printf(24, 38, id(my_font3), "%.1f (%.1f)", (id(bme_280_rh).state), (id(bme_280_rh_ema).state));
      it.printf(0, 50, id(my_font1), "P");
      it.printf(0, 58, id(my_font1), "HPa");
      it.printf(24, 54, id(my_font3), "%d (%d)", int(id(bme_280_press).state), int(id(bme_280_press_ema).state));      

Temperature calibration

The ‘closed’ box of the previous design reported higher temperatures than the room really was, and it appeared I underestimated the heat generation of the ESP8266 SoC. To solve this, I compared self-heating in different design (variations).

Preliminary conclusions: The PCB design without OLED and the D1 mini (non-pro), (which I called ’esp_kidsroom’) seems to give the best results. The ‘closed’ box (’esp_bathroom’) gives the worst results. Adding (arguably) ugly wires to increase distance always helps. Deep sleep helps. Turning off the OLED helps a little bit. The Wemos D1 mini seems cooler than the D1 mini pro.

Looking at each design in detail:





I will expand this section once I have more time for analysis/digestion of results.

Future work

#esp8266 #smarthome