eCO2-Traffic-Light

A cheap Traffic-Light-style gas detector (ESP32 + CCS811 + 3 LEDs) that gives an indication of when to open the windows in a room with a lot of exhaled and thus possibly polluted air.
The intention of this project was to have a simple device that gives an indication of when to open the windows in a (class)room because of exhaled and possibly polluted air.
I have used a cheap CCS881 sensor in combination with an ESP32 . This sensor does not directly measure the CO2 content, but in particular the amount of particles exhaled by the human lungs. From this, the sensor indirectly calculates an eTVOC and eCO2 value.

The CCS811 requires a one-time BURN-IN time of at least 48 hours and a regular RUN-IN time of 20 minutes after start before reliable values can be output.
The sensor assumes that the lowest eCO2 value measured in a longer interval (at the same temperature and humidity) corresponds to “clean” air in an in-between well-ventilated room with about 400ppm CO2.
This highly scattering baseline value is also dependent on various environmental factors. It is determined dynamically in each case, as the sensor exhibits principle-related short-term and long-term changes in sensitivity. However, the simple and cheap sensor itself does not store this baseline value.
I have tried to take this behaviour into account in my code.
The code is written in ANNEX32, a BASIC-programming language for ESP32
Helpful links for me:
https://www.sciosense.com/products/environmental-sensors/ccs811-gas-sensor-solution/
https://sites.google.com/site/annexwifi/home
https://cicciocb.com/forum/viewtopic.php?f=8&t=51















