Goal of this project
This project is about building a CO2 sensor from standard modules.
- It displays the result on a cheap OLED display.
- It communicates via WIFI.
- It sends the measurements via my MQTT server to my domotica system (Domoticz).
- It has a battery inside, so it runs (for a while) without power source.
- It has a battery charging circuit and can be charged by a USB-C connector.
The hardware
The parts
- The CO2 sensor SCD41
- The CPU module ESP32-C3-SuperMini, plus extension board, plus 3.7V lithium battery.
- The display SH1106 OLED 1.3”
- A single WS2812B LED
- A 74HCT86 chip (PDF datasheet)
- A box
- Tasmota software, making use of the universal display driver, HaspMota and the Berry language.
Wiring
At first, I have been building a proto-type with Dupont wires.
Module | pin | pin | Module |
---|---|---|---|
SCD41 | GND | GND | ESP32-C3-SuperMini |
SCD41 | VDD | 3V3 | ESP32-C3-SuperMini |
SCD41 | SCL | 9 | ESP32-C3-SuperMini |
SCD41 | SDA | 8 | ESP32-C3-SuperMini |
Module | pin | pin | Module |
---|---|---|---|
SH1106 OLED | GND | GND | ESP32-C3-SuperMini |
SH1106 OLED | VDD | 3V3 | ESP32-C3-SuperMini |
SH1106 OLED | SCL | 9 | ESP32-C3-SuperMini |
SH1106 OLED | SDA | 8 | ESP32-C3-SuperMini |
Module | pin | pin | Module |
---|---|---|---|
74HCT86 | 7 GND | GND | ESP32-C3-SuperMini |
74HCT86 | 14 VDD | 5V | ESP32-C3-SuperMini |
74HCT86 | pin 1 input 1 of gate 1 | 5 | ESP32-C3-SuperMini |
74HCT86 | pin 2 input 2 of gate 1 | GND | ESP32-C3-SuperMini |
74HCT86 | pin 3 output of gate 1 | Din | WS2812 LED |
WS2812 LED | 5V | 5 | ESP32-C3-SuperMini |
WS2812 LED | GND | GND | ESP32-C3-SuperMini |
Wired by wire-wrapping and mounted in a box
I used the proto-type to develop the software (see below).
When that was done, I mounted everything in a box with hot glue, and wire-wrapped all connections.
Flashing Tasmota software
The module is a esp32-c3 SuperMini.
We need to flash Tasmota software on this module.
For flashing, we use esptool
from Espressif.
Install esptool
See Espressif esptool.
After install, verify:
michiel@Delphinus:~/development/github/esptool> which esptool.py
/home/michiel/.local/bin/esptool.py
michiel@Delphinus:~/development/github/esptool>
Connect the esp32-c3 SuperMini
Connect a data USB-C cable from your PC to the ESP32-C3.
As described at Tasmota - Getting started, we erase the flash first. No need to specify the serial port to use - we are using Linux. No need to put the device in programming mode - modern devices do that automatically.
michiel@Delphinus:~/development/github/esptool> esptool.py erase_flash
esptool.py v3.3.3
Found 2 serial ports
Serial port /dev/ttyS4
Connecting......................................
/dev/ttyS4 failed to connect: Failed to connect to Espressif device: No serial data received.
For troubleshooting steps visit: https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html
Serial port /dev/ttyACM0
Connecting...
Detecting chip type... ESP32-C3
Chip is ESP32-C3 (revision v0.4)
Features: Wi-Fi
Crystal is 40MHz
MAC: dc:da:0c:a1:77:fc
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 14.9s
Hard resetting via RTS pin...
michiel@Delphinus:~/development/github/esptool>
Looking for the Tasmota bin file and flashing
Look at the list of Tasmota releases. We look for a heading: “Factory binaries to be used for inital flashing using esptool”.
They even describe how to flash: esptool.py write_flash 0x0 tasmota32.factory.bin
.
We need the tasmota32c3.factory.bin
file, so download it and flash it:
michiel@Delphinus:~/temp> ls
tasmota32c3.factory.bin
michiel@Delphinus:~/temp> esptool.py write_flash 0x0 tasmota32c3.factory.bin
esptool.py v3.3.3
Found 2 serial ports
Serial port /dev/ttyS4
Connecting......................................
/dev/ttyS4 failed to connect: Failed to connect to Espressif device: No serial data received.
For troubleshooting steps visit: https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html
Serial port /dev/ttyACM0
Connecting...
Detecting chip type... ESP32-C3
Chip is ESP32-C3 (revision v0.4)
Features: Wi-Fi
Crystal is 40MHz
MAC: dc:da:0c:a1:77:fc
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Flash will be erased from 0x00000000 to 0x002d0fff...
Compressed 2950432 bytes to 1751355...
Wrote 2950432 bytes (1751355 compressed) at 0x00000000 in 37.8 seconds (effective 624.0 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
michiel@Delphinus:~/temp>
This took only a minute…
Use Platformio as serial monitor
michiel@Delphinus:~/temp> get_pio
(penv) michiel@Delphinus:~/temp>
(penv) michiel@Delphinus:~/temp> pio device monitor
--- Terminal on /dev/ttyACM0 | 9600 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
Does not work. Next, I created a platformio.ini file with the correct (?) baud rate. Also this does not work:
(penv) michiel@Delphinus:~/temp> pio run -t monitor
Processing esp32c3_supermini (platform: espressif32; board: esp32-c3-devkitm-1; framework: arduino)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Terminal on /dev/ttyACM0 | 460800 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, esp32_exception_decoder, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
Configuration
See the Tasmota documentation at initial-configuration.
Set the Wifi SSID and password
Follow the description on the Tasmota website to set the Wifi SSID and password.
I renamed the module to CO2SensorSCD41 in my router, and gave it a fixed lease for the IP number.
Now we have a web-based UI:
Flashing the complete software
We have to replace the “factory” sftware with the complete one.
Go to the web-UI, main menu
, then Firmware Upgrade
, and the OTA URL
is already filled in as: http://ota.tasmota.com/tasmota32/release/tasmota32c3.bin
.
However, we need the Tasmota software including LVGL and HaspMota, so we look elsewhere - this is the one: https://github.com/tasmota/install/blob/firmware/firmware/unofficial/tasmota32c3-lvgl.bin
.
Replace the URL, and press Start Upgrade
.
Templates
See the entry for the SuperMini-ESP32-C3 in the Tasmota supported hardware list.
Use this as template:
{"NAME":"SuperMini ESP32-C3","GPIO":[1,1,1,1,1,1,1,1,640,608,1,0,0,0,0,0,0,0,1,1,1,1],"FLAG":0,"BASE":1}
Set the name to CO2SensorSCD41
.
Set the friendly name to CO2 Sensor SCD41
.
Save and restart the module.
Configure the I2C bus
This configuration is already included in the template above!
We connected the I2C devices to GPIO8 as SDA and GPIO9 and SCL.
To check, go to the web-UI menu Configuration
, then Configure Template
.
The GPIO8 is I2C SDA
, and GPIO9 is I2C SCL
, both number 1.
In the main page of the web-ui, we now see:
SCD40 eCO₂ 596 ppm
SCD40 Carbon dioxide 565 ppm
SCD40 Temperature 25.0 °C
SCD40 Humidity 72.1 %
SCD40 Dew point 19.6 °C
The Tasmota software does not make the distinction between
the SCD40 and the SCD41 in the labels in the UI: it always shows SCD40.
The same applies to the SCD40/41 specific commands:
even though the single shot
command is SCD41 only, it is still named SCD40Sing
.
Configure the internal blue LED
According my previous investigations, the internal blue LED is at GPIO8. That pin is used for SDA of the I2C bus, so it flashes with any communication with the sensor.
Set the SCD41 Altitude
See description at the Tasmota SCD41 documentation.
I live at around 12m altitude, so these commands will configure the sensor:
SCD40Stop
SCD40Alt 12
SCD40Toff 400
SCD40pers
Restart the module to see the periodic measurements again!
Set the SCD41 to low power mode
Since I would like to be able to use this module with a battery, I would like to use the low power mode of the SCD41, with periodic measurements every 30s.
See the Tasmota documentation for SCD41 commands.
SCD40Stop
SCD40StLp
Restart the module to see the periodic measurements again! The web-UI of the Tasmota module now refreshes the values every 30s.
Configure the display
Install LVGL firmware for the display
We connected a SH1106 I2C OLED display, 1.3”, 128x64 pixels, blue.
Use the web-UI to install the tasmota32c3-lvgl.bin
firmware.
On the console, scan for I2C devices:
11:43:08.016 CMD: I2Cscan
11:43:08.035 RSL: RESULT = {"I2CScan":"Device(s) found on bus1 at 0x3c 0x62"}
The 0x62 is the SCD41, and the 0x3c is the display.
Setting up the display
See the Tasmota documentation for displays and display commands.
Initially, giving the command display
on the console gives:
12:20:16.858 CMD: display
12:20:16.862 RSL: RESULT = {"Display":{"Model":0,"Type":0,"Width":0,"Height":0,"Mode":0,"Dimmer":50,"Size":1,"Font":1,"Rotate":0,"Invert":0,"Refresh":2,"Cols":[16,8],"Rows":2}}
Our displaymodel is the SH1106 OLED display, which is 0x07.
But, we prefer to use the universal display driver. The Universal Display Driver or uDisplay is a way to define your display settings using a simple text file and easily add it to Tasmota. uDisplay is DisplayModel 17. The driver must be enabled by OPTION A3 on any GPIO pin.
The display itself is defined by a “descriptor” file. Many display descriptor files are included in Tasmota GitHub in tasmota/displaydesc folder. There is the file SH1106_display.ini
. We download this file, rename it to display.ini and upload it to the Tasmota file system: in the Tasmota Web-UI, goto the tools
menu, then Manage file system
.
Now, we enable the driver by setting OPTION A3 on any of the available GPIO pins.
Now, after a restart, the display
command returns:
18:50:43.763 CMD: display
18:50:43.767 RSL: RESULT = {"Display":{"Model":17,"Type":0,"Width":128,"Height":64,"Mode":0,"Dimmer":50,"Size":1,"Font":1,"Rotate":0,"Invert":1,"Refresh":2,"Cols":[16,8],"Rows":8}}
Using DisplayMode 3
In DisplayMode 3 for an OLED, the display shows Local sensors and Time/Date. So, on the console, we give:
13:01:01.654 CMD: displaymode 3
13:01:01.658 RSL: RESULT = {"DisplayMode":3}
13:03:49.593 CMD: displayrows 8
13:03:49.597 RSL: RESULT = {"DisplayRows":8}
Using HASPmota
Stop using the displaymode to display things, and invert the display colors:
14:58:16.085 CMD: displaymode 0
14:58:16.208 RSL: RESULT = {"DisplayMode":0}
15:07:28.669 CMD: displayinvert 1
15:07:28.673 RSL: RESULT = {"DisplayInvert":1}
Create a new file pages.jsonl with as content:
{"page":0,"comment":"----------------"}
{"id":10,"obj":"label","x":2,"y":2,"w":124,"text":"5000","align":1,"text_rule":"SCD40#CarbonDioxide","bg_color":"#000000","line_color":"#FFFFFF","text_rule_format":"%d","text_rule_formula":"val","text_font":"montserrat-28"}
{"id":20,"obj":"label","x":2,"y":42,"w":124,"text":"CO2","align":0,"bg_color":"#000000","line_color":"#FFFFFF"}
{"id":30,"obj":"label","x":2,"y":42,"w":124,"text":"ppm","align":2,"bg_color":"#000000","line_color":"#FFFFFF"}
Then, create an autoexec.be file containing the following:
## simple 'autoexec.be' to run HASPmota using the default 'pages.jsonl'
import haspmota
haspmota.start()
Prevent burn-in
To prevent burn-in of the text, we move the text around between the above and below:
{"id":10, ... "y":32, ...}
{"id":20, ... "y":2, ...}
{"id":30, ... "y":2, ...}
We do that by adding the following to the autoexec.be
.
So, every 15 seconds, we toggle the display contents from top to bottom and vice versa.
var burn = false
def toggle_burn()
if burn
global.p0b10.y = 2
global.p0b20.y = 42
global.p0b30.y = 42
burn = false
else
global.p0b10.y = 32
global.p0b20.y = 2
global.p0b30.y = 2
burn = true
end
end
tasmota.add_cron('*/15 * * * * *', toggle_burn, "toggle_burn")
Adding a color LED WS2812
See the Tasmota documentation for WS2812B-and-WS2813 and Berry_Addressable-LED.
I used a 74HCT86 as signal buffer, and connected a single WS2812B to GPIO05.
Rename the Web-UI button names
The buttons in the web-UI are named “Toggle 1” and “Toggle 2”.
We can change the names with the following console commands:
WebButton1 Display
WebButton2 LED
And this is what it looks like afterwards:
Testing reveals that this they are swapped, so, correction:
17:40:21.572 CMD: backlog WebButton2 Display ; WebButton1 LED
17:40:21.595 RSL: RESULT = {"WebButton2":"Display"}
17:40:21.804 RSL: RESULT = {"WebButton1":"LED"}
Set the LED color according the CO2 level
The LED should depend on the measured CO2 value.
CO2 value | LED Color | Value |
---|---|---|
<400 | off | 000000 |
400 .. 800 | Green | 00FF00 |
800 .. 1200 | Yellow/Orange | BC4000 |
>1200 | Red | FF0000 |
To test the events, we add this to the autoexec.be
:
def co2value(value, trigger, msg)
print (str(value) .. " - " .. trigger .. " - " .. msg)
end
tasmota.add_rule("SCD40#CarbonDioxide", co2value)
The returned values look like this:
18:30:13.473 465 - SCD40#CarbonDioxide - {'SCD40': {'CarbonDioxide': 465, 'Temperature': 25.7, 'Humidity': 57, 'eCO2': 489, 'DewPoint': 16.5}}
So, we can use the first value as an integer:
def co2value(value)
if (value < 400)
tasmota.cmd("led1 000000")
elif (value < 800)
tasmota.cmd("led1 00FF00")
elif (value < 1200)
tasmota.cmd("led1 BC4000")
else
tasmota.cmd("led1 FF0000")
end
end
tasmota.add_rule("SCD40#CarbonDioxide", co2value)
This last code generates much traffic on the MQTT bus for LED updates.
Communication with the outside world
Set up MQTT
See the Tasmota documentation for MQTT.
Setting the MQTT server address, enables the MQTT messages.
Setting the teleperiod
The default teleperiod is 300 seconds. That is a bit long, so we change to 30s:
20:08:19.732 CMD: teleperiod 30
20:08:19.737 MQT: stat/tasmota_A177FC/RESULT = {"TelePeriod":30}
Auto-created Tasmoticz devices
Setting the MQTT server address, causes my Tasmoticz to create 3 new devices in Domoticz:
Adding a Domoticz device for CO2
So, I created a new “Air Quality” dummy device in Domoticz. It got idx 528.
We can update the Domoticz value with MQTT. It works with this example - sending a numeric value 400 for the device with idx 528:
mosquitto_pub -h mqtt -t "domoticz/in" -m '{"idx":528,"nvalue":400}'
Type the above in the command line of your PC. You will see the dummy device in Domoticz updated to the value 400.
So, now we want to have our Tasmota module do this automatically whenever the measurement changes.
Hence, we extend the autoexec.be
with:
import mqtt
def hitdom(value)
mqtt.publish ("domoticz/in", '{ "command": "udevice", "idx" : 528, "nvalue" : ' .. str(value) .. ', "parse": false }')
end
tasmota.add_rule("Tele#SCD40#CarbonDioxide", hitdom)
Measurements got stuck after several months
I had the module measuring the CO2 in my living room for several months, until a problem appeared: the value did not update any more.
Is this caused by a millis overflow? Probably not.
The cause is unknown - so, let’s just reset the module every week by extending the autoexec.be
with:
## Reboot every week to prevent long term unstability (do we need this?):
def f()
tasmota.cmd("restart 1")
end
tasmota.add_cron("0 0 * * 0 *", f, "every_week")