Tim's blah blah blah

Particulates kill, build your sensor now!

(Updated: )

One metric still missing from my home sensors (vanwerkhoven.org) is particulates (wikipedia.org), which are a known health hazard (ycombinator.com). Although I (secretly) already had a board running for a while, I wasn’t satisfied enough to share it. Today, it’s ready for sharing, including screaming headline for a change ;).

This board builds on my previous (smaller) design (vanwerkhoven.org) without particulate sensor, and also sports a custom-designed PCB (which you can order here!).

Starting with the end, it looks like:

Introduction

Building on my previous sensor projects, I’m now adding a Nova Fitness SDS011 (inovafitness.com) PM2.5/PM10 fine dust sensor, widely used & recommended by e.g. RIVM (samenmetenaanluchtkwaliteit.nl), Luftdaten (sensor.community), and EU VAQUUMS (vaquums.eu).

For basic/context/intro, please read my previous guides here (vanwerkhoven.org) and here (vanwerkhoven.org).

Bill of materials

  1. Special-built BigSensorThing PCB (tweakers.net) -- 9 euros with same-day shipping!
  2. ESP8266 WeMos D1 mini (tinytronics.nl) (do not get the pro! it’s too big)
  3. Nova Fitness SDS011 fine dust sensor (tinytronics.nl)
  4. Winsen MH-Z19B CO2 sensor (tinytronics.nl)
  5. Optional: 1.3" 128x64 OLED (tinytronics.nl) or 1.5" 128x128 OLED (tinytronics.nl) (I use a 1.5" here)
  6. BME280 module with level converter (tinytronics.nl) - N.B. ensure you have a BME280 module that accepts 5V to use my print!
  7. DS18B20 temperature sensor (tinytronics.nl)
  8. Male pin headers straight (for modules) (tinytronics.nl)
  9. Male pin headers 90 degrees (for SDS011 & optionally DS18B20) (tinytronics.nl)
  10. Optional: Dupont female-female jumper wires (to increase distance of DS18B20) (tinytronics.nl)
  11. M3 screws >20mm thread (gamma.nl) or 2x 10mm M3 afstandsbusjes (tinytronics.nl) -- you need at least 15mm clearance between the PCB and the SDS011
  12. Optional: get a hose (tinytronics.nl) to sample air from a distance

Some caveats/considerations:

Bill of process

Software integration

Install esphome as documented here (vanwerkhoven.org).

Hardware integration

Solder male header pins onto PCB for (1) D1 mini, (2) MH-Z19B, (3) OLED, (4) BME280, (5) DS18B20 and (6) SDS011 connector (use 90 deg here!)

N.B. 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).
N.B. 2 you can connect the DS18B20 on the front or on the back

Solder on modules.

Sandwitch PCB & SDS011, and wrap serial cable around / through the sandwich. Optionally (this design), connect the DS18B20 using double female Dupont wires to reduce/prevent self-heating.

This design directly powers the ESP8266 board, so no need for (micro) USB port as in previous designs.

Software configuration

Again similar to previous projects, with modification to add SDS011. Some notes:

esphome:
  name: esp_bigsensorthing
  platform: ESP8266
  board: d1_mini_pro # Change to d1_mini if you don't have a pro

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

  # reboot_timeout: 0s # Do not reboot if no wifi (can be useful for offline use)

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

logger:
  # Disable because somehow this conflicts with double serial ports of SDS011 and MH-Z19B
  # Disable UART Logging to fix MHZ-19B preamble issue when using hardware UART, see https://github.com/esphome/issues/issues/488
  # N.B. disabling logger crashes my Mac because of buggy CH34x driver, so I disable it as last setting.
  baud_rate: 0
  # level: VERBOSE

# 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
api:
  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
ota:

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


uart:
  - id: myuart0 # For SDS011 pm2.5/pm10
    rx_pin: GPIO14 # = D5
    tx_pin: GPIO12  # = D6
    baud_rate: 9600
  - id: myuart1 # For MH-Z19B CO2
    rx_pin: GPIO13 # = D7
    tx_pin: GPIO15 # = D8
    baud_rate: 9600


# For BME280 & OLED
i2c:
  sda: GPIO4 # = D2 = 4
  scl: GPIO5 # = D1 = 5
  scan: False
  frequency: 100kHz # Trying to get more stable OLED value display

# For Dallas temp sensors connected to pin GPIO0 = D3, enable internal pull-up
# resistor
dallas:
  id: ds18b20_temp_sensor
  pin:
    number: GPIO0 # D3 = 0
    # inverted: True
    mode: INPUT_PULLUP
  update_interval: 60s # if changed also update EMA alpha

