ESP8266 sensing CO2 + Temp + RH + Pressure

By Atomstar on Friday 20 December 2019 16:20 - Comments (9)
Categories: ESP8266, Smarthome, Views: 3.325

I've progressed somewhat in home-integrated sensors, and I've established a baseline for a connected home-environment sensor, which I document here.

ESP8266 Lolin d1 mini pro kit back

Goal

For this sensor, my goals are:
  1. Measure indoor air quality: achieved with range CO2, temperature, relative humidity, and pressure sensors
  2. Small formfactor: achieved with small Arduino clone
  3. Extensible: achieved with ESPHome / flexible hardware
  4. Versatile data collection: achieved with ESPHome
  5. Non-cloud: achieved with ESPHome combined with RPi running Influxdb/Mosquitto broker

Options and considerations

I use the following hardware and software, alternatives are also included for comparison
  1. Board: ESP8266 Lolin D1 Mini Pro: because it's cheap, compact, enough CPU power, sufficient IO. See also my previous post
  2. CO2: Winsen MH-Z19b, reasonable price/quality ratio, measures direct CO2 (vs equivalent CO2 like AMS CSS811), see also in my previous post
  3. T/RH/P: Bosch BME280: very good price/quality ratio, see also this definitive guide
  4. Screen: 1.3" I2C 128x64 pixel OLED display, reasonable size and price, I2C is easy to work with.
  5. OS: ESPHome, similar to ESPEasy but I like it a bit better because it builds on existing platformio backbone. See also this comparison
  6. Mounting: perspex with m2/m3/m4 bolts

Integration

Optional: get familiar with ESP8266
If you've never done anything with ESP8266 yet, I recommend running the fade.ino routine on your board. See my previous post until 'test program' for instructions.
Install ESPHome
First install ESPHome. This allows us to update the board later.
  1. Get ESPHome on your computer
  2. Start ESPHome dashboard
  3. Optional: install serial port driver, e.g. for the Winchiphead CH340 (CH340C/CH340G): check instructions for your OS (outside scope of this article)
  4. Create new configuration in ESPHome dashboard (see here for instructions for Adafruit version)
  5. Upload basic config, example below
  6. You can now access (and change) your board over WiFi!

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

  # 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

# Disable Home Assistant API
# api:

web_server:
  port: 80

ota:

Assemble hardware
Once you have all parts, connect as below. Either using headers and jump wires like these (easier), or direct wire soldering (more compact).

The ESP8266 has one hardware serial (UART) port, marked RX and TX on the board, which is also used for logging and uploading firmware over USB.

Alternatively, ESPHome supports software serial where the CPU simulates a UART port on any GPIO pin. This can cause glitches, but at the low baud rates (<100k) our sensors need this is likely OK. The advantage is that you keep logging and uploading firmware over the serial (USB) port.

When connecting the serial MH-Z19B sensor to the ESP8266 board, you have two choices:
  • Recommended: use software serial port (any pins, e.g. 12 and 13), lower baud rate, and keep logging
  • Use hardware UART (pins RX and TX) and disable logging, see also this issue
  • Unfortunately it took me a while to figure out SW serial works, so several of my first boards (like the one below) use the hardware serial.
In both cases, 'RX' and 'TX' pins are always with respect to the component, i.e. TX pin on the MH-Z19B should be connected to a RX pin on the board (the sensor sends data, the board receives).
Wiring using software UART
EPS8266 Lolin D1 mini pro kit wiring v2: SW UART
Wiring using hardware UART
This is not recommended as it breaks uploading firmware via serial port until you disconnect the MH-Z19B sensor.

EPS8266 Lolin D1 mini pro kit wiring v1: HW UART
Wire example
ESP8266 Lolin d1 mini pro kit front
ESP8266 Lolin d1 mini pro kit bottom
ESP8266 Lolin d1 mini pro kit side
ESP8266 Lolin d1 mini pro kit top
Configure software
Once hardware is connected, configure the sensors as you see fit. See the extensive documentation at ESPHome.io. My example is shown below.


code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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

  # 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

# Disable Home Assistant API
# api:

web_server:
  port: 80

ota:

uart:
  # For hardware serial:
  rx_pin: GPIO3 # GPIO3 = RX hardware
  tx_pin: GPIO1 # GPIO1 = TX hardware
  # For software serial:
  rx_pin: GPIO12
  tx_pin: GPIO13
  baud_rate: 9600
  id: myuart1

