esp32


CO2 sensor with LCD and ESP32-C3 Supermini

I wanted to add an LCD screen to my CO2 sensor, so I bought a white LCD 1602 with an I2C controller. The I2C controller needs to be soldered to the LCD, but my basic soldering skills were sufficient for the task.

I also wanted to place it in a box, so I purchased this plastic enclosure but I cannot recommend it. It required a lot of glue from a glue gun to install the LCD and the ESP. I also had to use the soldering iron to create space for the ESP and a hole for the USB connector. I installed the plastic buttons but they are only decorative.

I made room for the sensors inside the box, but finally left them outside because they are more precise that way.

The ESP32-WROOM-32 was too large for the enclosure, so I used a ESP32-C3 Supermini with an expansion board. This is a really amazing board with a 32-bit RISC-V 160MHz microcontroller, WiFi, Bluetooh, I2C and UART. It’s not as powerful as the ESP32-WROOM-32 with a dual core 32-bit Xtensa 240Mhz, but it’s more than capable to control the sensors and the LCD.

This is the ESPHome configuration, which includes a switch to control the LCD backlight and a clock synchronizing the time with Home Assistant:

esphome:
  name: co2sensor
  friendly_name: CO2 Sensor

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: arduino

logger:
  level: ERROR

api:
  password: ""

ota:
  platform: esphome
  password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  reboot_timeout: 90s
  ap:
    ssid: ${friendly_name} Hotspot
    password: !secret wifi_password

captive_portal:

web_server:
  port: 80

debug:

time:
  - platform: homeassistant
    id: homeassistant_time
    timezone: "Europe/Madrid"

uart:
  rx_pin: GPIO20
  tx_pin: GPIO21
  baud_rate: 9600

i2c:
  sda: GPIO8
  scl: GPIO9

sensor:
  - platform: uptime
    name: Uptime
    filters:
      - lambda: return x / 60.0;
    unit_of_measurement: minutes

  - platform: wifi_signal
    name: "WiFi signal sensor"
    update_interval: 60s

  - platform: dht
    model: AM2302
    pin: GPIO10
    temperature:
      id: "dht22_temperature"
      name: "DHT22 Temperature"
    humidity:
      id: "dht22_humidity"
      name: "DHT22 Humidity"
    update_interval: 50s

  - platform: mhz19
    co2:
      id: "mhz19_co2"
      name: "MH-Z19 CO2"
    temperature:
      id: "mhz19_temperature"
      name: "MH-Z19 Temperature"
    update_interval: 60s
    automatic_baseline_calibration: false

display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    update_interval: 1s
    lambda: |-
        auto time =  id(homeassistant_time).now();
        it.printf(11, 0, "%02d:%02d", time.hour, time.minute);
        it.printf(11, 1, "%02d/%02d", time.day_of_month, time.month);
        auto co2 = id(mhz19_co2).state;
        if (!isnan(co2)) {
          it.printf(0, 0, "%.0fppm", co2);
        }
        it.printf(0, 1, "%.1fC", id(dht22_temperature).state);
        it.printf(6, 1, "%.0f%%", id(dht22_humidity).state);

switch:
  - platform: gpio
    pin: GPIO7
    name: "LED Backlight"
    id: led_backlight
    restore_mode: ALWAYS_OFF

And the full BOM:


MH-Z19B CO2 sensor

To keep a healthy environment at home or at the workplace, one of the important things to control is the carbon dioxide (CO2) level.

It’s measured in ppm (parts per million), indicating how many parts of CO2 there are in one million parts of air. As a reference:

  • Less than 1000 ppm are healthy levels
  • Between 1000 ppm and 2000 ppm, we need to reduce the CO2 levels
  • Levels greater than 2000 ppm are associated with headaches, sleepiness, poor concentration, loss of attention…

To reduce the CO2 level, we need to ventilate the room. It can be manually done (opening the windows) or it can be automated with a ventilation system.

To measure it we need a proper CO2 sensor, and one of the most reliables sensors is the MH-Z19B. It is not cheap for the Aliexpress standards (it costs around 20 EUR), but other cheap sensors announced as “air quality” sensors or “eCO2” sensors are not really measuring the CO2 level (i.e. the MQ135).

I bought this MH-Z19B from Aliexpress and hooked it to an ESP32-WROOM-32 board. This board is going to be also purposed as a temperature and humidity sensor, so I also attached a DHT22 sensor. I bought this DHT22 sensor but it is not an original one, and the measures do not seem very correct, so I ordered again an original AM2302 (=DHT22). The MH-Z19B includes a temperature sensor, but it’s mainly used for calibration and it lacks precision, as it does not report decimals. I’m also using an expansion board to simplify the connections.

