Particulates kill, build your sensor now!
One metric still missing from my home sensors is particulates, which are a known health hazard. 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 without particulate sensor, and also sports a custom-designed PCB (which you can order here!).
Starting with the end, it looks like:
Building on my previous sensor projects, I’m now adding a Nova Fitness SDS011 PM2.5/PM10 fine dust sensor, widely used & recommended by e.g. RIVM, Luftdaten, and EU VAQUUMS.
For basic/context/intro, please read my previous guides here and here.
Bill of materials
- Special-built BigSensorThing PCB -- 9 euros with same-day shipping!
- ESP8266 WeMos D1 mini (do not get the pro! it’s too big)
- Nova Fitness SDS011 fine dust sensor
- Winsen MH-Z19B CO2 sensor
- Optional: 1.3" 128x64 OLED or 1.5" 128x128 OLED (I use a 1.5" here)
- BME280 module with level converter - N.B. ensure you have a BME280 module that accepts 5V to use my print!
- DS18B20 temperature sensor
- Male pin headers straight (for modules)
- Male pin headers 90 degrees (for SDS011 & optionally DS18B20)
- Optional: Dupont female-female jumper wires (to increase distance of DS18B20)
- M3 screws >20mm thread or 2x 10mm M3 afstandsbusjes -- you need at least 15mm clearance between the PCB and the SDS011
- Optional: get a hose to sample air from a distance
- Get a flexible/small USB micro cable. The Wemos/Lolin D1 Mini Pro is a bit too long to comfortably fit a USB cable at the bottom without extruding. I actually built mine with the Pro before I figured this out, and it sort of stands because I had a very flexible USB cable. Even with the regular (smaller) Wemos D1 mini, a short/flexible USB micro plug helps.
- 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
Install esphome as documented here.
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.
Again similar to previous projects, with modification to add SDS011. Some notes:
- Ensure you reduce OLED refresh rate to e.g. 30s
- I only got stable operation by disabling UART logger.
- Optionally use deep sleep loop, losing EMA
- I filter some sensors as exponential moving average (EMA) mode as well to easily get a average of the last ~12hrs. Somehow I can’t get all sensors to run like this, perhaps I maxed out the ESP8266.
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);