i2c:
  sda: 4
  scl: 5
  scan: True

# 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: mhz19
    co2:
      name: "MH-Z19 CO2"
      state_topic: influx/environv3/quantity/CO2/source/mhz19b/board/esp_living/location/mobile/room/car2/value/state
      id: mhz_19_co2
    temperature:
      name: "MH-Z19 Temperature"
      #state_topic: influx/environv3/quantity/T/source/mhz19b/board/esp_testing/location/home/room/bathroom/value/state
      state_topic: influx/environv3/quantity/T/source/mhz19b/board/esp_living/location/mobile/room/car2/value/state
      id: mhz_19_T
    update_interval: 20s
    uart_id: myuart1
  - platform: mhz19
    co2:
      name: "MH-Z19 CO2"
      #state_topic: influx/environv3/quantity/CO2/source/mhz19b/location/testing/value/state
      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"
      #state_topic: influx/environv3/quantity/T/source/mhz19b/location/testing/value/state
      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_mobile/location/mobile/room/car/value/state
      #state_topic: influx/environv3/quantity/T/source/bme280/board/esp_mobile/location/home/room/living/value/state
      state_topic: influx/environv3/quantity/T/source/bme280/board/esp_living/location/mobile/room/car2/value/state
    pressure:
      name: "BME280 Pressure"
      id: bme_280_press
      #state_topic: influx/environv3/quantity/P/source/bme280/board/esp_mobile/location/mobile/room/car/value/state
      #state_topic: influx/environv3/quantity/P/source/bme280/board/esp_mobile/location/home/room/living/value/state
      state_topic: influx/environv3/quantity/P/source/bme280/board/esp_living/location/mobile/room/car2/value/state
    humidity:
      name: "BME280 Humidity"
      id: bme_280_rh
      # state_topic: influx/environv3/quantity/RH/source/bme280/board/esp_mobile/location/mobile/room/car/value/state
      #state_topic: influx/environv3/quantity/RH/source/bme280/board/esp_mobile/location/home/room/living/value/state
      state_topic: influx/environv3/quantity/RH/source/bme280/board/esp_living/location/mobile/room/car2/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
      # state_topic: influx/environv3/quantity/T/source/bme280/location/mobile/value/state
    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
      # state_topic: influx/environv3/quantity/P/source/bme280/location/mobile/value/state
    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
      # state_topic: influx/environv3/quantity/RH/source/bme280/location/mobile/value/state
    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));

Future work

  • Document fine dust measurements with SDS-011
  • Get AMS CCS811 sensor working
  • Improve housing options

References

  1. Atomstar's blog: Getting started with LOLIN D1 mini/DHT22 on Mac
  2. Atomstar's blog: Measuring CO2 using MH-Z19B and D1 mini pro
  3. https://www.circuits.dk/t...9-ndir-co2-sensor-module/
  4. http://www.kandrsmith.org...grometers/calib_many.html
  5. https://www.jaredwolff.co...811-vs-bme680-vs-sgp30/#!
  6. https://blog.jokielowie.c...680-oraz-ccs811-iaq-tvoc/

Volgende: Securely wiping SSDs 29-12 Securely wiping SSDs
Volgende: Flash problems to ESP8266 boards 21-11 Flash problems to ESP8266 boards

Comments


By GAEvakYD, Saturday 21 December 2019 20:17

Thanks. Ik wil al een tijdje soortgelijks maken. Aan de hand van dit boodschappenlijstje en beschrijving ga ik er eens voor zitten.

By Derecho, Sunday 22 December 2019 18:26

Very nice and elaborate post! Over the last 6 months I have spent a lot of time programming ESP32's and lately ESP8266's in combination with a range of temperature, humidity, luminosity and UV sensors, mostly in Micropython, but I never heard of the ESPhome OS, so I will give that a try sooner rather than later.