The ESP32-WROOM-32 on the expansion board connected to the MH-Z19B (gold) and to the DHT22 (red)
  • The jumper in the expansion board needs to be set to 5V (because both of these sensors need 5V).
  • Connected VCC and GND of both sensors to the expansion board
  • Connected the RX and TX of the MH-Z19B to the TX and RX (GPIO1 and GPIO3) of the ESP
  • Connected GPIO16 to the DAT of the DHT22

Finally, I installed ESPHome to the board with this configuration:

substitutions:
  devicename: co2sensor
  friendly_name: CO2 sensor

esphome:
  name: ${devicename}
  friendly_name: ${friendly_name}
  platform: ESP32
  board: nodemcu-32s

logger:

api:
  password: ""

ota:
  password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  reboot_timeout: 90s
  ap:
    ssid: ${friendly_name} Hotspot
    password: !secret wifi_password

captive_portal:

web_server:
  port: 80

debug:

time:
  - platform: homeassistant
    id: homeassistant_time

uart:
  rx_pin: GPIO3
  tx_pin: GPIO1
  baud_rate: 9600

sensor:
  - platform: uptime
    name: Uptime
    filters:
      - lambda: return x / 60.0;
    unit_of_measurement: minutes

  - platform: wifi_signal
    name: "WiFi signal sensor"
    update_interval: 60s

  - platform: dht
    model: DHT22
    pin: GPIO16
    temperature:
      id: "dht22_temperature"
      name: "DHT22 Temperature"
    humidity:
      id: "dht22_humidity"
      name: "DHT22 Humidity"
    update_interval: 60s

  - platform: mhz19
    co2:
      id: "mhz19_co2"
      name: "MH-Z19 CO2"
    temperature:
      id: "mhz19_temperature"
      name: "MH-Z19 Temperature"
    update_interval: 60s
    automatic_baseline_calibration: false

And It’s working nicely, this is the ESPHome web interface:

Keeping the CO2 levels under control is helping me create a healthier workplace environment and improve performance.


Improving WiFi reception with an ESP32-WROOM-32U

I’m using a ESP32 with ESPHome connected to my heating system for climate control, as expained in a previous post.

The heating system is in a different builng than the router and I was experiencing some WiFi coverage issues (the WiFi signal needs to cross two metallic window blinds…).

To diagnose the WiFi coverage is very useful the wifi_signal sensor in ESPHome:

sensor:
  - platform: wifi_signal
    name: Wifi Signal
    update_interval: 60s

It was showing a WiFi signal of -95 dBm in the board: This is very low, and it was experiencing some disconnections.

Usually the ESP32 boards have an antenna integrated in the board, but the ESP32-WROOM-32U has an IPEX connector for an external antenna:

So, I spent less than 10 EUR in Aliexpress buying (affiliate links):

And replaced the previous ESP32-WROOM-32 module with an ESP32-WROOM-32U, installing the external antenna. This is how it looks now:

ESP32-WROOM-32U mounted on an expasion board connected to the 4 relay module

The WiFi signal shown in ESPHome increased from -95 dBm to –75 dBm and it’s no longer experiencing any interruptions.


Climate control with ESPHome and Home Assistant

Two years ago I started to need controlling my home heating system while I’m not at home. I could go the easy way and buy a couple Nest thermostats, but I preferred the DIY way.

Connecting the boiler to the ESP32 via the relay module

ESP32 board with ESPHome

I connected the boiler to a ESP-WROOM-32 board via a relay module. The box and the cables were more expensive than the board (~10 EUR) and the relay module (~5 EUR).

The ESP32 board is running ESPHome: https://esphome.io/. I think it is a very nice project and very easy to setup. All the configuration is done via YAML files. The board is connected to the home WiFi and it has a fallback hotspot.

My home heating system has two radiating floor zones with two independent pumps. I also decided to automate the boiler’s “Winter mode”, in this mode the boiler heats the water for the heating, and I wanted to disable it when the heating is not working.

ESPHome has a nice web UI

In my case I needed to activate the winter mode when any pump is working and keep it working for a period of time after the pump is off.

This is my ESPHome YAML config:

substitutions:
  devicename: heating
  friendly_name: Heating

esphome:
  name: ${devicename}
  friendly_name: ${friendly_name}
  platform: ESP32
  board: nodemcu-32s

logger:

api:
  password: ""

ota:
  platform: esphome
  password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  reboot_timeout: 90s

  ap:
    ssid: ${friendly_name} Fallback Hotspot
    password: !secret wifi_password

captive_portal:

web_server:
  port: 80

debug:

time:
  - platform: homeassistant
    id: homeassistant_time

sensor:
  - platform: uptime
    name: Uptime
    filters:
      - lambda: return x / 60.0;
    unit_of_measurement: minutes

  - platform: wifi_signal
    name: Wifi Signal
    update_interval: 60s