'#######################################################################
' CO2-TRAFFIC-LIGHT
' ##################
'Shows AIR QUALITY with
' - CCS811 eCO2-TVOC-Sensor at I2C-pins of an ESP32
' - 3 LEDs (green, yellow, red to show the range of CO2 ppm
' - saves the 2 BASELINE-bytes in /BASELINE1.txt and /BASELINE2.txt
' - SAVE OF BASELINE after 20 Minutes in an unpolluted environment
' as a kind of calibration if variable BASELINE_SAVE = 1
' - AUTOMATED RESTORE OF SAVED BASELINE to avoid taking a baseline
' in polluted evir if variable RESTORE_BASELINE = 1
'to do:
' - Webinterface with CO2 ppm and TVOV ppb and settings
' - BEEP if poor quality for more than X seconds
' - eMail if poor quality for more than X seconds
'#######################################################################
' DB9JG@me.com
Version$ = "V1.4"
'------SETTINGS-------------
SAVE_BASELINE = 0 '1 => saves the two baseline-bytes to data file after 20 Minutes of run-in regularly
RESTORE_BASELINE = 1 '1 => restores the two baseline-bytes from data file after 20 Minutes of run-in regularly
SILENT = 1 '1 => suppress all messages 0 => show some status-messages via wlog
LL_GREEN = 400 'lower limit of the range for the green LED
LL_Yellow = 500 'lower limit of the range for the yellow LED
LL_RED = 600 'lower limit of the range for the red LED
'---------------------------
SDA_PIN = 21 'SDA=21 at ESP32
SCL_PIN = 22 'SCL=22 at ESP32
CCS811temp = 0
STATUS_REG = &h00
MEAS_MODE_REG = &h01
ALG_RESULT_DATA = &h02
ENV_DATA = &h05
NTC_REG = &h06
THRESHOLDS = &h10
BASELINE = &h11
HW_ID_REG = &h20
ERROR_ID_REG = &hE0
APP_START_REG = &hF4
SW_RESET = &hFF
CCS811_I2C_ADR = &h5A
GPIO_WAKE = &h5
DRIVE_MODE_IDLE = &h0
DRIVE_MODE_1SEC = &h10
DRIVE_MODE_10SEC = &h20
DRIVE_MODE_60SEC = &h30
INTERRUPT_DRIVEN = &h8
THRESHOLDS_ENABLED = &h4
BASELINE1$ = "??"
BASELINE2$ = "??"
count = 0
Dim BUFFER(10) 'I2C-Sende- / Empfangspuffer.
LED_RED = 14 'GPIOs for LEDs
LED_YELLOW = 13
LED_GREEN = 12
pin.mode LED_RED, OUTPUT
pin.mode LED_YELLOW, OUTPUT
pin.mode LED_GREEN, OUTPUT
PIN(LED_RED) = 1 'show that the device is working
PIN(LED_YELLOW) = 1 'show that the device is working
PIN(LED_GREEN) = 1 'show that the device is working
I2C.SETUP SDA_PIN, SCL_PIN ' set I2C ports
if not silent GOSUB I2C_SCANNER ' show all I2C-bus-devices
GOSUB SHOW_CCS811_STATUS ' show Status-byte
GOSUB CCS811_GO_APP_MODE 'leave BOOT-Mode, start APP-Mode
GOSUB SHOW_CCS811_STATUS
GOSUB CCS811_SET_DRIVE_MODE 'CCS811 generates eCO2 and TVOC once per second
GOSUB SHOW_CCS811_STATUS
'save baseline to files regularly after min 20Min of run-in !! IN CLEAN AIR CONDITION !!
if SAVE_BASELINE then TIMER0 (20*60000), CCS811_SAVE_BASELINE_TO_FILE
'restore baseline from files regularly after min 20Min of run-in
if RESTORE_BASELINE then TIMER0 (5*60000), CCS811_RESTORE_BL_FROM_FILE
TIMER1 1050, CCS811_SHOW 'read and show eCO2 and TVOC from CCS811 sensor
WAIT
end '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
'######################################################################
CCS811_SHOW:
GOSUB CCS811_READ_eCO2
gosub SET_CO2_TRAFFIC_LIGHT
'wlog "eCO2 = ";eCO2,"TVOC = ";TVOC
if count = 0 then
' gosub CCS811_SAVE_BASELINE_TO_FILE 'TEST ONLY---------------------
GOSUB CSS811_READ_BASELINE
wlog time$, "eCO2 = ";eCO2,"TVOC = ";TVOC ,"BASELINE: ";BASELINE1$, BASELINE2$
end if
count = (count +1) mod 30
'count = (count +1) mod 60 ' 60 * 1 Sekunden
'count = (count +1) mod 300 ' x * 1 Sekunden !!!!!TEST!!!!!!!!!!!!!!!!!
return
'######################################################################
CCS811_READ_eCO2:
' Bit 3 = Data Ready 0:no new Data 1:new Data
' Bit 0 Error dedection 0:no Error 1: Error on i2c or Sensor
'''''''''''''''''''''''''''''i2c.writeRegByte CCS811_I2C_ADR, STATUS_REG , 0
i2c.writeRegByte CCS811_I2C_ADR, STATUS_REG , 0
i2c.ReqFrom CCS811_I2C_ADR,1
CCS811temp = i2c.read
If CCS811temp and 8 then 'DATA_available
i2c.writeRegByte CCS811_I2C_ADR, ALG_RESULT_DATA ,0 'Select the Result mailbox
i2c.ReqFrom CCS811_I2C_ADR, 8
for i = 1 to 8 'The Result mailbox contains 8 Bytes
BUFFER(i) = i2c.read
next
eCO2 = 256*BUFFER(1) + BUFFER(2) 'eCO2 ppm
TVOC = 256*BUFFER(3) + BUFFER(4) 'eTVOC
else
eCO2 = 0
TVOC = 0
end if
return
'######################################################################
CSS811_READ_BASELINE:
'read 2 bytes from baseline-mailbox
if not silent wlog "Read the two baseline bytes:"
i2c.begin CCS811_I2C_ADR
I2c.write BASELINE
i2c.end
i2c.ReqFrom CCS811_I2C_ADR, 2 'Two bytes from baseline mailbox
BASELINE1$ = hex$(i2c.read)
BASELINE2$ = hex$(i2c.read)
if not silent wlog " ",BASELINE1$,BASELINE2$
return
'######################################################################
CCS811_SAVE_BASELINE_TO_FILE:
GOSUB CSS811_READ_BASELINE
File.save "/BASELINE1.txt", BASELINE1$
File.save "/BASELINE2.txt", BASELINE2$
if not silent wlog "Saved baseline-byte1 ";BASELINE1$ ; " to file /BASELINE1.txt"
if not silent wlog "Saved baseline-byte2 ";BASELINE2$ ; " to file /BASELINE2.txt"
return
'######################################################################
CCS811_RESTORE_BL_FROM_FILE:
BASELINE1$ = "09" 'default if no file
BASELINE2$ = "81" 'default if no file
'restore 2 bytes from Datafile to baseline-mailbox
if not silent wlog "Restore the two baseline bytes:"
if file.exists("/BASELINE1.txt") then BASELINE1$ = File.read$("/BASELINE1.txt")
if file.exists("/BASELINE2.txt") then BASELINE2$ = File.read$("/BASELINE2.txt")
'BASELINE1$ = "09" 'TEST
'BASELINE2$ = "81" 'TEST
i2c.begin CCS811_I2C_ADR
I2c.write BASELINE ' select baseline-mailbox
I2c.write val("&h" + BASELINE1$) ' write byte #1 to baseline-mailbox
I2c.write val("&h" + BASELINE2$) ' write byte #2 to baseline-mailbox
i2c.end
if not silent wlog "Restored baseline-byte1 ";BASELINE1$ ; " from file /BASELINE1.txt"
if not silent wlog "Restored baseline-byte2 ";BASELINE2$ ; " from file /BASELINE2.txt"
GOSUB CSS811_READ_BASELINE
return
'######################################################################
SET_CO2_TRAFFIC_LIGHT:
SELECT CASE eCO2
CASE LL_GREEN to (LL_YELLOW -1) : 'GREEN range
PIN(LED_GREEN) = 1 - PIN(LED_GREEN)
PIN(LED_RED) = 0
PIN(LED_YELLOW) = 0
CASE LL_YELLOW to (LL_RED -1) : 'YELLOW range
PIN(LED_YELLOW) = 1 - PIN(LED_YELLOW)
PIN(LED_RED) = 0
PIN(LED_GREEN) = 0
CASE LL_RED to 9999 : 'RED range
PIN(LED_RED) = 0
pause 200
PIN(LED_RED) = 1 - PIN(LED_RED)
PIN(LED_GREEN) = 0
PIN(LED_YELLOW) = 0
CASE ELSE:
PIN(LED_RED) = 1
PIN(LED_YELLOW) = 1
PIN(LED_GREEN) = 1
END SELECT
return
'######################################################################
SHOW_CCS811_STATUS:
if silent return
wlog ""
wlog "-------------CCS811-Sensor_Status:-------------"
'Read the status-byte
i2c.writeRegByte CCS811_I2C_ADR, STATUS_REG , 0
i2c.ReqFrom CCS811_I2C_ADR,1
CCS811temp = i2c.read
wlog "STATUSBYTE :",CCS811temp,"= &b"; bin$(CCS811temp)
If CCS811temp and 128 then
wlog " CCS811 is in Application Mode"
else
wlog " CCS811 is in Boot Mode"
end if
If CCS811temp and 16 then
wlog " A valid firmware is loaded"
else
wlog " NO valid firmware loaded!"
end if
If CCS811temp and 8 then
wlog " DATA is available to read"
else
wlog " NO DATA available to read"
end if
If CCS811temp and 1 then
wlog " Error on i2c or sensor!!"
'Read the ERROR-ID only if error bit is set
i2c.writeRegByte CCS811_I2C_ADR, ERROR_ID_REG , 0
i2c.ReqFrom CCS811_I2C_ADR,1
CCS811temp = i2c.read
wlog " ERROR_ID_REG : dec",CCS811temp,"= &b";bin$(CCS811temp)
else
wlog " NO Error detected"
end if
wlog "----------------------------------------"
wlog " "
RETURN
'######################################################################
CCS811_GO_APP_MODE:
if not silent wlog "Change from BOOT-Mode to App Mode"
i2c.begin CCS811_I2C_ADR
i2c.write APP_START_REG
i2c.end
return
'######################################################################
CCS811_SET_DRIVE_MODE:
'Bit7=0reserved, 'Bit 6-4=001 read data 1 per sec
'3=0 Interrupt off, 2=0 Interrupt Mode Normal, 1-0 =00 Reserved
if not silent then
wlog "Set MEAS_MODE_REG to &b:"; bin$(DRIVE_MODE_1SEC); " = read new data once per 1 second"
end if
i2c.writeRegByte CCS811_I2C_ADR, MEAS_MODE_REG, DRIVE_MODE_1SEC
return
'######################################################################
I2C_SCANNER:
'I2C Address Scanner
'wlog all addresses of the I2C-devices found
'I2C.SETUP SDA_PIN, SCL_PIN ' set I2C ports
wlog "---start-I2C-scan---"
for i = 0 to 120
i2c.begin i
if i2c.end = 0 then
wlog "Found a device at I2C-Adr dec "; i ,", hex "; hex$(i)
pause 10
end if
next i
wlog "---end-I2C-scan---"
return