sensor:
  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 60s
    id: mywifi
  - platform: uptime
    name: Uptime
    update_interval: 60s
    id: myuptime
  - platform: sds011
    pm_2_5:
      state_topic: influx/environv3/quantity/pm25/source/sds011/board/esp_bigsensorthing/location/home/room/living/value/state
      name: "SDS011 PM2.5"
      id: sds011_pm25
    pm_10_0:
      state_topic: influx/environv3/quantity/pm10/source/sds011/board/esp_bigsensorthing/location/home/room/living/value/state
      name: "SDS011 PM10"
      id: sds011_pm10
    update_interval: 10min # factory default 0min, set to 10min to save limited 8000h laser lifetime. Also reduces fan noise
    id: mysds011
    uart_id: myuart0
  - platform: dallas
    index: 0
    name: "DS18B20 Temperature"
    state_topic: influx/environv3/quantity/T/source/dallas1/board/esp_bigsensorthing/location/home/room/living/value/state
    id: ds18b20_temp
  - platform: mhz19
    co2:
      name: "MH-Z19 CO2"
      state_topic: influx/environv3/quantity/CO2/source/mhz19b/board/esp_bigsensorthing/location/home/room/living/value/state
      id: mhz_19_co2
    temperature:
      name: "MH-Z19 Temperature"
      state_topic: influx/environv3/quantity/T/source/mhz19b/board/esp_bigsensorthing/location/home/room/living/value/state
      id: mhz_19_temp
    update_interval: 60s
    uart_id: myuart1
  - platform: mhz19
    co2:
      name: "MH-Z19 CO2"
      id: mhz_19_co2_ema
      filters:
      - exponential_moving_average:
          alpha: 0.0028 # We want ~12 hour averaging, at 60s update, that's 12*3600/60 = 720points, thus alpha should be 2/1440 ~ 0.0028 
          send_every: 1
    update_interval: 60s # if changed also update alpha
    uart_id: myuart1
  - platform: bme280
    temperature:
      name: "BME280 Temperature"
      oversampling: 1x
      id: bme_280_temp
      state_topic: influx/environv3/quantity/T/source/bme280/board/esp_bigsensorthing/location/home/room/living/value/state
    pressure:
      name: "BME280 Pressure"
      id: bme_280_press
      state_topic: influx/environv3/quantity/P/source/bme280/board/esp_bigsensorthing/location/home/room/living/value/state
    humidity:
      name: "BME280 Humidity"
      id: bme_280_rh
      state_topic: influx/environv3/quantity/RH/source/bme280/board/esp_bigsensorthing/location/home/room/living/value/state
    address: 0x76
    update_interval: 60s
  - platform: bme280
    humidity:
      name: "BME280 Humidity"
      id: bme_280_rh_ema
      filters:
      - exponential_moving_average:
          alpha: 0.0028 # We want ~12 hour averaging, at 60s update, that's 12*3600/60 = 720points, thus alpha should be 2/1440 ~ 0.0028 
          send_every: 1
    address: 0x76
    update_interval: 60s # if you change this don't forget to update alpha
    
font:
  - file: "slkscr.ttf"
    id: my_font1
    size: 8
  - file: "Arial.ttf"
    id: my_font3
    size: 16
    # # I want to use ø, drop unused chars in return
    glyphs: 
      ['ø', '~', '.', '%', '(', ')', '+', '-', '_', ':', '°', '0',
      '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '/']

display:
  - platform: ssd1327_i2c
    model: "SSD1327 128x128"
    update_interval: 30s # Don't forget to set this to prevent esphome crash due to default 5s
    # reset_pin: D0
    address: 0x3C
    rotation: 180°

    # Small font, without EMA
    # lambda: |-
    #   it.printf(0, 0, id(my_font1), "CO2 (PPM): %d", int(id(mhz_19_co2).state));
    #   it.printf(0, 10, id(my_font1), "T1 (C):   %.1f", id(ds18b20_temp).state);
    #   it.printf(0, 20, id(my_font1), "T2 (C):   %.1f", id(bme_280_temp).state);
    #   it.printf(0, 30, id(my_font1), "RH (%%):  %.1f", id(bme_280_rh).state);
    #   it.printf(0, 40, id(my_font1), "P (HPa):  %d", int(id(bme_280_press).state));
    #   it.printf(0, 50, id(my_font1), "PM (2.5): %5d",  int(id(sds011_pm25).state));
    #   it.printf(0, 60, id(my_font1), "PM (10):  %5d", int(id(sds011_pm10).state));
    # Bigger font, mixed EMA      
    lambda: |-
      it.printf(0,  0, id(my_font1), "CO2");
      it.printf(0,  8, id(my_font1), "PPM");
      it.printf(24, 0, id(my_font3), "%d (~%d)", int(id(mhz_19_co2).state), int(id(mhz_19_co2_ema).state));
      
      it.printf(0, 16, id(my_font1), "T1");
      it.printf(0,  24, id(my_font1), "C");
      it.printf(24, 16, id(my_font3), "%.1f", id(ds18b20_temp).state);

      it.printf(0, 32, id(my_font1), "T2");
      it.printf(0,  40, id(my_font1), "C");
      it.printf(24, 32, id(my_font3), "%.1f", id(bme_280_temp).state);
      
      it.printf(0, 48, id(my_font1), "RH");
      it.printf(0,  56, id(my_font1), "%%");
      it.printf(24, 48, id(my_font3), "%.1f (~%.1f)", (id(bme_280_rh).state), (id(bme_280_rh_ema).state));
      
      it.printf(0, 64, id(my_font1), "P");
      it.printf(0, 72, id(my_font1), "HPa");
      it.printf(24, 64, id(my_font3), "%d", int(id(bme_280_press).state));

      it.printf(0, 80, id(my_font1), "PM");
      it.printf(0, 88, id(my_font1), "2.5");
      it.printf(24, 80, id(my_font3), "%.1f", id(sds011_pm25).state);

      it.printf(0, 96, id(my_font1), "PM");
      it.printf(0, 104, id(my_font1), "10");
      it.printf(24, 96, id(my_font3), "%.1f", id(sds011_pm10).state);      

#ESP8266 #Smarthome