script:
  - id: keep_winter_mode_on
    mode: restart
    then:
      - logger.log: "Keep Winter mode start"
      - if:
          condition:
            and:
              - switch.is_off: zone1_pump
              - switch.is_off: zone2_pump
          then:
            - logger.log: "Keep Winter mode will stop"
            - delay: 15min
            - switch.turn_off: winter_mode
            - logger.log: "Keep Winter mode stopped"

  - id: zone1_pump_security
    mode: restart
    then:
      - logger.log: "Zone 1 security start"
      - delay: 60min
      - switch.turn_off: zone1_pump
      - logger.log: "Zone 1 security stop"

  - id: zone2_pump_security
    mode: restart
    then:
      - logger.log: "Zone 2 security start"
      - delay: 60min
      - switch.turn_off: zone2_pump
      - logger.log: "Zone 2 security stop"

switch:
  - platform: gpio
    pin: GPIO16
    name: "Winter mode"
    id: winter_mode
    inverted: true
    restore_mode: ALWAYS_OFF

  - platform: gpio
    pin: GPIO17
    name: "Zone 1 pump"
    id: zone1_pump
    inverted: true
    restore_mode: ALWAYS_OFF
    on_turn_on:
      then:
        - script.stop: keep_winter_mode_on
        - switch.turn_on: winter_mode
        - script.execute: zone1_pump_security
    on_turn_off:
      then:
        - script.stop: zone1_pump_security
        - script.execute: keep_winter_mode_on

  - platform: gpio
    pin: GPIO18
    name: "Zone 2 pump"
    id: zone2_pump
    inverted: true
    restore_mode: ALWAYS_OFF
    on_turn_on:
      then:
        - script.stop: keep_winter_mode_on
        - switch.turn_on: winter_mode
        - script.execute: zone2_pump_security
    on_turn_off:
      then:
        - script.stop: zone2_pump_security
        - script.execute: keep_winter_mode_on

Thermometers

To measure the temperature in the rooms, I used two Xiaomi Mi Home Bluetooth Thermometer 2 (~6 EUR each). They transmit the temperature via BLE (Bluetooth Low Energy) beacons.

Their LCD display is very convenient and, as they are battery powered, you can place them in the better part of the room. I flashed them with this custom firmware:

https://github.com/pvvx/ATC_MiThermometer

I’m still surprised by these small beasts, there are now firmwares to transform them in Zigbee:

https://devbis.github.io/telink-zigbee/.

Home Assistant

The control, reading the thermometers and activating the pumps, is done via a Home Assistant (HA) running in an old X86 tablet with Ubuntu (this is usually run in a Raspberry Pi or similar…).

I installed HA in a Docker container, this is my script to update and start the container:

#!/bin/bash
cd $(dirname $(readlink -f $0))
docker stop homeassistant
docker rm homeassistant
docker pull ghcr.io/home-assistant/home-assistant:stable
docker run -d \
	--name homeassistant \
	--privileged \
	--restart=unless-stopped \
	-e TZ=Europe/Madrid \
	-v ./config:/config \
	-v /etc/letsencrypt:/etc/letsencrypt \
	--network=host \
	ghcr.io/home-assistant/home-assistant:stable
docker image prune --all

Home assistant reads the thermometers via the Passive BLE monitor integration: https://github.com/custom-components/ble_monitor that can be easily installed via HACS (the Home Assistant Community Store). I needed a Bluetooth 5 USB adapter.

Then, I needed to setup two thermostats in HA via the config/configuration.yaml file:

climate:
  - platform: generic_thermostat
    name: "Living Room"
    unique_id: zone_1_thermostat
    heater: switch.zone_1_pump
    target_sensor: sensor.ble_temperature_living_room_thermometer
    min_temp: 15
    max_temp: 20
    ac_mode: false
    target_temp: 17
    cold_tolerance: 0.5
    hot_tolerance: 0
    min_cycle_duration:
      minutes: 30
    keep_alive:
      minutes: 5
    initial_hvac_mode: "off"
    away_temp: 15
    precision: 0.1

  - platform: generic_thermostat
    name: "Bedrooms"
    unique_id: zone_2_thermostat
    heater: switch.zone_2_pump
    target_sensor: sensor.ble_temperature_bedrooms_thermometer
    min_temp: 15
    max_temp: 20
    ac_mode: false
    target_temp: 17
    cold_tolerance: 0.5
    hot_tolerance: 0
    min_cycle_duration:
      minutes: 3
    keep_alive:
      minutes: 5
    initial_hvac_mode: "off"
    away_temp: 15
    precision: 0.1

Home Assistant doubles as temperature and humidity logger, and its easy to configure dashboards:

And, of course, now I’m using HA to control many other things at home.

Another complex parts were:

  • Making HA accessible via internet setting up a couple port redirections (one for HA and another for certbot) and a dynamic DNS service
  • Setup nginx and certbot for HTTPS
  • Connecting it to Google Home to allow receiving voice commands from my Nest Minis

But that is another long story…

The final installation if the ESP board, the relay module and the power adapter inside a box