Measuring CO2/Temp/RH/Pressure with ESP8266 version 2
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:
- More compact & uncluttered thanks to PCB
- Added one (optionally more distant) temperature sensor to prevent self-heating
- Added deep-sleep cycle to alternatively mitigate self-heating
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
- Special-built SensorThing mk2 PCB -- 5 euros with same-day shipping!
- ESP8266 WeMos D1 mini (optionally pro)
- Winsen MH-Z19B CO2 sensor
- Optional: 1.3" I2C OLED screen (for displaying values live)
- 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)
- Optional: Micro usb dip adapter/module (for bottom-side power supply, else use Wemos USB port directly)
- Optional: Male pin headers 90 degrees (for USB micro module)
- 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
Install esphome as documented here.
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.
This esphome configuration is very similar to my previous project, with some tweaks:
- Added Dallas DS18B20 sensor code
- Re-enable home assistant api for logging over wifi
- Disable web server to reduce resource usage and since it’s not necessary
- Optionally use deep sleep loop
esphome: name: esp_test_board platform: ESP8266 board: d1_mini_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" # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "Esp Living Fallback Hotspot" password: "your fallback password" captive_portal: # Enable logging logger: # 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 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: # For MH-Z19B CO2 via software serial uart: - id: myuart1 rx_pin: GPIO12 tx_pin: GPIO13 baud_rate: 9600 # For BME280 i2c: sda: GPIO4 scl: GPIO5 scan: True # For Dallas temp sensors connected to pin GPIO0 = D3, enable internal pull-up # resistor dallas: id: ds18b20_temp_sensor pin: number: GPIO0 # inverted: True mode: INPUT_PULLUP update_interval: 30s # Ensure you get *ttf files from somewhere font: - file: "slkscr.ttf" id: my_font1 size: 8 - file: "slkscr.ttf" id: my_font2 size: 16 - file: "Arial.ttf" id: my_font3 size: 16 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" sensor: - platform: wifi_signal name: "WiFi Signal" update_interval: 10s id: mywifi1 - platform: wifi_signal name: "WiFi Signal2" update_interval: 10s filters: - 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 co2: 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 temperature: 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 co2: name: "MH-Z19 CO2" id: mhz_19_co2_ema filters: - 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 temperature: name: "MH-Z19 Temperature" id: mhz_19_T_ema filters: - 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 temperature: 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 pressure: 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 humidity: 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 temperature: name: "BME280 Temperature" oversampling: 16x id: bme_280_temp_ema filters: - 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 pressure: name: "BME280 Pressure" id: bme_280_press_ema filters: - 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 humidity: name: "BME280 Humidity" id: bme_280_rh_ema filters: - 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 display: - 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));
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.
- Try to measure, model, and calibrate self-heating
- Use direct ESP8266 chips instead of modules for more compact design.
- Perhaps just give up and buy 10 euro wireless temperature sensors 😬