Unfortunately I am afraid you will very likely run into problems with your BME280 sensor. Despite the good results in the comparative test, I have identified several issues with this sensor during 3 months non-stop measuring, at a rate of 1 reading every 12 seconds.
- in temperatures close to freezing, the sensor shows erratic temperature readings, with jumps of about 2 degrees up and down.
-I had 1 BME280 with a soldering fault in factory, leading to shorting the sensor and burning the ESP32 on the way.
-the sensor needs proper calibration. On average it measure the humidity 7% too low (verified against 2 old-fashioned thermometers (dry and wet bulb) calibrated to WMO standards.
-Place two BME280 next to each other and get 2 different temperature readings, up to 1.5 degrees apart.

So far I seem to be getting most accurate temperature readings on the MCP9808.

By Tweakers user CurlyMo, Sunday 22 December 2019 21:24

Je weet dat de MHZ19B afgeschermd moet worden van extern infrarood licht?

By Tweakers user Iruk1981, Monday 23 December 2019 09:54

Zelf was/ ben ikook bezig zulke sensor units te bouwen. Uiteindelijk Xiaomi sensoren door het huis geplaatst, goedkoper en makkelijker.

Ik heb een aantal van die BME 280 sensoren naast elkaar aan 1 ESP32 Gehad om hun gedrag te bekijken. Ik kwam er achter dat ze temperatuur veranderingen goed aangeven, maar bij elke keer opnieuw opstarten hadden ze een nieuwe offset. Die offset kreeg ik niet weggewerkt.

Voor CO2 was ik nog aan het rondkijken. De MHZ19B ziet er interessant uit.

By Tweakers user Zwartoog, Monday 23 December 2019 23:34

Interesting project! Don't forget to calibrate the humidity and temperature settings. Especially the humidity can be way off (up to 5%-points), which may be relevant to indoor measurements. I used this guide to calibrate:
https://www.allaboutcircu...ibrate-a-humidity-sensor/

It is not easy to acquire cheap good salts. I think I used normal salt and "badzout", which were easy to obtain. You can find the saturation values for different salts online.


For temperature, I have more faith in the DS18B20 for accurate measurements. Calibration there is also straightforward: melting ice, boiling water (just put the sensor in your kettle), and use a simple fever thermometer to get an accurate reading of around 37 degrees.

CO2 is silently on my list as well...

By Tweakers user Atomstar, Sunday 29 December 2019 10:47

CurlyMo wrote on Sunday 22 December 2019 @ 21:24:
Je weet dat de MHZ19B afgeschermd moet worden van extern infrarood licht?
I vaguely remembered, but now I do ;) I assume you refer to the IR laser being used in the spectrograph. Although I have not experienced any trouble with this, I only expect this to be a problem if close to a direct IR source (direct sunlight) shining on the membrane, as the sensor itself is shielded by its metal housing.
Iruk1981 wrote on Monday 23 December 2019 @ 09:54:
Zelf was/ ben ikook bezig zulke sensor units te bouwen. Uiteindelijk Xiaomi sensoren door het huis geplaatst, goedkoper en makkelijker.
COTS hardware is probably much easier, but I like the learning experience and the versatility of these sensors as well :)

[Comment edited on Wednesday 15 January 2020 18:26]


By Tweakers user Atomstar, Sunday 29 December 2019 10:49

Derecho wrote on Sunday 22 December 2019 @ 18:26:
[...]
So far I seem to be getting most accurate temperature readings on the MCP9808.
Zwartoog wrote on Monday 23 December 2019 @ 23:34:
Interesting project! Don't forget to calibrate the humidity and temperature settings. Especially the humidity can be way off (up to 5%-points), which may be relevant to indoor measurements.
Thanks for the sensor and calibration recommendations! I was thinking of somehow getting the sensors close to a calibrated station (e.g. KNMI), but that might require building a stand-alone box and put it in a field somewhere. Consider this on my to-do list ;)

By Tweakers user Zwartoog, Wednesday 8 January 2020 15:15

Atomstar wrote on Sunday 29 December 2019 @ 10:49:

Thanks for the sensor and calibration recommendations! I was thinking of somehow getting the sensors close to a calibrated station (e.g. KNMI), but that might require building a stand-alone box and put it in a field somewhere. Consider this on my to-do list ;)
For calibration, you will need to be very close to their sensors. Pressure is easier to tune by checking a nearby calibrated station, since this is more regional. Accurate devices are very expensive, unfortunately. :| A fever thermometer the only exception.

By Dr snuggles, Saturday 8 February 2020 21:00

Thanks Atomstar, your project greatly influenced my recent build of an air quality logger. See https://forum.arduino.cc/index.php?topic=663389.0 to se it in real life.

I am very curious about your dust sensor research in the future. Please keep me informed if you don't mind.

Comment form
(required)
(required, but will not be displayed)
(optional)