- local NeoPixel-LED-Strip, via a
- Webinterface and via a
- TELEGRAM message produced by a built-in telegram bot
Hardware: Required are an eCO2-Sensor CCS811 and an ESP32 -SoC-module - preferably an M5Stack "ATOM-matrix" or "ATOM lite" with a build-in NeoPixel-strip. The Hardware components get placed on a simple grid board or a breadboard and are powered from a 5V-USB-power-supply .(5V@min500mA).
The eCO2-Sensor CCS811:
The CCS811 sensor calculates the eCO2-level by meassuring the tVOC (total Volatile Organic Compound) of exhaled air that may be "polluted" by the human lungs. It can not return the absolute CO2-level!
Additionally the sensors sensitivity changes over the time and under different environmental conditions.
This cheap type of sensor searches a relative "good air condition"-baseline by finding the "best air condition" in a longer interval and then assuming the sensor being in fresh unpolluted air at that time.
BUT: The sensor does not store this value by itself.
The sensor needs two conditions for giving reliable values:
- a one-time burn-in-time of more than 48 hours
- a minimum run-in of about 20minutes after each cold start.
The BASIC-Program was created with and runs in ANNEX32 - a BASIC interpreter for ESP32.. Minimum required version is V1.435 (Version without Bluetooth,, but with TELEGRAM-support)
- The program measures the eCO2-level once per second..
- The air eCO2 status gets categorized as GREEN, YELLOW or RED condition..
- The build-in NeoPixel-Matrix locally indicates the condition by its color.
- A Webinterface displays the eCO2-Value and the category in the (W)LAN..
Additionally a TELEGRAM-Alert is send to the last TELEGRAM-chat_id, if condition is indicated as RED .
The baseline value can be stored manually by pressing the front button of the ATOM-module.
The Telegram-BOT will respond to this commands:
- /e returns eCO2 value and category [GREEN,YELLOW,RED]
- /s stores the baseline in /baseline.txt
- /r restores the baseline from /baseline.txt
- /i returns the local IP-settings of the module
- [any other character] same as /e
To use the TELEGRAM functions in the BASIC program you first have to create your own TELEGRAM-BOT by following the instructions of BotFather bot in your telegram APP. This will provide your personal TELEGRAM_TOKEN and a bot name for the BASIC-program code that can be inserted in the BASIC-code lines to set the appropriate variables
The ANNEX32-BASIC code follows here for a better understanding of the functions.
It is also attached as a file eCO2_BOT.txt. . This file can be pasted into the editor of ANNEX32 and stored into the ESP32 module as an autorun-file in e.g. /default.bas
Code: [Local Link Removed for Guests]
'#######################################################################
' eCO2-Sensor
' ##################
'# Shows AIR QUALITY via the estimated concentration of carbon dioxide
'# calculated from known TVOC concentration. This is based on the
'# assumption that the VOC produced by humans is proportional to their exhaled CO2.
'
'# USED HARDWARE:
' - ESP32-device best: M%Stack "ATOM lite" or "ATOM matrix" with buit-in NeoPixel(s)
' - CCS811 eCO2-TVOC-Sensor at I2C-pins of the ESP32
' - switch-button (e.g. the front button of M5Stack ATOM)
'
'# OUTPUT of eCO2-ppm via ...
' - NEOPIXEL_LED with at least 1 pixel to indicate the range [GREEN|YELLOW|RED] of eCO2 ppm
' - WebInterface to display eCO2-ppm and Condition [GREEN|YELLOW|RED]
' - TELEGRAM-BOT message-on-demand with eCO2-ppm and Condition [GREEN|YELLOW|RED]
' - TELEGRAM-BOT alert message, if condition reaches RED eCO2-range for more than x measures values
'# Saves the BASELINEof the CCS811 as "GOOD-AIR-CONDITION" in file /BASELINE.txt
' if switch-button is pressed for more than 3 seconds
'# Sends a TELEGRAM Alert message to currentt CHAT_ID if RED-condition
'# TELEGRAM-BOT commands (with or without leading /) :
' /i => return local IP-Settings
' /s => store the current baseline representing "clean-air-condition" in file /baeline.txt
' /r => restore the baseline from file /baeline.txt
' /e or [any other character] will return the eCO2-ppm-value and the condition [GREEN|YELLOW|RED]
'#######################################################################
' Peter Neufeld 2021/09; DB9JG@me.com
Version$ = "V2.1"
'------SETTINGS-------------
RESTORE_BASELINE = 1 '1 => after a run-in time: restores the baseline from data file if available
SAVE_BASELINE = 0 '1 => after a run-in time: save current air conditions as the "clean air" baseline
SILENT = 0 '1 => suppress all messages 0 => show some status-messages via wlog
LOCATION$ = "ROOM 1" 'a description of the sensor location etc
TELEGRAM_activated = 1 '1 => activates the Telegram-BOT
TELEGRAM_TOKEN$ = "xxxxxxxxx:AAHQ3qTIj248QCU0ao_GcetggK3l_nhANj4"
TELEGRAM_BOTNAME$ = "@xxxxxxxx_eCO2_BOT"
TELEGRAM_MSG_Threshold = 5 'Message if bad-air-condition for too long
TELEGRAM_MAX_FAILED_COUNTER = 0
TELEGRAM_MAX_FAILED = 10
TELEGRAM_is_still_running = 0
LL_GREEN = 400 'lower limit of the range for the green LED; usually ~400
LL_Yellow = 1000 'lower limit of the range for the yellow LED; usually ~1000
LL_RED = 1800 'lower limit of the range for the red LED; usually ~1500
'---------------------------
eCO2 = 0
eTVOC = 0
CONDITION$ = "GREEN"
CONDITION_OLD$ = CONDITION$
T$ = time$
STR_eCO2$ = ""
CHAT_ID$ = "484112878" 'me
CHAT_ID$ = ""
gosub SETUP_PERIPHERAL_HARDWARE
IF RESTORE_BASELINE = 1 then gosub CCS811_RESTORE_BL_FROM_FILE
gosub MAKE_WEBPAGE
onhtmlreload MAKE_WEBPAGE
onHtmlChange MAKE_WEBPAGE
IF TELEGRAM_activated gosub TELEGRAM_INIT
timer0 1000, MAIN
wait
'####################################################################################
'####################################################################################
MAIN:
'-------------
gosub READ_CO2
gosub SHOW_CO2
T$ = time$
STR_eCO2$ = str$(eCO2)
if CONDITION$ <> CONDITION_OLD$ then gosub MAKE_WEBPAGE 'change the webpage
'===Send a telegram alert message to latest CHAT_ID if too many RED conditions-----
IF condition$ = "RED" and CHAT_ID$ <>"" then
RED_COUNT = RED_COUNT +1
else 'reset counter if good condition return before reaching threshold
RED_COUNT = 0
endif
IF RED_COUNT = TELEGRAM_MSG_Threshold then gosub TELEGRAM_send_alert
'===-------------------------------------------------------------------------------
'++++restore the latest baseline regularly after a run-in time of 10 minutes
count=(count +1) mod (10*60)
'if count = (10*60)-1 then gosub CCS811_SAVE_BASELINE_TO_FILE
if count = (10*60)-1 gosub CCS811_RESTORE_BL_FROM_FILE
'++++--------------------------------------------------------
'''
'---SAVE baseline if frontbutton pressed longer
IF pin(FRONT_BUTTON) = PRESSED then
PRESSED_COUNT = (PRESSED_COUNT + 1) mod 4
else
PRESSED_COUNT = 0
ENDIF
IF PRESSED_COUNT = 3 gosub CCS811_SAVE_BASELINE_TO_FILE
'---
return
'####################################################################################
MAKE_WEBPAGE:
'-------------
cls
autorefresh 1000
'create the textbox in the html page
A$ = ""
A$ = A$ + "<H1>eCO2 " + LOCATION$
'A$ = A$ + VERSION$
A$ = A$ + "</H1>"
A$ = A$ + "Time :" + textbox$(T$) + "<br>"
A$ = A$ + "eCO2:" + textbox$(STR_eCO2$) + "<br>"
'A$ = A$ + "Cond :" + textbox$(CONDITION$) + "<br>"
A$ = A$ + |<span style="color:| + CONDITION$ + |">|
A$ = A$ + "<H1>Condition: "+ CONDITION$+ "</H1>"
A$ = A$ + "</span>"
html A$
return
'####################################################################################
SHOW_CO2:
'---------
x=5-x 'toggle the Brigthness 0 or 20
CONDITION_OLD$=CONDITION$
select case eCO2
case 0 to LL_YELLOW 'GREEN
R=0 : G=x+20 : B=0
CONDITION$ = "GREEN"
case LL_YELLOW to LL_RED 'YELLOW
R=x+10 : G=x+10 : B=0
CONDITION$ = "YELLOW"
CASE LL_RED to 99999 'RED
R=x+20 : G=0 : B=0
CONDITION$ = "RED"
end select
neo.strip 0, NEO_NUM, R,G,B
wlog "eCO2 = "; eCO2, " Condition: "; CONDITION$
return
'####################################################################################
READ_CO2:
'---------
if ccs811.avail = 1 then
a = ccs811.read
eCO2 = CCS811.CO2
eTVOC = CCS811.TVOC
end if
return
'####################################################################################
SETUP_PERIPHERAL_HARDWARE:
'-------------------------
'I2C
i2c.setup 21, 22
'button to inizialize a save-baseline-to-file
FRONT_BUTTON = 39 'pin39 for build-in button of M5Stack ATOM xxxx
PRESSED = 0 'switch pulls up at ATOM devices
PRESSED_COUNT = 0
pin.mode FRONT_BUTTON, input
'initialize the NEOPIXELs
NEO_PIN = 27 'NeoPixel data-pin for M5stack "ATOM xxxx" devices
NEO_NUM = 1 ' Number of Neopixels for a M5stack "ATOM lite"
if bas.device = 103 then '103 is a M5Stack "ATOM matrix"
NEO_PIN = 27
NEO_NUM = 25 'Number of Neopixels
endif
R=2 : G=2 : B=2 'initial colors for neopixel
neo.setup NEO_PIN, NEO_NUM
neo.strip 0,NEO_NUM,R,G,B
'CCS811 eCO2-Sensor
if ccs811.Setup(&h5a) <> 0 then
print "CCS811 not found. Program stopped "
wlog "CCS811 not found. Program stopped "
end '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
endif
wlog ccs811.setdrivemode(1) 'update every second
return
'####################################################################################
CCS811_SAVE_BASELINE_TO_FILE:
'----------------------------
neo.strip 0,NEO_NUM,0,0,150 'Blue LED to indicate
BASELINE$ = str$(CCS811.GETBASELINE)
File.save "/BASELINE.txt", BASELINE$
wlog "Saved baseline ";BASELINE$ ; " to file /BASELINE.txt"
neo.strip 0,NEO_NUM,R,G,B 'Back to ald colors
return
'####################################################################################
CCS811_RESTORE_BL_FROM_FILE:
'---------------------------
BASELINE$ = "39103" 'default if no file
if file.exists("/BASELINE.txt") then
BASELINE$ = File.read$("/BASELINE.txt")
if val(BASELINE$) >0 then
wlog "CCS811.SETBASELINE returned: "; CCS811.SETBASELINE(val(baseline$))
wlog "Restored baseline ";BASELINE$ ; " from file /BASELINE1.txt"
endif
endif
return
'####################################################################################
TELEGRAM_INIT:
'-------------
WLOG "TELEGRAM_INIT"
telegram.settoken TELEGRAM_TOKEN$
telegram.setwait 10
telegram.setmode 0
onwgetasync TELEGRAM_asynco
'Get the update each 5 seconds
timer1 10000, TELEGRAM_getMessage
return
'####################################################################################
TELEGRAM_getMessage:
'-------------------
WLOG time$;": TELEGRAM_getMessage:"
telegram.GetUpdatesAsync
return
'####################################################################################
TELEGRAM_send_alert:
'---------------
'send an ALERT message to latest telegram chat-id
if TELEGRAM_is_still_running then return
TELEGRAM_is_still_running = 1
onwgetasync off '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
If CHAT_ID$="" then return
tt$ = "RED-ALERT condition for " + LOCATION$ +" at "+ time$ + ": eCO2 = " + str_eCO2$ + " Condition: " + CONDITION$
neo.strip 0,NEO_NUM,30,30,30 'white Neopixels to indicate a TELEGRAM transmission
WLOG telegram.sendmessage$(val(CHAT_ID$),tt$ )
WLOG tt$
neo.strip 0,NEO_NUM,R,G,B 'back to former condition
TELEGRAM_is_still_running = 0
onwgetasync TELEGRAM_asynco
return
'####################################################################################
TELEGRAM_asynco:
'---------------
'Receive the messages and respond according to included command-string
if TELEGRAM_is_still_running then return
TELEGRAM_is_still_running = 1
onwgetasync off '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
r$ = WGETRESULT$
WLOG "The TELEGRAM_BOT-Service returns at "; Time$; ": "; r$
if instr(lcase$(r$)," failed") then
TELEGRAM_MAX_FAILED_COUNTER = TELEGRAM_MAX_FAILED_COUNTER + 1
WLOG "The TELEGRAM_BOT-Service failed; REBOOT countdown initialized : ", str$(TELEGRAM_MAX_FAILED - TELEGRAM_MAX_FAILED_COUNTER)
if TELEGRAM_MAX_FAILED_COUNTER = TELEGRAM_MAX_FAILED then
WLOG "REBOOT NOW..."
reboot
endif
endif
if instr(r$,|"ok"|) then TELEGRAM_MAX_FAILED_COUNTER = 0
c$ = json$(r$, "chat.id") 'get the chat_id
if c$ <>"not found" then CHAT_ID$=c$
tt$ = LOCATION$ +" at "+ time$ + ": eCO2 = " + str_eCO2$ + " Condition: " + CONDITION$
text$ = json$(r$, "text")
if (text$ <> "not found") then
text$=replace$(text$,"/","") ' now the command may, but must not include a leading /
select case left$(lcase$(text$),1)
case "i" : tt$=" Local IP-setting is " + IP$
case "r"
gosub CCS811_RESTORE_BL_FROM_FILE
tt$="Restored the BASELINE from file"
case "s"
gosub CCS811_SAVE_BASELINE_TO_FILE
tt$="Saved the BASELINE to file"
end select
neo.strip 0,NEO_NUM,30,30,30 'white Neopixels to indicate a TELEGRAM transmission
WLOG telegram.sendmessage$(val(CHAT_ID$),tt$ )
neo.strip 0,NEO_NUM,R,G,B 'back to former condition
end if
TELEGRAM_is_still_running = 0
onwgetasync TELEGRAM_asynco
return
'####################################################################################